import sys
if sys.platform == "emscripten": # on the course website: install the course package
import micropip, js
await micropip.install(js.location.origin + "/lite/wheels/trafficlab-0.3.0-py3-none-any.whl")
import numpy as np
import matplotlib.pyplot as plt
from trafficlab.hw import my_problems, check, submit, whoamiLab 1 · Phantom Jams on Your Laptop
Homework 1 · reproduce the 1959 GM experiment · local and string stability · when a platoon collides
▶ Start this homework in your browser (nothing to install; your work is saved in this browser).
This notebook is Homework 1 (100 points): the lab (60) and three problems by hand (40). Part A is done in class; finish Parts B and C at home.
This lab is open to everyone. Try it freely; if you are enrolled, open https://trgy7353.pages.dev/login once (NYU login) so that the Submit cell at the end works.
It runs right here on the course website: Python runs inside your browser, with nothing to install. Your work is saved automatically in this browser, on this computer; to move to another computer, use File → Download and upload the file there. To submit: upload a PDF or photo of your hand derivations into this folder (name it derivation...), save the notebook, and run the last cell. You may submit again until the deadline; the last submission counts.
In 1959, four researchers at General Motors (Herman, Montroll, Potts and Rothery) asked a simple question: if every driver just tries to match the speed of the car ahead, after a short reaction time, is traffic stable? Their answer explained phantom jams sixty years before anyone filmed one on a ring road.
Nine perfectly sensible drivers follow a leader who taps the brakes for two seconds, then speeds back up. Nobody is distracted, nobody is reckless. Can that end in a crash? Write down yes or no, and why.
Your numbers
Every student gets their own numbers, made from their NetID, and the grader recomputes yours. On the course website your NetID comes from your NYU login.
NETID = whoami() or "your-netid" # filled in from your NYU login
P = my_problems(NETID, hw=1)def brake_pulse(t):
"""The leader brakes at 1 m/s² for 2 s, then accelerates back for 2 s."""
return -1.0 if t < 2 else (1.0 if t < 4 else 0.0)Part A · In class
A1 · The model
Car n+1 follows car n. After a reaction time T, the follower accelerates in proportion to the speed difference:
\ddot x_{n+1}(t) = \lambda\,\big[\dot x_n(t-T) - \dot x_{n+1}(t-T)\big].
Everything depends on C = \lambda T. We track u_n(t), each car’s speed change from cruising speed, and step forward in time with Euler’s method, u(t+\Delta t) = u(t) + a\,\Delta t. The delay means the acceleration now uses speeds from T seconds ago.
Your turn. Complete simulate.
def simulate(C, n_cars=8, T=1.0, dt=0.01, t_end=50.0, leader_accel=brake_pulse):
"""Speed change of each car behind a leader, for the 1959 GM model.
Returns (t, u): t has shape (steps + 1,), and u has shape (n_cars + 1, steps + 1).
Row 0 of u is the leader; row n is the n-th follower. All speed changes start at 0.
The follower's acceleration at step k uses speeds from `lag = round(T / dt)` steps earlier:
a_n = (C / T) * (u[n-1, k-1-lag] - u[n, k-1-lag]) (and 0 while k-1-lag < 0)
"""
lam = C / T
lag = round(T / dt)
steps = round(t_end / dt)
t = np.arange(steps + 1) * dt
u = np.zeros((n_cars + 1, steps + 1))
# YOUR CODE HERE: step through time with Euler's method.
# At each step: update the leader from leader_accel(t), then each follower from the delayed speeds.
return Noner = simulate(0.5, n_cars=2, t_end=10)
assert r is not None, "complete simulate() first"
t, u = r
assert u.shape == (3, 1001) and abs(u[0, -1]) < 0.02, "the leader ends back at cruising speed (to one Euler step)"
assert u[1, 99] == 0 and u[1, 400] < 0, "the follower reacts only after T = 1 s, then slows down"
print("simulate: public checks passed (the grader runs more)")A2 · One follower: local stability
Cs = [0.3, 1 / np.e, 1.0, np.pi / 2, 1.7]
fig, axes = plt.subplots(1, len(Cs), figsize=(16, 3), sharey=True)
for ax, C in zip(axes, Cs):
t, u = simulate(C, n_cars=1, t_end=40)
ax.plot(t, u[0], "k--", lw=1, label="leader")
ax.plot(t, u[1], color="#57068c", lw=2, label="follower")
ax.set_title(f"C = {C:.3f}"); ax.set_xlabel("Time (s)"); ax.set_ylim(-4, 4)
axes[0].set_ylabel("Speed change (m/s)"); axes[0].legend(); plt.tight_layout()Herman and his colleagues proved that the follower’s response is smooth for C \le 1/e, oscillates but settles for 1/e < C < \pi/2, and grows for C > \pi/2. Do your plots agree?
A3 · A line of cars: string stability
A disturbance can fade or grow as it passes back down a line of cars. The amplification is the largest speed change of the last car divided by that of the first follower. Compute it for your line of cars at your two values of C, then find where it crosses 1.
def amplification(C, n_cars=8):
_, u = simulate(C, n_cars=n_cars, t_end=60)
peak = np.abs(u).max(axis=1)
return peak[-1] / peak[1]n = P["lab"]["n_cars"]
amp_a = amplification(P["lab"]["C_a"], n_cars=n)
amp_b = amplification(P["lab"]["C_b"], n_cars=n)
print(f"your line of {n} cars: amplification {amp_a:.3f} at C_a, {amp_b:.2f} at C_b")
C_cross = None # YOUR CODE HERE: the C (to 0.01) at which amplification(C, n_cars=n) crosses 1Question 2 (multiple choice, 8 points). Theory says a long line of cars is stable only when C < 1/2. Your crossing is not exactly 1/2. Why?
- A. Euler’s method is too inaccurate at \Delta t = 0.01 s.
- B. The theory is about an endless line and a steady, small oscillation; your line is short and the leader’s disturbance is one brief pulse, so the measure of growth is different.
- C. The reaction time should have been 0.5 s.
- D. Some cars collide before the disturbance reaches the end of the line.
Part B · At home
B1 · When does a platoon crash?
Speed changes are one thing; collisions are another. Give every car a real position: 20 m/s cruising, 5 m long. Your turn. Complete first_collision, then find when your platoon crashes.
def first_collision(C, n_cars=9, spacing=15.0, speed=20.0, car_length=5.0, **kw):
"""Time (s) of the first collision in the platoon, or None if nobody collides.
Every car starts at `speed` m/s with `spacing` metres from front bumper to front bumper.
Hint: position = initial position + integral of (speed + u) dt. A collision happens when
the gap to the car ahead, minus car_length, reaches 0.
"""
# YOUR CODE HERE
return "not implemented"assert first_collision(0.4) is None, "a stable platoon never collides"
L = P["lab"]
t_crash = first_collision(L["C_crash"], spacing=L["spacing"])
print(f"C = {L['C_crash']:g}, {L['spacing']:g} m apart: first collision at t = {t_crash} s")Question 4 (multiple choice, 8 points). Every driver in this platoon follows a perfectly sensible rule. Who is to blame for the collision?
- A. The last driver, who hit the car ahead.
- B. The first follower, who reacted too strongly.
- C. Nobody in particular: each driver’s reasonable rule, delayed by a reaction time, passes the disturbance back a little larger each time. The instability belongs to the line, not to any one driver.
- D. The leader, for braking.
What does this mean for automated vehicles that follow each other closely?
Going further (not graded). Keep \lambda fixed and halve T: what happens to C, and to the crash? Make car 4 react with C = 0.3 while the others keep C = 0.8: does one careful driver protect the cars behind it?
Part C · Problems by hand (40 points)
Use your numbers from my_problems above. Write each derivation on paper (photo or PDF upload) and enter the numbers in the answer sheet. Cars are 4.5 m long.
P1 · Spacing and headway (12). Every car drives at your speed v (m/s), with your spacing s (m, front bumper to front bumper). Find the headway h (s), the flow q (veh/h), the density k (veh/km), and the time gap between one car’s rear bumper and the next car’s front bumper (s). Check that q = kv.
P2 · Greenshields (12). With your v_f and k_j: the critical density k_c (veh/km), the capacity q_{\max} (veh/h), and the speed (km/h) and flow (veh/h) at your density k.
P3 · The ring road (16, stretch). Your N cars drive around a ring of length L. Each driver keeps a bumper-to-bumper gap of s_0 + vT, with s_0 = 2 m and your time gap T. Find the density k (veh/km), the speed v_e (km/h) at which every car can drive in equilibrium, and the flow q (veh/h). Then, with the 1959 model, your sensitivity \lambda and reaction time \tau, compute C = \lambda\tau. Would a single follower be smooth, oscillating or unstable? Would the whole ring be string stable? Explain on paper why an equilibrium can exist and still never be seen.
- Your answers go to the course website when you run the last cell, together with this notebook and your derivation file. You get a receipt with the time.
- Your numbers are recomputed from your NetID and checked within a small tolerance, step by step, so a slip in one step costs only that step.
- Your functions (
simulate,first_collision) are run on hidden tests: data you have not seen. Only those functions are run, nothing else from your notebook, with a 2-minute limit. - Your derivations (the PDF or photo) are read by the instructor.
ANSWERS = {
"netid": NETID,
"lab": {"amp_a": amp_a, "amp_b": amp_b, "C_cross": C_cross, "t_crash": t_crash,
"Q2": "", "Q4": ""},
"P1": {"h": None, "q": None, "k": None, "gap_t": None},
"P2": {"k_c": None, "q_max": None, "v_at_k": None, "q_at_k": None},
"P3": {"k": None, "v_e": None, "q": None, "C": None},
}# Save the notebook first (Ctrl+S), then run this cell to hand in.
submit(ANSWERS, hw=1, notebook="lab01-phantom-jams.ipynb")