integrations
External-backend dynamics adapters for OpenSCvx.
Two backends are currently supported. MjxDynamics wraps a MuJoCo MJX model;
FraxDynamics wraps a frax.Robot (Fast Robot Kinematics and Dynamics in
JAX). Both go directly into the dynamics= slot of Problem and construct
the matching State/Control objects for the user::
from openscvx.integrations import FraxDynamics
from frax.robots.franka_panda import load_panda
dyn = FraxDynamics(load_panda())
problem = ox.Problem(dynamics=dyn, states=dyn.states, controls=dyn.controls, ...)
For MJX, free-joint quaternion kinematics for floating-base models (drones, humanoids) are detected and handled automatically::
from openscvx.integrations import MjxDynamics
dyn = MjxDynamics(mjx_model)
problem = ox.Problem(
dynamics=dyn,
states=dyn.states,
controls=dyn.controls,
...
)
For advanced users who need custom State/Control names (or to interleave
them with extra custom states), mjx_dynamics is exposed as the underlying
BYOF callable factory — assemble your own byof["dynamics"] dict from it.
All MJX symbols delegate lazily so mujoco.mjx is only imported when
actually used. The menagerie submodule is also loaded lazily.
Example — cartpole (nq == nv)::
from openscvx.integrations import MjxDynamics
dyn = MjxDynamics(mjx_model)
problem = ox.Problem(dynamics=dyn, states=dyn.states, controls=dyn.controls, ...)
Example — quadrotor with free joint (nq=7, nv=6)::
from openscvx.integrations import MjxDynamics
dyn = MjxDynamics(mjx_model)
problem = ox.Problem(dynamics=dyn, states=dyn.states, controls=dyn.controls, ...)
DynamicsAdapter
¶
Bases: ABC
Abstract base class for external-backend dynamics adapters.
Subclasses describe the State/Control objects they synthesize on
.states / .controls and implement expand to return the
two-channel (dynamics_dict, byof_dict) representation consumed by
Problem.
The split mirrors the existing two-channel API: dynamics_dict carries
symbolic Expr entries (e.g. {"qpos": qvel}) while byof_dict carries
raw JAX callables under the "dynamics" key. Either or both may be
empty, but expand() should never silently produce overlapping keys.
Source code in openscvx/integrations/base.py
expand() -> Tuple[dict, dict]
abstractmethod
¶
Return (dynamics_dict, byof_dict) in OpenSCvx's internal form.
dynamics_dict maps state names to symbolic Expr derivatives
(the same shape as the dynamics= argument to Problem).
byof_dict has the same shape as the byof= argument: its
"dynamics" key (if present) maps state names to raw JAX callables.
Source code in openscvx/integrations/base.py
FraxDynamics
¶
Bases: DynamicsAdapter
First-class frax dynamics adapter for Problem.
Wraps a frax.Robot so it can be passed directly to the dynamics=
argument of Problem. The adapter constructs default q / qd State
objects and a tau Control matching the robot's joint count, exposes
them via .states / .controls, and routes the frax forward dynamics
through the BYOF channel internally — without requiring the user to know
about BYOF.
Joint limits and torque bounds are read from the robot's URDF and auto-populated on the State/Control objects. Override any of them after construction if you want tighter problem-specific bounds::
dyn = ox.FraxDynamics(load_panda())
q, qd = dyn.states
q.initial = np.array([0.0, -0.7854, 0.0, -2.3562, 0.0, 1.5708, 0.7854])
# tighten velocity limits for a slow approach
qd.max = np.full(7, 1.0)
Example
Franka Panda::
from frax.robots.franka_panda import load_panda
import openscvx as ox
dyn = ox.FraxDynamics(load_panda())
problem = ox.Problem(
dynamics=dyn,
states=dyn.states,
controls=dyn.controls,
constraints=[...],
N=50,
time=ox.Time(...),
)
Floating-base robots (robot.includes_floating_dof = True) are
supported. The tau Control covers only the num_actuated_joints
actuated DOFs; the adapter inserts the required zero-torque entries for the
unactuated floating-base DOFs internally.
Custom State/Control names are not supported — drop to the lower-level
frax_dynamics helper for that.
Auto-populated bounds
q.min/q.max: from the URDF<limit lower/upper>.qd.min/qd.max: from the URDF<limit velocity>(mirrored as±max_vel).tau.min/tau.max: from the URDF<limit effort>for the actuated joints.
frax's ±1e6 "no limit" sentinel (used for floating-base DOFs) is
mapped to ±inf on all of the above.
Source code in openscvx/integrations/frax.py
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 | |
expand() -> Tuple[dict, dict]
¶
Return (dynamics_dict, byof_dict) for this frax robot.
frax always has nq == nv (no quaternion floating-base split), so
dynamics_dict is always {"q": qd} and the full joint
acceleration goes through BYOF.
Source code in openscvx/integrations/frax.py
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
frax_dynamics(*args: Any, **kwargs: Any) -> Any
¶
Lazy delegate to the low-level frax BYOF factory.
mjx_dynamics(*args: Any, **kwargs: Any) -> Any
¶
Lazy delegate; imports mujoco.mjx on first call.