Skip to main content

rigidax

Differentiable rigid-body kinematics and dynamics in JAX. Its design starts from frax: the dense, loop-free formulation of the dynamics over a DOF ancestor mask. It fills in frax's gaps:

frax 0.2 rigidax
Model formats URDF MJCF and URDF (compiled by MuJoCo)
Joint types revolute, prismatic, free hinge, slide, ball, free, several joints per body
Gravity hardcoded 9.81 in forward_dynamics model parameter, any vector
Physical parameters frozen into the compiled function pytree leaves: jax.grad / vmap over masses, inertias, offsets, gravity; swap them without recompiling
Armature no yes (in M and inverse dynamics)
External wrenches per-link per-body, MuJoCo xfrc_applied layout
Ground truth tests Pinocchio / MuJoCo every quantity vs MuJoCo on random trees + G1/A2/Panda, plus Pinocchio

Operational space control demo

The Panda tracks a figure-eight with operational space control (examples/osc_demo.py). rigidax computes the kinematics, Jacobian, mass matrix and bias forces in about 54 µs per step, Python dispatch included. Mean tracking error is 0.9 mm. MuJoCo only simulates and renders.

env -u PYTHONPATH MUJOCO_GL=egl python examples/osc_demo.py --out osc_demo.mp4

G1 + Dex3 hands, retargeted LAFAN1 motions

Two output variants are produced from the same solve settings. Speed on the same laptop with self-collision: 22.8k frames/s for the plain G1 and 18.8k for G1 + Dex3. The Dex3 version is slower because the finger envelope adds collision spheres. frax, with self-collision, runs at 18.3k.

Variant Command Output GIF
G1 + Dex3 hands (default) retarget_lafan1.py <clips> data/g1_lafan1_hands/ (nq 50) examples/g1_lafan1.gif
Plain G1, rigid hands retarget_lafan1.py <clips> --no-hands data/g1_lafan1_nohands/ (nq 36) examples/g1_lafan1_nohands.gif

Both variants track the forearm twist. Palm floor contact and fingers are Dex3-only. Render a variant with render_motions.py --data data/g1_lafan1_nohands ....

rigidax.ik: batched IK and retargeting

A dedicated differential IK solver for retargeting whole datasets on CPU:

  • Each Gauss-Newton step is a small QP, solved by a purpose-built fixed-iteration ADMM (rigidax.ik.solve_qp). It has no branches, so it vectorizes over thousands of frames. It uses:
    • row equilibration,
    • one explicit inverse per solve, then matrix-vector products (batched triangular solves are slow on XLA:CPU),
    • warm starts from the previous frame,
    • optional polishing.
  • Constraints:
    • hard joint limits,
    • a per-iteration trust region, so a joint can never flip across its range in one frame,
    • a hard floor (FloorPoints),
    • elastic self-collision, an exact L1 penalty that keeps the problem always feasible, on an automatic sphere model of any MuJoCo model (SphereModel.fit).
  • Tasks: position, orientation, limb direction (retargets between skeletons with different proportions) and posture.
  • Batching: solve_trajectory splits clips into segments that run in parallel (vmap within a device, pmap across devices). Each segment is seeded by a multi-start solve, and consecutive segments are crossfaded so seams are smooth.
from rigidax import ik
tasks = [ik.position(model, bodies, w), ik.direction(model, links, bases, w),
         ik.orientation(model, frames, w), ik.PostureTask(posture_w)]
solver = ik.IKSolver(model, tasks, floor=ik.FloorPoints.on(model, feet, sole_points),
                     collision=ik.SphereModel.fit(mj_model, model, pair_filter=...))
qs, costs = solver.solve_trajectory(seed_fn, [clip1_targets, clip2_targets, ...])

vs frax on LAFAN1 → Unitree G1

Both libraries ran frax's own LAFAN1 settings on the same i9-11900H laptop (8 cores, one XLA device per core), on 8 clips (53,621 frames, 30 min of motion). Both outputs are scored with the same metrics:

  • Motion kept: robot vs human limb angular speed (1.0 = all of it).
  • Lag: in frames.
  • Joint acceleration: worst 5% of frames.
  • Skating: the robot's toe contact sliding while the human toe is planted.
  • Penetration: MuJoCo's own mesh collision check.

bench/bench_frax_ik.py runs frax.

frames/s arm / leg dir. error p95 hand / forearm rot. p95 arms / legs kept lag (frames) >20° jumps skate median / p95 touchdown p95 penetration through floor
frax, self-collision 18,327 17.4° / 11.6° 13° / 20° 0.88 / 0.95 0.16 1.9% 4.6 / 32 cm/s 40 0.0% 0.6 cm max
rigidax, self-collision (plain G1) 22,836 14.0° / 11.7° 12° / 24° 0.92 / 0.94 -0.01 0.1% 0.9 / 21 35 0.0% 3.8 cm max (lying)
frax, no self-collision 37,490 17.1° / 11.3° 11° / 19° 0.88 / 0.95 0.15 1.8% 4.4 / 31 38 3.3%
rigidax, no self-collision 46,808 3.9° / 11.7° 10° / 22° 0.96 / 0.94 -0.10 0.1% 0.9 / 21 35 2.2%

The rigidax task set covers everything frax tracks: pelvis, the thigh / shin / foot links, torso, and the upper arm / forearm / hand orientations. It adds limb directions and toe contact points on top.

Where frax is still slightly ahead:

  • forearm twist (24° vs 20°),
  • leg motion kept (0.94 vs 0.95).

frax's lower joint-acceleration number (8.4 vs 9.3°/f²) is mostly smoothed-away motion. On the knees, rigidax matches the human's own knee acceleration and frax sits below it.

Hands: the output uses the G1 with Dex3-1 hands (g1_with_hands.xml, 7 finger joints per hand). The body is solved on the 35-DOF model and the finger angles are written afterwards, since nothing depends on them, which keeps the solve as fast as before.

  • Synthesized fingers: LAFAN1 records no fingers, so the grip is inferred from context and is not tracked motion:
    • fist in a fighting guard (hand in front of the chest, in the chest frame) or during a strike, in fight clips only;
    • flat open hand while the human hand rests on the floor;
    • relaxed otherwise.
  • Palm contact: a height task puts the palm on the floor while the human hand rests there. The previous hover was 6.2 cm; now it is about 0.
  • Forearm twist: a twist-only task about the forearm axis.

Diagnostics:

  • examples/render_overlay.py: draws the solver's targets over the robot (visible red means a real tracking error).
  • examples/floor_report.py: whole-body floor penetration.
  • examples/collision_report.py: self-collision.
# CPU, one XLA device per core
XLA_FLAGS="--xla_force_host_platform_device_count=8 --xla_cpu_multi_thread_eigen=false" \
  python examples/retarget_lafan1.py dance1_subject1 walk1_subject1 ...   [--no-collision]
python examples/collision_report.py dance1_subject1 ...   # MuJoCo mesh ground truth

LAFAN1 is CC BY-NC-ND 4.0 (non-commercial). Get it with:

curl -L -o data/lafan1.zip https://github.com/ubisoft/ubisoft-laforge-animation-dataset/raw/master/lafan1/lafan1.zip
unzip -j data/lafan1.zip '*.bvh' -d data/lafan1
MUJOCO_GL=egl python examples/render_motions.py --out g1_lafan1.gif dance1_subject1:DANCE \
  dance2_subject3:"DANCE 2" fightAndSports1_subject1@13.8:"FIGHT + KICK" jumps1_subject1:JUMPS \
  run1_subject2:RUN sprint1_subject2@177:SPRINT walk1_subject1:WALK fallAndGetUp1_subject1@63:"FALL + GET UP"
# clip@seconds picks the window; without it the most active 10 s are used

G1 motions side by side

G1 whole-body control demo

A free-floating Unitree G1 (35 DOF) squats, twists and waves while balancing on its feet (examples/g1_demo.py). Per step, rigidax computes:

  • M and h, the foot contact Jacobians, and Jc_dot·v (via autodiff)
  • reference accelerations that keep the feet planted (KKT solve)
  • the contact wrenches that balance the floating base
  • the feedforward joint torques

Joint PD supplies the feedback. A step takes about 190 µs. MuJoCo's position servos are replaced by pure torque motors.

env -u PYTHONPATH MUJOCO_GL=egl python examples/g1_demo.py --out g1_demo.mp4
# one motion set, frax-style grey studio: --motion squat|twist|wave|squat,wave|all --style light

Use

import jax, rigidax as rx
jax.config.update("jax_enable_x64", True)

model = rx.load("robot.xml")              # or .urdf; strip_meshes=True if meshes are missing
q, v = model.q0, jax.numpy.zeros(model.nv)

M, bias = jax.jit(rx.dynamics_terms)(model, q, v)            # one kinematics pass
qacc = jax.jit(rx.forward_dynamics)(model, q, v, tau)
moon = model.replace(gravity=jax.numpy.array([0, 0, -1.62]))  # no recompile

# gradients w.r.t. physical parameters (e.g. system identification)
dL_dmass = jax.grad(lambda m: loss(model.replace(body_mass=m)))(model.params.body_mass)

Available functions: forward_kinematics, site_pose, body_pose, point_jacobian, site_jacobian, body_jacobian, center_of_mass, center_of_mass_jacobian, rnea, mass_matrix, bias_forces, gravity_forces, coriolis_forces, dynamics_terms, forward_dynamics, mass_matrix_inverse, integrate, difference. Every function takes an optional kin= argument to reuse a forward-kinematics pass.

Conventions follow MuJoCo: q uses [pos, wxyz quat] for free joints and wxyz for ball joints. v uses world-frame linear and body-frame angular velocity.

Tests

env -u PYTHONPATH python -m pytest      # unset ROS's PYTHONPATH if it is sourced

Speed vs frax (CPU, float64, single thread, jax 0.4.30)

Median µs per call, from bench/bench_vs_frax.py, two runs. A ratio above 1 means rigidax is faster.

Panda single G1 (35 dof) single Panda batch 1024 G1 batch 1024
mass matrix 0.96–0.98x 0.97x 1.09x 1.02–1.15x
bias forces 0.85–0.91x 0.78–0.80x 0.95–1.02x 0.91–0.92x
forward dynamics 0.82–0.87x 0.95–0.96x 0.97–1.11x 0.94–0.97x

The two are about equal overall. The single-call bias computation is still 10–20% slower. GPU has not been benchmarked.

Known limitations (1.0)

  • Synthesized fingers: Dex3 finger motion is inferred from context (LAFAN1 has no finger data). Finger collisions are handled with rigid envelopes, so fists held against the torso in the fight guard still touch it in about 1% of frames.
  • GPU: the GPU path works but is not tuned (~6k frames/s on an RTX 3060 laptop).
  • G1 forearm pronation: on the G1 this lives on wrist_roll_link. The twist task currently targets elbow_link.

Roadmap

  • Foot skating p95 (contact-aware per-frame weights), GPU throughput
  • Capsule/box collision primitives
  • Close the remaining bias-forces gap; GPU benchmarks
  • Analytic derivatives of RNEA and ABA

Metadata

Release files for rigidax 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rigidax 1.0.0
File Size Uploaded Provenance Actions
rigidax-1.0.0.tar.gz 47.1 kB Without provenance Details for rigidax-1.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for rigidax 1.0.0
File Interpreter ABI Platform Actions
rigidax-1.0.0-py3-none-any.whl Python 3 none any Details for rigidax-1.0.0-py3-none-any.whl

Total release size: 85.3 kB

Release files / rigidax-1.0.0.tar.gz

Download URL rigidax-1.0.0.tar.gz
Size 47.1 kB
Tags Source
SHA-256 checksum
How to use checksums
cd454e8056677b3e823d0868d80e12489ae63269c221c7ed0b4f7b26d26c3928
BLAKE2b-256 checksum
How to use checksums
f90d57026c0e51391473c89418c7bc281a771d29fe1b6ad6d6c731f39c7e6cd6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / rigidax-1.0.0-py3-none-any.whl

Download URL rigidax-1.0.0-py3-none-any.whl
Size 38.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c2d6f95f0f9e7d2c2da43ff17c38653418077b45ec21d1d61584fb6fb90c0314
BLAKE2b-256 checksum
How to use checksums
666631e6fd57d376a14b01b8e5f26b2ed421e26f0f73afb746f48542374036e2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"22.04","id":"jammy","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for rigidax 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page