Coding Example: Implement the behavior of Boids (NumPy approach)
In this lesson we will try to implement all three rules that Boids follow using the NumPy approach.
We'll cover the following
NumPy Implementation
As you might expect, the NumPy implementation takes a different approach and we’ll gather all our boids into a position array and a velocity array:
n = 500
velocity = np.zeros((n, 2), dtype=np.float32)
position = np.zeros((n, 2), dtype=np.float32)
The first step is to compute the local neighborhood for all boids, and for this, we need to compute all paired distances:
#np.subtract.outer apply the ufunc op to all pairs (a, b) with a in A and b in B.
dx = np.subtract.outer(position[:, 0], position[:, 0])
dy = np.subtract.outer(position[:, 1], position[:, 1])
distance = np.hypot(dx, dy)
We could have used the scipy
cdist but we’ll need the dx
and dy
arrays later. Once those have been computed, it is faster to use the hypot method. Note that distance shape is (n, n)
and each line relates to one boid, i.e. each line gives the distance to all other boids (including self).
From these distances, we can now compute the local neighborhood for each of the three rules, taking advantage of the fact that we can mix them together. We can actually compute a mask for distances that are strictly positive (i.e. have no self-interaction) and multiply it with other distance masks.
Note: If we suppose that boids cannot occupy the same position, how can you compute
mask_0
more efficiently?
mask_0 = (distance > 0)
mask_1 = (distance < 25)
mask_2 = (distance < 50)
mask_1 *= mask_0
mask_2 *= mask_0
mask_3 = mask_2
Then, we compute the number of neighbors within the given radius and we ensure it is at least 1 to avoid division by zero.
mask_1_count = np.maximum(mask_1.sum(axis=1), 1)
mask_2_count = np.maximum(mask_2.sum(axis=1), 1)
mask_3_count = mask_2_count
We’re ready to write our three rules: