Biomechanics Quick Start

This guide is for biomechanists who want to use MyoSuite to run forward simulations, extract kinematic and kinetic data, perform inverse dynamics, and interface with tools like OpenSim.

No machine learning background is required.

Key Concepts

MyoSuite simulates musculoskeletal models in MuJoCo. A few terms you will encounter:

  • MjData — the simulation state at the current time step (positions, velocities, forces).

  • qpos — generalised joint positions (angles in radians for rotational joints).

  • qvel — generalised joint velocities (rad/s).

  • actuator_force — net force/torque generated by each muscle actuator (N or N·m).

  • Frame skip — number of physics sub-steps per control call (default 10 × 2 ms = 20 ms control period). Change the control step per env with EnvConfig(env_id, ctrl_dt=...) (see Machine Learning Quick Start).

Installation

pip install -U myosuite
# or with uv:
uv sync -p 3.10

See Installation for a from-source install and Python version support.

Running a Forward Simulation

from myosuite import make_env
import numpy as np

env = make_env('myoElbowPose1D6MRandom-v0')
obs, info = env.reset(seed=0)

# Access the underlying MuJoCo model and data
model = env.unwrapped.model  # mujoco.MjModel
data  = env.unwrapped.data   # mujoco.MjData

print("Number of joints:  ", model.njnt)
print("Number of muscles: ", model.nu)
print("Joint names:       ", [model.joint(i).name for i in range(model.njnt)])

obs, info = env.reset()
for _ in range(100):
    env.step(env.action_space.sample())  # random muscle excitations

env.close()

Note

env.unwrapped is the underlying MyoSuite env, which holds model, data and the state of the muscle conditions. On an env straight from make_env these attributes also work without .unwrapped, but not behind other wrappers (for example Stable-Baselines3’s Monitor or gymnasium’s RecordEpisodeStatistics). Use env.unwrapped in code that may be wrapped.

Extracting Kinematics

from myosuite import make_env
import numpy as np

env = make_env('myoElbowPose1D6MRandom-v0')
obs, info = env.reset(seed=0)

records = []
for _ in range(500):
    obs, reward, terminated, truncated, info = env.step(env.action_space.sample())
    d = env.unwrapped.data
    records.append({
        "time":    d.time,
        "qpos":    d.qpos.copy(),    # joint angles (rad)
        "qvel":    d.qvel.copy(),    # joint velocities (rad/s)
        "xpos":    d.xpos.copy(),    # body Cartesian positions (m)
    })
    if terminated or truncated:
        obs, info = env.reset()

# Summarise range of motion
qpos_array = np.array([r["qpos"] for r in records])
rom = np.ptp(qpos_array, axis=0)  # peak-to-peak per joint
model = env.unwrapped.model
for i, r in enumerate(rom):
    print(f"  {model.joint(i).name:30s}  ROM = {np.degrees(r):.1f} deg")

env.close()

Extracting Muscle Forces

data.actuator_force is the force of each actuator in N. MuJoCo reports a muscle’s tension with a negative sign, so the peak force of a muscle is the most negative value. The resulting joint torques are in data.qfrc_actuator:

from myosuite import make_env
import numpy as np

env = make_env('myoElbowPose1D6MRandom-v0')
obs, info = env.reset(seed=0)

force_records = []
for _ in range(500):
    obs, reward, terminated, truncated, info = env.step(env.action_space.sample())
    force_records.append(env.unwrapped.data.actuator_force.copy())
    if terminated or truncated:
        obs, info = env.reset()

forces = np.array(force_records)
model = env.unwrapped.model
print("Peak muscle forces (N):")
for i in range(model.nu):
    print(f"  {model.actuator(i).name:30s}  {-forces[:, i].min():.1f}")

env.close()

Inverse Dynamics

MyoSuite includes a tutorial for computing joint torques from motion data. See tutorials/3.3_Inverse_Dynamics.ipynb for a step-by-step walkthrough.

The key MuJoCo function is mujoco.mj_inverse(model, data), which fills data.qfrc_inverse with the generalised forces required to produce the observed acceleration:

import mujoco
import numpy as np

# After setting data.qpos, data.qvel, data.qacc:
mujoco.mj_inverse(model, data)
joint_torques = data.qfrc_inverse.copy()  # N·m per generalised coordinate

Inverse Kinematics

See tutorials/3.2_Inverse_Kinematics.ipynb for an IK solver built on top of MINK:

pip install -e ".[examples]"   # install mink
python tutorials/files/3.2/inverse_kinematics.py

Replaying OpenSim Motion Files

MyoSuite can play back *.mot files exported from OpenSim. See tutorials/3.5_Playback_Mot_File.ipynb for the full workflow.

The high-level steps are:

  1. Load the motion file (column headers map to joint names).

  2. Iterate over frames, setting data.qpos to the corresponding angles.

  3. Call mujoco.mj_forward(model, data) to update all derived quantities.

  4. Extract whatever quantities you need (muscle forces, contact forces, etc.).

Body Centre of Mass and Ground Reaction Forces

Continuing with the model and data of the forward simulation above:

import mujoco
import numpy as np

mujoco.mj_forward(model, data)

# Subtree centre of mass for the whole body (world frame)
com = data.subtree_com[0].copy()   # shape (3,)

# Contact forces (all contacts)
for i in range(data.ncon):
    contact = data.contact[i]
    force_torque = np.zeros(6)
    mujoco.mj_contactForce(model, data, i, force_torque)
    print(f"Contact {i}: force = {force_torque[:3]} N")

Available Musculoskeletal Models

Model

DoF

Muscles

Example environment

myoFinger

4

5–6

myoFingerPoseRandom-v0

myoElbow

1

6

myoElbowPose1D6MRandom-v0

myoHand

23

39

myoHandPoseRandom-v0

myoArm (hand-free)

18

50

myoArmReachRandom-v0

myoArm (full)

27

63

myoArmReachRandom-v0

myoLeg (14 joints plus the 6-DoF free root)

20

80

myoLegWalk-v0

myoTorso

18

216

myoTorsoPoseFixed-v0

For the full environment listing — task variants, pathological conditions, and challenge IDs — see Environment Reference.

To inspect any model interactively:

python -m myosuite.utils.examine_sim -s $(python -c "import myo_sim; print(myo_sim.get_path('legacy/elbow/myoelbow_1dof6muscles.xml'))")

Pathological Conditions

Variant environments model common clinical conditions:

from myosuite import make_env

# Sarcopenia: 50 % reduction in peak muscle force
env = make_env('myoSarcElbowPose1D6MFixed-v0')

# Cumulative fatigue: activation history reduces muscle output
env = make_env('myoFatiElbowPose1D6MFixed-v0')

Each variant is the base env registered with the matching wrapper, so the same conditions (and motor noise) can be applied to any other env, with your own parameters, through EnvConfig(env_id, features=...); see Neuroscience Quick Start for examples and docs/wiki/cross-backend-contract.md for which envs take which features.

Next Steps

  • tutorials/3.1_Analyse_Movements.ipynb — movement analysis with plotting

  • tutorials/3.3_Inverse_Dynamics.ipynb — joint torque computation

  • tutorials/3.4_Computed_Muscle_Control.ipynb — feedforward muscle control (CMC)

  • Neuroscience Quick Start — sensory feedback and reflex controllers

  • Sports Medicine & Rehabilitation Quick Start — rehabilitation and clinical condition modelling