This site is version 0.0.1. Most of the code on this site is drafted by an agent and needs review. I apologize for the AI-speak, we are working on rewriting it!
viv-lang.org

← Posts

demoAIRobotics

A swing-up policy trained offline

An acrobot is two links with one motor, at the elbow; the shoulder turns freely. A linear controller can balance it only near upright, so from hanging down it first has to swing itself up. The swing-up controller here was not derived: it is nine weights found offline by a cross-entropy search over 1600 simulated swings, run as compiled native code, and the page contains only those nine numbers. In your browser the arm swings up in 1.188 seconds, hands over to a balancing controller, and settles to within 0.005 radians of upright.

Look inside this program

  • Atlas

    The program’s map: every function and which calls which.

  • There’s no recorded run of this program yet.

The acrobot is like a gymnast on a high bar who can bend only at the hips. It is two links hinged end to end; the shoulder joint turns freely and the elbow has a motor. Its state is and its input is a single torque, so the shoulder moves only through its coupling to the elbow. A linear balancing controller works only near the upright, and the arm starts hanging straight down, far outside that region.

So it takes two controllers and a rule for when to switch from one to the other.

The balancing controller. Linearize the dynamics about the upright by central differences on the same chain_rate this page simulates, and the balancing gains come from the Riccati equation (the LQR method). Solved by the Schur method, it gives gains and a closed loop whose slowest mode decays like .

The Riccati solution also gives the switching rule. Write for the state's distance from upright. The Riccati matrix turns it into one number, , the cost the balancing controller expects to pay to bring the arm from back to upright (its cost-to-go). When that number is small enough, the controller brings the arm upright within the torque limit; when it is larger, the controller can fail. To find how small is small enough (the switching level), the full nonlinear model with the torque limit was simulated offline from states along random directions in the four-dimensional state space, bisecting on the cost-to-go along each, and the level was set below the smallest one at which the controller failed. The arm switches to the balancing controller when the cost-to-go drops below 20.

The swing-up. It is not derived. The torque is over nine features (a constant, the sines and cosines of both angles, both rates, the energy error, and the energy error times the shoulder rate), limited to 40 N·m. The nine weights come from a cross-entropy search: sample forty policies from a Gaussian, simulate each one, refit the Gaussian to the best six, and repeat forty times. That is 1600 simulated swings, run as Viv compiled to native code. The search runs offline; this page runs only the nine weights it found.

What it does. From hanging, the arm swings up in 1.188 seconds, the cost-to-go drops below 20, and the balancing controller takes over. At the end of the six seconds this page simulates, the two joint angles add up to 0.0045 radians from upright and are still closing; with twelve seconds instead of six they reach . The torque hits its 40 N·m limit early in the swing.

What it does not do. The weights were trained from one starting position, and they fail from a nearby one. Start with the shoulder 0.04 radians away from its trained value and the elbow at 0.05 radians instead of 0.08. Two seconds later the arm still has not reached the switching level; it is still turning, and its two joint angles add up to 9.5 radians away from upright. From the trained start the switch happened at 1.188 seconds. A test checks this failure. The search scored each policy on one swing from one start, so nothing in it rewarded working from other starts.

Printed

ncatch: 4
KG: [-189.464, -47.5077, -89.4265, -29.2129]
2.49124e-16
-1.68485
E_TOP: 19.62
off_shoulder: [-1, 9.5092, 40]
long_run: [1.188, 2.75951e-07, 40]
switch_at: 1.188
settled: 0.00450463
peak_torque: 40
The arm, driven by the nine trained weights
upright

 

0.0 / 6.8 s
From hanging to balanced
Both joints
shoulder, which has no motorelbow, which has one
−2000200degrees
Elbow torque and total energy
torque at the elbowtotal energy (right axis)
−2002040012345seconds−2002040joulesnewton meters

Tests 7 of 7 pass

  • the upright is an equilibrium, so there is something to linearize about (passes)
  • the Riccati equation is solved to rounding and the closed loop is stable (passes)
  • moving the shoulder alone would need torque at the shoulder, and the shoulder has no motor (passes)
  • the trained policy switches to the balancing controller at 1.188 seconds and then balances (passes)
  • from a start 0.04 rad off at the shoulder and 0.03 rad off at the elbow, it never reaches the switching level (passes)
  • with twelve seconds instead of six the joint angles reach 2.8e-7 radians from upright (passes)
  • the energy ends where the upright says it should (passes)

Checked when the site was built: 7 of 7 tests passed, in 1.32 s. Download 2026-09-23-a-swing-up-policy-trained-offline-and-flown-in-the-tab.viv