mjx
MuJoCo MJX dynamics adapters for OpenSCvx.
The recommended entry-point is MjxDynamics, a DynamicsAdapter that goes
directly into the dynamics= slot of Problem and exposes the synthesized
State/Control objects on .states / .controls::
dyn = ox.MjxDynamics(mjx_model)
problem = ox.Problem(
dynamics=dyn,
states=dyn.states,
controls=dyn.controls,
...
)
Free-joint quaternion kinematics (nq > nv models such as drones or
humanoids) are detected and handled automatically.
The lower-level mjx_dynamics callable factory is also public for advanced
users who need to assemble their own BYOF dynamics dict (e.g. with custom
State/Control names or interleaved with other states).
Note
Time dilation is handled automatically by the BYOF lowering pipeline; all functions return physical (un-dilated) quantities.
MjxDynamics
¶
Bases: DynamicsAdapter
First-class MJX dynamics adapter for Problem.
Wraps a mujoco.mjx model so it can be passed directly to the
dynamics= argument of Problem. The adapter
constructs default qpos / qvel State objects and a ctrl
Control matching the model's nq / nv / nu, exposes them via
.states / .controls, and routes the MJX forward dynamics through
the BYOF channel internally — without requiring the user to know about
BYOF at all.
Example
Cartpole (nq == nv)::
mj_model = mujoco.MjModel.from_xml_path("cartpole.xml")
mj_model.opt.disableflags |= mujoco.mjtDisableBit.mjDSBL_CONTACT
mjx_model = mjx.put_model(mj_model)
dyn = ox.MjxDynamics(mjx_model)
problem = ox.Problem(
dynamics=dyn,
states=dyn.states,
controls=dyn.controls,
...
)
Quadrotor with a free joint (nq > nv) — quaternion kinematics
are inserted automatically::
dyn = ox.MjxDynamics(mjx_model) # nq=7, nv=6
problem = ox.Problem(
dynamics=dyn, states=dyn.states, controls=dyn.controls, ...
)
Custom State/Control names or shapes are not supported here on purpose
— the whole point of the adapter is "I don't want to think about names."
Drop to the lower-level mjx_dynamics helper if you need that control —
construct your own State/Control objects, pass them in, and assemble the
BYOF dict yourself.
Supported joint structure
- Free (
mjJNT_FREE), slide (mjJNT_SLIDE), and hinge (mjJNT_HINGE) joints only. - If the model contains any free joints, they must all come before any slide/hinge joints in the MuJoCo layout.
- Ball joints (
mjJNT_BALL) are explicitly refused — they sharenq=4, nv=3with free joints but use different kinematics, and would silently produce wrong dynamics.
Construction raises NotImplementedError if any of these
conditions are violated; fall back to mjx_dynamics for those
cases.
Auto-populated bounds
qpos.min/qpos.maxare read frommjx_model.jnt_rangefor slide / hinge joints flaggedjnt_limited=True; free-joint slots and unlimited joints default to±inf.ctrl.min/ctrl.maxare read fromactuator_ctrlrangefor actuators flaggedactuator_ctrllimited=True; otherwise±inf.qvelbounds default to±inf(MuJoCo has no per-joint velocity-limit concept).
Override any of these after construction if you want tighter problem-specific bounds.
Source code in openscvx/integrations/mjx.py
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 | |
expand() -> Tuple[dict, dict]
¶
Return (dynamics_dict, byof_dict) for this MJX model.
nq == nv:dynamics_dict = {"qpos": qvel}(symbolic kinematic identity),byof_dict["dynamics"] = {"qvel": ...}.nq > nv:dynamics_dict = {},byof_dict["dynamics"]contains both"qpos"(quaternion kinematics) and"qvel".
Source code in openscvx/integrations/mjx.py
mjx_dynamics(mjx_model: Any, *, qpos: State | slice, qvel: State | slice, ctrl: Control | slice, return_component: str = 'qacc', extra_postprocess: Optional[Callable[[Any], Any]] = None) -> Callable
¶
Wrap a MuJoCo MJX model as a BYOF dynamics function.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
mjx_model
|
Any
|
A model produced by :func: |
required |
qpos
|
State | slice
|
Position state (or slice into the unified |
required |
qvel
|
State | slice
|
Velocity state (or slice). Length must equal |
required |
ctrl
|
Control | slice
|
Control variable (or slice into the unified |
required |
return_component
|
str
|
Which MJX field to return. |
'qacc'
|
extra_postprocess
|
Optional[Callable[[Any], Any]]
|
Optional callable applied to the MJX |
None
|
Returns:
| Type | Description |
|---|---|
Callable
|
A function |
Callable
|
dynamics signature. |
Raises:
| Type | Description |
|---|---|
ImportError
|
If |
ValueError
|
If |
Note
MJX's contact solver uses lax.while_loop, which is not
reverse-mode differentiable. For contact-free systems (manipulators,
cartpoles, etc.) disable contacts before uploading the model::
mj_model.opt.disableflags |= mujoco.mjtDisableBit.mjDSBL_CONTACT
mjx_model = mjx.put_model(mj_model)
Example
Cartpole swing-up dynamics::
import mujoco
import mujoco.mjx as mjx
import openscvx as ox
from openscvx.integrations import mjx_dynamics
mj_model = mujoco.MjModel.from_xml_path("cartpole.xml")
mjx_model = mjx.put_model(mj_model)
qpos = ox.State("qpos", shape=(mjx_model.nq,))
qvel = ox.State("qvel", shape=(mjx_model.nv,))
ctrl = ox.Control("ctrl", shape=(mjx_model.nu,))
qvel_dynamics = mjx_dynamics(mjx_model, qpos=qpos, qvel=qvel, ctrl=ctrl)
problem = ox.Problem(
dynamics={"qpos": qvel},
byof={"dynamics": {"qvel": qvel_dynamics}},
states=[qpos, qvel],
controls=[ctrl],
...
)
Source code in openscvx/integrations/mjx.py
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | |