Skip to content

Commit 94e9ec8

Browse files
authored
Merge branch 'develop' into fix/avoid-duplicate-mujoco-schema-registration
2 parents 3cbd836 + b486648 commit 94e9ec8

44 files changed

Lines changed: 993 additions & 256 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/run-tests/action.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,17 @@ runs:
182182
retention-days: 7
183183
compression-level: 9
184184

185+
# The reports carry the dump truncated to 10 000 chars; the whole thing per test file is here.
186+
- name: Upload hang stack dumps
187+
if: always()
188+
id: upload-hang-dumps
189+
uses: actions/upload-artifact@v7
190+
with:
191+
name: hang-dumps-${{ inputs.container-name }}
192+
path: ${{ inputs.reports-dir }}/hang-dumps/
193+
if-no-files-found: ignore
194+
retention-days: 7
195+
185196
- name: Upload JUnit XML report
186197
if: always()
187198
id: upload-junit-report

.github/actions/run-tests/run_tests.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,17 @@ run_tests() {
470470
elif docker cp "$container_name:/workspace/isaaclab/tests/ovrtx-logs" "$ovrtx_dir" 2>/dev/null; then
471471
echo "🟢 OVRTX renderer logs copied from container to $ovrtx_dir"
472472
fi
473+
474+
# Copy per-file thread stack dumps (written by tools/hang_dump.py when the runner kills a hung test).
475+
# The reports carry these truncated to 10 000 chars; the whole dump is only here. Absent unless
476+
# something hung, which is the normal case.
477+
local hang_dir="$reports_dir/hang-dumps"
478+
if [ -n "$volume_mount_source" ] && [ -d "${volume_mount_source}/tests/hang-dumps" ]; then
479+
cp -r "${volume_mount_source}/tests/hang-dumps" "$hang_dir"
480+
echo "🟢 Hang stack dumps copied to $hang_dir"
481+
elif docker cp "$container_name:/workspace/isaaclab/tests/hang-dumps" "$hang_dir" 2>/dev/null; then
482+
echo "🟢 Hang stack dumps copied from container to $hang_dir"
483+
fi
473484
echo "::endgroup::"
474485

475486
echo "::group::Cleanup"

.github/actions/warp-cache-key/collection_id.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,20 +47,25 @@ def main() -> int:
4747
warp = re.search(r'^version = "(.*)"$', packages["warp-lang"], flags=re.MULTILINE).group(1)
4848
mjwarp = re.search(r'^version = "(.*)"$', packages["mujoco-warp"], flags=re.MULTILINE).group(1)
4949
newton = packages["newton"]
50+
newton_version = re.search(r'^version = "(.*)"$', newton, flags=re.MULTILINE).group(1)
5051
except (AttributeError, KeyError) as exc:
5152
print(f"::error::{LOCKFILE} has no version entry for {exc}", file=sys.stderr)
5253
return 1
5354

54-
# Newton is a git dependency, so its resolved version is a placeholder like
55-
# 1.5.0.dev0 that does not move between revisions. Use the pinned revision.
55+
# Git builds can share a placeholder version across revisions, so identify
56+
# them by commit. Registry releases are immutable and use their version.
5657
source = re.search(r"^source = (.*)$", newton, flags=re.MULTILINE)
5758
source = source.group(1) if source else ""
5859
match = re.search(r"(?:rev=|#)([0-9a-f]{7,40})", source)
59-
if not match:
60-
print(f"::error::Could not read the pinned newton revision from {source!r}", file=sys.stderr)
60+
if match:
61+
newton_id = match.group(1)[:8]
62+
elif "registry" in source:
63+
newton_id = newton_version
64+
else:
65+
print(f"::error::Could not identify the newton package from {source!r}", file=sys.stderr)
6166
return 1
6267

63-
print(f"wp{warp}-newton{match.group(1)[:8]}-mjwarp{mjwarp}")
68+
print(f"wp{warp}-newton{newton_id}-mjwarp{mjwarp}")
6469
return 0
6570

6671

.github/workflows/check-links.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ jobs:
8585
--exclude '^file://'
8686
--exclude '^mailto:'
8787
--exclude 'localhost'
88+
--exclude 'www\.tensorflow\.org/tensorboard'
8889
--exclude '127\.0\.0\.1'
8990
--exclude 'example\.com'
9091
--exclude 'your-organization'

.github/workflows/config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,6 @@
1010
# which the CI credential can reach.
1111
isaacsim_image_name: nvcr.io/0947644777160149/internal/isaac-sim
1212
# Isaac Sim 6.1.0-alpha.50 (b86cf6ce) includes Kit 110.3.0-360924's fix for NVBug 6566677.
13-
isaacsim_image_tag: latest-develop@sha256:1bbd249c2ef8b522bc901d5321a627c750e2e7b609262c56d01b516a60bd7072
13+
isaacsim_image_tag: latest-develop@sha256:769450b03a4c634d709a0f5d4bf96e47bfaf025444b6e4728045b42733575e71
1414
isaaclab_image_name: nvcr.io/0947644777160149/internal/isaac-lab
1515
ovphysx_wheelhouse_image: ""

conftest.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@
1919
e.g. ``pytest -m unit source/isaaclab/test`` or ``pytest -m "not unit" source/isaaclab/test``.
2020
2121
Also loads ``tools/ovrtx_log.py``, which replays the OVRTX renderer log per test, so every suite that
22-
builds a renderer reports what it logged the same way.
22+
builds a renderer reports what it logged the same way, and ``tools/hang_dump.py``, which lets the CI
23+
runner ask this process for a stack dump before it kills it for hanging.
2324
"""
2425

2526
from __future__ import annotations
@@ -35,7 +36,7 @@
3536
else:
3637
wp.config.enable_backward = False
3738

38-
pytest_plugins = ["tools.ovrtx_log"]
39+
pytest_plugins = ["tools.ovrtx_log", "tools.hang_dump"]
3940

4041
JOURNAL_ENV_VAR = "ISAACLAB_TEST_JOURNAL"
4142
"""Environment variable naming the crash-journal file. Unset (the default) disables journaling."""

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ Table of Contents
101101

102102
source/concepts/backends_and_presets
103103
source/concepts/actuators
104+
source/concepts/coupled_solvers
104105

105106

106107
.. toctree::

docs/source/api/lab_contrib/isaaclab_contrib.coupling.rst

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,51 @@ isaaclab_contrib.coupling
33

44
.. automodule:: isaaclab_contrib.coupling
55

6-
.. rubric:: Classes
6+
.. currentmodule:: isaaclab_contrib.coupling
77

8-
.. autosummary::
8+
.. rubric:: Classes
99

10-
coupler_cfg.CouplerEntryCfg
11-
coupler_cfg.CouplerCfg
12-
coupler_cfg.CouplerProxyMappingCfg
13-
coupler_cfg.CouplerProxyCfg
14-
coupler_cfg.CouplerAdmmCfg
15-
coupler.NewtonCouplerManager
10+
.. autosummary::
11+
12+
CouplerEntryCfg
13+
CouplerCfg
14+
CouplerProxyMappingCfg
15+
CouplerProxyCfg
16+
CouplerAdmmCfg
17+
NewtonCouplerManager
1618

1719
Coupler Configurations
1820
----------------------
1921

20-
.. autoclass:: isaaclab_contrib.coupling.coupler_cfg.CouplerEntryCfg
22+
.. autoclass:: CouplerEntryCfg
2123
:members:
2224
:show-inheritance:
2325
:exclude-members: __init__
2426

25-
.. autoclass:: isaaclab_contrib.coupling.coupler_cfg.CouplerCfg
27+
.. autoclass:: CouplerCfg
2628
:members:
2729
:show-inheritance:
2830
:exclude-members: __init__
2931

30-
.. autoclass:: isaaclab_contrib.coupling.coupler_cfg.CouplerProxyMappingCfg
32+
.. autoclass:: CouplerProxyMappingCfg
3133
:members:
3234
:show-inheritance:
3335
:exclude-members: __init__
3436

35-
.. autoclass:: isaaclab_contrib.coupling.coupler_cfg.CouplerProxyCfg
37+
.. autoclass:: CouplerProxyCfg
3638
:members:
3739
:show-inheritance:
3840
:exclude-members: __init__
3941

40-
.. autoclass:: isaaclab_contrib.coupling.coupler_cfg.CouplerAdmmCfg
42+
.. autoclass:: CouplerAdmmCfg
4143
:members:
4244
:show-inheritance:
4345
:exclude-members: __init__
4446

4547
Newton Coupler
4648
--------------
4749

48-
.. autoclass:: isaaclab_contrib.coupling.coupler.NewtonCouplerManager
50+
.. autoclass:: NewtonCouplerManager
4951
:members:
5052
:inherited-members:
5153
:show-inheritance:
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
.. Copyright (c) 2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2+
.. All rights reserved.
3+
..
4+
.. SPDX-License-Identifier: BSD-3-Clause
5+
6+
.. _newton-coupled-solvers:
7+
8+
Coupled Solvers
9+
===============
10+
11+
.. warning::
12+
13+
Coupled solvers are experimental and exposed through
14+
:mod:`isaaclab_contrib.coupling`. Their API, behavior, feature support,
15+
performance, and implementation may change.
16+
17+
Newton can partition one model between multiple solvers and exchange state and
18+
forces between them during each simulation step. This lets a task combine
19+
solver families that target different physics, such as an MJWarp rigid robot
20+
interacting with VBD cloth or an MPM material.
21+
22+
Isaac Lab exposes this framework through
23+
:mod:`isaaclab_contrib.coupling`. The adapter turns configuration selectors
24+
into named Newton solver entries, constructs the selected coupling algorithm,
25+
and integrates it with :class:`~isaaclab_newton.physics.NewtonCfg`. For the
26+
shared-model architecture, iteration algorithms, supported constraint rows,
27+
and solver-specific implementation details, see Newton's
28+
`Coupled Solvers concept page
29+
<https://newton-physics.github.io/newton/stable/concepts/coupling.html>`_.
30+
31+
32+
The coupling model
33+
------------------
34+
35+
A coupled simulation starts from one Newton model. Instead of giving the whole
36+
model to one solver, Isaac Lab partitions it into named entries. Each entry
37+
selects a solver and owns a disjoint part of the model:
38+
39+
.. code-block:: text
40+
41+
Shared Newton model
42+
├── "rigid" entry → MJWarp owns robot bodies, joints, and shapes
43+
├── "soft" entry → VBD owns deformable particles and static shapes
44+
└── interface → proxy or ADMM coupling connects the entries
45+
46+
Each solver receives a view of the shared model and advances only the elements
47+
owned by its entry. An element can belong to at most one entry; unassigned
48+
elements remain outside the nested solvers. Keep each articulation in a single
49+
entry.
50+
51+
Isaac Lab resolves ownership selectors, constructs the Newton entry views, and
52+
runs the coupled solver through the normal Newton backend lifecycle. Newton
53+
owns the coupling algorithms and the exchange of poses, forces, and constraint
54+
information between entries.
55+
56+
57+
Choose Proxy or ADMM Coupling
58+
-----------------------------
59+
60+
.. list-table::
61+
:header-rows: 1
62+
:widths: 20 40 40
63+
64+
* - Approach
65+
- How it works
66+
- When to use it
67+
* - Proxy
68+
- A source-owned body or particle appears as a virtual endpoint in a
69+
destination solver. The destination returns feedback on a later pass or
70+
iteration.
71+
- Use when the interaction is naturally directional, such as a rigid
72+
collider inside a deformable or particle solve. Proxy coupling can
73+
reuse the destination solver's contact path and is the established path
74+
for Isaac Lab's coupled MJWarp--VBD and rigid--MPM tasks.
75+
* - ADMM
76+
- The coupler creates interface constraints between entries, iterates the
77+
sub-solvers, and applies equal and opposite interface forces.
78+
- Use when the interface should be symmetric, especially for supported
79+
cross-entry joints, body--particle attachments, or frictional contacts.
80+
ADMM has more tuning parameters and supports a narrower set of
81+
constraint rows.
82+
83+
Proxy coupling is usually the simpler starting point for collider-style
84+
rigid--deformable interaction. Use ``mode="lagged"`` first; the
85+
``"staggered"`` mode uses a newer source state but is more sensitive to the
86+
timestep and ordering. Increase coupling iterations only after each entry is
87+
stable on its own.
88+
89+
ADMM is a better fit when assigning a source and destination would make the
90+
physical interface artificially one-way. Its fixed iteration count and
91+
penalty, proximal, and stabilization parameters are part of the coupled
92+
constraint solve, so tune them together with the timestep and the participating
93+
solvers. Newton's concept page is the source of truth for the currently
94+
supported joints, contacts, and limitations.
95+
96+
Proxy coupling can have lower coupling overhead because it reuses the
97+
destination solver's contact path and may work with one pass, but its
98+
directional exchange is timestep- and ordering-sensitive. ADMM represents a
99+
symmetric interface, but every coupling iteration advances the participating
100+
solvers again. Additional passes or iterations can improve coupled response and
101+
interface convergence at a higher runtime cost. Neither approach is uniformly
102+
more accurate; compare them on task-relevant physical metrics.
103+
104+
105+
Configure a coupled solver
106+
--------------------------
107+
108+
In Isaac Lab, :class:`~isaaclab_contrib.coupling.CouplerEntryCfg` defines each
109+
entry's solver and ownership. Use
110+
:class:`~isaaclab_contrib.coupling.CouplerProxyCfg` or
111+
:class:`~isaaclab_contrib.coupling.CouplerAdmmCfg` as the
112+
:class:`~isaaclab_newton.physics.NewtonCfg` solver configuration.
113+
114+
The following configuration mirrors the maintained Franka rigid--deformable tasks. It
115+
assigns the complete robot to MJWarp, particles and static collision geometry
116+
to VBD, and exposes only the hand and fingers as VBD proxy colliders:
117+
118+
.. code-block:: python
119+
120+
from isaaclab_contrib.coupling import (
121+
CouplerEntryCfg,
122+
CouplerProxyCfg,
123+
CouplerProxyMappingCfg,
124+
)
125+
from isaaclab_newton.physics import MJWarpSolverCfg, NewtonCfg, VBDSolverCfg
126+
127+
entries = [
128+
CouplerEntryCfg(
129+
name="rigid",
130+
solver_cfg=MJWarpSolverCfg(),
131+
bodies=[r"/World/envs/env_[^/]+/Robot"],
132+
),
133+
CouplerEntryCfg(
134+
name="soft",
135+
solver_cfg=VBDSolverCfg(),
136+
all_particles=True,
137+
include_static_shapes=True,
138+
),
139+
]
140+
141+
physics = NewtonCfg(
142+
solver_cfg=CouplerProxyCfg(
143+
entries=entries,
144+
proxies=[
145+
CouplerProxyMappingCfg(
146+
source="rigid",
147+
destination="soft",
148+
bodies=[
149+
r"/World/envs/env_[^/]+/Robot/Geometry/.*panda_hand",
150+
r"/World/envs/env_[^/]+/Robot/Geometry/.*panda_(left|right)finger",
151+
],
152+
mode="lagged",
153+
)
154+
],
155+
iterations=1,
156+
),
157+
num_substeps=2,
158+
)
159+
160+
For ADMM, keep the ownership entries and replace the proxy mapping with the
161+
symmetric interfaces that should be coupled:
162+
163+
.. code-block:: python
164+
165+
from isaaclab_contrib.coupling import CouplerAdmmCfg
166+
167+
physics = NewtonCfg(
168+
solver_cfg=CouplerAdmmCfg(
169+
entries=entries,
170+
contact_pairs=[("rigid", "soft")],
171+
iterations=5,
172+
rho=1.0,
173+
),
174+
num_substeps=2,
175+
)
176+
177+
Set ``contact_pairs=None`` to generate every distinct entry pair, or use an
178+
empty list to disable ADMM contact coupling while retaining supported
179+
cross-entry joints and attachments.
180+
181+
182+
Tune Coupling
183+
-------------
184+
185+
Stabilize each entry independently before changing coupling controls.
186+
187+
* ``CouplerEntryCfg.substeps`` changes the time resolution for one entry; more
188+
substeps add solver work.
189+
* For proxy coupling, ``mode`` controls exchange ordering, ``iterations``
190+
controls relaxation passes, ``mass_scale`` changes proxy effective inertia in
191+
the destination, and ``collide_interval`` controls contact refresh frequency.
192+
* For ADMM, ``iterations`` controls interface passes, ``rho`` sets the penalty
193+
weight, ``gamma`` adds proximal inertia and velocity weighting, and
194+
``baumgarte`` adds positional-error correction.
195+
196+
More substeps or iterations can improve stability or convergence, but cost
197+
runtime and cannot repair an unstable entry. The generated
198+
:doc:`coupling configuration API
199+
</source/api/lab_contrib/isaaclab_contrib.coupling>` lists every field and
200+
default; Newton's concept page explains the underlying algorithms.
201+
202+
203+
Start from a maintained task
204+
----------------------------
205+
206+
The :ref:`newton-vbd-proxy-coupling` guide contains the complete configuration
207+
and runnable commands for the Franka soft-body tasks. Start from that example
208+
when building a proxy-coupled rigid--deformable environment, then narrow entry
209+
ownership and proxy selectors to the bodies that participate in the
210+
interaction.
211+
212+
Current Isaac Lab limitations include no support for nested couplers or Newton
213+
contact sensors, and proxy coupling supports at most two entries. Some solver
214+
modes require manager-specific lifecycle work and cannot be nested in a
215+
coupler. Validate each entry independently before tuning the coupled result,
216+
and consult the Newton concept page for current algorithm-level support and
217+
limitations.

0 commit comments

Comments
 (0)