My buses don’t come in threes!

A few weeks ago, I blogged about some code I was writing to simulate buses arriving at bus stops and picking up passengers.

I mentioned at the end that the code shown there had three major shortcomings. Having nothing better to do with an hour this afternoon (meaning: not being bothered to do what I was supposed to be doing and preferring to have a play instead), I decided to address those shortcomings.

Whilst doing so, I realised that my original choice of records to represent the busses and stops was causing messy code. Had I written proper functional code, this wouldn’t have happened, but I was playing, and dumped the contents of my brain into LinqPad as I was thinking, so it wasn’t very organised. I switched to plain old classes for the second version, as this allowed me to update the bus and stop properties more cleanly.

1. Unrealistic passenger arrival

Passengers arrive at all stops right from the start, meaning that by the time the first bus gets to a later stop, there is a huge queue. You can see this in the simulation, which causes bus #1 to keep stopping, resulting in subsequent buses catching it up. You end up with a big cluster of buses at the end.

This turned out to be a pretty simple one. I assumed that people wouldn’t arrive at a stop too far in advance, so set up a variable to specify how long before the (theoretical) arrival of the first bus they would start arriving. Then, I only added passengers that far ahead of the current time…

void PassengersArriveAtStops(int time) {
  foreach (BusStop stop in busStops.Where(s => s.Distance <= time + passengerAdvance)) {
    int rn = r.Next() % 100;
    if (rn < chanceOfPassengerArrival) {
      stop.PassengersWaiting += r.Next() % maxNewPassengers;
    }
  }
}

2. Infinite capacity buses

The buses have an infinite capacity (bit like Hilbert’s Hotel on wheels!), so the first bus gets all the passengers. We should give the buses a maximum capacity, so if a bus is full, it carries on and leaves the next bus to pick up the passengers. This will keep us out of trouble with the health and safety people as well!

Once I had set a maximum capacity, then I needed to check if the bus had any space when arriving at a stop. If it didn’t, it would just go straight past. Don’t you hate it when they do that?

So, our new code for taking on passengers became…

if (bus.Passengers < busCapacity && stop.PassengersWaiting > 0) {
  // There are passengers, and we have space for some, so take on the number that can
  // board in one time unit, up to the capacity of the bus and don't move on
  int spaceOnBus = busCapacity - bus.Passengers;
  int numberOfPassengersThatCouldBoard = Math.Min(stop.PassengersWaiting, boardingRate);
  int numberThatWillBoard = Math.Min(spaceOnBus, numberOfPassengersThatCouldBoard);
  int passengersBoarding = Math.Max(0, numberThatWillBoard);
  stop.PassengersWaiting = stop.PassengersWaiting - passengersBoarding;
  bus.Passengers += passengersBoarding;
}

The code is a bit convoluted, but basically we are checking how much space is on the bus (line 4), then taking the smaller of that and the maximum number of passengers that can possibly get on in one time unit (line 5). That allows us to work out how many passengers are actually going to board in this time unit (line 6), which we then fix up in case it’s negative (line 7).

In order to check that this was working, I also added a display to show the number of passengers on each bus…

string ShowNumberOfPassengersOnBuses() =>
  $"{Environment.NewLine}Number of passengers on each bus:{Environment.NewLine}   {"".PadRight(busCapacity, '-')}{Environment.NewLine}" + buses.Select(b => $"{Num(b.Number)}: {"".PadLeft(b.Passengers, '*')}")
  .JoinStr(Environment.NewLine);

This made it much easier to see what was going on…

Passengers per bus

The dashed line below the title shows the capacity of the bus, so you can see which buses are full and which aren’t.

3. Allow passengers to get off the buses

No-one ever gets off the buses, so even if we added a capacity, once a bus were full, it would never stop again. We should change it so that a random number of people get off the bus at each stop.

I made the assumption that people got off the bus very quickly, so didn’t delay the bus for this. This isn’t strictly accurate, but given that buses generally stop to pick up passengers, and that it takes less time for a passenger to get off the bus than to get on, it seemed good enough for the purpose of this simulation. I also set a maximum number of passengers that would get off at any one stop.

int maxGettingOff = Math.Max(0, bus.Passengers - departureRate);
int gettingOff = maxGettingOff * ((r.Next() % 100) / 100);
bus.Passengers -= gettingOff;

We first work out how many people could get off the bus. This is simply the number of passengers on board, minus the number that get off at any one stop, adjusted to make sure it was never negative. The we took a random percentage of this number and reduced the number of passengers on the bus by that amount.

Once it was all put together, it made a nice simulation…

So, do buses come in threes? Not according to my simulation they don’t!

Does this mean that Rob Eastaway was wrong? I certainly wouldn’t use my simulation as any proof, as it is full of simplifications and assumptions (as most simulations are), so probably doesn’t reflect Real Life(TM) very well.

Having said that, the idea of buses coming in threes (or twos as he claims is what really happens) is based on a perception, and the human mind is very good at fooling us with perceptions. We don’t notice the ordinary, but do notice the unusual things in life. Therefore, it could be that buses generally come alone, but we don’t notice that, we only notice when they happen to come along in twos or threes.

A quick bit of research and a much better simulation

A quick search turned up an archive of a nice article that explains the phenomenon of bus bunching (as they call it, although it also seems to be known by several similar names). The explanation is basically the same as Rob Eastaway’s, but is accompanied by a really well written simulation that makes mine look like something someone knocked up in a LinqPad script in his spare time. Erm, actually that’s what it was, so I guess I shouldn’t feel too bad!

It could be that by altering the parameters in my code, we might see some bunching, but it could equally be that my assumptions were too simple. Either way, it was an enjoyable way of avoiding some real work!

You can see the full code for my simple simulation here. As before, paste it into LinqPad with the language set to “C# Statements”

Be First to Comment

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.