Ahmed Doghri Logo Image
Ahmed Doghri

pendulumlab

Most robot learning demos make you install a simulator before you see a single reward curve. pendulumlab skips the download. CEM learns entirely from its own rollouts and moves the return from -54.30 to 217.38.

pendulumlab, an inverted pendulum control benchmark

No GPU, No Simulator Download, No Excuse

Most robotics repos want you to install half a lab before you see a single curve, which is a strange way to teach anyone how control actually works. pendulumlab makes the whole loop small enough to audit on a lunch break. The state is angle and angular velocity. The action is torque. The reward is upright, smooth, and cheap to compute.

This is not pretending to replace MuJoCo or Isaac Lab. Those are for when your pendulum also needs friction, sensor noise, and a PhD thesis attached. This is for when you want to see the actual control loop instead of trusting it exists somewhere inside a config file you'll never open.

Cross-Entropy Method, No Smoke Machine

The policy is a linear torque controller. The optimizer is cross-entropy method: sample gain vectors, score them on identical starting conditions, keep the elites, tighten the distribution around them, repeat until the pendulum stops embarrassing itself.

I like this benchmark because every number has somewhere to live. If the controller gets better, the CSV says so. If the reward changes, the curve changes. No hidden checkpoint, no borrowed environment, no API key required to watch a pendulum learn not to fall over.

The Number

Doing nothing gets you a return of -54.30, which is the pendulum's way of telling you it face-planted immediately. A hand-tuned PD controller gets to 137.39, training wheels included. CEM, learning entirely from its own rollouts with nobody touching a dial, lands at 217.38 and holds a final-window upright reward of 1.000.

The benchmark writes `artifacts/reward_curve.csv` and `artifacts/reward_curve.svg` every run, which is more receipts than most control demos ever hand you.

CEM Beat a Guess. Did It Beat a Search?

Here is the detail I glossed over the first time through. `hand_tuned_pd` and `cem_policy` are the exact same policy class, a linear controller with two numbers to set: how hard to correct for angle, how hard to correct for angular velocity. CEM's entire job is searching that two-number space for a good point in it. So "CEM beats hand-tuned PD by 58%" answers a much narrower question than it sounds like: did CEM beat one specific hand-picked guess, not did CEM find something a plain search over the same tiny space couldn't.

It doesn't. I built a deterministic 224-point grid search over that exact same two-parameter family, no optimization algorithm at all, just enumeration, and it reached 217.70, at or above the published CEM number, using fewer total evaluations than CEM's run took. Widening the grid further explained why: the reward landscape over this family is a broad plateau above roughly 217 across a wide swath of gain values, not a narrow peak that needs a smart search to locate. Four very different gain pairs I tried all landed within two points of each other near the top.

None of this is a code bug. The physics, the policy, and the optimizer all do exactly what they say, and every published number still reproduces exactly. It's a baseline-strength problem: report CEM's win against a weak, hand-picked comparison point and the headline reads like CEM learned something clever. Report it against a fair, deterministic enumeration of the same tiny policy family, and CEM's real edge over honest search is approximately zero.

11 tests pass locally and on GitHub Actions across Python 3.9, 3.11, and 3.13. The original benchmark output is untouched; the fair comparison ships as an explicit companion script.

Tools Used

Python
Physics Simulation
CEM Policy Search
Control
Baseline Analysis
pytest
Ruff