e-neko aka e-cat of IRC told me that buses come in pairs for some reason. I did not believe him. I still find it hard to believe. It looks so strange.
Figured made into a mooovie:
https://kaka.farm/pub/images/2025-12-06-buses-simulation/bus.mp4
Simulation code:
https://kaka.farm/pub/images/2025-12-06-buses-simulation/waiting-for-the-bus.scm
Jupyter notebook on:
https://kaka.farm/pub/images/2025-12-06-buses-simulation/bus.ipynb
(import
(srfi srfi-1)
(srfi srfi-9)
(ice-9 match)
)
(define *bus-capacity* 50)
(define *bus-departure-interval* 100)
(define *bus-route-length* 10000)
(define *bus-speed* 10)
(define *number-of-stations* 100)
(define *station-fill-rate* 1)
(define *time-passenger-on* 1)
(define *time-passenger-off* 1)
(define-record-type <event>
(make-event time data)
event?
(time event-time)
(data event-data))
(define-record-type <bus-departure>
(make-bus-departure)
bus-departure?)
(define-record-type <bus-at-station>
(make-bus-at-station station-number number-of-passengers)
bus-at-station?
(station-number bus-at-station-station-number)
(number-of-passengers bus-at-station-number-of-passsangers))
(define-record-type <passenger-waiting>
(make-passenger-waiting station-number)
passenger-waiting?
(station-number passenger-waiting-station-number))
(define-record-type <world>
(make-world events stations)
world?
(events world-events)
(stations world-stations))
(define (initialise-world)
(make-world '()
(make-vector *number-of-stations* 0)))
(define (sort-event-queue event-queue)
(sort event-queue
(lambda (event-a event-b)
(< (event-time event-a)
(event-time event-b)))))
(define (tick world)
(match (world-events world)
['() (make-world (list (make-event 0 (make-bus-departure))
(make-event 0 (make-passenger-waiting (random *number-of-stations*))))
(world-stations world))]
[(and events (head . rest))
(let* ([sorted-event-queue (sort-event-queue events)]
[earliest-event (car sorted-event-queue)]
[rest-of-events (cdr sorted-event-queue)])
(match earliest-event
[($ <event> time data)
(match data
[($ <passenger-waiting> station-number)
(let ([new-events (list (make-event (+ time (* (random:uniform)
*station-fill-rate*))
(make-passenger-waiting (random *number-of-stations*))))]
[stations (world-stations world)])
(vector-set! stations station-number (1+ (vector-ref stations station-number)))
(make-world (append new-events rest-of-events)
stations))]
[($ <bus-departure>)
(let ([new-events (list (make-event (+ time *bus-departure-interval*)
(make-bus-departure))
(make-event (+ time (/ *bus-route-length* *number-of-stations*))
(make-bus-at-station 1 0)))])
(make-world (append new-events rest-of-events)
(world-stations world)))]
[($ <bus-at-station> station-number number-of-passengers)
(format #t "~A,~A~%" time station-number)
(cond
[(= station-number *number-of-stations*)
(make-world rest-of-events (world-stations world))]
[else
(let* ([stations (world-stations world)]
[waiting-at-station (vector-ref stations station-number)]
[passengers-off (round (* number-of-passengers (random:uniform)))]
[number-of-passengers-after-off (- number-of-passengers passengers-off)]
[free-seats-after-off (- *bus-capacity* number-of-passengers-after-off)]
[passengers-on (min free-seats-after-off waiting-at-station)]
[number-of-passengers-after-off-on (+ number-of-passengers-after-off passengers-on)]
[time-bus-waiting-in-station (+ (* passengers-off *time-passenger-off*)
(* passengers-on *time-passenger-on*))]
[next-time (+ time
time-bus-waiting-in-station
(/ *bus-route-length* *number-of-stations*))]
[new-events (list (make-event next-time
(make-bus-at-station (1+ station-number)
number-of-passengers-after-off-on)))])
(vector-set! stations station-number (- (vector-ref stations station-number)
passengers-on))
(make-world (append new-events rest-of-events)
stations))])]
[($ <event> time 'bus-final-arrival data)
(make-world rest-of-events
(world-stations world))])]))]))
(let loop ([world (tick (make-world '() (make-vector *number-of-stations* 0)))]
[n 0])
(cond
[(= n 100000)
'()]
[else
(loop (tick world)
(1+ n))]))
Jupyter notebook source, sorta:
from matplotlib import pyplot as plt
import numpy as np
s = open('data-file.csv', 'r').read()
l = [[float(n.split(',')[0]), int(n.split(',')[1])] for n in s.split("\n")]
def f(station_number):
last = [n for n in l if n[1] == station_number]
last = [n_1[0] - n_0[0] for n_0, n_1 in zip(last, last[1:])]
fig, ax = plt.subplots(1, 1)
ax.set_title(f'bus station #{station_number:03}')
ax.set_xlabel('time difference between two consecutive buses')
ax.set_ylabel('number of busess')
ax.hist(last, bins=100)
fig.savefig(f'bus-{station_number:03}.png')
for range(1, 101):
f(n)

Holy hell! Thank you! I am too tired to understand anything, but when I wake up and read it I will still be too dumb to understand anything. I will try and read it anyway. Thank you!
Thanks! I suspect this is not too difficult to Monte Carlo model, with different routes, number of busses and delay distribution curves, possible adaptations, and impact on costs.