当前位置: 首页 > 后端技术 > Python

试试用Python编写的病毒扩散模拟器

时间:2023-03-26 11:17:07 Python

病毒扩散模拟器,python也可以。事情的概要如下。B站UP主@eleLab写了一个简单的疫情传播模拟程序,告诉大家宅在家里的重要性。视频相信大家都看过了,UP主也放出了源码。因为是Java开发的,所以一开始并没有太在意。后来看到有人分析代码,发现自己也能看懂,然后就想怎么用Python实现。Java版程序将一个人分析为一个(x,y)坐标点,每个人都有一个状态。publicclassPersonextendsPoint{privateintstate=State.NORMAL;}在每一轮迭代中,遍历每个人,每个人根据自己的状态采取一定的动作,包括:移动状态的变化影响其他人的动作具体变化取决于定义的各种系数。一轮迭代完成后,打印这些点,不同的状态对应不同的颜色。绘图部分直接使用的Java绘图类Graphics。Python版的思路如果我们想用Python来实现,应该怎么做呢?如果完全照搬Java版,每次迭代都需要遍历所有的人,计算与他人的距离,也就是N^2次计算。如果是1000人,需要循环100万次。这个Python的性能一定很着急。不过Python有numpy,可以快速操作数组。结合matplotlib,可以绘制图形。importnumpyasnpimportmatplotlib.pyplotasplt如何模拟人群为了减少函数间的互参传递和全局变量的使用,我们还定义了一个类:classPeople(object):def__init__(self,count=1000,first_infected_count=3):self.count=countself.first_infected_count=first_infected_countself.init()所有人的坐标数据是一个N行2列的数组,伴随着某种状态:definit(self):self._people=np.random.normal(0,100,(self.count,2))self.reset()状态值和定时器也是数组,同时每次随机选择指定人数感染:defreset(self):self._round=0self._status=np.array([0]*self.count)self._timer=np.array([0]*self.count)self.random_people_state(self.first_infected_count,1)关键这里的重点是,辅助数组的大小和人数是一致的,这样就可以形成一一对应的关系。状态改变的人顺便记录时间:defrandom_people_state(self,num,state=1):"""随机选择人设置状态"""assertself.count>num#TODO:在极端情况下,将发生无限循环n=0whilen14))]=self._roundself._status[(self._status==1)&((dt==d)|(dt>14))]+=1依然是通过切片筛选出要改变的目标,然后全部更新。这里的具体实现很简单,没有引入太多变量:将一定时期内感染者(infected)的状态设置为confirmed(确认)。我在这里简单地假设确诊者已经入院,因此我失去了继续感染他人的机会(见下文)。如果想复杂一点,可以引入病床、治愈、死亡等状态。如何影响他人是整个程序的性能瓶颈,因为需要计算每个人之间的距离。这里继续简化,只处理被感染者:definfect_possible(self,x=0.,safe_distance=3.0):"""x值按概率感染附近健康人参考正态分布概率表,x=0感染概率为50%"""infinself.infected:dm=(self._people-inf)**2d=dm.sum(axis=1)**0.5sorted_index=d.argsort()foriinsorted_index:ifd[i]>=safe_distance:break#超出范围,不用担心ifself._status[i]>0:continueifnp.random.normal()>x:continueself._status[i]=1#记录状态变化的时间self._timer[i]=self._round可以看出距离的计算仍然是通过numpy的矩阵运算。但是每个感染者都需要单独计算,所以如果感染者很多,python的处理效率还是很可观的。howtomove_people是一个坐标矩阵,直接生成移动距离矩阵dt,然后相加即可。我们可以设置一个可移动的范围宽度来控制移动距离在一定范围内。defmove(self,width=1,x=.0):movement=self.random_movement(width=width)#限制特定状态下人的移动switch=self.random_switch(x=x)movement[switch==0]=0self._people=self._people+movement这里还需要加一个option来控制movementintention,还是用的正态分布概率。考虑到这种场景可能会被复用,所以特地抽取出这个方法,生成一个只包含0和1的数组来充当开关。defrandom_switch(self,x=0.):"""随机生成一个开关,0-关,1-开。x的取值范围近似为-1.99-1.99;对应正态分布的概率,当取值为0,对应概率为50%:paramx:控制开关比例:return:"""normal=np.random.normal(0,1,self.count)switch=np.where(normal