病毒扩散仿真程序火了,其实模型很简单!
熊饲   掘金   2020-02-10

明天几角,天道回暖,阳光和煦,虽然疫情依然严重,但是已经震慑不住某些人蠢蠢欲动的心扉了,这不 B 站上的一大佬就用艺术来告诉人们 “为什么现在还没到出门的时节?”,下一场这位大佬就火了。

 

图表来自 Pexels

作业是这样的,B 站 UP 东道主 @ele候车室,用了一夜的年华,写了一番简单的灾情传播仿真程序,告知大家在学者待着的重大,视频如下:

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(把感染的时候)和 confirmedTime(确诊的时候)。

    从,假设医院是高为 H,宽敞为 W 的椭圆形区域,其中矩形左下角坐标为 H(800,110)。

    为了表示医院容量的大小,咱们把 H=606 设为常量,则 W 越大表示医院的可容纳量越大(也即床位越多);下一场,假设 B(x,y) 就表示位于医院内的床位。

     

    说到底我们要设置一些启动参数: 

  • int ORIGINAL_COUNT=50:初步感染数量。
  • float BROAD_RATE=0.8f:传播率。
  • float SHADOW_TIME=140:隐身时间。
  • int HOSPITAL_RECEIVE_TIME=10:诊所收治响应时间。
  • int BED_COUNT=1000:诊所床位。
  • float u=0.99f:流动意向平均值。
  • 模型启动初始化

    模型启动时,咱们在以 C 为骨干 L 为半径的圆内随机产生 5000 个 P:

         
    1. /** 
    2.      * 以(400,400)为都市中心,在四周100单位长度以内, 
    3.      * 伪随机(近似正态分布)出5000人口; 
    4.      * 如果person的x轴坐标超过了700,则就按700算(为了限制到一定范围内) 
    5.      */ 
    6.     private PersonPool() { 
    7.         City city = new City(400,400); 
    8.         for (int i = 0; i < 5000; i++) { 
    9.             /** 
    10.              * random.nextGaussian() 
    11.              * 回到均值0.0和专业差1的伪随机(近似)正态分布的double。 
    12.              */ 
    13.             Random random = new Random(); 
    14.             int x = (int) (100 * random.nextGaussian() + city.getCenterX()); 
    15.             int y = (int) (100 * random.nextGaussian() + city.getCenterY()); 
    16.             if(x>700){ 
    17.                 x=700; 
    18.             } 
    19.             Person person = new Person(city,x,y); 
    20.             personList.add(person); 
    21.         } 
    22.     } 

    并根据 ORIGINAL_COUNT=50:初步感染数量,初始化 50 个感染者(状态为 S.SHADOW 的 P):

         
    1. List<Person> people = PersonPool.getInstance().getPersonList(); 
    2.         for(int i=0;i<Constants.ORIGINAL_COUNT;i++){ 
    3.             //浮动人口规模范围内的随机整数 
    4.             int index = new Random().nextInt(people.size()-1); 
    5.             Person person = people.get(index); 
    6.             //避免随机值碰撞 
    7.             while (person.isInfected()){ 
    8.                 index = new Random().nextInt(people.size()-1); 
    9.                 person = people.get(index); 
    10.             } 
    11.             //浮动感染者 
    12.             person.beInfected(); 
    13.         } 

    模型运行

    起先后模型就开始模拟人员流动,宪章病毒随人群如何传播,以及医院如何收治,我这里根本讲解一下。

    ①宪章人员流动

    第一要掌握,P 只是流动与 P 的状态 S 和流动意愿值有联系,如果 S=S.FREEZE(也即把医院隔离)则无从流动,如果 P 不想动则也不会流动。其中这里流动意愿值如何计算的呢?

    个体流动意愿值=流动意向平均值+随机流动意向:

         
    1. public boolean wantMove(){ 
    2.        //流动意向平均值+随机流动意向 
    3.        double value = sig*new Random().nextGaussian()+Constants.u; 
    4.        return value>0; 
    5.    } 

    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 抵达目的地后会在随机产生下一个基地,下一场以同样的作法趋近目的地。 

         
    1. private void action(){ 
    2.         //已隔离,无法行动 
    3.         if(state==State.FREEZE){ 
    4.             return
    5.         } 
    6.         //不想动,也无从行动 
    7.         if(!wantMove()){ 
    8.             return
    9.         } 
    10.         //如果还没有行动过,或者目标地已经达到,则重新随机产生下一个目标地 
    11.         if(moveTarget==null||moveTarget.isArrived()){ 
    12.  
    13.             double targetX = targetSig*new Random().nextGaussian()+targetXU; 
    14.             double targetY = targetSig*new Random().nextGaussian()+targetYU; 
    15.             moveTarget = new MoveTarget((int)targetX,(int)targetY); 
    16.  
    17.         } 
    18.  
    19.         /** 
    20.          * dX : 目标地与目前位置的相对x轴坐标差 
    21.          * dY : 目标地与目前位置的相对y轴坐标差 
    22.          * length : 目标地与目前位置的距离 
    23.          */ 
    24.         int dX = moveTarget.getX()-x; 
    25.         int dY = moveTarget.getY()-y; 
    26.         double length=Math.sqrt(Math.pow(dX,2)+Math.pow(dY,2)); 
    27.         //如果目标地与目前位置误差在1大幅度内,则视为已经达到目的地 
    28.         if(length<1){ 
    29.             moveTarget.setArrived(true); 
    30.             return
    31.         } 
    32.         //否则,缩小每次移动的幅度,控制在(1,根号2)以内 
    33.         int udX = (int) (dX/length); 
    34.         if(udX==0&&dX!=0){ 
    35.             if(dX>0){ 
    36.                 udX=1; 
    37.             }else
    38.                 udX=-1; 
    39.             } 
    40.         } 
    41.         int udY = (int) (dY/length); 
    42.         if(udY==0&&dY!=0){ 
    43.             if(dY>0){ 
    44.                 udY=1; 
    45.             }else
    46.                 udY=-1; 
    47.             } 
    48.         } 
    49.         //如果当前位置已经超出边界,则重新规划目的地,并往回走udx个增幅 
    50.         if(x>700){ 
    51.             moveTarget=null
    52.             if(udX>0){ 
    53.                 udX=-udX; 
    54.             } 
    55.         } 
    56.         moveTo(udX,udY); 
    57.     } 

    ②宪章病毒传播与医院收治

    因为有没有感染艾滋病毒,有没有隔离病毒,其实都是和人有联系,故此模拟病毒传播其实就是人云亦云 P 的状态 S 的浮动。

    此地有一度前提说明:安装 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 时,好人会把成为感染者,状态 S=S.SHADOW(潜伏者):

         
    1. public void update(){ 
    2.         //已隔离,状态不更新 
    3.         if(state>=State.FREEZE){ 
    4.             return
    5.         } 
    6.         //若已确诊时长超过医院反应时间,则表示此确诊者已把隔离到医院 
    7.         if(state==State.CONFIRMED&&MyPanel.worldTime-confirmedTime>=Constants.HOSPITAL_RECEIVE_TIME){ 
    8.             Bed bed = Hospital.getInstance().pickBed(); 
    9.             if(bed==null){ 
    10.                 System.out.println("隔离区没有空床位"); 
    11.             }else
    12.                 //把隔离起来了 
    13.                 state=State.FREEZE; 
    14.                 x=bed.getX(); 
    15.                 y=bed.getY(); 
    16.                 bed.setEmpty(false); 
    17.             } 
    18.         } 
    19.         //若已感染时长超过同期,则潜伏者就会确诊,确诊时间就是目前时间 
    20.         if(MyPanel.worldTime-infectedTime>Constants.SHADOW_TIME&&state==State.SHADOW){ 
    21.             state=State.CONFIRMED; 
    22.             confirmedTime = MyPanel.worldTime; 
    23.         } 
    24.  
    25.         action(); 
    26.  
    27.         List<Person> people = PersonPool.getInstance().personList; 
    28.         if(state>=State.SHADOW){ 
    29.             return
    30.         } 
    31.        for(Person person:people){ 
    32.            if(person.getState()== State.NORMAL){ 
    33.                continue
    34.            } 
    35.            /** 
    36.             * Random().nextFloat() 
    37.             * 用于获取下一个下这个伪随机数生成器的队中均匀分布的0.0和1.0之间的float值 
    38.             */ 
    39.            float random = new Random().nextFloat(); 
    40.            //随机float值小于传播率,且与感染者安全距离小于SAFE_DIST时,此人就会别感染 
    41.            if(random<Constants.BROAD_RATE&&distance(person)<SAFE_DIST){ 
    42.                this.beInfected(); 
    43.            } 
    44.        } 
    45.     } 

    调整参数来模拟效果

    咱们上面提到了启动仿真所需的这些参数:

         
    1. public class Constants { 
    2.     public static int ORIGINAL_COUNT=50;//初步感染数量 
    3.     public static float BROAD_RATE = 0.8f;//传播率 
    4.     public static float SHADOW_TIME = 140;//隐身时间 
    5.     public static int HOSPITAL_RECEIVE_TIME=10;//诊所收治响应时间 
    6.     public static int BED_COUNT=1000;//诊所床位 
    7.     public static float u=0.99f;//流动意向平均值 

    根据宪章效果可以明确看出来,流动意愿平均值是一番很重大的底数,即使是传率较大,诊所资源缺乏,首期较长的情况下,只要大家都不出门,使得控制人群流动,这就是说疫情很快就足以把消灭。

    故此“防治的主导力量其实是周边的平民大众,忍一时风平浪静,别在往出去跑给国家添麻烦了!”

    模型优化

    其实这个模型并不复杂,大概总结一下:

  • 此地模拟的是一番城市,且城市模型是幻想的。
  • 人流分布是伪随机正态分布的。
  • 人口之流动模型很简单,就是一番线向另一番线以小幅度趋近。
  • 病毒传播模型就是根据一定概率加上安全距离的限定来模拟人传人。
  • 诊所收治模型就是根据感染时长和确诊时长来模拟收治。
  • 针对这几个点,想到的僵化思路:

  • 多个城市中心(这也是程序作者的观点之一)。
  • 人流分布可以调参,可以根据现实状况来确定分布密度。
  • 在加上收治病人治愈出院的状况,更加符合实际。
  • 病毒传染更加科学准确的模子(因为一个人口染上病是多地方因素的归纳叠加)。
  • 笔者:熊饲

    编纂:陶家龙、孙淑娟

    出处:https://juejin.im/post/5e3ab6d3518825491d320a38

     

    【编纂推荐】

    1. 程序员硬核劝告:如今还不是出门的时节
    2. 用Python执行程序的4种方法,编程必备
    3. 跟我学 “Linux” 小程序Web版开发(五):赶上的组成部分坑
    4. 程序员经典面试题,信息队列怎么用,才能保证万无一失
    5. 数量模型分析报告你,如今还不是出门的时节!
    【义务编辑: 武晓燕 TEL:(010)68476606】

     

    分享到朋友圈 分享到微博
  • 病毒
  • 先后
  • 模型
  • 相关推荐

    如何从SaaS使用程序查询和索取数据?

    2020-02-12 08:00:44

    高考:ZooKeeper二十三连问,探望你能不能接住

    2020-02-11 20:01:44

    不安全的Docker守护程序中攻击者的方针与艺术

    2020-02-11 19:27:22

    Copyright © 2005-2020 51CTO.COM 必发娱乐登入
    情节话题
    必发娱乐登入 移步 传感器 系统 安全 网络 必发娱乐登录 虚拟化 付出
    热门产品
    51CTO必发娱乐登录 51CTO高招 移步开发者服务联盟网+ 51CTO博客 WOT碰头会