'How can I keep track of the information of multiple objects where there are various events in Simpy?

I tried to build a simulation model as below, but I got error. Here's my scenario. I have two big areas of hospital.

  1. EU (EU1 and EU2)

  2. IU (IU1 and IU2)

  3. Each big area has two care areas and patient transfer from EU to IU (EU1 to IU1 or EU1 to IU2 or EU2 to IU1 or EU2 to IU2)

  4. Transporter move the patient from EU to IU

  5. Cleaner clean all beds, but it doesn't matter about the care areas.

  6. However, the beds that have been cleaned, the bed should put each care area clean lit.

  7. Sometimes, the bed that the patient needs and the bed that will be cleaned are different. -> I can say this is "Not matched case". For example, if a EU1 patient needs a EU1 bed cleaning, but the top of queue bed is a IU1 bed(or EU2 or IU2, NOT EU1 bed). So, the EU1 patient should wait until the EU1 bed come to queue and the bed is cleaned.

  8. This is also happen to when a IU patient need IU bed cleaning

  9. Maybe the order of cleaning bed queue should changeable by cleaning request later

import simpy
import random

class Pre_Define:
    eu_beds= 10
    iu_beds= 10

class Bed():
    def __init__(self, id, request, care_area, name):
        self.id = id
        self.request = request
        self.care_area = care_area
        self.name = name
        self.pt_info = None
        self.priority = 2

class Patients:
    def __init__(self, p_id):
        self.id = p_id
        self.request = None
        self.edbed_info = None
        self.iubed_info = None
        self.care_area = None
        self.admission_decision = ""
        self.priority = 2
    def admin_decision(self):
        admin_decision_prob = random.uniform(0, 1)
        if admin_decision_prob <= 0.3:
            self.admission_decision = "DIS"
        elif admin_decision_prob > 0.3 and admin_decision_prob <= 0.6:
            self.admission_decision = "IU1"
        elif admin_decision_prob > 0.6:
            self.admission_decision = "IU2"
        return self.admission_decision
    
class Model:
    def __init__(self, run_number):
        self.env = simpy.Environment()
        self.pt_ed_q = simpy.Store(self.env)
        self.pt_counter = 0
        self.tg = simpy.Resource(self.env, capacity = 4)
        self.physician = simpy.Resource(self.env, capacity = 4)
        self.bed_cleaner = simpy.Resource(self.env, capacity = 7)
        self.bed_transporter = simpy.Resource(self.env, capacity = 5)
        
        self.iu1_bed_clean = simpy.Store(self.env, capacity = 5)
        self.iu2_bed_clean = simpy.Store(self.env, capacity = 5)
        self.eu1_bed_clean = simpy.Store(self.env, capacity = 5)
        self.eu2_bed_clean = simpy.Store(self.env, capacity = 5)
        
        self.iu1_bed_dirty = simpy.Store(self.env, capacity = 5)
        self.iu2_bed_dirty = simpy.Store(self.env, capacity = 5)
        self.eu1_bed_dirty = simpy.Store(self.env, capacity = 5)
        self.eu2_bed_dirty = simpy.Store(self.env, capacity = 5)
        
        self.clean_request_q = simpy.Store(self.env, capacity = Pre_Define.eu_beds+Pre_Define.iu_beds)
        
        self.bed_dirty = simpy.Store(self.env)
        self.clean_request = simpy.Store(self.env)
        self.pt_q = simpy.PriorityStore(self.env)
        
    def generate_beds(self):
        self.eu1_bed_clean.items = [Bed(i+1, "EU", "EU1", f'EU1bed_{i}') for i in range(Pre_Define.eu_beds)]
        self.eu2_bed_clean.items = [Bed(i+1, "EU", "EU2", f'EU2bed_{i}') for i in range(Pre_Define.eu_beds)]
        self.iu1_bed_clean.items = [Bed(i+1, "IU", "IU1", f'IU1bed_{i}') for i in range(Pre_Define.iu_beds)]
        self.iu2_bed_clean.items = [Bed(i+1, "IU", "IU2", f'IU2bed_{i}') for i in range(Pre_Define.iu_beds)]

    def generate_pt_arrivals(self):
        while True:
            self.pt_counter += 1
            pt = Patients(self.pt_counter)
            carearea_prob = random.uniform(0, 1)
            if carearea_prob <= 0.5:
                pt.care_area = "EU1"
                pt.request = "EU"
            else:
                pt.care_area = "EU2"
                pt.request = "EU"
            yield self.env.timeout(5)
            self.env.process(self.ed_process(pt))

    def clean_beds_process(self, cleaner_id):
        while True:
            bed = yield self.clean_request_q.get()
            print(f'{self.env.now:.2f} Case chedk!! patient {bed.pt_info.id} request bed {bed.pt_info.request} bed in queue {bed.request}')
            # This is the matched case, patient request bed and the first queue in being cleaned bed 
            if (bed.pt_info.request == 'EU' and bed.request == 'EU') or (bed.pt_info.request == 'IU' and bed.request == 'IU'):
                print(f'{self.env.now:.2f} Matched case patient {bed.pt_info.id} get matched bed {bed.pt_info.request} = {bed.request}')
                with self.bed_cleaner.request() as bedreq:
                    yield bedreq
                    if bed.care_area == 'EU1':
                        yield self.eu1_bed_clean.put(bed)
                    elif bed.care_area == 'EU2':
                        yield self.eu2_bed_clean.put(bed)
                    elif bed.care_area == 'IU1':
                        yield self.iu1_bed_clean.put(bed)
                    else:
                        yield self.iu2_bed_clean.put(bed)
            # This is the NOT matched case, so just clean the the first in queue bed and the patient should wait until matching the bed
            else:
                print(f'{self.env.now:.2f} NOT matched case patient {bed.pt_info.id} get matched bed {bed.pt_info.request} != {bed.request}')
                with self.bed_cleaner.request() as bedreq:
                    yield bedreq
                    if bed.care_area == 'EU1':
                        yield self.eu1_bed_clean.put(bed)
                        continue
                    elif bed.care_area == 'EU2':
                        yield self.eu2_bed_clean.put(bed)
                        continue
                    if bed.care_area == 'IU1':
                        yield self.iu1_bed_clean.put(bed)
                        continue
                    else:
                        yield self.iu2_bed_clean.put(bed)
                        continue

    def ed_process(self, pt):
        with self.tg.request() as req:
            yield req
            yield self.env.timeout(15)
        print(f'{self.env.now:.2f} patient {pt.id} has been triaged {pt.care_area}')
        #yield self.pt_q.put(pt)
        #pt = yield self.pt_q.get()
        
        # EU1 case
        if pt.care_area == 'EU1':
            # The case: clean EU bed
            if self.eu1_bed_clean.items != []:
                edbed = yield self.eu1_bed_clean.get()
                # Copy pt info when comparing pt care area and bed care area at "clean_beds_process" function
                edbed.pt_info = pt
                pt.edbed_info = edbed
            # The case: don't have clean EU bed, but have dirty EU bed
            elif self.eu1_bed_dirty.items != []:
                edbed = yield self.eu1_bed_dirty.get()
                edbed.pt_info = pt
                yield self.clean_request_q.put(edbed)
                edbed = yield self.eu1_bed_clean.get()
                edbed.pt_info = pt
                pt.edbed_info = edbed
            # The case: don't have not only clean EU beds, but also dirty EU beds, so we should wait until any EU dirty bed come to list
            else:
                pass # I don't know what I have to do!!!

        # EU2 case    
        elif pt.care_area == 'EU2':
            if self.eu2_bed_clean.items != []:
                edbed = yield self.eu2_bed_clean.get()
                edbed.pt_info = pt
                pt.edbed_info = edbed
            elif self.eu2_bed_dirty.items != []:
                edbed = yield self.eu2_bed_dirty.get()
                edbed.pt_info = pt
                yield self.clean_request_q.put(edbed)
                edbed = yield self.eu2_bed_clean.get()
                edbed.pt_info = pt
                pt.edbed_info = edbed
                # The case: don't have not only clean EU beds, but also dirty EU beds, so we should wait until any EU dirty bed come to list
            else:
                pass # I don't know what I have to do!!!

        with self.physician.request() as req:
            yield req
            yield self.env.timeout(25)
            pt.admin_decision()
            
        # IU1 case
        print(f'{self.env.now:.2f} patient {pt.id} IU case {pt.admission_decision}')
        if pt.admission_decision == "IU1":
            pt.request = "IU"
            # The case that already clean IU beds available
            if self.iu1_bed_clean.items != []:
                iubed = yield self.iu1_bed_clean.get()
                # Copy pt info when comparing pt care area and bed care area
                iubed.pt_info = pt
                pt.iubed_info = iubed
                print(f'{self.env.now:.2f} patient {pt.id} get IU1 bed {iubed.id} direct')
            # The case don't have clean IU beds, so request one of them is cleaned
            elif self.iu1_bed_dirty.items != []:
                iubed = yield self.iu1_bed_dirty.get()
                # Copy pt info when comparing pt care area and bed care area
                iubed.pt_info = pt
                yield self.clean_request_q.put(iubed)
                iubed = yield self.iu1_bed_clean.get()
                iubed.pt_info = pt
                pt.iubed_info = iubed
                print(f'{self.env.now:.2f} patient {pt.id} get IU1 bed {iubed.id} after cleaning')
            # The case don't have not only clean IU beds, but also dirty IU beds, so we should wait until IU dirty bed come to list
            else:
                pass # I don't know what I have to do!!!

            # The case EU bed return base on the care area
            if pt.care_area == 'EU1':
                print(f'{self.env.now:.2f} patient {pt.id} return edbed {pt.edbed_info.id}')
                yield self.eu1_bed_dirty.put(pt.edbed_info)
            else:
                print(f'{self.env.now:.2f} patient {pt.id} return edbed {pt.edbed_info.id}')
                yield self.eu2_bed_dirty.put(pt.edbed_info)
                
            # After waiting bed transporter for moving from EU and IU
            with self.bed_transporter.request() as transreq:
                yield transreq
                
            # After using IU bed, return, put the iubed dirty queue list by care area
            yield self.env.timeout(30)
            if iubed.care_area == 'IU1':
                print(f'{self.env.now:.2f} patient {pt.id} return iubed {pt.edbed_info.id}')
                yield self.iu1_bed_dirty.put(pt.iubed_info)
            else:
                print(f'{self.env.now:.2f} patient {pt.id} return iubed {pt.edbed_info.id}')
                yield self.iu2_bed_dirty.put(pt.iubed_info)
        # IU2 case    
        elif pt.admission_decision == "IU2":
            pt.request = "IU"
            if self.iu2_bed_clean.items != []:
                iubed = yield self.iu2_bed_clean.get()
                # Copy pt info when comparing pt care area and bed care area
                iubed.pt_info = pt
                pt.iubed_info = iubed
                print(f'{self.env.now:.2f} patient {pt.id} get IU2 bed {iubed.id} direct')
            # The case don't have clean IU beds, so request one of them is cleaned
            elif self.iu2_bed_dirty.items != []:
                iubed = yield self.iu2_bed_dirty.get()
                # Copy pt info when comparing pt care area and bed care area
                iubed.pt_info = pt
                yield self.clean_request_q.put(iubed)
                iubed = yield self.iu2_bed_clean.get()
                iubed.pt_info = pt
                pt.iubed_info = iubed
                print(f'{self.env.now:.2f} patient {pt.id} get IU2 bed {iubed.id} after cleaning')
            # The case don't have not only clean IU beds, but also dirty IU beds, so we should wait until IU dirty bed come to list
            else:
                pass # I don't know what I have to do!!!
                
            if pt.care_area == 'EU1':
                yield self.eu1_bed_dirty.put(pt.edbed_info)
            else:
                yield self.eu2_bed_dirty.put(pt.edbed_info)
            # After waiting bed transporter for moving from EU and IU
            with self.bed_transporter.request() as transreq:
                yield transreq
            # After using IU bed, return, put the iubed dirty queue list
            yield self.env.timeout(30)
            if iubed.care_area == 'IU1':
                print(f'{self.env.now:.2f} patient {pt.id} return iubed {pt.edbed_info.id}')
                yield self.iu1_bed_dirty.put(pt.iubed_info)
            else:
                print(f'{self.env.now:.2f} patient {pt.id} return iubed {pt.edbed_info.id}')
                yield self.iu2_bed_dirty.put(pt.iubed_info)
        
        # Discharge case don't need IU bed, just discharge! and return EU bed
        else:
            # Return EU bed and put the eubed dirty queue list
            if pt.care_area == 'EU1':
                print(f'{self.env.now:.2f} patient {pt.id} return edbed {pt.edbed_info.id}')
                yield self.eu1_bed_dirty.put(pt.edbed_info)
            else:
                print(f'{self.env.now:.2f} patient {pt.id} return edbed {pt.edbed_info.id}')
                yield self.eu2_bed_dirty.put(pt.edbed_info)

    def run(self):
        self.env.process(self.generate_pt_arrivals())
        self.generate_beds()
        # creating and starting two cleaners
        for i in range(2):
            self.env.process(self.clean_beds_process(i+1))
        self.env.run(until = 300)

run_model = Model(0)
run_model.run()

I implemented the scenario, but got an error as below.

AttributeError: 'NoneType' object has no attribute 'id'

I think it's because the patient and bed information is not being monitored. Please give me some opinions or ideas to solve this problem. Thank you for your time and work.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source