2.21. Lecture 15: Herds, flocks, and traffic jams¶
Before this class you should:
Read Think Complexity, Chapter 10
Before next class you should:
Read Think Complexity, Chapter 11
Note taker: Nathan Chung
2.21.1. Overview¶
This chapter moves from grid-based agent models to agents operating in continuous space. The two main case studies are simulated cars on a circular highway (used to study spontaneous traffic jams) and simulated birds in 3D space (Boids). Both are used to explore emergence, which is the idea that macro-level behaviour can arise from simple local rules, without any central coordination.
2.21.2. Traffic Jams¶
Traffic jams don’t always need an obvious cause like an accident. They can
emerge spontaneously. The simulation uses two classes: Highway and
Driver. Cars are placed evenly on a circular road and always try to
accelerate at the maximum rate. The Highway class is initialized as
follows:
class Highway:
def __init__(self, n=10, length=1000, eps=0):
self.length = length
self.eps = eps
# create the drivers
locs = np.linspace(0, length, n, endpoint=False)
self.drivers = [Driver(loc) for loc in locs]
# and link them up
for i in range(n):
j = (i+1) % n
self.drivers[i].next = self.drivers[j]
Each time step, the highway moves every driver:
#Highway
def step(self):
for driver in self.drivers:
self.move(driver)
The move method handles the physics: it checks the distance to the car
ahead, applies the chosen acceleration, adds random noise controlled by
eps, enforces a speed limit of 40, and stops the car if it would
otherwise collide:
# Highway
def move(self, driver):
dist = self.distance(driver)
# let the driver choose acceleration
acc = driver.choose_acceleration(dist)
acc = min(acc, self.max_acc)
acc = max(acc, self.min_acc)
speed = driver.speed + acc
# add random noise to speed
speed *= np.random.uniform(1-self.eps, 1+self.eps)
# keep it nonnegative and under the speed limit
speed = max(speed, 0)
speed = min(speed, self.speed_limit)
# if current speed would collide, stop
if speed > dist:
speed = 0
# update speed and loc
driver.speed = speed
driver.loc += speed
The basic Driver class always accelerates at the maximum rate:
class Driver:
def __init__(self, loc, speed=0):
self.loc = loc
self.speed = speed
def choose_acceleration(self, dist):
return 1
Because of random noise, the spacing between cars gradually becomes uneven. Once a car is forced to stop, a jam can form and tends to persist. Cars approach from behind and pile up, while those at the front accelerate away. One interesting result is that the jam itself can propagate backwards even though every individual car is moving forward.
Figure 1: Circular highway simulation at three time steps. Squares = car positions; triangles = braking events.¶
2.21.3. Random Perturbation¶
Even a tiny amount of randomness has a large effect on highway capacity. With no noise (\(eps=0\)), the highway supports up to 25 cars at full speed. Beyond that, the spacing \(1000/n\) drops below 40 and speeds fall. With 0.1% noise, the maximum number of cars that can maintain full speed drops to \(\sim 20\), and with 1% noise, it drops further to \(\sim 10\). Small real-world imperfections compound into significant slowdowns.
Figure 2: Average speed vs. number of cars for \(eps=0\), \(eps=0.001\), and \(eps=0.01\).¶
2.21.4. Boids¶
In 1987 Craig Reynolds introduced Boids, an agent-based model of flocking. The name is a contraction of “bird-oid”. Boids are also used to model fish schools and herding animals. Each agent follows three simple rules using only local information:
Flock centering: move toward the centre of nearby Boids.
Collision avoidance: steer away from objects that are too close.
Velocity matching: align speed and direction with neighbours.
Carrot attraction: move toward a target “carrot” in the world.
Each Boid pays attention only to others within a field of view defined by a radius and a viewing angle. The implementation uses VPython for 3D graphics.
2.21.5. The Boid algorithm¶
The code is split into a Boid class (behaviours) and a World class
(holds the list of Boids plus a “carrot” attractor). The Boid class has four
methods, each returning an acceleration request:
center: finds neighbours and returns a vector toward their centroid.
avoid: same idea, smaller radius, wider range, result negated and steers away from nearby objects.
align: returns a vector toward the average velocity (not position) of neighbours.
love: returns a unit vector pointing toward the carrot.
The helper get_neighbors filters candidates by distance and viewing
angle, and vector_toward_center computes a unit vector toward their mean
position.
The get_neighbors method works as follows:
def get_neighbors(self, boids, radius, angle):
neighbors = []
for boid in boids:
if boid is self:
continue
# if not in range, skip it
offset = boid.pos - self.pos
if offset.mag > radius:
continue
# if not within viewing angle, skip it
if self.vel.diff_angle(offset) > angle:
continue
# otherwise add it to the list
neighbors.append(boid)
return neighbors
vector_toward_center computes a unit vector toward the centroid:
def vector_toward_center(self, vecs):
if vecs:
center = np.mean(vecs)
toward = vector(center - self.pos)
return limit_vector(toward)
else:
return null_vector
2.21.6. Arbitration¶
The four acceleration requests are combined as a weighted sum:
goal = w_center*center + w_avoid*avoid + w_align*align + w_love*love
The set_goal method implements this as follows:
def set_goal(self, boids, carrot):
w_avoid = 10
w_center = 3
w_align = 1
w_love = 10
self.goal = (w_center * self.center(boids) +
w_avoid * self.avoid(boids, carrot) +
w_align * self.align(boids) +
w_love * self.love(carrot))
self.goal.mag = 1
Typical weights are w_avoid=10, w_center=3, w_align=1, and w_love=10, meaning
avoidance is prioritised to prevent collisions. Velocity is then updated as a
blend of the old velocity and the goal: vel = (1-mu)*vel + mu*goal,
where mu sets manoeuvrability. Changing the weights, radii, angles,
and mu can produce qualitatively different collective behaviours, such as
bird flocks, fish schools, or insect swarms.
2.21.7. Emergence and Free Will¶
A central theme across the book is that complex systems can exhibit properties that none of their parts have. Traffic jams move backward while every car moves forward. Boid flocks look centrally organised but aren’t. Schelling’s non-racist agents produce segregation. These are all examples of emergence.
The chapter uses this to open a discussion on free will. This raises an apparent conflict: if the brain is a deterministic physical system, how can choices be free? Two classic positions are:
William James: actions are generated by a random process then selected deterministically, so behaviour is fundamentally unpredictable.
David Hume: the feeling of choice is an illusion, so the system is fully deterministic.
The complex-systems view offers a third option: free will at the level of decisions is compatible with determinism at the level of neurons, just as a backward-moving traffic jam is compatible with forward-moving cars. This position is called compatibilism.
2.21.8. Notes¶
These notes are based on the in-class lecture and Chapter 10 of Think Complexity by Allen Downey.