基于 Python 的新冠病毒传播仿真器!

 

前几天在B站看见某up主用java编写了一个病毒扩散仿真器,当时就在寻思用Python它不香吗?于是说干就干!

文件目录展示

本项目GUI部分是用PyQt5实现,并且使用了正态分布模拟群体分布以及群体运动轨迹。

在这里插入图片描述

演示成果

仿真器可以对多个数据进行模拟,包括健康者人数、潜伏期人数、发病者人数、已经隔离的人数、已经死亡的人数、空余床位、继续床位、病毒传播率、病毒潜伏期、医院收治响应时间、医院当前床位、安全距离、平均流动意向。

运行run.py,如果不进行设置,程序会利用初始值进行模拟,初始发病人数为50人,群体数为5000人。

在这里插入图片描述

中间区域的若干个点表示处于各种状态的群体,白色的表示健康、黄色表示潜伏期、红色表示发病、黑色表示死亡。右侧的竖条表示医院的床位,初始值是100。如果用参数值进行模拟,100张床位很快就会被填满,结果显示不到3个月病毒在人群中就会大爆发,很快红点就会遍布人群,如下图所示:

在这里插入图片描述

通过改变参数来模拟相应的措施,将床位数适当扩大,流动意向设置为负数

在这里插入图片描述

可以看到不到8个月的时间疫情彻底结束,当然最后得到的结果取决于设置的参数,千万不要觉得很诧异。

在这里插入图片描述

代码的实现

主要说下如何绘制市民的状态,绘制的工作通过drawing.py文件的Drawing类来完成。该类是QWidget的子类,这也就意味着Drawing类本身是PyQt5的一个组件。与按钮、标签类似。只是并不需要往Drawing上放置任何子组件,只要在Drawing上绘制各种图形即可。Drawing类中paintEvent方法的代码如下:

def paintEvent(self, event):
    qp = QPainter()
    qp.begin(self)
    # 绘制城市的各种状态的市民
    self.drawing(qp)
    qp.end()

在绘制图像前,需要创建QPainter对象,然后调用QPainter对象的begin方法,结束绘制后,需要调用QPainter对象的end方法。代码中的drawing方法用于完成具体的绘制工作。仿真器可以模拟5000个市民的状态,所以需要用5000个小矩形来表示这5000个市民。也就是在drawing方法中需要绘制这5000个表示市民的小矩形。代码如下:

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)
            ... ...

在上面的代码中,通过Persons对象的persons属性获取表示市民的对象(Person对象)列表。并在循环中根据Person对象的状态设置小矩形的颜色,以及分别统计不同人群的数量,这些数量会显示在仿真器右侧的组件中。最后,使用drawRect方法绘制表示每一个市民的小矩形。这样就绘制了当前状态的5000个市民。当然,这些状态要不断更新。这里使用线程每100毫秒刷新一次,这些功能在refresh.py文件的Refresh类中,代码如下:

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

每次刷新Drawing,需要调用update方法,调用该方法后,Drawing就会调用自身的paintEvent方法重新绘制整个组件的内容。在paintEvent方法中,还调用了Person对象的update方法,用于不断更新每一个人的状态,这些状态会根据多个参数进行协调。该方法属于Person类,代码如下:

    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 < Params.broad_rate and self.distance(person) < Params.safe_distance:
                self.be_infected()
                break

update方法主要就是根据在params.py中的各种参数变量,以及随机值,计算下一次状态中潜伏期人数、感染人数、被隔离人数等数据,并且在每次刷新页面时更新这些数据。

源码加群:850591259

猜你喜欢

转载自www.cnblogs.com/Py1233/p/12626821.html