参考书籍《Python极客项目编程》。

运行环境

操作系统Win11。

Python3.10.5

电脑连接互联网。

安装相关包

在命令行窗口使用pip命令(我的电脑上,“pip.exe”文件所在目录是“D:\Programs\Python\Python310\Scripts”)安装numpy、matplotlib、scipy等相关包,命令如下:

pip install numpy

pip install matplotlib

pip install scipy

并根据提示使用如下命令升级:

D:\Programs\Python\Python310\python.exe -m pip install –upgrade pip

安装包相关信息的查看(以numpy为例)

启动python,进入python提示符,依次键入import numpy、print(numpy)、dir(numpy);或者help()numpy,显示该模块的相关信息。help(numpy)也可以)。

源代码

源代码网址: pp/boids.py at master · electronut/pp · GitHub

源代码如下:

"""boids.pyImplementation of Craig Reynold's BOIDsAuthor: Mahesh Venkitachalam"""import sys, argparseimport mathimport numpy as npimport matplotlib.pyplot as pltimport matplotlib.animation as animationfrom scipy.spatial.distance import squareform, pdist, cdistfrom numpy.linalg import normwidth, height = 640, 480class Boids:"""Class that represents Boids simulation"""def __init__(self, N):""" initialize the Boid simulation"""# init position & velocitiesself.pos = [width/2.0, height/2.0] + 10*np.random.rand(2*N).reshape(N, 2)# normalized random velocitiesangles = 2*math.pi*np.random.rand(N)self.vel = np.array(list(zip(np.sin(angles), np.cos(angles))))self.N = N# min dist of approachself.minDist = 25.0# max magnitude of velocities calculated by "rules"self.maxRuleVel = 0.03# max maginitude of final velocityself.maxVel = 2.0def tick(self, frameNum, pts, beak):"""Update the simulation by one time step."""# get pairwise distancesself.distMatrix = squareform(pdist(self.pos))# apply rules:self.vel += self.applyRules()self.limit(self.vel, self.maxVel)self.pos += self.velself.applyBC()# update datapts.set_data(self.pos.reshape(2*self.N)[::2],self.pos.reshape(2*self.N)[1::2])vec = self.pos + 10*self.vel/self.maxVelbeak.set_data(vec.reshape(2*self.N)[::2],vec.reshape(2*self.N)[1::2])def limitVec(self, vec, maxVal):"""limit magnitide of 2D vector"""mag = norm(vec)if mag > maxVal:vec[0], vec[1] = vec[0]*maxVal/mag, vec[1]*maxVal/magdef limit(self, X, maxVal):"""limit magnitide of 2D vectors in array X to maxValue"""for vec in X:self.limitVec(vec, maxVal)def applyBC(self):"""apply boundary conditions"""deltaR = 2.0for coord in self.pos:if coord[0] > width + deltaR:coord[0] = - deltaRif coord[0]  height + deltaR:coord[1] = - deltaRif coord[1] < - deltaR:coord[1] = height + deltaRdef applyRules(self):# apply rule #1 - SeparationD = self.distMatrix < 25.0vel = self.pos*D.sum(axis=1).reshape(self.N, 1) - D.dot(self.pos)self.limit(vel, self.maxRuleVel)# different distance thresholdD = self.distMatrix < 50.0# apply rule #2 - Alignmentvel2 = D.dot(self.vel)self.limit(vel2, self.maxRuleVel)vel += vel2;# apply rule #1 - Cohesionvel3 = D.dot(self.pos) - self.posself.limit(vel3, self.maxRuleVel)vel += vel3return veldef buttonPress(self, event):"""event handler for matplotlib button presses"""# left click - add a boidif event.button is 1:self.pos = np.concatenate((self.pos,np.array([[event.xdata, event.ydata]])),axis=0)# random velocityangles = 2*math.pi*np.random.rand(1)v = np.array(list(zip(np.sin(angles), np.cos(angles))))self.vel = np.concatenate((self.vel, v), axis=0)self.N += 1# right click - scatterelif event.button is 3:# add scattering velocityself.vel += 0.1*(self.pos - np.array([[event.xdata, event.ydata]]))def tick(frameNum, pts, beak, boids):#print frameNum"""update function for animation"""boids.tick(frameNum, pts, beak)return pts, beak# main() functiondef main():# use sys.argv if neededprint('starting boids...')parser = argparse.ArgumentParser(description="Implementing Craig Reynold's Boids...")# add argumentsparser.add_argument('--num-boids', dest='N', required=False)args = parser.parse_args()# number of boidsN = 100if args.N:N = int(args.N)# create boidsboids = Boids(N)# setup plotfig = plt.figure()ax = plt.axes(xlim=(0, width), ylim=(0, height))pts, = ax.plot([], [], markersize=10,c='k', marker='o', ls='None')beak, = ax.plot([], [], markersize=4,c='r', marker='o', ls='None')anim = animation.FuncAnimation(fig, tick, fargs=(pts, beak, boids),interval=50)# add a "button press" event handlercid = fig.canvas.mpl_connect('button_press_event', boids.buttonPress)plt.show()# call mainif __name__ == '__main__':main()

运行结果

将上述代码保存为文件“d:\temp\boids.py”。

在命令行窗口执行命令 “python d:\temp\boids.py”,运行结果如下: