5 · One Number Decides: the 1959 Experiment
Drivers who match the speed of the car ahead after a reaction time: one number, C, decides whether the line is calm, wobbly or crashes
// Herman, Montroll, Potts and Rothery (1959): linear car-following with a delay, Euler steps of 0.01 s.
herman = {
const N = 8, T = 1.0, dt = 0.01, tEnd = 50, lag = Math.round(T / dt), steps = Math.round(tEnd / dt);
const lam = hermanC / T;
const u = Array.from({length: N + 1}, () => new Float64Array(steps + 1)); // speed change of each car (m/s)
for (let k = 1; k <= steps; k++) {
const t = k * dt;
u[0][k] = u[0][k - 1] + (t < 2 ? -1 : t < 4 ? 1 : 0) * dt; // leader brakes, then recovers
const kd = k - 1 - lag;
for (let n = 1; n <= N; n++) {
const a = kd >= 0 ? lam * (u[n - 1][kd] - u[n][kd]) : 0;
u[n][k] = u[n][k - 1] + a * dt;
}
}
const rows = [];
for (let n = 0; n <= N; n++)
for (let k = 0; k <= steps; k += 10)
rows.push({car: n === 0 ? "Leader" : `Car ${n}`, n, t: k * dt, du: u[n][k]});
const peak = n => u[n].reduce((m, x) => Math.max(m, Math.abs(x)), 0);
return {rows, ratio: peak(N) / peak(1)};
}Plot.plot({
width: plotW, height: 320,
x: {label: "Time (s)"},
y: {label: "Speed change (m/s)", domain: [-6, 6], grid: true},
color: {type: "linear", range: accentRange, domain: [-2, 8], legend: false},
clip: true,
marks: [
Plot.ruleY([0]),
Plot.line(herman.rows, {x: "t", y: "du", z: "car", stroke: "n", strokeWidth: 1.8}),
Plot.text(herman.rows.filter(d => d.t === 0 && d.n === 0), {x: 6, y: -2.6, text: () => "Leader brakes", fill: "gray"})
]
}){
const C = hermanC, e = 1 / Math.E, h = Math.PI / 2;
const regime =
C <= e ? "**Smooth and stable.** Each driver eases back to speed without overshooting, and the disturbance fades down the line."
: C < 0.5 ? "**Oscillating, but still stable.** Drivers overshoot a little, yet the disturbance still fades from car to car."
: C < h ? "**Each pair is stable, but the line is not.** Every follower settles down eventually, yet the disturbance grows as it passes from car to car. On a long road, that is a phantom jam."
: "**Unstable.** Even a single follower overreacts more and more. In real traffic this ends in a collision."
return md`${regime}
In this run, the last car's largest speed change is **${herman.ratio < 100 ? herman.ratio.toFixed(2) : "more than 100"} ×** the first follower's.`;
}What it shows
Herman, Montroll, Potts and Rothery (General Motors, 1959) showed that one number, C = (how strongly a driver reacts) × (reaction time), decides everything. Try C below 1/2, between 1/2 and \pi/2, and above. Lecture 1 tells the story; Lab 1 has you rebuild it.
theme = Generators.observe(notify => {
const read = () => notify(document.body.classList.contains("quarto-dark") ? "dark" : "light");
read();
const watcher = new MutationObserver(read);
watcher.observe(document.body, {attributes: true, attributeFilter: ["class"]});
return () => watcher.disconnect();
})
accent = theme === "dark" ? "#c9a0ff" : "#57068c"
accentRange = theme === "dark" ? ["#4b3a5c", "#ecdcff"] : ["#dccbee", "#3a0463"]
ink = theme === "dark" ? "#f2f2f2" : "black"
// Plot width: Plot's default 640 px, except on a phone, where a plot is drawn to fit its card
// instead of being shrunk (and its text with it). Changes only when the width actually changes.
plotW = Generators.observe(notify => {
let last;
const read = () => {
const main = document.querySelector("main"), fit = main ? main.clientWidth - 50 : 640;
const w = !document.querySelector(".reveal") && fit < 640 ? Math.max(240, Math.floor(fit)) : 640;
if (w !== last) notify(last = w);
};
read();
window.addEventListener("resize", read);
return () => window.removeEventListener("resize", read);
})