前几天天气转暖,阳光暖暖的。虽然疫情依然严峻,但也阻挡不了一些人搬家的心。老板用技术告诉人们“怎么还没到出门的时间?”,然后老板就火了。图片来自Pexels。事情是这样的。B站UP主@eleLab,花了一个晚上写了一个简单的疫情传播模拟程序,告诉大家宅在家里的重要性。视频如下:GitHub地址如下:https://github.com/KikiLetGo/VirusBroadcast在家无聊的时候下载了代码研究了一下。这是一个分析。废话不多说,开始吧。源码结构源码结构比较简单,一起来看看:模型讲解我对仿真模型做了一个抽象和总结,我们对照源码分析了模型的整个仿真过程和思路。模型前提设定首先假设C(400,400)为城市中心,整个城市是以C为中心的圆,L=100为圆的半径。假设P(x,y)代表城市中的人,受疫情影响的人处于不同状态S:S.NORMAL=0:正常。S.SUSPECTED=1:怀疑。S.SHADOW=2:病毒携带潜伏者。S.CONFIRMED=3:确认。S.FREEZE=4:隔离。S.CURED=5:治愈。对应感染者,分别为确诊者设置infectedTime(感染时间)和confirmTime(确诊时间)。其次,假设医院是一个高为H、宽为W的矩形区域,其中矩形左下角的坐标为H(800,110)。为了表示医院容量的大小,我们设H=606为常数,那么W越大,表示医院的容量越大(即床位越多);床。最后我们必须设置一些启动参数:intORIGINAL_COUNT=50:初始感染数。floatBROAD_RATE=0.8f:传播率。floatSHADOW_TIME=140:延迟时间。intHOSPITAL_RECEIVE_TIME=10:入院响应时间。intBED_COUNT=1000:病床。floatu=0.99f:流意向的平均值。模型启动初始化模型启动时,我们在一个以C为圆心,L为半径的圆内随机生成5000个P:/***以(400,400)为城市中心,半径100个单位内,*5000人中的伪随机(近似正态分布);*如果人的x轴坐标超过700,则按700计算(为了限制在一定范围内)*/privatePersonPool(){Citycity=newCity(400,400);for(inti=0;i<5000;i++){/***random.nextGaussian()*返回一个伪随机(近似)正态分布的double,均值为0.0,标准差为1。*/Randomrandom=newRandom();intx=(int)(100*random.nextGaussian()+city.getCenterX());inty=(int)(100*random.nextGaussian()+city.getCenterY());if(x>700){x=700;}Personperson=newPerson(city,x,y);personList.add(person);}}并根据ORIGINAL_COUNT=50:初始感染数,初始化50个感染者(statusisS.SHADOW'sP):Listpeople=PersonPool.getInstance().getPersonList();for(inti=0;i0;}P(x1,y1)在第一次流动时会随机生成一个T(x2,y2)的目的地,T被限制在以P为中心的一定范围内。那么P是如何流向T的呢?这不是直接简单的moveTo(T)。为了更真实地模拟实际情况,P实际上是在逐渐接近T。假设D是P和T之间的距离,那么D=sqrt(pow(x1-x2,2)+pow(y1-y2,2)):如果D<1,则认为P已经到达T。如果D>1,则下一次P到达坐标[(x2-x1)/|x2-x1|,(y2-y1)/|y2-y1|],其实超过了-1,还没有达到+1,难道P到达目的地后就不动了吗?不是,P到达目的地后,会随机生成下一个目的地,然后用同样的算法向目的地逼近。privatevoidaction(){//隔离,不能动作if(state==State.FREEZE){return;}//不想动,不能动作if(!wantMove()){return;}//如果我还没有行动,或者target已经到了,那么随机生成下一个targettargetXU;doubletargetY=targetSig*newRandom().nextGaussian()+targetYU;moveTarget=newMoveTarget((int)targetX,(int)targetY);}/***dX:目标位置与目标位置的相对x轴坐标差当前位置*dY:目标位置与当前位置的差值相对y轴坐标差*length:目标与当前位置的距离*/intdX=moveTarget.getX()-x;intdY=moveTarget.getY()-y;doublelength=Math.sqrt(Math.pow(dX,2)+Math.pow(dY,2));//如果目标位置与当前位置的误差在1步以内,则认为已经到达目的地if(length<1){moveTarget.setArrived(true);返回;}//否则,减小每次移动的步长,控制在(1,平方根2)以内intudX=(int)(dX/length);if(udX==0&&dX!=0){if(dX>0){udX=1;}else{udX=-1;}}intudY=(int)(dY/length);if(udY==0&&dY!=0){if(dY>0){udY=1;}else{udY=-1;}}//如果当前位置超出边界,重新规划目的地,返回udx步if(x>700){moveTarget=null;if(udX>0){udX=-udX;}}moveTo(udX,udY);}②模拟病毒传播入院因为有没有病毒感染,有没有隔离病毒,其实跟人有关系,所以模拟病毒传播其实就是一个P状态模拟这里有一个转换的前提条件:设置worldTime代表当前时刻,初始化为0,每次刷新JPanel,worldTime+1。如果S=S.FREEZE,那么P已经入院隔离。状态未更新。如果S=S.CONFIRMED,且worldTime-confirmedTime>=Constants.HOSPITAL_RECEIVE_TIME,即P已经确诊,且距确诊时间超过医院响应时间,则说明P应该入院。但如果医院有床位,将P(x1,y1)移动到B(x2,y2),表示已经入住;如果医院没有床位,则P(x1,y1)无法容纳,仍参与人员流动过程。如果S=S.SHADOW,且worldTime-infectedTime>Constants.SHADOW_TIME,即P为感染者,且感染期超过潜伏期,则此时状态应改为CONFIRMED。状态转换清楚了,还有一个问题,正常人是怎么感染的?这个跟两个参数有关:BROAD_RATE,这个就是我们上面说的传输速率参数,表示一个人是否被感染有一定的概率。SAFE_DIST表示正常人与疑似/感染/确诊者之间的安全距离。当概率随机值超过BROAD_RATE,且正常人与疑似/感染/确诊者的距离小于SAFE_DIST时,正常人将被视为感染者,stateS=S.SHADOW(latent):publicvoidupdate(){//隔离,不更新状态if(state>=State.FREEZE){return;}//如果确诊超过医院响应时间,则说明确诊者已经被隔离到医院if(state==State.CONFIRMED&&MyPanel.worldTime-confirmedTime>=Constants.HOSPITAL_RECEIVE_TIME){Bedbed=Hospital.getInstance().pickBed();if(bed==null){System.out.println("没有可用的bedsintheisolationarea");}else{//isolatedstate=State.FREEZE;x=bed.getX();y=bed.getY();bed.setEmpty(false);}}//如果被感染时间超过潜伏期,潜伏者将被确诊,确诊时间为当前时间if(MyPanel.worldTime-infectedTime>Constants.SHADOW_TIME&&state==State.SHADOW){state=State.CONFIRMED;confirmedTime=MyPanel.worldTime;}action();Listpeople=PersonPool.getInstance().personList;if(state>=State.SHADOW){return;}for(Personperson:people){if(person.getState()==State.NORMAL){continue;}/***Random().nextFloat()*用于从thispseudo-randomnumbergenerator*/floatrandom=newRandom().nextFloat();//随机浮点值小于Transmissionrate,且与感染者的安全距离小于SAFE_DIST时,这个人不会被感染if(random