Skip to content

Feature request: f64 build and a way to clear solver warm-start state for finite-difference iLQR #17

Description

@TATP-233

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!

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions