Chapter 4 Bottleneck Analysis
In Chick-fil-A, as the flow chart suggests, the potential bottleneck resources are workers at the ordering station and the pickup station, as well as car spaces in the pickup lane. The strategy we employ to find the bottleneck is to increase the amount of one focal resource while holding others constant. For each parameter setting, we compute the performance metric, such as the average flow time, using the DES simulator functions. Finally, we claim a resource is a bottleneck if the increment in its amount greatly improves the performance metric.
4.1 Simulator functions
We first organize the simulators before and after the renovation in two functions that take model parameters as input and return the performance metrics as output.
Specifically, the simulator of Chick-fil-A before renovation is as follows.
################################################################################
########### Simulator before renovation ########################################
################################################################################
=function(num_cashier,num_cook,space_pickup){
DES_before= trajectory("Customer's path") %>%
customer set_attribute("lane", function() {
get_server_count(env, "lane1")>=get_server_count(env, "lane2"))*1+1}
(%>%
) select(function() {
paste0("lane",get_attribute(env, "lane"))
%>%
}) seize_selected(1) %>% # Choose the shortest ordering lane
seize("cashier",amount=1) %>% # Try to seize a cashier for placing the order
timeout(function() {rexp(1, mu_cashier)}) %>%
release("cashier",amount=1) %>%
seize("lane_pickup",amount=1) %>% # Try to seize a car space in pickup lane
release_selected(1) %>% # Once going to the pickup lane, the customer releases the ordering lane
seize("cook",amount=1) %>% # Try to seize a cook for preparing the order
timeout(function() {rexp(1, mu_cook)}) %>%
release("cook",amount=1) %>%
release("lane_pickup") # After the order is ready, the car immediately leaves the system
= trajectory() %>% # A dummy trajectory for recording the system carline
dummy set_attribute("carline_ordering",function() {
= max(get_server_count(env,"lane1"),get_server_count(env,"lane2"))}) %>%
carline1 set_attribute("carline_pickup",function() {
= get_server_count(env,"lane_pickup")})
carline2
= simmer()
env
= 500
num_rep =data.frame(repetition=1:num_rep,system_carline=1:num_rep,flow_time=1:num_rep)
Data_before
for(ii in 1:num_rep){
print(paste0("Repetition ",ii))
= simmer()
env %>%
env add_resource("cashier", num_cashier) %>% # 2 Cashiers for taking orders
add_resource("lane1", capacity=space_ordering) %>% # Ample car spaces in ordering lane1
add_resource("lane2", capacity=space_ordering) %>% # Ample car spaces in ordering lane2
add_resource("lane_pickup", capacity=space_pickup) %>% # 6 car spaces in pick up lane
add_resource("cook", num_cook) %>% # 3 Cooks for preparing the orders
add_generator("Customer", customer, function() rexp(1, arrival_rate), mon=2) %>% # Customer's arrival process
add_generator("Dummy recorder", dummy, function() 1, mon=2) %>% # Dummy trajectory records every 1 minute
run(simTime)
## average system carline length
= get_mon_attributes(env)
df_att = df_att[substr(df_att$name,1,1)=='D',] # only look at dummy recorder
df_att = df_att[df_att$key=="carline_ordering",'value']
ordering
= df_att[df_att$key=="carline_pickup",'value']
pickup
= ordering + pickup
system_carline
'system_carline']=mean(system_carline[500:length(system_carline)])
Data_before[ii,
## average flow time
= get_mon_arrivals(env)
df_arr = df_arr[substr(df_arr$name,1,1)=='C',] # picking customer's data only
df_arr
$flow_time = df_arr$end_time - df_arr$start_time
df_arr
'flow_time']=mean(df_arr$flow_time[200:nrow(df_arr)])
Data_before[ii,
}return(list(flow_time=mean(Data_before$flow_time),system_carline=mean(Data_before$system_carline)))
}
And, the simulator of Chick-fil-A after renovation is given by:
################################################################################
########### Simulator after renovation ########################################
################################################################################
=function(num_cashier,num_cook,space_pickup){
DES_after= trajectory("Customer's path") %>%
customer set_attribute("lane", function() {
get_server_count(env, "lane1")>=get_server_count(env, "lane2"))*1+1}
(%>%
) select(function() {
paste0("lane",get_attribute(env, "lane"))
%>%
}) seize_selected(1) %>% # Choose the shortest ordering lane
seize("cashier",amount=1) %>% # Try to seize a cashier for placing the order
timeout(function() {rexp(1, mu_cashier)}) %>%
release("cashier",amount=1) %>%
select(function() {ifelse(get_attribute(env, "lane")==1,"lane1pickup","lane2pickup")}, id=2) %>%
seize_selected(amount=1, id=2) %>% # Try to go on to the corresponding pickup lane
release_selected(1) %>% # Once going to the pickup lane, release the ordering lane
seize("cook",amount=1) %>% # Try to seize a cook for preparing the order
timeout(function() {rexp(1, mu_cook)}) %>%
release("cook",amount=1) %>%
release_selected(amount=1, id=2) # After the order is ready, the car immediately leaves the system
= trajectory() %>% # A dummy trajectory for recording the system carline of lane1 and lane2
dummy set_attribute("carline_lane1",function() {
= get_server_count(env,"lane1")+get_server_count(env,"lane1pickup")}) %>%
carline1 set_attribute("carline_lane2",function() {
= get_server_count(env,"lane2")+get_server_count(env,"lane2pickup")})
carline2
= simmer()
env
= 500
num_rep =data.frame(repetition=1:num_rep,system_carline=1:num_rep,flow_time=1:num_rep)
Data_after
for(ii in 1:num_rep){
print(paste0("Repetition ",ii))
= simmer()
env %>%
env add_resource("cashier", num_cashier) %>% # Cashiers for taking orders
add_resource("lane1", capacity=space_ordering) %>% # Ample car spaces in ordering lane1
add_resource("lane2", capacity=space_ordering) %>% # Ample car spaces in ordering lane2
add_resource("lane1pickup", capacity=space_pickup) %>% # Car spaces in pick up lane1
add_resource("lane2pickup", capacity=space_pickup) %>% # Car spaces in pick up lane2
add_resource("cook", num_cook) %>% # 3 Cooks for preparing the orders
add_generator("Customer", customer, function() rexp(1, arrival_rate), mon=2) %>% # Customer's arrival process
add_generator("Dummy recorder", dummy, function() 1, mon=2) %>% # Dummy trajectory records every 1 minute
run(simTime)
## average system carline length
= get_mon_attributes(env)
df_att = df_att[substr(df_att$name,1,1)=='D',] # Extract values for the dummy recorder
df_att = df_att[df_att$key=='carline_lane1',]
carline1 = df_att[df_att$key=='carline_lane2',]
carline2 = pmax(carline1$value,carline2$value)
system_carline
'system_carline']=mean(system_carline[500:length(system_carline)])
Data_after[ii,
## average flow time
= get_mon_arrivals(env)
df_arr = df_arr[substr(df_arr$name,1,1)=='C',] # picking customer's data only
df_arr
$flow_time = df_arr$end_time - df_arr$start_time
df_arr
'flow_time']=mean(df_arr$flow_time[200:nrow(df_arr)])
Data_after[ii,
}return(list(flow_time=mean(Data_after$flow_time),system_carline=mean(Data_after$system_carline)))
}
4.2 Identifying bottleneck
We consider the bottleneck of the Chick-fil-A drive-thru before renovation.
4.2.1 Vary the number of cashiers
First, we only vary the number of cashiers from 1 to 4.
################################################################################
############ Vary the number of cashier ########################################
################################################################################
= data.frame(num_cashier=as.numeric(),num_cook=as.numeric(),space_pickup=as.numeric(),flow_time=as.numeric(),system_carline=as.numeric())
Bottleneck_cashier
# num_cashier=2
=3
num_cook=6
space_pickup
for(num_cashier in 1:4){
= DES_before(num_cashier,num_cook, space_pickup)
out = rbind(Bottleneck_cashier, data.frame(num_cashier=num_cashier,num_cook=num_cook,space_pickup=space_pickup,flow_time=out$flow_time,system_carline=out$system_carline))
Bottleneck_cashier }
Plotting the results.
$ordering_line=Bottleneck_cashier$system_carline-Bottleneck_cashier$space_pickup
Bottleneck_cashier
= melt(Bottleneck_cashier,measure.vars=c("flow_time","system_carline","ordering_line"))
data_plot
ggplot(data_plot, aes(x=num_cashier,y=value,shape=variable,linetype=variable)) + theme_bw()+
geom_line() +
geom_point(size=5)+
labs(title="Varying the number of cashiers",x="Number of cashiers", y = "Performance metric")+
scale_shape_manual(name = "Metric",
labels = c("Flow time", "System carline", "Ordering carline"),
values = c(16,2,3))+
scale_linetype_discrete(guide = "none") +
theme(text=element_text(size=20),legend.text=element_text(size=20))
4.2.2 Vary the number of cooks
Second, we vary the number of cooks, while keeping the numbers of cashiers and car spaces constant.
################################################################################
############ Vary the number of cook ###########################################
################################################################################
= data.frame(num_cashier=as.numeric(),num_cook=as.numeric(),space_pickup=as.numeric(),flow_time=as.numeric(),system_carline=as.numeric())
Bottleneck_cook
=2
num_cashier#num_cook=3
=6
space_pickup
for(num_cook in 3:5){
= DES_before(num_cashier,num_cook, space_pickup)
out = rbind(Bottleneck_cook, data.frame(num_cashier=num_cashier,num_cook=num_cook,space_pickup=space_pickup,flow_time=out$flow_time,system_carline=out$system_carline))
Bottleneck_cook }
Plotting the results.
$ordering_line=Bottleneck_cook$system_carline-Bottleneck_cook$space_pickup
Bottleneck_cook
= melt(Bottleneck_cook,measure.vars=c("flow_time","system_carline","ordering_line"))
data_plot
ggplot(data_plot, aes(x=num_cook,y=value,shape=variable,linetype=variable)) + theme_bw()+
geom_line() +
geom_point(size=5)+
labs(title="Varying the number of cooks",x="Number of cooks", y = "Performance metric")+
scale_shape_manual(name = "Metric",
labels = c("Flow time", "System carline", "Ordering carline"),
values = c(16,2,3))+
scale_linetype_discrete(guide = "none") +
theme(text=element_text(size=20),legend.text=element_text(size=20))
4.2.3 Vary the number of car spaces
Finally, we vary the number of car spaces in the pickup lane.
################################################################################
############ Vary the number of car space ######################################
################################################################################
= data.frame(num_cashier=as.numeric(),num_cook=as.numeric(),space_pickup=as.numeric(),flow_time=as.numeric(),system_carline=as.numeric())
Bottleneck_car
=2
num_cashier=3
num_cook#space_pickup=6
for(space_pickup in 4:10){
print(num_cook)
= DES_before(num_cashier,num_cook, space_pickup)
out = rbind(Bottleneck_car,
Bottleneck_car data.frame(num_cashier=num_cashier,num_cook=num_cook,space_pickup=space_pickup,flow_time=out$flow_time,system_carline=out$system_carline))
}
Plotting the results.
$ordering_line=Bottleneck_car$system_carline-Bottleneck_car$space_pickup
Bottleneck_car
= melt(Bottleneck_car,measure.vars=c("flow_time","system_carline","ordering_line"))
data_plot
ggplot(data_plot, aes(x=space_pickup,y=value,shape=variable,linetype=variable)) + theme_bw()+
geom_line() +
geom_point(size=5)+
labs(title="Varying the number of car spaces \n at the pickup station",x="Number of car spaces", y = "Performance metric")+
scale_shape_manual(name = "Metric",
labels = c("Flow time", "System carline", "Ordering carline"),
values = c(16,2,3))+
scale_linetype_discrete(guide = "none") +
theme(text=element_text(size=20),legend.text=element_text(size=20))
4.2.4 Compile the results
Figure 4.1 compiles the results of the above bottleneck analysis. Specifically, in the original model, the facility is staffed with 2 cashiers at the ordering lane and 3 cooks at the pickup station, and there are 6 car spaces in the pickup lane. Figure 4.1 (a) varies the number of cashiers from 1 to 4 while the numbers of cooks and car spaces do not change from the original setting. Similarly, Figures 4.1 (b) and (c) vary the number of cooks and the number of car spaces, respectively. In addition to the flow time and system carline, we add another metric, ordering carline, which is the longer queue among two ordering lanes.
We summarize our findings as follows.
- The current staffing level at the ordering station is sufficient and should not be reduced. Increasing the number of cashiers from 2 does not improve the system performance, but reducing the number of cashier workers may significantly increase the congestion.
- The current number of cooks at the pickup station is insufficient, as one additional cook (i.e., staffing level raises from 3 to 4) could significantly reduce congestion measured by all three metrics. However, the benefit of employing more than 4 cooks is marginal.
- Increasing the car spaces in the pickup lane does not have a clear impact on the flow time but negatively affects the system carline.
- This observation regarding flow time is because the additional car spaces do not contribute to a quicker service at the drive-thru.
- Moreover, as we increase the car spaces in the pickup lane, cars are more likely to move from the ordering station to the pickup station. Since there is only one pickup lane, a merge from two ordering lanes to a single pickup lane will typically result in a longer queue of cars.
- Finally, increasing the capacity of the pickup lane effectively shortens the queue at the ordering station, as cars have a higher chance of leaving the ordering station and moving to the pickup lane.
According to the above analysis, the worker who prepares the orders (i.e., cook) is the bottleneck resource that causes customers’ long wait times, and the car space at the pickup station is the bottleneck resource that causes a long carline at the ordering station. Because the renovation of Chick-fil-A at the Marketplace Mall mainly redesigned the organization of the car spaces, it would have no impact on customers’ waiting time.
Nevertheless, additional car spaces at the pickup station reduce the ordering queue. Since the system carline combines the carlines at both the pickup and the ordering stations, the design of two pickup lanes successfully reduces the carline at the pickup station. Therefore, we observe a reduction in the system carline after renovation.