Skip to content

Fix for body_iquat randomization #147#685

Merged
eric-heiden merged 9 commits into
newton-physics:mainfrom
adenzler-nvidia:dev/adenzler/randomize-body-iquat
Aug 29, 2025
Merged

Fix for body_iquat randomization #147#685
eric-heiden merged 9 commits into
newton-physics:mainfrom
adenzler-nvidia:dev/adenzler/randomize-body-iquat

Conversation

@adenzler-nvidia

@adenzler-nvidia adenzler-nvidia commented Aug 29, 2025

Copy link
Copy Markdown
Member

Now that the body frame is correct, setting this up is pretty simple.

Inspired by mjuu_fullinertia which is the function that MuJoCo uses in the compiler to go from matrix to diagonal + principal axes.

Before your PR is "Ready for review"

  • Necessary tests have been added and new examples are tested (see newton/tests/test_examples.py)
  • Documentation is up-to-date
  • Code passes formatting and linting checks with pre-commit run -a

Summary by CodeRabbit

  • New Features

    • Orientation quaternions are now derived from inertia tensors via eigen-decomposition, improving accuracy for non-diagonal inertias.
    • Per-world body orientation quaternions are included in expanded model data.
  • Bug Fixes

    • More robust handling and normalization of inertia-derived orientations with consistent component ordering.
  • Refactor

    • Simplified inertia update flow by removing reliance on externally provided body orientations.
  • Tests

    • Enhanced tests generate full SPD inertias with off-diagonals, verify eigenvalues and orientation quaternions against MuJoCo (including post-step checks) and ensure float32 consistency.

Signed-off-by: Alain Denzler <adenzler@nvidia.com>
Signed-off-by: Alain Denzler <adenzler@nvidia.com>
Signed-off-by: Alain Denzler <adenzler@nvidia.com>
Signed-off-by: Alain Denzler <adenzler@nvidia.com>
@coderabbitai

coderabbitai Bot commented Aug 29, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Kernel now derives per-body orientation from the inertia tensor via eigen-decomposition, removing body_quat and up_axis from the kernel signature; call site and model field expansion updated. Tests switched to full SPD 3x3 inertias and validate eigenvalues and reconstructed quaternions against MuJoCo before and after stepping.

Changes

Cohort / File(s) Summary of Changes
MuJoCo solver kernel and call site
newton/_src/solvers/mujoco/solver_mujoco.py
Simplified update_body_inertia_kernel signature (removed body_quat, up_axis). Kernel now eigendecomposes body_inertia, sorts eigenvalues/eigenvectors descending, builds rotation matrix from eigenvectors, computes/normalizes quaternion (converted XYZW→WXYZ), and writes to body_iquat_out. Call site updated to stop passing removed args and body_iquat added to expanded model fields.
Tests for inertia randomization and verification
newton/tests/test_mujoco_solver.py
Replace diagonal-only inertia generation with full SPD 3x3 matrices produced by random rotations of sorted principal moments (float32). Use wp.eig3 to obtain eigenvalues/eigenvectors, reorder to descending, reconstruct normalized quaternion from eigenbasis, align conventions with MuJoCo body_iquat, and compare eigenvalues and orientations before and after stepping.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Test as Test Suite
    participant Solver as update_model_inertial_properties
    participant Kernel as update_body_inertia_kernel
    participant Model as MuJoCo Model Buffers

    Test->>Solver: request inertial update
    Solver->>Kernel: body_inertia, bodies_per_env, body_mapping
    rect rgb(230,245,255)
    note right of Kernel: Derive orientation from inertia
    Kernel->>Kernel: eigendecompose inertia → (vals, vecs)
    Kernel->>Kernel: sort vals/vecs (descending)
    Kernel->>Kernel: R = matrix_from_sorted_vecs
    Kernel->>Kernel: q = normalize(quat_from_matrix(R))\nconvert XYZW → WXYZ
    end
    Kernel-->>Solver: body_inertia_out, body_iquat_out
    Solver->>Model: write inertia and iquat fields
    Test->>Model: read back for verification
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • eric-heiden
  • jvonmuralt

📜 Recent review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 86649fc and e468cae.

📒 Files selected for processing (2)
  • newton/_src/solvers/mujoco/solver_mujoco.py (3 hunks)
  • newton/tests/test_mujoco_solver.py (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T15:56:26.587Z
Learnt from: adenzler-nvidia
PR: newton-physics/newton#552
File: newton/_src/solvers/mujoco/solver_mujoco.py:0-0
Timestamp: 2025-08-18T15:56:26.587Z
Learning: In Newton's MuJoCo solver, when transforming joint axes from Newton's internal frame to MuJoCo's expected frame, use wp.quat_rotate(joint_rot, axis) not wp.quat_rotate_inv(joint_rot, axis). The joint_rot represents rotation from joint-local to body frame, so forward rotation is correct.

Applied to files:

  • newton/tests/test_mujoco_solver.py
  • newton/_src/solvers/mujoco/solver_mujoco.py
🧬 Code graph analysis (1)
newton/tests/test_mujoco_solver.py (1)
newton/_src/sim/builder.py (1)
  • body_count (518-522)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
🔇 Additional comments (5)
newton/_src/solvers/mujoco/solver_mujoco.py (2)

906-911: Quaternion convention conversion and storage look correct.

xyzw→wxyz conversion and write to body_iquat_out align with MuJoCo.


2369-2371: Good: expand body_iquat across worlds.

Including "body_iquat" in model_fields_to_expand is necessary for per-world inertials.

newton/tests/test_mujoco_solver.py (3)

386-400: Solid SPD inertia generator.

Right‑handed Q via det check and SPD construction are correct.


401-412: LGTM: deterministic per‑env parameters.

Keeps tests reproducible and varied across envs.


482-490: LGTM: updated SPD inertias mirror initial generation.

Keeps constraints and dtype consistent.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
newton/_src/solvers/mujoco/solver_mujoco.py (1)

879-908: Fix eigenbasis handedness and canonicalize quaternion sign to avoid nondeterministic body_iquat.

As-is, wp.eig3 returns eigenvectors up to arbitrary sign; without enforcing a right‑handed basis and a consistent quaternion sign, body_iquat can flip between q and −q across runs (and when eigenvalues are close), causing test flakiness and orientation mismatches.

Apply the following minimal, branch-light fixes after sorting:

@@
-    # Convert eigenvectors to quaternion (xyzw format)
-    q = wp.quat_from_matrix(wp.matrix_from_cols(eigenvectors[0], eigenvectors[1], eigenvectors[2]))
-    q = wp.normalize(q)
+    # Ensure right-handed eigenbasis (det(R) > 0) before forming quaternion
+    v0 = eigenvectors[0]
+    v1 = eigenvectors[1]
+    v2 = eigenvectors[2]
+    # flip last axis if needed so that cross(v0, v1)·v2 > 0
+    if wp.dot(wp.cross(v0, v1), v2) < 0.0:
+        eigenvectors[2] = -eigenvectors[2]
+
+    # Convert eigenvectors to quaternion (xyzw format)
+    R = wp.matrix_from_cols(eigenvectors[0], eigenvectors[1], eigenvectors[2])
+    q = wp.quat_from_matrix(R)
+    q = wp.normalize(q)
+    # Canonicalize sign for determinism (prefer w >= 0 in xyzw)
+    if q[3] < 0.0:
+        q = -q
@@
-    # Convert from xyzw to wxyz format
+    # Convert from xyzw to wxyz format
     q = wp.quat(q[1], q[2], q[3], q[0])
@@
-    body_inertia_out[worldid, mjc_idx] = eigenvalues
+    body_inertia_out[worldid, mjc_idx] = eigenvalues
     body_iquat_out[worldid, mjc_idx] = q
🧹 Nitpick comments (1)
newton/tests/test_mujoco_solver.py (1)

472-485: Updated inertias are diagonal-only; summary claims SPD with off-diagonals.

Tests still pass, but this weakens coverage for orientation updates after notify_model_changed, and contradicts the PR summary.

Consider mirroring the initial SPD construction (with off-diagonals and PD fix) for updated_inertias:

-        updated_inertias = np.zeros((self.model.body_count, 3, 3), dtype=np.float32)
-        for i in range(self.model.body_count):
-            env_idx = i // bodies_per_env
-            if env_idx == 0:
-                a = np.float32(2.5 + self.rng.uniform(0.0, 0.5))
-                b = np.float32(3.5 + self.rng.uniform(0.0, 0.5))
-                c = np.float32(min(a + b - 0.1, 4.5))
-                updated_inertias[i] = np.diag([a, b, c]).astype(np.float32)
-            else:
-                a = np.float32(3.5 + self.rng.uniform(0.0, 0.5))
-                b = np.float32(4.5 + self.rng.uniform(0.0, 0.5))
-                c = np.float32(min(a + b - 0.1, 5.5))
-                updated_inertias[i] = np.diag([a, b, c]).astype(np.float32)
+        updated_inertias = np.zeros((self.model.body_count, 3, 3), dtype=np.float32)
+        for i in range(self.model.body_count):
+            env_idx = i // bodies_per_env
+            if env_idx == 0:
+                a_base, b_base, c_max = 2.5, 3.5, 4.5
+            else:
+                a_base, b_base, c_max = 3.5, 4.5, 5.5
+            a = np.float32(a_base + self.rng.uniform(0.0, 0.5))
+            b = np.float32(b_base + self.rng.uniform(0.0, 0.5))
+            c = np.float32(min(a + b - 0.1, c_max))
+            ab = np.float32(self.rng.uniform(-0.2, 0.2))
+            ac = np.float32(self.rng.uniform(-0.2, 0.2))
+            bc = np.float32(self.rng.uniform(-0.2, 0.2))
+            inertia = np.array([[a, ab, ac], [ab, b, bc], [ac, bc, c]], dtype=np.float32)
+            eigvals = np.linalg.eigvalsh(inertia)
+            if np.any(eigvals <= 0):
+                inertia += np.eye(3, dtype=np.float32) * (np.abs(np.min(eigvals)) + 0.1)
+            updated_inertias[i] = inertia
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a943c90 and bab6965.

📒 Files selected for processing (2)
  • newton/_src/solvers/mujoco/solver_mujoco.py (3 hunks)
  • newton/tests/test_mujoco_solver.py (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: adenzler-nvidia
PR: newton-physics/newton#552
File: newton/_src/solvers/mujoco/solver_mujoco.py:0-0
Timestamp: 2025-08-18T15:56:26.587Z
Learning: In Newton's MuJoCo solver, when transforming joint axes from Newton's internal frame to MuJoCo's expected frame, use wp.quat_rotate(joint_rot, axis) not wp.quat_rotate_inv(joint_rot, axis). The joint_rot represents rotation from joint-local to body frame, so forward rotation is correct.
📚 Learning: 2025-08-18T15:56:26.587Z
Learnt from: adenzler-nvidia
PR: newton-physics/newton#552
File: newton/_src/solvers/mujoco/solver_mujoco.py:0-0
Timestamp: 2025-08-18T15:56:26.587Z
Learning: In Newton's MuJoCo solver, when transforming joint axes from Newton's internal frame to MuJoCo's expected frame, use wp.quat_rotate(joint_rot, axis) not wp.quat_rotate_inv(joint_rot, axis). The joint_rot represents rotation from joint-local to body frame, so forward rotation is correct.

Applied to files:

  • newton/_src/solvers/mujoco/solver_mujoco.py
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (2)
newton/_src/solvers/mujoco/solver_mujoco.py (1)

2367-2368: Including body_iquat in model expansion is correct.

Required for per-world inertial orientations when nworld > 1. LGTM.

newton/tests/test_mujoco_solver.py (1)

386-406: SPD inertia generation looks good.

Float32 throughout, symmetric off-diagonals, and PD adjustment via eigvals guard are appropriate. LGTM.

Comment thread newton/tests/test_mujoco_solver.py
Signed-off-by: Alain Denzler <adenzler@nvidia.com>
@adenzler-nvidia adenzler-nvidia marked this pull request as draft August 29, 2025 12:14

@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.

Actionable comments posted: 0

♻️ Duplicate comments (1)
newton/tests/test_mujoco_solver.py (1)

420-447: Fix eigenvector reordering and make quaternion comparison sign-invariant.

  • Reversing rows (newton_eigvecs[::-1]) is incorrect; reorder columns with the eigenvalues.
  • Use NumPy’s eigh for symmetric matrices on host.
  • Enforce right-handed basis (det > 0).
  • Account for q ≡ −q and skip orientation check when eigenvalues are near-degenerate.

Apply this diff:

-                        # Get eigenvalues of both tensors
-                        newton_eigvecs, newton_eigvals = wp.eig3(newton_inertia)
-                        newton_eigvecs = np.array(newton_eigvecs)
-                        newton_eigvecs = newton_eigvecs.reshape((3, 3))
-
-                        newton_eigvals = np.array(newton_eigvals)
+                        # Symmetric eigendecomposition on host (vals asc., vecs in columns)
+                        vals, vecs = np.linalg.eigh(newton_inertia)

                         mjc_eigvals = mjc_inertia  # Already in diagonal form
                         mjc_iquat = np.roll(solver.mjw_model.body_iquat.numpy()[env_idx, mjc_idx].astype(np.float32), 1)

-                        # Sort eigenvalues in ascending order and shuffle eigenvectors accordingly
-                        sort_indices = np.argsort(newton_eigvals)
-                        newton_eigvals = newton_eigvals[sort_indices]
-                        newton_eigvecs = newton_eigvecs[:, sort_indices]
-
-                        # reverse because we want descending order of eigenvalues
-                        newton_eigvals = newton_eigvals[::-1]
-                        newton_eigvecs = newton_eigvecs[::-1]
-
-                        newton_quat = wp.quat_from_matrix(
-                            wp.matrix_from_cols(
-                                wp.vec3(newton_eigvecs[0]), wp.vec3(newton_eigvecs[1]), wp.vec3(newton_eigvecs[2])
-                            )
-                        )
-                        newton_quat = wp.normalize(newton_quat)
+                        # Sort to descending order and reorder eigenvectors by columns
+                        order = vals.argsort()[::-1]
+                        newton_eigvals = vals[order].astype(np.float32)
+                        vecs = vecs[:, order].astype(np.float32)
+                        # enforce right-handed basis
+                        if np.linalg.det(vecs) < 0.0:
+                            vecs[:, 2] *= -1.0
+                        # Build rotation from column eigenvectors and convert to a normalized quaternion (xyzw)
+                        R = wp.matrix_from_cols(
+                            wp.vec3(float(vecs[0, 0]), float(vecs[1, 0]), float(vecs[2, 0])),
+                            wp.vec3(float(vecs[0, 1]), float(vecs[1, 1]), float(vecs[2, 1])),
+                            wp.vec3(float(vecs[0, 2]), float(vecs[1, 2]), float(vecs[2, 2])),
+                        )
+                        newton_quat = wp.normalize(wp.quat_from_matrix(R))

@@
-                        for dim in range(4):
-                            self.assertAlmostEqual(
-                                float(mjc_iquat[dim]),
-                                float(newton_quat[dim]),
-                                places=5,
-                                msg=f"{msg_prefix}Inertia quaternion mismatch for body {body_idx} in environment {env_idx}",
-                            )
+                        # Quaternion compare: sign-invariant; skip when eigenvalues are nearly degenerate
+                        rel_gap12 = abs(float(newton_eigvals[0] - newton_eigvals[1])) / max(1.0, float(abs(newton_eigvals[0])))
+                        rel_gap23 = abs(float(newton_eigvals[1] - newton_eigvals[2])) / max(1.0, float(abs(newton_eigvals[1])))
+                        if min(rel_gap12, rel_gap23) > 1e-4:
+                            newton_quat_np = np.array(
+                                [float(newton_quat[0]), float(newton_quat[1]), float(newton_quat[2]), float(newton_quat[3])],
+                                dtype=np.float32,
+                            )
+                            if float(np.dot(mjc_iquat, newton_quat_np)) < 0.0:
+                                mjc_iquat = -mjc_iquat
+                            for dim in range(4):
+                                self.assertAlmostEqual(
+                                    float(mjc_iquat[dim]),
+                                    float(newton_quat_np[dim]),
+                                    places=5,
+                                    msg=f"{msg_prefix}Inertia quaternion mismatch for body {body_idx} in environment {env_idx}",
+                                )

Also applies to: 456-462

🧹 Nitpick comments (3)
newton/tests/test_mujoco_solver.py (3)

386-406: Prefer sampling SPD inertias via principal moments + random rotation (fewer edge cases).

Your SPD “shift” fix works, but it can yield near-degenerate eigenvalues and unstable eigenvectors, making quaternion checks flaky. Consider generating inertias as I = R diag(λ1≥λ2≥λ3) Rᵀ with a random right-handed R.

Example (drop-in inside the loop; no extra deps):

# Sample principal moments (triangle inequality on principal values)
l1 = np.float32(a_base + self.rng.uniform(0.0, 0.5))
l2 = np.float32(b_base + self.rng.uniform(0.0, 0.5))
l3 = np.float32(min(l1 + l2 - 0.1, c_max))
lam = np.array(sorted([l1, l2, l3], reverse=True), dtype=np.float32)

# Random right-handed rotation
Q, _ = np.linalg.qr(self.rng.normal(size=(3, 3)).astype(np.float32))
if np.linalg.det(Q) < 0.0:
    Q[:, 2] *= -1.0

inertia = (Q @ np.diag(lam) @ Q.T).astype(np.float32)
new_inertias[i] = inertia

451-455: Tolerance may be too strict for float32 paths; consider places=5.

If CI flakes due to ordering ties or fp32 accumulation, relax to places=5.


472-489: DRY the inertia sampling logic (factor into a helper).

The updated-inertia block duplicates the initial generation. Extract to a local helper to keep tests concise.

Add near the top of the test method:

def _make_spd_inertia(self, a_base, b_base, c_max):
    a = np.float32(a_base + self.rng.uniform(0.0, 0.5))
    b = np.float32(b_base + self.rng.uniform(0.0, 0.5))
    c = np.float32(min(a + b - 0.1, c_max))
    ab = np.float32(self.rng.uniform(-0.2, 0.2))
    ac = np.float32(self.rng.uniform(-0.2, 0.2))
    bc = np.float32(self.rng.uniform(-0.2, 0.2))
    inertia = np.array([[a, ab, ac], [ab, b, bc], [ac, bc, c]], dtype=np.float32)
    eigvals = np.linalg.eigvalsh(inertia)
    if np.any(eigvals <= 0):
        inertia += np.eye(3, dtype=np.float32) * (np.abs(np.min(eigvals)) + 0.1)
    return inertia

Then replace the block with:

-            a = np.float32(a_base + self.rng.uniform(0.0, 0.5))
-            b = np.float32(b_base + self.rng.uniform(0.0, 0.5))
-            c = np.float32(min(a + b - 0.1, c_max))
-            ab = np.float32(self.rng.uniform(-0.2, 0.2))
-            ac = np.float32(self.rng.uniform(-0.2, 0.2))
-            bc = np.float32(self.rng.uniform(-0.2, 0.2))
-            inertia = np.array([[a, ab, ac], [ab, b, bc], [ac, bc, c]], dtype=np.float32)
-            eigvals = np.linalg.eigvalsh(inertia)
-            if np.any(eigvals <= 0):
-                inertia += np.eye(3, dtype=np.float32) * (np.abs(np.min(eigvals)) + 0.1)
-            updated_inertias[i] = inertia
+            updated_inertias[i] = self._make_spd_inertia(a_base, b_base, c_max)
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bab6965 and 86649fc.

📒 Files selected for processing (1)
  • newton/tests/test_mujoco_solver.py (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-18T15:56:26.587Z
Learnt from: adenzler-nvidia
PR: newton-physics/newton#552
File: newton/_src/solvers/mujoco/solver_mujoco.py:0-0
Timestamp: 2025-08-18T15:56:26.587Z
Learning: In Newton's MuJoCo solver, when transforming joint axes from Newton's internal frame to MuJoCo's expected frame, use wp.quat_rotate(joint_rot, axis) not wp.quat_rotate_inv(joint_rot, axis). The joint_rot represents rotation from joint-local to body frame, so forward rotation is correct.

Applied to files:

  • newton/tests/test_mujoco_solver.py
🧬 Code graph analysis (1)
newton/tests/test_mujoco_solver.py (1)
newton/_src/sim/builder.py (1)
  • body_count (518-522)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: run-newton-tests / newton-unittests (windows-latest)
  • GitHub Check: run-newton-tests / newton-unittests (ubuntu-latest)
🔇 Additional comments (2)
newton/tests/test_mujoco_solver.py (2)

431-431: wxyz→xyzw conversion is correct.

Rolling MuJoCo’s (wxyz) to (xyzw) for comparison is the right move.


420-463: Ignore inertia ordering concern: eigenvalues are already sorted descending in the solver. The bubble sort in update_body_inertia_kernel (lines 885–893) orders eigenvalues in descending order before writing to body_inertia_out, so the test’s assumption holds.

Likely an incorrect or invalid review comment.

Signed-off-by: Alain Denzler <adenzler@nvidia.com>
Signed-off-by: Alain Denzler <adenzler@nvidia.com>
Signed-off-by: Alain Denzler <adenzler@nvidia.com>
@adenzler-nvidia adenzler-nvidia marked this pull request as ready for review August 29, 2025 12:36
Signed-off-by: Alain Denzler <adenzler@nvidia.com>

@eric-heiden eric-heiden left a comment

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.

Thanks for this fix!

@eric-heiden eric-heiden merged commit c9b2c5a into newton-physics:main Aug 29, 2025
12 checks passed
@eric-heiden eric-heiden linked an issue Aug 29, 2025 that may be closed by this pull request
maxkra15 pushed a commit to maxkra15/newton that referenced this pull request Sep 1, 2025
Signed-off-by: Alain Denzler <adenzler@nvidia.com>
eric-heiden pushed a commit to eric-heiden/newton that referenced this pull request Jan 28, 2026
Signed-off-by: Alain Denzler <adenzler@nvidia.com>
mmacklin pushed a commit to mmacklin/newton that referenced this pull request Apr 7, 2026
Signed-off-by: Alain Denzler <adenzler@nvidia.com>
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.

Fix body_iquat computation in MuJoCoSolver

2 participants