Kelton et al. (2014). Simio and Simulation: Modeling, Analysis, Applications (3rd Ed.)

Chapter 4: Problems 2, 3, 9 & 12

library(ggplot2)
library(dplyr)
library(tidyr)

4.2) Develop a queueing model for the Simio model from Problem 1 and compute the exact values for the steady state time entities spend in the system and the expected number of entities processed in 100 hours.

lambda <- 120 / 60
mu <- 190 / 60

rho <- lambda / mu
L <- rho / (1 - rho)
W <- L / lambda
W_q <- W - (1 / mu)
L_q <- W_q * lambda

N_100hrs <- lambda * 100 * 60

knitr::kable(data.frame(W = W, W_q = W_q, L = L, L_q = L_q, rho = rho, N_100hrs = N_100hrs))
W W_q L L_q rho N_100hrs
0.8571429 0.5413534 1.714286 1.082707 0.6315789 12000
# very slow to execute adaptation of manual simulation from model 3-4 in Kelton
# output from single, 100-hour run saved in this chunk and loaded in next 

getTimes <- function(lambda, mu, end_min) {
  n <- 1e5
  times <- data.frame(entity = seq(length.out = n), 
                      arrival = numeric(n), 
                      interarrival = numeric(n),
                      service = numeric(n))
  times[1, 2:3] <- c(0, 0)
  times[1:n, ]$service <- rexp(n, mu)
  times[2:n, ]$interarrival <- rexp(n - 1, lambda)
  for(i in 2:n) {
    times[i, ]$arrival <- times[i - 1, ]$arrival + times[i, ]$interarrival
    if (times[i, ]$arrival >= end_min) {
      break
    }
  }
  times <- times[1:i, ]
  
  return(times)
}

sim <- function(lambda, mu, end_min) {
  times <- getTimes(lambda, mu, end_min)
  n <- 5e4
  event_cal <- data.frame(arrival = rep(NA_real_, n),
                          departure = rep(NA_real_, n),
                          end = rep(end_min, n))
  
  system <- data.frame(event_time = numeric(n), 
                       event_type = numeric(n),
                       entity = integer(n),
                       server_status = logical(n),
                       in_system = integer(n),
                       in_queue = integer(n),
                       in_service = numeric(n),
                       queue_times = I(vector("list", n)))
  
  observations <- data.frame(time_in_system = rep(NA_real_, n),
                             time_in_queue = rep(NA_real_, n),
                             server_busy = numeric(n),
                             number_in_system = numeric(n),
                             number_in_queue = numeric(n))
  
  event_cal[1, ] <- c(times[1, ]$arrival, NA_integer_, end_min) 
  system[1, c(1, 3, 5:7)] <- c(0, NA_integer_, 0, 0, NA_integer_)
  system[1, ]$event_type <- "initialize"
  system[1, ]$server_status <- FALSE
  observations[1, ] <- c(NA_integer_, NA_integer_, 0, 0, 0)
  
  for (i in 2:n) {
    system[i, ]$event_time <- min(event_cal[i - 1, ], na.rm = TRUE)
    system[i, ]$event_type <- colnames(event_cal)[which.min(event_cal[i - 1, ])]
  
    if(system[i, ]$event_type == "end") {
      break
    }
    
    if(system[i, ]$event_type == "arrival") {
      system[i, ]$entity <- max(times[times$arrival == system[i, ]$event_time, ]$entity)
      event_cal[i, ]$arrival <- times[times$entity == system[i, ]$entity + 1, ]$arrival
      system[i, ]$in_system <- system[i - 1, ]$in_system + 1
      
      observations[i, ]$server_busy <- 
        (system[i, ]$event_time - system[i - 1, ]$event_time) *  system[i - 1, ]$server_status
      observations[i, ]$number_in_system <- 
        (system[i, ]$event_time - system[i - 1, ]$event_time) *  system[i - 1, ]$in_system
      observations[i, ]$number_in_queue <- 
        (system[i, ]$event_time - system[i - 1, ]$event_time) *  system[i - 1, ]$in_queue
      
      if (system[i - 1, ]$server_status == FALSE) {
        system[i, ]$in_service <- times[times$entity == system[i, ]$entity, ]$arrival
        event_cal[i, ]$departure <- 
          system[i, ]$event_time + times[times$entity == system[i, ]$entity, ]$service
        observations[i, ]$time_in_queue <- 0
      } else {
        system[i, ]$in_service <- max(unlist(system$in_service), na.rm = TRUE)
        system$queue_times[i] <- system$queue_times[i - 1]
        system$queue_times[[i]][system[i - 1, ]$in_queue + 1] <- 
          times[times$entity == system[i, ]$entity, ]$arrival
        system[i, ]$in_queue <- system[i - 1, ]$in_queue + 1
        event_cal[i, ]$departure <- max(event_cal$departure, na.rm = TRUE)
      }
      
      system[i, ]$server_status <- TRUE
      
    } else {
      system[i, ]$entity <- times[times$arrival == system[i - 1, ]$in_service, ]$entity
      
      observations[i, ]$server_busy <- 
          (system[i, ]$event_time - system[i - 1, ]$event_time) *  system[i - 1, ]$server_status
      observations[i, ]$number_in_system <- 
        (system[i, ]$event_time - system[i - 1, ]$event_time) *  system[i - 1, ]$in_system
      observations[i, ]$number_in_queue <- 
        (system[i, ]$event_time - system[i - 1, ]$event_time) *  system[i - 1, ]$in_queue
      
      observations[i, ]$time_in_system <- 
        system[i, ]$event_time - times[times$entity == system[i, ]$entity, ]$arrival
      
      system[i, ]$in_system <- system[i - 1, ]$in_system - 1 
      event_cal[i, ]$arrival <- max(event_cal$arrival, na.rm = TRUE)
      
      if (system[i - 1, ]$in_queue > 0) {
        system[i, ]$in_service <- unlist(system$queue_times[[i - 1]][1])
        system$queue_times[[i]] <- system$queue_times[[i - 1]][-1]
        event_cal[i, ]$departure <- 
          system[i, ]$event_time + times[times$entity == system[i, ]$entity, ]$service
        system[i, ]$server_status <- TRUE
        observations[i, ]$time_in_queue <- system[i, ]$event_time - system[i, ]$in_service
      } else {
        system[i, ]$in_service <- 
          ifelse(is.null(unlist(system$queue_times[[i - 1]][1])), 
                 NA_real_, unlist(system$queue_times[[i - 1]][1]))
        system[i, ]$server_status <- FALSE
        event_cal[i + 1, ]$departure <- NA_real_
      }
    }
    system[i, ]$in_queue <- system[i, ]$in_system - system[i, ]$server_status
  }
  system <- system[1:i, ]
  event_cal <- event_cal[1:i, ]
  observations <- observations[1:i, ]
  return(list(times, system, event_cal, observations))
}

end_min <- 60 * 100
results <- sim(lambda, mu, end_min)
results[[5]] <- end_min
save(results, file = "problem2_results.R")
load("problem2_results.R")
# manual simulation results
times <- results[[1]]
system <- results[[2]]
event_cal <- results[[3]]
observations <- results[[4]]
end_min <- results[[5]]

head(times)
##   entity  arrival interarrival    service
## 1      1 0.000000    0.0000000 0.29480899
## 2      2 2.585113    2.5851131 0.49504403
## 3      3 4.029919    1.4448055 1.39047007
## 4      4 4.262643    0.2327239 0.49137399
## 5      5 4.473362    0.2107198 0.41329224
## 6      6 6.096460    1.6230975 0.04245599
head(system)
##   event_time event_type entity server_status in_system in_queue in_service
## 1   0.000000 initialize     NA         FALSE         0        0         NA
## 2   0.000000    arrival      1          TRUE         1        0   0.000000
## 3   0.294809  departure      1         FALSE         0        0         NA
## 4   2.585113    arrival      2          TRUE         1        0   2.585113
## 5   3.080157  departure      2         FALSE         0        0         NA
## 6   4.029919    arrival      3          TRUE         1        0   4.029919
##   queue_times
## 1            
## 2            
## 3            
## 4            
## 5            
## 6
head(event_cal)
##    arrival departure  end
## 1 0.000000        NA 6000
## 2 2.585113  0.294809 6000
## 3 2.585113        NA 6000
## 4 4.029919  3.080157 6000
## 5 4.029919        NA 6000
## 6 4.262643  5.420389 6000
head(observations)
##   time_in_system time_in_queue server_busy number_in_system
## 1             NA            NA    0.000000         0.000000
## 2             NA             0    0.000000         0.000000
## 3       0.294809            NA    0.294809         0.294809
## 4             NA             0    0.000000         0.000000
## 5       0.495044            NA    0.495044         0.495044
## 6             NA             0    0.000000         0.000000
##   number_in_queue
## 1               0
## 2               0
## 3               0
## 4               0
## 5               0
## 6               0
averages <- observations %>%
  summarize(time_in_system = mean(time_in_system, na.rm = TRUE),
            time_in_queue = mean(time_in_queue, na.rm = TRUE),
            number_in_system = sum(number_in_system) / end_min,
            number_in_queue = sum(number_in_queue) / end_min,
            utilization = sum(server_busy) / end_min)

num_entities <- max(system$entity, na.rm = TRUE)

knitr::kable(cbind(averages, num_entities))
time_in_system time_in_queue number_in_system number_in_queue utilization num_entities
0.9791774 0.6461345 1.940545 1.280531 0.6600143 11891
Results of 100-hour long simulation run once in Simio

Results of 100-hour long simulation run once in Simio

4.3) Using the model from Problem 1, create an experiment that includes 100 replications. Run the experiement and observe the SMORE plot for the time entities spend in the system. Experiment with the various SMORE plot settings - viewing the histogram, rotating the plot, changing the upper and lower perecentile values.

Results of experiment with 100 iterations of 100-hour simulation

Results of experiment with 100 iterations of 100-hour simulation

SMORE plot of average time in system with upper and lower percentiles of 75% and 25%

SMORE plot of average time in system with upper and lower percentiles of 75% and 25%

SMORE plot of average time in system with upper and lower percentiles of 90% and 10%

SMORE plot of average time in system with upper and lower percentiles of 90% and 10%

Rotated SMORE plot of average time in system with histogram overlay

Rotated SMORE plot of average time in system with histogram overlay

4.9) Replicate the model from Problem 1 using Simio processes (i.e., not using objects from the Standard Library). Compare the run times for this model and the model from Problem 1 for 50 replications of length 100 hours.

50 replications using the model built from Simio processes executed in 14.2 seconds, more than 3 times faster than 50 replications of the model built from Standard Library objects (48.3 seconds).

Run time of 50 replications of 100-hour simulation modeled with processes

Run time of 50 replications of 100-hour simulation modeled with processes

Run time of 50 replications of 100-hour simulation modeled with Standard Library objects

Run time of 50 replications of 100-hour simulation modeled with Standard Library objects

Results of experiment on model built using processes with 50 replications of length 100 hours

Results of experiment on model built using processes with 50 replications of length 100 hours

4.12) Animate your model from Problem 1 assuming that you are modeling a cashier at a fast food restaurant - the entities represent customers and the server represents the cashier at the cash register. Use Simio’s standard symbols for your animation.

Fast food restaurant animation

Fast food restaurant animation