Scientists is still trying to understand why fireflies synchronize. In this project, we will do a simulation of fireflies synchronizing from random initial phases. This is part 4 of 4 tutorials leading to this final tutorial on Synchronous Fireflies. Please check out part 1, part 2, and part 3 before reading this tutorial.
Let’s make a very simple model: when a firefly sees another firefly lights up, it will increase its internal clock (phase) slightly to catch up. If it sees more fireflies light up, it will increase its phase more to catch up faster.
We will need to make minor changes to the firefly program in order to incorporate this model. The following is full program for the synchronous fireflies. We made a new constant PHASE_DELTA (0.01) to represent the increment of phase when a firefly sees another firefly lights up. You can increase this number to make synchronization faster. Instead of a firefly keeping track of all other flies to see if some of them light up, we can update the phase of fireflies when some fireflies lights up and reaches the peak. The function update_neibors(k) updates the other fireflies’ phases for firefly k that has reach its peak phase. If a firefly is already at its peak, it won’t update. Also, a firefly won’t update its phase beyond the peak. Because the phase of a firefly is updated at discrete times, it is possible that the peak values of a firefly is skipped. In this case, this firefly has lost its chance to influence other fireflies. To solve problem, we did a cheat in the update_brightness() function to make sure that the peak phase is not skipped. Please read the code to how it is done.
import turtle
import colorsys
import random
import math
screen = turtle.Screen()
screen.setup(1000,1000)
screen.title("Synchronized Fireflies - PythonTurtle.Academy")
turtle.hideturtle()
turtle.speed(0)
turtle.tracer(0,0)
# Constants
H_YELLOWGREEN = 0.22 # constant: hue value of yellow green color.
V_DARK = 0.1 # constant: brightness value of initial dark state
V_BRIGHT = 1 # constant: brightness value of the brightest state
FPS = 30 # constant: refresh about 30 times per second
TIMER_VALUE = 1000//FPS # the timer value in milliseconds for timer events
CYCLE = 5 # costant: 5 second cycle for firefly to light up
LIGHTUP_TIME = 1 # constant: 1 second light up and dim
SPEED = 20 # 100 units per second
CLOSE_ENOUGH = 16 # if distance squared to target is less than 16, then it is close enough.
# make sure that this number is greater than SPEED/FPS squared
N = 300 # Number of fireflies
PHASE_DELTA = 0.01 # increment of phase when saw a neighbor fires up
# Variables
fireflies = [] # list of firefly turtles
v = [] # list of brightness values
phase = [] # list of phases
current_xpos = [] # list of current x coordinate
current_ypos = [] # list of current y coordinate
target_xpos = [] # list of target x coordinate
target_ypos = [] # list of raget y coordinate
def initialze_fireflies():
for i in range(N):
fireflies.append(turtle.Turtle()) # Add a turtle to the firefly turtle list
v.append(V_DARK) # set them DARK first. The update function will update it to the correct value
phase.append(random.uniform(0,CYCLE)) # phase is random from 0 to CYCLE
current_xpos.append(random.uniform(-500,500)) # Let them go anywhere on screen
current_ypos.append(random.uniform(-500,500))
target_xpos.append(random.uniform(-500,500))
target_ypos.append(random.uniform(-500,500))
for firefly in fireflies: # initialize these turtles
firefly.hideturtle()
firefly.up()
# this function computes brightness based on phase
def compute_brightness(phase):
if phase < CYCLE-LIGHTUP_TIME:
temp = V_DARK # dormant period
elif phase < CYCLE-LIGHTUP_TIME/2: # gradually (linearly) lighting up period
temp = V_DARK + (V_BRIGHT-V_DARK)*(phase-(CYCLE-LIGHTUP_TIME))/(LIGHTUP_TIME/2)
else: # gradually (linearly) dimming period
temp = V_BRIGHT - (V_BRIGHT-V_DARK)*(phase-(CYCLE-LIGHTUP_TIME/2))/(LIGHTUP_TIME/2)
return temp
def update_neibors(k):
global phase
for i in range(N):
if i == k or phase[i] == CYCLE-LIGHTUP_TIME/2: # don't update phase for itself or fireflies at peak
continue
if phase[i] < CYCLE-LIGHTUP_TIME/2: # before peak
phase[i] = min(CYCLE-LIGHTUP_TIME/2,phase[i]+PHASE_DELTA) # make sure don't pass the peak after incrementing phase
else: # after peak
phase[i] += PHASE_DELTA # increment phase by delta
if phase[i] > CYCLE: # phase passed CYCLE
phase[i] -= CYCLE # make sure stays within CYCLE
v[i] = compute_brightness(phase[i]) # with new phase, update the brightness
def update_brightness():
global phase,v
for i in range(N):
phase[i] += TIMER_VALUE/1000 # increase the phase by time passed
if phase[i] > CYCLE: # phase passed CYCLE
phase[i] -= CYCLE # make sure phase stays within CYCLE
if phase[i] > CYCLE-LIGHTUP_TIME/2 and phase[i] - TIMER_VALUE/1000 < CYCLE-LIGHTUP_TIME/2: # skipped peak
phase[i] = CYCLE-LIGHTUP_TIME/2 #cheat here: adjust phase to peak value
v[i] = compute_brightness(phase[i]) # compute the brightness based on phase
for i in range(N): # update other fireflies
if phase[i] == CYCLE-LIGHTUP_TIME/2: # only update when firefly is in peak
update_neibors(i) # try to influence other fireflies
def update_position():
global current_xpos,current_ypos,target_xpos,target_ypos
for i in range(N):
# move towards target SPEED/FPS steps
# figure out angle to target first
angle_to_target = math.atan2(target_ypos[i]-current_ypos[i],target_xpos[i]-current_xpos[i])
# compute changes to current position based on the angle and distance to move per 1/FPS second.
current_xpos[i] += SPEED/FPS*math.cos(angle_to_target)
current_ypos[i] += SPEED/FPS*math.sin(angle_to_target)
# check to see if close enough to target.
dist_to_target_squared = (current_xpos[i]-target_xpos[i])**2 + (current_ypos[i]-target_ypos[i])**2
if dist_to_target_squared < CLOSE_ENOUGH: # close enough, set new target
target_xpos[i] = random.randint(-500,500) # target x coordinate, random location
target_ypos[i] = random.randint(-500,500) # target y coordinate, random location
def update_states():
global should_draw
update_brightness()
update_position()
should_draw = True
screen.ontimer(update_states,TIMER_VALUE)
def draw():
global v,fireflies,should_draw,current_xpos,current_ypos
if should_draw == False: # There is no change. Don't draw and return immediately
return
for i in range(N):
fireflies[i].clear() # clear the current drawing
color = colorsys.hsv_to_rgb(H_YELLOWGREEN,1,v[i]) # use colorsys to convert HSV to RGB color
fireflies[i].color(color)
fireflies[i].goto(current_xpos[i],current_ypos[i])
fireflies[i].dot(10)
should_draw = False # just finished drawing, set should_draw to False
screen.bgcolor('black')
initialze_fireflies()
update_states()
while True:
draw() # draw forever
screen.update()
How long did it take for fireflies to synchronize? Try more fireflies. Did it synchronize faster?
Related Tutorials: