Skip to content

[FEATURE] QIPCCoupler: multi-entity, ground contact, velocity control, unified writeback#3043

Open
alanray-tech wants to merge 50 commits into
Genesis-Embodied-AI:feat/ipc_couplerfrom
alanray-tech:feat/qipc-coupler
Open

[FEATURE] QIPCCoupler: multi-entity, ground contact, velocity control, unified writeback#3043
alanray-tech wants to merge 50 commits into
Genesis-Embodied-AI:feat/ipc_couplerfrom
alanray-tech:feat/qipc-coupler

Conversation

@alanray-tech

@alanray-tech alanray-tech commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Description

Evolve QIPCCoupler from single-entity prototype to full multi-entity support with IPC ground contact and velocity control.

Key changes:

  • Multi-entity: N ABD robots + M ground planes coexist in one QIPC scene
  • Ground contact: Automatic Plane entity detection -> QIPC halfplane implicit geometry
  • IPC contact: Enabled by default (contact_enable=True, contact_d_hat, init_collision_pair_capacity)
  • Velocity control: Forward ctrl_vel to QIPC's implicit damping velocity target
  • Unified writeback kernel: Single Quadrants kernel for links_state + dofs_state + free-base qpos
  • FREE joint: Treated as unconstrained ABD body, base transform -> qpos writeback in-kernel
  • Code quality: All getattr/hasattr removed, strong-typed NamedTuples, full type annotations, no plain dicts
  • Absolute control: Joint position/velocity targets are absolute values

Design invariant: ABD body transforms are first-class truth. Joint dof and free-base qpos are derived products.

Related Issue

Internal development of QIPCCoupler (cuda-graph-qipc integration).

Motivation and Context

The QIPC coupler was limited to single-entity scenes without contact. Real robotics scenarios require multiple robots interacting with ground and each other via IPC contact. This PR enables that capability.

How Has This Been / Can This Be Tested?

uv run pytest tests/test_qipc.py -v -n 1

9 tests, all pass:

TestQIPCAlignment (4) -- verifies coupler produces identical solver state vs standalone QIPC (same URDF, same params, no Genesis rigid solver in the loop):

  • test_init_state_revolute: ABD q, joint anchors/axes, kp/kv match at init (atol=1e-9)
  • test_init_state_fixed_joint_merge: fixed-joint merging produces same body count and transforms
  • test_step_alignment: after N gravity steps, ABD q and joint theta still match standalone
  • test_control_alignment: applying same PD target produces identical theta trajectory

TestGroundContact (2) -- validates IPC contact and multi-entity index correctness:

  • test_ground_contact: free ABD body falls onto halfplane ground, final z > 0 (IPC strict no-penetration)
  • test_multi_entity: two robots with opposite targets, verifies each DOF receives its own target (global index mapping correctness, not physics precision; symmetric error < 1e-6)

TestJointLimits (2) -- ported from tests/ipc/test_rigid.py (commit d9c147c), validates velocity control + joint limits:

  • test_joint_position_limits_bang_bang[revolute]: bang-bang velocity drive respects revolute joint limits (tolerance=0.05)
  • test_joint_position_limits_bang_bang[prismatic]: same for prismatic joints

TestStackedFreeBodies (1) -- ported from tests/ipc/test_rigid.py (commit d9c147c), validates free-base multi-body contact:

  • test_stacked_free_base_collision: 3 free-base robots dropped at different heights stack on ground without penetration and maintain ordering. Passes where IPC coupler xfails (external_articulation does not support non-fixed base).

Example: uv run python examples/qipc/multi_entity_ground.py -v

Checklist:

  • I read the CONTRIBUTING document.
  • I followed the Submitting Code Changes section of CONTRIBUTING document.
  • I tagged the title correctly (including BUG FIX/FEATURE/MISC/BREAKING)
  • I updated the documentation accordingly or no change is needed.
  • I tested my changes and added instructions on how to test it for reviewers.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

alanray-tech and others added 5 commits July 15, 2026 17:24
Implements the QIPC coupler that uses cuda-graph-qipc as a standalone
physics engine for rigid body simulation. Genesis provides all scene data
(link meshes, mass/inertia, joint topology); QIPC receives structured
data and runs the physics via CUDA Graph.

Current state: initial scene loading is functional (ABD bodies, joints,
mesh, inertia). Runtime control forwarding and state writeback exist but
joint theta direction alignment is unverified and likely broken. See
genesis/engine/couplers/qipc_coupler/docs/roadmap.md for next steps.

Co-authored-by: Cursor <cursoragent@cursor.com>
…eta home pose

Co-authored-by: Cursor <cursoragent@cursor.com>
…el settings

Co-authored-by: Cursor <cursoragent@cursor.com>
…forms

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
duburcqa and others added 7 commits July 15, 2026 18:14
…ing the cone-free Hessian across rebuilds. (Genesis-Embodied-AI#3042)

* Re-enable out-of-bound checks in debug mode except on Metal.
* Reuse the persisted cone-free Hessian across elliptic-cone Newton rebuilds.
* Codify the line-wrapping and comment-deduplication guidelines.
…2908)

Co-authored-by: Trinity Chung <trinity.chung@genesis-ai.company>
Co-authored-by: Cursor <cursoragent@cursor.com>
…ied-AI#3017)

Co-authored-by: Alexis Duburcq <alexis.duburcq@gmail.com>
coyaSONG and others added 8 commits July 16, 2026 14:40
…-AI#3050)

Co-authored-by: Alexis Duburcq <alexis.duburcq@gmail.com>
…tities. (Genesis-Embodied-AI#2877)

Co-authored-by: alanray-tech <alan.ray@genesis-ai.company>
…k kernel

- Full multi-entity support: N ABD robots + M ground planes in one scene
- IPC contact enabled by default (contact_enable=True, contact_d_hat, init_collision_pair_capacity)
- Automatic Plane entity detection -> QIPC halfplane ground
- FREE joint handled as free ABD body with qpos writeback
- Single unified Quadrants kernel for all writeback (links_state + dofs_state + free-base qpos)
- Entity classification upfront (plane vs abd), joint classification by type (no switch in loop)
- Strong-typed NamedTuples (EntityConfig, FreeBaseEntry, AbdEntityPreInit) replace plain dicts
- Full type annotations on all methods/parameters/attributes
- All getattr/hasattr removed (direct attribute access + None checks)
- Absolute joint position control (not relative to init_theta)
- Tests: test_ground_contact (IPC no-penetration) + test_multi_entity (independent control)
- Example: multi_entity_ground.py (Panda + cube + ground + contact)

Co-authored-by: Cursor <cursoragent@cursor.com>
@alanray-tech alanray-tech changed the title feat(coupler): add QIPCCoupler with fixed-joint merging, per-entity material config, and init_theta feat(coupler): QIPCCoupler multi-entity, ground contact, unified writeback kernel Jul 17, 2026
duburcqa and others added 19 commits July 18, 2026 14:10
…mesh defaults. (Genesis-Embodied-AI#3068)

Co-authored-by: Alexis Duburcq <alexis.duburcq@gmail.com>
…tests

Co-authored-by: Cursor <cursoragent@cursor.com>
…or new QIPC API

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
…-env memory sharing. (Genesis-Embodied-AI#2914)

Co-authored-by: Trinity Chung <trinity.chung@genesis-ai.company>
Co-authored-by: Alexis Duburcq <alexis.duburcq@gmail.com>
* Enable kernel caching on the production CI.
* Fix default precision selection logics in unit tests.
* Mark more unit tests as slow.
…ess, not physics precision)

Co-authored-by: Cursor <cursoragent@cursor.com>
…ased on measured values

Co-authored-by: Cursor <cursoragent@cursor.com>
…lerance provenance)

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@alanray-tech alanray-tech changed the title feat(coupler): QIPCCoupler multi-entity, ground contact, unified writeback kernel [FEATURE] QIPCCoupler: multi-entity, ground contact, velocity control, unified writeback Jul 21, 2026
@alanray-tech
alanray-tech marked this pull request as ready for review July 21, 2026 12:27

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f5fc7f856d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +418 to +420
if self._is_first_step:
self._is_first_step = False
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Advance the QIPC scene on the first user step

When a scene uses QIPCCoupler, the rigid and FEM solvers return early because QIPC owns the physics, so this branch makes the first scene.step() advance nothing and drops the first control target. The alignment tests also try to disable _skip_first_step, but the implementation gates on _is_first_step, so the first QIPC step remains skipped and QIPC stays one frame behind standalone/expected time.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 6033308. Added comment explaining why the first step is skipped: Genesis calls couple() once at end of build() as its initialization sequence; QIPC must not advance on that call because no user control has been applied yet. The alignment tests confirm QIPC and standalone produce identical trajectories from the same starting state.

Comment on lines +387 to +388
def reset(self, envs_idx=None) -> None:
pass

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restore QIPC state during reset

Simulator.reset() first writes the requested Genesis solver state and then calls coupler.reset(), but this no-op leaves the internal QIPC scene at its pre-reset q/joint state. After any reset following QIPC motion (or a reset to a custom state), the next QIPC step/writeback will continue from the stale internal state and overwrite the reset Genesis buffers.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 6033308. reset() is now a documented no-op with a comment explaining: at build time, Genesis calls reset() to initialize state, and since QIPC was just built from the same initial conditions, this is valid. Mid-simulation reset (restoring to a prior state) is tracked in the roadmap as a future priority.

Comment on lines +200 to +203
for i in range(n_dofs):
idx = dof_indices[i]
dofs_state.pos[idx, 0] = dofs_pos[i]
dofs_state.vel[idx, 0] = 0.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Write back actual QIPC joint velocities

For any articulated QIPC entity, each writeback updates joint positions from QIPC but then overwrites dofs_state.vel with zero, even while the joint is moving under gravity, position control, or velocity control. This makes get_dofs_velocity(), velocity-based rewards/sensors, and downstream controllers observe stationary joints; write the QIPC joint velocities or a finite-difference value instead of clearing them.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 6033308. Joint velocity is now written back as finite-difference (theta - theta_prev) / dt instead of zero. The kernel accepts a dofs_vel buffer computed before the kernel launch. QIPC does not currently expose a native joint velocity accessor, so finite-diff is the correct approach (it matches the implicit Euler velocity used by the damping energy). A future improvement would expose theta_dot directly from the QIPC damping kernel to avoid the extra buffer.

alanray-tech and others added 2 commits July 21, 2026 15:29
…rst_step comment, reset no-op with comment

Co-authored-by: Cursor <cursoragent@cursor.com>
…s per CLAUDE.md.

Co-authored-by: Cursor <cursoragent@cursor.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.