Skip to content

[BUG FIX] Refine IPC Coupling Logic#2502

Draft
ACMLCZH wants to merge 34 commits intoGenesis-Embodied-AI:mainfrom
ACMLCZH:feature/ipc-sap-style-coupling
Draft

[BUG FIX] Refine IPC Coupling Logic#2502
ACMLCZH wants to merge 34 commits intoGenesis-Embodied-AI:mainfrom
ACMLCZH:feature/ipc-sap-style-coupling

Conversation

@ACMLCZH
Copy link
Collaborator

@ACMLCZH ACMLCZH commented Mar 5, 2026

Summary

  • SAP-style substep split: Replaces the old pre/post-coupling substep with a predict → couple → restore → integrate flow. Pre-coupling runs step_1 + constraint + kernel_predict_integrate (saves vel_prev/qpos_prev, predicts qpos = qpos + (vel + acc·dt)·dt, runs FK). Post-coupling runs kernel_restore_integrate (back-computes velocity/acceleration from coupling-modified qpos) + step_2 (integration + FK).
  • Support set_qpos for IPC entities: now external articulation IPC entities can track qpos jumps correctly.
  • IPC Collider detection now align with RigidSolver's enable_self_collision options.
  • Eliminates set_qpos / set_dofs_velocity: All coupling types (IPC-only, two-way, external articulation) now write target positions directly into qpos during the coupling phase. kernel_restore_integrate then derives the velocity needed for step_2 to land on those targets. No more explicit set_qpos calls, collider resets, or redundant FK passes.
  • Requirement of explicitly setting coup_type when need_coup=True in IPC.
  • Predicted-pose animator targets: IPC's animator now receives predicted link transforms (where bodies will be after integration) instead of current transforms, making contact resolution consistent with step_2 output.
  • External articulation uses qpos_prev: delta_theta_tilde is now computed as qpos_predicted - qpos_prev instead of qpos_current - ref_dof_prev, eliminating the need for ref_dof_prev state.

ACMLCZH and others added 3 commits March 4, 2026 23:49
Replace the old post-coupling full-substep approach with an SAP-style split
to prevent post-coupling re-penetration:

Pre-coupling: step_1 + constraint_force + kernel_predict_integrate
  - New kernel_predict_integrate: saves vel_prev/qpos_prev, computes
    vel_predicted and qpos_predicted (with quaternion handling)
  - FK on predicted qpos gives predicted link transforms for IPC

Post-coupling: kernel_restore_integrate + step_2
  - New kernel_restore_integrate: back-computes velocity from qpos delta
    (including inverse quaternion for free/spherical joints), derives
    effective acceleration, restores vel and qpos to originals
  - step_2 integrates naturally to land on IPC's target positions

IPC coupler changes:
  - All coupling types now write IPC targets directly to qpos instead
    of using set_qpos (avoids FK/collider/constraint solver resets)
  - Two-way: writes IPC-resolved base link transforms to qpos
  - External articulation: writes qpos_prev + delta_theta_ipc to qpos,
    uses qpos_prev instead of ref_dof_prev (handles external set_qpos)
  - IPC-only: writes IPC transforms to qpos
  - Removed ref_dof_prev from ArticulatedEntityData (use qpos_prev)
  - Removed update_coupling_forces import (force-based coupling replaced)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
for env_idx in envs_idx if scene.n_envs > 0 else (slice(None),):
corr = np.corrcoef(cur_dof_pos_history[env_idx], target_dof_pos_history)[0, 1]
assert corr > 1.0 - 5e-3
assert corr > 1.0 - 1e-2
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

For non-fix articulations, the entity will fall on ground and bounce, with weak coupling perturbs tracking.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That is not a good reason. it was passing before. Why is it not passing anymore?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fail to resolve this. Added an FIXME and some explanations.

@ACMLCZH ACMLCZH force-pushed the feature/ipc-sap-style-coupling branch from 13b692f to d95ba5c Compare March 6, 2026 04:29
ACMLCZH and others added 4 commits March 5, 2026 20:33
Resolve conflicts:
- rigid_entity.py: use super() from upstream, keep set_qpos_changed + removal of ext_art exception
- rigid_solver.py: take upstream's refactored init (removed _init_dof_fields/_init_link_fields)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Mar 6, 2026

🔴 Benchmark Regression Detected ➡️ Report

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link

github-actions bot commented Mar 8, 2026

🔴 Benchmark Regression Detected ➡️ Report

"""

from enum import IntEnum
from __future__ import annotations
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove this. We don't use this in Genesis.

# only the filtered links are in IPC; for all other coupling modes, all links are in IPC.
from genesis.engine.couplers import IPCCoupler
from genesis.engine.couplers.ipc_coupler.data import COUPLING_TYPE
from genesis.utils.mesh import are_meshes_overlapping
Copy link
Collaborator

Choose a reason for hiding this comment

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

No local import for this.

Comment on lines 244 to +245
from genesis.engine.couplers import IPCCoupler
from genesis.engine.couplers.ipc_coupler.data import COUPLING_TYPE
Copy link
Collaborator

Choose a reason for hiding this comment

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

from genesis.engine.couplers.ipc_coupler.data import IPCCoupler, COUPLING_TYPE

Comment on lines 248 to 271
Copy link
Collaborator

Choose a reason for hiding this comment

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

This mechanism should be centralised in coupler.

target_dof_pos = SCALE * np.sin((2 * math.pi * FREQ) * scene.sim.cur_t)
target_dof_vel = SCALE * (2 * math.pi * FREQ) * np.cos((2 * math.pi * FREQ) * scene.sim.cur_t)
robot.control_dofs_position_velocity(target_dof_pos, target_dof_vel, dofs_idx_local=-1)
# robot.control_dofs_position(target_dof_pos, dofs_idx_local=-1)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove this.

Comment on lines -67 to -86
def _animate_rigid_link(coupler_ref, link, env_idx, info):
"""Animator callback for a soft-constraint coupled rigid link.

Uses a weakref to the coupler to avoid preventing garbage collection.
"""
coupler = coupler_ref()
if coupler is None:
gs.raise_exception("IPCCoupler was garbage collected while animator callback is still active.")

geom_slots = info.geo_slots()
if not geom_slots:
return
geom = geom_slots[0].geometry()

# Enable constraint and set target transform (q_genesis^n)
is_constrained_attr = geom.instances().find(uipc.builtin.is_constrained)
aim_transform_attr = geom.instances().find(uipc.builtin.aim_transform)
assert is_constrained_attr and aim_transform_attr
uipc.view(is_constrained_attr)[0] = 1
uipc.view(aim_transform_attr)[:] = coupler._abd_transforms_by_link[link][env_idx]
Copy link
Collaborator

@duburcqa duburcqa Mar 9, 2026

Choose a reason for hiding this comment

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

Please avoid removing stuffs you don't understand without asking and reading the documentation.

@duburcqa duburcqa closed this Mar 9, 2026
@duburcqa duburcqa reopened this Mar 9, 2026
@duburcqa duburcqa closed this Mar 9, 2026
@duburcqa duburcqa reopened this Mar 9, 2026
@duburcqa duburcqa marked this pull request as draft March 9, 2026 20:34
IPC natively computes inelastic contact (e=0). This adds a configurable
restitution parameter that accumulates velocity corrections during active
contact and applies e * total as a single impulse when contact ends,
avoiding the e^N -> 0 decay that per-frame restitution causes for e<1.

Verified for equal and asymmetric masses (1:3 ratio) at e=0, 0.5, 1.0
with ~2% error vs analytical solutions. All 51 tests pass.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ACMLCZH ACMLCZH force-pushed the feature/ipc-sap-style-coupling branch from dbeeb2d to a669b08 Compare March 9, 2026 22:11
ACMLCZH and others added 4 commits March 10, 2026 02:36
Restitution: switch from accumulate-and-flush to per-frame application for
base links. Each frame applies Δv = e * (q_solved - q_pred) / dt directly,
removing RestitutionTracker, _flush_restitution_trackers, and joint-level
restitution (only base links get restitution now). Default restitution=1.0.
test_single_joint sets restitution=0.0 since it tests joint tracking, not
bouncing, and IPC's loose newton_tolerance can't fully resolve penetration
when elastic bouncing changes the trajectory.

ABD dirty tracking: replace global _is_abd_updated bool with per-(link, env)
tracking. Build lookup tables (_q_to_abd_link, _dof_to_abd_link,
_link_to_abd_link) during _add_rigid_geoms_to_ipc for O(1) marking.
mark_abd_updated accepts qs_idx, dofs_idx, or links_idx so each set_qpos /
set_dofs_position / set_base_links_pos/quat call only marks affected ABD
links, preventing cross-entity state corruption.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@alanray-tech
Copy link
Contributor

Observation Conclusion & Solution for the BUG

The asset two_cube_revolute for test has a large drift in mass center w.r.t its collision mesh, currently is not fulfilling the IPC assumption
FK should be applied after coupling, so that the transform of link can be updated
The reason why the joints don't provide a bending behaviour after IPC coupling is that IPC know nothing about the joint info, when there is a rotation of links and the links are ready to go beneath the ground, IPC just move to align with the ground.

The video:

QQ2026311-152848-HD.mp4

RED: States after rigid-solving before ipc solving.
BLUE: States in IPC
GREEN: projection (feedback) to rigid-solver

Clarification

Current soft-two-way-coupling is set_qpos directly, no longer rely on the applying force.

This my misunderstanding before.

Short Term Solution

Only allow users to use two-way-coupling for end effector, (our initial design for two way coupling)

Long Term Improvement

Add the joint information in IPC, so that the coupling can be much better which means we can fully solve the artifact in the video.

Takeaway

Current implement of two-way-coupling is "link-local" any global behaviour requiring the structure of the joint chain can cause artifacts
Only add it to end effector for current version.

alanray-tech and others added 7 commits March 11, 2026 19:00
- Extract `compute_inertial_from_geoms` from `RigidLink._build()` into a
  reusable module-level function in `rigid_link.py`. Handles all primitive
  types analytically (SPHERE, ELLIPSOID, CYLINDER, CAPSULE, BOX) and falls
  back to trimesh for MESH type. Used by both `_build()` and per-variant
  inertial computation.

- Move variant tracking (geom/vgeom ranges) from entity-level `variants_*`
  lists to per-link `_variant_vgeom_ranges` (KinematicLink) and
  `_variant_geom_ranges` (RigidLink). Per-variant inertial properties are
  computed at build time from actual geom objects in `RigidLink._build()`.

- Replace 4 entity hooks (`_init_heterogeneous_tracking`,
  `_record_first_variant`, `_process_heterogeneous_variant`,
  `_record_variant_ranges`) with a single `_add_heterogeneous_variant` hook
  that RigidEntity overrides to add collision geoms.

- Delete `compute_inertial_from_geom_infos` (mesh-only, redundant with the
  better analytic computation in `_build()`), and all dead entity-level
  attributes: `variants_link_start/end/n_links`, `_first_variant_n_geoms`,
  `_first_variant_n_vgeoms`, `_first_g_infos`.

- Replace `_process_heterogeneous_link_info` in both solvers with
  `_dispatch_heterogeneous_vgeoms` inlined at end of `_init_link_fields`.
  RigidSolver overrides to also dispatch geom ranges and inertial.
  Extract `_balanced_variant_mapping` as shared helper.

Net: -193 lines, cleaner data ownership, no behavior change.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link

🔴 Benchmark Regression Detected ➡️ Report

- collider.py: keep auto-inference + COUPLING_TYPE enum with vectorized index-based filtering
- solvers.py: keep new IPC coupling fields (restitution, ignore_end_effector_check, export options), remove deprecated coupling params and _show_ipc_gui

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.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