Search code examples
pythonsimulationyieldsimpyevent-simulation

Simpy - service time dependent on length of queue and resources availability


I want to modify this code to solve the following problem: Lets say we have two nurses. If both nurses are free then a patient can use both nurses and the treatment time is cut in half. A patient can use both nurses only if there aren't any patients waiting in the queue. If there are two (or more) patients in queue then we take 2 patients off the queue and assign them to each nurse.

import simpy
import numpy as np

np.random.seed(1)

# Define arrival and service time distributions
def draw_arrival_time():
    # Use an exponential distribution for arrival times 
    return np.random.exponential(2.0)

def draw_service_time():
    # Use a normal distribution for service times 
    return np.random.normal(5.0, 1.0)

# Arrivals generator function
def patient_generator(env, nurse):
    patient_id = 1

    while True:
        # Create an instance of activity generator
        patient = treatment_activity(env, nurse, patient_id)

        # Run the activity generator for this patient
        env.process(patient)

        # Sample time until next arrival
        next_arrival = draw_arrival_time()

        # Freeze until that time has passed
        yield env.timeout(next_arrival)
        patient_id += 1

def treatment_activity(env, nurse, patient_id):
    # Patient arrival
    time_entered_queue = env.now
    print("Patient", patient_id, "arrived at", time_entered_queue)

    # Request the nurse
    with nurse.request() as req:
        yield req

        # Calculate the time patient left the queue
        time_left_queue = env.now
        print("Patient", patient_id, "started treatment at", time_left_queue)

        # Calculate the waiting time for this patient
        waiting_time = time_left_queue - time_entered_queue

        # Append the waiting time to the waiting_times list
        waiting_times.append(waiting_time)

        # Sample treatment time
        treatment _time = draw_service_time()

        yield env.timeout(treatment_time)

# Set up simulation environment
env = simpy.Environment()

# Set up resources
nurse = simpy.Resource(env, capacity=1)

# Set up parameter values
waiting_times = []

# Start patient arrivals generator
env.process(patient_generator(env, nurse))

# Run the simulation
env.run(until=90)

# Calculate and print statistics
average_waiting_time = np.mean(waiting_times)
print("Average Waiting Time:", average_waiting_time)

I tried using a resource with capacity 2 nurse = simpy.Resource(env, capacity=2) but this didn't work. Then using two nurses with capacity 1 each, but I couldn't keep track of the queue and didn't how to check if both are free.


Solution

  • Here is a quick example of a patient grabbing one or two nurses and setting the treatment time base on the nurse resource request queue size and the number of available nurses.

    """
    Simple simulation of nusrses treating a patient.
    
    If there is only one patient and both nureses are available,
    both nurses will treat the patient
    with a half the treatment time.
    
    If there is more then one patatient each nurse will take one patient
    
    Programmer: Michael R. Gibbs
    """
    
    import simpy
    
    class Patient():
        """
        Patient that needs a nurse,
        has a id for tracking
        """
    
        last_id = 0
    
        def __init__(self):
        
            self.__class__.last_id += +1
    
            self.id = self.__class__.last_id
    
    def treat_patient(env, patient, nurse_resource):
        """
        Main process where a patient:
        waits for a nurse (maybe 2)
        gets treated
        release the nurses
        """
    
        treat_time = 6
    
        # wait for nurse
        n_req =nurse_resource.request() 
        yield n_req
    
        # list of nurses to be released later
        n_reqs = [n_req]
    
        # chech if patient queue is empty
        if len(nurse_resource.queue ) == 0:
            
            #check if second nurse is available
            if nurse_resource.capacity - nurse_resource.count >= 1:
                # get second nurse and cut treatment time
                n_req_2 = nurse_resource.request()
                yield n_req_2
    
                n_reqs.append(n_req_2)
                treat_time = treat_time / 2
    
        # start treatment (note no yield here, just drop and go)
        print(f'{env.now:.1f}: patient {patient.id} is being treated by {len(n_reqs)} nurses')
    
        yield env.timeout(treat_time)
    
        # release all the nurses, could be 1 or 2
        for req in n_reqs:
            nurse_resource.release(req)
    
        print(f'{env.now:.1f}: patient {patient.id} treatment is finished by {len(n_reqs)} nurses')
    
    def gen_patients(env, nurse_resource):
        yield env.timeout(1)
        patient = Patient()
        env.process(treat_patient(env, patient, nurse_resource))
        print(f'{env.now:.1f}: patient {patient.id} has arrived')
    
        yield env.timeout(1)
        patient = Patient()
        env.process(treat_patient(env, patient, nurse_resource))
        print(f'{env.now:.1f}: patient {patient.id} has arrived')
    
        yield env.timeout(1)
        patient = Patient()
        env.process(treat_patient(env, patient, nurse_resource))
        print(f'{env.now:.1f}: patient {patient.id} has arrived')
    
        yield env.timeout(30)
        patient = Patient()
        env.process(treat_patient(env, patient, nurse_resource))
        print(f'{env.now:.1f}: patient {patient.id} has arrived')
    
        yield env.timeout(1)
        patient = Patient()
        env.process(treat_patient(env, patient, nurse_resource))
        print(f'{env.now:.1f}: patient {patient.id} has arrived')
    
    
    
    env = simpy.Environment()
    nurse_resource = simpy.Resource(env, capacity=2)
    env.process(gen_patients(env, nurse_resource))
    
    env.run(100)