Title: | Computationally Efficient Queue Simulation |
---|---|
Description: | Implementation of a computationally efficient method for simulating queues with arbitrary arrival and service times. Please see Ebert, Wu, Mengersen & Ruggeri (2020, <doi:10.18637/jss.v095.i05>) for further details. |
Authors: | Anthony Ebert [aut, cre] , Kerrie Mengersen [ths], Paul Wu [ths], Fabrizio Ruggeri [ths] |
Maintainer: | Anthony Ebert <[email protected]> |
License: | GPL-2 | file LICENSE |
Version: | 1.2.0 |
Built: | 2024-11-08 05:29:33 UTC |
Source: | https://github.com/anthonyebert/queuecomputer |
"server.list"
object from a list of times and starting availability.Creates a "server.list"
object from a list of times and starting availability.
as.server.list(times, init)
as.server.list(times, init)
times |
list of numeric vectors giving change times for each server. |
init |
vector of 1s and 0s with equal length to |
an object of class "server.list"
, which is a list of step functions of range {0, 1}.
# Create a server.list object with the first server available anytime before time 10, # and the second server available between time 15 and time 30. as.server.list(list(10, c(15,30)), c(1,0))
# Create a server.list object with the first server available anytime before time 10, # and the second server available between time 15 and time 30. as.server.list(list(10, c(15,30)), c(1,0))
server.stepfun
object with a roster of times and number of available servers.Create a server.stepfun
object with a roster of times and number of available servers.
as.server.stepfun(x, y)
as.server.stepfun(x, y)
x |
numeric vector giving the times of changes in number of servers. |
y |
numeric vector one longer than |
This function uses the analogy of a step function to specify the number of
available servers throughout the day. It is used as input to the queue_step
function. Alternatively one may use as.server.list
to specify available servers as
a list, however queue_step
is much faster when as.server.stepfun
is used
as input rather than as.server.list
.
If any of the service times are large compared to any element of diff(x)
then the
as.server.list
function should be used.
A list
and server.stepfun
object with x and y as elements.
as.server.list
, queue_step
, stepfun
.
servers <- as.server.stepfun(c(15,30,50), c(0, 1, 3, 2)) servers
servers <- as.server.stepfun(c(15,30,50), c(0, 1, 3, 2)) servers
Compute time average queue length
average_queue(times, queuelength)
average_queue(times, queuelength)
times |
numeric vector of times |
queuelength |
numeric vector of queue lengths |
n <- 1e3 arrivals <- cumsum(rexp(n)) service <- rexp(n) departures <- queue(arrivals, service, 1) queuedata <- queue_lengths(arrivals, service, departures) average_queue(queuedata$times, queuedata$queuelength)
n <- 1e3 arrivals <- cumsum(rexp(n)) service <- rexp(n) departures <- queue(arrivals, service, 1) queuedata <- queue_lengths(arrivals, service, departures) average_queue(queuedata$times, queuedata$queuelength)
queue_list
objectget departure times from queue_list
object
depart(x)
depart(x)
x |
an |
departure times
arrivals <- cumsum(rexp(10)) service <- rexp(10) queue_obj <- queue_step(arrivals, service) depart(queue_obj) queue_obj$departures_df$departures
arrivals <- cumsum(rexp(10)) service <- rexp(10) queue_obj <- queue_step(arrivals, service) depart(queue_obj) queue_obj$departures_df$departures
Add lag to vector of arrival times.
lag_step(arrivals, service)
lag_step(arrivals, service)
arrivals |
Either a numeric vector or an object of class |
service |
A vector of service times with the same ordering as arrivals |
A vector of response times for the input of arrival times and service times.
# Create arrival times arrivals <- rlnorm(100, meanlog = 3) # Create service times service <- rlnorm(100) lag_step(arrivals = arrivals, service = service) # lag_step is equivalent to queue_step with a large number of queues, but it's faster to compute. cbind(queue(arrivals, service = service, servers = 100), lag_step(arrivals = arrivals, service = service))
# Create arrival times arrivals <- rlnorm(100, meanlog = 3) # Create service times service <- rlnorm(100) lag_step(arrivals = arrivals, service = service) # lag_step is equivalent to queue_step with a large number of queues, but it's faster to compute. cbind(queue(arrivals, service = service, servers = 100), lag_step(arrivals = arrivals, service = service))
ggplot2 method for output from queueing model
## S3 method for class 'queue_list' plot(x, which = c(2:6), annotated = TRUE, ...)
## S3 method for class 'queue_list' plot(x, which = c(2:6), annotated = TRUE, ...)
x |
an object of class |
which |
Numeric vector of integers from 1 to 6 which represents which plots are to be created. See examples. |
annotated |
logical, if |
... |
other parameters to be passed through to plotting functions. |
## Not run: n_customers <- 50 arrival_rate <- 1.8 service_rate <- 1 arrivals <- cumsum(rexp(n_customers, arrival_rate)) service <- rexp(n_customers, service_rate) queue_obj <- queue_step(arrivals, service, servers = 2) plot(queue_obj) library(ggplot2) ## density plots of arrival and departure times plot(queue_obj, which = 1) ## histograms of arrival and departure times plot(queue_obj, which = 2) ## density plots of waiting and system times plot(queue_obj, which = 3) ## step function of queue length plot(queue_obj, which = 4) ## line range plot of customer and server status plot(queue_obj, which = 5) ## empirical distribution plot of arrival and departure times plot(queue_obj, which = 6) ## End(Not run)
## Not run: n_customers <- 50 arrival_rate <- 1.8 service_rate <- 1 arrivals <- cumsum(rexp(n_customers, arrival_rate)) service <- rexp(n_customers, service_rate) queue_obj <- queue_step(arrivals, service, servers = 2) plot(queue_obj) library(ggplot2) ## density plots of arrival and departure times plot(queue_obj, which = 1) ## histograms of arrival and departure times plot(queue_obj, which = 2) ## density plots of waiting and system times plot(queue_obj, which = 3) ## step function of queue length plot(queue_obj, which = 4) ## line range plot of customer and server status plot(queue_obj, which = 5) ## empirical distribution plot of arrival and departure times plot(queue_obj, which = 6) ## End(Not run)
summary.queue_list
.Print method for output of summary.queue_list
.
## S3 method for class 'summary_queue_list' print(x, ...)
## S3 method for class 'summary_queue_list' print(x, ...)
x |
an object of class |
... |
further arguments to be passed to or from other methods. |
A list of performance statistics for the queue:
"Total customers": Total customers in simulation,
"Missed customers": Customers who never saw a server,
"Mean waiting time": The mean time each customer had to wait in queue for service,
"Mean response time": The mean time that each customer spends in the system (departure time - arrival time),
"Utilization factor": The ratio of available time for all servers and time all servers were used. It can be greater than one if a customer arrives near the end of a shift and keeps a server busy,
"Mean queue length": Average queue length, and
"Mean number of customers in system": Average number of customers in queue or currently being served.
n <- 1e3 arrivals <- cumsum(rexp(n, 1.8)) service <- rexp(n) queue_obj <- queue_step(arrivals, service, servers = 2) summary(queue_obj)
n <- 1e3 arrivals <- cumsum(rexp(n, 1.8)) service <- rexp(n) queue_obj <- queue_step(arrivals, service, servers = 2) summary(queue_obj)
Summarise queue lengths
ql_summary(times, queuelength)
ql_summary(times, queuelength)
times |
numeric vector of times |
queuelength |
numeric vector of queue lengths |
n <- 1e3 arrivals <- cumsum(rexp(n)) service <- rexp(n) departures <- queue(arrivals, service, 1) queuedata <- queue_lengths(arrivals, service, departures) ql_summary(queuedata$times, queuedata$queuelength)
n <- 1e3 arrivals <- cumsum(rexp(n)) service <- rexp(n) departures <- queue(arrivals, service, 1) queuedata <- queue_lengths(arrivals, service, departures) ql_summary(queuedata$times, queuedata$queuelength)
queue
is a faster version of queue_step
but the input returned is much simpler. It is not compatible with the summary.queue_list
method or the plot.queue_list
method.
queue(arrivals, service, servers = 1, serveroutput = FALSE)
queue(arrivals, service, servers = 1, serveroutput = FALSE)
arrivals |
numeric vector of non-negative arrival times |
service |
numeric vector of non-negative service times |
servers |
a non-zero natural number, an object of class |
serveroutput |
boolean whether the server used by each customer should be returned. |
If the arrival vector is out of order the function will reorder it. The same reordering will be applied to the service vector, this is so each customer keeps their service time. Once the queue is computed the original order is put back.
n <- 1e2 arrivals <- cumsum(rexp(n, 1.8)) service <- rexp(n) departures <- queue( arrivals, service, servers = 2) head(departures) curve(ecdf(departures)(x) * n, from = 0, to = max(departures), xlab = "Time", ylab = "Number of customers") curve(ecdf(arrivals)(x) * n, from = 0, to = max(departures), col = "red", add = TRUE)
n <- 1e2 arrivals <- cumsum(rexp(n, 1.8)) service <- rexp(n) departures <- queue( arrivals, service, servers = 2) head(departures) curve(ecdf(departures)(x) * n, from = 0, to = max(departures), xlab = "Time", ylab = "Number of customers") curve(ecdf(arrivals)(x) * n, from = 0, to = max(departures), col = "red", add = TRUE)
Compute queue lengths from arrival, service and departure data
queue_lengths(arrivals, service = 0, departures, epsilon = 1e-10, ...)
queue_lengths(arrivals, service = 0, departures, epsilon = 1e-10, ...)
arrivals |
vector of arrival times |
service |
vector of service times. Leave as zero if you want to compute the number of customers in the system rather than queue length. |
departures |
vector of departure times |
epsilon |
numeric small number added to departures to prevent negative queue lengths |
... |
additional arguments - does nothing, for compatibility |
library(dplyr) library(queuecomputer) set.seed(1L) n_customers <- 100 queueoutput_df <- data.frame( arrivals = runif(n_customers, 0, 300), service = rexp(n_customers) ) queueoutput_df <- queueoutput_df %>% mutate( departures = queue(arrivals, service, servers = 2) ) queue_lengths( queueoutput_df$arrivals, queueoutput_df$service, queueoutput_df$departures ) # The dplyr way queueoutput_df %>% do( queue_lengths(.$arrivals, .$service, .$departures)) n_customers <- 1000 queueoutput_df <- data.frame( arrivals = runif(n_customers, 0, 300), service = rexp(n_customers), route = sample(c("a", "b"), n_customers, TRUE) ) server_df <- data.frame( route = c("a", "b"), servers = c(2, 3) ) output <- queueoutput_df %>% left_join(server_df) %>% group_by(route) %>% mutate( departures = queue(arrivals, service, servers = servers[1]) ) %>% do(queue_lengths(.$arrivals, .$service, .$departures)) if(require(ggplot2, quietly = TRUE)){ ggplot(output) + aes(x = times, y = queuelength) + geom_step() + facet_grid(~route) }
library(dplyr) library(queuecomputer) set.seed(1L) n_customers <- 100 queueoutput_df <- data.frame( arrivals = runif(n_customers, 0, 300), service = rexp(n_customers) ) queueoutput_df <- queueoutput_df %>% mutate( departures = queue(arrivals, service, servers = 2) ) queue_lengths( queueoutput_df$arrivals, queueoutput_df$service, queueoutput_df$departures ) # The dplyr way queueoutput_df %>% do( queue_lengths(.$arrivals, .$service, .$departures)) n_customers <- 1000 queueoutput_df <- data.frame( arrivals = runif(n_customers, 0, 300), service = rexp(n_customers), route = sample(c("a", "b"), n_customers, TRUE) ) server_df <- data.frame( route = c("a", "b"), servers = c(2, 3) ) output <- queueoutput_df %>% left_join(server_df) %>% group_by(route) %>% mutate( departures = queue(arrivals, service, servers = servers[1]) ) %>% do(queue_lengths(.$arrivals, .$service, .$departures)) if(require(ggplot2, quietly = TRUE)){ ggplot(output) + aes(x = times, y = queuelength) + geom_step() + facet_grid(~route) }
Compute the departure times and queue lengths for a queueing system from arrival and service times.
queue_step(arrivals, service, servers = 1, labels = NULL)
queue_step(arrivals, service, servers = 1, labels = NULL)
arrivals |
numeric vector of non-negative arrival times |
service |
numeric vector of service times with the same ordering as arrival_df. |
servers |
a non-zero natural number, an object of class |
labels |
character vector of customer labels (deprecated). |
If only departure times are needed, the queue
function is faster.
An list object of class queue_list
with the following components:
departures
- A vector of response times for the input of arrival times and service times.
server
- A vector of server assignments for the input of arrival times and service times.
departures_df
- A data frame with arrivals, service, departures, waiting, system time, and server assignments for each customer.
queuelength_df
- A data frame describing the evolution of queue length over time
systemlength_df
- A data frame describing the evolution of system length over time
servers_input
- A copy of the server argument
state
- A vector of availability times for the servers
queue
, summary.queue_list
, plot.queue_list
# With two servers set.seed(1) n <- 100 arrivals <- cumsum(rexp(n, 3)) service <- rexp(n) queue_obj <- queue_step(arrivals, service = service, servers = 2) summary(queue_obj) plot(queue_obj, which = 5) # It seems like the customers have a long wait. # Let's put two more servers on after time 20 server_list <- as.server.stepfun(c(20),c(2,4)) queue_obj2 <- queue_step(arrivals, service = service, servers = server_list) summary(queue_obj2) if(require(ggplot2, quietly = TRUE)){ plot(queue_obj2, which = 5) }
# With two servers set.seed(1) n <- 100 arrivals <- cumsum(rexp(n, 3)) service <- rexp(n) queue_obj <- queue_step(arrivals, service = service, servers = 2) summary(queue_obj) plot(queue_obj, which = 5) # It seems like the customers have a long wait. # Let's put two more servers on after time 20 server_list <- as.server.stepfun(c(20),c(2,4)) queue_obj2 <- queue_step(arrivals, service = service, servers = server_list) summary(queue_obj2) if(require(ggplot2, quietly = TRUE)){ plot(queue_obj2, which = 5) }
Summary method for queue_list object
## S3 method for class 'queue_list' summary(object, ...)
## S3 method for class 'queue_list' summary(object, ...)
object |
an object of class |
... |
further arguments to be passed to or from other methods. |
Compute maximum time for each row from two vectors of arrival times.
wait_step(arrivals, service)
wait_step(arrivals, service)
arrivals |
Either a numeric vector or an object of class |
service |
A vector of times which represent the arrival times of the second type
of customers. The ordering of this vector should have the same ordering as |
A good real-world example of this is finding the departure times for passengers after they pick up their bags from the baggage carousel. The time at which they leave is the maximum of the passenger and bag arrival times.
The maximum time from two vectors of arrival times.
set.seed(500) arrivals <- rlnorm(100, meanlog = 4) service <- rlnorm(100) #Airport example ------------------------ # Create a number of bags for each of 100 customers bags <- rpois(100,1) # Create a bags dataframe, with each bag associated with one customer. bags.df <- data.frame(BagID = 1:sum(bags), ID = rep(1:100, bags), times = rlnorm(sum(bags), meanlog = 2)) # Create a function which will return the maximum time from each customer's set of bags. reduce_bags <- function(bagdataset, number_of_passengers){ ID = NULL times = NULL zerobags <- data.frame(BagID = NA, ID = c(1:number_of_passengers), times = 0) reduced_df <- as.data.frame(dplyr::summarise(dplyr::group_by( rbind(bagdataset, zerobags), ID), n = max(times, 0))) ord <- order(reduced_df$ID) reduced_df <- reduced_df[order(ord),] names(reduced_df) <- c("ID", "times") return(reduced_df) } arrivals2 <- reduce_bags(bags.df, 100)$times # Find the time when customers can leave with their bags. wait_step(arrivals = arrivals, service = arrivals2)
set.seed(500) arrivals <- rlnorm(100, meanlog = 4) service <- rlnorm(100) #Airport example ------------------------ # Create a number of bags for each of 100 customers bags <- rpois(100,1) # Create a bags dataframe, with each bag associated with one customer. bags.df <- data.frame(BagID = 1:sum(bags), ID = rep(1:100, bags), times = rlnorm(sum(bags), meanlog = 2)) # Create a function which will return the maximum time from each customer's set of bags. reduce_bags <- function(bagdataset, number_of_passengers){ ID = NULL times = NULL zerobags <- data.frame(BagID = NA, ID = c(1:number_of_passengers), times = 0) reduced_df <- as.data.frame(dplyr::summarise(dplyr::group_by( rbind(bagdataset, zerobags), ID), n = max(times, 0))) ord <- order(reduced_df$ID) reduced_df <- reduced_df[order(ord),] names(reduced_df) <- c("ID", "times") return(reduced_df) } arrivals2 <- reduce_bags(bags.df, 100)$times # Find the time when customers can leave with their bags. wait_step(arrivals = arrivals, service = arrivals2)