{
"cells": [
{
"cell_type": "markdown",
"id": "3c7aa3a8",
"metadata": {},
"source": [
"[](https://colab.research.google.com/github/tayalmanan28/MuJoCo-Tutorial/blob/main/tutorial/10_troubleshooting.ipynb)\n",
"\n",
"# Tutorial 10: Troubleshooting & Common Mistakes\n",
"\n",
"A survival guide for the errors every MuJoCo beginner hits."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6d97aeec",
"metadata": {},
"outputs": [],
"source": [
"!pip install -q mujoco mediapy"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "75132590",
"metadata": {},
"outputs": [],
"source": [
"import mujoco\n",
"import numpy as np\n",
"import mediapy as media"
]
},
{
"cell_type": "markdown",
"id": "e0d012da",
"metadata": {},
"source": [
"---\n",
"## Problem 1: Black screen when rendering\n",
"\n",
"**Symptom:** You render an image but it's all black (or partially black).\n",
"\n",
"**Cause:** You didn't call `mj_forward()` after setting `qpos` manually, so derived quantities (like Cartesian positions of geoms) haven't been computed yet."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e73e06da",
"metadata": {},
"outputs": [],
"source": [
"xml = \"\"\"\n",
"\n",
" \n",
" \n",
" \n",
" \n",
"\n",
"\"\"\"\n",
"model = mujoco.MjModel.from_xml_string(xml)\n",
"data = mujoco.MjData(model)\n",
"renderer = mujoco.Renderer(model)\n",
"\n",
"# ❌ WRONG: rendering without mj_forward\n",
"renderer.update_scene(data)\n",
"bad_image = renderer.render().copy()\n",
"\n",
"# ✅ CORRECT: call mj_forward first\n",
"mujoco.mj_forward(model, data)\n",
"renderer.update_scene(data)\n",
"good_image = renderer.render().copy()\n",
"\n",
"renderer.close()\n",
"media.show_images([bad_image, good_image], titles=['❌ Without mj_forward', '✅ With mj_forward'])"
]
},
{
"cell_type": "markdown",
"id": "49acfb38",
"metadata": {},
"source": [
"**Fix:** Always call `mujoco.mj_forward(model, data)` after manually setting `data.qpos` or `data.qvel`. If you're calling `mj_step()` in a loop, this is done automatically."
]
},
{
"cell_type": "markdown",
"id": "87b19acd",
"metadata": {},
"source": [
"---\n",
"## Problem 2: Simulation \"explodes\" (NaN or huge values)\n",
"\n",
"**Symptom:** Positions become `inf` or `nan`, objects fly to infinity.\n",
"\n",
"**Common causes:**\n",
"1. Timestep too large for the model\n",
"2. Extremely large control forces\n",
"3. Penetrating geoms at initialization"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a9ac343a",
"metadata": {},
"outputs": [],
"source": [
"# Example: timestep too large\n",
"xml_stiff = \"\"\"\n",
"\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
"\n",
"\"\"\"\n",
"\n",
"m = mujoco.MjModel.from_xml_string(xml_stiff)\n",
"d = mujoco.MjData(m)\n",
"\n",
"print(\"Simulating with dt=0.1 (dangerously large):\")\n",
"for i in range(20):\n",
" mujoco.mj_step(m, d)\n",
" z = d.qpos[2]\n",
" if abs(z) > 100 or np.isnan(z):\n",
" print(f\" Step {i}: z = {z} ← EXPLODED!\")\n",
" break\n",
" elif i < 5 or i % 5 == 0:\n",
" print(f\" Step {i}: z = {z:.4f}\")\n",
"\n",
"print(\"\\n✅ Fix: Use timestep ≤ 0.005 (default is 0.002)\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eb2a2453",
"metadata": {},
"outputs": [],
"source": [
"# How to detect instability in your code:\n",
"def check_simulation_health(data):\n",
" \"\"\"Call this periodically to catch explosions early.\"\"\"\n",
" if np.any(np.isnan(data.qpos)) or np.any(np.isnan(data.qvel)):\n",
" return \"NaN detected! Simulation diverged.\"\n",
" if np.any(np.abs(data.qpos) > 1e6):\n",
" return \"Extreme positions! Likely unstable.\"\n",
" if np.any(np.abs(data.qvel) > 1e6):\n",
" return \"Extreme velocities! Likely unstable.\"\n",
" return \"OK\"\n",
"\n",
"print(\"Use this helper to catch problems early!\")\n",
"print(f\"Health check: {check_simulation_health(d)}\")"
]
},
{
"cell_type": "markdown",
"id": "dbf117e2",
"metadata": {},
"source": [
"---\n",
"## Problem 3: Objects pass through each other\n",
"\n",
"**Symptom:** Bodies that should collide don't.\n",
"\n",
"**Common causes:**\n",
"1. Mismatched `contype`/`conaffinity` (collision filtering)\n",
"2. Geoms in the same body don't collide with each other by default\n",
"3. Objects starting in deep penetration"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "222841e1",
"metadata": {},
"outputs": [],
"source": [
"# Collision happens when: (geomA.contype & geomB.conaffinity) OR (geomB.contype & geomA.conaffinity)\n",
"# Default: contype=1, conaffinity=1 → everything collides with everything\n",
"\n",
"# If you accidentally set contype=0:\n",
"xml_no_collide = \"\"\"\n",
"\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
"\n",
"\"\"\"\n",
"\n",
"m = mujoco.MjModel.from_xml_string(xml_no_collide)\n",
"d = mujoco.MjData(m)\n",
"\n",
"# The ball falls through the floor\n",
"for _ in range(500):\n",
" mujoco.mj_step(m, d)\n",
"print(f\"Ball fell to z={d.qpos[2]:.2f} (below the ground!)\")\n",
"print(\"\\n✅ Fix: Ensure colliding geoms have matching contype & conaffinity\")\n",
"print(\" Default (contype=1, conaffinity=1) works for most cases.\")"
]
},
{
"cell_type": "markdown",
"id": "aa9137ac",
"metadata": {},
"source": [
"---\n",
"## Problem 4: Robot doesn't move (control has no effect)\n",
"\n",
"**Symptom:** You set `data.ctrl` but nothing happens.\n",
"\n",
"**Common causes:**\n",
"1. Forgot to add `` in XML\n",
"2. Control range is too small\n",
"3. Joint has high damping that kills all motion\n",
"4. Setting ctrl outside the control loop (only once)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ed5b6232",
"metadata": {},
"outputs": [],
"source": [
"# Debugging checklist:\n",
"xml_debug = \"\"\"\n",
"\n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
" \n",
"\n",
"\"\"\"\n",
"m = mujoco.MjModel.from_xml_string(xml_debug)\n",
"d = mujoco.MjData(m)\n",
"\n",
"print(\"=== Control Debugging Checklist ===\")\n",
"print(f\" Number of actuators (model.nu): {m.nu}\")\n",
"print(f\" Control range: {m.actuator_ctrlrange}\")\n",
"print(f\" Actuator gear: {m.actuator_gear[:, 0]}\")\n",
"print(f\" Joint damping: {m.jnt_stiffness}\")\n",
"print(f\" data.ctrl shape: {d.ctrl.shape}\")\n",
"print()\n",
"print(\"If nu=0, you forgot to add in your XML!\")\n",
"print(\"If ctrl has no effect, check gear and ctrlrange.\")"
]
},
{
"cell_type": "markdown",
"id": "7dc225b4",
"metadata": {},
"source": [
"---\n",
"## Problem 5: Model won't load (XML errors)\n",
"\n",
"**Common XML mistakes:**"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a37c7fd",
"metadata": {},
"outputs": [],
"source": [
"# Example: common XML mistakes and how to fix them\n",
"\n",
"bad_examples = {\n",
" \"Missing closing tag\": '',\n",
" \"Actuator references nonexistent joint\": '''\n",
" \n",
" \n",
" \n",
" ''',\n",
" \"Body outside worldbody\": '',\n",
"}\n",
"\n",
"for name, xml in bad_examples.items():\n",
" try:\n",
" mujoco.MjModel.from_xml_string(xml)\n",
" print(f\" {name}: no error (unexpected)\")\n",
" except Exception as e:\n",
" # Get first line of error\n",
" error_msg = str(e).split('\\n')[0][:80]\n",
" print(f\"❌ {name}\")\n",
" print(f\" Error: {error_msg}\")\n",
" print()"
]
},
{
"cell_type": "markdown",
"id": "c73b0fa7",
"metadata": {},
"source": [
"**Tips for XML debugging:**\n",
"1. Start with a minimal working model and add things one at a time\n",
"2. MuJoCo's error messages tell you the line number — read them!\n",
"3. Use an XML validator or IDE with XML highlighting\n",
"4. Common typo: `fromto` needs 6 numbers (x1 y1 z1 x2 y2 z2)"
]
},
{
"cell_type": "markdown",
"id": "3c1b3119",
"metadata": {},
"source": [
"---\n",
"## Problem 6: Rendering works locally but not on a server/Colab\n",
"\n",
"**Symptom:** `mujoco.viewer.launch()` fails with display errors.\n",
"\n",
"**Cause:** Interactive viewer needs a display (GUI). Servers/Colab don't have one.\n",
"\n",
"**Fix:** Use `mujoco.Renderer` (offscreen) instead:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a2305594",
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"\n",
"# Check if we have a display\n",
"has_display = 'DISPLAY' in os.environ or os.environ.get('MUJOCO_GL') == 'egl'\n",
"\n",
"print(\"Rendering options:\")\n",
"print(f\" Display available: {has_display}\")\n",
"print()\n",
"print(\" Local machine (has display):\")\n",
"print(\" → mujoco.viewer.launch(model, data) # interactive 3D viewer\")\n",
"print(\" → mujoco.viewer.launch_passive(model, data) # non-blocking\")\n",
"print()\n",
"print(\" Server/Colab (no display):\")\n",
"print(\" → renderer = mujoco.Renderer(model) # offscreen\")\n",
"print(\" → renderer.render() # returns numpy array\")\n",
"print(\" → media.show_image(pixels) # display in notebook\")\n",
"print()\n",
"print(\" Tip: Set MUJOCO_GL=egl for headless GPU rendering:\")\n",
"print(\" os.environ['MUJOCO_GL'] = 'egl' # before importing mujoco\")"
]
},
{
"cell_type": "markdown",
"id": "7dd26a80",
"metadata": {},
"source": [
"---\n",
"## Problem 7: Performance is slow\n",
"\n",
"**Tips for speed:**"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a63ece75",
"metadata": {},
"outputs": [],
"source": [
"import time\n",
"\n",
"xml = \"\"\"\n",
"\n",
" \n",
"\"\"\"\n",
"m = mujoco.MjModel.from_xml_string(xml)\n",
"d = mujoco.MjData(m)\n",
"\n",
"# Benchmark\n",
"n_steps = 100_000\n",
"t0 = time.time()\n",
"for _ in range(n_steps):\n",
" mujoco.mj_step(m, d)\n",
"elapsed = time.time() - t0\n",
"\n",
"sim_time = n_steps * m.opt.timestep\n",
"print(f\"{n_steps:,} steps in {elapsed:.2f}s\")\n",
"print(f\"Simulated {sim_time:.0f}s of physics in {elapsed:.2f}s wall time\")\n",
"print(f\"Speed: {sim_time/elapsed:.0f}× real-time\")\n",
"print()\n",
"print(\"Performance tips:\")\n",
"print(\" 1. Don't render every step — render at 30-60 fps only\")\n",
"print(\" 2. Use larger timestep if stable (0.005 instead of 0.002)\")\n",
"print(\" 3. Reduce model complexity (fewer geoms/contacts)\")\n",
"print(\" 4. For RL: use MJX (GPU) to batch thousands of envs\")\n",
"print(\" 5. Avoid Python loops around mj_step — use native callbacks\")"
]
},
{
"cell_type": "markdown",
"id": "baed7cfd",
"metadata": {},
"source": [
"---\n",
"## Quick Reference: Common Operations\n",
"\n",
"```python\n",
"# ─── Setup ───\n",
"model = mujoco.MjModel.from_xml_string(xml) # or from_xml_path(\"file.xml\")\n",
"data = mujoco.MjData(model)\n",
"\n",
"# ─── Reset ───\n",
"mujoco.mj_resetData(model, data) # reset to initial state\n",
"data.qpos[0] = 1.5 # then set custom state\n",
"mujoco.mj_forward(model, data) # propagate (IMPORTANT!)\n",
"\n",
"# ─── Simulate ───\n",
"data.ctrl[0] = 1.0 # set control\n",
"mujoco.mj_step(model, data) # advance one timestep\n",
"\n",
"# ─── Read state ───\n",
"pos = data.qpos[:] # all positions\n",
"vel = data.qvel[:] # all velocities\n",
"t = data.time # current sim time\n",
"\n",
"# ─── Named access ───\n",
"model.joint('shoulder').id # joint ID by name\n",
"data.body('robot').xpos # body position in world\n",
"data.site('end_effector').xpos # site position\n",
"\n",
"# ─── Render ───\n",
"renderer = mujoco.Renderer(model, height=480, width=640)\n",
"mujoco.mj_forward(model, data) # needed before first render\n",
"renderer.update_scene(data)\n",
"pixels = renderer.render() # numpy array (H, W, 3)\n",
"renderer.close() # free resources\n",
"\n",
"# ─── Contacts ───\n",
"n = data.ncon # number of active contacts\n",
"force = np.zeros(6)\n",
"mujoco.mj_contactForce(model, data, 0, force) # force at contact 0\n",
"```"
]
},
{
"cell_type": "markdown",
"id": "b5b26b41",
"metadata": {},
"source": [
"---\n",
"## Still stuck?\n",
"\n",
"1. **Official docs:** [mujoco.readthedocs.io](https://mujoco.readthedocs.io/)\n",
"2. **GitHub issues:** [github.com/google-deepmind/mujoco/issues](https://github.com/google-deepmind/mujoco/issues)\n",
"3. **XML reference:** [mujoco.readthedocs.io/en/stable/XMLreference.html](https://mujoco.readthedocs.io/en/stable/XMLreference.html)\n",
"4. **Python bindings:** [mujoco.readthedocs.io/en/stable/python.html](https://mujoco.readthedocs.io/en/stable/python.html)"
]
}
],
"metadata": {
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 5
}