Hospital Simulation with SimPy: Modelling Patient Flow
Hospitals are complex systems. Patients arrive unpredictably. Resources are constrained. Lives depend on getting it right. Simulation helps.
The Hospital Model
Key elements: - Patients - Arrive with varying urgency - Staff - Doctors, nurses, technicians - Beds - Limited and precious - Departments - A&E, wards, theatres - Pathways - Patient journeys through the system
Emergency Department Simulation
import simpy
import random
class EmergencyDepartment:
def __init__(self, env, config):
self.env = env
self.triage_nurses = simpy.Resource(env, config['triage_nurses'])
self.doctors = simpy.PriorityResource(env, config['doctors'])
self.beds = simpy.Resource(env, config['beds'])
self.stats = {
'patients': [],
'wait_times': [],
'length_of_stay': []
}
def patient_pathway(self, patient_id, severity):
arrival = self.env.now
# Priority: 1 = critical, 5 = minor
priority_map = {'critical': 1, 'urgent': 2, 'standard': 3, 'minor': 4}
priority = priority_map.get(severity, 3)
# Triage
with self.triage_nurses.request() as req:
yield req
yield self.env.timeout(random.uniform(3, 8))
triage_complete = self.env.now
# Wait for doctor (priority queue)
with self.doctors.request(priority=priority) as req:
yield req
wait_for_doctor = self.env.now - triage_complete
self.stats['wait_times'].append({
'patient': patient_id,
'severity': severity,
'wait': wait_for_doctor
})
# Treatment time varies by severity
treatment_times = {
'critical': (30, 60),
'urgent': (20, 45),
'standard': (15, 30),
'minor': (10, 20)
}
min_t, max_t = treatment_times.get(severity, (15, 30))
yield self.env.timeout(random.uniform(min_t, max_t))
# Some patients need admission
admission_rates = {'critical': 0.8, 'urgent': 0.5, 'standard': 0.2, 'minor': 0.05}
if random.random() < admission_rates.get(severity, 0.2):
with self.beds.request() as req:
yield req
yield self.env.timeout(random.uniform(60, 240)) # Ward stay
departure = self.env.now
self.stats['length_of_stay'].append(departure - arrival)
self.stats['patients'].append({
'id': patient_id,
'severity': severity,
'arrival': arrival,
'departure': departure,
'los': departure - arrival
})
def patient_arrivals(env, ed, arrival_rate):
patient_id = 0
severity_probs = [0.05, 0.15, 0.40, 0.40] # critical, urgent, standard, minor
severities = ['critical', 'urgent', 'standard', 'minor']
while True:
yield env.timeout(random.expovariate(arrival_rate))
severity = random.choices(severities, weights=severity_probs)[0]
env.process(ed.patient_pathway(patient_id, severity))
patient_id += 1
# Run simulation
env = simpy.Environment()
ed = EmergencyDepartment(env, {
'triage_nurses': 2,
'doctors': 3,
'beds': 20
})
env.process(patient_arrivals(env, ed, arrival_rate=1/10)) # ~6 per hour
env.run(until=480) # 8-hour shift
Ward Model
class HospitalWard:
def __init__(self, env, name, beds, nurses_per_shift):
self.env = env
self.name = name
self.beds = simpy.Resource(env, capacity=beds)
self.nurses = simpy.Resource(env, capacity=nurses_per_shift)
self.patients_discharged = 0
def admit_patient(self, patient_id, expected_los):
"""Admit patient, occupy bed for length of stay."""
with self.beds.request() as bed:
yield bed
print(f"Patient {patient_id} admitted to {self.name} at {self.env.now}")
# Daily care rounds
while self.env.now < expected_los:
with self.nurses.request() as nurse:
yield nurse
yield self.env.timeout(random.uniform(10, 30)) # Care time
yield self.env.timeout(random.uniform(60, 120)) # Time between rounds
print(f"Patient {patient_id} discharged from {self.name} at {self.env.now}")
self.patients_discharged += 1
Operating Theatre
class OperatingTheatre:
def __init__(self, env, num_theatres):
self.env = env
self.theatres = simpy.Resource(env, capacity=num_theatres)
self.surgeons = simpy.Resource(env, capacity=num_theatres)
self.anaesthetists = simpy.Resource(env, capacity=num_theatres)
self.surgeries_completed = 0
def surgery(self, patient_id, surgery_type):
"""Perform surgery requiring theatre, surgeon, and anaesthetist."""
surgery_times = {
'minor': (30, 60),
'major': (120, 240),
'emergency': (60, 180)
}
min_t, max_t = surgery_times.get(surgery_type, (60, 120))
# Need all three resources
with self.theatres.request() as theatre:
yield theatre
with self.surgeons.request() as surgeon:
yield surgeon
with self.anaesthetists.request() as anaes:
yield anaes
# Pre-op
yield self.env.timeout(15)
# Surgery
yield self.env.timeout(random.uniform(min_t, max_t))
# Recovery (still in theatre)
yield self.env.timeout(30)
self.surgeries_completed += 1
print(f"Surgery for patient {patient_id} completed at {self.env.now}")
Clinic Appointments
class OutpatientClinic:
def __init__(self, env, num_consultants, appointment_slots_per_hour):
self.env = env
self.consultants = simpy.Resource(env, capacity=num_consultants)
self.scheduled_appointments = simpy.Store(env)
self.patients_seen = 0
self.no_shows = 0
def schedule_appointment(self, patient_id, appointment_time):
"""Schedule an appointment."""
yield self.scheduled_appointments.put({
'patient_id': patient_id,
'time': appointment_time
})
def run_clinic(self, start_time, end_time):
"""Run clinic for a session."""
yield self.env.timeout(start_time - self.env.now)
while self.env.now < end_time:
try:
appt = yield self.scheduled_appointments.get()
# No-show probability
if random.random() < 0.1:
self.no_shows += 1
continue
with self.consultants.request() as req:
yield req
yield self.env.timeout(random.uniform(10, 25))
self.patients_seen += 1
except simpy.Interrupt:
break
Ambulance and A&E Integration
class AmbulanceService:
def __init__(self, env, num_ambulances, hospital_ed):
self.env = env
self.ambulances = simpy.Resource(env, capacity=num_ambulances)
self.hospital_ed = hospital_ed
self.calls_responded = 0
def respond_to_call(self, call_id, location_distance):
with self.ambulances.request() as req:
yield req
# Travel to scene
travel_time = location_distance / 30 # 30 km/h average
yield self.env.timeout(travel_time)
# On-scene time
yield self.env.timeout(random.uniform(10, 30))
# Travel to hospital
yield self.env.timeout(travel_time)
# Handover to ED
severity = random.choice(['critical', 'urgent', 'standard'])
self.env.process(
self.hospital_ed.patient_pathway(f"amb_{call_id}", severity)
)
# Turnaround time
yield self.env.timeout(15)
self.calls_responded += 1
Complete Hospital Simulation
import simpy
import random
import pandas as pd
class Hospital:
def __init__(self, env, config):
self.env = env
self.config = config
# Resources
self.ed_doctors = simpy.PriorityResource(env, config['ed_doctors'])
self.ed_nurses = simpy.Resource(env, config['ed_nurses'])
self.ed_beds = simpy.Resource(env, config['ed_beds'])
self.ward_beds = simpy.Resource(env, config['ward_beds'])
# Statistics
self.patient_records = []
def ed_patient(self, patient_id, severity, arrival_time):
record = {
'id': patient_id,
'severity': severity,
'arrival': arrival_time
}
# Triage
with self.ed_nurses.request() as req:
yield req
yield self.env.timeout(random.uniform(5, 10))
record['triage_complete'] = self.env.now
# Doctor
priority = {'critical': 1, 'urgent': 2, 'standard': 3, 'minor': 4}[severity]
with self.ed_doctors.request(priority=priority) as req:
yield req
treatment = random.uniform(15, 60)
yield self.env.timeout(treatment)
record['treatment_complete'] = self.env.now
# Admission decision
admission_prob = {'critical': 0.7, 'urgent': 0.4, 'standard': 0.15, 'minor': 0.05}
if random.random() < admission_prob[severity]:
with self.ward_beds.request() as req:
yield req
los = random.uniform(24*60, 72*60) # 1-3 days in minutes
yield self.env.timeout(los)
record['admitted'] = True
record['ward_discharge'] = self.env.now
else:
record['admitted'] = False
record['departure'] = self.env.now
self.patient_records.append(record)
def arrivals(self):
patient_id = 0
severities = ['critical', 'urgent', 'standard', 'minor']
probs = [0.05, 0.20, 0.35, 0.40]
while True:
# Time-varying arrival rate
hour = (self.env.now / 60) % 24
if 8 <= hour <= 20:
rate = 1/8 # Busier during day
else:
rate = 1/15 # Quieter at night
yield self.env.timeout(random.expovariate(rate))
severity = random.choices(severities, weights=probs)[0]
self.env.process(self.ed_patient(patient_id, severity, self.env.now))
patient_id += 1
def run(self, duration):
self.env.process(self.arrivals())
self.env.run(until=duration)
def analyse(self):
df = pd.DataFrame(self.patient_records)
df['wait_for_doctor'] = df['treatment_complete'] - df['triage_complete']
df['ed_los'] = df['treatment_complete'] - df['arrival']
print("\n=== Hospital Simulation Results ===")
print(f"Total patients: {len(df)}")
print(f"Admissions: {df['admitted'].sum()}")
print("\nWait for doctor (minutes):")
print(df.groupby('severity')['wait_for_doctor'].describe())
return df
# Run
random.seed(42)
env = simpy.Environment()
hospital = Hospital(env, {
'ed_doctors': 3,
'ed_nurses': 4,
'ed_beds': 15,
'ward_beds': 50
})
hospital.run(duration=24*60) # 24 hours
results = hospital.analyse()
Summary
Hospital simulation models: - Patient pathways through departments - Resource constraints (staff, beds, equipment) - Priority-based treatment (triage) - Admission and discharge dynamics - Time-varying demand patterns
Simulate to save lives. Optimise to save more.
Next Steps
Build Professional Simulations
Break free from commercial software and learn how to build powerful, industry-standard simulations in Python. The Complete Simulation in Python with SimPy Bootcamp gives you everything you need.
Explore the Bootcamp