用Python在地图上模拟疫情扩散

Dear 丶 2022-06-09 09:23 312阅读 0赞

用Python在地图上模拟疫情扩散

受杰森的《Almost Looks Like Work》启发,我来展示一些病毒传播模型。需要注意的是这个模型并不反映现实情况,因此不要误以为是西非可怕的传染病。相反,它更应该被看做是某种虚构的僵尸爆发现象。那么,让我们进入主题。

image

这就是SIR模型,其中字母S、I和R反映的是在僵尸疫情中,个体可能处于的不同状态。

  • S 代表易感群体,即健康个体中潜在的可能转变的数量。
  • I 代表染病群体,即僵尸数量。
  • R 代表移除量,即因死亡而退出游戏的僵尸数量,或者感染后又转回人类的数量。但对与僵尸不存在治愈者,所以我们就不要自我愚弄了(如果要把SIR模型应用到流感传染中,还是有治愈者的)。

至于β(beta)和γ(gamma):

  • β(beta)表示疾病的传染性程度,只要被咬就会感染。
  • γ(gamma)表示从僵尸走向死亡的速率,取决于僵尸猎人的平均工作速率,当然,这不是一个完美的模型,请对我保持耐心。

S′=−βIS告诉我们健康者变成僵尸的速率,S′是对时间的导数。

I′=βIS−γI告诉我们感染者是如何增加的,以及行尸进入移除态速率(双关语)。

R′=γI只是加上(gamma I),这一项在前面的等式中是负的。

上面的模型没有考虑S/I/R的空间分布,下面来修正一下!

一种方法是把瑞典和北欧国家分割成网格,每个单元可以感染邻近单元,描述如下:

image

实验完整代码如下:

Main.py

  1. # -*- coding: utf-8 -*-
  2. import numpy as np
  3. import math
  4. import matplotlib.pyplot as plt
  5. from matplotlib import rcParams
  6. import matplotlib.image as mpimg
  7. from PIL import Image
  8. rcParams['font.family'] = 'serif'
  9. rcParams['font.size'] = 16
  10. rcParams['figure.figsize'] = 12, 8
  11. beta = 0.010
  12. gamma = 1
  13. def euler_step(u, f, dt):
  14. return u + dt * f(u)
  15. def f(u):
  16. S = u[0]
  17. I = u[1]
  18. R = u[2]
  19. new = np.array([-beta*(S[1:-1, 1:-1]*I[1:-1, 1:-1] + \
  20. S[0:-2, 1:-1]*I[0:-2, 1:-1] + \
  21. S[2:, 1:-1]*I[2:, 1:-1] + \
  22. S[1:-1, 0:-2]*I[1:-1, 0:-2] + \
  23. S[1:-1, 2:]*I[1:-1, 2:]),
  24. beta*(S[1:-1, 1:-1]*I[1:-1, 1:-1] + \
  25. S[0:-2, 1:-1]*I[0:-2, 1:-1] + \
  26. S[2:, 1:-1]*I[2:, 1:-1] + \
  27. S[1:-1, 0:-2]*I[1:-1, 0:-2] + \
  28. S[1:-1, 2:]*I[1:-1, 2:]) - gamma*I[1:-1, 1:-1],
  29. gamma*I[1:-1, 1:-1]
  30. ])
  31. padding = np.zeros_like(u)
  32. padding[:,1:-1,1:-1] = new
  33. padding[0][padding[0] < 0] = 0
  34. padding[0][padding[0] > 255] = 255
  35. padding[1][padding[1] < 0] = 0
  36. padding[1][padding[1] > 255] = 255
  37. padding[2][padding[2] < 0] = 0
  38. padding[2][padding[2] > 255] = 255
  39. return padding
  40. img = Image.open('popdens2.png')
  41. img = img.resize((img.size[0]/2,img.size[1]/2))
  42. img = 255 - np.asarray(img)
  43. imgplot = plt.imshow(img)
  44. imgplot.set_interpolation('nearest')
  45. S_0 = img[:,:,1]
  46. I_0 = np.zeros_like(S_0)
  47. I_0[309,170] = 1 # patient zero
  48. R_0 = np.zeros_like(S_0)
  49. T = 900 # final time
  50. dt = 1 # time increment
  51. N = int(T/dt) + 1 # number of time-steps
  52. t = np.linspace(0.0, T, N) # time discretization
  53. # initialize the array containing the solution for each time-step
  54. u = np.empty((N, 3, S_0.shape[0], S_0.shape[1]))
  55. u[0][0] = S_0
  56. u[0][1] = I_0
  57. u[0][2] = R_0
  58. import matplotlib.cm as cm
  59. theCM = cm.get_cmap("Reds")
  60. theCM._init()
  61. alphas = np.abs(np.linspace(0, 1, theCM.N))
  62. theCM._lut[:-3,-1] = alphas
  63. for n in range(N-1):
  64. u[n+1] = euler_step(u[n], f, dt)
  65. from images2gif import writeGif
  66. keyFrames = []
  67. frames = 60.0
  68. for i in range(0, N-1, int(N/frames)):
  69. imgplot = plt.imshow(img, vmin=0, vmax=255)
  70. imgplot.set_interpolation("nearest")
  71. imgplot = plt.imshow(u[i][1], vmin=0, cmap=theCM)
  72. imgplot.set_interpolation("nearest")
  73. filename = "outbreak" + str(i) + ".png"
  74. plt.savefig(filename)
  75. keyFrames.append(filename)
  76. images = [Image.open(fn) for fn in keyFrames]
  77. gifFilename = "outbreak.gif"
  78. writeGif(gifFilename, images, duration=0.3)
  79. plt.clf()

image2gif.py

  1. """ MODULE images2gif
  2. Provides a function (writeGif) to write animated gif from a series
  3. of PIL images or numpy arrays.
  4. This code is provided as is, and is free to use for all.
  5. Almar Klein (June 2009)
  6. - based on gifmaker (in the scripts folder of the source distribution of PIL)
  7. - based on gif file structure as provided by wikipedia
  8. """
  9. try:
  10. import PIL
  11. from PIL import Image, ImageChops
  12. from PIL.GifImagePlugin import getheader, getdata
  13. except ImportError:
  14. PIL = None
  15. try:
  16. import numpy as np
  17. except ImportError:
  18. np = None
  19. # getheader gives a 87a header and a color palette (two elements in a list).
  20. # getdata()[0] gives the Image Descriptor up to (including) "LZW min code size".
  21. # getdatas()[1:] is the image data itself in chuncks of 256 bytes (well
  22. # technically the first byte says how many bytes follow, after which that
  23. # amount (max 255) follows).
  24. def intToBin(i):
  25. """ Integer to two bytes """
  26. # devide in two parts (bytes)
  27. i1 = i % 256
  28. i2 = int( i/256)
  29. # make string (little endian)
  30. return chr(i1) + chr(i2)
  31. def getheaderAnim(im):
  32. """ Animation header. To replace the getheader()[0] """
  33. bb = "GIF89a"
  34. bb += intToBin(im.size[0])
  35. bb += intToBin(im.size[1])
  36. bb += "\x87\x00\x00"
  37. return bb
  38. def getAppExt(loops=0):
  39. """ Application extention. Part that secifies amount of loops.
  40. if loops is 0, if goes on infinitely.
  41. """
  42. bb = "\x21\xFF\x0B" # application extension
  43. bb += "NETSCAPE2.0"
  44. bb += "\x03\x01"
  45. if loops == 0:
  46. loops = 2**16-1
  47. bb += intToBin(loops)
  48. bb += '\x00' # end
  49. return bb
  50. def getGraphicsControlExt(duration=0.1):
  51. """ Graphics Control Extension. A sort of header at the start of
  52. each image. Specifies transparancy and duration. """
  53. bb = '\x21\xF9\x04'
  54. bb += '\x08' # no transparancy
  55. bb += intToBin( int(duration*100) ) # in 100th of seconds
  56. bb += '\x00' # no transparant color
  57. bb += '\x00' # end
  58. return bb
  59. def _writeGifToFile(fp, images, durations, loops):
  60. """ Given a set of images writes the bytes to the specified stream.
  61. """
  62. # init
  63. frames = 0
  64. previous = None
  65. for im in images:
  66. if not previous:
  67. # first image
  68. # gather data
  69. palette = getheader(im)[1]
  70. data = getdata(im)
  71. imdes, data = data[0], data[1:]
  72. header = getheaderAnim(im)
  73. appext = getAppExt(loops)
  74. graphext = getGraphicsControlExt(durations[0])
  75. # write global header
  76. fp.write(header)
  77. fp.write(palette)
  78. fp.write(appext)
  79. # write image
  80. fp.write(graphext)
  81. fp.write(imdes)
  82. for d in data:
  83. fp.write(d)
  84. else:
  85. # gather info (compress difference)
  86. data = getdata(im)
  87. imdes, data = data[0], data[1:]
  88. graphext = getGraphicsControlExt(durations[frames])
  89. # write image
  90. fp.write(graphext)
  91. fp.write(imdes)
  92. for d in data:
  93. fp.write(d)
  94. # # delta frame - does not seem to work
  95. # delta = ImageChops.subtract_modulo(im, previous)
  96. # bbox = delta.getbbox()
  97. #
  98. # if bbox:
  99. #
  100. # # gather info (compress difference)
  101. # data = getdata(im.crop(bbox), offset = bbox[:2])
  102. # imdes, data = data[0], data[1:]
  103. # graphext = getGraphicsControlExt(durations[frames])
  104. #
  105. # # write image
  106. # fp.write(graphext)
  107. # fp.write(imdes)
  108. # for d in data:
  109. # fp.write(d)
  110. #
  111. # else:
  112. # # FIXME: what should we do in this case?
  113. # pass
  114. # prepare for next round
  115. previous = im.copy()
  116. frames = frames + 1
  117. fp.write(";") # end gif
  118. return frames
  119. def writeGif(filename, images, duration=0.1, loops=0, dither=1):
  120. """ writeGif(filename, images, duration=0.1, loops=0, dither=1)
  121. Write an animated gif from the specified images.
  122. images should be a list of numpy arrays of PIL images.
  123. Numpy images of type float should have pixels between 0 and 1.
  124. Numpy images of other types are expected to have values between 0 and 255.
  125. """
  126. if PIL is None:
  127. raise RuntimeError("Need PIL to write animated gif files.")
  128. images2 = []
  129. # convert to PIL
  130. for im in images:
  131. if isinstance(im,Image.Image):
  132. images2.append( im.convert('P',dither=dither) )
  133. elif np and isinstance(im, np.ndarray):
  134. if im.dtype == np.uint8:
  135. pass
  136. elif im.dtype in [np.float32, np.float64]:
  137. im = (im*255).astype(np.uint8)
  138. else:
  139. im = im.astype(np.uint8)
  140. # convert
  141. if len(im.shape)==3 and im.shape[2]==3:
  142. im = Image.fromarray(im,'RGB').convert('P',dither=dither)
  143. elif len(im.shape)==2:
  144. im = Image.fromarray(im,'L').convert('P',dither=dither)
  145. else:
  146. raise ValueError("Array has invalid shape to be an image.")
  147. images2.append(im)
  148. else:
  149. raise ValueError("Unknown image type.")
  150. # check duration
  151. if hasattr(duration, '__len__'):
  152. if len(duration) == len(images2):
  153. durations = [d for d in duration]
  154. else:
  155. raise ValueError("len(duration) doesn't match amount of images.")
  156. else:
  157. durations = [duration for im in images2]
  158. # open file
  159. fp = open(filename, 'wb')
  160. # write
  161. try:
  162. n = _writeGifToFile(fp, images2, durations, loops)
  163. print n, 'frames written'
  164. finally:
  165. fp.close()
  166. if __name__ == '__main__':
  167. im = np.zeros((200,200), dtype=np.uint8)
  168. im[10:30,:] = 100
  169. im[:,80:120] = 255
  170. im[-50:-40,:] = 50
  171. images = [im*1.0, im*0.8, im*0.6, im*0.4, im*0]
  172. writeGif('lala3.gif',images, duration=0.5, dither=0)

实验原始图像与实验后图像如下:

image

image

发表评论

表情:
评论列表 (有 0 条评论,312人围观)

还没有评论,来说两句吧...

相关阅读