DATA 604 - Week 3 Assignment

Bikash Bhowmik —- 21 Feb 2026

Column

Column

Instructions

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:

    1. narrative of the process and attributes considered

    2. analysis of bottlenecks in the queue

    3. suggested changes

    4. comparison of results

Introduction

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.

Load Packages

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 10
simmer 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 }
env %>% run(until = 60)
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 }
get_mon_arrivals(env)
          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 }
env2 %>% run(until = 60)
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 }
get_mon_arrivals(env2)
    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.

Comparison

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"
)
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.

Conclusion

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.