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!
Adam Zettel · September 22, 2026 · written with an agent
The pole starts 15 degrees from upright. A controller that pushes the cart in proportion to the pole's angle and rate, and a little toward the origin, catches it in under two seconds and holds it. At a fifth of the gains it falls.
F=kθθ+kθ˙θ˙+kxx+kx˙x˙
The dynamics, the integrator and the drawing come from lib/cartpole, loaded with
import cartpole; this post adds the controller and the tests. The force is
F=kθθ+kθ˙θ˙+kxx+kx˙x˙, with the cart terms
small and positive: to move the cart left toward the origin, the controller must first
lean the pole left, and the way to do that is a push to the right.
The gains are top-level constants, so viv run --set K_THETA=30 ... tries another
controller without editing the program.
The program’s map: every function and which calls which.
There’s no recorded run of this program yet.
Shift+Enter in a cell runs it and moves to the next. Running a cell runs every cell above it too, as one program.
[1]lines 21–79
from cartpole import cartpole_draw, cartpole_step
DT =0.01
T_END =6.0
K_THETA =60.0
K_THETAD =12.0
K_X =1.5
K_XD =3.0
FRAME_EVERY =3# the starting angle of the pole from upright, in degrees
START_DEG =15.0
START_ANGLE = START_DEG *3.141592653589793/180.0#| The controller: the force on the cart, a weighted sum of the four state#| variables, with every weight multiplied by `gain`.defpd_force(s, gain) {
return gain * (K_THETA * s[2] + K_THETAD * s[3] + K_X * s[0] + K_XD * s[1])
}
#| Simulate from 15 degrees with the gains scaled by `gain`; draw when `draw` is true.#| Returns [largest |theta| after t = 2, final x, final theta].defrun(gain, draw:Bool) {
s = [0.0, 0.0, START_ANGLE, 0.0]
steps = T_END / DT
worst_late =0.0for k to steps {
s =cartpole_step(s, pd_force(s, gain), DT)
if k * DT >2.0 {
worst_late =max(worst_late, abs(s[2]))
}
if draw andmod(k, FRAME_EVERY) ==0.0 {
cartpole_draw(s)
frame(DT * FRAME_EVERY)
}
}
return [worst_late, s[0], s[2]]
}
stage("Cart-pole, released at 15 degrees", -1.5, 1.5, -0.2, 1.4)
hline("track", 0.0)
cartpole_draw([0.0, 0.0, START_ANGLE, 0.0])
balanced =run(1.0, true)
fallen =run(0.2, false)
show balanced
show fallen
test"after two seconds the pole stays within two degrees of upright" {
assert_lt(balanced[0], 0.035)
}
test"and the cart comes back to within 10 cm of where it started" {
# the final cart position, in centimetersassert_lt(100.0*abs(balanced[1]), 10.0)
}
test"at a fifth of the gains the pole falls over" {
assert_gt(abs(fallen[2]), 1.0)
}
# SPDX-License-Identifier: MIT OR Apache-2.0# SPDX-FileCopyrightText: 2026 The Viv authors