I have long been fascinated by the flock behaviour, may it be in nature, like beautiful starlings flock shown in the youtube video below, or artificial swarms like such described in Crichton’s book ‘Prey’. In its essence, it is a corporative problem between agents. I had some shallow dips into swarm literatures, I did want to do it as my third year literature review but I did not manage to find enough papers. Better weather made me open the blinds at work, and from time to time, around sunset, bird flocks start to appear, like a gentle reminder. It is now that I decided to have another, more documented, more freestyle attempt.
There probably are fundamental difference between swarm and flock behaviour. To me they are the same, but I will mainly use flock for now. Anyways, let’s play the creator. A natural question is how do the individuals move in conjunction with one another? What would be required to achieve something like the above? I plan to write a simulational system to investigate this.
As I said, I am interested in the how rather than the why. The purpose of this project is not to decipher how flock behaviour arises in nature (the stimulus seems to be different for different species anyways). It is a mere mimic of the observed behaviour.
I am using python for this project because its support for scientific computing and decent computation speed. This is meant to be a quick hit and run, so no compiled language. I did want to use R because I liked the look of ggplot2 more than matplotlib, but I had to install some additional programs in order to render animation, so no. I am using object-oriented paradigm for this project. The whole problem is split into a world object and a flock object. The former object contains the dimension (I will mainly focus on 3D but any dimension is allowed) in which the flock lives. More importantly it contains all the components to plot the animations for a given flock. Functions to construct plots that allow more quantitative studies are to come.
The World class looks like this:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as p3
import matplotlib.animation as animation
class World(object):
def __init__(self, resolution = 64, dim = [[-1, 1], [-1, 1], [-1, 1]], trace_lim = 7):
self.dim, self.resolution = dim, resolution
# the trace for the trajectory shouldn't be too large to avoid storing too much data
self.trace_lim = trace_lim
def setDim(self, dim): self.dim = dim
def getDim(self): return self.dim
def drawDots3D(self, ax, coords):
return ax.scatter(coords[0], coords[1], coords[2])
def snapshot(self, func, *args):
ax = self.worldSetup()
func(ax, *args)
plt.show()
def worldSetup(self):
self.fig = plt.figure()
if len(self.dim) == 2:
ax = plt.axes(self.fig)
ax.set_xlim(self.dim[0])
ax.set_xlabel('X')
ax.set_ylim(self.dim[1])
ax.set_ylabel('Y')
return ax
if len(self.dim) == 3 :
ax = p3.Axes3D(self.fig)
ax.set_xlim3d(self.dim[0])
ax.set_xlabel('X')
ax.set_ylim3d(self.dim[1])
ax.set_ylabel('Y')
ax.set_zlim3d(self.dim[2])
ax.set_zlabel('Z')
return ax
def animate3D_setup(self, flock):
self.members = flock.members
ax = self.worldSetup()
self.traj = [[[0 for i in range(self.trace_lim)] for j in range(len(self.dim))] for k in range(self.members)] # keep track of line trajectory
points = [ax.plot([], [], [],flock.shapes[i], c = flock.colors[i], ms = flock.sizes[i])[0] for i in range(self.members)]
lines = [ax.plot([], [], [], ls = flock.traceStyle[i], lw = flock.traceSize[i], c = flock.colors[i])[0] for i in range(self.members)]
return points, lines
def animate3D(self, frame, fields ):
if fields[1 : ] == () :
coord = fields[0]()
else:
coord = fields[0](fields[1 : ])
for i in range(self.members):
self.points[i].set_data(coord[i][0], coord[i][1])
self.points[i].set_3d_properties( coord[i][2])
return self.points
def animate3D_trace(self, frame, fields):
if fields[1 : ] == () :
coord = fields[0]()
else:
coord = fields[0](fields[1 : ])
for i in range(self.members):
self.points[i].set_data(coord[i][0], coord[i][1])
self.points[i].set_3d_properties( coord[i][2])
for j in range(len(self.dim)):
self.traj[i][j].pop(0)
self.traj[i][j].append(coord[i][j])
self.lines[i].set_data(self.traj[i][0][-self.trace_lim : ], self.traj[i][1][-self.trace_lim : ])
self.lines[i].set_3d_properties( self.traj[i][2][-self.trace_lim : ])
return self.points + self.lines
def playAnimation(self, flock, func, *args, **kwargs ):
self.points, self.lines = self.animate3D_setup(flock)
frames = 20
if "frames" in kwargs:
frames = kwargs["frames"]
anim = animation.FuncAnimation(self.fig, func, frames = frames, fargs = (args, ), blit = True)
plt.show()
def saveAnimation(self, flock, func, *args, **kwargs):
self.points, self.lines = self.animate3D_setup(flock)
frames = 20
if "frames" in kwargs:
frames = kwargs["frames"]
anim = animation.FuncAnimation(self.fig, func, frames = frames, fargs = (args, ), blit = True)
anim.save('./tmp.mp4', fps=30, extra_args=['-vcodec', 'libx264'])
The flock object basically contains the physical positions of a each member in the flock as a list as well as storing other properties such as the size and color of each individual member in lists. Rather than pre-package the position, size, color, etc of a member into a individual object, and make up a flock object as a list of individual objects, I envisage most if not all operations are performed on the flock as a whole, it is more computationally efficient to do so this way. In this class, various different functions that describes how the individuals move are to come. This will be the main meat and bone of the project in order to mimic flock behaviour.
The Flock class looks like this (at the point of writing):
from individual import Individual
import numpy as np
from scipy import spatial
class Flock(object):
def __init__(self, world , members = 7):
self.positions, self.colors, self.shapes, self.sizes, self.traceStyle, self.traceSize= np.zeros((members, len(world.dim))), ["green"] * members, ["^"] * members, [2] * members, ["-"] * members, [0.5] * members
self.world, self.members = world, members
def addIndividual(self, ind):
self.positions = np.concatenate((self.positions, [ind.position]))
self.colors.append(ind.color)
self.shapes.append(ind.shape)
def deleteIndividual(self, index = 0):
self.positions = np.delete(self.postions, (index), axis = 0)
self.colors.pop(index)
self.shapes.pop(index)
def getPositions(self): return self.positions
def setPositions(self, positions): self.positions = positions
def updatePositions(self, vel):
self.positions = np.add(self.positions, vel)
return self.positions
# def getIndividual(self, index): return self.flock[index]
def uniformVel(self):
coords = []
for d in range(len(self.world.dim)):
tmp = np.abs(self.world.dim[d][1] - self.world.dim[d][0])/float(self.world.resolution)
coords.append(np.random.uniform(-tmp, tmp, self.members) )
return self.updatePositions(np.transpose(coords))
def genRandPositions(self):
coords = []
for d in range(len(self.world.dim)):
coords.append(np.random.rand(self.members) * (self.world.dim[d][1] - self.world.dim[d][0]) + self.world.dim[d][0])
return np.transpose(coords)
The following simulation shows the trajectory of 200 individuals starting at the same position and moving with random velocities (drawn from a uniform distribution). The line behind each individual indicates its past 7 locations. As expected, no flock behaviour when each individual moves irrespective or everything else.
To replicate that, you can copy the above two code snippets and put them in a folder (call them world.py and flock.py respectively) along with the code below. Then run the below script. You will obviously need python installed (I’m using python2 rather than python3) as well as additional libraries matplotlib and numpy. So it might be easier to install anaconda which by default includes scientific libraries if you haven’t got python. You can visit my repo on GitHub for the full, evolving code.
from world import World
from flock import Flock
w = World();
f = Flock(world = w, members = 200)
w.playAnimation(f, w.animate3D_trace, f.uniformVel, frames = 1)
The amount of code might seem to be an overkill at this point, but with the overarching framework more or less setup, it is relatively easy to write functions that are specifically dealing with the dynamics to better simulate the flock behaviour. One immediate modification to modelling the dynamics can be made, namely individuals within a flock have a orientation. There is a limit to the degree an individual can turn within one time step with respect to the initial direction it was flying, let’s leave that for the next post…