程序员硬核操作 | 利用代码程序建立新冠病毒传播模型


憋疯了?在家宅不下去了?想出去透透气了?

千万别!

虽然你对疫情已经麻木了,觉得没什么大碍了。

但现实并非如此,抗疫战斗仍在继续,还没有达到钟南山等专家所说的爆发期。
如果大家现在要是出门,真的是在疫情防控添乱!

不信?一名程序员,连夜打造的计算机仿真程序,模拟新冠病毒传播,在B站上播放量已达到数百万:
在这里插入图片描述
视频链接:

https://www.bilibili.com/video/av86478875

它告诉我们:

如果现在出门逛,迎接我们的,就是疫情越来越难控制的局面。

本文对火爆B站的仿真程序进行了深度解读:

一、JFrame面板组件布局

在这里插入图片描述

二、数学概念:高斯(正态)分布

为什么要讲高斯分布?

病毒传播代码最精华部分,最精彩部分,最能体现仿真模拟的就是这个高斯分布的代码,在本章尽量也最简单的话语为大家简单的介绍一下高斯分布的理论。

2.1 高斯分布概念

一个非常常见的连续概率分布。正态分布在统计学上十分重要,经常用在自然和社会科学来代表一个不明的随机变量。

例如说人的体重、身高、某种疾病的患病年龄、城市人口的分布,基本都是符合高斯分布的统计学应用。高斯分布的公式为:
在这里插入图片描述
纵观整个高斯公式,有两个很重要的参数:μ(可以谐音为 “谬”),σ(发音为sigma);那么跟大家解释一下μ为平均值,σ为标准差。

标准差决定了整个样本数据的分布密度,标准差越小,数据越集中,如下图非常直观的描述的μ与σ的关系,以及σ对整个概率分布的影响。
在这里插入图片描述
另外,如果μ=0, σ=1的时候,就称之为标准高斯分布。而X轴落在整个⾼斯曲线内的值,专业的说法为服从高斯分布。

2.2 正态变量的标准化

正态变量的标准化是高斯公式的一个重要推导,这里直接给出结论:

v 为服从高斯分布的数据

σ 为标准差

μ 为平均值

W 对v进行标准化处理后的数据,依然是服从高斯分布的

正态变量的标准化公式:

W = (V - μ) / σ

那么V值的计算公式为:

V = W * σ + μ

2.3 高斯分布在Java中的应用

java.util.Random函数有个方法叫做nextGaussian()函数,定义如下:

/*

* @return the next pseudorandom, Gaussian ("normally") distributed

* {@code double} value with mean {@code 0.0} and

* standard deviation {@code 1.0} from this random number

* generator's sequence

*/

synchronized public double nextGaussian() {

// See Knuth, ACP, Section 3.4.1 Algorithm C. if (haveNextNextGaussian) {

haveNextNextGaussian = false; return nextNextGaussian;

} else {


double v1, v2, s;

do {

v1 = 2 * nextDouble() - 1; // between -1 and 1

v2 = 2 * nextDouble() - 1; // between -1 and 1 s = v1 * v1 + v2 * v2;

} while (s >= 1 || s == 0);

double multiplier = StrictMath.sqrt(-2 * StrictMath.log(s)/s); nextNextGaussian = v2 * multiplier;

haveNextNextGaussian = true;

return v1 * multiplier;

}

}

根据API的描述:返回一个double类型的值,这个值服从均值为0,均方差为1的标准正态分布。

然后根据正态变量的标准化推导公式:、

double value = sigma * new Random().nextGaussian() + 0.99;

三、核心代码解读

3.1启动类函数:

在这里插入图片描述

3.2画布相关代码

初始化画布:

在这里插入图片描述

如上图所示的MyPanel 类实现了Runnable接口:

在这里插入图片描述

重写的run方法如下:
在这里插入图片描述
如上图所示的MyPanel.this.repaint()方法为一个钩子函数,会调用MyPanel类中法paint方法,paint方法会重新设置人员的状态,数据的变更,医院的床位等。

在paint方法中会调用这个person.update()方法,这个方法至关重要。在后续有一节专门介绍。

3.3初始化感染人员

在这里插入图片描述
代码中会看到PersonPool这个类,这个类中有人员池这样一个个静态变量,在加载时候去初始化城市人口:
在这里插入图片描述
PersonPool的构造函数:
在这里插入图片描述

3.4Person类

在这里插入图片描述
Person的构造方法:
在这里插入图片描述
为了方便大家理解,给出下面这张图:
在这里插入图片描述
Person类中的wantMove法的实现:
在这里插入图片描述
Person 类中distance 方法的实现,用以判断是否能被感染:

在这里插入图片描述

3.5Person中的action方法

action方法是一个至关重要的方法,故单独提出为一个章节,该方法决定了用户坐标的移动,方法如下:


/**

* 不同状态下的单个人实例运动行为

*/

private void action() {

if (state == State.FREEZE || state == State.DEATH) { 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);

}

//计算运动位移

int dX = moveTarget.getX() - x; int dY = moveTarget.getY() - y;

// 勾股定理

double length = Math.sqrt(Math.pow(dX, 2) + Math.pow(dY, 2));//

与目标点的距离

if (length < 1) {

//判断是否到达目标点

moveTarget.setArrived(true); return;

}

/**

* 如果没有到达目标,一步一步的走,根据坐标方向,如果为正方向,就往前移动

1,

* 如果坐标轴的反方向,就移动

-1

* udx的结果只可能为两种三种情况:

-1 1 0

*/

int udX = (int) (dX / length); 

//x轴移动步,符号为沿x轴前进方向

if (udX == 0 && dX != 0) {

if (dX > 0) {

udX = 1;

} else { udX = -1;

}

}

int udY = (int) (dY / length);

//y轴移动步,符号为沿x轴前进方向

if (udY == 0 && dY != 0) {

if (dY > 0) { udY = 1;

} else { udY = -1;

}

} 

// 如果超过边界,就往回走

if (x > 700) {

//这个700也许是x方向边界的意思,因为画布大小

1000x800

//TODO:如果是边界那么似乎边界判断还差一个y方向

moveTarget = null;

if (udX > 0) { udX = -udX;

}

}

3.6Person中的update方法

update方法是一个至关重要的方法,故单独提出为一个章节,该方法决定了根据用户不同的状态决定如何处理,方法如下:

/**

* 对各种状态的人进行不同的处理

*/

public void update() {

//@TODO找时间改为状态机

if (state == State.FREEZE || state == State.DEATH) { return;

//如果已经隔离或者死亡了,就不需要处理了

}

//处理已经确诊的感染者(即患者)

if (state == State.CONFIRMED && dieMoment == 0) {

int destiny = new Random().nextInt(10000)+1;

//命运数字[1,10000]随机数

if (1 <= destiny && destiny <= (int)(Constants.FATALITY_RATE * 10000))

{

//如果命运数字落在死亡区间

int dieTime = (int) (Constants.DIE_VARIANCE * new Random().nextGaussian()+Constants.DIE_TIME);

dieMoment = confirmedTime + dieTime;

//发病后确定死亡时刻

//System.out.printf("%d,%f,%d\n",destiny,Constants.FATALITY_RATE * 10000,dieTime);

}

else {

dieMoment = -1;

//逃过了死神的魔手

}

}

//*/

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((state == State.CONFIRMED || state == State.FREEZE )&& MyPanel.worldTime >= dieMoment && dieMoment > 0) {

state = State.DEATH;

//患者死亡

}

//处理发病的潜伏期感染者

if (MyPanel.worldTime - infectedTime > Constants.SHADOW_TIME && state == State.SHADOW) {

state = State.CONFIRMED;

//潜伏者发病

confirmedTime = MyPanel.worldTime;

//刷新时间

}

//处理病死者

if((state == State.CONFIRMED || state == State.FREEZE )&& MyPanel.worldTime >= dieMoment && dieMoment > 0) {

state = State.DEATH;

//患者死亡

}

//处理发病的潜伏期感染者

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;

}

// 随机生成一个值

float random = new Random().nextFloat();

// 如果概率大于感染概率,并且小于安全距离,那么当前这个人肯定会被感染

if (random > Constants.BROAD_RATE && distance(person) < SAFE_DIST) { this.beInfected();

// 如果被感染了,就继续判断下一个

break;

}

}
}

最后,希望大家能够多点耐心,身体健康最重要。等疫情过去,再撒欢儿玩~

发布了475 篇原创文章 · 获赞 400 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_35456045/article/details/104260749