Python 写了个新型冠状病毒疫情传播模拟程序


Posted in Python onFebruary 14, 2020

病毒扩散仿真程序,用 python 也可以。

概述

事情是这样的,B 站 UP 主 @ele 实验室,写了一个简单的疫情传播仿真程序,告诉大家在家待着的重要性,视频相信大家都看过了,并且 UP 主也放出了源码。

因为是 Java 开发的,所以开始我并没有多加关注。后来看到有人解析代码,发现我也能看懂,然后就琢磨用 Python 应该怎么实现。

Java 版程序浅析

一个人就是 1 个(x, y)坐标点,并且每个人有一个状态。

public class Person extends Point {
  private int state = State.NORMAL;
}

在每一轮的迭代中,遍历每个人,每个人根据自身的状态,做出一定的动作,包括:

  • 移动
  • 状态变化
  • 影响他人

这些动作的具体变更,取决于定义的各种系数。

一轮迭代完成,打印这些点,不同的状态对应不同的颜色。

绘图部分直接使用的 Java 绘图类 Graphics。

Python 版思路

如果我们想用 Python 实现应该怎么做呢?

如果完全复刻 Java 版本,则每次迭代需遍历所有人,并计算和他人距离,这就是 N^2 次计算。如果是 1000 个人,就需要循环 1 百万次。这个 Python 的性能肯定捉急。

不过 Python 有 numpy ,可以快速的操作数组。结合 matplotlib 则可以画出图形。

import numpy as np
import matplotlib.pyplot as plt

如何模拟人群

为了减少函数之间互相传参和使用全局变量,我们也来定义一个类:

class People(object):
  def __init__(self, count=1000, first_infected_count=3):
    self.count = count
    self.first_infected_count = first_infected_count
    self.init()

所有人的坐标数据就是 N 行 2 列的数组,同时伴随一定的状态:

def init(self):
    self._people = np.random.normal(0, 100, (self.count, 2))
    self.reset()

状态值和计时器也都是数组,同时每次随机选取指定数量的人感染:

def reset(self):
    self._round = 0
    self._status = np.array([0] * self.count)
    self._timer = np.array([0] * self.count)
    self.random_people_state(self.first_infected_count, 1)

这里关键的一点是,辅助数组的大小和人数保持一致,这样就能形成一一对应的关系。

状态发生变化的人才顺带记录时间:

def random_people_state(self, num, state=1):
    """随机挑选人设置状态
    """
    assert self.count > num
    # TODO:极端情况下会出现无限循环
    n = 0
    while n < num:
      i = np.random.randint(0, self.count)
      if self._status[i] == state:
        continue
      else:
        self.set_state(i, state)
        n += 1

  def set_state(self, i, state):
    self._status[i] = state
    # 记录状态改变的时间
    self._timer[i] = self._round

通过状态值,就可以过滤出人群,每个人群都是 people 的切片视图。这里 numpy 的功能相当强大,只需要非常简洁的语法即可实现:

@property
  def healthy(self):
    return self._people[self._status == 0]

  @property
  def infected(self):
    return self._people[self._status == 1]

按照既定的思路,我们先来定义每轮迭代要做的动作:

def update(self):
    """每一次迭代更新"""
    self.change_state()
    self.affect()
    self.move()
    self._round += 1
    self.report()

顺序和开始分析的略有差异,其实并不是十分重要,调换它们的顺序也是可以的。

如何改变状态

这一步就是更新状态数组 self._status 和 计时器数组 self._timer:

def change_state(self):
    dt = self._round - self._timer
    # 必须先更新时钟再更新状态
    d = np.random.randint(3, 5)
    self._timer[(self._status == 1) & ((dt == d) | (dt > 14))] = self._round
    self._status[(self._status == 1) & ((dt == d) | (dt > 14))] += 1

仍然是通过切片过滤出要更改的目标,然后全部更新。

这里具体的实现我写的非常简单,没有引入太多的变量:

在一定周期内的 感染者(infected),状态置为 确诊(confirmed)。 我这里简单假设了确诊者就被医院收治,所以失去了继续感染他人的机会(见下面)。如果要搞复杂点,可以引入病床,治愈,死亡等状态。

如何影响他人

影响别人是整个程序的性能瓶颈,因为需要计算每个人之间的距离。

这里继续做了简化,只处理感染者:

def infect_possible(self, x=0., safe_distance=3.0):
    """按概率感染接近的健康人
    x 的取值参考正态分布概率表,x=0 时感染概率是 50%
    """
    for inf in self.infected:
      dm = (self._people - inf) ** 2
      d = dm.sum(axis=1) ** 0.5
      sorted_index = d.argsort()
      for i in sorted_index:
        if d[i] >= safe_distance:
          break # 超出范围,不用管了
        if self._status[i] > 0:
          continue
        if np.random.normal() > x:
          continue
        self._status[i] = 1
        # 记录状态改变的时间
        self._timer[i] = self._round

可以看到,距离的计算仍然是通过 numpy 的矩阵操作。但是需要对每一个感染者单独计算,所以如果感染者较多,python 的处理效率感人。

如何移动

_people 是一个坐标矩阵,只要生成移动距离矩阵 dt,然后它相加即可。我们可以设置一个可移动的范围 width,把移动距离控制在一定范围内。

def move(self, width=1, x=.0):
    movement = self.random_movement(width=width)
    # 限定特定状态的人员移动
    switch = self.random_switch(x=x)
    movement[switch == 0] = 0
    self._people = self._people + movement

这里还需要增加一个控制移动意向的选项,仍然是利用了正态分布概率。考虑到这种场景有可能会重用,所以特地把这个方法提取了出来,生成一个只包含 0 1 的数组充当开关。

def random_switch(self, x=0.):
    """随机生成开关,0 - 关,1 - 开

    x 大致取值范围 -1.99 - 1.99;
    对应正态分布的概率, 取值 0 的时候对应概率是 50%
    :param x: 控制开关比例
    :return:
    """
    normal = np.random.normal(0, 1, self.count)
    switch = np.where(normal < x, 1, 0)
    return switch

输出结果

有了一切数据和变化之后,接下来最重要的事情自然就是图形化显示结果了。直接使用 matplotlib 的散点图就可以了:

   

def report(self):
    plt.cla()
    # plt.grid(False)
    p1 = plt.scatter(self.healthy[:, 0], self.healthy[:, 1], s=1)
    p2 = plt.scatter(self.infected[:, 0], self.infected[:, 1], s=1, c='pink')
    p3 = plt.scatter(self.confirmed[:, 0], self.confirmed[:, 1], s=1, c='red')

    plt.legend([p1, p2, p3], ['healthy', 'infected', 'confirmed'], loc='upper right', scatterpoints=1)
    t = "Round: %s, Healthy: %s, Infected: %s, Confirmed: %s" % \
      (self._round, len(self.healthy), len(self.infected), len(self.confirmed))
    plt.text(-200, 400, t, ha='left', wrap=True)

实际效果
启动。

if __name__ == '__main__':
  np.random.seed(0)
  plt.figure(figsize=(16, 16), dpi=100)
  plt.ion()
  p = People(5000, 3)
  for i in range(100):
    p.update()
    p.report()
    plt.pause(.1)
  plt.pause(3)

因为这个小 demo 主要是个人用来练手,目前一些参数没有完全抽出来。有需要的只能直接改源码。

Python 写了个新型冠状病毒疫情传播模拟程序

后记

从多次实验的结果,通过调整人员的流动意愿,流动距离等因素,是可以得到直观的结论的。

本人也是初次使用 numpy 和 matplotlib,现学现卖,若有使用不当之处请指正。其中的概率参数设置 基本没有科学依据,仅供 Python 爱好者参考。

总得来说,用 numpy 来模拟病毒感染情况应该是能行得通的。但是其中的影响因子还需要仔细设计。性能也是需要考量的问题。

源码地址

总结

以上所述是小编给大家介绍的Python 写了个新型冠状病毒疫情传播模拟程序,希望对大家有所帮助,也非常感谢大家对三水点靠木网站的支持!

Python 相关文章推荐
学习python (2)
Oct 31 Python
python中__slots__用法实例
Jun 04 Python
Python 的内置字符串方法小结
Mar 15 Python
python使用matplotlib绘制柱状图教程
Feb 08 Python
Python实现的选择排序算法原理与用法实例分析
Nov 22 Python
flask中使用蓝图将路由分开写在不同文件实例解析
Jan 19 Python
python机器人行走步数问题的解决
Jan 29 Python
删除DataFrame中值全为NaN或者包含有NaN的列或行方法
Nov 06 Python
Django密码存储策略分析
Jan 09 Python
Python基于gevent实现文件字符串查找器
Aug 11 Python
pycharm永久激活超详细教程
Oct 29 Python
python中HTMLParser模块知识点总结
Jan 25 Python
在pycharm中实现删除bookmark
Feb 14 #Python
python图形开发GUI库wxpython使用方法详解
Feb 14 #Python
解决Pycharm中恢复被exclude的项目问题(pycharm source root)
Feb 14 #Python
Python requests模块基础使用方法实例及高级应用(自动登陆,抓取网页源码)实例详解
Feb 14 #Python
Python实现名片管理系统
Feb 14 #Python
pycharm设置当前工作目录的操作(working directory)
Feb 14 #Python
python设置代理和添加镜像源的方法
Feb 14 #Python
You might like
纯php打造的tab选项卡效果代码(不用js)
2010/12/29 PHP
PHP json格式和js json格式 js跨域调用实现代码
2012/09/08 PHP
PHP文件操作实例总结
2016/09/27 PHP
PHP简单判断iPhone、iPad、Android及PC设备的方法
2016/10/11 PHP
yii 2.0中表单小部件的使用方法示例
2017/05/23 PHP
Thinkphp框架使用list_to_tree 实现无限级分类列出所有节点示例
2020/04/04 PHP
js 页面传参数时 参数值含特殊字符的问题
2009/12/13 Javascript
jQuery 借助插件Lavalamp实现导航条动态美化效果
2013/09/27 Javascript
js简单实现HTML标签Select联动带跳转
2013/10/23 Javascript
jQuery实现仿腾讯微博滑出效果报告每日天气的方法
2015/05/11 Javascript
JavaScript如何实现在文本框(密码框)输入提示语
2015/12/25 Javascript
去除html代码里面的script正则方法
2016/05/19 Javascript
微信开发之调起摄像头、本地展示图片、上传下载图片实例
2016/12/08 Javascript
JS html时钟制作代码分享
2017/03/03 Javascript
vue按需引入element Transfer 穿梭框
2017/09/30 Javascript
[01:43]3.19DOTA2发布会 三代刀塔人第三代
2014/03/25 DOTA
python 域名分析工具实现代码
2009/07/15 Python
Python实现SSH远程登陆,并执行命令的方法(分享)
2017/05/08 Python
Python编写一个优美的下载器
2018/04/15 Python
python删除文件夹下相同文件和无法打开的图片
2019/07/16 Python
快速解决vue.js 模板和jinja 模板冲突的问题
2019/07/26 Python
python pprint模块中print()和pprint()两者的区别
2020/02/10 Python
Python2与Python3关于字符串编码处理的差别总结
2020/09/07 Python
python利用xlsxwriter模块 操作 Excel
2020/10/14 Python
css3使用animation属性实现炫酷效果(推荐)
2020/02/04 HTML / CSS
整理HTML5中表单的常用属性及新属性
2016/02/19 HTML / CSS
深入解析HTML5 Canvas控制图形矩阵变换的方法
2016/03/24 HTML / CSS
韩国CJ食品专卖网:CJonmart
2016/09/11 全球购物
美国定制钻石订婚戒指:Ritani
2017/12/08 全球购物
平面设计师的工作职责
2013/11/21 职场文书
拾金不昧表扬信范文
2014/01/11 职场文书
棉花姑娘教学反思
2014/02/15 职场文书
食品质量与安全专业毕业生求职信
2014/08/11 职场文书
2015年乡镇纪检工作总结
2015/04/22 职场文书
感恩教育主题班会
2015/08/12 职场文书
2016保送生自荐信范文
2016/01/29 职场文书