0

I have an endless loop problem in Simpy simulation.

My scenario is: After finishing triage, a patient wait until getting empty bed. However, if there is no empty bed, the patient should wait until an empty bed is available. When a patient uses a bed and leaves the hospital, the bed turns into a dirty bed. The dirty bed turns into an empty bed when the cleaner cleans it. Bed cleaning work start after getting clean request.

I used "Store" for managing beds I think I have an infinite loop when there is no empty bed and no dirty bed.

I think...

  1. Add a queue (After triage)
  2. From the queue, assign a bed based on the FIFO.
  3. If we don't have empty or dirty bed, we should wait until a dirty bed is available.

But, I don't know how to implement this idea. Please help me to solve this problem.

import simpy
import random

class Pre_Define:
    warmup_period = 1440
    sim_duration = 14400
    number_of_runs = 3  

class Patients:
    def __init__(self, p_id):
        self.id = p_id
        self.bed_name = ""
        self.admission_decision = ""
    def admin_decision(self):
        admin_decision_prob = random.uniform(0, 1)
        if admin_decision_prob <= 0.7:
            self.admission_decision = "DIS"

class Model:
    def __init__(self, run_number):
        self.env = simpy.Environment()
        self.pt_counter = 0
        self.tg = simpy.Resource(self.env, capacity = 4)
        self.physician = simpy.Resource(self.env, capacity = 4) 
        self.bed_clean = simpy.Store(self.env, capacity = 77)
        self.bed_dirty = simpy.Store(self.env, capacity = 77)
        self.IU_bed = simpy.Resource(self.env, capacity = 50)   
        self.bed_cleaner = simpy.Resource(self.env, capacity = 2)
        self.run_number = run_number
        
    def generate_beds(self):
        for i in range(77):
            yield self.env.timeout(0)
            yield self.bed_clean.put(f'bed{i}')
        print(self.bed_clean.items)
       
    def generate_pt_arrivals(self):
        while True:
            self.pt_counter += 1
            pt = Patients(self.pt_counter)
            yield self.env.timeout(1/7)
            self.env.process(self.Process(pt))

    def Process(self, Patients):
        with self.tg.request() as req:
            yield req
            triage_service_time = random.expovariate(1.0/18)
            yield self.env.timeout(triage_service_time)

        if self.bed_clean.items != []:
            get_empty_bed_name = yield self.bed_clean.get()         
            Patients.bed_name = get_empty_bed_name
        elif self.bed_dirty.items != []:
            get_dirty_bed_name = yield self.bed_dirty.get() 
            with self.bed_cleaner.request() as req:
                yield req
                yield self.env.timeout(50)
        else:
            print("NO BED, Should Wait!!")
            no_bed = True
            while no_bed:
                #print("Waiting dirty bed")
                if self.bed_dirty.items != []:
                    get_dirty_bed_name = yield self.bed_dirty.get() 
                    print("Find dirty bed!")
                    with self.bed_cleaner.request() as req:
                        yield req
                        yield self.env.timeout(30)
                        Patients.bed_name = get_dirty_bed_name
                    no_bed = False

        with self.physician.request() as req:
            yield req
            yield self.env.timeout(10)
            Patients.admin_decision()
            
        if Patients.admission_decision == "DIS":
            with self.IU_bed.request() as req:
                yield req
                yield self.env.timeout(600)
                get_dirty_bed_name = Patients.bed_name
                yield self.bed_dirty.put(get_dirty_bed_name)
        else:
            get_dirty_bed_name = Patients.bed_name
            yield self.bed_dirty.put(get_dirty_bed_name)

    def run(self):
        self.env.process(self.generate_pt_arrivals())
        self.env.process(self.generate_beds())
        self.env.run(until = Pre_Define.warmup_period + Pre_Define.sim_duration)

for run in range(Pre_Define.number_of_runs):
    run_model = Model(run)
    run_model.run()
    print()
CrazyChucky
  • 3,263
  • 4
  • 11
  • 25

1 Answers1

1

So you got the right idea where you use two Stores to track dirty and clean beds. The trick is to request both a clean and a dirty bed at the same time, and discard the request you do not use.

So the big changes I made was to request both a clean bed and a dirty bed and used env.any_of() to get me the first filled request. Note that both requests could get filled. Since I made two requests, that means I need to either cancel the request I do not use, or if filled, return the unused bed back to its queue. The other thing I did was to make a separate process for the cleaners. This means that the both the patients and the cleaners will be competing for dirty beds.

"""
    Quick sim model of beds in a hosptial triage

    beds have two states, clean and dirty

    When a patient arrives they are assigned to a empty bed
    If no beds are avail then the patient wait for first empty bed
    If there is both a clean bed and a empty bed, the clean bed will be assigned
    When the patient leaves triage the bed state is dirty.

    empty dirty beads are queued to be clean, after cleaning bed state is clean

    After being triaged, a patient is either admited or discharged.
    If the patient is admitted, then they wait for a IU bed befor the triage bed is empty

    Programmer: Michael R. Gibbs

"""

import simpy
import random

TRIAGE_TIME = 30
BED_CLEAN_TIME = 20

class Patient():
    """
    has id to track patient progress
    and bed when assigned to a bed
    """

    next_id = 1

    @classmethod
    def get_next_id(cls):
        id = cls.next_id
        cls.next_id += 1

        return id

    def __init__(self):
        self.id = self.get_next_id()
        self.bed = None

class Bed():
    """
    has id to track patient progress
    and bed when assigned to a bed
    """

    next_id = 1

    @classmethod
    def get_next_id(cls):
        id = cls.next_id
        cls.next_id += 1

        return id

    def __init__(self):
        self.id = self.get_next_id()
        self.bed_state = 'Clean'

def clean_beds(env, dirty_bed_q, clean_bed_q):
    """
        Sim process for cleaning dirty beds from
        the dirty queue, and putting the clean
        beds into clean queue

        A instance shoud be started for each cleaner
    """

    while True:
        bed = yield dirty_bed_q.get()
        print(f'{env.now:.2f} bed {bed.id} is being cleaned')

        # clean
        yield env.timeout(BED_CLEAN_TIME)
        bed.bed_state = "Clean"

        clean_bed_q.put(bed)
        print(f'{env.now:.2f} bed {bed.id} is clean')

def triage(env, pat, clean_bed_q, dirty_bed_q, ui_bed_q):
    """
        models the patients life cycle in triage
        stepss are:
            get bed (clean perfered)
            get triaged
            leave
        if addmited leaving is blocked until a ui bed is found
    """

    # get bed
    clean_req = clean_bed_q.get()
    dirty_req = dirty_bed_q.get()

    print(f'{env.now:.2f} patient {pat.id} has arrived')

    fired = yield env.any_of([clean_req, dirty_req])

    # see if we got a clean or dirty or both
    if clean_req in fired:
        # got a clean bead
        pat.bed = fired[clean_req]

        # need to deal with the dirty req
        if dirty_req in fired:
            # got two beds but dirty back
            dirty_bed_q.put(fired[dirty_req])
        else:
            # stil need to cancel req
            dirty_req.cancel()
    else:
        # we have a dirty bed
        pat.bed = fired[dirty_req]

        # need to deal with the dirty req
        if clean_req in fired:
            # got two beds but dirty back
            clean_bed_q.put(fired[clean_req])
        else:
            # stil need to cancel req
            clean_req.cancel()

    print(f'{env.now:.2f} {pat.bed.bed_state} bed {pat.bed.id} is occupied and dirty with patient {pat.id}')

    pat.bed.bed_state = 'Dirty'

    # triage
    yield env.timeout(TRIAGE_TIME)

    admit = (random.uniform(0,1) < 0.7)

    if admit:
        # need to get a IU bed before
        # patient gives up triage bed

        print(f'{env.now:.2f} patient {pat.id} is being admitted')
        ui_req = ui_bed_q.request()
        yield ui_req
        print(f'{env.now:.2f} patient {pat.id} got iu bed')

    # patient leaves triage
    # give up dirty bed
    dirty_bed_q.put(pat.bed)

    print(f'{env.now:.2f} bed {pat.bed.id} is empty and patient {pat.id} has left')


def gen_pats(env, clean_bed_q, dirty_bed_q, iu_bed_q):
    """
        creates the arriving patients
    """

    while True:
        yield env.timeout(random.uniform(1,8))
        
        pat = Patient()

        # not each patient gets their own process
        # so if one patient blocks while waiting for a iu bed, it does
        # not block the other patients
        env.process(triage(env, pat, clean_bed_q, dirty_bed_q, iu_bed_q))

def model():
    env = simpy.Environment()

    # make queues
    clean_bed_q = simpy.Store(env)
    dirty_bed_q = simpy.Store(env)

    iu_bed_q = simpy.Resource(env,capacity=5)

    clean_bed_q.items = [Bed() for _ in range(10)]

    # start generating patients
    env.process(gen_pats(env, clean_bed_q, dirty_bed_q, iu_bed_q))

    # star up cleaners
    for _ in range(2):
        env.process(clean_beds(env, dirty_bed_q, clean_bed_q))

    env.run(until=100)

    print('simulation finish')

model()
Michael
  • 1,671
  • 2
  • 4
  • 8
  • Wow, you are amazing. I respect your idea and implementation! I will try your idea and concept to my code. I really appreciate it. – Justin King Apr 01 '22 at 15:02
  • I have a question!, If a patient arrives, but there are no clean beds or dirty beds, and the patient just have to wait for another patient to go to IU, Therefore, the patient event should wait a dirty bed, but the other event should have to go on to occur dirty bed. Also, after the patient get the dirty bed, the patient flow should restart at the point. How can I implement this case? – Justin King Apr 01 '22 at 15:21
  • 1
    I am not understanding your use case. you got a more detailed example? – Michael Apr 01 '22 at 17:05
  • @ Michael, thank you for your comments. I tried solve some problems by myself, but I couldn't. Please let me explain about it. When I just modified the number of bed(From 10 to 2), the result had something different form what I thought. For example, "Dirty bed 1 is occupied and dirty with patient 5" and then the dirty bed should be cleaned, but without cleaning the dirty bed be assigned the new patient. Also, may I ask why you use "for loop" for "env.process(clean_beds(env, dirty_bed_q, clean_bed_q))" in the model() function? Thank you so much for your work and time. – Justin King Apr 06 '22 at 16:24
  • 1
    each clean_bed process is a cleaner, the loop makes two cleaners – Michael Apr 06 '22 at 17:07
  • 1
    When I ran it with 2 beds, they got cleaned after the first patient. However the cleaners and the patients use the same queue and same priority to get dirty beds. At the start of the sim the cleaners are first in the dirty bed queue, so they get the first two dirty beads. but when they finish, the dirty bed queue now has about 7 patients ahead of them in the queue. If you extend the run time, I think the cleaners would show up again. – Michael Apr 06 '22 at 17:45
  • I really appreciate your fast response!! Please understand that I'm still confused. I copied some parts of the result as below. The flow I expect must be cleaned before the dirty beds are assigned as below. ============================== 31.09 bed 1 is empty and patient 1 has left 31.09 bed 1 is being cleaned ... 37.37 bed 2 is empty and patient 2 has left 37.37 bed 2 is being cleaned ... 51.09 bed 1 is clean 51.09 Clean bed 1 is occupied and dirty with patient 3 ... 57.37 bed 2 is clean 57.37 Clean bed 2 is occupied and dirty with patient 4 – Justin King Apr 06 '22 at 18:35
  • However, some results, unlike the above, are assigned to the patient immediately before being cleaned as below. This is the part I'm curious about. ============================================ 111.09 bed 1 is empty and patient 5 has left 111.09 Dirty bed 1 is occupied and dirty with patient 7 ... 117.37 bed 2 is empty and patient 6 has left 117.37 Dirty bed 2 is occupied and dirty with patient 8 If I misunderstand, can I ask you to explain it again? Thank you again for your valuable time and effort. – Justin King Apr 06 '22 at 18:36
  • 1
    if there are no clean beds, then the patient gets a dirty bed – Michael Apr 06 '22 at 21:00
  • Thank you so much Michael!! Now I can understand why I had the result. So, if the model doesn't have an empty bed and a dirty bed, the model wait until has a dirty bed. And then when the dirty bed is cleaned, the patient who's been waiting gets it and the simulation continues to flow. So to implement this, I made a class called "bed mgmt" and tried to call it after discharge, but I'm not sure how the patient who's been waiting can start again right after waiting... I'm so sorry for asking.. but please give me some idea or advice... Thank you again. – Justin King Apr 07 '22 at 01:30
  • 1
    what is the use case? a patient is discharged, giving up his bed, then he needs another bed? – Michael Apr 07 '22 at 02:32
  • Hi Michael, thank you for your reply!, I mean in this " I made a class called "bed mgmt" and tried to call it after discharge..." to call means trigger the event of bed mgmt. According to what you commended above, if a patient don't have a bed(dirty or clean), the patient gets a dirty bed. However, what I want to do is that no patient should receive a dirty bed. So I think that's what I said earlier because I thought that the event of the dirty ED(Emergency Department) bed is triggered was when the patient is discharged or moved to IU (Inpatient Unit). Thank you so much for reading! – Justin King Apr 07 '22 at 12:38
  • FYI, this is brief scenario: Patients arrive at ED(Emergency Department). When patients arrive at the ED, they first receive a trial from the nurse. After the triage, patients will be assigned an ED bed, but if patients don't have a clean bed, they have to wait until get a dirty bed. (Dirty beds occur when the patient is discharged or goes to the IU(Inpatient Unit).) The meaning of waiting here has two cases, the first is that when the patient arrives, if there is only a dirty bed(not cleaned), system should give an order of cleaning. – Justin King Apr 07 '22 at 13:10
  • The second is that if there is no dirty bed, this means dirty beds are being cleaned now or wait until patients are discharged or goes to the IU(the event of a dirty bed occurs.) When an ED bed is assigned, the patient is treated by a doctor. After examination of doctor, the patient will either go to the IU or be discharged. When the patient goes to the IU, if there is a clean bed in IU, the ED bed is returned immediately, but if not, the patient must continue to wait in the ED bed. – Justin King Apr 07 '22 at 13:13
  • And if the patient is discharged from the hospital, the ED bed will be returned, but cleaning will not proceed immediately.(Leave it dirty until the cleaning order is issued) Dirty ED beds are cleaned on request and then assigned to new arriving patients. Anyway, the part where I'm having a hard time implementing my program right now is that the part where assigning the ED bed is not being implemented as I intended. – Justin King Apr 07 '22 at 13:14
  • 1
    so in my code, putting the dirty bed into the dirty bed queue, queues the bed for cleaning by the cleaners. If the patient only get clean beds then you can delete the part from my code where the patient requests a dirty bed and only patient requests a clean beds, dropping all the any of stuff. Start a new question and post your new code – Michael Apr 07 '22 at 16:03
  • Thank you Michael, I posted it and this is the link: https://stackoverflow.com/questions/71787074/how-to-simulation-allow-to-wait-until-the-next-process-is-resolved – Justin King Apr 07 '22 at 18:13