Context
MotrixSim's batched API is already a very good fit for finite-difference trajectory optimization in the style of mjbatch:
model = load_model(path)
data = SceneData(model, batch=(T * (1 + nx + nu),))
# write one perturbed (state, control) row per world
data.set_dof_pos(perturbed_pos, model)
data.set_dof_vel(perturbed_vel)
data.actuator_ctrls = perturbed_ctrl
model.step(data) # all worlds advance one step in parallel
Porting the iLQR solver itself is straightforward, but I hit two engine-level blockers that would be great to address.
1. Float32 wheels make finite differences noisy
mjbatch uses EPS=1e-6 with MuJoCo's float64 dynamics. On motrixsim==0.10.1 (default real_dtype() == "float32"), a one-step forward-difference Jacobian of a simple free-falling body has this error against the analytic dynamics:
| eps |
max Jacobian error |
| 1e-7 |
1.92e-1 |
| 1e-6 |
1.65e-2 |
| 1e-5 |
2.00e-3 |
| 1e-4 |
2.12e-4 |
| 1e-3 |
2.66e-5 |
So eps=1e-6 is not usable, and eps>=1e-4 starts to cross contact/discontinuity scales on contact-rich humanoid models.
motrixsim.real_dtype() documents that an f64 build exists ("float64 for an f64 build"), but PyPI currently only exposes float32 wheels.
Request: publish an f64 build (a separate wheel/extra, or a documented build flag). Even an opt-in package such as motrixsim-core-f64 would unblock FD iLQR/MPC.
2. No way to clear cached solver warm-start state
The SceneData.reset docstring explicitly says:
Cached solver state (warm-start lambdas, actuator activations) and derived outputs of the previous step ... are kept and are simply recomputed by the next step.
For one-step FD linearization, the step function must be a pure function of the written state/control. MuJoCo-based implementations avoid this by zeroing qacc_warmstart before each perturbed step. In MotrixSim I could not find an equivalent public API.
A simple sphere-plane contact smoke test did not show contamination (different contact histories followed by the same written state produced bit-identical next-step and 50-step outputs), but I would not rely on that for a contact-rich G1 model.
Request: expose an explicit way to make the next step independent of cached solver state, for example:
data.clear_solver_cache(model) # per-world or full-batch
# or
model.options.disable_warmstart = True
Reproduction snippet for the FD noise table
import numpy as np
import motrixsim as mtx
mjcf = """
<mujoco>
<option timestep="0.002"/>
<worldbody>
<body pos="0 0 0.5">
<freejoint/>
<geom type="sphere" size="0.05" mass="1"/>
</body>
</worldbody>
</mujoco>
"""
model = mtx.load_mjcf_str(mjcf)
data = mtx.SceneData(model)
def step(z, v):
pos = np.zeros(7, np.float32)
vel = np.zeros(6, np.float32)
pos[2], pos[6], vel[2] = z, 1.0, v
data.set_dof_pos(pos, model)
data.set_dof_vel(vel)
model.step(data)
return np.array([float(data.dof_pos[2]), float(data.dof_vel[2])])
dt, g = float(model.options.timestep), 9.81
analytic = np.array([[1.0, dt], [0.0, 1.0]])
for eps in (1e-7, 1e-6, 1e-5, 1e-4, 1e-3):
base = step(0.5, 0.3)
col_z = (step(0.5 + eps, 0.3) - base) / eps
col_v = (step(0.5, 0.3 + eps) - base) / eps
jac = np.stack([col_z, col_v], axis=1)
print(eps, np.max(np.abs(jac - analytic)))
Environment
motrixsim==0.10.1, motrixsim-core==0.10.1 (PyPI, macOS arm64)
- Python 3.11
- Use case: mjbatch-style batched FD iLQR for humanoid trajectory optimization
Thank you!
Context
MotrixSim's batched API is already a very good fit for finite-difference trajectory optimization in the style of mjbatch:
Porting the iLQR solver itself is straightforward, but I hit two engine-level blockers that would be great to address.
1. Float32 wheels make finite differences noisy
mjbatch uses
EPS=1e-6with MuJoCo's float64 dynamics. Onmotrixsim==0.10.1(defaultreal_dtype() == "float32"), a one-step forward-difference Jacobian of a simple free-falling body has this error against the analytic dynamics:So
eps=1e-6is not usable, andeps>=1e-4starts to cross contact/discontinuity scales on contact-rich humanoid models.motrixsim.real_dtype()documents that an f64 build exists ("float64 for an f64 build"), but PyPI currently only exposes float32 wheels.Request: publish an f64 build (a separate wheel/extra, or a documented build flag). Even an opt-in package such as
motrixsim-core-f64would unblock FD iLQR/MPC.2. No way to clear cached solver warm-start state
The
SceneData.resetdocstring explicitly says:For one-step FD linearization, the step function must be a pure function of the written state/control. MuJoCo-based implementations avoid this by zeroing
qacc_warmstartbefore each perturbed step. In MotrixSim I could not find an equivalent public API.A simple sphere-plane contact smoke test did not show contamination (different contact histories followed by the same written state produced bit-identical next-step and 50-step outputs), but I would not rely on that for a contact-rich G1 model.
Request: expose an explicit way to make the next step independent of cached solver state, for example:
Reproduction snippet for the FD noise table
Environment
motrixsim==0.10.1,motrixsim-core==0.10.1(PyPI, macOS arm64)Thank you!