Skip to content

Improve the logic with njmax and nconmax#787

Merged
preist-nvidia merged 8 commits into
newton-physics:mainfrom
Kenny-Vilella:dev/kvilella/improve_njmax_ncon_logic
Sep 23, 2025
Merged

Improve the logic with njmax and nconmax#787
preist-nvidia merged 8 commits into
newton-physics:mainfrom
Kenny-Vilella:dev/kvilella/improve_njmax_ncon_logic

Conversation

@Kenny-Vilella

@Kenny-Vilella Kenny-Vilella commented Sep 19, 2025

Copy link
Copy Markdown
Member

Description

  • Changed default value for njmax and nconmax to mujoco value
  • Added Warning if user provided value are overrided

Newton Migration Guide

Please ensure the migration guide for warp.sim users is up-to-date with the changes made in this PR.

  • The migration guide in docs/migration.rst is up-to date

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

    • Solver can omit njmax to auto-detect constraint/joint limits, reducing manual configuration.
  • Bug Fixes

    • Per-world contact and joint counts are clamped to safe MuJoCo-backed values to avoid runtime errors.
    • Console warnings added when user-supplied njmax or per-environment contact counts are auto-adjusted.
  • Tests

    • Tests updated to instantiate the MuJoCo-based solver with explicit njmax where required.
  • Chores

    • Examples updated to pass explicit njmax and/or ncon_per_env settings.

@coderabbitai

coderabbitai Bot commented Sep 19, 2025

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Makes njmax optional in SolverMuJoCo and convert_to_mjc; computes and clamps nconmax and njmax from MuJoCo runtime (mj_data.ncon, mj_data.nefc) when necessary, emits warnings if user values are reduced, and passes clamped values to mujoco_warp.put_data. Tests and examples updated to supply njmax/ncon_per_env.

Changes

Cohort / File(s) Summary
MuJoCo solver API & clamping
newton/_src/solvers/mujoco/solver_mujoco.py
njmax changed to `int
Tests
newton/tests/test_rigid_contact.py
Test registry now constructs SolverMuJoCo(..., njmax=100) for the mujoco_warp solver entry.
Examples — solver instantiations
newton/examples/.../*.py
Multiple example scripts updated to pass explicit njmax and/or ncon_per_env to newton.solvers.SolverMuJoCo (values vary per example). No other logic changes in examples.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant Solver as SolverMuJoCo
    participant MJ as MuJoCo (model/data)
    participant Warp as mujoco_warp

    User->>Solver: __init__(..., njmax=None|int, ncon_per_env=None|int)
    Solver->>MJ: read mj_data.ncon, mj_data.nefc
    note right of Solver #DDEBF7: compute nconmax and njmax\n(use provided values or default to MJ data)\nclamp to MJ limits if needed
    alt provided values exceed MJ limits
        Solver-->>User: [WARNING] adjusted ncon_per_env or njmax
    end
    Solver->>Warp: put_data(..., nconmax=clamped_nconmax, njmax=clamped_njmax)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • adenzler-nvidia
  • shi-eric
  • eric-heiden

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "Improve the logic with njmax and nconmax" is concise and directly reflects the primary change in the PR, namely adjustments to njmax/nconmax handling (auto-detection, clamping, and warnings). It names the relevant variables and signals a behavior-focused improvement that a reviewer scanning history will recognize as the main intent.
✨ 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

Comment @coderabbitai help to get the list of available commands and usage tips.

@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: 2

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)

2441-2446: Pass total njmax to put_data.

After the per‑env fix, pass njmax_total.

Apply this diff:

             self.mjw_data = mujoco_warp.put_data(
                 self.mj_model,
                 self.mj_data,
                 nworld=nworld,
                 nconmax=nconmax,
-                njmax=njmax,
+                njmax=njmax_total,
             )
🧹 Nitpick comments (2)
newton/_src/solvers/mujoco/solver_mujoco.py (1)

1214-1214: Doc mismatch: njmax is Optional now.

Update the docstring to reflect njmax: int | None and that None uses MuJoCo’s model size. Also clarify whether it’s per‑env or total (see njmax handling below).

newton/tests/test_rigid_contact.py (1)

186-186: njmax argument is per‑env (per your comment in solver); test is fine for single‑env but will under‑allocate for multi‑env.

If this suite ever runs with multiple envs per model, either multiply by env count or rely on the solver default (MuJoCo model size). Optionally set ncon_per_env for deterministic capacity.

Optional tweak:

-    "mujoco_warp": lambda model: newton.solvers.SolverMuJoCo(model, use_mujoco_cpu=False, njmax=100),
+    "mujoco_warp": lambda model: newton.solvers.SolverMuJoCo(model, use_mujoco_cpu=False, njmax=100, ncon_per_env=64),
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08ff1bf and 6c7e891.

📒 Files selected for processing (2)
  • newton/_src/solvers/mujoco/solver_mujoco.py (3 hunks)
  • newton/tests/test_rigid_contact.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-19T12:27:30.629Z
Learnt from: preist-nvidia
PR: newton-physics/newton#579
File: newton/examples/example_mujoco.py:350-354
Timestamp: 2025-08-19T12:27:30.629Z
Learning: In Newton examples, Example.use_mujoco is a high-level attribute that controls whether to use MuJoCo solver vs other solvers (like XPBD), while SolverMuJoCo.use_mujoco_cpu controls the backend within MuJoCo (CPU vs Warp). These are separate concepts serving different purposes - the PR rename only applies to the solver parameter, not the Example class attributes.

Applied to files:

  • newton/tests/test_rigid_contact.py
  • 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). (3)
  • 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 (1)
newton/_src/solvers/mujoco/solver_mujoco.py (1)

2424-2431: Use MjModel.nconmax and warnings.warn (replace Data.ncon / print)

Verified: MjModel.nconmax exists and is the runtime maximum number of contacts. Using self.mj_data.ncon can be zero at init — use int(self.mj_model.nconmax) * nworld instead; replace prints with warnings.warn and remove the now-unused rigid_contact_max assignment.

File: newton/_src/solvers/mujoco/solver_mujoco.py Lines: 2424-2431

-            # TODO find better heuristics to determine nconmax and njmax
-            if disable_contacts:
-                nconmax = 0
-            else:
-                if ncon_per_env is not None:
-                    rigid_contact_max = nworld * ncon_per_env
-                    if rigid_contact_max < self.mj_data.ncon * nworld:
-                        print(f"[WARNING] Value for ncon_per_env is changed from {ncon_per_env} to {self.mj_data.ncon}")
-                        nconmax = self.mj_data.ncon * nworld
-                    else:
-                        nconmax = rigid_contact_max
-                else:
-                    nconmax = self.mj_data.ncon * nworld
-                    rigid_contact_max = model.rigid_contact_max
+            # Default nconmax to MuJoCo model size; allow per‑env override with clamping
+            if disable_contacts:
+                nconmax = 0
+            else:
+                if ncon_per_env is not None:
+                    rigid_contact_max = nworld * ncon_per_env
+                    mj_nconmax_total = int(self.mj_model.nconmax) * nworld
+                    if rigid_contact_max < mj_nconmax_total:
+                        warnings.warn(
+                            f"ncon_per_env={ncon_per_env} is below MuJoCo nconmax per env ({int(self.mj_model.nconmax)}); clamping to {int(self.mj_model.nconmax)}.",
+                            stacklevel=2,
+                        )
+                        nconmax = mj_nconmax_total
+                    else:
+                        nconmax = rigid_contact_max
+                else:
+                    nconmax = int(self.mj_model.nconmax) * nworld

Comment thread newton/_src/solvers/mujoco/solver_mujoco.py
Comment thread newton/_src/solvers/mujoco/solver_mujoco.py

@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

🧹 Nitpick comments (3)
newton/examples/selection/example_selection_articulations.py (1)

119-119: Avoid hard‑coding njmax/ncon_per_env in examples; rely on MuJoCo defaults or expose CLI flags.

Hard‑coding small caps here can trigger runtime “[WARNING] … is changed to …” when env count or model complexity varies. Let the solver use MuJoCo‑derived defaults (None) or make these user‑tunable via CLI.

Suggested minimal change:

-        self.solver = newton.solvers.SolverMuJoCo(self.model, njmax=100, ncon_per_env=50)
+        self.solver = newton.solvers.SolverMuJoCo(self.model)

Optional (outside this hunk): add CLI knobs and pass them through if provided.

newton/examples/robot/example_robot_anymal_d.py (1)

87-93: Prefer defaults (None) or CLI for ncon_per_env; 20 per env is brittle and may warn under load.

Keep examples resilient across num‑envs/models by removing the cap or exposing it via CLI.

-        self.solver = newton.solvers.SolverMuJoCo(
-            self.model,
-            cone=mujoco.mjtCone.mjCONE_ELLIPTIC,
-            impratio=100,
-            iterations=100,
-            ls_iterations=50,
-            ncon_per_env=20,
-        )
+        self.solver = newton.solvers.SolverMuJoCo(
+            self.model,
+            cone=mujoco.mjtCone.mjCONE_ELLIPTIC,
+            impratio=100,
+            iterations=100,
+            ls_iterations=50,
+        )

Note: You currently compute contacts via self.model.collide(...) for rendering; if using MuJoCo contacts, consider populate_contacts to keep visuals consistent with the solver.

newton/examples/sensors/example_sensor_contact.py (1)

142-142: Make njmax/ncon_per_env optional or user‑configurable; fixed 100/100 can cause noisy overrides.

Defaulting to MuJoCo‑clamped values reduces surprise and warning spam across different env counts.

-        self.solver = newton.solvers.SolverMuJoCo(self.model, njmax=100, ncon_per_env=100)
+        self.solver = newton.solvers.SolverMuJoCo(self.model)

If you need deterministic caps for demos, add CLI args (e.g., --njmax, --ncon-per-env) and pass them when set.

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c7e891 and cb7143d.

📒 Files selected for processing (8)
  • newton/examples/robot/example_robot_anymal_c_walk.py (1 hunks)
  • newton/examples/robot/example_robot_anymal_d.py (1 hunks)
  • newton/examples/robot/example_robot_h1.py (1 hunks)
  • newton/examples/robot/example_robot_humanoid.py (1 hunks)
  • newton/examples/robot/example_robot_policy.py (1 hunks)
  • newton/examples/selection/example_selection_articulations.py (1 hunks)
  • newton/examples/selection/example_selection_materials.py (1 hunks)
  • newton/examples/sensors/example_sensor_contact.py (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-08-19T12:27:30.629Z
Learnt from: preist-nvidia
PR: newton-physics/newton#579
File: newton/examples/example_mujoco.py:350-354
Timestamp: 2025-08-19T12:27:30.629Z
Learning: In Newton examples, Example.use_mujoco is a high-level attribute that controls whether to use MuJoCo solver vs other solvers (like XPBD), while SolverMuJoCo.use_mujoco_cpu controls the backend within MuJoCo (CPU vs Warp). These are separate concepts serving different purposes - the PR rename only applies to the solver parameter, not the Example class attributes.

Applied to files:

  • newton/examples/robot/example_robot_anymal_c_walk.py
  • newton/examples/robot/example_robot_policy.py
  • newton/examples/selection/example_selection_articulations.py
  • newton/examples/selection/example_selection_materials.py
  • newton/examples/sensors/example_sensor_contact.py
  • newton/examples/robot/example_robot_humanoid.py
  • newton/examples/robot/example_robot_h1.py
  • newton/examples/robot/example_robot_anymal_d.py
📚 Learning: 2025-08-19T12:27:30.629Z
Learnt from: preist-nvidia
PR: newton-physics/newton#579
File: newton/examples/example_mujoco.py:350-354
Timestamp: 2025-08-19T12:27:30.629Z
Learning: In Newton examples, there's a distinction between solver parameters and Example class attributes. The Example class can have its own use_mujoco attribute for controlling example-level behavior (like CUDA graphs, rendering logic), while the solver uses use_mujoco_cpu for backend selection. These serve different purposes and should not be conflated during API renames.

Applied to files:

  • newton/examples/robot/example_robot_anymal_c_walk.py
  • newton/examples/robot/example_robot_policy.py
  • newton/examples/selection/example_selection_articulations.py
  • newton/examples/selection/example_selection_materials.py
  • newton/examples/sensors/example_sensor_contact.py
  • newton/examples/robot/example_robot_humanoid.py
  • newton/examples/robot/example_robot_h1.py
  • newton/examples/robot/example_robot_anymal_d.py
📚 Learning: 2025-08-27T19:05:44.697Z
Learnt from: Milad-Rakhsha-NV
PR: newton-physics/newton#535
File: newton/tests/test_examples.py:320-414
Timestamp: 2025-08-27T19:05:44.697Z
Learning: In newton/examples/__init__.py, the robot policy example is registered with the key "robot_policy" (not "robot.example_robot_policy"), so tests should reference it as name="robot_policy".

Applied to files:

  • newton/examples/robot/example_robot_policy.py
🧬 Code graph analysis (2)
newton/examples/selection/example_selection_materials.py (1)
newton/_src/solvers/mujoco/solver_mujoco.py (1)
  • SolverMuJoCo (1135-2760)
newton/examples/robot/example_robot_humanoid.py (1)
newton/_src/solvers/mujoco/solver_mujoco.py (1)
  • SolverMuJoCo (1135-2760)
⏰ 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 (5)
newton/examples/robot/example_robot_policy.py (1)

268-274: LGTM! The explicit njmax parameter improves clarity.

The addition of njmax=100 to the SolverMuJoCo constructor is appropriate and aligns with the PR's API changes to make constraint limits explicit rather than relying on implicit defaults.

newton/examples/robot/example_robot_anymal_c_walk.py (1)

145-145: LGTM! Appropriate constraint limit for this simulation.

Setting njmax=50 is reasonable for a single Anymal robot simulation and aligns with the PR's API changes.

newton/examples/robot/example_robot_humanoid.py (1)

70-70: LGTM! Consistent with other multi-environment examples.

The njmax=100 value is appropriate for multiple humanoid environments and consistent with other robot examples in the PR.

newton/examples/selection/example_selection_materials.py (1)

97-97: Consider if njmax=50 is sufficient for 16 ant environments.

The example defaults to 16 environments with ant robots. Given that other multi-environment examples use njmax=100, you may want to verify that njmax=50 provides adequate constraint capacity for all environments, especially if users increase --num-envs.

newton/examples/robot/example_robot_h1.py (1)

79-79: LGTM! Comprehensive solver configuration.

The addition of njmax=100 alongside the existing iterations and ls_iterations parameters provides complete control over the solver's constraint handling.

@preist-nvidia preist-nvidia self-requested a review September 19, 2025 07:23
Comment thread newton/_src/solvers/mujoco/solver_mujoco.py Outdated
Comment thread newton/_src/solvers/mujoco/solver_mujoco.py
Comment thread newton/_src/solvers/mujoco/solver_mujoco.py
Comment thread newton/_src/solvers/mujoco/solver_mujoco.py Outdated
Comment thread newton/_src/solvers/mujoco/solver_mujoco.py Outdated

@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

🧹 Nitpick comments (1)
newton/_src/solvers/mujoco/solver_mujoco.py (1)

1764-1765: Update docstring comment to clarify per-environment units.

The comment states "per world" but the parameter represents constraints per environment. This should be clarified to avoid confusion.

-        njmax: int | None = None,  # number of constraints per world
+        njmax: int | None = None,  # number of constraints per environment
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb7143d and a3269d8.

📒 Files selected for processing (1)
  • newton/_src/solvers/mujoco/solver_mujoco.py (4 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-19T12:27:30.629Z
Learnt from: preist-nvidia
PR: newton-physics/newton#579
File: newton/examples/example_mujoco.py:350-354
Timestamp: 2025-08-19T12:27:30.629Z
Learning: In Newton examples, Example.use_mujoco is a high-level attribute that controls whether to use MuJoCo solver vs other solvers (like XPBD), while SolverMuJoCo.use_mujoco_cpu controls the backend within MuJoCo (CPU vs Warp). These are separate concepts serving different purposes - the PR rename only applies to the solver parameter, not the Example class attributes.

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). (2)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
🔇 Additional comments (4)
newton/_src/solvers/mujoco/solver_mujoco.py (4)

1213-1214: LGTM on the API change.

The docstring accurately describes the parameter as optional and mentions that the maximum value is estimated from the model when None is provided.


1239-1240: LGTM on updated docstring and parameter descriptions.

The updated docstrings clearly explain that larger of user-provided or default values will be used, which accurately reflects the clamping behavior implemented.


2479-2488: Use model.njmax instead of mj_data.ncon for constraint estimation.

The current logic uses mj_data.ncon which represents the current number of contacts, but for constraint estimation we should use the model's maximum contact capacity. Also, the scaling by nworld should happen consistently for both user-provided and default values.

Based on the past review comments, this should use self.mj_data.ncon as the per-environment baseline and scale by nworld for the total constraint capacity.

             if disable_contacts:
                 nconmax = 0
             else:
                 if ncon_per_env is not None:
-                    rigid_contact_max = nworld * ncon_per_env
-                    if rigid_contact_max < self.mj_data.ncon * nworld:
+                    if ncon_per_env < self.mj_data.ncon:
                         warnings.warn(
-                            f"[WARNING] Value for ncon_per_env is changed from {ncon_per_env} to {self.mj_data.ncon} following a MjWarp requirement.",
+                            f"Value for ncon_per_env is changed from {ncon_per_env} to {self.mj_data.ncon} following a MjWarp requirement.",
                             stacklevel=2,
                         )
-                        nconmax = self.mj_data.ncon * nworld
-                    else:
-                        nconmax = rigid_contact_max
+                        ncon_per_env = self.mj_data.ncon
+                    nconmax = ncon_per_env * nworld
                 else:
                     nconmax = self.mj_data.ncon * nworld

2490-2499: Use model.njmax and scale properly for total constraint allocation.

The current implementation uses mj_data.nefc (current constraint count) instead of the model's maximum constraint capacity. This can under-allocate constraints for vectorized environments.

Based on past review feedback, this should be corrected to use self.mj_model.njmax and scale by nworld:

             if njmax is not None:
-                if njmax < self.mj_data.nefc:
+                mj_njmax = int(self.mj_model.njmax)
+                if njmax < mj_njmax:
                     warnings.warn(
-                        f"[WARNING] Value for njmax is changed from {njmax} to {self.mj_data.nefc} following a MjWarp requirement.",
+                        f"njmax={njmax} is below MuJoCo njmax per env ({mj_njmax}); clamping to {mj_njmax}.",
                         stacklevel=2,
                     )
-                    njmax = self.mj_data.nefc
+                    njmax = mj_njmax
+                njmax_total = njmax * nworld
             else:
-                njmax = self.mj_data.nefc
+                njmax_total = int(self.mj_model.njmax) * nworld
```Based on the search results, I can see that MuJoCo's Python bindings expose model fields, but let me search for more specific information about njmax field:Let me search more specifically for the njmax field in mjModel:Based on my search results and the existing review comments, I can confirm that the approach should indeed use `mj_model.njmax` instead of `mj_data.nefc`. The search results confirm that MuJoCo preallocates all the memory needed at runtime in mjData, and the model structure contains allocation parameters. However, I need to also consider that the past review comment uses `self.mj_data.nefc` as the baseline. The final approach should pass the correctly scaled total to `put_data`.

The key issue is that `njmax` is expected to be a total value for `put_data`, not per-environment, so the final line should pass `njmax_total` instead of `njmax`.

```diff
+            njmax_total = njmax * nworld
             self.mjw_data = mujoco_warp.put_data(
                 self.mj_model,
                 self.mj_data,
                 nworld=nworld,
                 nconmax=nconmax,
-                njmax=njmax,
+                njmax=njmax_total,
             )

preist-nvidia
preist-nvidia previously approved these changes Sep 22, 2025

@preist-nvidia preist-nvidia 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 the fixes! Minor grammar thing in the warning, otherwise LGTM.

Comment thread newton/_src/solvers/mujoco/solver_mujoco.py
Comment thread newton/_src/solvers/mujoco/solver_mujoco.py
Comment thread newton/_src/solvers/mujoco/solver_mujoco.py
Comment thread newton/_src/solvers/mujoco/solver_mujoco.py

@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

🧹 Nitpick comments (1)
newton/_src/solvers/mujoco/solver_mujoco.py (1)

1764-1764: Inline comment: “per world” → “per environment (scaled to total)”.

Avoid “world” here since it depends on separate_envs_to_worlds; keep public units per‑env and state scaling explicitly.

Apply:

-        njmax: int | None = None,  # number of constraints per world
+        njmax: int | None = None,  # maximum scalar constraint rows per environment (scaled by nworld)
📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3269d8 and ab73ce2.

📒 Files selected for processing (1)
  • newton/_src/solvers/mujoco/solver_mujoco.py (6 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-19T12:27:30.629Z
Learnt from: preist-nvidia
PR: newton-physics/newton#579
File: newton/examples/example_mujoco.py:350-354
Timestamp: 2025-08-19T12:27:30.629Z
Learning: In Newton examples, Example.use_mujoco is a high-level attribute that controls whether to use MuJoCo solver vs other solvers (like XPBD), while SolverMuJoCo.use_mujoco_cpu controls the backend within MuJoCo (CPU vs Warp). These are separate concepts serving different purposes - the PR rename only applies to the solver parameter, not the Example class attributes.

Applied to files:

  • newton/_src/solvers/mujoco/solver_mujoco.py
📚 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). (2)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
🔇 Additional comments (5)
newton/_src/solvers/mujoco/solver_mujoco.py (5)

1498-1502: LGTM on clarifying comments.

The added comments make the CPU vs Warp branches clearer.

Also applies to: 1503-1507


1543-1547: LGTM on clarifying comments.

Clearer distinction between MjWarp Data and MjData paths.

Also applies to: 1551-1558


2478-2489: nconmax derived from runtime ncon — can under‑allocate and trigger put_data errors; clamp to model.nconmax and scale.

Using self.mj_data.ncon (current contact count) as the default/lower bound can be 0 at init and below MuJoCo’s compiled limit; mujoco_warp.put_data will reject too‑small allocations. Use self.mj_model.nconmax per‑env, scale by nworld, and warn without the “[WARNING]” prefix.

Apply:

-                if ncon_per_env is not None:
-                    rigid_contact_max = nworld * ncon_per_env
-                    if rigid_contact_max < self.mj_data.ncon * nworld:
-                        warnings.warn(
-                            f"[WARNING] Value for ncon_per_env is changed from {ncon_per_env} to {self.mj_data.ncon} following an MjWarp requirement.",
-                            stacklevel=2,
-                        )
-                        nconmax = self.mj_data.ncon * nworld
-                    else:
-                        nconmax = rigid_contact_max
-                else:
-                    nconmax = self.mj_data.ncon * nworld
+                if ncon_per_env is not None:
+                    mj_nconmax = int(self.mj_model.nconmax)
+                    if ncon_per_env < mj_nconmax:
+                        warnings.warn(
+                            f"ncon_per_env={ncon_per_env} is below MuJoCo per-env nconmax ({mj_nconmax}); clamping to {mj_nconmax}.",
+                            stacklevel=2,
+                        )
+                        ncon_per_env = mj_nconmax
+                    nconmax = ncon_per_env * nworld
+                else:
+                    nconmax = int(self.mj_model.nconmax) * nworld
#!/bin/bash
rg -nC2 -e '\bncon\b' --type=py

2490-2499: njmax clamped to Data.nefc and not scaled — risks under‑allocation; use model.njmax and scale by nworld.

Baseline must be self.mj_model.njmax (per‑env). Also compute a total (* nworld) for put_data. Current behavior can be below model size and fail; also repeats a previously flagged issue.

Apply:

-            if njmax is not None:
-                if njmax < self.mj_data.nefc:
-                    warnings.warn(
-                        f"[WARNING] Value for njmax is changed from {njmax} to {self.mj_data.nefc} following an MjWarp requirement.",
-                        stacklevel=2,
-                    )
-                    njmax = self.mj_data.nefc
-            else:
-                njmax = self.mj_data.nefc
+            if njmax is not None:
+                mj_njmax = int(self.mj_model.njmax)
+                if njmax < mj_njmax:
+                    warnings.warn(
+                        f"njmax={njmax} is below MuJoCo per-env njmax ({mj_njmax}); clamping to {mj_njmax}.",
+                        stacklevel=2,
+                    )
+                    njmax = mj_njmax
+                njmax_total = njmax * nworld
+            else:
+                njmax_total = int(self.mj_model.njmax) * nworld

And pass the total to MjWarp:

-                njmax=njmax,
+                njmax=njmax_total,
#!/bin/bash
rg -n 'nefc|njmax_total|put_data\(.+njmax=' --type=py

1239-1241: Clarify MuJoCo/MjWarp defaults, per‑env scaling, and upward clamping in docstrings

Replace the two parameter descriptions so they state defaults come from the MuJoCo/MjWarp model/data limits, are per‑environment and scaled by nworld, and that user values below the model limit are clamped upward with a warning.

-            njmax (int): Maximum number of constraints per environment (world). If None, a default value is estimated from the initial state. Note that the larger of the user-provided value or the default value is used.
-            ncon_per_env (int | None): Number of contact points per environment (world). If None, a default value is estimated from the initial state. Note that the larger of the user-provided value or the default value is used.
+            njmax (int | None): Maximum number of scalar constraint rows per environment. If None, defaults to the MuJoCo/MjWarp data/model limit (``mj_data.nefc``). The per-env value is scaled by the number of worlds (``nworld``) when allocating in MuJoCo Warp; if a user value is lower than the model limit it is clamped upward and a warning is emitted.
+            ncon_per_env (int | None): Maximum number of contacts per environment. If None, defaults to the MuJoCo/MjWarp data/model limit (``mj_data.nconmax``). The per-env value is scaled by ``nworld`` for allocation; user values below the model limit are clamped upward with a warning.

Also update the Migration Guide (docs/) to mention this behaviour. Quick check command:
rg -n 'njmax|nconmax|ncon_per_env' docs/ -S || true

Comment thread newton/_src/solvers/mujoco/solver_mujoco.py

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

2501-2506: Pass totals to put_data (per‑env × nworld).

Wire up the totals computed above.

-                nconmax=nconmax,
-                njmax=njmax,
+                nconmax=nconmax_total if not disable_contacts else 0,
+                njmax=njmax_total,
🧹 Nitpick comments (4)
newton/_src/solvers/mujoco/solver_mujoco.py (4)

1239-1241: Clarify docstrings: per‑env semantics, model‑based defaults, clamp behavior.

Current text suggests “estimated from initial state”; defaults should come from MuJoCo model limits and be lower‑bounded to them. Suggested rewrite:

-            njmax (int): Maximum number of constraints per environment (world). If None, a default value is estimated from the initial state. Note that the larger of the user-provided value or the default value is used.
-            ncon_per_env (int | None): Number of contact points per environment (world). If None, a default value is estimated from the initial state. Note that the larger of the user-provided value or the default value is used.
+            njmax (int | None): Maximum number of scalar constraints per environment. If None, uses the MuJoCo model limit (MjModel.njmax). Values below the model limit are clamped up. Internally multiplied by the number of worlds.
+            ncon_per_env (int | None): Maximum number of contacts per environment. If None, uses the MuJoCo model limit (MjModel.nconmax). Values below the model limit are clamped up. Internally multiplied by the number of worlds.

1498-1506: Typo: “MuJoCo” capitalization.

Use “MuJoCo” in comments for consistency.

-            # we have an MjWarp Data object
+            # we have an MjWarp Data object
...
-            # we have an MjData object from Mujoco
+            # we have an MjData object from MuJoCo

1543-1558: Typo: “MuJoCo” capitalization.

Same nit here.

-            # we have an MjData object from Mujoco
+            # we have an MjData object from MuJoCo

2481-2483: Warning messages: drop “[WARNING]” prefix; message already comes via warnings.warn.

Optional polish for consistency.

-                        f"[WARNING] Value for ncon_per_env is changed from {ncon_per_env} to {self.mj_data.ncon} following an MjWarp requirement.",
+                        f"ncon_per_env={ncon_per_env} is below MuJoCo estimate; clamping to {self.mj_data.ncon}.",
...
-                        f"[WARNING] Value for njmax is changed from {njmax} to {self.mj_data.nefc} following an MjWarp requirement.",
+                        f"njmax={njmax} is below MuJoCo estimate; clamping to {self.mj_data.nefc}.",

Also applies to: 2493-2495

📜 Review details

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3269d8 and ab73ce2.

📒 Files selected for processing (1)
  • newton/_src/solvers/mujoco/solver_mujoco.py (6 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-19T12:27:30.629Z
Learnt from: preist-nvidia
PR: newton-physics/newton#579
File: newton/examples/example_mujoco.py:350-354
Timestamp: 2025-08-19T12:27:30.629Z
Learning: In Newton examples, Example.use_mujoco is a high-level attribute that controls whether to use MuJoCo solver vs other solvers (like XPBD), while SolverMuJoCo.use_mujoco_cpu controls the backend within MuJoCo (CPU vs Warp). These are separate concepts serving different purposes - the PR rename only applies to the solver parameter, not the Example class attributes.

Applied to files:

  • newton/_src/solvers/mujoco/solver_mujoco.py
📚 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). (2)
  • GitHub Check: Run GPU Unit Tests on AWS EC2 (Pull Request)
  • GitHub Check: Run GPU Benchmarks (Pull Request)
🔇 Additional comments (4)
newton/_src/solvers/mujoco/solver_mujoco.py (4)

1213-1215: Making njmax optional is good.

Signature change aligns with PR goals; matches convert_to_mjc parameter. LGTM.


1764-1766: Units mismatch: comment says “per world”. It’s per‑env; put_data needs totals.

Align comment with per‑env semantics and ensure code multiplies by nworld before passing to put_data.

-        njmax: int | None = None,  # number of constraints per world
+        njmax: int | None = None,  # max scalar constraints per environment (per‑env; scaled by nworld)

2490-2499: njmax clamp must use MjModel.njmax (model limit) and scale per‑env to total; current use of Data.nefc is unsafe.

nefc is the current number of efc rows, not the capacity. This can under‑allocate and crash under different contact/limit configurations.

-            if njmax is not None:
-                if njmax < self.mj_data.nefc:
-                    warnings.warn(
-                        f"[WARNING] Value for njmax is changed from {njmax} to {self.mj_data.nefc} following an MjWarp requirement.",
-                        stacklevel=2,
-                    )
-                    njmax = self.mj_data.nefc
-            else:
-                njmax = self.mj_data.nefc
+            mj_njmax = int(self.mj_model.njmax)
+            if njmax is not None:
+                if njmax < mj_njmax:
+                    warnings.warn(
+                        f"njmax={njmax} is below MuJoCo njmax per env ({mj_njmax}); clamping to {mj_njmax}.",
+                        stacklevel=2,
+                    )
+                    njmax = mj_njmax
+                njmax_total = njmax * nworld
+            else:
+                njmax_total = mj_njmax * nworld

1239-1241: Docs/tests/migration: verify all call sites and docs align with per‑env semantics and clamping.

  • Confirmed in code: ncon_per_env is multiplied by number of environments (rigid_contact_max = nworld * ncon_per_env — newton/_src/solvers/mujoco/solver_mujoco.py:2478) and njmax is clamped to MuJoCo limits with warning/assignment logic (newton/_src/solvers/mujoco/solver_mujoco.py:2490–2496). Constructor docstring already refers to "per environment (world)" (newton/_src/solvers/mujoco/solver_mujoco.py:1239–1240).
  • Action required: ensure examples/tests and the migration guide explicitly state that njmax and ncon_per_env are per‑env, that ncon_per_env is scaled by num_envs, and that both are clamped to MuJoCo model limits. Check/update call sites such as newton/tests/test_equality_constraints.py:48–49, newton/tests/test_mujoco_solver.py:309, newton/tests/test_rigid_contact.py:185 and newton/examples/sensors/example_sensor_contact.py:142, and add/update docs/migration.rst accordingly.

Comment thread newton/_src/solvers/mujoco/solver_mujoco.py
@preist-nvidia preist-nvidia enabled auto-merge (squash) September 23, 2025 12:25
@preist-nvidia preist-nvidia merged commit 73b2f2e into newton-physics:main Sep 23, 2025
12 checks passed
@adenzler-nvidia

Copy link
Copy Markdown
Member

I have 2 additional comments here - not sure if we need to address but here we go:

  • why are we even using put_data here? shouldn't make_data be enough, as we are updating the state from newton state anyway?
  • I'm not sure if mjd.nefc is a good value for default number of constraints. If we are spawning the assets correctly (in non-penetrated state) this default will lead to overflow as soon as you have an additional overflow. In addition, joint limit constraints are also added/removed based on the joint positions. I would add some slack onto that value.

@Kenny-Vilella Kenny-Vilella deleted the dev/kvilella/improve_njmax_ncon_logic branch September 30, 2025 01:03
@Kenny-Vilella

Copy link
Copy Markdown
Member Author

@adenzler-nvidia Yeah it's the same issue that with MjWarp, it's quite difficult to get default value that makes sense for all cases.
Here, we kind of put the burden on the user to find and optimize the value by himself.

@coderabbitai coderabbitai Bot mentioned this pull request Dec 31, 2025
4 tasks
eric-heiden pushed a commit to eric-heiden/newton that referenced this pull request Jan 28, 2026
Co-authored-by: Philipp Reist <66367163+preist-nvidia@users.noreply.github.com>
mmacklin pushed a commit to mmacklin/newton that referenced this pull request Apr 7, 2026
Co-authored-by: Philipp Reist <66367163+preist-nvidia@users.noreply.github.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.

3 participants