Lecture 35: Cellular Automata in Python#

Note

This lectures focuses on implementation of Safety Distance Model through Cellular Automata for a single-lane car following model in Python.


Nagel–Schreckenberg Model#

Introduction#

The NaSch Cellular Automaton (short for Nagel–Schreckenberg model) is one of the most widely used cellular automata models to study vehicular traffic flow. It was proposed in 1992 by Kai Nagel and Michael Schreckenberg as a microscopic, stochastic traffic model, designed to capture essential dynamics of highway traffic while being simple enough for large-scale simulation.

Attributes

  • Spatial: Space is discretized into cells (1D Lattice) wherein each cell is either empty or occupied by exactly one vehicle. Consequently, the gap for vehicle \(i\) to the next vehicle is denoted as \(h_i\) (headway).

  • Temporal: Time progresses in discrete steps; Vehicle speeds are discrete, ranging from 0 to \(\bar{v}\)

Behavioral Rules

  • Acceleration: \(v_i \rightarrow \min (v_i + 1, \bar{v})\)

  • Deceleration: \(v_i \rightarrow \min (v_i, h_i)\)

  • Stochastic Braking: \(v_i \rightarrow \max (v_i - 1, 0)\) with probability \(p\)

  • Movement: \(x_i \rightarrow x_i + v_i\)

Simulation Mechanism

  • Initialization: Define road length \(l\), maximum velocity \(\bar{v}\), braking probability \(p\), and number of vehicle \(n\). Randomly assign vehicle positions \(x_i\) and initial speed \(v_i \in [0, \bar{v}]\)

  • Perception: Each vehicle observes its immediate leader, computes its headway \(h_i\), and notes its current speed \(v_i\)

  • Decision-Making: Accelerate if possible; Decelerate to maintain safe distance; Randomization (stochastic braking)

  • Action: Update each vehicle’s velocity and advance position

  • Environment Update: Resolve the new lattice configuration (occupied and empty cells), ensuring synchronous updates for all vehicles.

  • Iteration: Repeat steps for each time step until the simulation horizon \(k\) is reached. Collect metrics (flow, density, average speed, jam length).

Emergent Behavior

  • Free Flow: At low densities, car move at a speed close to \(v_{max}\)

  • Traffic Jams: At higher densities, jams form spontaneously due to the randomization rule

  • Phase Transition: The model shows a transition from free flow to congested traffic as density increases

  • Stop-and-Go Waves: Jam fronts propagate backward through traffic, resembling real highway observations.


Implementation#

Pseudo Code#

\[\begin{split} \begin{aligned} 1.\ & \textbf{Procedure } \text{NaSchCA}(l, n, v, p, k) \\ 2.\ & s \leftarrow \left\lfloor \tfrac{l}{n} \right\rfloor \quad \text{// lattice spacing for uniform placement} \\ 3.\ & X \leftarrow \{0, s, 2s, \dots, s(n-1)\} \bmod l \quad \text{// initial vehicle positions (ring)} \\ 4.\ & V \leftarrow \{0;\ i=1,\dots,n\} \quad \text{// initial speeds} \\ 5.\ & f \leftarrow 0 \quad \text{// mean flow accumulator} \\ 6.\ & Z \leftarrow \text{matrix}_{k \times l}(-1) \quad \text{// space--time raster ($-1 =$ empty)} \\ 7.\ & \textbf{for } t = 0 \ \textbf{to } k-1 \ \textbf{do} \\ 8.\ & \quad Z[t, X] \leftarrow V \quad \text{// record current occupancy/speeds} \\ 9.\ & \quad K \leftarrow \operatorname{argsort}(X) \quad \text{// order vehicles by position} \\ 10.\ & \quad X \leftarrow X[K]; \quad V \leftarrow V[K] \quad \text{// reorder state} \\ 11.\ & \quad I \leftarrow (\{0,\dots,n-1\}+1) \bmod n \quad \text{// leader index (periodic)} \\ 12.\ & \quad H \leftarrow (X[I] - X - 1) \bmod l \quad \text{// gaps: empty cells ahead} \\ 13.\ & \quad V \leftarrow \min(V+1,\ v) \quad \text{// Acceleration} \\ 14.\ & \quad V \leftarrow \min(V,\ H) \quad \text{// Deceleration (safety distance)} \\ 15.\ & \quad r \leftarrow \text{rng.uniform}(0,1)^n \quad \text{// i.i.d. uniforms} \\ 16.\ & \quad M \leftarrow (r < p)\ \wedge\ (V > 0) \quad \text{// stochastic braking mask} \\ 17.\ & \quad V[M] \leftarrow V[M] - 1 \quad \text{// Randomization} \\ 18.\ & \quad X \leftarrow (X + V) \bmod l \quad \text{// Movement} \\ 19.\ & \quad f \leftarrow f + \dfrac{\sum_i V_i}{k \cdot l} \quad \text{// accumulate normalized flow} \\ 20.\ & \textbf{end for} \\ 21.\ & \textbf{return } \{X, V, Z, f\} \quad \text{// final state, raster, mean flow} \end{aligned} \end{split}\]

Python Implementation#

# NaSch Cellular Automaton
import numpy as np

def NaSchCA(l, n, v, p, k, seed):
    # Intialize
    rng = np.random.default_rng(seed)
    s = l // n
    X = np.arange(0, s*n, s) % l
    V = np.zeros(n, dtype=int)
    # Iterate    
    f = 0
    Z = np.full((k, l), -1, dtype=int)  # cell entry indicates speed of the vehicle occupying it; -1 if it is empty
    for t in range(k):
        Z[t, X] = V
        
        K = np.argsort(X)
        X = X[K]
        V = V[K]

        # Perception
        I = (np.arange(n) + 1) % n
        H = (X[I] - X - 1) % l

        # Decision-Making | Action | Environment Update
        ## Acceleration
        V = np.minimum(V + 1, v)
        ## Deceleration
        V = np.minimum(V, H)
        ## Stochastic Braking
        r = rng.random(n)
        K = (r < p) & (V > 0)
        V[K] -= 1

        X = (X + V) % l
        
        f += V.sum() / (k * l)

    return {"X": X, "V": V, "Z": Z, "f": f}
# Time–space occupancy diagram
import matplotlib.pyplot as plt

# Input Parameters
l = 300
n = 60
v = 5
p = 0.2
k = 400
seed = 0

# Output
R = NaSchCA(l, n, v, p, k, seed)
Z = (R["Z"] >= 0).astype(int)

# Plot
plt.figure()
plt.imshow(Z, aspect='auto', origin='lower', interpolation='nearest')
plt.xlabel("Space (cell index)")
plt.ylabel("Time step")
plt.title("NaSch Time–Space Diagram (occupied=1, empty=0)")
plt.show()
../_images/4b23791fac576e01990b03da1254c65e6a6b2fd3dfbb07ee4bfb82fa1de7c567.png
# Fundamental diagram (flow vs density) via density sweep
import numpy as np
import matplotlib.pyplot as plt

# Input Parameters
l = 300
n = 60
v = 5
p = 0.25
k1 = 300
k2 = 600
seed = 0

D = np.linspace(0.05, 0.95, 19)
F = []

for d in D:
    n = max(1, int(round(d*l)))
    _ = NaSchCA(l, n, v, p, k1, seed)
    R = NaSchCA(l, n, v, p, k2, seed)
    F.append(R["f"])

plt.figure()
plt.plot(D, F, marker='o')
plt.xlabel("Density (veh/cell)")
plt.ylabel("Flow (veh/cell/step)")
plt.title("Fundamental Diagram")
plt.show()
../_images/e857d7c2306268969375ee014eb4d2279c30085208a7651e4f5a6f2987a55e4b.png