← Cellular Automata From First Principles

Simulate Traffic with Rule 184

The forest fire consumed its fuel. Rule 184 is the opposite case: a conserved quantity that moves. It is also a clean example of going from an abstract rule table to a model with a concrete interpretation.

Use a one-dimensional ring road:

1 = car
0 = empty road

A car moves one cell to the right when the destination is empty.

That is enough to produce free flow, queues and a macroscopic density-flow relationship.

The mechanism below is written directly in traffic language, but it is exactly Rule 184 underneath: traffic_step agrees with the elementary update for rule 184 (10111000) on every tested configuration. The number and the metaphor are the same object.


Write the traffic mechanism directly

import numpy as np


def traffic_step(road):
    cars = road == 1

    empty_ahead = (
        np.roll(road, -1) == 0
    )

    moving = cars & empty_ahead

    next_road = road.copy()

    next_road[moving] = 0
    next_road[
        np.roll(moving, 1)
    ] = 1

    return next_road

The road is periodic, so the final road cell connects back to the first.

For this model that is deliberate:

closed ring road

not an implementation accident. The ring fixes the car count and removes boundary inflow, so density becomes a clean control parameter — at the price that every result is conditional on closed-system assumptions.


Conservation gives us a strong invariant

Cars do not appear or disappear:

road = np.array(
    [1, 0, 1, 1, 0, 0, 1],
    dtype=np.uint8,
)

next_road = traffic_step(road)

assert road.sum() == next_road.sum()

(Verified over extended runs: the count never changes, including under the stochastic variant below, which moves cars but creates none.)

That invariant is stronger than testing only a few expected cells.

It expresses something the model must preserve under every valid update.

Conservation is doing structural work here: it is what makes density a meaningful axis for the sweep that follows. Name the conserved quantity whenever a model has one; later chapters will reuse the habit.

The whole mechanism in one place — local condition, movement, and what conservation buys:

NeighborhoodCar moves iffInterpretationConservation consequence
cell ahead emptyone step rightfree flowcount unchanged
cell ahead occupiedstaysqueueing, jamscount unchanged
hesitation draw failsstaysdriver delay (stochastic variant)count unchanged

Initialize by density

def make_road(
    length=240,
    density=0.35,
    seed=42,
):
    rng = np.random.default_rng(seed)

    return (
        rng.random(length) < density
    ).astype(np.uint8)

Run it:

road = make_road(density=0.62)

history = []

for _ in range(180):
    history.append(road.copy())
    road = traffic_step(road)

history = np.array(history)

Because rows represent time and columns represent road position, the result is another spacetime diagram.

Rule 184 traffic spacetime diagram

Diagonal traces show cars advancing.

Dense structures reveal blocked movement.

One local exclusion rule is enough to create collective congestion.


Measure movement, not only occupancy

Density tells us how much of the road is occupied.

Flow — flux, the number of cars passing per site per step — tells us how much movement occurs.

def traffic_step_with_flow(road):
    cars = road == 1

    empty_ahead = (
        np.roll(road, -1) == 0
    )

    moving = cars & empty_ahead

    next_road = road.copy()
    next_road[moving] = 0
    next_road[
        np.roll(moving, 1)
    ] = 1

    return next_road, int(moving.sum())

Normalize movement by road length:

flow_per_cell = moving_cars / len(road)

Now sweep density after allowing a warm-up period.

def average_flow(
    density,
    steps=700,
    warmup=200,
    length=600,
    seed=1,
):
    road = make_road(
        length,
        density,
        seed,
    )

    values = []

    for t in range(steps):
        road, moving = (
            traffic_step_with_flow(road)
        )

        if t >= warmup:
            values.append(
                moving / length
            )

    return float(np.mean(values))

Rule 184 density-flow relationship

For deterministic Rule 184 on a ring, the steady-state shape is quantitative, not just qualitative: flow follows approximately min(density, 1 − density), peaking near 0.5 at density 0.5 and falling symmetrically (verified: measured 0.095/0.303/0.483/0.293/0.098 at densities 0.1, 0.3, 0.5, 0.7 and 0.9). Below half-filling, cars are the scarce resource; above it, empty spaces are.

The curve reads in three regimes:

low density:
few cars exist
-> low total flow

intermediate density:
many cars can move
-> high flow

high density:
empty destinations are scarce
-> flow falls

The macroscopic relationship is not coded directly.

It emerges from local occupancy constraints.


A traffic jam is an observer-level object

No cell contains:

JAM = True

No car computes queue length.

Each car only needs to know:

am I here?
is the cell ahead empty?

Yet we can observe a persistent region of blocked vehicles and call it a traffic jam.

This is the same ontological split we saw with gliders:

implementation:
bits + local rules

observer:
cars + queues + flow

Add stochastic slowing

Real drivers do not always move whenever space is available.

A hesitation probability adds another mechanism:

def stochastic_traffic_step(
    road,
    rng,
    slow_probability=0.1,
):
    cars = road == 1

    empty_ahead = (
        np.roll(road, -1) == 0
    )

    willing = (
        rng.random(len(road))
        >= slow_probability
    )

    moving = (
        cars
        & empty_ahead
        & willing
    )

    next_road = road.copy()
    next_road[moving] = 0
    next_road[
        np.roll(moving, 1)
    ] = 1

    return next_road

Now two sources can reduce flow:

physical blocking
random hesitation

Those should be measured separately.

This is the entry point to the famous Nagel–Schreckenberg traffic model (1992), which builds on this deterministic core by adding variable speeds, braking, and stochastic slowing. Rule 184 is its minimal ancestor: one speed, one lane, hesitation optional.


Rule 184 is one traffic model, not traffic itself

The model assumes:

one lane
one cell per vehicle
maximum speed one cell per step
no overtaking
closed ring road
synchronous updates

Those assumptions are useful because they isolate the mechanism.

A richer model can add velocity, braking, lane changes or open boundaries.

But the discipline stays the same, with the guardrail this chapter earns:

conservation law
  ≠ realistic traffic model
  ≠ predictive transportation model

Start from the smallest local information needed to express the mechanism you care about.


One idea to keep

Rule 184 turns a microscopic rule into a macroscopic observable.

That gives us a useful experimental pattern:

local update
    ↓
trajectory
    ↓
observable
    ↓
parameter sweep
    ↓
emergent relationship

In the next chapter we will apply that pattern to a continuous quantity and show how local exchange creates diffusion.


Research

  • Zenil, H. & Martinez, G. J. — Cellular Automata (Scholarpedia). Situates this chapter’s model in the traffic lineage: stochastic automata with conditionally independent draws, and the Nagel–Schreckenberg freeway model as the prominent descendant of deterministic cores like Rule 184. Read before extending the model toward realism. http://www.scholarpedia.org/article/Cellular_automata

  • Berto, F. & Tagliabue, J. — Cellular Automata (Stanford Encyclopedia of Philosophy). Frames Rule 184-style work as discrete dynamical-system simulation: local conservation laws reproducing macroscopic behavior is the mechanism, and the value lies in what the simple model generates rather than in predictive fidelity. https://plato.stanford.edu/entries/cellular-automata/