Fix equality constraints handling on collapse fixed joints#1233
Conversation
📝 WalkthroughWalkthroughcollapse_fixed_joints now tracks bodies referenced by equality constraints, conditionally skips certain merges, remaps constraint body/joint indices and transforms after merges, disables constraints referencing removed joints, and returns additional merge-state fields ( Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🧰 Additional context used🧠 Learnings (3)📚 Learning: 2025-08-18T15:56:26.587ZApplied to files:
📚 Learning: 2025-09-22T21:08:31.901ZApplied to files:
📚 Learning: 2025-08-12T18:04:06.577ZApplied to files:
🧬 Code graph analysis (1)newton/_src/sim/builder.py (2)
🔇 Additional comments (3)
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. Comment |
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
newton/_src/sim/builder.py (1)
3205-3258: Equality-constraint remap and frame transforms are consistent; consider adding coverage for anchors/relposeThe post-collapse remap of equality constraints looks logically sound:
- Bodies are mapped either directly via
body_remapor viabody_merged_parent(including merges into world via-1), so no body index is left dangling.- For weld/connect constraints, the anchor and
relposeupdates implement the expected frame change:
- If body1 is merged:
anchor' = T1 * anchor,relpose' = T1 * relpose.- If body2 is merged (weld):
relpose' = relpose * inv(T2).- Together this matches
relpose' = T1 * relpose * T2^{-1}when both sides are merged.- Joint indices are remapped via
joint_remap, and constraints referencing removed joints are safely disabled viaequality_constraint_enabled.Given the complexity, it would be worthwhile to extend tests to cover non-zero anchors and non-identity weld
relpose(e.g., rotated/translated bodies) to validate that the transformed anchor/relpose behave as expected after collapsing.newton/tests/test_equality_constraints.py (1)
210-332: Good coverage of index remap; you may want to also validate anchor/relpose transformsThis test nicely exercises
collapse_fixed_jointswith connect, joint, and weld equality constraints, and verifies that body/joint indices and counts are remapped as expected both on the builder and the finalized model.To fully cover the new remap logic in
collapse_fixed_joints, consider extending this test (or adding a sibling test) to:
- Use a non-zero weld anchor and a non-identity
relposebetweenlink2andlink3, and- Assert that the post-collapse
equality_constraint_anchorandequality_constraint_relposematch the analytically expected transforms afterlink2is merged intolink1.That would catch any future regressions in the frame-change math, not just index remapping.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
newton/_src/sim/builder.py(3 hunks)newton/tests/test_equality_constraints.py(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 1107
File: newton/_src/solvers/mujoco/kernels.py:973-974
Timestamp: 2025-11-24T08:05:21.390Z
Learning: In Newton's MuJoCo solver integration (newton/_src/solvers/mujoco/), the mapping between Newton joints and MuJoCo joints is not 1-to-1. Instead, each Newton DOF maps to a distinct MuJoCo joint. This means that for multi-DOF joints (like D6 with 6 DOFs), there will be 6 corresponding MuJoCo joints, each with its own properties (margin, solimp, solref, etc.). The mapping is done via dof_to_mjc_joint array, ensuring each DOF's properties are written to its own MuJoCo joint without overwriting.
📚 Learning: 2025-09-22T21:03:39.624Z
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/_src/sim/ik/ik_lbfgs_optimizer.py:739-752
Timestamp: 2025-09-22T21:03:39.624Z
Learning: The L-BFGS optimizer in newton/_src/sim/ik/ik_lbfgs_optimizer.py currently intentionally only supports additive updates (assuming n_coords == n_dofs). Velocity space integration for joints with mismatched coordinate/DOF dimensions (like free/ball joints) is planned for future work and should not be flagged as an issue in current reviews.
Applied to files:
newton/_src/sim/builder.py
📚 Learning: 2025-11-24T08:05:21.390Z
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 1107
File: newton/_src/solvers/mujoco/kernels.py:973-974
Timestamp: 2025-11-24T08:05:21.390Z
Learning: In Newton's MuJoCo solver integration (newton/_src/solvers/mujoco/), the mapping between Newton joints and MuJoCo joints is not 1-to-1. Instead, each Newton DOF maps to a distinct MuJoCo joint. This means that for multi-DOF joints (like D6 with 6 DOFs), there will be 6 corresponding MuJoCo joints, each with its own properties (margin, solimp, solref, etc.). The mapping is done via dof_to_mjc_joint array, ensuring each DOF's properties are written to its own MuJoCo joint without overwriting.
Applied to files:
newton/_src/sim/builder.py
📚 Learning: 2025-08-20T18:02:36.099Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 587
File: newton/_src/sim/builder.py:656-661
Timestamp: 2025-08-20T18:02:36.099Z
Learning: In Newton physics, collisions between shapes with body index -1 (world-attached/global shapes) are automatically skipped by the collision system, so no manual collision filter pairs need to be added between global shapes from different builders.
Applied to files:
newton/_src/sim/builder.py
🧬 Code graph analysis (2)
newton/tests/test_equality_constraints.py (1)
newton/_src/sim/builder.py (7)
key(395-397)add_equality_constraint_connect(2655-2684)add_equality_constraint_joint(2686-2715)add_equality_constraint_weld(2717-2752)body_count(939-943)joint_count(946-950)collapse_fixed_joints(2887-3266)
newton/_src/sim/builder.py (1)
newton/_src/sim/joints.py (2)
JointType(20-44)EqType(82-97)
⏰ 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). (1)
- GitHub Check: Run GPU Benchmarks / Run GPU Benchmarks on AWS EC2
🔇 Additional comments (1)
newton/_src/sim/builder.py (1)
2967-2976: Skip-merge guard for equality-constrained bodies into world looks correctThe
bodies_in_constraintsset plusshould_skip_merge = child_body in bodies_in_constraints and last_dynamic_body == -1correctly prevents bodies referenced by equality constraints from being collapsed into the world, while still allowing them to be merged into other dynamic parents (which are then handled via the remap logic below). The fixed-joint skip path falls through to the existing “retain joint” branch, preserving the fixed joint as intended.No functional issues spotted here.
Also applies to: 2991-3005
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
newton/tests/test_equality_constraints.py (1)
210-331: New collapse/equality test is well-structured; consider tightening assertions and removing printsThe scenario (fixed–revolute–fixed–revolute chain plus connect/joint/weld constraints) is a good minimal repro for the new collapse logic and validates remapping of both body and joint indices, as well as post-finalize counts.
Two optional refinements:
- Add explicit checks that the three constraints remain enabled after collapse, e.g.
self.assertTrue(builder.equality_constraint_enabled[eq_connect])(similarly foreq_jointandeq_weld) to guard against future changes that might silently disable them while still keeping the count at 3.- Drop the
print(...)at lines 331 and nearby or convert them to comments; they add noise to test output without affecting coverage.newton/_src/sim/builder.py (1)
2967-3005: Equality-referenced bodies blocking world merges looks correct; optional tweak re disabled constraintsThe
bodies_in_constraintsset andshould_skip_mergecondition:bodies_in_constraints = set() for i in range(len(self.equality_constraint_body1)): ... should_skip_merge = child_body in bodies_in_constraints and last_dynamic_body == -1correctly implement “don’t collapse a fixed joint into world when its child body is mentioned by any equality constraint”, matching the PR intent and the new test.
One optional consideration: currently even constraints with
equality_constraint_enabled[i] == Falsestill prevent collapse into world, since you don’t filter on the enabled flag when populatingbodies_in_constraints. That’s a safe, conservative choice; if the intent is that disabled constraints should be completely ignored for topology decisions, you could restrict the set to enabled constraints only.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
newton/_src/sim/builder.py(3 hunks)newton/tests/test_equality_constraints.py(1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 1107
File: newton/_src/solvers/mujoco/kernels.py:973-974
Timestamp: 2025-11-24T08:05:21.390Z
Learning: In Newton's MuJoCo solver integration (newton/_src/solvers/mujoco/), the mapping between Newton joints and MuJoCo joints is not 1-to-1. Instead, each Newton DOF maps to a distinct MuJoCo joint. This means that for multi-DOF joints (like D6 with 6 DOFs), there will be 6 corresponding MuJoCo joints, each with its own properties (margin, solimp, solref, etc.). The mapping is done via dof_to_mjc_joint array, ensuring each DOF's properties are written to its own MuJoCo joint without overwriting.
Learnt from: dylanturpin
Repo: newton-physics/newton PR: 806
File: newton/_src/sim/ik/ik_lbfgs_optimizer.py:739-752
Timestamp: 2025-09-22T21:03:39.624Z
Learning: The L-BFGS optimizer in newton/_src/sim/ik/ik_lbfgs_optimizer.py currently intentionally only supports additive updates (assuming n_coords == n_dofs). Velocity space integration for joints with mismatched coordinate/DOF dimensions (like free/ball joints) is planned for future work and should not be flagged as an issue in current reviews.
📚 Learning: 2025-08-20T18:02:36.099Z
Learnt from: eric-heiden
Repo: newton-physics/newton PR: 587
File: newton/_src/sim/builder.py:656-661
Timestamp: 2025-08-20T18:02:36.099Z
Learning: In Newton physics, collisions between shapes with body index -1 (world-attached/global shapes) are automatically skipped by the collision system, so no manual collision filter pairs need to be added between global shapes from different builders.
Applied to files:
newton/_src/sim/builder.py
🧬 Code graph analysis (2)
newton/tests/test_equality_constraints.py (1)
newton/_src/sim/builder.py (8)
key(395-397)add_articulation(1034-1137)add_equality_constraint_connect(2655-2684)add_equality_constraint_joint(2686-2715)add_equality_constraint_weld(2717-2752)body_count(939-943)collapse_fixed_joints(2887-3266)finalize(5403-5908)
newton/_src/sim/builder.py (1)
newton/_src/sim/joints.py (2)
JointType(20-44)EqType(82-97)
🔇 Additional comments (1)
newton/_src/sim/builder.py (1)
3205-3258: Weld equality anchor remapping appears to use the wrong body frameFor WELD constraints, the anchor is documented as relative to
body2, but the current remapping logic transforms it whenbody1_was_mergedand never whenbody2_was_merged. This is inconsistent with the documented semantics.The issue: weld anchors will be misplaced whenever the anchored body (
body2) is merged, while anchors are incorrectly modified when onlybody1is merged.For CONNECT constraints (where the anchor is on
body1), the current logic is correct.The proposed fix specializes anchor remapping per constraint type:
- CONNECT: Transform anchor only when
body1_was_merged- WELD: Transform anchor only when
body2_was_merged, while updatingrelposewhen either body is merged
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
newton/tests/test_equality_constraints.py (1)
210-331: Good targeted regression test; consider also validating anchors/relpose.The scenario and assertions around body/joint remapping after
collapse_fixed_joints()look solid and exercise the new equality-constraint handling. To fully cover the new logic, you might also assert that the CONNECT/WELD anchors and the WELDrelposeare transformed consistently with the expected merge transforms (especially for thelink2→link1weld), since that code is subtle and easy to regress.newton/_src/sim/builder.py (1)
2967-3005: Skip‑merge heuristic for constrained bodies looks reasonable; consider its scope.Using
bodies_in_constraintsto prevent collapsing a constrained child body directly into world (last_dynamic_body == -1) makes sense and matches the goal of preserving bodies that appear explicitly in equality constraints. A couple of points to keep in mind:
- This logic currently ignores
equality_constraint_enabled; even disabled constraints will block world merges. That’s conservative but may be more restrictive than needed if users programmatically disable constraints before collapsing.- The heuristic is intentionally narrow: constrained bodies can still be merged into another dynamic body, relying on the post‑pass remap to keep constraints consistent. That matches the new tests, but it’d be good to double‑check that this is the intended behavior for all constraint types (CONNECT/WELD/JOINT) and that no callers rely on constrained bodies staying as separate links in those cases.
If that behavior is intentional, a short comment near
should_skip_mergeclarifying “only protect from merging into world; merges into other dynamics are remapped later” would make the design intent explicit.
📜 Review details
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
newton/_src/sim/builder.py(3 hunks)newton/tests/test_equality_constraints.py(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: adenzler-nvidia
Repo: newton-physics/newton PR: 1107
File: newton/_src/solvers/mujoco/kernels.py:973-974
Timestamp: 2025-11-24T08:05:21.390Z
Learning: In Newton's MuJoCo solver integration (newton/_src/solvers/mujoco/), the mapping between Newton joints and MuJoCo joints is not 1-to-1. Instead, each Newton DOF maps to a distinct MuJoCo joint. This means that for multi-DOF joints (like D6 with 6 DOFs), there will be 6 corresponding MuJoCo joints, each with its own properties (margin, solimp, solref, etc.). The mapping is done via dof_to_mjc_joint array, ensuring each DOF's properties are written to its own MuJoCo joint without overwriting.
🧬 Code graph analysis (1)
newton/_src/sim/builder.py (1)
newton/_src/sim/joints.py (2)
JointType(20-44)EqType(82-97)
Description
Newton Migration Guide
Please ensure the migration guide for warp.sim users is up-to-date with the changes made in this PR.
docs/migration.rstis up-to dateBefore your PR is "Ready for review"
newton/tests/test_examples.py)pre-commit run -aSummary by CodeRabbit
Bug Fixes
Tests
✏️ Tip: You can customize this high-level summary in your review settings.