I'm new to Simpy and encountered resource request problem. I'm writing a simple hospital system. Many patients visit hospital and enter waiting room. Several doctors deal with the line of the waiting room. Patients keep visiting hospital and doctors pick up patients from waiting room with taking several minutes to diagnose.
Somehow, after simulation, the total visiting patients number is bigger than the sum of diagnosed patient number and patients number on waiting room. Maybe my way of requesting resource about waiting room and doctor are wrong on HospitalManager
.
How can I fix this code to do the job above? Thanks.
This below is my code.
import random
from dataclasses import dataclass
from datetime import datetime
from uuid import UUID, uuid4
from faker import Faker
from simpy import Environment, Resource, Store
@dataclass
class Patient:
id: UUID
created_at: int | float
name: str
address: str
sex: str
birthdate: datetime
class WaitingRoom:
def __init__(self, env: Environment):
self._patients = Store(env)
def put(self, patient: Patient):
self._patients.put(patient)
def get(self):
return self._patients.get()
class DoctorManager:
def __init__(self, env: Environment, num_doctors: int, diagnosis_time: float):
self._doctors = Resource(env, capacity=num_doctors)
self._diagnosis_time = diagnosis_time
def diagnose(self, env: Environment, patient: Patient):
yield env.timeout(random.expovariate(1.0 / self._diagnosis_time))
print(env.now, patient, "is diagnosed.")
class HospitalManager:
def __init__(self, waiting_room: WaitingRoom, doctor_manager: DoctorManager):
self._waiting_room = waiting_room
self._doctor_manager = doctor_manager
self._diagnosed_patients_number = 0
def add_patient_to_waiting_room(self, patient: Patient):
self._waiting_room.put(patient)
def invite_patient_to_doctor(self, env: Environment):
with self._waiting_room.get() as patient_req:
with self._doctor_manager._doctors.request() as doctor_req:
yield doctor_req
yield patient_req
yield env.process(self._doctor_manager.diagnose(env, patient_req.value))
self._diagnosed_patients_number += 1
def keep_inviting_patient_to_doctor(self, env: Environment):
while True:
yield env.timeout(1)
env.process(self.invite_patient_to_doctor(env))
def monitor_waiting_room(self, env: Environment):
while True:
print(
env.now,
len(self._waiting_room._patients.items),
"patients are waiting",
)
print(f"Diagnosed patients: {self._diagnosed_patients_number}")
yield env.timeout(5)
class Ecosystem:
def __init__(self, patient_visit_time: float, hospital_manager: HospitalManager):
self._patient_visit_time = patient_visit_time
self._hospital_manager = hospital_manager
self._patient_provider = Faker()
def run(self, env: Environment, until):
env.process(self._continue_to_generate_patients(env))
env.process(self._hospital_manager.keep_inviting_patient_to_doctor(env))
env.process(self._hospital_manager.monitor_waiting_room(env))
env.run(until=until)
def _generate_patient(self, env: Environment):
profile = self._patient_provider.simple_profile()
return Patient(
id=uuid4(),
created_at=env.now,
name=profile["name"],
address=profile["address"],
sex=profile["sex"],
birthdate=profile["birthdate"],
)
def _continue_to_generate_patients(self, env: Environment):
patient_number = 0
while True:
yield env.timeout(random.expovariate(1.0 / self._patient_visit_time))
patient_number += 1
print(f"Total Visiting Patients: {patient_number}")
patient = self._generate_patient(env)
self._hospital_manager.add_patient_to_waiting_room(patient)
if __name__ == "__main__":
env = Environment()
waiting_room = WaitingRoom(env)
doctor_manager = DoctorManager(env, 3, 10)
hospital_manager = HospitalManager(waiting_room, doctor_manager)
ecosystem = Ecosystem(1, hospital_manager)
ecosystem.run(env, 1000)
You are creating too many invite_patient_to_doctor threads.
Each thread pulls a patient from the patient queue and holds that patient until a doctor becomes available. So if you have 100 threads running, your patient queue is going to be under counted by 100. I fixed this by changing keep_inviting_patient_to_doctor to create only one thread per doctor (3 in this case). I also changed invite_patient_to_doctor to be a infinite loop. So each doctor is always looking for a patient to fix.
Note your count can still be off by up to 3, one for each patient in the process of being diagnosed by a doctor.
Here is the fixed code.