rigidax 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rigidax-1.0.0/LICENSE +21 -0
- rigidax-1.0.0/PKG-INFO +257 -0
- rigidax-1.0.0/README.md +223 -0
- rigidax-1.0.0/pyproject.toml +48 -0
- rigidax-1.0.0/rigidax/__init__.py +64 -0
- rigidax-1.0.0/rigidax/dynamics.py +212 -0
- rigidax-1.0.0/rigidax/ik/__init__.py +48 -0
- rigidax-1.0.0/rigidax/ik/bvh.py +99 -0
- rigidax-1.0.0/rigidax/ik/collision.py +235 -0
- rigidax-1.0.0/rigidax/ik/qp.py +177 -0
- rigidax-1.0.0/rigidax/ik/solver.py +405 -0
- rigidax-1.0.0/rigidax/ik/tasks.py +195 -0
- rigidax-1.0.0/rigidax/ik/trajectory.py +57 -0
- rigidax-1.0.0/rigidax/kinematics.py +181 -0
- rigidax-1.0.0/rigidax/model.py +340 -0
- rigidax-1.0.0/rigidax/spatial.py +107 -0
- rigidax-1.0.0/rigidax.egg-info/PKG-INFO +257 -0
- rigidax-1.0.0/rigidax.egg-info/SOURCES.txt +23 -0
- rigidax-1.0.0/rigidax.egg-info/dependency_links.txt +1 -0
- rigidax-1.0.0/rigidax.egg-info/requires.txt +12 -0
- rigidax-1.0.0/rigidax.egg-info/top_level.txt +1 -0
- rigidax-1.0.0/setup.cfg +4 -0
- rigidax-1.0.0/tests/test_ik.py +152 -0
- rigidax-1.0.0/tests/test_ik_tasks.py +159 -0
- rigidax-1.0.0/tests/test_vs_mujoco.py +215 -0
rigidax-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nishanth Sundaran
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
rigidax-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rigidax
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Differentiable rigid-body kinematics, dynamics and batched IK / motion retargeting in JAX (MJCF + URDF)
|
|
5
|
+
Author-email: Nishanth Sundaran <sundharnishanth@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/NishanthSundaran/Rigidax
|
|
8
|
+
Project-URL: Repository, https://github.com/NishanthSundaran/Rigidax
|
|
9
|
+
Project-URL: Issues, https://github.com/NishanthSundaran/Rigidax/issues
|
|
10
|
+
Project-URL: Changelog, https://github.com/NishanthSundaran/Rigidax/blob/master/CHANGELOG.md
|
|
11
|
+
Keywords: robotics,kinematics,dynamics,inverse-kinematics,retargeting,jax,mujoco
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: numpy
|
|
24
|
+
Requires-Dist: jax>=0.4.30
|
|
25
|
+
Requires-Dist: mujoco>=3.1
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest; extra == "test"
|
|
28
|
+
Requires-Dist: pin; extra == "test"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest; extra == "dev"
|
|
31
|
+
Requires-Dist: pin; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# rigidax
|
|
36
|
+
|
|
37
|
+
Differentiable rigid-body kinematics and dynamics in JAX. Its design starts from
|
|
38
|
+
[frax](https://github.com/StanfordASL/frax): the dense, loop-free formulation of the
|
|
39
|
+
dynamics over a DOF ancestor mask. It fills in frax's gaps:
|
|
40
|
+
|
|
41
|
+
| | frax 0.2 | rigidax |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| Model formats | URDF | **MJCF and URDF** (compiled by MuJoCo) |
|
|
44
|
+
| Joint types | revolute, prismatic, free | hinge, slide, **ball**, free, **several joints per body** |
|
|
45
|
+
| Gravity | hardcoded 9.81 in `forward_dynamics` | **model parameter**, any vector |
|
|
46
|
+
| Physical parameters | frozen into the compiled function | **pytree leaves**: `jax.grad` / `vmap` over masses, inertias, offsets, gravity; swap them without recompiling |
|
|
47
|
+
| Armature | no | yes (in M and inverse dynamics) |
|
|
48
|
+
| External wrenches | per-link | per-body, MuJoCo `xfrc_applied` layout |
|
|
49
|
+
| Ground truth tests | Pinocchio / MuJoCo | every quantity vs MuJoCo on random trees + G1/A2/Panda, plus Pinocchio |
|
|
50
|
+
|
|
51
|
+

|
|
52
|
+
|
|
53
|
+
The Panda tracks a figure-eight with operational space control (`examples/osc_demo.py`).
|
|
54
|
+
rigidax computes the kinematics, Jacobian, mass matrix and bias forces in about 54 µs
|
|
55
|
+
per step, Python dispatch included. Mean tracking error is 0.9 mm. MuJoCo only
|
|
56
|
+
simulates and renders.
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
env -u PYTHONPATH MUJOCO_GL=egl python examples/osc_demo.py --out osc_demo.mp4
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
Two output variants are produced from the same solve settings. Speed on the same
|
|
65
|
+
laptop with self-collision: 22.8k frames/s for the plain G1 and 18.8k for G1 + Dex3. The
|
|
66
|
+
Dex3 version is slower because the finger envelope adds collision spheres. frax, with
|
|
67
|
+
self-collision, runs at 18.3k.
|
|
68
|
+
|
|
69
|
+
| Variant | Command | Output | GIF |
|
|
70
|
+
|---|---|---|---|
|
|
71
|
+
| G1 + Dex3 hands (default) | `retarget_lafan1.py <clips>` | `data/g1_lafan1_hands/` (nq 50) | `examples/g1_lafan1.gif` |
|
|
72
|
+
| Plain G1, rigid hands | `retarget_lafan1.py <clips> --no-hands` | `data/g1_lafan1_nohands/` (nq 36) | `examples/g1_lafan1_nohands.gif` |
|
|
73
|
+
|
|
74
|
+
Both variants track the forearm twist. Palm floor contact and fingers are Dex3-only.
|
|
75
|
+
Render a variant with `render_motions.py --data data/g1_lafan1_nohands ...`.
|
|
76
|
+
|
|
77
|
+
## `rigidax.ik`: batched IK and retargeting
|
|
78
|
+
|
|
79
|
+
A dedicated differential IK solver for retargeting whole datasets on CPU:
|
|
80
|
+
|
|
81
|
+
- **Each Gauss-Newton step is a small QP**, solved by a purpose-built fixed-iteration
|
|
82
|
+
ADMM (`rigidax.ik.solve_qp`). It has no branches, so it vectorizes over thousands of
|
|
83
|
+
frames. It uses:
|
|
84
|
+
- row equilibration,
|
|
85
|
+
- one explicit inverse per solve, then matrix-vector products (batched triangular
|
|
86
|
+
solves are slow on XLA:CPU),
|
|
87
|
+
- warm starts from the previous frame,
|
|
88
|
+
- optional polishing.
|
|
89
|
+
- **Constraints:**
|
|
90
|
+
- hard joint limits,
|
|
91
|
+
- a per-iteration trust region, so a joint can never flip across its range in one
|
|
92
|
+
frame,
|
|
93
|
+
- a hard floor (`FloorPoints`),
|
|
94
|
+
- elastic self-collision, an exact L1 penalty that keeps the problem always feasible,
|
|
95
|
+
on an automatic sphere model of any MuJoCo model (`SphereModel.fit`).
|
|
96
|
+
- **Tasks:** position, orientation, limb direction (retargets between skeletons with
|
|
97
|
+
different proportions) and posture.
|
|
98
|
+
- **Batching:** `solve_trajectory` splits clips into segments that run in parallel (vmap
|
|
99
|
+
within a device, pmap across devices). Each segment is seeded by a multi-start solve,
|
|
100
|
+
and consecutive segments are crossfaded so seams are smooth.
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from rigidax import ik
|
|
104
|
+
tasks = [ik.position(model, bodies, w), ik.direction(model, links, bases, w),
|
|
105
|
+
ik.orientation(model, frames, w), ik.PostureTask(posture_w)]
|
|
106
|
+
solver = ik.IKSolver(model, tasks, floor=ik.FloorPoints.on(model, feet, sole_points),
|
|
107
|
+
collision=ik.SphereModel.fit(mj_model, model, pair_filter=...))
|
|
108
|
+
qs, costs = solver.solve_trajectory(seed_fn, [clip1_targets, clip2_targets, ...])
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### vs frax on LAFAN1 → Unitree G1
|
|
112
|
+
|
|
113
|
+
Both libraries ran frax's own LAFAN1 settings on the same i9-11900H laptop (8 cores, one
|
|
114
|
+
XLA device per core), on 8 clips (53,621 frames, 30 min of motion). Both outputs are scored
|
|
115
|
+
with the same metrics:
|
|
116
|
+
- **Motion kept:** robot vs human limb angular speed (1.0 = all of it).
|
|
117
|
+
- **Lag:** in frames.
|
|
118
|
+
- **Joint acceleration:** worst 5% of frames.
|
|
119
|
+
- **Skating:** the robot's toe contact sliding while the human toe is planted.
|
|
120
|
+
- **Penetration:** MuJoCo's own mesh collision check.
|
|
121
|
+
|
|
122
|
+
`bench/bench_frax_ik.py` runs frax.
|
|
123
|
+
|
|
124
|
+
| | 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 |
|
|
125
|
+
|---|---|---|---|---|---|---|---|---|---|---|
|
|
126
|
+
| 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 |
|
|
127
|
+
| **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) |
|
|
128
|
+
| 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% | |
|
|
129
|
+
| **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% | |
|
|
130
|
+
|
|
131
|
+
The rigidax task set covers everything frax tracks: pelvis, the thigh / shin / foot links,
|
|
132
|
+
torso, and the upper arm / forearm / hand orientations. It adds limb directions and
|
|
133
|
+
toe contact points on top.
|
|
134
|
+
|
|
135
|
+
Where frax is still slightly ahead:
|
|
136
|
+
- forearm twist (24° vs 20°),
|
|
137
|
+
- leg motion kept (0.94 vs 0.95).
|
|
138
|
+
|
|
139
|
+
frax's lower joint-acceleration number (8.4 vs 9.3°/f²) is mostly smoothed-away motion.
|
|
140
|
+
On the knees, rigidax matches the human's own knee acceleration and frax sits below it.
|
|
141
|
+
|
|
142
|
+
Hands: the output uses the G1 with Dex3-1 hands (`g1_with_hands.xml`, 7 finger joints
|
|
143
|
+
per hand). The body is solved on the 35-DOF model and the finger angles are written
|
|
144
|
+
afterwards, since nothing depends on them, which keeps the solve as fast as before.
|
|
145
|
+
- **Synthesized fingers:** LAFAN1 records no fingers, so the grip is inferred from context
|
|
146
|
+
and is not tracked motion:
|
|
147
|
+
- fist in a fighting guard (hand in front of the chest, in the chest frame) or during
|
|
148
|
+
a strike, in `fight` clips only;
|
|
149
|
+
- flat open hand while the human hand rests on the floor;
|
|
150
|
+
- relaxed otherwise.
|
|
151
|
+
- **Palm contact:** a height task puts the palm on the floor while the human hand rests
|
|
152
|
+
there. The previous hover was 6.2 cm; now it is about 0.
|
|
153
|
+
- **Forearm twist:** a twist-only task about the forearm axis.
|
|
154
|
+
|
|
155
|
+
Diagnostics:
|
|
156
|
+
- `examples/render_overlay.py`: draws the solver's targets over the robot (visible red
|
|
157
|
+
means a real tracking error).
|
|
158
|
+
- `examples/floor_report.py`: whole-body floor penetration.
|
|
159
|
+
- `examples/collision_report.py`: self-collision.
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
# CPU, one XLA device per core
|
|
163
|
+
XLA_FLAGS="--xla_force_host_platform_device_count=8 --xla_cpu_multi_thread_eigen=false" \
|
|
164
|
+
python examples/retarget_lafan1.py dance1_subject1 walk1_subject1 ... [--no-collision]
|
|
165
|
+
python examples/collision_report.py dance1_subject1 ... # MuJoCo mesh ground truth
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
LAFAN1 is CC BY-NC-ND 4.0 (non-commercial). Get it with:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
curl -L -o data/lafan1.zip https://github.com/ubisoft/ubisoft-laforge-animation-dataset/raw/master/lafan1/lafan1.zip
|
|
172
|
+
unzip -j data/lafan1.zip '*.bvh' -d data/lafan1
|
|
173
|
+
MUJOCO_GL=egl python examples/render_motions.py --out g1_lafan1.gif dance1_subject1:DANCE \
|
|
174
|
+
dance2_subject3:"DANCE 2" fightAndSports1_subject1@13.8:"FIGHT + KICK" jumps1_subject1:JUMPS \
|
|
175
|
+
run1_subject2:RUN sprint1_subject2@177:SPRINT walk1_subject1:WALK fallAndGetUp1_subject1@63:"FALL + GET UP"
|
|
176
|
+
# clip@seconds picks the window; without it the most active 10 s are used
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+

|
|
180
|
+
|
|
181
|
+

|
|
182
|
+
|
|
183
|
+
A free-floating Unitree G1 (35 DOF) squats, twists and waves while balancing on its
|
|
184
|
+
feet (`examples/g1_demo.py`). Per step, rigidax computes:
|
|
185
|
+
- M and h, the foot contact Jacobians, and Jc_dot·v (via autodiff)
|
|
186
|
+
- reference accelerations that keep the feet planted (KKT solve)
|
|
187
|
+
- the contact wrenches that balance the floating base
|
|
188
|
+
- the feedforward joint torques
|
|
189
|
+
|
|
190
|
+
Joint PD supplies the feedback. A step takes about 190 µs. MuJoCo's position servos
|
|
191
|
+
are replaced by pure torque motors.
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
env -u PYTHONPATH MUJOCO_GL=egl python examples/g1_demo.py --out g1_demo.mp4
|
|
195
|
+
# one motion set, frax-style grey studio: --motion squat|twist|wave|squat,wave|all --style light
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
## Use
|
|
199
|
+
|
|
200
|
+
```python
|
|
201
|
+
import jax, rigidax as rx
|
|
202
|
+
jax.config.update("jax_enable_x64", True)
|
|
203
|
+
|
|
204
|
+
model = rx.load("robot.xml") # or .urdf; strip_meshes=True if meshes are missing
|
|
205
|
+
q, v = model.q0, jax.numpy.zeros(model.nv)
|
|
206
|
+
|
|
207
|
+
M, bias = jax.jit(rx.dynamics_terms)(model, q, v) # one kinematics pass
|
|
208
|
+
qacc = jax.jit(rx.forward_dynamics)(model, q, v, tau)
|
|
209
|
+
moon = model.replace(gravity=jax.numpy.array([0, 0, -1.62])) # no recompile
|
|
210
|
+
|
|
211
|
+
# gradients w.r.t. physical parameters (e.g. system identification)
|
|
212
|
+
dL_dmass = jax.grad(lambda m: loss(model.replace(body_mass=m)))(model.params.body_mass)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Available functions: `forward_kinematics`, `site_pose`, `body_pose`, `point_jacobian`,
|
|
216
|
+
`site_jacobian`, `body_jacobian`, `center_of_mass`, `center_of_mass_jacobian`, `rnea`,
|
|
217
|
+
`mass_matrix`, `bias_forces`, `gravity_forces`, `coriolis_forces`, `dynamics_terms`,
|
|
218
|
+
`forward_dynamics`, `mass_matrix_inverse`, `integrate`, `difference`.
|
|
219
|
+
Every function takes an optional `kin=` argument to reuse a forward-kinematics pass.
|
|
220
|
+
|
|
221
|
+
Conventions follow MuJoCo: `q` uses `[pos, wxyz quat]` for free joints and `wxyz` for
|
|
222
|
+
ball joints. `v` uses world-frame linear and body-frame angular velocity.
|
|
223
|
+
|
|
224
|
+
## Tests
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
env -u PYTHONPATH python -m pytest # unset ROS's PYTHONPATH if it is sourced
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Speed vs frax (CPU, float64, single thread, jax 0.4.30)
|
|
231
|
+
|
|
232
|
+
Median µs per call, from `bench/bench_vs_frax.py`, two runs. A ratio above 1 means rigidax is faster.
|
|
233
|
+
|
|
234
|
+
| | Panda single | G1 (35 dof) single | Panda batch 1024 | G1 batch 1024 |
|
|
235
|
+
|---|---|---|---|---|
|
|
236
|
+
| mass matrix | 0.96–0.98x | 0.97x | 1.09x | 1.02–1.15x |
|
|
237
|
+
| bias forces | 0.85–0.91x | 0.78–0.80x | 0.95–1.02x | 0.91–0.92x |
|
|
238
|
+
| forward dynamics | 0.82–0.87x | 0.95–0.96x | 0.97–1.11x | 0.94–0.97x |
|
|
239
|
+
|
|
240
|
+
The two are about equal overall. The single-call bias computation is still 10–20% slower.
|
|
241
|
+
GPU has not been benchmarked.
|
|
242
|
+
|
|
243
|
+
## Known limitations (1.0)
|
|
244
|
+
|
|
245
|
+
- **Synthesized fingers:** Dex3 finger motion is inferred from context (LAFAN1 has no
|
|
246
|
+
finger data). Finger collisions are handled with rigid envelopes, so fists held
|
|
247
|
+
against the torso in the fight guard still touch it in about 1% of frames.
|
|
248
|
+
- **GPU:** the GPU path works but is not tuned (~6k frames/s on an RTX 3060 laptop).
|
|
249
|
+
- **G1 forearm pronation:** on the G1 this lives on `wrist_roll_link`. The twist task
|
|
250
|
+
currently targets `elbow_link`.
|
|
251
|
+
|
|
252
|
+
## Roadmap
|
|
253
|
+
|
|
254
|
+
- Foot skating p95 (contact-aware per-frame weights), GPU throughput
|
|
255
|
+
- Capsule/box collision primitives
|
|
256
|
+
- Close the remaining bias-forces gap; GPU benchmarks
|
|
257
|
+
- Analytic derivatives of RNEA and ABA
|
rigidax-1.0.0/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# rigidax
|
|
2
|
+
|
|
3
|
+
Differentiable rigid-body kinematics and dynamics in JAX. Its design starts from
|
|
4
|
+
[frax](https://github.com/StanfordASL/frax): the dense, loop-free formulation of the
|
|
5
|
+
dynamics over a DOF ancestor mask. It fills in frax's gaps:
|
|
6
|
+
|
|
7
|
+
| | frax 0.2 | rigidax |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| Model formats | URDF | **MJCF and URDF** (compiled by MuJoCo) |
|
|
10
|
+
| Joint types | revolute, prismatic, free | hinge, slide, **ball**, free, **several joints per body** |
|
|
11
|
+
| Gravity | hardcoded 9.81 in `forward_dynamics` | **model parameter**, any vector |
|
|
12
|
+
| Physical parameters | frozen into the compiled function | **pytree leaves**: `jax.grad` / `vmap` over masses, inertias, offsets, gravity; swap them without recompiling |
|
|
13
|
+
| Armature | no | yes (in M and inverse dynamics) |
|
|
14
|
+
| External wrenches | per-link | per-body, MuJoCo `xfrc_applied` layout |
|
|
15
|
+
| Ground truth tests | Pinocchio / MuJoCo | every quantity vs MuJoCo on random trees + G1/A2/Panda, plus Pinocchio |
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
The Panda tracks a figure-eight with operational space control (`examples/osc_demo.py`).
|
|
20
|
+
rigidax computes the kinematics, Jacobian, mass matrix and bias forces in about 54 µs
|
|
21
|
+
per step, Python dispatch included. Mean tracking error is 0.9 mm. MuJoCo only
|
|
22
|
+
simulates and renders.
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
env -u PYTHONPATH MUJOCO_GL=egl python examples/osc_demo.py --out osc_demo.mp4
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
Two output variants are produced from the same solve settings. Speed on the same
|
|
31
|
+
laptop with self-collision: 22.8k frames/s for the plain G1 and 18.8k for G1 + Dex3. The
|
|
32
|
+
Dex3 version is slower because the finger envelope adds collision spheres. frax, with
|
|
33
|
+
self-collision, runs at 18.3k.
|
|
34
|
+
|
|
35
|
+
| Variant | Command | Output | GIF |
|
|
36
|
+
|---|---|---|---|
|
|
37
|
+
| G1 + Dex3 hands (default) | `retarget_lafan1.py <clips>` | `data/g1_lafan1_hands/` (nq 50) | `examples/g1_lafan1.gif` |
|
|
38
|
+
| Plain G1, rigid hands | `retarget_lafan1.py <clips> --no-hands` | `data/g1_lafan1_nohands/` (nq 36) | `examples/g1_lafan1_nohands.gif` |
|
|
39
|
+
|
|
40
|
+
Both variants track the forearm twist. Palm floor contact and fingers are Dex3-only.
|
|
41
|
+
Render a variant with `render_motions.py --data data/g1_lafan1_nohands ...`.
|
|
42
|
+
|
|
43
|
+
## `rigidax.ik`: batched IK and retargeting
|
|
44
|
+
|
|
45
|
+
A dedicated differential IK solver for retargeting whole datasets on CPU:
|
|
46
|
+
|
|
47
|
+
- **Each Gauss-Newton step is a small QP**, solved by a purpose-built fixed-iteration
|
|
48
|
+
ADMM (`rigidax.ik.solve_qp`). It has no branches, so it vectorizes over thousands of
|
|
49
|
+
frames. It uses:
|
|
50
|
+
- row equilibration,
|
|
51
|
+
- one explicit inverse per solve, then matrix-vector products (batched triangular
|
|
52
|
+
solves are slow on XLA:CPU),
|
|
53
|
+
- warm starts from the previous frame,
|
|
54
|
+
- optional polishing.
|
|
55
|
+
- **Constraints:**
|
|
56
|
+
- hard joint limits,
|
|
57
|
+
- a per-iteration trust region, so a joint can never flip across its range in one
|
|
58
|
+
frame,
|
|
59
|
+
- a hard floor (`FloorPoints`),
|
|
60
|
+
- elastic self-collision, an exact L1 penalty that keeps the problem always feasible,
|
|
61
|
+
on an automatic sphere model of any MuJoCo model (`SphereModel.fit`).
|
|
62
|
+
- **Tasks:** position, orientation, limb direction (retargets between skeletons with
|
|
63
|
+
different proportions) and posture.
|
|
64
|
+
- **Batching:** `solve_trajectory` splits clips into segments that run in parallel (vmap
|
|
65
|
+
within a device, pmap across devices). Each segment is seeded by a multi-start solve,
|
|
66
|
+
and consecutive segments are crossfaded so seams are smooth.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from rigidax import ik
|
|
70
|
+
tasks = [ik.position(model, bodies, w), ik.direction(model, links, bases, w),
|
|
71
|
+
ik.orientation(model, frames, w), ik.PostureTask(posture_w)]
|
|
72
|
+
solver = ik.IKSolver(model, tasks, floor=ik.FloorPoints.on(model, feet, sole_points),
|
|
73
|
+
collision=ik.SphereModel.fit(mj_model, model, pair_filter=...))
|
|
74
|
+
qs, costs = solver.solve_trajectory(seed_fn, [clip1_targets, clip2_targets, ...])
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### vs frax on LAFAN1 → Unitree G1
|
|
78
|
+
|
|
79
|
+
Both libraries ran frax's own LAFAN1 settings on the same i9-11900H laptop (8 cores, one
|
|
80
|
+
XLA device per core), on 8 clips (53,621 frames, 30 min of motion). Both outputs are scored
|
|
81
|
+
with the same metrics:
|
|
82
|
+
- **Motion kept:** robot vs human limb angular speed (1.0 = all of it).
|
|
83
|
+
- **Lag:** in frames.
|
|
84
|
+
- **Joint acceleration:** worst 5% of frames.
|
|
85
|
+
- **Skating:** the robot's toe contact sliding while the human toe is planted.
|
|
86
|
+
- **Penetration:** MuJoCo's own mesh collision check.
|
|
87
|
+
|
|
88
|
+
`bench/bench_frax_ik.py` runs frax.
|
|
89
|
+
|
|
90
|
+
| | 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 |
|
|
91
|
+
|---|---|---|---|---|---|---|---|---|---|---|
|
|
92
|
+
| 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 |
|
|
93
|
+
| **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) |
|
|
94
|
+
| 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% | |
|
|
95
|
+
| **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% | |
|
|
96
|
+
|
|
97
|
+
The rigidax task set covers everything frax tracks: pelvis, the thigh / shin / foot links,
|
|
98
|
+
torso, and the upper arm / forearm / hand orientations. It adds limb directions and
|
|
99
|
+
toe contact points on top.
|
|
100
|
+
|
|
101
|
+
Where frax is still slightly ahead:
|
|
102
|
+
- forearm twist (24° vs 20°),
|
|
103
|
+
- leg motion kept (0.94 vs 0.95).
|
|
104
|
+
|
|
105
|
+
frax's lower joint-acceleration number (8.4 vs 9.3°/f²) is mostly smoothed-away motion.
|
|
106
|
+
On the knees, rigidax matches the human's own knee acceleration and frax sits below it.
|
|
107
|
+
|
|
108
|
+
Hands: the output uses the G1 with Dex3-1 hands (`g1_with_hands.xml`, 7 finger joints
|
|
109
|
+
per hand). The body is solved on the 35-DOF model and the finger angles are written
|
|
110
|
+
afterwards, since nothing depends on them, which keeps the solve as fast as before.
|
|
111
|
+
- **Synthesized fingers:** LAFAN1 records no fingers, so the grip is inferred from context
|
|
112
|
+
and is not tracked motion:
|
|
113
|
+
- fist in a fighting guard (hand in front of the chest, in the chest frame) or during
|
|
114
|
+
a strike, in `fight` clips only;
|
|
115
|
+
- flat open hand while the human hand rests on the floor;
|
|
116
|
+
- relaxed otherwise.
|
|
117
|
+
- **Palm contact:** a height task puts the palm on the floor while the human hand rests
|
|
118
|
+
there. The previous hover was 6.2 cm; now it is about 0.
|
|
119
|
+
- **Forearm twist:** a twist-only task about the forearm axis.
|
|
120
|
+
|
|
121
|
+
Diagnostics:
|
|
122
|
+
- `examples/render_overlay.py`: draws the solver's targets over the robot (visible red
|
|
123
|
+
means a real tracking error).
|
|
124
|
+
- `examples/floor_report.py`: whole-body floor penetration.
|
|
125
|
+
- `examples/collision_report.py`: self-collision.
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
# CPU, one XLA device per core
|
|
129
|
+
XLA_FLAGS="--xla_force_host_platform_device_count=8 --xla_cpu_multi_thread_eigen=false" \
|
|
130
|
+
python examples/retarget_lafan1.py dance1_subject1 walk1_subject1 ... [--no-collision]
|
|
131
|
+
python examples/collision_report.py dance1_subject1 ... # MuJoCo mesh ground truth
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
LAFAN1 is CC BY-NC-ND 4.0 (non-commercial). Get it with:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
curl -L -o data/lafan1.zip https://github.com/ubisoft/ubisoft-laforge-animation-dataset/raw/master/lafan1/lafan1.zip
|
|
138
|
+
unzip -j data/lafan1.zip '*.bvh' -d data/lafan1
|
|
139
|
+
MUJOCO_GL=egl python examples/render_motions.py --out g1_lafan1.gif dance1_subject1:DANCE \
|
|
140
|
+
dance2_subject3:"DANCE 2" fightAndSports1_subject1@13.8:"FIGHT + KICK" jumps1_subject1:JUMPS \
|
|
141
|
+
run1_subject2:RUN sprint1_subject2@177:SPRINT walk1_subject1:WALK fallAndGetUp1_subject1@63:"FALL + GET UP"
|
|
142
|
+
# clip@seconds picks the window; without it the most active 10 s are used
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+

|
|
146
|
+
|
|
147
|
+

|
|
148
|
+
|
|
149
|
+
A free-floating Unitree G1 (35 DOF) squats, twists and waves while balancing on its
|
|
150
|
+
feet (`examples/g1_demo.py`). Per step, rigidax computes:
|
|
151
|
+
- M and h, the foot contact Jacobians, and Jc_dot·v (via autodiff)
|
|
152
|
+
- reference accelerations that keep the feet planted (KKT solve)
|
|
153
|
+
- the contact wrenches that balance the floating base
|
|
154
|
+
- the feedforward joint torques
|
|
155
|
+
|
|
156
|
+
Joint PD supplies the feedback. A step takes about 190 µs. MuJoCo's position servos
|
|
157
|
+
are replaced by pure torque motors.
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
env -u PYTHONPATH MUJOCO_GL=egl python examples/g1_demo.py --out g1_demo.mp4
|
|
161
|
+
# one motion set, frax-style grey studio: --motion squat|twist|wave|squat,wave|all --style light
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Use
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
import jax, rigidax as rx
|
|
168
|
+
jax.config.update("jax_enable_x64", True)
|
|
169
|
+
|
|
170
|
+
model = rx.load("robot.xml") # or .urdf; strip_meshes=True if meshes are missing
|
|
171
|
+
q, v = model.q0, jax.numpy.zeros(model.nv)
|
|
172
|
+
|
|
173
|
+
M, bias = jax.jit(rx.dynamics_terms)(model, q, v) # one kinematics pass
|
|
174
|
+
qacc = jax.jit(rx.forward_dynamics)(model, q, v, tau)
|
|
175
|
+
moon = model.replace(gravity=jax.numpy.array([0, 0, -1.62])) # no recompile
|
|
176
|
+
|
|
177
|
+
# gradients w.r.t. physical parameters (e.g. system identification)
|
|
178
|
+
dL_dmass = jax.grad(lambda m: loss(model.replace(body_mass=m)))(model.params.body_mass)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Available functions: `forward_kinematics`, `site_pose`, `body_pose`, `point_jacobian`,
|
|
182
|
+
`site_jacobian`, `body_jacobian`, `center_of_mass`, `center_of_mass_jacobian`, `rnea`,
|
|
183
|
+
`mass_matrix`, `bias_forces`, `gravity_forces`, `coriolis_forces`, `dynamics_terms`,
|
|
184
|
+
`forward_dynamics`, `mass_matrix_inverse`, `integrate`, `difference`.
|
|
185
|
+
Every function takes an optional `kin=` argument to reuse a forward-kinematics pass.
|
|
186
|
+
|
|
187
|
+
Conventions follow MuJoCo: `q` uses `[pos, wxyz quat]` for free joints and `wxyz` for
|
|
188
|
+
ball joints. `v` uses world-frame linear and body-frame angular velocity.
|
|
189
|
+
|
|
190
|
+
## Tests
|
|
191
|
+
|
|
192
|
+
```
|
|
193
|
+
env -u PYTHONPATH python -m pytest # unset ROS's PYTHONPATH if it is sourced
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Speed vs frax (CPU, float64, single thread, jax 0.4.30)
|
|
197
|
+
|
|
198
|
+
Median µs per call, from `bench/bench_vs_frax.py`, two runs. A ratio above 1 means rigidax is faster.
|
|
199
|
+
|
|
200
|
+
| | Panda single | G1 (35 dof) single | Panda batch 1024 | G1 batch 1024 |
|
|
201
|
+
|---|---|---|---|---|
|
|
202
|
+
| mass matrix | 0.96–0.98x | 0.97x | 1.09x | 1.02–1.15x |
|
|
203
|
+
| bias forces | 0.85–0.91x | 0.78–0.80x | 0.95–1.02x | 0.91–0.92x |
|
|
204
|
+
| forward dynamics | 0.82–0.87x | 0.95–0.96x | 0.97–1.11x | 0.94–0.97x |
|
|
205
|
+
|
|
206
|
+
The two are about equal overall. The single-call bias computation is still 10–20% slower.
|
|
207
|
+
GPU has not been benchmarked.
|
|
208
|
+
|
|
209
|
+
## Known limitations (1.0)
|
|
210
|
+
|
|
211
|
+
- **Synthesized fingers:** Dex3 finger motion is inferred from context (LAFAN1 has no
|
|
212
|
+
finger data). Finger collisions are handled with rigid envelopes, so fists held
|
|
213
|
+
against the torso in the fight guard still touch it in about 1% of frames.
|
|
214
|
+
- **GPU:** the GPU path works but is not tuned (~6k frames/s on an RTX 3060 laptop).
|
|
215
|
+
- **G1 forearm pronation:** on the G1 this lives on `wrist_roll_link`. The twist task
|
|
216
|
+
currently targets `elbow_link`.
|
|
217
|
+
|
|
218
|
+
## Roadmap
|
|
219
|
+
|
|
220
|
+
- Foot skating p95 (contact-aware per-frame weights), GPU throughput
|
|
221
|
+
- Capsule/box collision primitives
|
|
222
|
+
- Close the remaining bias-forces gap; GPU benchmarks
|
|
223
|
+
- Analytic derivatives of RNEA and ABA
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "rigidax"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "Differentiable rigid-body kinematics, dynamics and batched IK / motion retargeting in JAX (MJCF + URDF)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Nishanth Sundaran", email = "sundharnishanth@gmail.com" }]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
keywords = ["robotics", "kinematics", "dynamics", "inverse-kinematics", "retargeting", "jax", "mujoco"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
|
16
|
+
"Intended Audience :: Science/Research",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Physics",
|
|
23
|
+
]
|
|
24
|
+
dependencies = ["numpy", "jax>=0.4.30", "mujoco>=3.1"]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/NishanthSundaran/Rigidax"
|
|
28
|
+
Repository = "https://github.com/NishanthSundaran/Rigidax"
|
|
29
|
+
Issues = "https://github.com/NishanthSundaran/Rigidax/issues"
|
|
30
|
+
Changelog = "https://github.com/NishanthSundaran/Rigidax/blob/master/CHANGELOG.md"
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
test = ["pytest", "pin"]
|
|
34
|
+
dev = ["pytest", "pin", "ruff"]
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
include = ["rigidax*"]
|
|
38
|
+
|
|
39
|
+
[tool.pytest.ini_options]
|
|
40
|
+
testpaths = ["tests"]
|
|
41
|
+
|
|
42
|
+
[tool.ruff]
|
|
43
|
+
line-length = 100
|
|
44
|
+
target-version = "py310"
|
|
45
|
+
|
|
46
|
+
[tool.ruff.lint]
|
|
47
|
+
select = ["E", "F", "W", "I"]
|
|
48
|
+
ignore = ["E731", "E741"]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""rigidax: differentiable rigid-body kinematics and dynamics in JAX"""
|
|
2
|
+
|
|
3
|
+
from rigidax.dynamics import (
|
|
4
|
+
bias_forces,
|
|
5
|
+
body_spatial_inertias,
|
|
6
|
+
coriolis_forces,
|
|
7
|
+
difference,
|
|
8
|
+
dof_inertias,
|
|
9
|
+
dynamics_terms,
|
|
10
|
+
forward_dynamics,
|
|
11
|
+
gravity_forces,
|
|
12
|
+
integrate,
|
|
13
|
+
mass_matrix,
|
|
14
|
+
mass_matrix_inverse,
|
|
15
|
+
rnea,
|
|
16
|
+
)
|
|
17
|
+
from rigidax.kinematics import (
|
|
18
|
+
Kinematics,
|
|
19
|
+
body_coms,
|
|
20
|
+
body_jacobian,
|
|
21
|
+
body_pose,
|
|
22
|
+
center_of_mass,
|
|
23
|
+
center_of_mass_jacobian,
|
|
24
|
+
forward_kinematics,
|
|
25
|
+
point_jacobian,
|
|
26
|
+
points_jacobians,
|
|
27
|
+
site_jacobian,
|
|
28
|
+
site_pose,
|
|
29
|
+
)
|
|
30
|
+
from rigidax.model import Model, Params, Topology, from_mujoco, from_xml_string, load
|
|
31
|
+
|
|
32
|
+
__version__ = "1.0.0"
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"Kinematics",
|
|
36
|
+
"Model",
|
|
37
|
+
"Params",
|
|
38
|
+
"Topology",
|
|
39
|
+
"bias_forces",
|
|
40
|
+
"body_coms",
|
|
41
|
+
"body_jacobian",
|
|
42
|
+
"body_pose",
|
|
43
|
+
"body_spatial_inertias",
|
|
44
|
+
"center_of_mass",
|
|
45
|
+
"center_of_mass_jacobian",
|
|
46
|
+
"coriolis_forces",
|
|
47
|
+
"difference",
|
|
48
|
+
"dof_inertias",
|
|
49
|
+
"dynamics_terms",
|
|
50
|
+
"forward_dynamics",
|
|
51
|
+
"forward_kinematics",
|
|
52
|
+
"from_mujoco",
|
|
53
|
+
"from_xml_string",
|
|
54
|
+
"gravity_forces",
|
|
55
|
+
"integrate",
|
|
56
|
+
"load",
|
|
57
|
+
"mass_matrix",
|
|
58
|
+
"mass_matrix_inverse",
|
|
59
|
+
"point_jacobian",
|
|
60
|
+
"points_jacobians",
|
|
61
|
+
"rnea",
|
|
62
|
+
"site_jacobian",
|
|
63
|
+
"site_pose",
|
|
64
|
+
]
|