最近新冠在神州大陸橫行,全國上下一心抗擊疫情。作爲一枚程序員,我也希望可以爲抗擊疫情做出自己的貢獻,鍾院士一直勸說大家不要出門,減少人口間的流動。對此,我特意做了一個病毒仿真器,探詢冠狀病毒傳播。
1. 仿真效果
仿真開始,一開始只有5個發病者,傳播率爲0.8,潛伏期爲14天
由於人口的流動,以及醫院牀位的隔離,一開始病毒擴撒不是很速度
隨着醫院牀位滿了,隔離失敗,加上人口的流動,病患數開始幾何式的增加
2. 什麼是仿真器
仿真器(emulator)以某一系統復現另一系統的功能。與計算機模擬系統(Computer Simulation)的區別在於,仿真器致力於模仿系統的外在表現、行爲,而不是模擬系統的抽象模型。
3. python如何實現仿真器
現在來談談仿真器實現的原理。仿真器使用Python和PyQt5實現。PyQt5是封裝了Qt library的跨平臺GUI開發庫,基於Python語言。
這裏主要涉及到仿真器效果繪製,以及如何模擬多個參數。先來說一下繪製市民的狀態。繪製的工作通過drawing.py文件的Drawing類來完成。該類是QWidget的子類,這也就意味着Drawing類本身是PyQt5的一個組件。與按鈕、標籤類似。只是並不需要往Drawing上放置任何子組件。只要在Drawing上繪製各種圖形即可。
在PyQt5中,任何一個QWidget的子類,都可以實現一個paintEvent方法,當組件每次刷新時,就會調用paintEvent方法重新繪製組件的內容。Drawing類中paintEvent方法的代碼如下:
# 每次Drawing刷新,都會調用該方法
def paintEvent(self,event):
qp = QPainter()
qp.begin(self)
self.drawing(qp) # 繪製城市的各種狀態的市民
qp.end()
在繪製圖像前,需要創建QPainter對象,然後調用QPainter對象的begin方法,結束繪製後,需要調用QPainter對象的end方法。上面代碼中的drawing方法用於完成具體的繪製工作。
仿真器設置一個人員池,來進行人員的流動性
class Persons(metaclass=Singleton):
def __init__(self):
self.persons = [] # 保存所有的人員
self.latency_persons = [] # 保存處於潛伏期的人員
city = City(Params.city_center_x,Params.city_center_y)
for value in range(0, Params.city_person_count):
x = Params.person_position_scale * next_gaussian() + city.center_x
y = Params.person_position_scale * next_gaussian() + city.center_y
if x > Params.city_width:
x = Params.city_width
if y > Params.city_height:
y = Params.city_height
self.persons.append(Person(city,x,y))
# 獲取特定人羣的數量
def get_person_size(self,state):
if state == -1:
return len(self.persons)
count = 0
for person in self.persons:
if person.state == state:
count += 1
return count
,仿真代碼
from PyQt5.QtWidgets import *
from socket import *
class Transmission:
def __init__(self,ui):
self.ui = ui
self.host = 'localhost'
self.port = 5678
self.addr = (self.host,self.port)
def send_command(self,command,value=None):
tcp_client_socket = socket(AF_INET,SOCK_STREAM)
tcp_client_socket.connect(self.addr)
if value == None:
value = 0
data = command + ':' + str(value)
tcp_client_socket.send(('%s\r\n' % data).encode(encoding='utf-8'))
data = tcp_client_socket.recv(1024)
result = data.decode('utf-8').strip()
tcp_client_socket.close()
return result
def setup(self):
self.ui.horizontalSliderBedCount.valueChanged.connect(self.bed_count_value_change)
self.ui.pushButtonUpdateBedCount.clicked.connect(self.update_bed_count)
self.ui.horizontalSliderFlowIntention.valueChanged.connect(self.flow_intention_value_change)
self.ui.pushButtonFlowIntention.clicked.connect(self.update_flow_intention)
self.ui.horizontalSliderBroadRate.valueChanged.connect(self.broad_rate_value_change)
self.ui.pushButtonBroadRate.clicked.connect(self.update_broad_rate)
self.ui.horizontalSliderLatency.valueChanged.connect(self.latency_value_change)
self.ui.pushButtonLatency.clicked.connect(self.update_latency)
self.ui.pushButtonClose.clicked.connect(self.close_virus_simulation)
def bed_count_value_change(self):
self.ui.labelBedIncrement.setText(
f'<html><head/><body><p><span style=\" font-size:24pt; color:#0000ff;\">{self.ui.horizontalSliderBedCount.value()}</span></p></body></html>')
def update_bed_count(self):
print(self.ui.horizontalSliderBedCount.value())
result = self.send_command('add_bed_count', self.ui.horizontalSliderBedCount.value())
if result == 'ok':
QMessageBox.information(self.ui.centralwidget, '消息', f'成功添加了{self.ui.horizontalSliderBedCount.value()}張牀位',
QMessageBox.Ok)
def flow_intention_value_change(self):
self.ui.labelFlowIntention.setText(
f'<html><head/><body><p><span style=\" font-size:24pt; color:#fc02ff;\">{self.ui.horizontalSliderFlowIntention.value() / 100}</span></p></body></html>')
def update_flow_intention(self):
result = self.send_command('set_flow_intention', self.ui.horizontalSliderFlowIntention.value())
if result == 'ok':
QMessageBox.information(self.ui.centralwidget, '消息',
f'成功設置流動意向爲{self.ui.horizontalSliderFlowIntention.value() / 100}', QMessageBox.Ok)
def broad_rate_value_change(self):
self.ui.labelBroadRate.setText(
f'<html><head/><body><p><span style=\" font-size:24pt; color:#fc0107;\">{self.ui.horizontalSliderBroadRate.value() / 100}</span></p></body></html>')
def update_broad_rate(self):
result = self.send_command('set_broad_rate', self.ui.horizontalSliderBroadRate.value())
if result == 'ok':
QMessageBox.information(self.ui.centralwidget, '消息',
f'成功設置傳播率爲{self.ui.horizontalSliderBroadRate.value() / 100}', QMessageBox.Ok)
def latency_value_change(self):
self.ui.labelLatency.setText(
f'<html><head/><body><p><span style=\" font-size:24pt; color:#ffff0a;\">{self.ui.horizontalSliderLatency.value()}</span></p></body></html>')
def update_latency(self):
result = self.send_command('set_latency', self.ui.horizontalSliderLatency.value())
if result == 'ok':
QMessageBox.information(self.ui.centralwidget, '消息', f'成功設置傳播率爲{self.ui.horizontalSliderLatency.value()}',
QMessageBox.Ok)
def close_virus_simulation(self):
reply = QMessageBox.information(self.ui.centralwidget, "請問", "是否真的要關閉病毒擴散仿真器?",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
result = self.send_command('close')
if result == 'ok':
QMessageBox.information(self.ui.centralwidget, '消息', '已經成功關閉病毒擴散仿真器', QMessageBox.Ok)
設置變量
class Params:
success = False
# 初始感染人數
original_infected_count = 50
# 病毒傳播率
broad_rate = 0.8
# 病毒潛伏期,14天
virus_latency = 140
# 醫院收治響應時間
hospital_receive_time = 10
# 醫院牀位
hospital_bed_count = 100
# 安全距離
safe_distance = 2
# 平均流動意向[-3,3] 值越大,流動意向越強
average_flow_intention = 3
# 城市總人口數量
city_person_count = 5000
# 病死率
fatality_rate = 0.02
# 死亡時間
dead_time = 30
# 死亡時間方差
dead_variance = 30
# 城市寬度
city_width = 1100
# 城市高度
city_height = 800
# 醫院寬度(需要計算獲得)
hospial_width = 0
# 城市中心x座標
city_center_x = 550
# 城市中心y座標
city_center_y = 400
# 用於計算城市中每個人隨機位置的scale(用於正態分佈)
person_position_scale = 200
current_time = 1 # 當前時間
我們可以根據這些修改仿真頁面