Simpy looks to request out of control

52 Views Asked by At

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)

1

There are 1 best solutions below

1
On

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.

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):
        print("started doc seeing patients")
        while True:
            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):
        """
        Create one thread for each doctor
        """
        for _ in range(self._doctor_manager._doctors.capacity):
            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))
        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)