PTENES
MODULE 5.5 · FUNDAMENTALS

🧪 Simulate before going real

In simulation, the arm can hit things, fall, and make mistakes a thousand times without breaking anything. Here you run the SO-101 digital twin in MuJoCo, learn when Isaac Sim is worth using, turn a photo of the table into a scene, and learn to measure the gap between simulation and reality, where most projects stumble.

6
Topics
Advanced
Level
Foundation
Type
PC only
Hardware required
0%
0 of 0 topics read · Section 1 of 6

Detailed content

1

🧭 Why simulate first

With the real arm, every attempt costs something: you set up the table, wait for the movement, wear out the servos, and risk knocking something over. In simulation, the computer calculates the physics and runs hundreds of attempts while you have a coffee. That’s why this course has a simple rule: no plan or policy goes to the real arm without first passing through simulation.

🌱 New here?

A physics simulator is a program that calculates, step by step, how objects with mass, friction, and motors move and collide. A digital twin is a copy of a robot (and sometimes the room) inside the simulator, with the same dimensions, joints, and limits as the real one, so something that works in one has a chance of working in the other.

SIMULATIONhundreds of attempts, almost no cost SLOW REAL-WORLD TESTING20 attempts USE breaksnothing every mistakecosts

How to read this map: the funnel narrows from top to bottom. The wide section (simulation) filters out almost all planning and code errors. The middle section (slow real-world testing) catches what the simulation does not represent well. Only what passes both reaches normal use.

✓ Simulation gets these right

  • ✓Arm reach and collisions with the table.
  • ✓The order of a plan’s steps and logic errors.
  • ✓Generating many variations in position for training.

✗ Simulation often gets these wrong

  • ✗Gripper friction and objects that slip.
  • ✗Slack and delay in cheap servos.
  • ✗Lighting, reflections, and textures seen by the camera.

Why learn this

AI-powered robots make mistakes in unpredictable ways. Simulation is a safe place to discover those mistakes, and it is also a data factory that reduces how many demonstrations you need to record by hand.

Key concepts

Simulator
calculated physics
Digital twin
measured copy of reality
Test funnel
sim → slow real-world testing → use
Cost of an error
zero in sim
2

🦾 MuJoCo and the SO-101 twin

O MuJoCo is a free, open physics simulator maintained by Google DeepMind, light enough to run on a laptop without a dedicated graphics card. Its official model collection, MuJoCo Menagerie, already includes the SO-101, ready to use, with the same 6 joints as the real arm. You install it with pip and control everything in Python.

🌱 New here?

MJCF is the XML file that describes the robot for MuJoCo: parts, joints, masses, and motors (similar to the ROS URDF, covered in 4.2). The physics step is the interval the simulator advances with each calculation; for the SO-101 model, it is 5 ms, so 2 simulated seconds equal 400 steps.

real arm twin (MJCF) shoulder_panshoulder_liftelbow_flexwrist_flexwrist_rollgripper same command, same 6 joints

How to read this map: the box in the middle is the joint list, the same on both sides. The same command ("shoulder at 0.6 rad") applies to the real arm and the twin. If the twin and the real arm end up in different places with the same command, you have just measured the sim2real gap from topic 5.

Goal: install MuJoCo in a virtual environment, download only the SO-101 from Menagerie, and open the twin in a 3D window.

install-sim.sh
python3 -m venv .venv-sim
.venv-sim/bin/pip install mujoco numpy

# downloads only the SO-101 folder from the official model collection
git clone --depth 1 --filter=blob:none --sparse \
  https://github.com/google-deepmind/mujoco_menagerie.git
git -C mujoco_menagerie sparse-checkout set robotstudio_so101

# 3D window: drag to rotate, double-click a part, and Ctrl+drag to push it
.venv-sim/bin/python -m mujoco.viewer --mjcf=mujoco_menagerie/robotstudio_so101/scene.xml

Now control the same twin with code: move two joints to a position and measure where the gripper stopped.

gemeo_so101.py
# SO-101 twin in MuJoCo: load the model, move joints, and measure where the gripper ended up.
import sys
import mujoco

CENA = sys.argv[1] if len(sys.argv) > 1 else "mujoco_menagerie/robotstudio_so101/scene.xml"
model = mujoco.MjModel.from_xml_path(CENA)
data = mujoco.MjData(model)

print("juntas:", [model.joint(i).name for i in range(model.njnt)])
print("atuadores:", [model.actuator(i).name for i in range(model.nu)])

mujoco.mj_resetData(model, data)
alvo = {"shoulder_pan": 0.6, "elbow_flex": 0.5}   # radians (0.6 rad = about 34 degrees)
for nome, valor in alvo.items():
    data.ctrl[model.actuator(nome).id] = valor

garra = model.body("gripper").id
for passo in range(int(2.0 / model.opt.timestep)):  # 2 seconds of physics
    mujoco.mj_step(model, data)
    if passo % int(0.5 / model.opt.timestep) == 0:
        x, y, z = data.xpos[garra]
        pan = data.qpos[model.joint("shoulder_pan").qposadr[0]]
        print(f"t={data.time:4.1f}s  pan={pan:+.3f} rad  garra=({x:+.3f}, {y:+.3f}, {z:+.3f}) m")

erro = abs(data.qpos[model.joint("shoulder_pan").qposadr[0]] - alvo["shoulder_pan"])
print("final shoulder_pan error:", round(erro, 4), "rad", "OK" if erro < 0.05 else "FAR FROM TARGET")

How to check

  • ✓.venv-sim/bin/python gemeo_so101.py lists the 6 joints and the 6 actuators, from shoulder_pan a gripper.
  • ✓In half a second, the pan reaches +0.600 rad, and the gripper moves from about (+0.29; 0; +0.23) m to (+0.23; −0.13; +0.14) m. The last line says "OK".
  • ✗File not found error? Run the script in the folder containing mujoco_menagerie, or pass the path to scene.xml as an argument.

💡 See also: robotica-so101

The INEMA project robotica-so101 Take this digital twin further: pick up a cube, a ball, and a small container and place them on a tray (50 out of 50 episodes in simulation), record it with three cameras, and run a test with varied measurements before the real arm moves.

Why learn this

With the twin, you can test reach, camera position, and project logic before printing a part. It also lets the whole class work with the arm when the school has only one.

Key concepts

MuJoCo
lightweight, open-source physics
Menagerie
ready-made models
MJCF
robot description
ctrl / qpos
command / position
3

🖥️ Isaac Sim/Isaac Lab: when to use them

O NVIDIA Isaac Sim is a simulator with near-photographic images, detailed sensors, and ROS 2 integration. The Isaac Lab, built on top of it, runs thousands of robot copies in parallel on the GPU, making reinforcement learning feasible (the robot learns through trial and error, guided by rewards). The trade-off is the hardware: it requires an NVIDIA RTX GPU with plenty of memory and a much heavier installation.

CriterionMuJoCoIsaac Sim / Isaac Lab
Computerany PC or laptopNVIDIA RTX GPU with plenty of memory
Installationpip install mujocolarge, with driver requirements
Camera imagesimplenear-photographic
Many robots in parallelyes (GPU versions are available)thousands; its strong point
Best forlearning, testing plans, generating episodeslarge-scale reinforcement learning, realistic vision, humanoids

✓ Isaac is a good choice when

  • ✓You have (or rent) an RTX GPU and are going to train with reinforcement learning.
  • ✓The policy depends heavily on images and needs realistic scenes.
  • ✓The project already uses the NVIDIA ecosystem (Jetson, ROS 2).

✗ Stick with MuJoCo when

  • ✗The PC is a school computer without an NVIDIA GPU.
  • ✗The goal is to test the plan, reach, and logic.
  • ✗The class is still learning what a digital twin is.

💡 What about Gazebo?

Gazebo (Track 4) is the natural simulator for a mobile robot with ROS 2, maps, and Nav2. For an arm with fine contact control and generating episodes for LeRobot, MuJoCo is more straightforward. You can use both in the final project: Gazebo for the base, MuJoCo for the arm.

Why learn this

Choosing a simulator based on the problem, not its reputation, saves weeks of installation time. Most of what this course covers works in MuJoCo; use Isaac when the project calls for scale or realistic images.

Key concepts

Isaac Sim
realistic images
Isaac Lab
parallel training
Reinforcement learning
learns through rewards
Gazebo
mobile base with ROS 2
4

📷 Real2sim: turn a photo of the table into a scene

Building a scene by hand (where the box is, how big the cube is) takes work. Real2sim is the reverse of sim2real: you start from the real world to build the simulation. A vision model looks at a photo of the table, estimates objects, sizes, and positions in meters, and a program builds the scene in MuJoCo. The simulation then generates episodes from it and tests the plan before the arm moves.

photo of the table cena.jsonobjects andmeters vision model digital twinMuJoCo episodeswith variation TEST: 20 variationspass only with 90% or higher fail: go back to the plan

How to read this map: the top row builds the virtual world; the box at the bottom is the gate. The weak point is the cyan box: the vision model gets measurements wrong, so the test varies sizes and positions to avoid approving a plan that only works with perfect measurements.

Step by step with robotica-real2sim

1

Take a photo with an A4 sheet on the table

The sheet provides a scale. Take 2 or 3 photos from different angles: measurements are where errors are most common.

2

Generate or write the scene

The project uses a vision model to read the photo. Without one, copy the example exemplos/mesa-teste/cena.json and fill in the measurements with a tape measure.

3

Record, generate episodes, and test

Compare the digital twin video with the photo. Only then run the plan test.

Goal: run the project's ready-made example (with the Franka Panda arm from the MuJoCo collection) without depending on any external service.

real2sim.sh
git clone https://github.com/inematds/robotica-real2sim
cd robotica-real2sim
./instalar.sh                     # venv + MuJoCo + arm model + tests
# without a monitor (server, WSL): export MUJOCO_GL=egl  (without a GPU: osmesa)
./r2s.py filmar exemplos/mesa-teste/cena.json ep.mp4 --cameras geral,cima,pulso
./r2s.py episodios exemplos/mesa-teste/cena.json saida/ -n 50
./r2s.py provar exemplos/mesa-foto/cena.json resultados/mesa-foto/plano.json --sem-vagas
./r2s.py provar exemplos/mesa-foto/cena.json resultados/mesa-foto/plano.json

How to check

  • ✓ep.mp4 shows the arm picking up the cube, with the current phase displayed on screen, from three cameras.
  • ✓Generation finishes with 50 out of 50 successful episodes.
  • ✓The first test (without spaces on the tray) fails, at around 4 out of 20: the ball lands on top of the can. The second, with one space per object, passes, at around 19 out of 20.

Why learn this

The test result is the lesson: a plan that seemed obvious failed 16 times out of 20, and only the simulation revealed that, without knocking anything over. Building the scene from your own table is what turns simulation from a polished demo into a real tool.

Key concepts

Real2sim
from real to simulation
Scale
A4 sheet in the photo
Test
N variations, minimum score
Tape measure
checks the vision model
5

📏 Sim2real: the gap between simulation and reality

Every simulation is an approximation. The difference between what happens in the digital twin and the real arm has a name: sim2real gap (in English, reality gap). It comes from friction, servo backlash and delay, the actual mass of the parts, and the light and texture seen by the camera. You can't eliminate this gap; you measure it and train the policy to handle it.

🌱 New here?

Domain randomization means sampling slightly different values for friction, mass, motor force, color, and lighting in each simulated episode. A policy that works across all these “wrong” versions is more likely to work in the real world, which is just another different version.

time joint angle target digital twin real arm randomized range

How to read this map: The green curve (twin) rises smoothly to the target. The dashed red curve (real) starts late, overshoots, and stops with a small error, typical of a cheap servo with backlash. The blue band shows the set of sampled twins: if it covers the real curve, the policy trained on it has already “seen” something similar to the real world.

Goal: Run the same command on 20 versions of the SO-101, with sampled friction, servo force, and gripper mass, and measure how much the gripper position varies.

variar_so101.py
# Domain randomization: the same command, 20 "slightly different" arms.
# If the results vary a lot, your control depends on details that the real world doesn't guarantee.
import sys
import numpy as np
import mujoco

CENA = sys.argv[1] if len(sys.argv) > 1 else "mujoco_menagerie/robotstudio_so101/scene.xml"
base = mujoco.MjModel.from_xml_path(CENA)
rng = np.random.default_rng(42)
garra = base.body("gripper").id
finais = []

for k in range(20):
    model = mujoco.MjModel.from_xml_path(CENA)
    model.dof_damping[:] = base.dof_damping * rng.uniform(0.7, 1.3)          # internal friction ±30%
    model.actuator_gainprm[:, 0] *= rng.uniform(0.8, 1.2)                    # servo force ±20%
    model.actuator_biasprm[:, 1] = -model.actuator_gainprm[:, 0]            # keeps the position servo consistent
    model.body_mass[garra] = base.body_mass[garra] * rng.uniform(0.8, 1.5)  # heavier gripper
    data = mujoco.MjData(model)
    data.ctrl[model.actuator("shoulder_lift").id] = -0.4
    data.ctrl[model.actuator("elbow_flex").id] = 0.6
    for _ in range(int(0.3 / model.opt.timestep)):   # just 0.3 s: the arm is still moving into position
        mujoco.mj_step(model, data)
    finais.append(data.xpos[garra].copy())

finais = np.array(finais)
print("average gripper position (m):", np.round(finais.mean(axis=0), 3))
print("spread (standard deviation, mm):", np.round(finais.std(axis=0) * 1000, 1))

How to check

  • ✓Output similar to: average position [0.244 -0. 0.2] and a spread of just a few millimeters (in the test, 0.4 mm in x and 2.5 mm in z).
  • ✓Change 0.3 s to 2.0 s: the spread decreases because the position servo reaches the target in every version. The difference is in the path, not the endpoint.
  • ✓Increase the ranges (for example, mass from 0.5 to 3.0) and watch the spread grow: that's the amount of uncertainty the policy needs to handle.

💡 A tape measure is worth more than the model

In robotica-real2sim, changing the vision model changed the type of measurement error, but didn't eliminate it. What solved the problem was measuring the table with a tape measure and testing the plan under the worst-case conditions (a tray 15% smaller). The rule to remember: only send something to the real arm if it passes the most plausible worst-case test.

Why learn this

If you ignore the sim2real gap, you may think the simulation “lied” when the real arm fails. If you measure it, you know in advance where it will fail and have already trained for that.

Key concepts

Sim2real
transfer to the real world
Reality gap
the measured difference
Randomization
randomize the physics
Worst case
gate to the real world
6

🧬 Generate synthetic data for training

Recording 50 demonstrations by hand is tiring; generating 1,000 in simulation takes little computer time. This synthetic data provides variety (positions, sizes, masses), and a few real episodes correct the physics the simulation got wrong. The most common approach today is a mix: lots of synthetic data + some real data.

lots of synthetic data a little real data TRAININGACT or SmolVLA real only simulation only real + simulation 20 trials each

How to read this map: The green grid on the left (synthetic) is much larger than the blue line (real), but both go into the same training run. On the right are the three policies you compare on the real arm. If “real + simulation” beats “real only” with the same real episodes, the twin is helping.

Goal: Generate episodes with cameras in robotica-real2sim, export them in LeRobot format, and train an ACT directly from the local folder.

sinteticos.sh
# inside the robotica-real2sim folder
./instalar.sh --lerobot           # creates .venv-lerobot just for the exporter
./r2s.py episodios exemplos/mesa-foto/cena.json sint/episodios -n 50 --imagens
./r2s.py exportar sint/episodios sint/lerobot

# train using the local folder (check the flags with: lerobot-train --help)
.venv-lerobot/bin/lerobot-train \
  --dataset.repo_id=local/real2sim --dataset.root=sint/lerobot \
  --policy.type=act --policy.device=cuda \
  --output_dir=outputs/train/act-sint --policy.push_to_hub=false

How to check

  • ✓In the project test, the export produced 50 episodes, 4.361 frames at 10 frames per second, 9,7 MB, with the "cima" and "pulso" cameras.
  • ✓Training starts and shows the loss going down, as in 5.4.
  • ✗Note: in this example, the simulated arm is a Franka Panda with 7 joints. The data only works for the SO-101 after you replace the scene model with the SO-101 twin; that's the workflow in the robotica-so101 project.

✓ Well-made synthetic data

  • ✓Same cameras, names, and joints as the real arm.
  • ✓Only episodes with success verified by the physics.
  • ✓Variation in position, mass, lighting, and color.

✗ Synthetic data that gets in the way

  • ✗Camera at a different angle from the real one.
  • ✗Everything perfect and identical: the policy memorizes it.
  • ✗No real episodes to correct the physics.

Why learn this

Data is the bottleneck in AI robotics. Knowing how to generate data in simulation and prove that it helps by comparing three versions is the skill that separates a science fair project from one that improves with every round.

Key concepts

Synthetic
generated in simulation
Mix
lots of sim + a little real
Export
LeRobot format
3 versions
proof that it helps

✅ Module summary

✓
Simulation first: cheap mistakes, then slow work with the real robot, then use.
✓
MuJoCo + Menagerie: the SO-101 twin runs on any PC, with the same 6 joints.
✓
Isaac Sim/Isaac Lab when you have an RTX GPU and the project calls for large-scale reinforcement learning or realistic images.
✓
Real2sim + proof: the photo builds the scene, the tape measure checks it, and the worst-case test decides.
✓
Sim2real is measurable; randomization and a synthetic + real data mix help the policy handle the difference.

To go further

Next module

5.6 · Final project: the assistant robot that listens, plans, sees, and picks things up, bringing together everything from Track 5.