我也寫了個疫情傳播模擬程式

  • 2020 年 2 月 19 日
  • 筆記

前言

前一陣子看到有人製作了《疫情傳播模擬程式》,是用 Java做的。裡面根據多種實際情況,如居民移動意願、醫護能力、病毒傳播能力,來模擬疫情的發展。看完之後,我暗暗稱奇,特別是結合一些影片和照片,確實做得非常好。

後來過了幾天,這個影片還上了 人民日報的微信公眾號,這時我們 .NET社區就開始騷動了起來??,咱們 .NET能不能也做一個?

既然有需要, 2月6號那天晚上我決定說干就干,經過兩個晚上的思考與編碼,已經有了初步效果……運行效果如下:

這個效果可以這樣解讀,如果不加以任何控制,疫情會很快蔓延到整個城市,只有 8:1000床位的城市,將很快失去控制,一年後運行效果如下(非常慘烈):

參數與使用

程式碼中實際有很多參數可以操作:

static double MoveWilling = 0.90f; // 移動意願,0-1  static bool WearMask = false; // 是否戴口罩  static int HospitalBeds = 40; // 床位數    const float InffectRate = 0.8f; // 靠得夠近時,被攜帶者感染的機率  const float SecondsPerDay = 0.3f; // 模擬器的秒數,對應真實一天  const float MovingDistancePerDay = 10.0f; // 每天移動距離  const int InitialInfectorCount = 5; // 最初感染者數  const double DeathRate = 0.021; // 死亡率    // 要靠多近,才會觸發感染驗證  static float SafeDistance() => WearMask ? 1.5f : 3.5f;    // 住院治癒時間,最短5天,最長12.75天,平均約7天  static float GenerateCureDays() => random.NextFloat(5, 12.75f);  // 潛伏期,1-14天  static float GenerateShadowDays() => random.Next(1, 14);  // 發病後,就醫時間,0-3天  static float GenerateToHospitalDays() => random.Next(0, 3);

由於參數太多,很難在運行時全部都做調整,我選取了是否戴口罩移動意願醫院床數作為參數,程式碼如下:

protected override void OnKeyPress(KeyPressEventArgs e)  {      switch (e.KeyChar)      {          case '1': MoveWilling = 0.10f; break;          case '2': MoveWilling = 0.50f; break;          case '3': MoveWilling = 0.90f; break;          case 'M': WearMask = !WearMask; break;          case 'A': HospitalBeds += 40; break;          case 'D': HospitalBeds -= 40; break;          case 'R':              {                  if (MessageBox.Show("要重來嗎?", "確認", MessageBoxButtons.YesNo) == DialogResult.Yes)                  {                      City = City.Create();                  }                  break;              }      }  }

其中,按數字鍵 123可以指定不同的移動意願,其中 1表示居民最不願意出門, 3表示最願意,按 M可以控制居民是否能戴上口罩,按 A可以添加醫院可接納病人數。

一些演示

超多床位

這裡我將床位調成 240個(比例為 48: 1000):

static int HospitalBeds = 240; // 床位數

也可以運行時增加床位,按鍵盤 A即可(不用改程式碼),運行效果如下:

可見床位變多後,死亡率會降低,治癒人數大大提高,但仍然無法控制住疫情的發展。

「理想」情況1·沒有潛伏期

可以這樣配置:

// 0潛伏期  static float GenerateShadowDays() => 0;

運行效果如下:

可見就算潛伏期為 0,由於沒有及時就醫,疫情仍會失去控制。

「理想」情況2·沒有潛伏期、且立刻隔離就醫

像那個 Java版中,會先介紹一個「理想」情況,即感染即發病,發病即就就醫。此種模型的參數,可以這樣配置:

// 0潛伏期  static float GenerateShadowDays() => 0;  // 發病後,立即就醫。  static float GenerateToHospitalDays() => 0;

在這種情況下,運行效果如下:

可見,由最初的 5人,最終只感染了 6人,理想情況下局勢很快就被控制。當然這種情況是不存在的。

戴口罩出門

假如居民都戴口罩出門,可以用如下配置(也可以運行時按下 M鍵):

static bool WearMask = true; // 一定戴口罩

運行效果如下: ‍

堅持了 107天,疫情發展速度大大降低,但由於醫療資源的匱乏,死亡率仍然居高不下,最終仍會失去控制。

居民呆在家中不出門,且戴口罩

配置如下:

static bool WearMask = true; // 一定戴口罩  static double MoveWilling = 0.10f; // 每天只有10%的人願意出門

運行效果如下:

病毒在最初的 5人身上,只感染了 2人。效率可謂驚人。

模擬現實模型

我想貼近現實,模擬一些現實情況,然後看看效果如何,我的腳本如下:

  1. 20天,不作任何控制
  2. 20天,全民戴口罩,移動意願降低至 50%,床位增加 40個(模擬進入重大突發公共衛生事件Ⅰ級響應
  3. 25天,移動願意降低至 10%,床位增加 80個(模擬火神山醫院)
  4. 30天,床位再增加 80個,共 240個(模擬雷神山醫院)

為了儘可能短地截取 gif,我將時間縮短了,真實時間可能會和這個比例為 1:2(也就是相當於前 40天不作任何控制),腳本如下:

void StepDay()  {      if (day < 20)      {          WearMask = false;          MoveWilling = 0.9;          HospitalBeds = 40;      }      if (day >= 20)      {          WearMask = true;          MoveWilling = 0.5;          HospitalBeds = 80;      }      if (day >= 25)      {          MoveWilling = 0.1;          HospitalBeds = 160;      }      if (day >= 30)      {          HospitalBeds = 240;      }        // 其它程式碼  }

運行效果如下:

可見這真是與病毒的一部史詩級鬥爭。

20天,沒有任何控制,病毒瘋狂肆虐,前期只花了 9天,就把醫院給擠滿了;

20之後,居民帶上口罩,傳播進度迅速下降,但由於潛伏期較長,發病人數仍然持續上升;

30天之後,由於 5天前床位(醫護資源)的跟進,發病人數增速降低;

35天后,發病人數開始下降;

68天后,除了醫院中的病人,城市中已經沒有病人;

78天,已經沒有被病毒感染的人了。

總結

所有這些程式碼,我都上傳到了我的部落格數據網站,各們可以下載程式碼,通過 LINQPad6直接模擬運行,或者拷到 VisualStudio中亦可。各位也可以或者提出您的想法, Github鏈接如下:https://github.com/sdcb/blog-data/tree/master/2020/20200207-2019-ncov-simulate

寫完這個東西,給我最大的感受就是震撼,模擬起來就是一些數據而已,但背後是千千萬萬有血有肉的病人和醫務工作者,向那些偉大的「逆行者」們致敬!

從上面的數據也可以看出,任何單項指標做好,都是不能完全阻止疫情的。我們要相互信任,做好自己。我們能做的有:儘可能呆在家,別出門,就是對國家最好的貢獻;出門一定要戴口罩,這樣可以明顯降低感染率。