Skip to content

implement passive damping for the featherstone solver#2304

Open
purmecia wants to merge 5 commits intonewton-physics:mainfrom
purmecia:fix-mjcf-damping
Open

implement passive damping for the featherstone solver#2304
purmecia wants to merge 5 commits intonewton-physics:mainfrom
purmecia:fix-mjcf-damping

Conversation

@purmecia
Copy link
Copy Markdown

@purmecia purmecia commented Apr 3, 2026

Description

SolverFeatherstone ignores the MJCF <joint damping="..."> attribute. The MJCF parser reads the value but only registers it in SolverMuJoCo, so the Featherstone joint-force computation never sees it.

This PR adds a passive_damping field that flows through the standard JointDofConfig → builder list → Model.joint_passive_damping → joint_force() path, making the MJCF damping attribute work for the Featherstone solver. It adds a passive velocity-proportional damping force of -damping * qd that is always active, independent of joint limits or target drives.

Closes #2303

Checklist

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

Test plan

Running the reproduction script (damped pendulum, damping=5.0) with this fix, the pendulum correctly decays to rest.

  t=0.0s  qd=-0.1963
  t=1.0s  qd=0.6563
  t=2.0s  qd=-0.5345
  t=3.0s  qd=0.2782
  t=4.0s  qd=-0.1080
  t=5.0s  qd=0.0292
  t=6.0s  qd=-0.0021
  t=7.0s  qd=-0.0036
  t=8.0s  qd=0.0030
  t=9.0s  qd=-0.0016
  t=10.0s  qd=0.0006
  t=11.0s  qd=-0.0002
  t=12.0s  qd=0.0000
  t=13.0s  qd=0.0000
  t=14.0s  qd=-0.0000
  t=15.0s  qd=0.0000
  t=16.0s  qd=-0.0000

Bug fix

Steps to reproduce:

  1. Load the MJCF of pendulum with using ModelBuilder.add_mjcf()
  2. Simulate with SolverFeatherstone
  3. Observe the joint has no damping — the pendulum swings indefinitely

Minimal reproduction:

'''
The script below creates a simple damped pendulum via MJCF (`<joint damping="5.0">`), \
loads it with `SolverFeatherstone`, and prints the joint velocity over time. \
The damping has no effect — the pendulum swings forever.
'''


import warp as wp
import numpy as np
import newton

MJCF = """
<mujoco model="pendulum_damped">
  <compiler angle="radian" />
  <worldbody>
    <body name="link" pos="0 0 2">
      <joint name="hinge" type="hinge" axis="0 1 0" damping="5.0"/>
      <geom type="capsule" size="0.05 0.5" pos="0 0 -0.5" rgba="0.8 0.4 0.2 1"/>
    </body>
  </worldbody>
</mujoco>
"""

builder = newton.ModelBuilder()
builder.add_mjcf(MJCF)
builder.add_ground_plane()
model = builder.finalize("cpu")

print(f"joint_target_kd = {model.joint_target_kd.numpy()}")

# Set initial angle
q = model.joint_q.numpy()
q[0] = 1.0
model.joint_q.assign(wp.array(q, dtype=wp.float32, device="cpu"))

solver = newton.solvers.SolverFeatherstone(model)
state_0 = model.state()
state_1 = model.state()
control = model.control()
contacts = model.contacts()
newton.eval_fk(model, model.joint_q, model.joint_qd, state_0)

dt = 1.0 / 60.0 / 16
velocities = []

for frame in range(1000):
    for _ in range(16):
        state_0.clear_forces()
        model.collide(state_0, contacts)
        solver.step(state_0, state_1, control, contacts, dt)
        state_0, state_1 = state_1, state_0
    qd = state_0.joint_qd.numpy()[0]
    velocities.append(qd)
    if frame % 60 == 0:
        print(f"  t={frame/60:.1f}s  qd={qd:.4f}")

New feature / API change

import newton
builder = newton.ModelBuilder()
# Option 1: Set via default config (applies to all joints)
builder.default_joint_cfg.passive_damping = 2.0
# Option 2: Set per-joint
builder.add_joint_revolute(..., passive_damping=5.0)
# Option 3: Set via JointDofConfig
ax = newton.ModelBuilder.JointDofConfig(axis=(0, 1, 0), passive_damping=5.0)
builder.add_joint_revolute(..., axis=ax)
# Option 4: Automatically read from MJCF <joint damping="5.0"/>
builder.add_mjcf(mjcf_string)

Summary by CodeRabbit

  • New Features

    • Added joint passive damping — a velocity-proportional damping that is always active per joint degree of freedom.
    • MJCF joint damping attributes are now imported and applied in simulation (previously ignored).
  • Tests

    • Updated tests to validate per-DOF passive damping is loaded from MJCF and applied in simulation.

Signed-off-by: purmecia <purmecia@gmail.com>
Signed-off-by: purmecia <995460208@qq.com>
@linux-foundation-easycla
Copy link
Copy Markdown

linux-foundation-easycla bot commented Apr 3, 2026

CLA Signed

The committers listed above are authorized under a signed CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

Added a per-DOF joint damping field parsed from MJCF <joint damping="...">, exposed as JointDofConfig.damping and Model.joint_damping, and threaded that array into Featherstone and semi-implicit solver kernels so joint force evaluation includes a velocity-proportional damping term.

Changes

Cohort / File(s) Summary
Model building & MJCF import
newton/_src/sim/builder.py, newton/_src/utils/import_mjcf.py
Added JointDofConfig.damping (default 0.0), collect per-DOF joint_damping in builder, parse MJCF damping into DOF config, and propagate into final Model attribute.
Model definition
newton/_src/sim/model.py
Declared new `Model.joint_damping: wp.array[wp.float32]
Featherstone solver kernels & call sites
newton/_src/solvers/featherstone/kernels.py, newton/_src/solvers/featherstone/solver_featherstone.py
Extended jcalc_tau / eval_rigid_tau signatures to accept joint_damping and passed model.joint_damping into kernel so per-DOF damping is used when computing joint tau.
Semi-implicit solver kernels
newton/_src/solvers/semi_implicit/kernels_body.py
Added damping parameter to joint_force, applied -damping * qd as passive damping, threaded joint_damping into eval_body_joints and kernel launches.
Flags & tests
newton/_src/solvers/flags.py, newton/tests/test_import_mjcf.py
Updated JOINT_DOF_PROPERTIES docstring to mention joint_damping; test now asserts MJCF damping is loaded into model.joint_damping.

Sequence Diagram

sequenceDiagram
    participant MJCF as MJCF Input
    participant Parser as import_mjcf (Parser)
    participant Builder as ModelBuilder
    participant Model as Model
    participant Solver as SolverFeatherstone
    participant Kernel as eval_rigid_tau (kernel)
    participant Force as joint_force

    MJCF->>Parser: parse joint `damping`
    Parser->>Builder: set JointDofConfig.damping
    Builder->>Builder: append to joint_damping array
    Builder->>Model: finalize -> model.joint_damping (wp.array)
    Solver->>Model: read model.joint_damping
    Solver->>Kernel: launch kernel with joint_damping
    Kernel->>Kernel: loop DOFs -> d = joint_damping[j]
    Kernel->>Force: call joint_force(..., damping=d)
    Force->>Force: compute passive = -d * qd
    Force-->>Kernel: return augmented joint force
    Kernel-->>Solver: accumulate tau -> update qdd
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

bug

Suggested reviewers

  • eric-heiden
  • jvonmuralt
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'implement passive damping for the featherstone solver' clearly and concisely summarizes the main objective of the pull request.
Linked Issues check ✅ Passed The PR successfully implements passive damping support for the Featherstone solver by adding joint_damping field to JointDofConfig, propagating it through the model, and applying velocity-proportional damping in solver kernels, directly addressing issue #2303.
Out of Scope Changes check ✅ Passed All changes directly support the objective of implementing passive damping for the Featherstone solver; no extraneous modifications were detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

@purmecia purmecia had a problem deploying to external-pr-approval April 3, 2026 03:42 — with GitHub Actions Error
@purmecia purmecia had a problem deploying to external-pr-approval April 3, 2026 03:42 — with GitHub Actions Error
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
newton/_src/sim/builder.py (2)

3710-3772: ⚠️ Potential issue | 🟠 Major

Mirror passive_damping in add_joint_ball() as well.

Right now the new default/override path only exists for revolute and prismatic joints. add_joint_ball() still synthesizes its three DOFs from default_joint_cfg.armature / friction only, so builder.default_joint_cfg.passive_damping is silently ignored for ball joints.

Also applies to: 3806-3867

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/sim/builder.py` around lines 3710 - 3772, The ball-joint builder
currently ignores builder.default_joint_cfg.passive_damping; update
add_joint_ball to accept the passive_damping parameter (or use the existing one
if already present) and, when synthesizing the three DOFs, set each DOF's
passive_damping to passive_damping if not None else
self.default_joint_cfg.passive_damping; specifically modify the code that
constructs the three ModelBuilder.JointDofConfig instances in add_joint_ball so
their passive_damping fields mirror the same override logic used in
add_joint_revolute/add_joint_prismatic (refer to add_joint_ball,
ModelBuilder.JointDofConfig, and default_joint_cfg.passive_damping).

435-463: ⚠️ Potential issue | 🟡 Minor

Reject negative passive_damping values.

A negative coefficient flips -damping * qd into anti-damping, so a typo here injects energy instead of dissipating it. Validating this once in JointDofConfig would protect builder defaults, direct API calls, and imported MJCF values through the same entry point.

Proposed fix
             self.target_kd = target_kd
             """The derivative gain of the target drive PD controller. Defaults to 0.0."""
             self.passive_damping = passive_damping
             """Passive velocity damping that is always active. Defaults to 0.0."""
+            if self.passive_damping < 0.0:
+                raise ValueError("passive_damping must be non-negative.")
             self.armature = armature
             """Artificial inertia added around the joint axis [kg·m² or kg]. Defaults to 0."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/sim/builder.py` around lines 435 - 463, The passive_damping field
in JointDofConfig (constructor in builder.py) must be validated to reject
negative values: in JointDofConfig.__init__ (where passive_damping is assigned)
add a guard that checks if passive_damping < 0 and raise a ValueError with a
clear message including the invalid value (e.g., "passive_damping must be
non-negative"), so builder defaults, direct API calls, and MJCF imports all get
the same validation through JointDofConfig.
🧹 Nitpick comments (3)
newton/_src/sim/model.py (1)

458-459: Add SI units to docstring for consistency.

Other joint damping fields like joint_target_kd include units [N·s/m or N·m·s/rad, depending on joint type]. The joint_passive_damping docstring should follow the same convention.

         self.joint_passive_damping: wp.array[wp.float32] | None = None
-        """Passive velocity damping always active on the joint, shape [joint_dof_count], float."""
+        """Passive velocity damping [N·s/m or N·m·s/rad, depending on joint type] always active on the joint, shape [joint_dof_count], float."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/sim/model.py` around lines 458 - 459, The docstring for
self.joint_passive_damping is missing SI units; update its triple-quoted comment
to match the style of joint_target_kd by appending the units "[N·s/m or
N·m·s/rad, depending on joint type]" (i.e., make the docstring read something
like "Passive velocity damping always active on the joint, shape
[joint_dof_count], float. Units: [N·s/m or N·m·s/rad, depending on joint type]")
so it is consistent with joint_target_kd and other joint damping fields.
newton/_src/solvers/featherstone/kernels.py (1)

406-408: Trailing whitespace on line 407.

There's a trailing whitespace/empty space after the assignment on line 407.

🧹 Remove trailing whitespace
             passive_damping = joint_passive_damping[j]
-            
+
             drive_f = joint_force(q, qd, target_pos, target_vel, target_ke, target_kd, lower, upper, limit_ke, limit_kd, passive_damping)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/solvers/featherstone/kernels.py` around lines 406 - 408, Remove
the trailing whitespace after the assignment to passive_damping in the block
where passive_damping = joint_passive_damping[j] is set; ensure the line with
the assignment has no trailing spaces so the subsequent call to joint_force(q,
qd, target_pos, target_vel, target_ke, target_kd, lower, upper, limit_ke,
limit_kd, passive_damping) sits on the next line cleanly without extra
whitespace characters.
newton/_src/sim/builder.py (1)

462-463: Add SI units to the new passive_damping docs.

The new public API docs explain the behavior, but not the units callers are expected to pass.

As per coding guidelines "Include SI units for physical quantities in public API docstrings: \"\"\"Particle positions [m], shape [particle_count, 3].\"\"\". Joint-dependent: [m or rad]. Spatial vectors: [N, N·m]. Compound arrays: per-component. Skip non-physical fields."

Also applies to: 3740-3740, 3835-3835

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/sim/builder.py` around lines 462 - 463, The docstring for the new
public attribute passive_damping is missing SI units; update the docstring for
passive_damping (the attribute set in the class in builder.py) to include its
units (e.g., "[s^-1]" for a velocity-damping coefficient) following the
project's style (include units in brackets after the description), and apply the
same units-formatting to the other listed occurrences (the other
passive_damping/docstring locations referenced) so all public API docstrings
consistently state the expected SI units.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@newton/_src/sim/builder.py`:
- Around line 3710-3772: The ball-joint builder currently ignores
builder.default_joint_cfg.passive_damping; update add_joint_ball to accept the
passive_damping parameter (or use the existing one if already present) and, when
synthesizing the three DOFs, set each DOF's passive_damping to passive_damping
if not None else self.default_joint_cfg.passive_damping; specifically modify the
code that constructs the three ModelBuilder.JointDofConfig instances in
add_joint_ball so their passive_damping fields mirror the same override logic
used in add_joint_revolute/add_joint_prismatic (refer to add_joint_ball,
ModelBuilder.JointDofConfig, and default_joint_cfg.passive_damping).
- Around line 435-463: The passive_damping field in JointDofConfig (constructor
in builder.py) must be validated to reject negative values: in
JointDofConfig.__init__ (where passive_damping is assigned) add a guard that
checks if passive_damping < 0 and raise a ValueError with a clear message
including the invalid value (e.g., "passive_damping must be non-negative"), so
builder defaults, direct API calls, and MJCF imports all get the same validation
through JointDofConfig.

---

Nitpick comments:
In `@newton/_src/sim/builder.py`:
- Around line 462-463: The docstring for the new public attribute
passive_damping is missing SI units; update the docstring for passive_damping
(the attribute set in the class in builder.py) to include its units (e.g.,
"[s^-1]" for a velocity-damping coefficient) following the project's style
(include units in brackets after the description), and apply the same
units-formatting to the other listed occurrences (the other
passive_damping/docstring locations referenced) so all public API docstrings
consistently state the expected SI units.

In `@newton/_src/sim/model.py`:
- Around line 458-459: The docstring for self.joint_passive_damping is missing
SI units; update its triple-quoted comment to match the style of joint_target_kd
by appending the units "[N·s/m or N·m·s/rad, depending on joint type]" (i.e.,
make the docstring read something like "Passive velocity damping always active
on the joint, shape [joint_dof_count], float. Units: [N·s/m or N·m·s/rad,
depending on joint type]") so it is consistent with joint_target_kd and other
joint damping fields.

In `@newton/_src/solvers/featherstone/kernels.py`:
- Around line 406-408: Remove the trailing whitespace after the assignment to
passive_damping in the block where passive_damping = joint_passive_damping[j] is
set; ensure the line with the assignment has no trailing spaces so the
subsequent call to joint_force(q, qd, target_pos, target_vel, target_ke,
target_kd, lower, upper, limit_ke, limit_kd, passive_damping) sits on the next
line cleanly without extra whitespace characters.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: fc085151-1267-4439-af86-8b0029efe78c

📥 Commits

Reviewing files that changed from the base of the PR and between d26dcbd and 7fd1a7d.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • newton/_src/sim/builder.py
  • newton/_src/sim/model.py
  • newton/_src/solvers/featherstone/kernels.py
  • newton/_src/solvers/featherstone/solver_featherstone.py
  • newton/_src/solvers/flags.py
  • newton/_src/solvers/semi_implicit/kernels_body.py
  • newton/_src/utils/import_mjcf.py
  • newton/tests/test_import_mjcf.py

@purmecia purmecia had a problem deploying to external-pr-approval April 3, 2026 21:51 — with GitHub Actions Error
@purmecia purmecia had a problem deploying to external-pr-approval April 3, 2026 21:51 — with GitHub Actions Error
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

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/sim/builder.py (1)

3761-3773: ⚠️ Potential issue | 🟡 Minor

Avoid silent ignore when axis is JointDofConfig and passive_damping is also passed.

If axis is a ModelBuilder.JointDofConfig, the new passive_damping= argument is ignored silently. Please raise a TypeError (or explicitly document precedence) to prevent accidental misconfiguration.

💡 Suggested guard
         if isinstance(axis, ModelBuilder.JointDofConfig):
+            if passive_damping is not None:
+                raise TypeError(
+                    "Cannot pass passive_damping when axis is JointDofConfig; "
+                    "set axis.passive_damping instead."
+                )
             ax = axis

Apply similarly in both add_joint_revolute() and add_joint_prismatic().

Also applies to: 3858-3870

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/sim/builder.py` around lines 3761 - 3773, When axis is provided
as a ModelBuilder.JointDofConfig the passed-in passive_damping is currently
ignored; change both add_joint_revolute and add_joint_prismatic so they check if
isinstance(axis, ModelBuilder.JointDofConfig) and passive_damping is not None,
and in that case raise a TypeError describing that passive_damping cannot be
passed when axis is a JointDofConfig (or alternatively document precedence).
Ensure the check references ModelBuilder.JointDofConfig, the passive_damping
parameter name, and is applied in the same manner in both methods (also update
the same guard at the other location around lines 3858-3870).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@newton/_src/sim/builder.py`:
- Line 463: Update the passive-damping parameter docstrings so each joint method
only states its own unit: in add_joint_prismatic() describe damping as linear
units ("N·s/m") and in add_joint_revolute() describe damping with the
repo-preferred angular wording ("torque per radian") instead of using "N·m/rad"
notation; apply the same replacement to other passive-damping docstrings in this
file (e.g., the other occurrences referenced) so all angular damping
descriptions use "torque per radian" and linear ones use "N·s/m".

---

Outside diff comments:
In `@newton/_src/sim/builder.py`:
- Around line 3761-3773: When axis is provided as a ModelBuilder.JointDofConfig
the passed-in passive_damping is currently ignored; change both
add_joint_revolute and add_joint_prismatic so they check if isinstance(axis,
ModelBuilder.JointDofConfig) and passive_damping is not None, and in that case
raise a TypeError describing that passive_damping cannot be passed when axis is
a JointDofConfig (or alternatively document precedence). Ensure the check
references ModelBuilder.JointDofConfig, the passive_damping parameter name, and
is applied in the same manner in both methods (also update the same guard at the
other location around lines 3858-3870).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 3066ed27-85a3-4ade-b718-58170be2a9cb

📥 Commits

Reviewing files that changed from the base of the PR and between 7fd1a7d and b367403.

📒 Files selected for processing (4)
  • CHANGELOG.md
  • newton/_src/sim/builder.py
  • newton/_src/sim/model.py
  • newton/_src/solvers/featherstone/kernels.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • newton/_src/solvers/featherstone/kernels.py

Comment thread newton/_src/sim/builder.py
@eric-heiden
Copy link
Copy Markdown
Member

Clean implementation of per-DOF passive damping through the Featherstone and semi-implicit solvers. The main question worth settling before this merges is whether passive_damping is the best long-term public API name, or whether plain damping would be more consistent with the rest of the joint-property surface.

Comment thread newton/_src/sim/model.py
@codecov
Copy link
Copy Markdown

codecov bot commented Apr 4, 2026

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!

@purmecia purmecia had a problem deploying to external-pr-approval April 5, 2026 18:35 — with GitHub Actions Error
@purmecia purmecia had a problem deploying to external-pr-approval April 5, 2026 18:35 — with GitHub Actions Error
@purmecia purmecia requested a deployment to external-pr-approval April 5, 2026 18:36 — with GitHub Actions Waiting
@purmecia purmecia requested a deployment to external-pr-approval April 5, 2026 18:36 — with GitHub Actions Waiting
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

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 (2)
newton/tests/test_import_mjcf.py (1)

3202-3217: ⚠️ Potential issue | 🟠 Major

Fix overwritten damping variable so both damping sources are actually validated.

Line 3202 overwrites the earlier model.mujoco.dof_passive_damping array, and Line 3216/Line 3217 then assert the same array twice. This drops the intended parity check between MuJoCo and model.joint_damping.

Suggested fix
-        joint_stiffness = model.mujoco.dof_passive_stiffness.numpy()
-        joint_damping = model.mujoco.dof_passive_damping.numpy()
+        joint_stiffness = model.mujoco.dof_passive_stiffness.numpy()
+        joint_damping_mujoco = model.mujoco.dof_passive_damping.numpy()
         joint_target_ke = model.joint_target_ke.numpy()
         joint_target_kd = model.joint_target_kd.numpy()
-        joint_damping = model.joint_damping.numpy()
+        joint_damping_model = model.joint_damping.numpy()
@@
-            self.assertAlmostEqual(joint_damping[dof_idx], expected["damping"], places=4)
-            self.assertAlmostEqual(joint_damping[dof_idx], expected["damping"], places=4)
+            self.assertAlmostEqual(joint_damping_mujoco[dof_idx], expected["damping"], places=4)
+            self.assertAlmostEqual(joint_damping_model[dof_idx], expected["damping"], places=4)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/tests/test_import_mjcf.py` around lines 3202 - 3217, The test
currently overwrites the earlier MuJoCo damping array and then asserts the same
variable twice; preserve both arrays by reading model.joint_damping and
model.mujoco.dof_passive_damping into two distinct variables (e.g.,
joint_damping and mujo_passive_damping) before the loop, and inside the for loop
assert mujo_passive_damping[dof_idx] against expected["damping"] and assert
joint_damping[dof_idx] against the intended expected value (e.g.,
expected["target_kd"] or the correct expected key), ensuring you reference
model.joint_damping and model.mujoco.dof_passive_damping by their variable names
in the assertions.
newton/_src/sim/builder.py (1)

424-494: ⚠️ Potential issue | 🟠 Major

Move the damping parameter to keyword-only to preserve positional API compatibility.

The damping parameter was inserted before existing parameters in JointDofConfig.__init__(), add_joint_revolute(), and add_joint_prismatic(). This breaks the positional API: any call passing arguments starting from armature onward positionally will silently bind those values to the new damping parameter and shift the rest.

While in-repo call sites do not currently use positional arguments beyond this threshold, this is a public API breaking change that violates the "breaking changes require a deprecation first" guideline. Move damping to keyword-only using * to keep the signature clean and compatible:

Proposed signature changes
         target_ke: float = 0.0,
         target_kd: float = 0.0,
         armature: float = 0.0,
         effort_limit: float = 1e6,
         velocity_limit: float = 1e6,
         friction: float = 0.0,
         actuator_mode: JointTargetMode | None = None,
+        *,
+        damping: float = 0.0,

Apply the same change to add_joint_revolute() and add_joint_prismatic().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/sim/builder.py` around lines 424 - 494, The new damping parameter
in ModelBuilder.JointDofConfig.__init__ was inserted as a positional parameter
and breaks positional API compatibility; make damping keyword-only to avoid
shifting existing positional arguments by moving a bare '*' before the damping
parameter in JointDofConfig.__init__ and apply the same change to the signatures
of add_joint_revolute() and add_joint_prismatic() so damping (and any subsequent
params) must be passed by keyword, preserving existing positional bindings for
armature, effort_limit, velocity_limit, etc.
🧹 Nitpick comments (1)
newton/_src/solvers/semi_implicit/kernels_body.py (1)

233-240: BALL joint does not apply passive damping.

The BALL joint path does not call joint_force(), so the new passive damping term will not be applied to ball joints even if joint_damping values are set for their DOFs. This may be intentional given the existing TODO comments, but creates an inconsistency with other multi-DOF joints (D6 with 3 angular axes does apply damping).

If BALL joints should support passive damping, the implementation would need to incorporate the damping term into the torque calculation:

# Sketch (not a complete fix):
t_total += wp.vec3(-joint_f[qd_start], -joint_f[qd_start + 1], -joint_f[qd_start + 2])
# Add passive damping per angular DOF
t_total -= wp.vec3(
    joint_damping[qd_start] * wp.dot(w_err, axis_0),
    joint_damping[qd_start + 1] * wp.dot(w_err, axis_1),
    joint_damping[qd_start + 2] * wp.dot(w_err, axis_2),
)

Do you want me to open a follow-up issue to track adding passive damping support for BALL joints?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@newton/_src/solvers/semi_implicit/kernels_body.py` around lines 233 - 240,
The BALL joint branch (JointType.BALL) never applies the new passive damping
because it doesn't invoke joint_force() or add damping to t_total; update the
BALL path so t_total includes the passive damping term similar to the D6/other
multi-DOF joints by reading joint_damping[qd_start..qd_start+2] and projecting
w_err onto the joint angular axes (or using w_err components in joint-local
coords) and subtracting those damping torques from t_total, or simply call the
existing joint_force(...) helper with the ball angular DOFs so the same damping
logic is reused; reference symbols: JointType.BALL, joint_force(), t_total,
joint_damping, qd_start, and w_err (and local axes e.g. axis_0/axis_1/axis_2) to
locate and implement this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@newton/_src/solvers/featherstone/solver_featherstone.py`:
- Line 514: The kernel call passes model.joint_damping (a JOINT_DOF‑shaped
array) but jcalc_tau in kernels.py indexes it with the joint loop index j;
change the kernel to pass per‑DOF values or update jcalc_tau to index
joint_damping using the DOF offset (qd_start + axis) instead of j so multi‑DOF
joints (BALL, FREE, DISTANCE, D6) get correct damping; locate the joint_damping
usage and replace joint_damping[j] with joint_damping[qd_start + axis] (or
compute the DOF index before the axis loop) so it aligns with how other
DOF‑level properties are accessed.

---

Outside diff comments:
In `@newton/_src/sim/builder.py`:
- Around line 424-494: The new damping parameter in
ModelBuilder.JointDofConfig.__init__ was inserted as a positional parameter and
breaks positional API compatibility; make damping keyword-only to avoid shifting
existing positional arguments by moving a bare '*' before the damping parameter
in JointDofConfig.__init__ and apply the same change to the signatures of
add_joint_revolute() and add_joint_prismatic() so damping (and any subsequent
params) must be passed by keyword, preserving existing positional bindings for
armature, effort_limit, velocity_limit, etc.

In `@newton/tests/test_import_mjcf.py`:
- Around line 3202-3217: The test currently overwrites the earlier MuJoCo
damping array and then asserts the same variable twice; preserve both arrays by
reading model.joint_damping and model.mujoco.dof_passive_damping into two
distinct variables (e.g., joint_damping and mujo_passive_damping) before the
loop, and inside the for loop assert mujo_passive_damping[dof_idx] against
expected["damping"] and assert joint_damping[dof_idx] against the intended
expected value (e.g., expected["target_kd"] or the correct expected key),
ensuring you reference model.joint_damping and model.mujoco.dof_passive_damping
by their variable names in the assertions.

---

Nitpick comments:
In `@newton/_src/solvers/semi_implicit/kernels_body.py`:
- Around line 233-240: The BALL joint branch (JointType.BALL) never applies the
new passive damping because it doesn't invoke joint_force() or add damping to
t_total; update the BALL path so t_total includes the passive damping term
similar to the D6/other multi-DOF joints by reading
joint_damping[qd_start..qd_start+2] and projecting w_err onto the joint angular
axes (or using w_err components in joint-local coords) and subtracting those
damping torques from t_total, or simply call the existing joint_force(...)
helper with the ball angular DOFs so the same damping logic is reused; reference
symbols: JointType.BALL, joint_force(), t_total, joint_damping, qd_start, and
w_err (and local axes e.g. axis_0/axis_1/axis_2) to locate and implement this
change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: f3e42c80-00bc-407a-93cd-3bb50fa24ca0

📥 Commits

Reviewing files that changed from the base of the PR and between b367403 and 4b79f32.

📒 Files selected for processing (9)
  • CHANGELOG.md
  • newton/_src/sim/builder.py
  • newton/_src/sim/model.py
  • newton/_src/solvers/featherstone/kernels.py
  • newton/_src/solvers/featherstone/solver_featherstone.py
  • newton/_src/solvers/flags.py
  • newton/_src/solvers/semi_implicit/kernels_body.py
  • newton/_src/utils/import_mjcf.py
  • newton/tests/test_import_mjcf.py
✅ Files skipped from review due to trivial changes (1)
  • newton/_src/solvers/flags.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • newton/_src/utils/import_mjcf.py
  • newton/_src/solvers/featherstone/kernels.py
  • newton/_src/sim/model.py

Comment thread newton/_src/solvers/featherstone/solver_featherstone.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[BUG] Featherstone solver ignores MJCF joint damping attribute

3 participants