{ "cells": [ { "cell_type": "markdown", "id": "3c7aa3a8", "metadata": {}, "source": [ "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](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", "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", "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 }