Identify a routinary activity that requires a queue to be managed, and document it. Highlight the attributes of the entities involved and the estimated times allotted for the execution of each step.
Create a Simio, or Python, or R model that resembles this activity. Identify bottlenecks in the process as it was initially documented, and suggest how it can be improved. Apply the suggested changes to the Simio model and compare the results of the original and modified process.
Deliverables:
Simio, Python or R file with original process and modified process
Document with:
narrative of the process and attributes considered
analysis of bottlenecks in the queue
suggested changes
comparison of results
For this assignment, I selected a simple routine activity: waiting in line at a pharmacy checkout counter. Customers arrive at the counter individually and require assistance from a pharmacist to complete their transaction. Each transaction takes approximately 5 minutes to process. If the pharmacist is already assisting another customer, newly arriving customers must wait in a queue until service becomes available. In the original process configuration, there is only one service counter, meaning all customers are served sequentially by a single staff member.
I have loaded necessary packages.I have installed package install.packages(“simmer”)
library(simmer)
library(dplyr)
library(ggplot2)
Original process with only 1 pharmacist
# Initialize the simulation environment for the pharmacy checkout system
env <- simmer("pharmacy_checkout_counter")
# Define the customer trajectory through the system
# Each customer must wait until a pharmacist is available,
# receives service for 5 minutes, and then exits the system
customer <- trajectory("customer path") %>%
seize("pharmacist", 1) %>% # Request one pharmacist for service
timeout(5) %>% # Service time of 5 minutes per customer
release("pharmacist", 1) # Release the pharmacist after service completion
# Add resources and customer arrivals to the simulation environment
env %>%
add_resource("pharmacist", capacity = 1) %>% # Single pharmacist available (original system)
add_generator("pharmacist", customer, at(1:10)) # Ten customers arrive at minutes 1 through 10simmer environment: pharmacy_checkout_counter | now: 0 | next: 0
{ Monitor: in memory }
{ Resource: pharmacist | monitored: TRUE | server status: 0(1) | queue status: 0(Inf) }
{ Source: pharmacist | monitored: 1 | n_generated: 0 }
simmer environment: pharmacy_checkout_counter | now: 51 | next:
{ Monitor: in memory }
{ Resource: pharmacist | monitored: TRUE | server status: 0(1) | queue status: 0(Inf) }
{ Source: pharmacist | monitored: 1 | n_generated: 10 }
name start_time end_time activity_time finished replication
1 pharmacist0 1 6 5 TRUE 1
2 pharmacist1 2 11 5 TRUE 1
3 pharmacist2 3 16 5 TRUE 1
4 pharmacist3 4 21 5 TRUE 1
5 pharmacist4 5 26 5 TRUE 1
6 pharmacist5 6 31 5 TRUE 1
7 pharmacist6 7 36 5 TRUE 1
8 pharmacist7 8 41 5 TRUE 1
9 pharmacist8 9 46 5 TRUE 1
10 pharmacist9 10 51 5 TRUE 1
The bottleneck in this process is the pharmacist resource. Since only one pharmacist is available to serve customers, periods of high customer arrivals result in a growing queue. Customers arriving during these busy periods must wait until the pharmacist becomes available, which leads to increased waiting times for subsequent arrivals.
Improved process with 3 pharmacists
env2 <- simmer("pharmacy_checkout_counter_improved")
env2 %>% add_resource("pharmacist", capacity = 3) %>% add_generator("cust", customer, at(1:10))simmer environment: pharmacy_checkout_counter_improved | now: 0 | next: 0
{ Monitor: in memory }
{ Resource: pharmacist | monitored: TRUE | server status: 0(3) | queue status: 0(Inf) }
{ Source: cust | monitored: 1 | n_generated: 0 }
simmer environment: pharmacy_checkout_counter_improved | now: 21 | next:
{ Monitor: in memory }
{ Resource: pharmacist | monitored: TRUE | server status: 0(3) | queue status: 0(Inf) }
{ Source: cust | monitored: 1 | n_generated: 10 }
name start_time end_time activity_time finished replication
1 cust0 1 6 5 TRUE 1
2 cust1 2 7 5 TRUE 1
3 cust2 3 8 5 TRUE 1
4 cust3 4 11 5 TRUE 1
5 cust4 5 12 5 TRUE 1
6 cust5 6 13 5 TRUE 1
7 cust6 7 16 5 TRUE 1
8 cust7 8 17 5 TRUE 1
9 cust8 9 18 5 TRUE 1
10 cust9 10 21 5 TRUE 1
To improve and expedite the process, two additional pharmacists are added to the model. With a total of three pharmacists available, multiple customers can be served simultaneously. This parallel service structure significantly reduces customer waiting times and alleviates congestion in the queue.
In the original model with one pharmacist, customers experienced noticeable waiting times, particularly during periods of high arrival rates. In the improved model with three pharmacists, most customers were able to begin service immediately, and the queue was largely eliminated. This comparison demonstrates that increasing service capacity by adding two additional service providers can substantially improve system efficiency and significantly reduce customer waiting times.
Boxplot Comparison of Waiting Times
1 pharmacist → large spread, many long waits
3 pharmacists → small spread, mostly near zero
Clearly visualizes bottleneck reduction
# Extract arrivals from both models
arr1 <- get_mon_arrivals(env) %>%
mutate(
waiting_time = end_time - start_time - activity_time,
model = "1 Pharmacist"
)
arr2 <- get_mon_arrivals(env2) %>%
mutate(
waiting_time = end_time - start_time - activity_time,
model = "3 Pharmacists"
)
# Combine results
arrivals <- bind_rows(arr1, arr2)
# Plot comparison
ggplot(arrivals, aes(x = model, y = waiting_time, fill = model)) +
geom_boxplot(alpha = 0.7) +
labs(
title = "Waiting Time Distribution Comparison",
x = "Model",
y = "Waiting Time (minutes)"
) +
theme_minimal()# Get resource monitoring data
res1 <- get_mon_resources(env)
res2 <- get_mon_resources(env2)
# Calculate utilization
util1 <- res1 %>%
summarise(
utilization = sum(server * diff(c(time, 60))) / 60,
model = "1 Pharmacist"
)
util2 <- res2 %>%
summarise(
utilization = sum(server * diff(c(time, 60))) / (60 * 3),
model = "3 Pharmacists"
)
# Combine results
utilization <- bind_rows(util1, util2)
utilization utilization model
1 0.8333333 1 Pharmacist
2 0.2777778 3 Pharmacists
Performance Metrics Comparison
calculate_metrics <- function(env, capacity, sim_time, model_name) {
# Arrival metrics
arrivals <- get_mon_arrivals(env) %>%
mutate(
waiting_time = end_time - start_time - activity_time,
time_in_system = end_time - start_time
)
# Resource metrics
resources <- get_mon_resources(env)
# Average queue length (time-weighted)
avg_queue <- sum(resources$queue * c(diff(resources$time), 0)) / sim_time
# Utilization (time-weighted)
utilization <- sum(resources$server * c(diff(resources$time), 0)) /
(sim_time * capacity)
# Output table
data.frame(
Model = model_name,
Customers_Served = nrow(arrivals),
Avg_Waiting_Time = round(mean(arrivals$waiting_time), 2),
Max_Waiting_Time = round(max(arrivals$waiting_time), 2),
Avg_Time_in_System = round(mean(arrivals$time_in_system), 2),
Avg_Queue_Length = round(avg_queue, 2),
Utilization = round(utilization, 2)
)
}
metrics1 <- calculate_metrics(
env = env,
capacity = 1,
sim_time = 60,
model_name = "1 Pharmacist"
)
metrics2 <- calculate_metrics(
env = env2,
capacity = 3,
sim_time = 60,
model_name = "3 Pharmacists"
)
performance_table <- rbind(metrics1, metrics2)knitr::kable(
performance_table,
align = "c",
caption = "Performance Metrics Comparison: 1 vs 3 Pharmacists"
)| Model | Customers_Served | Avg_Waiting_Time | Max_Waiting_Time | Avg_Time_in_System | Avg_Queue_Length | Utilization |
|---|---|---|---|---|---|---|
| 1 Pharmacist | 10 | 18.0 | 36 | 23.0 | 3.0 | 0.83 |
| 3 Pharmacists | 10 | 2.4 | 6 | 7.4 | 0.4 | 0.28 |
The performance metrics confirm that the Pharmacist was the system bottleneck in the original configuration. With one Pharmacist, utilization was extremely high, leading to long queues and excessive waiting times. After increasing capacity to three Pharmacists, the system achieved significantly lower waiting times and queue lengths while maintaining reasonable utilization levels. This demonstrates that increasing service capacity effectively reduces congestion and improves overall system efficiency.
This simulation demonstrates how increasing service capacity in a single-server queueing system significantly reduces waiting time, queue length, and congestion, highlighting the importance of resource planning in service operations.