Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Firefly Sync

Twenty micro:bits, each blinking to its own clock, teaching themselves to blink as one.

Twenty oscillators falling into synchrony

No leader. No clock signal. No unit knows how many others exist.


What this is

In parts of Southeast Asia, thousands of fireflies in a single tree flash in perfect unison. Nobody conducts them. Each insect follows one rule - see a neighbour flash, nudge your own timer forward slightly - and unison falls out of it.

This repo runs that rule on a swarm of BBC micro:bits, and includes a simulator that proves it works before you buy any hardware.

Status: simulation-verified, not yet hardware-verified

The synchronisation rule is thoroughly tested - 26 tests covering convergence from many random starts, the uncoupled control case, clock mismatch between units, and local-only coupling. The GIF and every figure below come from the simulator, not from real boards.

The firmware in microbit/firefly.py is written and syntax-checked, and its maths is asserted identical to the simulator's to 1e-12 - but it has not been run on physical micro:bits, because a meaningful swarm needs a dozen or more and I have one. Radio range, LED-as-light-sensor reliability in optical mode, and timing under real load are all unvalidated.

Treat this as a verified algorithm plus untested firmware, not a finished build.

The rule is not a metaphor. It's Mirollo & Strogatz's 1990 pulse-coupled oscillator model, and it comes with a theorem: for a concave, increasing charging curve, the swarm converges to synchrony from almost every starting condition.


The rule

Each unit carries a phase φ that climbs from 0 to 1. At 1 it flashes and resets.

The trick is that a neighbour's flash doesn't push the phase directly - it pushes a charging curve f(φ), the way current dumped into a leaky capacitor raises its voltage:

f(φ)    = ln(1 + (e^b − 1)·φ) / b          concave, increasing
f⁻¹(u)  = (e^(b·u) − 1) / (e^b − 1)

on seeing a flash:   φ ← f⁻¹( min(1, f(φ) + ε) )

That concavity is the whole ballgame. It means a unit late in its cycle gets shoved further than one early in its cycle, so trailing units catch up faster than leading units run away.

For this particular f the induced phase map turns out to be exactly affine:

φ ← (e^(b·ε) − 1)/(e^b − 1)  +  φ·e^(b·ε)

with slope e^(b·ε) > 1. Gaps between units expand every cycle until they collide at the firing threshold and lock. tests/test_sync.py checks this closed form to 1e-9.

Two details keep real hardware from tearing itself apart:

  • Refractory period. Ignore incoming flashes for 250 ms after your own, or your flash comes back and re-triggers you.
  • Deferred updates. Bank the shove and apply it on the next tick rather than mid-flash - the Reachback Firefly Algorithm fix (Werner-Allen et al., 2005). Without it the swarm rings like audio feedback instead of settling.

Results from the simulator

Twenty units, each able to see only its two nearest neighbours on each side. Coupling switched on at 8 seconds:

Flash raster, control vs coupled

Each tick is one flash. Before 8 s the units drift at their own rates and the raster is a mess of diagonals. Three seconds after coupling comes on, it's vertical bars - every unit firing on the same beat.

Order parameter vs coupling strength

Stronger coupling locks faster. Zero coupling never locks, which is the control condition and the thing that makes the rest of it evidence.

A finding worth keeping: if each unit can see only one neighbour each side, the swarm never fully locks - it stalls around 0.93 and stays there. It settles into a twisted wave that chases itself around the ring forever instead of collapsing to unison. Connectivity, not just coupling strength, decides whether synchrony is reachable at all.


What you need

micro:bits 2 minimum, 12-20 for the good version. V1 or V2 both work.
Power A battery pack per unit, or USB.
Room Dim, if you want to try optical mode.

That's the entire bill of materials.

Running it

Flash the same file - microbit/firefly.py - onto every unit, using the micro:bit Python editor or uflash:

uflash microbit/firefly.py

They start with random phases and find each other.

Controls

Button Does
A Re-randomise this unit's phase - scrambles the swarm so you can watch it re-converge
B Toggle coupling on/off - this is the control condition, and the best thing to film
A + B Switch between radio and optical coupling

Radio vs optical

Default is radio: units announce their flashes over 2.4 GHz. Reliable, works in any lighting, and lets you shrink the coupling range by lowering RADIO_POWER - which is how you get the local-coupling effects above.

Optical mode is truer to the biology. The micro:bit's LED array is reverse-biased and used as a light sensor, so the units genuinely watch each other, exactly as fireflies do. It's also much fussier: they need to face each other, sit 5-15 cm apart, and the room has to be dim. Worth doing once for the principle.


The simulator

Same rule, same constants, in sim/firefly_sim.py. Use it to tune parameters before touching hardware.

from firefly_sim import simulate, ring_coupling

run = simulate(n=20, epsilon=0.12, seed=0)
print(run.order[-1])            # ~1.0 once synchronised
print(run.sync_time_ms())       # when it locked

# only see your 2 nearest neighbours, as with low radio power
simulate(n=20, coupling=ring_coupling(20, neighbours=2))

# real hardware never shares a clock
simulate(n=20, period_jitter=0.03)

Regenerate every figure in this README:

pip install -r requirements.txt
python3 sim/make_figures.py

Run the tests:

python3 -m pytest tests -q      # 18 passed

The suite checks that the charging curve is concave and increasing (the hypothesis of the theorem), that the phase map matches its closed form, that coupled swarms converge from eight different random starts, that uncoupled ones never do, that stronger coupling is faster, and that it survives 3% clock mismatch between units.


Tuning

Everything lives at the top of microbit/firefly.py. Keep it in step with sim/firefly_sim.py if you change it.

Constant Default Effect
PERIOD_MS 1000 Natural blink period of one lonely unit
EPSILON 0.12 Coupling strength. 0 = independent. Higher = faster but jumpier
B_SHAPE 3.0 Curvature of the charging curve. Must be > 0 for the theorem to hold
REFRACTORY_MS 250 Deafness window after your own flash
RADIO_POWER 6 0-7. Lower = shorter range = local coupling = more interesting patterns

Things to try

  • Turn RADIO_POWER down to 0 and spread the units across a long table. Synchrony has to travel, and you can watch the wave move.
  • Set EPSILON = 0.02 and see how long unison takes - or whether it arrives at all.
  • Give half the swarm a different PERIOD_MS. How different can two populations be and still lock?
  • Hold one unit's reset down. Does one stubborn insect drag the whole tree?

References

About

Pulse-coupled oscillators on a micro:bit swarm: independent blinkers self-synchronise with no leader and no shared clock. Mirollo-Strogatz, simulation-verified.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages