2.16. 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: Maya Shapiro
Today’s class focused on Traffic Jams and Boids. This chapter/lecture is based on the previous lecture on agent-based models; however, here we consider agents that move in continuous space rather than ones that occupy discrete locations on a 2D grid.
2.16.1. Traffic Jams¶
This portion of the lecture discussed the causes of traffic jams and how they can be simulated. The simulation produced simulates cars driving on a 1D circular highway.
2.16.1.1. Causes of Traffic Jams¶
Different causes of traffic jams were discussed during the lecture. The difference between a proximal cause and an ultimate cause was explored.
- Proximate cause:
direct event leading to an outcome.
- Ultimate cause:
usually related to evolutionary reasons of why a system or behaviour exists.
Some causes of traffic jams discussed in class include: braking because you see something on the road, a speed trap, and no reason at all.
2.16.1.1.1. The code¶
A highway class is defined and initialized. There are several methods defined in this class including: the initialization method, the step()
method, and the move()
method. A Driver
class is also defined with an initialization method, and a choose_acceleration()
method.
2.16.1.1.1.1. The Highway Class¶
def __init__(self, n=10, length=1000, eps=0, constructor=Driver):
self.length = length
self.eps = eps
self.crashes = 0
# create the drivers
locs = np.linspace(0, length, n, endpoint=False)
self.drivers = [constructor(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]
The initialization method has the following parameters:
self
n
= number of carslength
= length of the highwayeps
= amount of random noise.
Another variable used in the function is called locs
. locs
contains the location of each driver using the NumPy function called linspace()
and creating an array of n
locations that are equally spread out between 0 and length
.
The drivers
attribute contains a list of Driver
objects. To link the drivers to the driver ahead of them, a for
loop is used.
def step(self):
for driver in self.drivers:
self.move(driver)
The step()
method calls on the move()
method in a for
loop to move each driver.
def move(self, driver):
# get the distance to the next 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 with next driver, stop
if speed > dist:
speed = 0
self.crashes += 1
# update speed and loc
driver.speed = speed
driver.loc += speed
The move`()
method takes the parameters of self, and driver. First, dist
is defined as the distance between the driver and the driver ahead of them. The value dist
is then passed to the choose_acceleration()
method to choose the acceleration of the driver. The acceleration of the driver must be between the minimum and maximum acceleration values defined elsewhere in the code. The acceleration is stored in a variable labelled acc
.
The speed of the driver is then calculated by adding the driver’s speed to the acceleration. Random noise is added to the speed. The value of eps
determines the magnitude of the error to model the imperfectness of the world. The model enforces that the speed must be non-negative (so cars can only go forward) and that no car can go over the speed limit. If a car is to collide with the car in front of it, then the speed is set to zero.
After all this, the speed and location of the driver can be updated.
2.16.1.1.1.2. The Driver class¶
def __init__(self, loc, speed):
self.start = loc
self.loc = loc
self.speed = speed
The driver class is initialized using the following parameters:
self
loc
= location of the driverspeed
= speed of the driver.
def choose_acceleration(self, dist):
return 1
The default implementation of this function returns 1. For homework, students were instructed to use the dist
attribute passed to the function to make a unique implementation of this function.
2.16.1.1.1.3. The simulation¶
The simulation uses blue boxes to represent cars and red triangles to represent accidents. See the image below for and example of the simulation at three different points in time.

2.16.1.1.1.4. Random Perturbation¶
Traffic jams become more severe as the number of cars increase. In addition, when randomness is introduced into the simulation (even in very small amounts), the number of traffic jams increases.
2.16.2. Boids¶
Boids is a combination of the words bird and ‘-iod‘. Boids are an agent-based model that describes the behaviour of herds. Boids simulate three behaviours.
- Flock centering:
move towards the centre of the flock
- Collision avoidance:
avoid obstacles
- Velocity matching:
align speed and direction with neighbouring boids
Boids are only able to simulate the behaviour of other things they can see (meaning if it is not in their field of vision, they can’t make decisions about how they move based on it).
2.16.2.1. The code¶
2.16.2.1.1. The Boid class¶
The boid class has several methods including: center()
, avoid()
, align()
, and love()
.
center(self, boids, radius=1, angle=1)
The center()
method finds other boids it can see and calculates a vector pointing towards the other boids’ centroids. The parameters include the radius and angle of the field of view. This method uses the get_neighbours()
method to get a list of the Boid
objects that it can see. These values are stored in a list of vectors called vecs
that represents their positions.
vector_towards_center()
returns a vector that goes from the corresponding boid to the centroid of its neighbours.
get_neighbours()
computes the vector from a corresponding boid to another boid in its field of view and computes the magnitude of the vector between them and the angle between them.
vector_toward_center()
calculates a vector from a corresponding boid to the centroid of its neighbours.
avoid(self, boids, carrot, radius=0.3, angle=np.pi)
The avoid()
method finds objects in field of vision and computes a vector pointing away from their centroid. It uses the same methods as the center()
method; however, it takes the carrot into account. It takes self
, boids
, carrot
, radius
, and the angle
as parameters. avoid()
uses the get_neighbours()
method which returns a list of things to avoid. The negative sign in the return statement tells the boid to go in the opposite direction in order to avoid other boids
.
align(self, boids, radius=0.5, angle=1)
The align()
method finds boids
in field of vision and computes the average of the direction they are heading in. It is similar to the center()
method; however, it looks at average velocity where center()
looks at average position.
love(self, carrot)
The love method()
calculates vector that points towards the carrot
.
2.16.2.1.2. Arbitration¶
The boids
have conflicting goals about which way they want to move. To solve this issue, a weighted sum is computed to determine which way the boid moves. After computing the direction, the boid will move, the velocity and position of the boid are updated.
2.16.3. Emergence and free will¶
Components of systems have properties that the systems do not have themselves. Some properties we studied so far include:
Rule 30 (from cellular automaton): We know the rules that govern the system; however, the system produces a sequence that is hard to distinguish from random.
Schelling’s model: The model is not racist, but it appears as though it has a high degree of segregation.
Sugarscape: Waves are moving diagonally but the actual agents can not move in a diagonal fashion.
Traffic jams: Cars move forward but traffic jams move backwards.
Free will demonstrates the ability for us to make decisions. If we are governed by physical laws, then are we really making choices? There are many arguments from philosophers that try to resolve this conflict; however, many of the models suggest that if the parts of a system are deterministic then a system cannot have free will.