基於 Python 的新冠病毒傳播仿真器!


前幾天在B站看見某up主用java編寫了一個病毒擴散仿真器,當時就在尋思用Python它不香嗎?於是說幹就幹!


文件目錄展示

本項目GUI部分是用PyQt5實現,並且使用了正態分佈模擬群體分佈以及群體運動軌跡。

基於 Python 的新冠病毒傳播仿真器!

在這裡插入圖片描述

演示成果

仿真器可以對多個數據進行模擬,包括健康者人數、潛伏期人數、發病者人數、已經隔離的人數、已經死亡的人數、空餘床位、繼續床位、病毒傳播率、病毒潛伏期、醫院收治響應時間、醫院當前床位、安全距離、平均流動意向。

運行run.py,如果不進行設置,程序會利用初始值進行模擬,初始發病人數為50人,群體數為5000人。

基於 Python 的新冠病毒傳播仿真器!

在這裡插入圖片描述

中間區域的若干個點表示處於各種狀態的群體,白色的表示健康、黃色表示潛伏期、紅色表示發病、黑色表示死亡。右側的豎條表示醫院的床位,初始值是100。如果用參數值進行模擬,100張床位很快就會被填滿,結果顯示不到3個月病毒在人群中就會大爆發,很快紅點就會遍佈人群,如下圖所示:

基於 Python 的新冠病毒傳播仿真器!

在這裡插入圖片描述

通過改變參數來模擬相應的措施,將床位數適當擴大,流動意向設置為負數

基於 Python 的新冠病毒傳播仿真器!

在這裡插入圖片描述

可以看到不到8個月的時間疫情徹底結束,當然最後得到的結果取決於設置的參數,千萬不要覺得很詫異。

基於 Python 的新冠病毒傳播仿真器!

在這裡插入圖片描述

代碼的實現

主要說下如何繪製市民的狀態,繪製的工作通過drawing.py文件的Drawing類來完成。該類是QWidget的子類,這也就意味著Drawing類本身是PyQt5的一個組件。與按鈕、標籤類似。只是並不需要往Drawing上放置任何子組件,只要在Drawing上繪製各種圖形即可。Drawing類中paintEvent方法的代碼如下:

<code>def paintEvent(self, event):
    qp = QPainter()
    qp.begin(self)
    # 繪製城市的各種狀態的市民
    self.drawing(qp)
    qp.end()/<code>

在繪製圖像前,需要創建QPainter對象,然後調用QPainter對象的begin方法,結束繪製後,需要調用QPainter對象的end方法。代碼中的drawing方法用於完成具體的繪製工作。仿真器可以模擬5000個市民的狀態,所以需要用5000個小矩形來表示這5000個市民。也就是在drawing方法中需要繪製這5000個表示市民的小矩形。代碼如下:

<code>def drawing(self, event):
       ... ...
        # 繪製代表市民的小矩形
    persons = Persons().persons
    if persons == None:
        return
    normal_person_count = 0
    latency_person_count = 0

    confirmed_person_count = 0
    freeze_person_count = 0
    death_person_count = 0
            # 掃描內一個人的狀態
    for person in persons:
        if person.state == NORMAL:
            # 健康人
            qp.setPen(Qt.white)
            normal_person_count += 1
        elif person.state == LATENCY:
            # 潛伏期感染者
            qp.setPen(QColor(255,238,0))
            latency_person_count += 1
        elif person.state == CONFIRMED:
            # 確診患者
            qp.setPen(Qt.red)
            confirmed_person_count += 1
        elif person.state == FREEZE:
            # 已隔離者
            qp.setPen(QColor(72, 255, 252))
            freeze_person_count += 1
        elif person.state == DEATH:
            # 死亡患者
            qp.setPen(Qt.black)
            death_person_count += 1
        person.update()   # 更新每一個人的狀態
        bed_half_size = Hospital().bed_size // 2
        rect = QRect(person.x - bed_half_size, person.y - bed_half_size,Hospital().bed_size//2, Hospital().bed_size//2)
        brush = QBrush(Qt.SolidPattern)
        brush.setColor(qp.pen().color())

        qp.setBrush(brush)
        qp.drawRect(rect)
            ... .../<code>

在上面的代碼中,通過Persons對象的persons屬性獲取表示市民的對象(Person對象)列表。並在循環中根據Person對象的狀態設置小矩形的顏色,以及分別統計不同人群的數量,這些數量會顯示在仿真器右側的組件中。最後,使用drawRect方法繪製表示每一個市民的小矩形。這樣就繪製了當前狀態的5000個市民。當然,這些狀態要不斷更新。這裡使用線程每100毫秒刷新一次,這些功能在refresh.py文件的Refresh類中,代碼如下:

<code>from PyQt5.QtCore import *
from params import  *

class Refresh(QThread):
    def __init__(self, drawing):
        super(Refresh, self).__init__()
        self.drawing = drawing
    def run(self):
        while not Params.success:
            try:
                QThread.msleep(100)
                # 刷新Drawing
                self.drawing.update()
                Params.current_time += 1
            except:
                pass/<code>

每次刷新Drawing,需要調用update方法,調用該方法後,Drawing就會調用自身的paintEvent方法重新繪製整個組件的內容。在paintEvent方法中,還調用了Person對象的update方法,用於不斷更新每一個人的狀態,這些狀態會根據多個參數進行協調。該方法屬於Person類,代碼如下:

<code>    def update(self):
        # 如果已經隔離或者死亡了,就不需要處理了
        if self.state == FREEZE or self.state == DEATH:
            return
        # 處理已經確診的感染者(即患者)
        if self.state == CONFIRMED and self.dead_time == 0:
            destiny = random.randrange(1,10001)  # 幸運數字,[1,10000]隨機數
            if destiny >= 1 and destiny <= int(Params.fatality_rate * 10000):
                # 幸運數字落在死亡區間
                dt = int(sp.random.normal(Params.dead_time,Params.dead_variance))
                self.dead_time = self.confirmed_time + self.dead_time
            else:
                self.dead_time = -1   # 逃過了死神的魔爪

        if self.state == CONFIRMED and Params.current_time - self.confirmed_time >= Params.hospital_receive_time:

            # 如果患者已經確診,且(世界時刻-確診時刻)大於醫院響應時間,即醫院準備好病床了,可以抬走了
            bed = Hospital().pick_bed()  # 查找空床位
            if bed == None:
                # 沒有空床位,報告需求床位數
                if not self.need_bed:
                    Hospital().need_bed_count += 1
                    self.need_bed = True

            else:
                # 安置病人
                self.used_bed = bed
                self.state = FREEZE
                self.x = bed.x + Hospital().bed_size // 2
                self.y = bed.y +  Hospital().bed_size // 2
                if self.need_bed and Hospital().need_bed_count > 0:
                    Hospital().need_bed_count -= 1
                bed.is_empty = False
        # 處理病死者
        if (self.state == CONFIRMED or self.state == FREEZE) and Params.current_time >= self.dead_time and self.dead_time > 0:
            self.state = DEATH                          # 患者死亡
            personpool.Persons().latency_persons.remove(self)      # 已經死亡,無法傳染別人,需要從確診者中刪除
            Hospital().empty_bed(self.used_bed)         # 騰出床位
            if Hospital().need_bed_count > 0:
                Hospital().need_bed_count -= 1

        # 增加一個正態分佈用於潛伏期內隨機發病時間
        latency_symptom_time = sp.random.normal(Params.virus_latency / 2,25)

        # 處理發病的潛伏期感染者

        if Params.current_time - self.infected_time > latency_symptom_time and self.state == LATENCY:
            self.state = CONFIRMED                                  # 潛伏者發病
            self.confirmed_time = Params.current_time              # 刷新確診時間
        # 處理未隔離者的移動問題
        self.action()

        # 處理健康人被感染的問題



        persons = personpool.Persons().persons

        # 不是健康人,返回
        if self.state >= LATENCY:
            return
        # 通過一個隨機幸運值和安全距離決定感染其他人
        latency_persons = personpool.Persons().latency_persons.copy()
        for person in latency_persons:
            random_value = random.random()
            if random_value                 self.be_infected()
                break/<code>

update方法主要就是根據在params.py中的各種參數變量,以及隨機值,計算下一次狀態中潛伏期人數、感染人數、被隔離人數等數據,並且在每次刷新頁面時更新這些數據。


分享到:


相關文章: