A real-time desktop simulation of a pole balancing on a moving cart, controlled by an LQR algorithm, the same principle used in self-balancing robots, Segways, and rocket guidance systems.
Every frame, the Linear Quadratic Regulator (LQR) reads the system's current state vector [x, x_dot, theta, theta_dot] (cart position, cart velocity, pole angle, and angular velocity) and computes the exact force needed to keep the pole upright:
F = -(k_x·x + k_xdot·x_dot + k_theta·theta + k_tdot·theta_dot)
The gains were solved offline via the discrete algebraic Riccati equation:
| Gain | Value |
|---|---|
| k_x | −22.3607 |
| k_xdot | −34.0726 |
| k_theta | −233.8775 |
| k_tdot | −76.2254 |
k_theta is nearly 10× larger than k_x, which reflects a deliberate tuning choice: angle deviation is penalised far more heavily than cart position, so the controller willingly lets the cart drift laterally to keep the pole upright.
The equations of motion are derived from Lagrangian mechanics and integrated using Runge-Kutta 4 at 8 substeps per frame (dt = 0.002s), with the following physical parameters:
| Parameter | Value |
|---|---|
| Cart mass (M) | 1.5 kg |
| Pole mass (m) | 0.1 kg |
| Pole length (l) | 1.0 m |
| Gravity (g) | 9.81 m/s² |
Pushing the cart past its recoverable limit causes the controller to fail. That's the physics working as intended, not a bug.
- Push Left / Push Right — apply a disturbance force for 30 frames
- Force slider — set disturbance magnitude anywhere from 0 to 500 N
- Reset — return to upright equilibrium
The live HUD in the top-left of the canvas shows the current angle (theta), cart position (x), and controller output force (F) updating in real time.
Requires Node.js 18+.
git clone https://github.com/nourawadallah/inverted-pendulum
cd inverted-pendulum
npm install
npm startElectron · HTML Canvas · Vanilla JS
