Skip to content

[Newton] Backend-agnostic task-space accessors for IK/OSC#5400

Draft
hujc7 wants to merge 5 commits intoisaac-sim:developfrom
hujc7:jichuanh/ik-newton-compat-mvp
Draft

[Newton] Backend-agnostic task-space accessors for IK/OSC#5400
hujc7 wants to merge 5 commits intoisaac-sim:developfrom
hujc7:jichuanh/ik-newton-compat-mvp

Conversation

@hujc7
Copy link
Copy Markdown

@hujc7 hujc7 commented Apr 25, 2026

1. Summary

Make IK / OSC / RMPFlow task-space controllers backend-agnostic so Franka manipulation envs run under Newton.

The action terms previously called PhysX-only methods on asset.root_view directly, which crashed under Newton with AttributeError. The Franka reach envs worked around this by hardcoding self.sim.physics = PhysxCfg(...) with the comment "{IK,OSC} control is not supported with Newton physics; use PhysX only". This PR removes that workaround.

End-to-end result: Isaac-Reach-Franka-{IK-Abs,IK-Rel,OSC}-v0 run on either backend, picked via presets=newton.

2. Design

Three new abstract methods on BaseArticulation:

  • get_jacobians()(N, num_jacobi_bodies, 6, num_jacobi_joints)
  • get_mass_matrix()(N, num_jacobi_joints, num_jacobi_joints)
  • get_gravity_compensation_forces()(N, num_jacobi_joints)

Per-backend implementations:

Backend Jacobian Mass matrix Gravity comp
isaaclab_physx passthrough passthrough passthrough
isaaclab_newton eval_jacobian + gather kernel eval_jacobian + eval_mass_matrix + gather kernel NotImplementedError (no upstream primitive)
isaaclab_ovphysx NotImplementedError (stubs to keep class concrete) NotImplementedError NotImplementedError

3. Newton-side details

3.1 Pre-allocation for capture safety

Every buffer used at step time is pre-allocated in _create_buffers: model-sized source buffers (_jacobian_buf_flat, _mass_matrix_buf), view-sized output buffers (_jacobian_view_buf, _mass_matrix_view_buf), kernel scratch (_jacobian_joint_S_s_buf, _mass_matrix_body_I_s_buf), and the flattened view-to-model articulation index map (_jacobian_view_art_ids). This keeps get_jacobians / get_mass_matrix allocation-free and safe under CUDA graph capture.

3.2 View-level row gather

Newton's eval_jacobian / eval_mass_matrix write every articulation in the model into a single buffer (shape includes model.articulation_count), regardless of which ArticulationView invoked them. PhysX returns view-scoped data already. Two Warp kernels (gather_jacobian_rows 4-D, gather_mass_matrix_rows 3-D) gather just this view's rows into a contiguous view-sized destination so the caller-facing shape contract matches PhysX. The view-to-model index map is reused across both gathers.

3.3 Fixed-base row offset

Newton's eval_jacobian includes the fixed-root joint as row 0 (with zero motion subspace). PhysX's contract drops that row and reindexes bodies by −1 (_jacobi_body_idx = body_idx − 1). The Newton gather kernel takes a link_offset (1 for fixed-base, 0 for floating-base) so the output skips the fixed-root row and matches PhysX's body indexing.

3.4 eval_mass_matrix's "J as input" gotcha

Newton's eval_mass_matrix(state, H, J=None, body_I_s=None, joint_S_s=None) treats J as input when provided — it skips the internal eval_jacobian and uses the buffer as-is. Passing an empty pre-allocated J produces H = J^T·M·J = 0 → singular → LinAlgError in OSC's torch.inverse(mass_matrix). The wrapper explicitly populates J by calling eval_jacobian first; we reuse _jacobian_buf_flat (same shape) so no separate scratch is needed.

4. Action-term gating

OperationalSpaceControllerAction._compute_dynamic_quantities previously fetched mass matrix and gravity-compensation forces unconditionally on every step. Under the new abstraction this would still call Newton's gravity-comp stub even when the user disabled gravity compensation in the controller config.

The fetches are now gated to match what the controller actually consumes:

  • Mass matrix fetched when inertial_dynamics_decoupling=True or nullspace_control != "none" (the null-space torque term in OperationalSpaceController.compute() consumes mass matrix independently of inertial decoupling).
  • Gravity comp fetched only when gravity_compensation=True.

Side benefit: skips a per-step engine call on PhysX when neither flag is set.

5. Known limitation: gravity compensation on Newton

Newton's ArticulationView exposes eval_fk / eval_jacobian / eval_mass_matrix only — no gravity-compensation primitive. get_gravity_compensation_forces() raises NotImplementedError on Newton with a message pointing at the upstream gap. OSC users on Newton must set gravity_compensation=False (the default in the current Isaac-Reach-Franka-OSC-v0 config); other flag combinations work normally.

The gap is pinned by test_get_gravity_compensation_forces_not_implemented_on_newton, which is deliberately written to fail when upstream Newton closes the gap (pytest.raises reports DID NOT RAISE). The failure points the maintainer at the wrapper, the test, and the OSC guidance to remove.

6. Reach-env cfg cleanup

Removed the self.sim.physics = PhysxCfg(bounce_threshold_velocity=0.2) override from Isaac-Reach-Franka-{IK-Abs,IK-Rel,OSC}-v0. Tasks now inherit the parent ReachPhysicsCfg preset, so presets=newton selects NewtonCfg and presets=physx (the default) keeps the previous behavior — same bounce_threshold_velocity=0.2 lives on ReachPhysicsCfg.default = PhysxCfg(bounce_threshold_velocity=0.2). No information lost.

7. Test plan

  • test_differential_ik.py migrated to call robot.get_jacobians(). Passes on PhysX (2/2).
  • test_operational_space.py migrated to call robot.get_jacobians(), get_mass_matrix(), get_gravity_compensation_forces(). Passes on PhysX (18/18).
  • test_floating_base_osc_action_term_indexing cfg updated to enable both inertial_dynamics_decoupling=True and gravity_compensation=True, so the new flag gating doesn't short-circuit the indexing the test was written to verify.
  • test_get_gravity_compensation_forces_not_implemented_on_newton added — pins the Newton gap.
  • random_agent smoke on Isaac-Reach-Franka-IK-Abs-v0 under Newton: ran 5,205 physics substeps zero-error.
  • random_agent smoke on Isaac-Reach-Franka-IK-Rel-v0 under Newton: ran 306 substeps zero-error.
  • random_agent smoke on Isaac-Reach-Franka-OSC-v0 under Newton: ran 290 substeps zero-error.
  • Codex review pass: 4 findings (P1 ovphysx-uninstantiable, P1 fixed-root row offset, P2 nullspace mass-matrix gating, P2 mass-matrix scratch buffers) — all addressed.

8. Files

Touched four extensions, version-bumped each:

  • isaaclab 4.6.12 → 4.6.13: abstract methods, action-term gating, doc updates.
  • isaaclab_physx 0.5.22 → 0.5.23: three passthroughs.
  • isaaclab_newton 0.5.21 → 0.5.22: real wrappers + gather kernels + gravity stub + gap-pin test.
  • isaaclab_tasks 1.5.24 → 1.5.25: hardcode removals + direct-workflow caller migrations.

26 files changed, +470 / −47.

9. Status

Draft / WIP. Open questions for reviewers:

  • Naming: should _jacobian_view_art_ids be on BaseArticulation (since both backends would benefit from a view→model index map for any future gathers) or stay private to Newton?
  • Is link_offset the right contract on the gather kernel, or should the offset be baked into the destination shape and the kernel always use dst[i, l, ...] = src[id, l, ...]?
  • Should the upstream Newton issue be filed before merging this PR, so the test docstring can link to a real issue number?

Task-space controllers (IK, OSC, RMPFlow) previously called PhysX-only
methods directly on `asset.root_view` — `get_jacobians`,
`get_generalized_mass_matrices`, `get_gravity_compensation_forces`. On
Newton these crashed with AttributeError because the corresponding
view (`newton.selection.ArticulationView`) exposes a different API
(`eval_jacobian`, `eval_mass_matrix`, no gravity-compensation
primitive).

Add three abstract methods on `BaseArticulation`:

  * `get_jacobians()` — per-env spatial Jacobians.
  * `get_mass_matrix()` — per-env joint-space mass matrix.
  * `get_gravity_compensation_forces()` — per-env joint torques to hold
    against gravity.

PhysX implements all three as thin passthroughs. Newton wraps
`eval_jacobian` and `eval_mass_matrix`, with all source / output /
scratch buffers pre-allocated in `_create_buffers` (CUDA-graph-capture
safe). Two Warp gather kernels copy the rows owned by each
ArticulationView out of Newton's model-sized output, applying a
`link_offset` of 1 for fixed-base articulations to drop Newton's
fixed-root row and match the PhysX shape contract. Newton's gravity
stub raises NotImplementedError; ovphysx mirrors with stubs since it
has no view-level access at all.

Migrate every direct caller to the new accessors:

  * Action terms: DifferentialInverseKinematicsAction,
    OperationalSpaceControllerAction, RMPFlowTaskSpaceAction,
    PinkTaskSpaceAction.
  * Direct-workflow envs: factory, automate (assembly + disassembly),
    deploy events.
  * Tests: test_differential_ik, test_operational_space.

Gate OSC's per-step fetches in `_compute_dynamic_quantities`: mass
matrix only when `inertial_dynamics_decoupling` or
`nullspace_control != "none"`; gravity comp only when
`gravity_compensation`. Previously both were fetched unconditionally,
which (a) crashed on Newton even when the values were unused and
(b) was wasted work on PhysX.

Remove the `self.sim.physics = PhysxCfg(...)` workaround from
`Isaac-Reach-Franka-{IK-Abs,IK-Rel,OSC}-v0`. The previous comment
*"{IK,OSC} control is not supported with Newton physics; use PhysX
only"* no longer holds. All three tasks now inherit the
`ReachPhysicsCfg` preset, so `presets=newton` selects `NewtonCfg` and
the env runs under Newton without further overrides. The default OSC
config keeps `gravity_compensation=False` so the Newton path doesn't
hit the upstream-blocked gravity primitive.

Pin the upstream Newton gap with a regression test
(`test_get_gravity_compensation_forces_not_implemented_on_newton`)
that asserts `NotImplementedError` is raised. When upstream Newton
ships an `eval_gravity_compensation` API and the wrapper switches to
a real implementation, the test fails with `DID NOT RAISE` — directing
the maintainer to remove the stub and the OSC config-time guidance.

Tighten the floating-base OSC indexing regression test by enabling
both `inertial_dynamics_decoupling=True` and
`gravity_compensation=True` in its OSCCfg, so the new flag-gating
doesn't short-circuit the indexing checks the test was written to
verify.

Validation:
  * PhysX: test_differential_ik 2/2, test_operational_space 18/18
    (including the floating-base regression).
  * Newton: Isaac-Reach-Franka-{IK-Abs,IK-Rel,OSC}-v0 random_agent
    smokes ran zero-error through thousands of physics substeps.
  * Codex review pass (4 P1/P2 findings — all addressed).
@github-actions github-actions Bot added the isaac-lab Related to Isaac Lab team label Apr 25, 2026
Copy link
Copy Markdown

@isaaclab-review-bot isaaclab-review-bot Bot left a comment

Choose a reason for hiding this comment

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

🤖 Isaac Lab Review Bot

Summary

This PR introduces backend-agnostic task-space accessors (get_jacobians, get_mass_matrix, get_gravity_compensation_forces) on BaseArticulation to enable IK/OSC/RMPFlow controllers to work under both PhysX and Newton backends. The implementation is well-designed with proper CUDA graph capture safety, pre-allocated buffers, and correct gating of dynamic quantity fetches. The core approach is sound, though there are a few issues worth addressing.

Architecture Impact

High impact, well-contained. The changes span the articulation abstraction layer (BaseArticulation), all three backend implementations (PhysX, Newton, ovphysx), the task-space action terms, and downstream task configs. The Newton implementation introduces new gather kernels that extract view-specific rows from model-sized buffers. The OSC action term now conditionally fetches mass matrix and gravity compensation based on controller config flags, which is the correct pattern for backend compatibility.

Implementation Verdict

Minor fixes needed — One potential correctness issue with the mass matrix gating logic and a few smaller concerns.

Test Coverage

  • test_get_gravity_compensation_forces_not_implemented_on_newton pins the known Newton gap
  • test_operational_space.py updated to enable gravity_compensation=True for floating-base test
  • ⚠️ Missing: No direct unit test for get_jacobians() or get_mass_matrix() on Newton verifying shape/value correctness against PhysX
  • ⚠️ Missing: No test exercising OSC with inertial_dynamics_decoupling=False and nullspace_control="position" to verify the new gating logic

CI Status

No CI checks available yet — recommend waiting for CI before merge.

Findings

🔴 Critical: source/isaaclab/isaaclab/envs/mdp/actions/task_space_actions.py:659-663 — Mass matrix gating may skip required data for nullspace control

The condition needs_mass_matrix = self.cfg.controller_cfg.inertial_dynamics_decoupling or (self.cfg.controller_cfg.nullspace_control != "none") is correct, but the _mass_matrix tensor is initialized to zeros in __init__ (line 409) and never cleared between steps. If a user toggles config at runtime (unlikely but possible), or if the controller internally assumes the mass matrix is always fresh, stale zeros could cause issues. More importantly, if needs_mass_matrix is False, _mass_matrix remains zeros and gets passed to self._osc.compute() at line 538. The OSC controller's compute() method receives mass_matrix unconditionally — verify the controller handles zero mass matrices gracefully when neither flag is set.

# Line 538 always passes self._mass_matrix regardless of gating
self._joint_efforts[:] = self._osc.compute(
    ...
    mass_matrix=self._mass_matrix,  # Could be zeros if not fetched
    ...
)

🟡 Warning: source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py:226-240 — Redundant jacobian evaluation in get_mass_matrix

When get_jacobians() and get_mass_matrix() are called in the same step (common for OSC), eval_jacobian is called twice on the same state. The comment acknowledges this ("any earlier get_jacobians this step is overwritten with identical data"), but this is still a performance overhead. Consider caching the jacobian evaluation per-state or adding a combined accessor.

🟡 Warning: source/isaaclab_newton/isaaclab_newton/assets/articulation/kernels.py:581 — gather_jacobian_rows uses int instead of wp.int32

def gather_jacobian_rows(
    ...
    link_offset: int,  # Should be wp.int32 for Warp kernel type consistency
    ...
):

While Warp may implicitly handle Python int, using wp.int32 would be more consistent with other kernel signatures in the codebase.

🟡 Warning: source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py:3320-3325 — Jacobian view buffer shape uses max_links from model

The Newton implementation uses model.max_joints_per_articulation for max_links, but the comment at line 3320 says "Source buffer covers every articulation in the model." For scenes with multiple articulation types (e.g., Franka + mobile base), max_links and max_dofs are model-wide maximums. This is correct for the source buffer, but the view buffer at line 3340 should ideally use this view's actual link/dof count rather than model maximums to avoid over-allocation. This isn't a bug (the gather kernel respects bounds), but wastes memory.

🔵 Improvement: source/isaaclab/isaaclab/envs/mdp/actions/task_space_actions.py:654-668 — Consider caching needs_mass_matrix flag

The needs_mass_matrix computation happens every step in _compute_dynamic_quantities(). Since self.cfg.controller_cfg is immutable after init, this boolean could be computed once in __init__ and stored as self._needs_mass_matrix and self._needs_gravity.

🔵 Improvement: source/isaaclab_newton/test/assets/test_articulation.py:2604-2640 — Test should also verify jacobian/mass_matrix shapes

The new test only checks get_gravity_compensation_forces raises NotImplementedError. Consider adding companion tests that verify get_jacobians() and get_mass_matrix() return tensors with the expected shapes (num_instances, num_jacobi_bodies, 6, num_jacobi_joints) and (num_instances, num_jacobi_joints, num_jacobi_joints) respectively, matching the PhysX contract.

🔵 Improvement: source/isaaclab_physx/isaaclab_physx/assets/articulation/articulation.py:182-190 — PhysX method naming inconsistency

The base class defines get_mass_matrix() but PhysX's underlying view method is get_generalized_mass_matrices() (plural). The passthrough is correct, but a brief comment noting this naming difference would help maintainability:

def get_mass_matrix(self) -> wp.array:
    # PhysX view uses plural "matrices" naming convention
    return self._root_view.get_generalized_mass_matrices()

@hujc7
Copy link
Copy Markdown
Author

hujc7 commented Apr 26, 2026

@greptileai review

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

This PR introduces three abstract methods on BaseArticulation (get_jacobians, get_mass_matrix, get_gravity_compensation_forces) and routes all task-space controller calls (IK, OSC, RMPFlow) through them, replacing direct root_view PhysX-only calls. Newton gets real Warp gather-kernel implementations backed by pre-allocated model-sized buffers for CUDA-graph safety; ovphysx gets NotImplementedError stubs; the Franka reach env configs drop their PhysX-hardcode workaround. The design is sound and the OSC fetch-gating (_needs_mass_matrix, _needs_gravity) correctly avoids calling unimplemented Newton primitives when they aren't needed.

Confidence Score: 5/5

Safe to merge; only P2 documentation and naming nits found, no correctness or runtime issues.

All changes are well-tested (shape contracts for both fixed/floating-base and heterogeneous scenes, xfail test pinning the Newton gap, smoke runs on Newton). The fetch-gating logic is correct, the kernel gather patterns are sound, and the PhysX passthroughs are trivial. Remaining findings are a docstring shape mismatch and a confusing variable name — neither affects runtime behavior.

source/isaaclab_newton/isaaclab_newton/assets/articulation/kernels.py (docstring), source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py (max_links naming), source/isaaclab/isaaclab/envs/mdp/actions/pink_task_space_actions.py (PhysX +6 offset comment)

Important Files Changed

Filename Overview
source/isaaclab/isaaclab/assets/articulation/base_articulation.py Adds three abstract methods (get_jacobians, get_mass_matrix, get_gravity_compensation_forces) with clear docstrings and shape contracts; no issues found.
source/isaaclab/isaaclab/envs/mdp/actions/task_space_actions.py Migrates direct root_view Jacobian/mass-matrix calls to abstract accessors; adds _needs_mass_matrix/_needs_gravity gating to avoid unconditional backend calls — logic is correct and symmetric.
source/isaaclab_newton/isaaclab_newton/assets/articulation/articulation.py Real Newton wrappers for get_jacobians/get_mass_matrix with pre-allocated model-sized buffers and gather kernels; max_links naming is confusing (uses max_joints_per_articulation), but functionally correct per tests.
source/isaaclab_newton/isaaclab_newton/assets/articulation/kernels.py Two new Warp gather kernels (gather_jacobian_rows, gather_mass_matrix_rows); gather_mass_matrix_rows dst docstring incorrectly states max_dofs shape instead of num_joints.
source/isaaclab_physx/isaaclab_physx/assets/articulation/articulation.py Thin passthroughs to existing root_view methods; naming aligned with abstract API (get_mass_matrix wraps get_generalized_mass_matrices).
source/isaaclab_ovphysx/isaaclab_ovphysx/assets/articulation/articulation.py Stub NotImplementedError implementations that keep the class concrete; straightforward.
source/isaaclab_newton/test/assets/test_articulation.py Comprehensive shape-contract tests for get_jacobians/get_mass_matrix (fixed/floating-base, heterogeneous scene); xfail test correctly pins the gravity-comp gap with strict=True.
source/isaaclab_physx/test/assets/test_articulation.py PhysX reference shape tests mirroring Newton tests; floating-base expected shape correctly uses num_joints+6 to reflect PhysX's virtual DOF convention.
source/isaaclab/isaaclab/envs/mdp/actions/pink_task_space_actions.py Fixed-base path now builds zeros directly (avoiding Newton crash); floating-base path applies PhysX +6 offset to abstract gravity-comp output without noting the backend-specific assumption.
source/isaaclab/isaaclab/envs/mdp/actions/rmpflow_task_space_actions.py Single-line migration from root_view.get_jacobians() to asset.get_jacobians(); no issues.
source/isaaclab_tasks/isaaclab_tasks/direct/automate/assembly_env.py Migrated root_view Jacobian and mass-matrix calls to abstract accessors; hardcoded body index offset (-1) remains correct since get_jacobians already drops the root row.
source/isaaclab_tasks/isaaclab_tasks/manager_based/manipulation/reach/config/franka/osc_env_cfg.py Removes PhysX hardcode override; task now inherits backend from parent preset.

Class Diagram

%%{init: {'theme': 'neutral'}}%%
classDiagram
    class BaseArticulation {
        <<abstract>>
        +get_jacobians() wp.array
        +get_mass_matrix() wp.array
        +get_gravity_compensation_forces() wp.array
    }
    class PhysXArticulation {
        +get_jacobians() wp.array
        +get_mass_matrix() wp.array
        +get_gravity_compensation_forces() wp.array
    }
    class NewtonArticulation {
        -_jacobian_buf_flat wp.array
        -_jacobian_buf wp.array4d
        -_mass_matrix_buf wp.array
        -_jacobian_view_buf wp.array4d
        -_mass_matrix_view_buf wp.array
        -_jacobian_view_art_ids wp.array
        +get_jacobians() wp.array
        +get_mass_matrix() wp.array
        +get_gravity_compensation_forces() NotImplementedError
    }
    class OvPhysXArticulation {
        +get_jacobians() NotImplementedError
        +get_mass_matrix() NotImplementedError
        +get_gravity_compensation_forces() NotImplementedError
    }
    class OSCAction {
        -_needs_mass_matrix bool
        -_needs_gravity bool
        +_compute_dynamic_quantities()
    }
    BaseArticulation <|-- PhysXArticulation
    BaseArticulation <|-- NewtonArticulation
    BaseArticulation <|-- OvPhysXArticulation
    OSCAction --> BaseArticulation : get_jacobians / get_mass_matrix / get_gravity_compensation_forces
Loading

Reviews (4): Last reviewed commit: "Add shape-contract regression tests for ..." | Re-trigger Greptile

Comment on lines 330 to 333
if self._asset.is_fixed_base:
gravity = torch.zeros_like(
wp.to_torch(self._asset.root_view.get_gravity_compensation_forces())[
:, self._controlled_joint_ids_tensor
]
wp.to_torch(self._asset.get_gravity_compensation_forces())[:, self._controlled_joint_ids_tensor]
)
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.

P1 Newton crash: unnecessary get_gravity_compensation_forces() call on fixed-base

_apply_gravity_compensation calls get_gravity_compensation_forces() on a fixed-base robot solely to get the tensor shape for torch.zeros_like. On Newton, this immediately raises NotImplementedError, so any fixed-base robot using PinkInverseKinematicsAction with gravity not disabled (rigid_props.disable_gravity=False) will crash at runtime — even though the intended result is all zeros and the underlying value is never used.

Suggested change
if self._asset.is_fixed_base:
gravity = torch.zeros_like(
wp.to_torch(self._asset.root_view.get_gravity_compensation_forces())[
:, self._controlled_joint_ids_tensor
]
wp.to_torch(self._asset.get_gravity_compensation_forces())[:, self._controlled_joint_ids_tensor]
)
if self._asset.is_fixed_base:
gravity = torch.zeros(
(self._asset.num_instances, len(self._controlled_joint_ids_tensor)),
device=self.device,
)

Comment on lines +336 to 338
gravity = wp.to_torch(self._asset.get_gravity_compensation_forces())[
:, self._controlled_joint_ids_tensor + self._physx_floating_joint_indices_offset
]
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.

P2 PhysX-specific index offset applied to future Newton data

_physx_floating_joint_indices_offset embeds a PhysX convention (6 virtual free-floating DOFs prepended to gravity-comp output). When Newton eventually implements get_gravity_compensation_forces(), its output likely won't include those 6 virtual DOFs — so applying this offset here would silently read the wrong joints. Consider adding a comment or an assertion that ties this indexing to the PhysX backend, so a future implementor knows to revisit it.

* pink_task_space_actions.py: fixed-base gravity-comp path now constructs
  the zeros tensor directly. Previously called get_gravity_compensation_forces()
  solely to feed torch.zeros_like for shape, which crashes on Newton.

* task_space_actions.py: cache _needs_mass_matrix / _needs_gravity at init
  (controller cfg is immutable). Pass None into OSC.compute() when those
  flags are False instead of forwarding the stale-zero buffer; the
  controller's existing None-checks then raise immediately on any
  misconfiguration rather than silently operating on zeros.

* Newton gather_jacobian_rows: type link_offset as wp.int32 to match the
  kernel-signature convention used elsewhere in this module.

* test_get_gravity_compensation_forces_not_implemented_on_newton: switch
  from pytest.raises to pytest.mark.xfail(strict=True, raises=...). Same
  contract (test fails when Newton ships the primitive), cleaner CI
  semantics — pytest reports XFAIL/XPASS instead of looking like a real
  failure when the gap is closed.

* PhysX passthrough: comment noting that get_generalized_mass_matrices
  (plural) is PhysX's view-side name; the singular IsaacLab API
  (get_mass_matrix) hides the difference.
@hujc7
Copy link
Copy Markdown
Author

hujc7 commented Apr 27, 2026

@greptileai review

* Newton get_jacobians / get_mass_matrix output buffers were sized using
  ``model.max_joints_per_articulation`` and ``model.max_dofs_per_articulation``
  — the model-wide maxima. In a homogeneous scene this matched per-asset
  counts, but in a heterogeneous scene where another articulation type has
  more joints/DoFs, this view's returned tensor would carry zero-padded
  rows/cols that don't belong to it. The padded mass-matrix was singular
  under torch.inverse. Output buffers now use ``self.num_bodies`` /
  ``self.num_joints`` so the returned shape always matches THIS view's
  asset, restoring the cross-backend contract. Source and scratch
  buffers stay model-sized — the kernel needs that to match Newton's
  writes.

* Bump isaaclab_ovphysx 0.1.1 -> 0.1.2 with a CHANGELOG entry covering
  the new BaseArticulation.get_* stubs added in the previous commit.
  Per the per-extension changelog/version rule.
@hujc7
Copy link
Copy Markdown
Author

hujc7 commented Apr 27, 2026

@greptileai review

Pin the public shape contract for ``get_jacobians`` and
``get_mass_matrix`` so future regressions fail at test time instead of
in production.

* Newton: 5 new tests in test_articulation.py
  - test_get_jacobians_shape_fixed_base — Franka fixed-base, locks
    (N, num_bodies-1, 6, num_joints) and the link_offset=1 fix.
  - test_get_mass_matrix_shape_and_nonsingular_fixed_base — Franka
    fixed-base, locks (N, num_joints, num_joints) and that the matrix
    has positive diagonals (a padded zero row would surface here).
  - test_get_jacobians_shape_floating_base — Anymal-C floating-base,
    locks (N, num_bodies, 6, num_joints) and link_offset=0 path.
  - test_get_mass_matrix_shape_floating_base — Anymal-C, mass-matrix
    shape on floating-base.
  - test_heterogeneous_scene_per_view_shapes — co-resident Franka +
    Anymal-C in one Newton model. Direct regression test for the
    Codex round-2 finding: each view's output uses its own
    num_bodies/num_joints, NOT model.max_*. ``num_per_type=1`` keeps
    the test off Newton's pre-existing actuator-default replication
    quirk in multi-instance multi-type scenes.

* PhysX: 3 new tests in test_articulation.py
  - Mirror the Franka fixed-base + Anymal floating-base shape tests
    so the cross-backend contract is locked at both ends. The
    heterogeneous case is Newton-specific (PhysX views are
    view-scoped at the engine level), so no PhysX-side equivalent.

Mass-matrix non-singularity check uses positive-diagonal rather than
``torch.linalg.det()``: a real Franka mass matrix has det~1e-13 by
numerical conditioning even when SPD, so the determinant threshold
would false-fail. Padded zero rows (the bug we want to catch) show up
clearly as zero diagonals.
@hujc7
Copy link
Copy Markdown
Author

hujc7 commented Apr 27, 2026

@greptileai review

* BaseArticulation.get_jacobians / get_mass_matrix /
  get_gravity_compensation_forces are now concrete with
  ``raise NotImplementedError`` bodies instead of ``@abstractmethod``.
  Out-of-tree subclasses no longer break at instantiation when these
  accessors are added — they fail only when the methods are actually
  invoked, matching AGENTS.md's deprecation policy. The in-tree
  PhysX / Newton / ovphysx subclasses still override explicitly.

* BaseArticulation.get_jacobians docstring documents the floating-base
  joint-dim convention difference between PhysX (prepends 6 virtual
  DoFs to the joint dim, ``num_joints`` counts only actuated) and
  Newton (``ArticulationView.joint_dof_count`` already includes the
  base 6, so ``num_joints`` is the total). The total joint-dim is
  identical on both backends; only how ``num_joints`` is reported
  differs. Floating-base task-space callers should verify their joint
  indexing against the active backend's DoF ordering. No floating-base
  IK/OSC env exists today so no caller is affected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

isaac-lab Related to Isaac Lab team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant