Log pendulum energy drift in MuJoCo
Tested by BSVSimulate a damped pendulum and write kinetic/potential/total energy vs time. Drift is expected with damping — the point is to see it in a CSV.
- Input
- Optional duration seconds (default 5).任意:秒数(既定 5)。
- Output
- pendulum_energy.csv with t_s, theta, omega, ke, pe, total.t_s, theta, omega, ke, pe, total の pendulum_energy.csv。
- Prerequisites
- Python 3 + mujoco + numpy (BSV fieldsvenv).Python 3 + mujoco + numpy(BSV fieldsvenv)。
- Steps
-
- Save the script.
- Run: python mujoco_pendulum_energy.py 5
- Open the CSV and check that total energy trends down when damping is present.
- スクリプトを保存します。
- 実行: python mujoco_pendulum_energy.py 5
- CSVを開き、減衰があるとき全エネルギーが下がる傾向か確認します。
- Expected result
- Hundreds of rows and a printed drift estimate. Not a stability proof for any real joint.数百行と漂移の目安表示。実ジョイントの安定証明ではありません。
- Next step
- Sample a planar IK workspace next, or open Robot Pilot for teleop practice.次は平面IKの作業空間か、テレオプ練習なら Robot Pilot を開きます。 Open
Code (BSV original, MIT licence)
Download .py · mujoco_pendulum_energy.py
#!/usr/bin/env python3
"""BSV recipe: swing a simple pendulum in MuJoCo and log energy drift.
Research / education / simulation only. Not a safety certification or real-hardware procedure.
Input : duration seconds (default 5), timestep hint via model (0.002).
Output: pendulum_energy.csv with time, angle, omega, kinetic, potential, total.
Original BSV code (model), MIT. Library: MuJoCo (Apache-2.0).
"""
import csv, sys
import mujoco
import numpy as np
XML = """
<mujoco model="bsv_pendulum">
<option timestep="0.002" gravity="0 0 -9.81"/>
<worldbody>
<body name="pole" pos="0 0 0">
<joint name="hinge" type="hinge" axis="0 1 0" damping="0.05"/>
<geom type="capsule" fromto="0 0 0 0 0 -0.3" size="0.01" mass="0.2"/>
<site name="tip" pos="0 0 -0.3" size="0.005"/>
</body>
</worldbody>
</mujoco>
"""
def main(duration="5"):
model = mujoco.MjModel.from_xml_string(XML)
data = mujoco.MjData(model)
data.qpos[0] = np.deg2rad(120.0) # start near inverted side, will fall
data.qvel[0] = 0.0
mujoco.mj_forward(model, data)
tip = model.site("tip").id
g = abs(model.opt.gravity[2])
# tip mass approx from geom mass
m = 0.2
L = 0.3
rows = []
steps = int(float(duration) / model.opt.timestep)
for i in range(steps):
mujoco.mj_step(model, data)
th = float(data.qpos[0])
w = float(data.qvel[0])
# potential relative to lowest point (theta=±pi... use height of tip)
z = float(data.site_xpos[tip][2])
pe = m * g * (z + L) # 0 at bottom z=-L
ke = 0.5 * m * (L * w) ** 2
rows.append([i * model.opt.timestep, th, w, ke, pe, ke + pe])
with open("pendulum_energy.csv", "w", newline="", encoding="utf-8") as f:
wri = csv.writer(f)
wri.writerow(["t_s", "theta_rad", "omega_rad_s", "ke_j", "pe_j", "total_j"])
wri.writerows(rows)
totals = [r[-1] for r in rows]
drift = max(totals) - min(totals)
print(f"wrote {len(rows)} rows -> pendulum_energy.csv; energy range drift≈{drift:.6f} J (damping present)")
if __name__ == "__main__":
main(*sys.argv[1:])
Output of BSV's own test run
Run on 2026-10-07 02:07 JST · Python 3.13.5, mujoco 3.15.0, numpy 2.5.3
wrote 2500 rows -> pendulum_energy.csv; energy range drift≈0.886669 J (damping present)