病毒擴散仿真java程序,仿真模擬新冠肺炎病毒擴散

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:

/** 
     * 以(400,400)爲城市中心,在方圓100單位長度以內, 
     * 僞隨機(近似正態分佈)出5000人; 
     * 如果person的x軸座標超過了700,則就按700算(爲了限制到一定範圍內) 
     */ 
    private PersonPool() { 
        City city = new City(400,400); 
        for (int i = 0; i < 5000; i++) { 
            /** 
             * random.nextGaussian() 
             * 返回均值0.0和標準差1的僞隨機(近似)正態分佈的double。 
             */ 
            Random random = new Random(); 
            int x = (int) (100 * random.nextGaussian() + city.getCenterX()); 
            int y = (int) (100 * random.nextGaussian() + city.getCenterY()); 
            if(x>700){ 
                x=700; 
            } 
            Person person = new Person(city,x,y); 
            personList.add(person); 
        } 
    } 

並根據 ORIGINAL_COUNT=50:初始感染數量,初始化 50 個感染者(狀態爲 S.SHADOW 的 P):

List<Person> people = PersonPool.getInstance().getPersonList(); 
        for(int i=0;i<Constants.ORIGINAL_COUNT;i++){ 
            //生成人口規模範圍內的隨機整數 
            int index = new Random().nextInt(people.size()-1); 
            Person person = people.get(index); 
            //避免隨機值碰撞 
            while (person.isInfected()){ 
                index = new Random().nextInt(people.size()-1); 
                person = people.get(index); 
            } 
            //生成感染者 
            person.beInfected(); 
        } 

模型運行

啓動之後模型就開始模擬人員流動,模擬病毒隨人羣如何傳播,以及醫院如何收治,我這裏着重講解一下。

①模擬人員流動

首先要知道,P 是否流動與 P 的狀態 S 和流動意願值有關係,如果 S=S.FREEZE(也即被醫院隔離)則無法流動,如果 P 不想動則也不會流動。其中這裏流動意願值如何計算的呢?

個人流動意願值=流動意向平均值+隨機流動意向:

public boolean wantMove(){ 
       //流動意向平均值+隨機流動意向 
       double value = sig*new Random().nextGaussian()+Constants.u; 
       return value>0; 
   } 

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 到達目的地後會在隨機產生下一個目的地,然後以同樣的算法趨近目的地。

private void action(){ 
        //已隔離,無法行動 
        if(state==State.FREEZE){ 
            return; 
        } 
        //不想動,也無法行動 
        if(!wantMove()){ 
            return; 
        } 
        //如果還沒有行動過,或者目標地已經到達,則重新隨機產生下一個目標地 
        if(moveTarget==null||moveTarget.isArrived()){ 
 
            double targetX = targetSig*new Random().nextGaussian()+targetXU; 
            double targetY = targetSig*new Random().nextGaussian()+targetYU; 
            moveTarget = new MoveTarget((int)targetX,(int)targetY); 
 
        } 
 
        /** 
         * dX : 目標地與當前位置的相對x軸座標差 
         * dY : 目標地與當前位置的相對y軸座標差 
         * length : 目標地與當前位置的距離 
         */ 
        int dX = moveTarget.getX()-x; 
        int dY = moveTarget.getY()-y; 
        double length=Math.sqrt(Math.pow(dX,2)+Math.pow(dY,2)); 
        //如果目標地與當前位置誤差在1步長內,則視爲已經到達目的地 
        if(length<1){ 
            moveTarget.setArrived(true); 
            return; 
        } 
        //否則,縮小每次移動的步長,控制在(1,根號2)以內 
        int udX = (int) (dX/length); 
        if(udX==0&&dX!=0){ 
            if(dX>0){ 
                udX=1; 
            }else{ 
                udX=-1; 
            } 
        } 
        int udY = (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 的狀態 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(潛伏者):

public void update(){ 
        //已隔離,狀態不更新 
        if(state>=State.FREEZE){ 
            return; 
        } 
        //若已確診時長超過醫院反應時間,則表示此確診者已被隔離到醫院 
        if(state==State.CONFIRMED&&MyPanel.worldTime-confirmedTime>=Constants.HOSPITAL_RECEIVE_TIME){ 
            Bed bed = Hospital.getInstance().pickBed(); 
            if(bed==null){ 
                System.out.println("隔離區沒有空牀位"); 
            }else{ 
                //被隔離起來了 
                state=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(); 
 
        List<Person> people = PersonPool.getInstance().personList; 
        if(state>=State.SHADOW){ 
            return; 
        } 
       for(Person person:people){ 
           if(person.getState()== State.NORMAL){ 
               continue; 
           } 
           /** 
            * Random().nextFloat() 
            * 用於獲取下一個從這個僞隨機數生成器的序列中均勻分佈的0.0和1.0之間的float值 
            */ 
           float random = new Random().nextFloat(); 
           //隨機float值小於傳播率,且與感染者安全距離小於SAFE_DIST時,此人就會別感染 
           if(random<Constants.BROAD_RATE&&distance(person)<SAFE_DIST){ 
               this.beInfected(); 
           } 
       } 
    } 

調節參數來模擬效果

我們上面提到了啓動仿真所需的那些參數:

public class Constants { 
    public static int ORIGINAL_COUNT=50;//初始感染數量 
    public static float BROAD_RATE = 0.8f;//傳播率 
    public static float SHADOW_TIME = 140;//潛伏時間 
    public static int HOSPITAL_RECEIVE_TIME=10;//醫院收治響應時間 
    public static int BED_COUNT=1000;//醫院牀位 
    public static float u=0.99f;//流動意向平均值 
} 

根據模擬效果可以明顯看出來,流動意願平均值是一個很重要的參數,即使是傳播率較大,醫院資源緊缺,潛伏期較長的情況下,只要大家都不出門,有效控制人羣流動,那麼疫情很快就可以被消滅。

所以“防疫的中堅力量其實是廣大的人民羣衆,忍一時風平浪靜,別在往出去跑給國家添麻煩了!”

模型優化

其實這個模型並不複雜,簡單總結一下:

  • 這裏模擬的是一個城市,且城市模型是理想化的。
  • 人羣分佈是僞隨機正態分佈的。
  • 人的流動模型很簡單,就是一個點向另一個點以小步長趨近。
  • 病毒傳播模型就是根據一定概率加上安全距離的限定來模擬人傳人。
  • 醫院收治模型就是根據感染時長和確診時長來模擬收治。

針對這幾個點,想到的優化思路:

  • 多個城市中心(這也是程序作者的意見之一)。
  • 人羣分佈可以調參,可以根據實際情況來確定分佈密度。
  • 在加上收治病人治癒出院的情況,更加符合實際。
  • 病毒傳染更加科學準確的模型(因爲一個人染上病是多方面因素的綜合疊加),作者嘗試使用DNN對病毒進行建模(包括病毒變異)。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章