2021年第十届数学建模国际赛小美赛
B题 疾病传播的风险
原题再现:
空气传播疾病可以通过咳嗽或打喷嚏、喷洒液体或灰尘传播。另一方面,一些常见的传染病只能通过飞沫传播。请建立一个模型,以评估密闭空间内空气传播和液滴传播疾病的可能性。要考虑的因素至少包括空间大小、空间通风和合用时间。这里希望更准确地考虑人员分布和气流条件。请给出一般结果并详细说明一些具体示例,如飞机舱室和室内体育场。结果需要包括公式或算法、每个参数的含义以及每个参数的测量方法。
整体求解过程概述(摘要)
空气传播和液滴传播的疾病更容易在密闭空间传播。对于感染风险的概率估计,在经典的Wells-Riley模型的基础上,考虑了更多的因素,建立了一个模型。
在影响感染风险发生概率的诸多因素中,空间的大小决定了传染病的气传病原体浓度。此外,空间的通风主要包括两个方面:通风量和气流模式。气流模式通过影响病原体的空间分布来影响受限空间中人群暴露的风险。根据这些因素的影响特点,通过对初始Wells-Riley方程参数的修正,建立了一个更具针对性的受限空间空气传播和液滴传播概率评估模型。
考虑到人口分布和气流条件对传染病传播风险的影响,采用元胞自动机模拟传染病在这两个附加因素影响下的传播。从人口流动和人口区域分布两个方面考虑人口分布。通过引入共定位时间内人员的总距离和最大位移等参数,构造了一个测量人员迁移量的方程。在元胞自动机中模拟人的均匀分布和随机分布等几种常见分布规律,寻找分布规律的相关系数。
对于特定场景的仿真,我们将先前改进的模型与借助python代码实现的元胞自动机相结合,具体分析飞机座舱和室内体育馆的传播概率。通过调整改进的Wells-Riley方程中的参数和元胞自动机中的元胞状态,实现了以下设置。在飞机座舱场景中,人和通风模式被分为三个座舱;在室内体育场内,设置人员分布为规则正态分布,设置两种不同的通风方式,比较上述情况下的传播概率。
最后,我们得出结论:对于受限环境中的现实人群,建议采取适度的人口控制和社会距离限制措施,以降低不同地区人群流动造成的传播风险;对于受限建筑的通风方式,建议采用分区通风和由内而外的通风方式,以降低病原体的传播率和传染病的传播风险。
模型假设:
为了简化这个问题,我们做了以下基本假设,每个假设都是正确的。
➢假设1:当研究某一时刻的感染风险概率时,室内空气混合良好且处于稳定状态。
理由:传统Wells-Riley模型的一些参数(如量子产率)依赖于反向计算,忽略了场地几何结构、传染源位置和气流模式对传染源空间分布的影响,这可能导致低估靠近传染源的易感人群的感染风险。在处理不同环境和案例的特殊性时,使用混合假设通常比在建模中假设特定环境和场景更合理。
➢假设2:不同病原体在传播过程中的空气动力学特性和暴露水平相同,在模拟传播概率时,不同病原体的致病性、存活率和大小等生理特性相同。
理由:空气紊流使感染性病原体颗粒在空气中的分布和运动具有随机性,并影响病原体在人体呼吸道的沉积速率,直接影响人体对病原体的摄入剂量和对传染病的易感性。
➢假设3:空气中离散传染性颗粒物的随机分布符合泊松概率分布,而忽略通过泄漏、过滤等从受限空间移除的颗粒物数量。
理由:根据泊松分布的统计特性,在建立模型时,可以在指数方程的指数项中加入一个与传染有关的参数,使其无量纲化,便于研究者设置参数。
问题重述:
问题重述
考虑到问题陈述中确定的背景信息和限制条件,我们需要解决以下问题:
⚫建立一个数学模型,包括空间大小、空间通风和共定位时间等因素,以评估空气传播和液滴传播疾病在受限空间中的传播概率,同时给出参数测量值。
⚫在问题1所建立的模型的基础上,加入人体分布和气流条件两个因素对模型进行细化,并给出了参数的测量方法。
⚫将改进后的模型应用于真实场景,例如飞机座舱和室内体育场。
我们的工作
我们的工作主要包括:
问题1
在经典的Wells-Riley模型的基础上,以量子值和泊松分布为核心,建立了一个考虑更多因素的受限空间气载和液滴传播概率模型。
分析了空间尺寸对传播概率的影响。在不考虑气流、人员分布和流动性的情况下,当敏感人群暴露或吸入滴核的概率降低时,受限空间的体积越大,均匀分散在整个空间中的等效滴核浓度越低。
分析空间通风对传播概率的影响。空间的主要通风包括两个方面:通风量和气流组织。通气量是影响感染概率的一个重要因素,已在原始方程中考虑。除通风量外,室内气流模式还影响气溶胶的空间分布,进而影响受限空间内人群的暴露风险。
问题2
为了准确分析人口分布对传染病传播概率的影响,可以引入元细胞自动机来模拟传染病的传播。人口分布因素可以从两个方面考虑:人口流动性和人口分布形态。人员的流动性与人员在同一地点时间内的移动或静止程度有关。受限空间内人员的流动性越好,易感人员与已易感人员之间接触的机会就越大,感染的可能性也就越大。人的分布方式大致可以分为随机分布、均匀分布等几种情况。当同一人群在受限空间内呈现不同的分布模式时,感染概率差异较大。
可以引入人的移动性在同一地点的时间、每个人的总旅行距离、最大位移的乘积、受限空间的面积等参数,利用它们构建关系来度量人的移动性的大小。
人的分布模式对传播概率的影响可以通过引入一个参数来表示。为了找到这个参数,使用元胞自动机进行仿真。通过调整初始状态下不同单元的分布状态来模拟该参数的值,从而模拟随机分布、均匀分布等情况下该参数的值。
问题3
要描述特定场景中的感染概率,需要考虑不同场景的空间结构以及人群在空间中进行的活动分析。除中间运动区外,室内运动场的观众服从正态分布。机舱内人员分布在多个频段。在前面的问题中修改的Wells-Riley模型可以与元胞自动机代码结合使用,通过改变相应的python代码并为两种场景中的每一种实现不同的空气交换气流条件,比较不同场景中感染概率随共存时间增加的变化。
模型的建立与求解整体论文缩略图
全部论文请见下方“ 只会建模 QQ名片” 点击QQ名片即可
部分程序代码:(代码和文档not free)
import numpy as np
import matplotlib.pyplot as plt
import random
import copy
lst=[]
class Modal:
def __init__ (self):
#参数表
self.stay_time = 6 #共处的时间 t
self.air_speed = 0.3 #空气的供应速度 Q
self.length = 9 #三维空间的长
self.width = 50 #三维空间的宽
self.height = 100 #三维空间的高
self.volume = self.length*self.width*self.height #空间的大小 V
self.direction = [(0,1),(1,0),(0,-1),(-1,0)] #行人移动的方向
self.search = [(1,1),(1,0),(1,-1),(0,1),(0,-1),(-1,1),(-1,0),(-1,-1)] #元胞的邻居
self.turn = 1#回合数目
self.people_rate = 0.2 #该点有人的概率-正态分布/均匀分布
self.people_move_rate = 0 #人们流动比例
self.people_change_dir_rate = 0 #转向概率
self.create_sick_rate = 0.01 #新增病人
self.people_getsick = 0.7 #患病概率
# self.people_rate = 0.2 #人的分布(均匀)
self.breath_speed = 0.48 #人均呼吸速率 p
self.virus_possibility = 0.25 #每一轮感染者产生病毒的概率
self.sickfromvirus = 0.7 #易感者遇到病毒被感染的概率
self.virusdead = 0.5 #病毒死亡的概率
# self.people_rate = 0.2 #人的分布(均匀)
self.rec_sick = 1
self.rec_infect_cnt = 0
self.miu1 = self.length/2 #正态分布参数
self.miu2 = self.width/2 #正态分布参数
self.sigma1 = (self.length/20)**2 #正态分布参数
self.sigma2 = (self.width/20)**2 #正态分布参数
self.rho = 0 #正态分布参数
# self.n = 500 #通风中的人数
# self.f = 0.3 #室内空气的呼气分数
self.people_cnt = 0 #统计人数
#初始化
self.mapsize = (self.length, self.width) #空间的大小
self.map = np.zeros(self.mapsize,int) #空间的初始化
self.map_temp = copy.deepcopy(self.map) #当前的状态图
self.map_temp_sp = copy.deepcopy(self.map) #记录特殊人员
self.map_virus = copy.deepcopy(self.map) #病毒元胞图
self.time=0.001
self.people_direct = copy.deepcopy(self.map) #记录人行走的方向
self.x_nscale=[2,6]
self.y_nscale=[i for i in range(11,16)]+[j for j in range(31,36)]+[k for k in range(0,5)]
self.x_scale = [0,1,3,4,5,7,8]
self.y_scale = [i for i in range(5,11)]+[j for j in range(16,31)]+[k for k in range(36,51)]
def fnormal(self,x1,y1):
#这里使用的是二维正态分布函数来模拟人的正态分布
#越往中间人出现的概率越大
x = self.miu1
y = self.miu2
f1 = (1/(2*3.14*self.sigma1*self.sigma2*np.sqrt(1-self.rho*self.rho)))*np.exp(
-1/(2*(1-self.rho*self.rho))*((x-self.miu1)**2/self.sigma1**2 -
2*rho*(x-self.miu1)*(yself.miu2)/(self.sigma1*self.sigma2) + (y-self.miu2)**2/self.sigma2**2))
x = x1
y = y1
f = (1/(2*3.14*self.sigma1*self.sigma2*np.sqrt(1-self.rho*self.rho)))*np.exp(
-1/(2*(1-self.rho*self.rho))*((x-self.miu1)**2/self.sigma1**2 -
2*rho*(x-self.miu1)*(yself.miu2)/(self.sigma1*self.sigma2) + (y-self.miu2)**2/self.sigma2**2))
return f/f1
def possibility(self,p):
#蒙特卡洛模拟是否达到某个概率
k = random.random()
return k < p
def add_virus(self):#每个回合感染者都有概率传播病毒元胞
p = np.random.rand()
if p < self.virus_possibility:
return
for i in range (self.mapsize[0]):
for j in range (self.mapsize[1]):
if self.map_temp[i][j] == 2:
self.map_virus[i][j] = 1
return
def work_virus(self):#病毒的走动
for x in range(self.mapsize[0]):
for y in range(self.mapsize[1]):
if self.map_virus[x][y] == 1:
flag = 0
dir = random.sample([0,1,2,3],1)[0]
new_x = x + self.direction[dir][0]
new_y = y + self.direction[dir][1]
if new_x<0 or new_x>=self.mapsize[0] or new_y<0 or
new_y>=self.mapsize[1]:
continue
else:
self.map_virus[x][y],self.map_virus[new_x][new_y] =
self.map_virus[new_x][new_y],self.map_virus[x][y]
#两个位置状态互换
return
def dead_virus(self):#病毒有概率死亡或沉底
for x in range (self.mapsize[0]):
for y in range(self.mapsize[1]):
if self.map_virus[x][y] == 1 and self.possibility(self.virusdead):
self.map_virus[x][y] = 0
def arrange(self):#整合地图从而可视化
self.map = copy.deepcopy(self.map_temp)
for i in range(self.mapsize[0]):
for j in range(self.mapsize[1]):
if self.map_virus[i][j] == 1:
self.map[i][j] = 3
return
def rand_make(self): #初始化人的分布
x_nscale = self.x_nscale
y_nscale = self.y_nscale
for x in range(self.mapsize[0]):
for y in range(self.mapsize[1]):
if not(x in x_nscale or y in y_nscale):
self.map_temp[x][y] = 1
self.people_direct[x][y] = random.randint(0,3)
self.people_cnt += 1
else:
self.people_direct[x][y] = -1
self.map_temp[random.sample(self.x_scale,1)[0]][random.sample(self.y_scale[0:6],1)[0]] = 2
self.map_temp[random.sample(self.x_scale,1)[0]][random.sample(self.y_scale[6:22],1)[0]] = 2
self.map_temp[random.sample(self.x_scale,1)[0]][random.sample(self.y_scale[22:],1)[0]] = 2
x1 = random.sample([i for i in range(0,8)],3)
y1 = random.sample([j for j in range(11,15)],3)
dict1 = dict(zip(x1,y1))
for x,y in dict1.items():
self.map_temp_sp[x][y]=3
self.map_temp[x][y]=1
else:
self.map_temp[x][y]=2
x2 = random.sample([i for i in range(0,8)],3)
y2 = random.sample([j for j in range(31,35)],3)
dict2 = dict(zip(x2,y2))
for x,y in dict2.items():
self.map_temp_sp[x][y]=3
self.map_temp[x][y] = 1
else:
self.map_temp[x][y]=2
self.map_temp_sp[3][1] = 4
self.map_temp[3][1] = 1
self.map_temp_sp[4][1] = 4
self.map_temp[4][1] = 1
self.map_temp_sp[5][1] = 4
self.map_temp[5][1] = 1
return
def infect(self,x,y): #感染
self.flag = 0
infect_num = 1 #统计邻居一共有多少个人患病
# miu = 66.91
# sigma = np.sqrt(1.53)
q = np.random.rand()*40+30
for id in self.search:
tmp_x = x+id[0]
tmp_y = y+id[1]
if tmp_x<0 or tmp_x>=self.mapsize[0] or tmp_y<0 or tmp_y>=self.mapsize[1]:
continue
if self.map_temp[tmp_x][tmp_y] == 2:
infect_num += 1
for id in self.search:
new_x = x+id[0]
new_y = y+id[1]
if new_x<0 or new_x>=self.mapsize[0] or new_y<0 or new_y>=self.mapsize[1]:
continue
people_getsick = 100*(1-np.exp(-infect_num*q*self.breath_speed*self.stay_time*
(1-self.volume*(1-np.exp(-
self.air_speed*self.stay_time/self.volume))
/(self.air_speed*self.stay_time))/self.air_speed))
if self.map_temp[new_x][new_y] == 1 and self.possibility(people_getsick):
self.map_temp[new_x][new_y] = 2
self.flag = 1
self.rec_sick += 1
return
def work_people(self,x,y):#人的走动
if not self.possibility(self.people_move_rate):
return
dir = self.people_direct[x][y]
if not self.possibility(self.people_change_dir_rate):
dir += random.randint(0, 3)
dir %= 4
new_x = x + self.direction[dir][0]
new_y = y + self.direction[dir][1]
if new_x<0 or new_x>=self.mapsize[0] or new_y<0 or new_y>=self.mapsize[1]:
return
if self.map_temp[new_x][new_y] != 0:
return
self.people_direct[x][y],self.people_direct[new_x][new_y] =
self.people_direct[new_x][new_y],self.people_direct[x][y]
self.map_temp[x][y],self.map_temp[new_x][new_y] =
self.map_temp[new_x][new_y],self.map_temp[x][y]
#两个位置状态互换
return
# def work_staff(self,x, y):
# flag2 = 0
# if not self.possibility(self.people_move_rate):
# return
# dir = self.people_direct[x][y]
# r1 = [i for i in range(4)]
# r2 = [j for j in range(2)]
# random.shuffle(r1)
# random.shuffle(r2)
# for i in range(4):
# if x in self.x_nscale and y in self.y_nscale:
# dir = r1[i]
# elif x in self.x_nscale and y in self.y_scale:
# dir = r2[random.randint(0,1)]
# new_x = x + self.direction[dir][0]
# new_y = y + self.direction[dir][1]
# if self.map_temp[new_x][new_y] == 0 and new_x>=0 and new_x<self.mapsize[0] and
new_y>=0 and new_y<self.mapsize[1] and ((new_x not in self.x_scale) and (new_y not in self.y_scale)):
# break;
# else:
# flag2 = 1
# if flag2 == 0:
# self.people_direct[x][y],self.people_direct[new_x][new_y] =
self.people_direct[new_x][new_y],self.people_direct[x][y]
# self.map_temp[x][y],self.map_temp[new_x][new_y] =
self.map_temp[new_x][new_y],self.map_temp[x][y]
# #两个位置状态互换
# return
def work_staff(self,x, y):#乘务员走动
flag2 = 0
if not self.possibility(self.people_move_rate):
return
dir = self.people_direct[x][y]
r1 = self.x_nscale
r2 = [i for i in range(11,16)]+[j for j in range(31,36)]
self.x_nscale=[2,6]
random.shuffle(r1)
random.shuffle(r2)
for i in range(6):
new_x = r1[random.randint(0,len(r1)-1)]
new_y = r2[i]
if self.map_temp[new_x][new_y] == 0 and new_x>=0 and new_x<self.mapsize[0] and
new_y>=0 and new_y<self.mapsize[1] and ((new_x not in self.x_scale) and (new_y not in self.y_scale)):
break;
else:
flag2 = 1
if flag2 == 0:
self.people_direct[x][y],self.people_direct[new_x][new_y] =
self.people_direct[new_x][new_y],self.people_direct[x][y]
self.map_temp[x][y],self.map_temp[new_x][new_y] =
self.map_temp[new_x][new_y],self.map_temp[x][y]
self.map_temp_sp[x][y],self.map_temp_sp[new_x][new_y] =
self.map_temp_sp[new_x][new_y],self.map_temp_sp[x][y]
#两个位置状态互换
return
def show(self,i=0):#可视化
j = i
self.arrange()
plt.figure(j,figsize=(50,50))
plt.imshow(self.map)
ax = plt.gca()
y = [i-0.5 for i in range(0,9)]
x = [j-0.5 for j in range(0,50)]
plt.xticks(x)
plt.yticks(y)
plt.grid()
return
def create_sick(self,x,y):#人在接触病毒的时候有概率被感染
# if self.possibility(self.create_sick_rate):
# self.map_temp[x][y] = 2
# self.rec_sick += 1
if (self.map_temp[x][y] == 1) and (self.map_virus[x][y] == 1):
if self.possibility(self.sickfromvirus):
self.map_temp[x][y] = 2
self.rec_sick += 1
def go(self):
self.rand_make()
try_num = 100
while(1):
self.work_virus()
self.add_virus()
self.dead_virus()
self.show(100-try_num)
#行人走动
for i in range (self.mapsize[0]):
for j in range (self.mapsize[1]):
if not self.map_temp[i][j] == 0:
self.work_people(i,j)
for i in range(self.mapsize[0]):
for j in range (self.mapsize[1]):
if self.map_temp[i][j] == 2:
self.infect(i,j)
elif self.map_temp[i][j] == 1:
self.create_sick(i,j)
lst.append(self.rec_sick)
try_num -= 1
if(try_num == 0):
break;
if __name__ == '__main__':
modal = Modal()
modal.go()