Nothing
## -------------------------------------------------------------------------
## This program animates the details of an event-drivent simulation of a
## single-server queue with a FIFO (first-in-first-out) queue discipline and
## default iid exponentially distributed interarrival times and iid
## exponentially distributed service times (i.e., an M/M/1 queue).
##
## Assumptions:
## * Initial state of the system: empty queue and idle server
## * Server takes no breaks
## * No feedback (once a customer is serviced, the customer departs)
## * Immediate service (no time delay between serving customers)
## * Ending conditions: the simulation is terminated by 1 of 3 conditions
## - number of customers departed,
## - simulated time exceeds a prescribed ending time,
## - number of arrivals (this option allows the system to clear out,
## unless one of the other stopping conditions overrides),
## whichever occurs first
##
## Name : ssqvis.R (single server queue visualization)
## Authors : Vadim Kudlay & Barry Lawson & Larry Leemis
## Language : R (part of simEd package)
## Latest Revision : 13 Dec 2020
## -------------------------------------------------------------------------
#'
#' Single-Server Queue Simulation Visualization
#'
#' @description A modified ssq implementation that illustrates event-driven
#' details, including the event calendar, inversion for interarrival and service
#' time variate generation, the simulation clock, the status of the queueing
#' system, and statistics collection. The function plots step-by-step in either
#' an interactive mode or time-delayed automatic mode.
#'
#' @param showSkyline Shorthand for specifying skyline display parameters
#' using chmod-like component specification: use 1, 2, 4 for system, queue,
#' and server in that order, summing to get combination (e.g., 7 for all).
#'
#' @details
#' Animates the details of an event-driven implementation of a single-server
#' queue simulation.
#'
#' The event calendar, inversion for interarrival and service time variates,
#' and an abbreviated (current) timeline are animated in the top pane of the
#' window. In this pane, blue corresponds to the arrival process, orange
#' corresponds to the service process, and purple corresponds to uniform
#' variates used in inversion. Yellow is used to highlight recent updates.
#'
#' The state of the queueing system is animated in the middle pane of the
#' window. In this pane, red indicates an idle server, orange indicates that
#' a new customer has just arrived to the server and a corresponding service
#' time is being generated, and green indicates a busy server. By default,
#' customers are depicted as black rectangles and identified by increasing
#' arrival number, but this depiction can be overridden by the \code{jobImage}
#' parameter.
#'
#' Statistics are displayed in the bottom pane of the window. Time-persistent
#' statistics are shown as "skyline functions" in the left portion of this
#' pane. Both time-persistent and based-on-observation statistics are shown
#' in respective tables in the right portion of this pane. In the tables,
#' yellow is used to highlight recent updates.
#'
#' @importFrom graphics legend
#' @importFrom shape roundrect Arrows
#' @importFrom grDevices as.raster
#' @importFrom stats sd
#' @importFrom grDevices dev.flush dev.hold recordPlot replayPlot
#'
#' @template queue
#' @keywords utilities
#' @concept queueing
#'
#' @examples
#'
#' # Visualizing ssq with a set seed, infinite queue capacity, 4 arrivals,
#' # and showing skyline with number in system, queue, and server.
#' ssqvis(seed = 1234, maxArrivals = 4, showSkyline = 7, plotDelay = 0.01)
#'
#' @export
################################################################################
ssqvis <- function(
maxArrivals = Inf,
seed = NA, # NA : use rng state; NULL : sys gen
#######
# custom arrival and service functions not (yet) supported for ssqvis
# interarrivalFcn = NA, # defaultInterarrival,
# serviceFcn = NA, # defaultService,
interarrivalType = "M",
serviceType = "M",
maxTime = Inf,
maxDepartures = Inf,
#######
# finite queue capacity not (yet) supported for ssqvis
# maxInSystem = Inf,
maxEventsPerSkyline = 15,
saveAllStats = FALSE,
saveInterarrivalTimes = FALSE,
saveServiceTimes = FALSE,
saveWaitTimes = FALSE,
saveSojournTimes = FALSE,
saveNumInQueue = FALSE,
saveNumInSystem = FALSE,
saveServerStatus = FALSE,
showOutput = TRUE,
showSkyline = NULL,
showSkylineQueue = TRUE,
showSkylineSystem = TRUE,
showSkylineServer = TRUE,
showTitle = TRUE,
jobImage = NA,
plotDelay = -1
)
{
#############################################################################
# using on.exit w/ par per CRAN suggestion (add 22 Nov 2023)
oldpar <- par(no.readonly = TRUE) # save current par settings (add 22 Nov 2023)
on.exit(par(oldpar)) # add (22 Nov 2023)
#DEBUG_ <- NA # if defined w/ any value, process debugging output displayed
#warnVal <- options("warn") # save current warning setting...
# remove RE CRAN req't (del 22 Nov 2023)
#############################################################################
# Do parameter checking and handling; stop execution or warn if erroneous
#############################################################################
{
interarrivalFcn <- NA
serviceFcn <- NA
checkVal(seed, "i", minex = 1, na = TRUE, null = TRUE)
checkVal(maxTime, minex = 0)
checkVal(maxArrivals, "i", minex = 0)
checkVal(maxDepartures, "i", minex = 0)
checkVal(maxEventsPerSkyline, "i", minex = 0)
# checkVal(maxInSystem, "i", min = 1)
if (maxTime == Inf && maxArrivals == Inf && maxDepartures == Inf)
stop("at least one of 'maxTime', 'maxArrivals', or 'maxDepartures' must be < Inf")
if (!(interarrivalType %in% c("M","G")))
stop("for animation purposes, interarrivalType must be either 'M' or 'G'")
if (!(serviceType %in% c("M","G")))
stop("for animation purposes, serviceType must be either 'M' or 'G'")
checkVal(saveAllStats, "l")
checkVal(saveInterarrivalTimes, "l")
checkVal(saveServiceTimes, "l")
checkVal(saveWaitTimes, "l")
checkVal(saveSojournTimes, "l")
checkVal(saveNumInQueue, "l")
checkVal(saveNumInSystem, "l")
checkVal(saveServerStatus, "l")
if (saveAllStats) {
saveInterarrivalTimes <- TRUE
saveServiceTimes <- TRUE
saveWaitTimes <- TRUE
saveSojournTimes <- TRUE
saveNumInQueue <- TRUE
saveNumInSystem <- TRUE
saveServerStatus <- TRUE
}
checkVal(showOutput, "l")
checkVal(showTitle, "l")
checkVal(showSkylineSystem, "l")
checkVal(showSkylineQueue, "l")
checkVal(showSkylineServer, "l")
showSkylineResults <- ParseShow(
showBools = c(showSkylineSystem, showSkylineQueue, showSkylineServer),
show = showSkyline,
ignoreBools = missing(showSkylineSystem)
&& missing(showSkylineQueue)
&& missing(showSkylineServer)
)
showSkylineSystem <- showSkylineResults[1]
showSkylineQueue <- showSkylineResults[2]
showSkylineServer <- showSkylineResults[3]
if (!is.na(jobImage) && !is.character(jobImage))
stop("'jobImage' must be a local link or URL to an image (or a vector of such)")
if (!isValNum(plotDelay) || (plotDelay < 0 && plotDelay != -1))
stop("'plotDelay' must be a numeric value (in secs) >= 0 or -1 (interactive mode)")
endCriteria <- list(ARRIVALS = 1, DEPARTURES = 2) #, TIME = 3)
endValue <- max(if (is.infinite(maxArrivals)) -1 else maxArrivals,
if (is.infinite(maxDepartures)) -1 else maxDepartures,
if (is.infinite(maxTime)) -1 else maxTime)
endType <- if (endValue == maxArrivals || endValue == maxTime)
endCriteria$ARRIVALS
else if (endValue == maxDepartures)
endCriteria$DEPARTURES
# NB: this is not a general solution for stopping criteria for at least
# two reasons:
# 1) user may include two stopping criteria (e.g., maxArrivals & maxTime)
# but there is no way for us to know in advance which of those two
# would occur first -- so, for now at least, consider in order
# of importance: arrivals, departures, time
# 2) these variables are used primarily to pass to compPlot.R:PausePlot,
# e.g., for jumping and displaying progress bar; but if user chooses
# to stop on max time only, we won't know in advance what the upper
# limit of arrivals would be in that case.
}
#############################################################################
#############################################################################
# Parses the show and associated parameters
# Should handle all show, showQueue, showSkyline, and showECDF of parent function
#############################################################################
numServers <- 1
#############################################################################
## default interarrival and service functions...
## defaultService needs to come prior to serviceFcn error checking below
#############################################################################
if (typeof(interarrivalFcn) != "closure") {
fcnInfo <- GetDefaultDistroFcn(interarrivalType,
isArrival = TRUE, asList = TRUE)
interarrivalFcn <- fcnInfo$fcn
arrivalNotation <- fcnInfo$notation
} else {
# for backward compatibility, we want to allow the user to provide a
# function having no arguments, a la
# iaFcn <- function() { rexp(1, rate = 2, stream = 1) }
# but Vadim's new code expects the function to allow for a variable number
# of arguments, a la
# iaFcn <- function(...) { rexp(1, rate = 2, stream = 1) }
# so parse the function to rebuild allowing for variable number of arguments
# if necessary:
interarrivalFcn <- ParseDistroFcn(interarrivalFcn, asList = TRUE)
if (is.na(interarrivalFcn))
stop("Error: custom distributions not supported for ssqvis")
arrivalNotation <- GetDistroNotation(interarrivalFcn)
}
if (typeof(serviceFcn) != "closure") {
fcnInfo <- GetDefaultDistroFcn(serviceType,
isArrival = FALSE, asList = TRUE)
serviceFcn <- fcnInfo$fcn
serviceNotation <- fcnInfo$notation
} else {
# allow for variable # of args if necessary -- see comment above
serviceFcn <- ParseDistroFcn(serviceFcn, asList = TRUE)
if (is.na(serviceFcn))
stop("Error: custom distributions not supported for ssqvis")
serviceNotation <- GetDistroNotation(serviceFcn)
}
#############################################################################
# Function that returns picture to be used for elements. Returns NA by default.
# If picture links are found, function is overridden.
GetPic <- function(i) return(NA)
if (!requireNamespace("magick", quietly = TRUE)) {
message("Package \"magick\" needed for image inclusion to work. ",
"Defaulting from using images.", call. = FALSE)
}
# Try to parse in pictures for jobs if any were specified
else if (!is.na(jobImage[1]))
{
# Initialize setup of picture-handling structures
# Attempt to determine max # of jobs to use as size of random image matrix
num.jobs <- if (maxArrivals < Inf) maxArrivals+1 else 100
# Parse the picture links, scale to 80 width, and convert to raster images
pics <- lapply(jobImage, function(p)
as.raster(magick::image_scale(magick::image_read(p), "80")))
# Override the GetPic function to either return
# - a randomly-set picture consistent with the inputted index
# - the picture to be used for all of the elements
if (length(jobImage > 2)) {
pictype <- sample(1:length(pics), num.jobs, replace = TRUE)
GetPic <- function(i) return(pics[[pictype[i]]])
} else {
GetPic <- function(i) return(pics)
}
}
#############################################################################
# Creating ssqvis-scope instance of PausePlot. To be overridden in main
PauseCurrPlot <- NULL # eventually a function
pauseData <- NULL
# Construct title for the plot
#if (maxInSystem < Inf || maxArrivals < Inf) {
if (maxArrivals < Inf) {
paramsText <- paste(" (",
#if (maxInSystem < Inf) paste("K = ", maxInSystem, ", ", sep = ""),
if (maxArrivals < Inf) paste("N = ", maxArrivals, ")", sep = ""),
sep = "")
} else {
paramsText <- ""
}
titleText <- bquote(
.(arrivalNotation) ~ "/" ~ .(serviceNotation)
~ "/" ~ .(numServers) ~ .(paramsText))
sf_ratio = c(1,2) # in future update, allow user to specify this
fs <- function(n) ScaleFont(n, f = 3, r = sf_ratio)
# declare various font sizes used throughout
rStudio_ <- Sys.getenv("RSTUDIO") == 1
fsChyron_ <- if (rStudio_) fs(8) else fs(15)
fsSubtitle_ <- if (rStudio_) fs(7) else fs(15)
fsSubtext_ <- if (rStudio_) fs(7) else fs(12)
fsIDFTime_ <- if (rStudio_) fs(7) else fs(12)
fsQueueSvcTime_ <- if (rStudio_) fs(5) else fs(9)
fsQueueArrTimeL_ <- if (rStudio_) fs(5) else fs(9)
fsQueueArrTimeS_ <- if (rStudio_)
{function(t_) fs(5 - max(0, log10(t_ / 100) / 2))}
# cluge to progressively decrease fs
else
{function(t_) fs(8.5 - max(0, log10(t_ / 100)))}
fsClock_ <- if (rStudio_) fs(7) else fs(13)
fsCompleted_ <- if (rStudio_) fs(6) else fs(11)
fsJob_ <- if (rStudio_) fs(6) else fs(10)
fsTableCell_ <- if (rStudio_) fs(7) else fs(12)
fsIDFAxis_ <- if (rStudio_) ScaleFont(12) else ScaleFont(15)
fsIDFPoint_ <- if (rStudio_) ScaleFont(18) else ScaleFont(25)
fsSkyLegend_ <- if (rStudio_) ScaleFont(10) else ScaleFont(12)
# We maintain a calendar as two R lists (consistent with msq.R):
# - a list of length one corresponding to the next arrival
# - a list of length one (single sever) corresponding to the server
# For arrivals, the event data structure entries correspond to:
# - type: 'a' for arrivals
# - time: time of next arrival to occur
# - state: 0 <--> arrivals disallowed; 1 <--> arrivals allowed
# For the server, the event data structure entries correspond to:
# - type: 's' for server
# - time: time of next (state == 1) or last (state == 0) completion of svc
# - state: 0 <--> server currently idle; 1 <--> server currently busy
arrivalsCal <- list(type = 'a', time = Inf, state = 0)
serverCal <- list(type = 's', time = 0, state = 0)
##############################################################################
## Define graphical components
##############################################################################
#TextBox <- NULL # defined in compPlot.R
#DrawBorder <- NULL # defined in compPlot.R
#NewPlot <- NULL # defined in compPlot.R
DrawCol <- NULL # to be defined below
# Ranges for Plots (Keep bounds in (10, 190))
# ---------- xmin, xmax, ymin, ymax ------------
# These define the (x,y) areas for plotting into the window:
# 1) calendarPlotRange: topmost plot, containing calendar & inversion plots
# 2) queuePlotRange: middle plot, containing the queue animation
# 3) skylinePlotRange: lower-left plot, containing skyline functions
# 4) statsPlotRange: lower-right plot, containing TPS & BOO stats (bank)
# 5) upArrowPlotRange: narrow plot b/w topmost and middle, w/ up arrow
# pointing at current time on timeline in top plot
calendarPlotRange <- c( 10, 190, 135, 190)
queuePlotRange <- c( 10, 190, 88, 130)
#skylinePlotRange <- c( 18, 120, 18, 78) # Vadim
skylinePlotRange <- c( 20, 122, 18, 78)
statsPlotRange <- c( 130, 181, 18, 78)
upArrowPlotRange <- c( 65, 70, 126, 136)
calendarPlotBGColor <- rgb(0.95, 0.95, 0.95)
##############################################################################
##############################################################################
### -------------- BEGIN FUNCITON DEFINITIONS FOR MAIN -------------- ###
##############################################################################
##############################################################################
## getNextEvent
## --------------------------------------------------------------------------
## Return a copy of the next event type
##############################################################################
getNextEvent <- function()
{
minTime <- Inf
nextEvent <- NULL
# first search the list-of-one arrivals calendar
if (arrivalsCal$state == 1 && arrivalsCal$time < minTime) {
minTime <- arrivalsCal$time
nextEvent <- arrivalsCal
}
# then search the list-of-one server event
if (serverCal$state == 1 && serverCal$time < minTime) {
minTime <- serverCal$time
nextEvent <- serverCal
}
return (nextEvent)
}
##############################################################################
# To be initialized to helper function
DrawJob <- NA
GetJobHalfWidth <- NA
GetJobHalfHeight <- NA
initDrawJob <- FALSE
# default/initial values used in DrawCurrQueue below
SSQVals <- list(
time = 0, # Time
idsInSystem = c(), # numeric ids of customers currently in server + queue
idJustRejected = 0, # numeric id of customer just reject and departing
idNextToEnter = 1, # numeric id of customer next to enter the system
idJustServed = 0, # numeric id of customer just served and departing
currentProgress = 0, # current progress
numRejects = 0, # number rejected
serviceTime = NA, # service time
interarrivalTime = NA, # interarrival time
arrivalTime = NA, # arrival time
completionTime = NA # completion time
)
##############################################################################
## DrawCurrQueue
## --------------------------------------------------------------------------
# Function to be initialized by setDrawCurrQueue
# Draws the current SSQ with default values modifiable by parameters
##############################################################################
DrawCurrQueue <- function(time,
idsInSystem,
idJustRejected,
idNextToEnter,
idJustServed,
currentProgress,
numRejects,
interarrivalTime,
arrivalTime,
serviceTime,
completionTime,
forceDraw = FALSE
)
{
# set SSQVals to defaults if any are missing
if (missing(time)) time <- SSQVals[["time"]]
if (missing(idsInSystem)) idsInSystem <- SSQVals[["idsInSystem"]]
if (missing(idJustRejected)) idJustRejected <- SSQVals[["idJustRejected"]]
if (missing(idNextToEnter)) idNextToEnter <- SSQVals[["idNextToEnter"]]
if (missing(idJustServed)) idJustServed <- SSQVals[["idJustServed"]]
if (missing(currentProgress)) currentProgress <- SSQVals[["currentProgress"]]
if (missing(numRejects)) numRejects <- SSQVals[["numRejects"]]
if (missing(interarrivalTime)) interarrivalTime <- SSQVals[["interarrivalTime"]]
if (missing(arrivalTime)) arrivalTime <- SSQVals[["arrivalTime"]]
if (missing(serviceTime)) serviceTime <- SSQVals[["serviceTime"]]
if (missing(completionTime)) completionTime <- SSQVals[["completionTime"]]
# call DrawQueueComponents (below) to plot the queueing system in middle of plot
DrawQueueComponents(time = time,
idsInSystem = idsInSystem,
idJustRejected = idJustRejected,
idNextToEnter = idNextToEnter,
idJustServed = idJustServed,
currentProgress = currentProgress,
numRejects = numRejects,
interarrivalTime = interarrivalTime,
arrivalTime = arrivalTime,
serviceTime = serviceTime,
completionTime = completionTime,
forceDraw = forceDraw
)
# switch to the up-arrow portion of the plot and draw time arrow there
TogglePlot(upArrowPlotRange)
Arrows(100, 0, 100, 150)
ScalePlots(dims = c(0, 200, 0, 200))
}
##############################################################################
# Updates DrawCurrQueue with specified value defauls
##############################################################################
setDrawCurrQueue <- function(time,
idsInSystem,
idJustRejected,
idNextToEnter,
idJustServed,
currentProgress,
numRejects,
interarrivalTime,
arrivalTime,
serviceTime,
completionTime,
shiftInQueue = FALSE,
#shiftInServer = FALSE,
# was set in a call, but never used here!
shiftOutServer = FALSE
)
{
drawPlot <- FALSE
if (!missing(time)) { SSQVals[["time"]] <<- time; drawPlot <- TRUE }
if (!missing(idsInSystem)) { SSQVals[["idsInSystem"]] <<- idsInSystem; drawPlot <- TRUE }
if (!missing(idJustRejected)) { SSQVals[["idJustRejected"]] <<- idJustRejected; drawPlot <- TRUE }
if (!missing(idNextToEnter)) { SSQVals[["idNextToEnter"]] <<- idNextToEnter; drawPlot <- TRUE }
if (!missing(idJustServed)) { SSQVals[["idJustServed"]] <<- idJustServed; drawPlot <- TRUE }
if (!missing(currentProgress)) { SSQVals[["currentProgress"]] <<- currentProgress; drawPlot <- TRUE }
if (!missing(numRejects)) { SSQVals[["numRejects"]] <<- numRejects; drawPlot <- TRUE }
if (!missing(interarrivalTime)) { SSQVals[["interarrivalTime"]] <<- interarrivalTime; drawPlot <- TRUE }
if (!missing(arrivalTime)) { SSQVals[["arrivalTime"]] <<- arrivalTime; drawPlot <- TRUE }
if (!missing(serviceTime)) { SSQVals[["serviceTime"]] <<- serviceTime; drawPlot <- TRUE }
if (!missing(completionTime)) { SSQVals[["completionTime"]] <<- completionTime; drawPlot <- TRUE }
if (shiftInQueue) {
SSQVals[["idsInSystem"]] <<- c(SSQVals[["idsInSystem"]],
SSQVals[["idNextToEnter"]])
SSQVals[["idNextToEnter"]] <<- SSQVals[["idNextToEnter"]] + 1
drawPlot <- TRUE
}
if (shiftOutServer) {
SSQVals[["idJustServed"]] <<- SSQVals[["idsInSystem"]][1]
SSQVals[["idsInSystem"]] <<- SSQVals[["idsInSystem"]][-1]
drawPlot <- TRUE
}
return(drawPlot)
}
##############################################################################
##############################################################################
## DrawQueueComponents
## --------------------------------------------------------------------------
## Function to generate SSQ plot given current status of simulation
##############################################################################
DrawQueueComponents <- function(time,
idsInSystem,
# vector of customer ids currently in system
# (server + queue)
idJustRejected = 0, # job # of just rejected (if any)
idNextToEnter = 0, # job # of next to enter
idJustServed = 0, # job # of just served (if any)
currentProgress,
# % of simulation completed -- for progress bar
numRejects = 0,
interarrivalTime,
arrivalTime,
serviceTime,
completionTime,
forceDraw = FALSE
)
{
if (pauseData$plotDelay == 0 && !forceDraw) return() # proceed-to-end approach
if (pauseData$plotDelay == -2) return() # jumping
if (!initDrawJob)
{
initDrawJob <<- TRUE
hasImg <- !is.na(jobImage[1])
if (hasImg) {
jtcol <- "red"
GetJobHalfWidth <<- function(i) 6
GetJobHalfHeight <<- function(i) 25
} else {
jtcol <- "white"
GetJobHalfWidth <<- function(i) max(6, 2 * nchar(i, "width"))
GetJobHalfHeight <<- function(i) ScaleFont(250)
if (Sys.getenv("RSTUDIO") == 1) {
GetJobHalfHeight <<- function(i) ScaleFont(180)
}
}
# set up the DrawJob function used below
DrawJob <<- function(i, midWidth = 10, midHeight = 100,
textXDisplace = 0, textYDisplace = 0, bg,
size = 15)
{
if (!hasImg)
textXDisplace <- textYDisplace <- 0
else
bg <- NA
# TextBox: can plot a textbook w/ text or scaled image -- see compPlot.R
TextBox(i, midWidth, midHeight, GetJobHalfWidth(i), GetJobHalfHeight(i),
bg = bg, col = jtcol, img = GetPic(i),
txd = textXDisplace, tyd = textYDisplace, size = size)
}
}
# use new == TRUE, the next high-level plotting command will not clean the
# frame before drawing
par(mfrow = c(1, 1), mar = c(0, 0, 0, 0), new = TRUE)
# Initialize plot and draw optional border (border to be phased out)
{
NewPlot() # create an initially empty plot
TogglePlot(queuePlotRange) # set drawing region to be queue (middle)
DrawBorder("grey", "white")
}
# Draw main components of plot
{
TextBox(paste("System Clock: t =", pround(time)), 45, 170, 25,
size = fsClock_)
#size = fs(18))
# draw lines defining the queue boundaries
{
segments (30, c(73, 127), x1 = 128, lwd = 2)
segments (128, c(73, 127), y1 = c(80, 120), lwd = 2)
}
# draw server node with current time and processing node
{
f.svrColor <- simcolors$busySvr
f.svcText <-
if (length(serviceTime) == 0 || is.na(serviceTime))
bquote(s[.(idsInSystem[1])] == ...)
else if (serviceTime <= 0)
#bquote(s[.(idsInSystem[1])] == .(sym$infinity))
bquote(s[.(idsInSystem[1])] == infinity)
else bquote(s[.(idsInSystem[1])] == .(pround(serviceTime)))
f.cmpText <-
if (length(completionTime) == 0 || is.na(completionTime))
bquote(c[.(idsInSystem[1])] == ...)
else if (completionTime <= 0)
#bquote(c[.(idsInSystem[1])] == .(sym$infinity))
bquote(c[.(idsInSystem[1])] == infinity)
else bquote(c[.(idsInSystem[1])] == .(pround(completionTime)))
if (length(serviceTime) == 0 || is.na(serviceTime)
|| length(completionTime) == 0 || is.na(completionTime))
f.svrColor <- simcolors$pendSvr
if (length(idsInSystem) == 0) {
f.svrColor <- simcolors$idleSvr
f.svcText <- "idle"
f.cmpText <- ""
}
roundrect(c(150, 100), radx = 15, rady = 50, rx = 5, col = f.svrColor)
TextBox(f.svcText, 150, 130, 20, size = fsQueueSvcTime_) #fs(12))
TextBox(f.cmpText, 150, 65, 20, size = fsQueueSvcTime_) #fs(12))
if (length(idsInSystem) > 0)
DrawJob(idsInSystem[1], midWidth = 150, midHeight = 100,
textXDisplace = 1.3, bg = simcolors$svgJob,
size = fsJob_)
}
# Draw incoming and outgoing arrows and jobs next to them
{
boxhw_ <- if (Sys.getenv("RSTUDIO") == 1) 15 else 12
TextBox(text = "", mw = 12, mh = 100, hw = boxhw_, hh = 50,
bg = simcolors$arr)
#TextBox("", 12, 100, 12, 50, bg = simcolors$arr)
Arrows( 5, 100, 25, 100)
Arrows(170, 100, 190, 100)
if (idNextToEnter > 0) {
DrawJob(idNextToEnter, midWidth = 10, midHeight = 100,
textXDisplace = 1.3, bg = simcolors$inqJob,
size = fsJob_)
f.iarText <-
if (is.na(interarrivalTime))
bquote(r[.(idNextToEnter)] == ...)
else
bquote(r[.(idNextToEnter)] == .(pround(interarrivalTime)))
f.arrText <-
if (is.na(arrivalTime))
bquote(a[.(idNextToEnter)] == ...)
else
bquote(a[.(idNextToEnter)] == .(pround(arrivalTime)))
#fsize <- if (is.na(arrivalTime)) fs(10) else fs(10 - log10(arrivalTime))
fsize <- if (is.na(arrivalTime)) fsQueueArrTimeL_
else fsQueueArrTimeS_(arrivalTime)
# font size changes per increasing time
# mw: mid-width mh: mid-height hw: half-width
#TextBox(f.iarText, mw = 12, mh = 131, hw = 12, size = fsize)
#TextBox(f.arrText, mw = 12, mh = 65, hw = 12, size = fsize)
TextBox(f.iarText, mw = 12, mh = 131, hw = boxhw_, size = fsize)
TextBox(f.arrText, mw = 12, mh = 65, hw = boxhw_, size = fsize)
}
if (idJustServed > 0) {
DrawJob(idJustServed, midWidth = 178, midHeight = 100,
textYDisplace = 1.6, bg = simcolors$svdJob,
size = fsJob_)
}
}
# Draw current progress bar
{
# Scaled half-height of progress bar
#f.phh <- ScaleFont(300)
f.phh <- if (Sys.getenv("RSTUDIO") == 1) ScaleFont(225) else ScaleFont(300)
barmh_ <- if (Sys.getenv("RSTUDIO") == 1) 20 else 20
#rect(0, 25 - f.phh, 200, 25 + f.phh, col = simcolors$progbar[1])
#rect(0, 25 - f.phh, 200 * currentProgress, 25 + f.phh,
# col = simcolors$progbar[2])
rect(0, barmh_ - f.phh, 200, barmh_ + f.phh, col = simcolors$progbar[1])
rect(0, barmh_ - f.phh, 200 * currentProgress, barmh_ + f.phh,
col = simcolors$progbar[2])
# Output message associated with current progress
f.ptext <- paste(sep = "", round(currentProgress * 100), "% Completed",
if (numRejects > 0) paste(" (", numRejects, " Rejected)", sep = ""))
# mw: mid-width mh: mid-height hw: half-width hh: half-height
TextBox (f.ptext, mw = 100, mh = barmh_, hw = 100, hh = f.phh,
col = simcolors$progtxt, size = fsCompleted_)
#TextBox (f.ptext, mw = 100, mh = 25, hw = 100, hh = f.phh,
# col = simcolors$progtxt, size = fs(15))
}
# Handle possible reject pathway
#if (maxInSystem < Inf && idJustRejected > 0)
# DrawJob(idJustRejected, midWidth = 10, midHeight = 30,
# textXDisplace = 1.3, bg = "red", size = fsJob_)
}
# Try to print all of the nodes in queue
if (length(idsInSystem) > 1)
{
f.lastjob <- idsInSystem[length(idsInSystem)]
# Last job in system
f.numslots <- max(3, 7 - floor(log10(f.lastjob)))
# Max num elements shown in queue
es <- function(n) return(134 - 100 * (n - 1)/f.numslots)
# X-Scale for nth element
# largest index to consider plotting; jobs after this (besides last) not
# plotted
f.peakindex <- min(f.numslots, length(idsInSystem))
for (i in 2:f.peakindex) {
# If queue is too long, insert "..."
if (i == f.numslots - 1 && length(idsInSystem) > f.numslots)
points(
x = es(f.numslots - 1) + 3 * c(-1,0,1),
y = c(100,100,100),
cex = 0.5, col = "black"
)
# If last job slot to fill, plot the last element in the queue
else if (i == f.numslots)
DrawJob(f.lastjob, midWidth = es(i), midHeight = 100,
textYDisplace = -1.6, bg = simcolors$inqJob,
size = fsJob_)
# Otherwise, just plot the ith element with default x-scaling
else
DrawJob(idsInSystem[i], midWidth = es(i), midHeight = 100,
textYDisplace = -1.6, bg = simcolors$inqJob,
size = fsJob_)
}
}
}
##############################################################################
##############################################################################
## PlotInitSkyline
## --------------------------------------------------------------------------
## Initialized the Skyline plot depending on specifications
##############################################################################
PlotInitSkyline <- function (xRange = c(0,1), yRange = c(0,1))
{
skybgRange <- skylinePlotRange + c(-5, 5, 0, 5) # Skyline background range
canShow <- c(showSkylineSystem, showSkylineQueue, showSkylineServer)
ScalePlots(skylinePlotRange)
# set drawing region to be lower-left skyline functions area
TogglePlot(skybgRange)
#DrawBorder("grey", "white") # Vadim
DrawBorder(br = "grey", col = "white", xLowerLeft = -5)
TextBox("t", 194, 34, 5, 5)
# set the drawing region to again be the lower-left, but now overlay
TogglePlot(skylinePlotRange, initPlot = FALSE, mar = c(1,1,1,1))
plot(NA, NA, xlim = xRange, ylim = yRange, xaxt = "n", yaxt = "n",
xlab = "", ylab = "", bty = "n", las = 1, type = "s")
#fontScale <- ScaleFont(15)
if (sum(canShow) > 0) {
legend("top", c("System", "Queue", "Server", "Avg")[canShow > 0],
lty = c(canShow[canShow > 0], 2),
col = simcolors$sky[c(which(canShow > 0), 1)],
cex = fsSkyLegend_, x.intersp = 0.5, horiz = TRUE)
}
title(paste("Number in System and In Queue"), cex.main = 1) #0.975)
}
##############################################################################
##############################################################################
## PlotSkyline
## --------------------------------------------------------------------------
## Plots Skyline plot in appropriate spot if showSkyline is true
##############################################################################
PlotSkyline <- function(times,
numsInSystem,
numsInQueue,
numsInServer,
rangeForPlotting,
entireRange,
forceDraw = FALSE)
{
rangePlot <- rangeForPlotting
rangeAll <- entireRange
yminratio <- (-0.4)/ScaleFont(15)
# Special ymin ratio to scale w/ resizings
# If there's not enough data to plot, plot base graph and return
if (length(rangeAll) < 2)
{
#PlotInitSkyline(c(-0.02, 0.8), c(yminratio, 1.5))
PlotInitSkyline(c(0, 1), c(yminratio, 1.5))
axis(1, 0:1, line = -2, cex.axis = ScaleFont(15))
axis(2, 0:1, line = 0, tck = -0.02, mgp = c(3,0.5,0),
cex.axis = ScaleFont(15), las = 1)
#points(0, 0, col = "black", cex = 0.5, pch = 19)
return()
}
if (length(rangePlot) > 1 && length(rangeAll) > 1) {
# Some function-side cleaning to ensure that no na values are let pass
#while (is.na(numsInSystem[rangePlot[2]])) rangePlot[2] <- rangePlot[2] - 1
#while (is.na(numsInSystem[rangeAll[2]])) rangeAll[2] <- rangeAll[2] - 1
rangePlot[2] <- max(which(!is.na(numsInSystem)))
# index of last non-NA entry
rangeAll[2] <- max(which(!is.na(numsInSystem)))
rangePlot <- rangePlot[1]:rangePlot[2]
rangeAll <- rangeAll [1]:rangeAll [2]
}
# Get subsets of input to match range
timesSub <- times[rangePlot]
numsSub <- numsInSystem[rangePlot]
numsQSub <- numsInQueue[rangePlot]
numsSSub <- numsInServer[rangePlot]
maxTime <- timesSub[length(timesSub)]
minTime <- timesSub[1]
# Specify the plotting range
xRange <- c(minTime, maxTime)
yRange <- c(yminratio, 1.5) * max(numsSub)
PlotInitSkyline(xRange, yRange)
# Plot lines and axes for the final graph
if (showSkylineSystem)
lines(timesSub, numsSub, type = "s", col = simcolors$sky[1], lwd = 1.25)
if (showSkylineQueue)
lines(timesSub, numsQSub, type = "s", col = simcolors$sky[2])
if (showSkylineServer)
lines(timesSub, numsSSub, type = "s", col = simcolors$sky[3], lwd = 0.75)
xlabs <- pretty(c(minTime, maxTime))
ylabs <- 0:max(numsSub)
if (ylabs[length(ylabs)] > 5) ylabs <- pretty(ylabs)
axis(1, xlabs, line = -2, cex.axis = ScaleFont(15))
axis(2, ylabs, line = 0, tck = -0.02, mgp = c(3,0.5,0),
cex.axis = ScaleFont(15), las = 1)
# notes: tck used to reduce tick length;
# mgp used to reduce distance b/w tick & label from 1 to 0.5
# Plot average nums for the execution so far
if (length(times[!is.na(times)]) > 1)
{
if (showSkylineSystem)
segments(
xRange[1], meanTPS(times[rangeAll], numsInSystem[rangeAll]),
xRange[2], lty = "dashed", col = simcolors$sky[1])
if (showSkylineQueue)
segments(
xRange[1], meanTPS(times[rangeAll], numsInQueue[rangeAll]),
xRange[2], lty = "dashed", col = simcolors$sky[2])
if (showSkylineServer)
segments(
xRange[1], meanTPS(times[rangeAll], numsInServer[rangeAll]),
xRange[2], lty = "dashed", col = simcolors$sky[3])
}
}
##############################################################################
calendar_xRange <- calendarPlotRange[2] - calendarPlotRange[1]
calendar_yRange <- calendarPlotRange[4] - calendarPlotRange[3]
stats_yRange <- statsPlotRange[4] - statsPlotRange[3]
# Ranges for Plots (Keep bounds in (10, 190))
# - Order of vectors in Range : xmin, xmax, ymin, ymax
# - Add/Minus Multiple of xRange : Shifts as percentage of full plot
# subRange <- baseRange + shift per dimension (allows for changes in baserange)
chyronRange <- calendarPlotRange + c(0, 0, calendar_yRange - 6, 0)
box1Range <- calendarPlotRange +
c(0.01 * calendar_xRange, -0.75 * calendar_xRange, 0, -5)
box2Range <- calendarPlotRange +
c(0.25 * calendar_xRange, -0.24 * calendar_xRange, 0, -5)
box3Range <- calendarPlotRange +
c(0.73 * calendar_xRange, -0.04 * calendar_xRange, 0, -5)
box2PlotRange <- box2Range + c(10, -5, 20, -10)
box2TimeRange <- box2Range + c(10, -5, 10, 0)
box2TimeRange[4] <- box2TimeRange[3] + 10
statsRange <- statsPlotRange + c(-5, 5, 0, 5)
tpsStatsRange <- statsPlotRange + c(0, 0, 5 + stats_yRange * ( 0.48), 0)
booStatsRange <- statsPlotRange + c(0, 0, 5, stats_yRange * (-0.44))
# Function to set title of the current step component
SetChyron <- function(text) {
TogglePlot(chyronRange)
TextBox(text, 100, 100, 100, 100,
bg = "grey22", col = "white", font = 2, size = fsChyron_)
#bg = "grey22", col = "white", font = 2, size = fs(18))
}
##############################################################################
## DrawEmptyIDF
## --------------------------------------------------------------------------
## Draw an empty "No Active Generation" box that will eventually be overlayed
## with an interarrival or service time inversion animation.
##############################################################################
DrawEmptyIDF <- function()
{
# Plot the main background for component
TogglePlot(calendarPlotRange + c(0, 0, 0, -4.5))
DrawBorder(calendarPlotBGColor, calendarPlotBGColor)
TogglePlot(box2PlotRange + c(-10, 10, -6, 6))
DrawBorder("grey")
TextBox("No Active \nGeneration", 100, 100, 100, 100, size = fsSubtitle_)
#TextBox("No Active \nGeneration", 100, 100, 100, 100, size = fs(20))
}
##############################################################################
##############################################################################
## DrawIDF
## --------------------------------------------------------------------------
## Draw IDF plot in steps plot box 2/3 in accordance with inputs
## - u is the random variate from U(0,1) as generated in DrawInversionProcess
## - x is the resulting mapped value from x
## - xColor is the color of the x (should correlate with plt.colors)
## - gxRange is the x range (0.01 - 0.99 expected) of the plot
## - g{x/y}Exact are the x and y coordinates of the theorhetical CDF
## - invline dictates whether to draw a line to continue potential idf above
##############################################################################
DrawIDF <- function(u, x = NA, gxRange, gxExact, gyExact, isArrival = FALSE)
{
timeMax <- gxRange[2]
xWidth <- diff(gxRange)
gyRange <- if (u > 0.1) c(-0.05,1) else c(-0.2,1)
f.pcol <- if (isArrival) simcolors$arr else simcolors$svc
f.xlab <- if (isArrival) "r" else "s"
TogglePlot(box2PlotRange, initPlot = FALSE)
plot(NA, NA, xlim = gxRange, ylim = gyRange,
xaxt = "n", yaxt = "n", bty = "n", xlab = "")
# wrap up so axis isn't drawn twice (heavy)
# draw an empty-line bottom axis with ticks...
if (is.na(x)) {
axis(side = 1, at = pretty(c(0, timeMax)), lwd = 0,
lwd.ticks = 1, labels = TRUE,
padj = -0.5/fsIDFAxis_, cex.axis = fsIDFAxis_)
#padj = -0.5/ScaleFont(15), cex.axis = ScaleFont(15))
axis(side = 1, at = pretty(c(0, timeMax)), labels = FALSE, tcl = 0,
padj = -0.5, cex.axis = fsIDFAxis_)
#padj = -0.5, cex.axis = ScaleFont(15))
text(timeMax*1.08, gyRange[1] - 0.05, f.xlab,
cex = ScaleFont(15), xpd = NA)
axis(side = 2, at = c(0, 0.25, 0.5, 0.75, 1), las = 1,
cex.axis = fsIDFAxis_)
#cex.axis = ScaleFont(15))
} else {
# draw segments before points so points will cover segments
segments(0, u, min(x, timeMax), u, lty = "dashed", lwd = 1, col = "red")
if (x < timeMax) segments(x, 0, x, u, lty = "dashed", lwd = 1, col = "red")
}
DrawPoint(-xWidth/55, u, simcolors$u, cex = fsIDFPoint_) # ScaleFont(25))
if (!is.na(x) && x < timeMax)
DrawPoint(x, if (gyRange[1] == -0.05) -0.02 else -0.15,
f.pcol, cex = fsIDFPoint_) # ScaleFont(25))
# draw the curve at the end so it covers segments
lines(gxExact, gyExact, col = f.pcol, lwd = 2)
}
##############################################################################
##############################################################################
## DrawTimelineComponents
## --------------------------------------------------------------------------
## Draw timeline in steps plot box 2/3 in accordance with inputs
## - Time and maxTime are for plotting range, and should correlate with IDF
## - Arrival/completion times specified by simcolors$arr/simcolors$svc
## - Nodes designated as new/old will be marked with simcolors$new/grey, resp.
## - invline dictates whether to draw a line to continue potential idf above
##############################################################################
DrawTimelineComponents <- function(time,
maxTime,
arrivalTime = NA,
completionTime = NA,
oldTimes = NA, # may be one or more
newTime = NA,
# only new time to plot
inversionLine = TRUE
)
{
# Cover for clean slate before plotting
TogglePlot(box2TimeRange + c(-5, 11, -6.5, -6.5))
DrawBorder(NA, calendarPlotBGColor)
maxX <- time + maxTime
TogglePlot(box2TimeRange, initPlot = FALSE)
plot(NA, NA, xlim = c(time, maxX), ylim = c(0, 1),
bty = "n", xaxt = "n", yaxt = "n")
axis(side = 1, at = pround(time + pretty(c(0, maxTime))), lwd = 0,
lwd.ticks = 1, labels = TRUE,
padj = -0.5/fsIDFAxis_, cex.axis = fsIDFAxis_)
#padj = -0.5/ScaleFont(15), cex.axis = ScaleFont(15))
axis(side = 1, at = pround(time + pretty(c(0, maxTime + 0.25))),
labels = FALSE, tcl = 0, padj = -0.5,
cex.axis = fsIDFAxis_)
#cex.axis = ScaleFont(15))
if (inversionLine) {
segments(newTime, 0, newTime, 1, lty = "dashed", lwd = 1, col = "red")
}
showDots <- FALSE
for (i in 1:length(oldTimes)) {
if (isValNum(oldTimes[i]) && oldTimes[i] == Inf) oldTimes <- NA
else if (isValNum(oldTimes[i]) && oldTimes[i] > maxX)
{ oldTimes[i] <- maxX; showDots <- TRUE }
}
for (i in 1:length(newTime)) {
if (isValNum(newTime[i]) && newTime[i] == Inf) newTime[i] <- NA
else if (isValNum(newTime[i]) && newTime[i] > maxX)
{ newTime[i] <- maxX; showDots <- TRUE }
}
if (isValNum(arrivalTime) && arrivalTime == Inf)
{ arrivalTime <- NA }
else if (isValNum(arrivalTime) && arrivalTime > maxX)
{ arrivalTime <- maxX; showDots <- TRUE }
if (isValNum(completionTime) && completionTime == Inf)
{ completionTime <- NA }
else if (isValNum(completionTime) && completionTime > maxX)
{ completionTime <- maxX; showDots <- TRUE }
if (showDots) text(time + maxTime * 0.95, 0.1, "...")
DrawPoint(oldTimes, 0.1, col = "grey",
cex = fsIDFPoint_)
#cex = ScaleFont(25))
DrawPoint(arrivalTime, 0.1, col = simcolors$arrivalTime,
cex = fsIDFPoint_)
#cex = ScaleFont(25))
DrawPoint(completionTime, 0.1, col = simcolors$svc,
cex = fsIDFPoint_)
#cex = ScaleFont(25))
DrawPoint(newTime, 0.1, col = simcolors$newTime,
cex = fsIDFPoint_)
#cex = ScaleFont(25))
text(time + maxTime * 1.1, -0.05, "time",
cex = fsIDFAxis_, xpd = NA)
#cex = ScaleFont(15), xpd = NA)
}
##############################################################################
##############################################################################
## DrawCalendarComponents
## --------------------------------------------------------------------------
# Draws calendar with current values into topmost region on the left
# - calendarTimes are in the order: next arrival, next completion-of-service;
# - if either time is noted to be encircled, a blue text box will be drawn
# around that time
# - if either time is noted to be highlighted, its background will be yellow
##############################################################################
DrawCalendarComponents <- function(calendarTimes = c(0,0),
encircleTimes = c(FALSE, FALSE),
highlightTimes = c(FALSE, FALSE)
)
{
TogglePlot(box1Range)
# determine whether to highligth either the arrival or CoS
arrcol <- if (highlightTimes[1]) "yellow" else simcolors$arr
svccol <- if (highlightTimes[2]) "yellow" else simcolors$svc
TextBox("", 100, 110, 75, 75, bg = "white")
TextBox("Calendar", 100, 180, 75, 15, col = "white",
bg = "lightsteelblue4", size = fsSubtitle_)
#bg = "lightsteelblue4", size = fs(20))
# determine whether the arrival or CoS (or both) should have a notifying
# blue rectangle draw around it
if (encircleTimes[1]) TextBox("", 100, 120, 67.5, 20, bg = "blue")
if (encircleTimes[2]) TextBox("", 100, 60, 67.5, 20, bg = "blue")
TextBox("Next Arrival", 100, 150, 100, 10, size = fsSubtext_)
TextBox("Next Completion", 100, 88, 100, 10, size = fsSubtext_)
TextBox(paste("a =", pround(calendarTimes[1])), 100, 120, 60, 15,
bg = arrcol, size = fsSubtext_)
TextBox(paste("c =", pround(calendarTimes[2])), 100, 60, 60, 15,
bg = svccol, size = fsSubtext_)
}
##############################################################################
##############################################################################
## DrawCurrCalendar
## --------------------------------------------------------------------------
# Function to be initialized by setDrawCurrCalendar
# Draws the current calendar with default values overridable by parameters
##############################################################################
CalendarVals <- list(arrivalTime = 0,
completionTime = 0,
encircleTimes = c(FALSE, FALSE),
highlightTimes = c(FALSE, FALSE))
DrawCurrCalendar <- function(arrivalTime,
completionTime,
encircleTimes,
highlightTimes,
forceDraw = FALSE)
{
if (missing(arrivalTime))
arrivalTime <- CalendarVals[["arrivalTime"]]
if (missing(completionTime))
completionTime <- CalendarVals[["completionTime"]]
if (missing(encircleTimes))
encircleTimes <- CalendarVals[["encircleTimes"]]
if (missing(highlightTimes))
highlightTimes <- CalendarVals[["highlightTimes"]]
DrawCalendarComponents(c(arrivalTime, completionTime),
encircleTimes, highlightTimes)
}
##############################################################################
# Updates DrawCurrCalendar with specified value defauls
##############################################################################
setDrawCurrCalendar <- function(arrivalTime,
completionTime,
encircleTimes = c(FALSE, FALSE),
highlightTimes = c(FALSE, FALSE)
)
{
plotCalendar <- FALSE
if (!missing(arrivalTime)) { CalendarVals[["arrivalTime"]] <<- arrivalTime; plotCalendar <- TRUE }
if (!missing(completionTime)) { CalendarVals[["completionTime"]] <<- completionTime; plotCalendar <- TRUE }
if (!missing(encircleTimes)) { CalendarVals[["encircleTimes"]] <<- encircleTimes; plotCalendar <- TRUE }
if (!missing(highlightTimes)) { CalendarVals[["highlightTimes"]] <<- highlightTimes; plotCalendar <- TRUE }
return(plotCalendar)
}
##############################################################################
##############################################################################
## DrawCurrTimeline
## --------------------------------------------------------------------------
# Function with default values in TimeLineVals
# Draws the current timeline with default values overridable by parameters
##############################################################################
TimeLineVals <- list(time = 0,
maxTime = 4,
arrivalTime = 0,
completionTime = 0,
oldTimes = NA,
newTime = NA)
DrawCurrTimeline <- function(time,
arrivalTime,
completionTime,
oldTimes,
newTime,
inversionLine = TRUE,
forceDraw = FALSE)
{
if (pauseData$plotDelay == 0 && !forceDraw) return()
if (pauseData$plotDelay == -2) return()
if (missing(time)) time <- TimeLineVals[["time"]]
if (missing(arrivalTime)) arrivalTime <- TimeLineVals[["arrivalTime"]]
if (missing(completionTime)) completionTime <- TimeLineVals[["completionTime"]]
if (missing(oldTimes)) oldTimes <- TimeLineVals[["oldTimes"]]
if (missing(newTime)) newTime <- TimeLineVals[["newTime"]]
DrawTimelineComponents(time,
TimeLineVals[["maxTime"]],
arrivalTime,
completionTime,
oldTimes,
newTime,
inversionLine)
}
##############################################################################
# Updates DrawCurrTimeline with specified value defauls
##############################################################################
setDrawCurrTimeline <- function(time,
maxTime,
arrivalTime,
completionTime,
oldTimes = NA,
newTime = NA
)
{
plotTimeline <- FALSE
if (!missing(time)) { TimeLineVals[["time"]] <<- time; plotTimeline <- TRUE; }
if (!missing(maxTime)) { TimeLineVals[["maxTime"]] <<- maxTime; plotTimeline <- TRUE; }
if (!missing(arrivalTime)) { TimeLineVals[["arrivalTime"]] <<- arrivalTime; plotTimeline <- TRUE; }
if (!missing(completionTime)) { TimeLineVals[["completionTime"]] <<- completionTime; plotTimeline <- TRUE; }
if (!missing(oldTimes)) { TimeLineVals[["oldTimes"]] <<- oldTimes; plotTimeline <- TRUE; }
if (!missing(newTime)) { TimeLineVals[["newTime"]] <<- newTime; plotTimeline <- TRUE; }
return(plotTimeline)
}
##############################################################################
##############################################################################
## SpecifyEventSteps
## --------------------------------------------------------------------------
## Plots Steps Required In Handling Current Event
## The 'process' parameter determines the steps to be animated for each type
## of event and is defined as follows:
## 0: initialize the calendar
## 1: generate service time for a customer arriving to empty system
## 2: generate next interarrival time for a customer arriving to empty
## system (and already entered/entering into service)
## 3: generate next interarrival time for a customer arriving to non-empty
## system, and therefore entering the queue
## 4: generate next completion-of-service time when customer departs
## with other customers waiting in the queue
## 5: nothing in queue to be served, so put infinity for CoS in calendar
## 6: arrivals have been stopped, so put infinity for arrival in calendar
## 7: plot a customer rejection (arriving to full system)
##
## Note that this function returns (almost) immediately if the plotDelay is
## set to 0 (i.e., jump to final graphical display), returning only the
## interarrival or service time generated.
##############################################################################
SpecifyEventSteps <- function(currentTime,
arrivalTime,
completionTime,
process, # see definitions above
isArrival,
advanceTime,
numInSystem,
forceDraw = FALSE
)
{
if ((pauseData$plotDelay == 0 && !forceDraw) ||
(pauseData$plotDelay == -2))
{
# if not drawing/plotting, just return the intarr or service time gen'd;
# recall that Vadim's approach expects numInSystem as first argument
# (perhaps revisit this idea later?)
vfunc <- if (isArrival == TRUE) interarrivalFcn else serviceFcn
return(vfunc(num_in_sys = numInSystem, as_list = FALSE))
}
# note: these appear in the "action" bar in pairs as msg[1]:msg[1+i]
if (process == 0)
msg <- c(
# this process describes generating the first arrival time
# on calendar initialization
"Initialize Calendar", # always present
"Initially, Impossible Events", # first action
"Generate U(0, 1) For 1st Interarrival", # second action ...
"Generate 1st Interarrival Time By Inverting",
"Compute 1st Arrival Time",
"Place 1st Arrival Time On Calendar"
)
else if (process == 1)
msg <- c(
# this process describes generating the service time for
# a customer that arrives to an empty system
"Process Arrival (Server Idle)",
"Check Calendar Times For Minimum Time",
"Generate U(0, 1) For Next Service Time",
"Compute Next Service Time By Inverting",
"Compute Next Completion Time",
"Place Next Completion Time On Calendar"
)
else if (process == 2)
msg <- c(
# this process describes generating the next arrival time
# when a customer arrives to an empty system
# (technically the customer immediately enters service
# so we do not describe as "Server Idle"
"Process Arrival",
"Check Calendar Times For Minimum Time",
"Generate U(0, 1) For Next Interarrival Time",
"Compute Next Interarrival Time By Inverting",
"Compute Next Arrival Time",
"Place Next Arrival Time On Calendar"
)
else if (process == 3)
msg <- c(
# this process describes generating the next arrival time
# when a customer arrives to an non-empty system and enters queue
"Process Arrival (System Not Empty)",
"Check Calendar Times For Minimum Time",
"Generate U(0, 1) For Next Interarrival Time",
"Compute Next Interarrival Time By Inverting",
"Compute Next Arrival Time",
"Place Next Completion Time on Calendar"
)
else if (process == 4)
msg <- c(
# this process describes generating the next CoS time
# when a customer departs with other customers waiting in queue
"Process Completion (Queue Not Empty)",
"Check Calendar Times For Minimum Time",
"Generate U(0, 1) For Next Service Time",
"Compute Next Service Time By Inverting",
"Compute Next Completion Time",
"Place Next Completion Time on Calendar"
)
# SPECIAL PROCESS: REMOVE SERVICE TIME
else if (process == 5) {
# this process is called when there is nothing to serve,
# puts inf in CoS slot in calendar
DrawServiceAsImpossible(currentTime, arrivalTime, completionTime)
#if (pauseData$menuChoice %in% c('q','e','j'))
# return(c(NA, pauseData$menuChoice))
# this vector is returned to specifyCurrentEventSteps
# if not quitting, ending, or jumping, just return (NULL)
return()
}
# SPECIAL PROCESS: REMOVE ARRIVAL TIME
else if (process == 6) {
# this process is called when stopping arrivals, puts inf
# in arrival slot in calendar
DrawArrivalAsImpossible(currentTime, arrivalTime, completionTime)
#if (pauseData$menuChoice %in% c('q','e','j'))
# return(c(NA, pauseData$menuChoice))
# this vector is returned to specifyCurrentEventSteps
# if not quitting, ending, or jumping, just return (NULL)
return()
}
else if (process == 7) {
# this process describes animate a rejection when a customer
# arrives to an already-at-capacity system
DrawRejection(currentTime, arrivalTime, completionTime)
#if (pauseData$menuChoice %in% c('q','e','j'))
# return(c(NA, pauseData$menuChoice))
# this vector is returned to specifyCurrentEventSteps
# if not quitting, ending, or jumping, just return (NULL)
return()
}
# DrawInversionProcess has several calls to PauseCurrPlot in it, from
# which the user may choose 'q', 'e', or 'j'...
#return(DrawInversionProcess(currentTime, arrivalTime, completionTime,
# isArrival, process, msg,
# advanceTime, numInSystem))
variate <- DrawInversionProcess(currentTime, arrivalTime, completionTime,
isArrival, process, msg,
advanceTime, numInSystem,
forceDraw = forceDraw)
return(variate)
} # SpecifyEventSteps(...)
##############################################################################
##############################################################################
## DrawServiceAsImpossible
## --------------------------------------------------------------------------
## Special progression function that visualizes placing inf on calendar for
## completion of service
##############################################################################
DrawServiceAsImpossible <- function(curr.time, arr.time, cmp.time)
{
# Plot the main plot for the component
ScalePlots(calendarPlotRange)
# set the plotting region to be the topmost, with calendar and inversion
TogglePlot(calendarPlotRange)
DrawBorder(calendarPlotBGColor, calendarPlotBGColor)
SetChyron("Advance System Clock")
DrawEmptyIDF()
DrawCurrCalendar(encircleTimes = c(FALSE, TRUE))
DrawCurrTimeline(time = cmp.time)
#pauseData <<- PauseCurrPlot(pauseData, "DrawServiceAsImpossible: #1")
pauseData <<- PauseCurrPlot("DrawServiceAsImpossible: #1")
if (pauseData$menuChoice %in% c('q','e','j')) return()
SetChyron("Process Completion (Queue Empty) : Remove Expired Completion From Calendar")
DrawEmptyIDF()
setDrawCurrCalendar(completionTime = Inf)
DrawCurrCalendar(highlightTimes = c(FALSE, TRUE))
setDrawCurrTimeline(time = cmp.time, completionTime = NA)
DrawCurrTimeline(newTime = c(cmp.time), inversionLine = FALSE)
#pauseData <<- PauseCurrPlot(pauseData, "DrawServiceAsImpossible: #2")
pauseData <<- PauseCurrPlot("DrawServiceAsImpossible: #2")
if (pauseData$menuChoice %in% c('q','e','j')) return()
if (arr.time != Inf) {
#SetChyron(paste("Determine Next Event Type : Find Min Time In Calendar"))
SetChyron(paste("Determine Next Event Type : Find Minimum Time On Calendar"))
DrawCurrTimeline(inversionLine = FALSE)
DrawCurrCalendar(encircleTimes = c(TRUE, FALSE))
}
#pauseData <<- PauseCurrPlot(pauseData, "DrawServiceAsImpossible: #3")
pauseData <<- PauseCurrPlot("DrawServiceAsImpossible: #3")
if (pauseData$menuChoice %in% c('q','e','j')) return()
}
##############################################################################
##############################################################################
## DrawArrivalAsImpossible
## --------------------------------------------------------------------------
## Special progression function that visualizes placing inf on calendar for
## next arrival (i.e., "closes the door on arrivals")
##############################################################################
DrawArrivalAsImpossible <- function(curr.time, arr.time, cmp.time)
{
# Plot the main plot for the component
ScalePlots(calendarPlotRange)
# Plot the main background for component
TogglePlot(calendarPlotRange)
DrawBorder(calendarPlotBGColor, calendarPlotBGColor)
SetChyron("Advance System Clock")
DrawEmptyIDF()
DrawCurrCalendar(encircleTimes = c(TRUE, FALSE))
DrawCurrTimeline(time = arr.time)
#pauseData <<- PauseCurrPlot(pauseData, "DrawArrivalAsImpossible: #1")
pauseData <<- PauseCurrPlot("DrawArrivalAsImpossible: #1")
if (pauseData$menuChoice %in% c('q','e','j')) return()
SetChyron("Finalizing Arrival : Cease Further Arrival Generations")
DrawEmptyIDF()
setDrawCurrCalendar(arrivalTime = Inf)
DrawCurrCalendar(highlightTimes = c(TRUE, FALSE))
setDrawCurrTimeline(time = arr.time, arrivalTime = NA)
DrawCurrTimeline(newTime = c(arr.time), inversionLine = FALSE)
#pauseData <<- PauseCurrPlot(pauseData, "DrawArrivalAsImpossible: #2")
pauseData <<- PauseCurrPlot("DrawArrivalAsImpossible: #2")
if (pauseData$menuChoice %in% c('q','e','j')) return()
SetChyron("Progressing Simulation : Awaiting Next Service")
#pauseData <<- PauseCurrPlot(pauseData, "DrawArrivalAsImpossible: #3")
pauseData <<- PauseCurrPlot("DrawArrivalAsImpossible: #3")
if (pauseData$menuChoice %in% c('q','e','j')) return()
}
##############################################################################
##############################################################################
## DrawRejection
## --------------------------------------------------------------------------
## Special progression function that visualizes the ejection of completion time
##############################################################################
DrawRejection <- function(curr.time, arr.time, cmp.time)
{
# Plot the main plot for the component
ScalePlots(calendarPlotRange)
# Plot the main background for component
TogglePlot(calendarPlotRange)
DrawBorder(calendarPlotBGColor, calendarPlotBGColor)
SetChyron("Advance System Clock")
DrawEmptyIDF()
DrawCurrQueue(idNextToEnter = SSQVals["idJustRejected"], idJustRejected = 0)
# Shows SSQ before rejection
DrawCurrCalendar(encircleTimes = c(TRUE, FALSE))
DrawCurrTimeline(time = arr.time)
#pauseData <<- PauseCurrPlot(pauseData, "DrawRejection: #1")
pauseData <<- PauseCurrPlot("DrawRejection: #1")
if (pauseData$menuChoice %in% c('q','e','j')) return()
SetChyron("Rejecting Arrival : Ejecting Job and Removing Stale Arrival")
DrawEmptyIDF()
DrawCurrQueue()
setDrawCurrCalendar(arrivalTime = Inf)
DrawCurrCalendar(highlightTimes = c(TRUE, FALSE))
setDrawCurrTimeline(time = arr.time, arrivalTime = Inf)
DrawCurrTimeline(newTime = c(arr.time), inversionLine = FALSE)
#pauseData <<- PauseCurrPlot(pauseData, "DrawRejection: #2")
pauseData <<- PauseCurrPlot("DrawRejection: #2")
if (pauseData$menuChoice %in% c('q','e','j')) return()
}
##############################################################################
##############################################################################
## DrawInversionProcess
## --------------------------------------------------------------------------
## Plots calendar changes experienced during sim progression as popup window
## Called from the end of SpecifyEventSteps
##############################################################################
DrawInversionProcess <- function(
time,
arrivalTime,
completionTime,
isArrival,
process,
# defined b/w 0-7: see SpecifyEventSteps above
message,
# vector of event-steps (see SpecifyEventSteps)
advanceTime,
# boolean: whether time should advance here
numInSystem,
forceDraw = FALSE
)
{
# Determine whether the function being plotted is interarrival or service
# function; recall that this function will be a list (see generators.R, with
# asList parameter) containing the u and x values, quantile function, and
# text representation of the distribution
vfunc <- if (isArrival == TRUE) interarrivalFcn else serviceFcn
# Parse initial arrival and completion time to a value
arr0 <- if (arrivalTime == 0) Inf else arrivalTime
cmp0 <- if (completionTime == 0) Inf else completionTime
# Compute inversion and get results and function for plotting
result <- vfunc(numInSystem, TRUE)
u <- result[["u"]] # Random variable generated from stream
x <- result[["x"]] # Mapping of u onto distribution
quantFnc <- result[["quantile"]] # q* function parameterized to distribution
plotText <- result[["text"]] # Textual representation of distribution
# this is the typical plotDelay bailout... another one below (see comments
# there)
if ((pauseData$plotDelay == 0 && !forceDraw) || (pauseData$plotDelay == -2))
{
return(x)
}
# Specify CDF plot ranges and ideal curve data
gxRange <- quantFnc(c(0.01, 0.99))
gyExact <- seq(0, 1, by = (diff(0:1))/1000)
gxExact <- quantFnc(gyExact)
# Get variable set and color of graph based on type of graph;
# These will be displayed either in the arriving customer rectangle
# or in the server rounded rectangle, with corresponding times.
displayVars <- if (isArrival) c("r", "a") else c("s", "c")
## SETUP STAGE: Set plot scale, draw border, and set title ##
{
# Plot the main plot for the component
ScalePlots(calendarPlotRange)
# set the drawing region to be the upper-portion w/ calendar & inversion
TogglePlot(calendarPlotRange)
DrawBorder(calendarPlotBGColor, calendarPlotBGColor)
# either "Initialize Calendar" or "Advance System Clock"
ctitle <- if (time == 0) message[1] else "Advance System Clock"
SetChyron(ctitle)
DrawEmptyIDF()
DrawCalendarComponents(calendarTimes = c(arr0, cmp0),
encircleTimes = c(FALSE, FALSE))
DrawTimelineComponents(time = time,
maxTime = gxRange[2],
arrivalTime = arr0,
completionTime = cmp0,
oldTimes = NA,
newTime = NA)
}
# need a separate plotDelay check here -- if user choose plotDelay 0 out of
# the gate, we still want the top third of the window drawn, but without
# any of the steps... by this point, that top third is now drawn, so just
# return
if (pauseData$plotDelay == 0) return(x)
# Skip this step unless the time is advancing
if (advanceTime)
{
#pauseData <<- PauseCurrPlot(pauseData, "DrawInversionProcess: #1")
pauseData <<- PauseCurrPlot("DrawInversionProcess: #1")
# if user decides here to quit, end, or jump, we need to get out
# and (eventually) let the main simulation while loop handle it;
# from here, we return first to SpecifyEventSteps (and from there
# either to specifyCurrentEventSteps or to main)...
if (pauseData$menuChoice %in% c('q','e','j'))
return(x)
#return(c(x, pauseData$menuChoice))
# x is the mapped variate;
# for why returning a vector with menuChoice, see comments
# after the call to SpecifyEventSteps inside the definition
# of specifyCurrentEventSteps (~line 2370)
}
## STAGE 1: Plot Initial calendar and that is all
{
# message("stage 1")
# Plot initial calendar in slot 1
SetChyron(paste(message[1], ":", message[2]))
DrawCalendarComponents(
calendarTimes = c(arr0, cmp0),
encircleTimes = c(time == 0 || isArrival,
time == 0 || !isArrival) )
}
if (time == 0) {
#pauseData <<- PauseCurrPlot(pauseData, "DrawInversionProcess: #2")
pauseData <<- PauseCurrPlot("DrawInversionProcess: #2")
if (pauseData$menuChoice %in% c('q','e','j'))
return(x)
#return(c(x, pauseData$menuChoice))
}
sizeTitle_ <- if (Sys.getenv("RSTUDIO") == 1) fs(10) else fs(18)
sizeS_ <- if (Sys.getenv("RSTUDIO") == 1) fs(8) else fs(15)
sizeL_ <- if (Sys.getenv("RSTUDIO") == 1) fs(12) else fs(20)
## STAGE 2: Show generation of U(0,1) and put point on graph
{
# message("stage 2")
# Plot function inversion in slot 2
SetChyron(paste(message[1], ":", message[3]))
# If first generation, redraw calendar with arrival in replacement
if (time == 0) {
DrawCalendarComponents(calendarTimes = c(arr0, cmp0),
encircleTimes = c(isArrival, !isArrival))
}
# Create plot of theorhetical CDF and plot u on y-axis
TogglePlot(box2PlotRange + c(-16, 16, -8, 8))
DrawBorder(calendarPlotBGColor, calendarPlotBGColor)
# Display title of graph
TogglePlot(box2Range + c(-2, 2, 0, 0), initPlot = FALSE)
typeText <- if (isArrival) "Interarrival" else "Service"
TextBox(paste(typeText, "CDF:", plotText), 100, 180, 100, 10,
font = 2, size = fsSubtitle_)
#font = 2, size = fs(18))
# Switch to shifted subplot to plot actual ideal function
DrawIDF(u, x = NA, gxRange, gxExact, gyExact, isArrival)
DrawTimelineComponents(time = time,
maxTime = gxRange[2],
arrivalTime = arr0,
completionTime = cmp0,
oldTimes = NA,
newTime = NA)
# Plot variable movement in slot 3
TogglePlot(box3Range)
TextBox("U(0,1)", 125, 175, 75, 20, bg = simcolors$u, tyd = 0.3,
size = fsIDFTime_)
TextBox(pround(u), 125, 155, 75/2, 15, bg = "white", size = fsIDFTime_)
Arrows(50, 175, 15, 163)
}
#pauseData <<- PauseCurrPlot(pauseData, "DrawInversionProcess: #3")
pauseData <<- PauseCurrPlot("DrawInversionProcess: #3")
if (pauseData$menuChoice %in% c('q','e','j'))
return(x)
#return(c(x, pauseData$menuChoice))
## STAGE 3: Invert uniform value across function & show resulting statistic
{
# message("stage 3")
SetChyron(paste(message[1], ":", message[4]))
# Update CDF plot by adding inversion visual
DrawIDF(u, x = x, gxRange, gxExact, gyExact, isArrival)
if (isArrival)
setDrawCurrQueue(interarrivalTime = x)
else
#setDrawCurrQueue(shiftInServer = TRUE, serviceTime = x)
setDrawCurrQueue(serviceTime = x)
DrawCurrQueue()
TogglePlot(box3Range)
boxtitle <- paste("New", displayVars[1])
TextBox(boxtitle, 125, 110, 75, 20, tyd = 0.3,
bg = if (isArrival) simcolors$arr else simcolors$svc,
size = fsIDFTime_)
TextBox(pround(x), 125, 90, 75/2, 15, bg = "white",
size = fsIDFTime_)
Arrows(0, 110, 35, 110)
}
#pauseData <<- PauseCurrPlot(pauseData, "DrawInversionProcess: #4")
pauseData <<- PauseCurrPlot("DrawInversionProcess: #4")
if (pauseData$menuChoice %in% c('q','e','j'))
return(x)
#return(c(x, pauseData$menuChoice))
## STAGE 4: Add new value to timeline & compute resulting completion/arrival
## Tease at entrance of new value into calendar
{
# message("stage 4")
SetChyron(paste(message[1], ":", message[5]))
# Make box for new calendar time in box 3/3
TogglePlot(box3Range)
boxtitle <- paste("New", displayVars[2])
boxvalue <- paste(pround(time), "+", displayVars[1])
TextBox(boxtitle, 125, 45, 75, 20, bg = "yellow", tyd = 0.3,
size = fsIDFTime_)
TextBox(boxvalue, 125, 25, 56.25, 15, bg = "white",
size = fsIDFTime_)
Arrows(70, 90, 70, 60)
# Update timeline to show inversion line
DrawTimelineComponents(time = time,
maxTime = gxRange[2],
arrivalTime = arr0,
completionTime = cmp0,
oldTimes = NA,
newTime = time + x)
if (isArrival)
setDrawCurrQueue(arrivalTime = time + x)
else
setDrawCurrQueue(completionTime = time + x)
DrawCurrQueue()
# Add textbox underneath calendar to tease the new time's entrance into
# calendar
TogglePlot(box1Range)
TextBox(paste(displayVars[2], "=", pround(time + x)), 100, 20, 60, 15,
bg = "yellow", size = fsIDFTime_)
}
#pauseData <<- PauseCurrPlot(pauseData, "DrawInversionProcess: #5")
pauseData <<- PauseCurrPlot("DrawInversionProcess: #5")
if (pauseData$menuChoice %in% c('q','e','j'))
return(x)
#return(c(x, pauseData$menuChoice))
## STAGE 5: Add value to plot 1, updating visual calendar
{
# message("stage 5")
SetChyron(paste(message[1], ":", message[6]))
arr1 <- if ( isArrival) pround(time + x) else arr0
cmp1 <- if (!isArrival) pround(time + x) else cmp0
# Hide new value slot under calendar
TogglePlot(box1Range)
rect(38, 3, 162, 34.4, col = calendarPlotBGColor, border = NA)
# reinitialize ssqvis-scope DrawCurrCalendar with latest arr and cmp
setDrawCurrCalendar(arrivalTime = arr1,
completionTime = cmp1)
DrawCurrCalendar(encircleTimes = c(isArrival, !isArrival),
highlightTimes = c(isArrival, !isArrival))
DrawTimelineComponents(
time = time,
maxTime = gxRange[2],
arrivalTime = arr1,
completionTime = cmp1,
oldTimes = c(arr0, cmp0),
inversionLine = FALSE
)
}
#pauseData <<- PauseCurrPlot(pauseData, "DrawInversionProcess: #6")
pauseData <<- PauseCurrPlot("DrawInversionProcess: #6")
if (pauseData$menuChoice %in% c('q','e','j'))
return(x)
#return(c(x, pauseData$menuChoice))
## STAGE 6:
{
# message("stage 6")
DrawEmptyIDF()
# reinitialize ssqvis-scope DrawCurrTimeline with latest stats
setDrawCurrTimeline(time = time,
maxTime = gxRange[2],
arrivalTime = arr1,
completionTime = cmp1)
DrawCurrTimeline()
DrawCurrCalendar()
SetChyron("Initialization Complete : Begin Event Loop")
}
# Plot this at all times, but stop only if it's the first one
# Allows plot consistent setup into next visual
if (process == 0) {
#pauseData <<- PauseCurrPlot(pauseData, "DrawInversionProcess: #7")
pauseData <<- PauseCurrPlot("DrawInversionProcess: #7")
if (pauseData$menuChoice %in% c('q','e','j'))
return(x)
#return(c(x, pauseData$menuChoice))
}
## STAGE 7:
if (time == 0 || cmp1 < Inf)
{
# If completion time is set or it is the first step, find the minimum i
# calendar time and tease entrance in next call
# message("stage 7-1")
# Plot initial calendar in slot 1
SetChyron(paste("Determine Next Event Type : Find Mininum Time On Calendar"))
DrawCurrTimeline(inversionLine = FALSE)
minIsArrival <- (arr1 < cmp1) && arr1 != Inf
DrawCurrCalendar(encircleTimes = c(minIsArrival, !minIsArrival))
} else {
# If the completion time is unset, tease generation of new completion time
# message("stage 7-2")
# Plot initial calendar in slot 1
SetChyron(paste("Process Arrival : Generate New Service Time"))
DrawCurrTimeline(inversionLine = FALSE)
DrawCalendarComponents(
calendarTimes = c(arr1, cmp1),
encircleTimes = c(FALSE, TRUE),
highlightTimes = c(FALSE, FALSE)
)
}
# return the x value being generated
return(x)
}
##############################################################################
StatsValsFresh <- FALSE
StatsVals <- list(nt = rep(0, 3),
qt = rep(0, 3),
xt = rep(0, 3),
w = rep("-", 3),
s = rep("-", 3),
o = rep("-", 3))
##############################################################################
## DrawStatsTableComponents
## --------------------------------------------------------------------------
## Plots time-persistent and based-on-observation statistics in a grid in
## the lower right of the window.
##############################################################################
DrawStatsTableComponents <- function(n = 0,
nt = StatsVals[["nt"]],
qt = StatsVals[["qt"]],
xt = StatsVals[["xt"]],
w = StatsVals[["w"]],
s = StatsVals[["s"]],
o = StatsVals[["o"]],
forceDraw = FALSE
)
{
# Plot only with fresh StatsVals unless plot forced.
# If fresh values, update StatsValsFresh booleon
if (identical(list(nt, qt, xt, w, s, o), StatsVals))
{
if (forceDraw == FALSE && StatsValsFresh == FALSE) {
return()
} else {
StatsValsFresh <<- FALSE
}
} else {
StatsValsFresh <<- TRUE
}
# Compute tracking of number in queue and pull out plotted subset
ScalePlots(statsPlotRange)
# set the plotting region to be the stats grid in lower right
TogglePlot(statsRange)
DrawBorder("grey")
# Initialize DrawCol only once, push it to global instance
if (is.null(DrawCol))
{
tw <- c(35, 55, 55, 55)
th <- c(35, 40, 40, 40, 40)
hbg <- "lightgrey"
cbg <- "white"
# Build table mid- and half-coords
tmw <- tw[1]/2
tmh <- th[1]/2
if (length(tw) > 2) for (i in 2:length(tw))
tmw <- c(tmw, sum(tw[1:(i-1)]) + tw[i]/2)
if (length(th) > 2) for (i in 2:length(th))
tmh <- c(tmh, sum(th[1:(i-1)]) + th[i]/2)
tmh <- 200 - tmh
# Define DrawCell to be used in DrawCol
DrawCell <- function(text, c, r, bg = cbg, textf = NA)
{
TextBox(text, tmw[c], tmh[r+1], tw[c]/2, th[r+1]/2,
bg = bg, textf = pround, size = fsTableCell_)
}
# Define DrawCol
DrawCol <<- function(col, texts, bg = cbg)
{
for (i in 1:length(texts))
DrawCell(texts[i], col, i, bg = if (col == 1 || i == 1) hbg else bg)
}
}
col1 <- "white"
col2 <- "yellow"
TogglePlot(tpsStatsRange)
size_ <- if (Sys.getenv("RSTUDIO") == 1) 12 else 15
TextBox("Time-Averaged Statistics", 100, 200, 100, 17,
bg = "grey22", col = "white",
size = fsTableCell_)
DrawCol(1, c("", "@t", "avg", "sd"))
DrawCol(2, c("n(t)", nt), if (all(nt == StatsVals[["nt"]])) col1 else col2)
DrawCol(3, c("q(t)", qt), if (all(qt == StatsVals[["qt"]])) col1 else col2)
DrawCol(4, c("x(t)", xt), if (all(xt == StatsVals[["xt"]])) col1 else col2)
# Dash all values if the first element is NaN
TogglePlot(booStatsRange)
w <- if (!is.na(w[1])) w else rep("-", 3)
s <- if (!is.na(s[1])) s else rep("-", 3)
o <- if (!is.na(o[1])) o else rep("-", 3)
TextBox(paste("Observed Statistics (n = ", n, ")", sep = ""),
100, 200, 100, 17, bg = "grey22", col = "white",
size = fsTableCell_)
DrawCol(1, c("", "i", "avg", "sd"))
DrawCol(2, c("wait", w), if (all(w == StatsVals[["w"]])) col1 else col2)
DrawCol(3, c("service", s), if (all(s == StatsVals[["s"]])) col1 else col2)
DrawCol(4, c("sojourn", o), if (all(o == StatsVals[["o"]])) col1 else col2)
StatsVals[1:6] <<- list(nt, qt, xt, w, s, o)
}
##############################################################################
##############################################################################
## MAIN METHOD
## --------------------------------------------------------------------------
## The equivalent of main() from a C program. This function will be
## invoked at the end of the encompassing ssq() function, causing the
## discrete-event simulation to execute.
##############################################################################
main <- function(seed)
{
# -----------------------------------------------------------------------
# Initialization of main-global variables
# -----------------------------------------------------------------------
# if seed == NULL, use system-generated seed a la base::set.seed;
# if seed == NA, use the most recent state of the generator (e.g., if
# user had done an explicit set.seed call prior to executing ssq) --
# i.e., do nothing new with respect to setting seed;
# otherwise, set the seed with the given (integer) value of 'seed'
# NB: simEd::set.seed version also explicitly calls base::set.seed
if (is.null(seed) || is.numeric(seed)) simEd::set.seed(seed)
numEntries <- 1000 ## initial size of storage vectors
# list of vectors, one entry per customer, optionally returned on exit
jobs <- list(
arrTimes = rep(NA, numEntries), # arrival time of customer i
intArrTimes = rep(NA, numEntries), # interarrival time of customer i
waitTimes = rep(NA, numEntries), # wait time of customer i
serviceTimes = rep(NA, numEntries), # service time of customer i
sojournTimes = rep(NA, numEntries), # service time of customer i
currState = rep("pending", numEntries))
# a running list of the customer ids currently present in the system
idsInSystem <- c()
# for storing system-state changes: times and corresponding num in sys
times <- rep(NA, numEntries) # times of changes to number in system
numsInSys <- rep(NA, numEntries) # corresponding number in system
numsInQue <- rep(NA, numEntries) # corresponding number in system
numsInSvr <- rep(NA, numEntries) # corresponding number in system
timesPos <- 0 # track where to place in times, nums
svrPos <- 0 # track where to place in timesServer,
# numsServer
timesServer <- rep(NA, numEntries) # per-job times: server idle/busy changes
numsServer <- rep(NA, numEntries) # corresponding number (0:idle, 1:busy)
## initialize system variables
currTime <- 0.0 # current time
prevTime <- 0.0 # time before the current time
currSvcTime <- 0.0 # holds current service time
currIntArrTime <- 0.0 # holds current interarrival time
## Running counters that only show current info
numInSystem <- 0 # number in the node
numArrivals <- 0 # used to count jobs arriving to the system
numStarted <- 0 # used to count jobs that have at least begun service
numDepartures <- 0 # used to count processed jobs
numRejects <- 0 # used to count dropout count
idJustRejected <- 0 # used to index newly-rejected customer (for plotting)
idJustServed <- 0 # used to index just-served customer, if one exists
# bgl 02 Jan 2020
idLastEntered <- -1 # used to know when a new job has entered, used in
# defining the pauseData$isJumpStep field passed
# to PausePlot (see compPlot.R) to allow for
# appropriate jumping (next arriving job == one
# step in ssqvis)
##############################################################################
####################################################################
## Setter functions and generic statistic utility functions
## -----------------------------------------------------------------
{
# Returns mean of first i based-on-observation elements of sparse array
GetBOOAvg <- function(d, i = length(d), getSd = FALSE)
{
vals <- d[1:i]; vals <- vals[!is.na(vals)]
if (length(vals) == 0) return(rep(NA, 1 + getSd))
xbar <- mean(vals)
if (getSd) {
s <- sd(vals)
s <- if (is.na(s)) 0 else s
return(c(xbar, s))
}
return(xbar)
}
# mean of time-persistent statistics of first i elements of sparse array
GetTPSAvg <- function(n, t = times, i = length(n), getSd = FALSE)
{
nVals <- n[1:i]; nVals <- nVals[!is.na(nVals)]
tVals <- t[1:i]; tVals <- tVals[!is.na(tVals)]
if (length(nVals) == 0) return(rep(NA, 1 + getSd))
nbar <- if (length(tVals) > 1) meanTPS(tVals, nVals) else mean(nVals)
if (length(nVals) == 1) {
return(if (getSd) c(nbar, 0) else nbar)
}
if (getSd) {
s <- sdTPS(tVals, nVals)
s <- if (is.na(s)) 0 else s
return(c(nbar, s))
}
return(nbar)
}
# Sets the current time and number in system/queue/server
SetSystemState <- function(time, numInSystem)
{
timesPos <<- timesPos + 1
if (timesPos > length(times)) {
times <<- resize(times)
numsInSys <<- resize(numsInSys)
numsInQue <<- resize(numsInQue)
numsInSvr <<- resize(numsInSvr)
}
times [timesPos] <<- time
numsInSys[timesPos] <<- numInSystem
numsInQue[timesPos] <<- max(0, numInSystem - numServers)
numsInSvr[timesPos] <<- min(numServers, numInSystem)
}
# Sets current server state
# (Note: Vadim has tried to design this to allow for future msq use,
# even though this animation is for ssq only...)
SetServerState <- function(time, numInService)
{
svrPos <<- svrPos + 1
if (svrPos > length(timesServer)) {
timesServer <<- resize(timesServer)
numsServer <<- resize(numsServer)
}
timesServer[svrPos] <<- time
numsServer [svrPos] <<- numInService
}
# Sets arrival & interarrival times, updates job state, and ups counts
# 'state' is a string indicating the state:
# - either "not arrived" or "queued" or "rejected"
SetJobStateArr <- function(arrivalTime, interarrivalTime,
state, i = numArrivals + 1)
{
if (i > length(jobs$arrTimes))
jobs$arrTimes <<- resize(jobs$arrTimes)
if (i > length(jobs$intArrTimes))
jobs$intArrTimes <<- resize(jobs$intArrTimes)
if (i > length(jobs$currState))
jobs$currState <- resize(jobs$currState)
# Double jobImage to facilitate job images
if (!is.na(jobImage) && length(idsInSystem) > length(pictype))
pictype <<- c(pictype, pictype)
jobs$arrTimes [i] <<- arrivalTime
jobs$intArrTimes[i] <<- interarrivalTime
jobs$currState [i] <<- state
}
# Sets arrival & interarrival times, updates job state, and ups counts.
# 'state' is a string indicating the state:
# - either "not arrived" or "queued" or "rejected"
UpdateJobStateArr <- function(arrivalTime, interarrivalTime,
state, i = numArrivals + 1)
{
jobs$arrTimes [i] <<- arrivalTime
jobs$intArrTimes[i] <<- interarrivalTime
jobs$currState [i] <<- state
}
# Sets service & completion times, updates job state, and ups counts
# 'state' is a string indicating the state:
# - either "not arrived" or "queued" or "rejected"
SetJobStateSvc <- function(waitTime, serviceTime,
state, i = numStarted + 1)
{
if (i > length(jobs$waitTimes))
jobs$waitTimes <<- resize(jobs$waitTimes)
if (i > length(jobs$serviceTimes))
jobs$serviceTimes <<- resize(jobs$serviceTimes)
if (i > length(jobs$sojournTimes))
jobs$sojournTimes <<- resize(jobs$sojournTimes)
jobs$waitTimes [i] <<- waitTime
jobs$serviceTimes [i] <<- serviceTime
jobs$sojournTimes [i] <<- waitTime + serviceTime
jobs$currState [i] <<- state
}
}
####################################################################
SetSystemState(time = 0, numInSystem = 0)
SetServerState(time = 0, numInService = 0)
# note that numsQueue will potentially have adjacent zero entries, e.g.,
# when the system toggles back and forth b/w 1 and 0 in the system;
# we leave these in intentionally, as the entries in numsQueue line up
# perfectly with the entries in nums (system), appearing on a
# job-started/job-completed basis; while some of this extra may be
# considered unnecessary, we want to allow the user to decide whether
# she would like to see things on a per-job-start/complete basis;
# same for numsServer
# numsQueue <- sapply(numsInSys, function(n) { max(0, n - 1) })
# avgNumInQueue <- meanTPS(times, numsQueue)
##############################################################################
##############################################################################
drawQueueSkylineStats <- NULL # to be declared as a function later
viewJob <- function(job.num, data = jobs)
{
if (is.na(job.num))
message("\t", sym$alert,
" 'job' must be followed by the job # you want to view,",
" (e.g., 'job 5')")
else if (job.num > numArrivals)
message("\t", sym$alert, " Job ", job.num, " has not yet arrived")
else {
message("\tViewing Job ", job.num)
message("\t ", sym$bullet, " Arrival Time = ",
format(round(data$arrTimes[job.num], 3), nsmall = 3))
message("\t ", sym$bullet, " Interarrival Time = ",
format(round(data$intArrTimes[job.num], 3), nsmall = 3))
message("\t ", sym$bullet, " Wait Time = ",
format(round(data$waitTimes[job.num], 3), nsmall = 3))
message("\t ", sym$bullet, " Service Time = ",
format(round(data$serviceTimes[job.num], 3), nsmall = 3))
sojourn_ = data$waitTimes[job.num] + data$serviceTimes[job.num]
message("\t ", sym$bullet, " Sojourn Time = ",
format(round(sojourn_, 3), nsmall = 3))
departure_ = data$arrTimes[job.num] + sojourn_
message("\t ", sym$bullet, " Departure Time = ",
format(round(departure_, 3), nsmall = 3))
}
}
pauseData <<- SetPausePlot(
plotDelay = plotDelay,
prompt =
"Hit 'ENTER' to proceed, 'q' to quit, or 'h' for help/more options: ",
viewCommand = c("job"), # vector of strings for custom view functions
viewNumArgs = c(1),
viewInstruct = c("'job n' = shows attributes of nth job"),
viewFunction = list("1" = function(n_) viewJob(n_))
)
#PauseCurrPlot <<- function(pauseData_, calledFromWhere = NULL)
PauseCurrPlot <<- function(calledFromWhere = NULL)
{
if (exists("DEBUG_") && !is.null(calledFromWhere)) {
message("PauseCurrPlot called from ", calledFromWhere,
"with plotDelay =", pauseData$plotDelay)
}
#endValue <- max(if (is.infinite(maxArrivals)) -1 else maxArrivals,
# if (is.infinite(maxDepartures)) -1 else maxDepartures,
# if (is.infinite(maxTime)) -1 else maxTime)
currValue <- if (endType == endCriteria$ARRIVALS) { numArrivals }
else { numDepartures }
#updatedPauseData <- PausePlot(
# pauseData = pauseData_,
pauseData <<- PausePlot(
pauseData = pauseData,
currStep = currValue,
maxSteps = endValue
)
#return(updatedPauseData)
return(pauseData)
}
CheckMenuChoice <- function(menuChoice, userOptions)
{
if (!is.null(userOptions)) {
# check to see whether user has already quit or ended;
# if so, just return
if (sum(sapply(1:2, function(i) userOptions[[i]])) != 0)
return(userOptions)
}
updatedUserOptions <- list(userQuits = FALSE,
userEnds = FALSE,
userJumps = FALSE)
if (menuChoice == 'q')
{
updatedUserOptions$userQuits <- TRUE
}
else if (pauseData$menuChoice == 'e')
{
updatedUserOptions$userEnds <- TRUE
pauseData$plotDelay <<- 0
# allow transition directly to end
}
else if (pauseData$menuChoice == 'j')
{
updatedUserOptions$userJumps <- TRUE
}
return(updatedUserOptions)
}
##############################################################################
## Printing of initial empty graphs prior to any meaningful computation
##############################################################################
## BEGIN PRIMARY PLOTTING/DRAWING CODE
# Force layout to show only outputted plots if possible
if(is.null(dev.list())) {
dev.new(width = 5, height = 6)
}
par(mfrow = c(1, 1), mar = c(1,1,1,1), new = FALSE)
# a bit about dev.hold() and dev.flush():
# - successive calls to dev.hold() will increase the hold level (default
# increase is 1, but value for level increase can be provided)
# - successive calls to dev.flush() will decrease the hold level (same
# as dev.hold() for default and optional value)
# - only when hold level is 0 will plots be displayed
# - dev.flush(dev.hold()) will _always_ decrease hold level to 0
dev.flush(dev.hold())
dev.hold() # hold level now 1
##############################################################################
ResetPlot() # just starts a new empty plot on the device
if (showTitle) title(titleText, cex.main = 0.975, line = 0, xpd = NA)
# another last-minute hack out of frustration -- title here needs
# to have line=0, but all others after ResetPlot use line = -1;
# likely from changing margins/plotting regions?
# again, exploration for another day...
# draw the default queue in the middle, with values drawn from the
# initial SSQVals list above
DrawCurrQueue(forceDraw = TRUE)
# draw the initial skyline plot
PlotSkyline(times = 0,
numsInSystem = 0, numsInQueue = 0, numsInServer = 0,
rangeForPlotting = 0, entireRange = 0, forceDraw = TRUE)
# draw the initial stats grid (bank), with values drawn from the
# initial StatsVals list above
DrawStatsTableComponents(n = 0, forceDraw = TRUE) # no jobs seen so far
# Used for timing functions
start.t <- rep(NA, numEntries)
end.t <- rep(NA, numEntries)
run.count <- 0
# NB: at this point, the initial queue, skyline, and table are drawn;
# entering SpecifyEventSteps below will draw the generationg block
# at the top of the window, and step through that process
# generate initial arrival time, and save the first interarrival to
# be added below to the list of saved arrivals;
# as appropriate, plot (in topmost region) the steps associated with
# initializing the calendar (i.e., process == 0)
currIntArrTime <- SpecifyEventSteps(currentTime = 0,
#returnVector <- SpecifyEventSteps(currentTime = 0,
arrivalTime = 0,
completionTime = 0,
process = 0, # 0: init calendar
isArrival = TRUE,
advanceTime = TRUE,
numInSystem = 0,
forceDraw = TRUE)
#currIntArrTime <- as.numeric(returnVector[1])
#if (length(returnVector) > 1) pauseData$menuChoice <- returnVector[2]
# # see comments circa line 2370 about bizarro process here
# PauseCurrPlot is called from w/in SpecifyEventSteps > DrawInversionProcess
# so we need to check to see whether the user decided to either quit or
# end or jump, and handle appropriately
# - if quit, don't enter the while loop below at all
# - if end, need to process through the while loop but w/o plotting,
# plotting only at the end
# - if jump, need to process through the while loop, with no plotting
# until we reach pauseData$jumpTo, and plotting from thereon
userOptions <- NULL
userOptions <- CheckMenuChoice(pauseData$menuChoice, userOptions)
arrivalsCal$time <<- currIntArrTime
arrivalsCal$state <<- 1 # indicate that arrivals are permitted
par(mfrow = c(1, 1), mar = c(0, 0, 0, 0), new = TRUE)
DrawQueueComponents(time = 0,
idsInSystem = NULL,
idJustRejected = 0,
idNextToEnter = 1,
idJustServed = 0,
currentProgress = 0,
numRejects = 0,
interarrivalTime = currIntArrTime,
arrivalTime = currIntArrTime,
serviceTime = NA,
completionTime = NA,
forceDraw = TRUE
)
TogglePlot(upArrowPlotRange)
Arrows(100, 0, 100, 150)
#pauseData <<- PauseCurrPlot(pauseData, "After Initial Drawing")
pauseData <<- PauseCurrPlot("After Initial Drawing")
userOptions <- CheckMenuChoice(pauseData$menuChoice, userOptions)
####################################################################
## current statistic retrieval/plotting functions
## -----------------------------------------------------------------
#{
# Function for calculating current progress
getCurrProgress <- function()
return(max(c(numDepartures/maxDepartures, numArrivals/maxArrivals,
currTime/maxTime)))
# Plots current SSQ graphic representation
drawCurrQueue <- function(forceDraw = FALSE)
{
if (pauseData$plotDelay == 0 && !forceDraw) return()
if (pauseData$plotDelay == -2) return() # jumping
# par(mfrow = c(1, 1), mar = c(0, 0, 0, 0), new = TRUE)
iar <- jobs$intArrTimes[numArrivals + 1]
if (is.null(idsInSystem)) svc <- NA
else svc <- jobs$serviceTimes[idsInSystem[1]]
arr <- if (is.na(iar)) iar else arrivalsCal$time
cmp <- if (is.na(svc)) svc else serverCal$time
if (setDrawCurrQueue(time = currTime,
idsInSystem = idsInSystem,
idJustRejected = idJustRejected,
idNextToEnter = numArrivals + 1,
idJustServed = idJustServed,
currentProgress = getCurrProgress(),
numRejects = numRejects,
serviceTime = svc,
interarrivalTime = iar,
arrivalTime = arr,
completionTime = cmp)
|| forceDraw)
{
DrawCurrQueue(forceDraw = forceDraw)
}
# Draws finishing line to connect SSQ plot with mapping plot
TogglePlot(upArrowPlotRange)
Arrows(100, 0, 100, 150)
}
# Plots current variable bank
drawCurrStatsTable <- function(forceDraw = FALSE, endOfSim = FALSE)
{
if (pauseData$plotDelay == 0 && !forceDraw) return()
if (pauseData$plotDelay == -2) return() # jumping
if (numDepartures > 0) {
w <- c(jobs$waitTimes[numDepartures],
GetBOOAvg(jobs$waitTimes[1:numDepartures],
i = numDepartures, getSd = TRUE))
s <- c(jobs$serviceTimes[numDepartures],
GetBOOAvg(jobs$serviceTimes[1:numDepartures],
i = numDepartures, getSd = TRUE))
o <- c(jobs$sojournTimes[numDepartures],
GetBOOAvg(jobs$sojournTimes[1:numDepartures],
i = numDepartures, getSd = TRUE))
} else {
w <- c(NA, NA)
s <- c(NA, NA)
o <- c(NA, NA)
}
# interestingly, if @ end of simulation and ending because of maxTime,
# then timesPos indexes one too far into the nums* vectors...
# but only when completing the entire simulation -- not if quitting
# before full finish...
# no time to chase a more elegant solution, so just handle here...
if (endOfSim)
{
if (!is.infinite(maxTime) && endValue == maxTime &&
timesPos > 1 && currTime >= maxTime)
{
timesPos <- timesPos - 1
}
}
nt = c(numsInSys[timesPos], GetTPSAvg(numsInSys, i = timesPos, getSd = TRUE))
qt = c(numsInQue[timesPos], GetTPSAvg(numsInQue, i = timesPos, getSd = TRUE))
xt = c(numsInSvr[timesPos], GetTPSAvg(numsInSvr, i = timesPos, getSd = TRUE))
DrawStatsTableComponents(n = numDepartures,
nt = nt, qt = qt, xt = xt, w = w, s = s, o = o,
forceDraw = forceDraw
)
}
# Plots current syline function
plotCurrSkyline <- function(forceDraw = FALSE, endOfSim = FALSE)
{
if (pauseData$plotDelay == 0 && !forceDraw) return()
if (pauseData$plotDelay == -2) return() # jumping
rangePlot <- NULL
entireRange <- NULL
if (timesPos == 1) {
# need to handle special case of quitting @ very start,
# where rangePlot would o/w be passed as c(1,1)
rangePlot <- 0
entireRange <- 0
} else if (!endOfSim) {
rangePlot <- c(max(timesPos - maxEventsPerSkyline, 1), timesPos)
entireRange <- c(1, timesPos)
} else {
rangePlot <- c(1, timesPos)
entireRange <- c(1, timesPos)
}
PlotSkyline(times, numsInSys, numsInQue, numsInSvr,
rangePlot, entireRange, forceDraw)
}
# Plots current generation mapping
specifyCurrentEventSteps <- function(process,
isArrival,
advanceTime = TRUE,
forceDraw = FALSE
)
{
drawQueueSkylineStats(forceDraw = forceDraw)
generatedTime <-
#returnVector <-
SpecifyEventSteps(currentTime = currTime,
arrivalTime = if (currIntArrTime == Inf) Inf
else arrivalsCal$time,
completionTime = if (currSvcTime == Inf) Inf
else serverCal$time,
process = process,
# integer 0-7: see SpecifyEventSteps
isArrival = isArrival,
advanceTime = advanceTime,
numInSystem = numInSystem,
forceDraw = forceDraw
)
# for some reason unclear to me, pauseData$menuChoice could be updated
# to 'q' inside the (eventual) call to DrawInversionProcess, which
# would then be seen as still 'q' in the return to SpecifyEventSteps,
# but then would not persist (and seen as 'n') when we get back here...
# (perhaps I'm just being dense...)
# so a hack fix is to have SpecifyEventSteps return a two-element
# vector, and update pauseData$menuChoice here if it was given back...
#if (is.null(returnVector)) return()
# # back from process == 5, 6, or 7 of SpecifyEventSteps
#generatedTime <-
# if (!is.na(returnVector[1])) as.numeric(returnVector[1]) else NULL
#if (length(returnVector) > 1)
# pauseData$menuChoice <<- returnVector[2]
return(generatedTime)
}
drawQueueSkylineStats <- function(forceDraw = FALSE,
endOfSim = FALSE)
{
if (pauseData$plotDelay == 0 && !forceDraw) return()
if (pauseData$plotDelay == -2) return() # jumping
drawCurrQueue(forceDraw = forceDraw)
plotCurrSkyline(forceDraw = forceDraw, endOfSim = endOfSim)
drawCurrStatsTable(forceDraw = forceDraw, endOfSim = endOfSim)
}
#}
####################################################################
## BEGIN MAIN SIMULATION LOOP
# NB: the user could have decided to quit or end or jump during the
# drawing of the initialization process above... see userHasQuit
# logic there.
# NB: with respect to chyron appearing at top of GUI, the first to
# appear as we are heading into the while loop will be
# "Advance System Clock"
while (!userOptions$userQuits
&& currTime < maxTime
&& (numDepartures < maxDepartures)
&& (arrivalsCal$state == 1 || numInSystem > 0))
{
########################################################################
# enter the simulation event loop:
# Continue so long as:
# - the maximum simulated time has not been reached, and
# - the maximum number of departures has not been reached, and
# - there are jobs in the system still to be flushed out
# The net effect of the last piece of the compound condition is to
# allow the user to set maxArrivals and allow them all to be cleared
# (unless the user specifies a maxTime that doesn't allow them to all
# be cleared, or unless the user specifies a maxDepartures that is
# less than maxArrivals)
# run.count <- run.count + 1
# start.t[run.count] <- Sys.time()
idJustServed <- idJustRejected <- 0 # Initializes newly-changed jobs
nextEvent <- getNextEvent() # next event for consideration
prevTime <- currTime # Stores previous clock tine
currTime <- nextEvent$time # Advances the clock
# Handle end of simulation
if (currTime > maxTime || numDepartures >= maxDepartures)
{
if (currTime > maxTime) currTime <- maxTime
# ensure max time vals are added to end of times/num structures
SetSystemState(time = currTime, numInSystem = numsInSys[timesPos])
# Update server states to reflect end of simulation state
SetServerState(time = currTime, numInService = numsServer[svrPos])
break
}
# event type 'a': process an arrival
if (nextEvent$type == 'a')
{
pauseData$isJumpStep <- TRUE
# If queue is infinite OR finite w/ room, process arrival
#if (numInSystem < maxInSystem)
#{
# Add new arrival statistics into jobs list
UpdateJobStateArr(arrivalTime = currTime,
interarrivalTime = currIntArrTime,
state = "queued")
numArrivals <- numArrivals + 1
idLastEntered <- numArrivals
numInSystem <- numInSystem + 1
if (!is.na(pauseData$jumpTo) && numArrivals == pauseData$jumpTo) {
pauseData$plotDelay <- -1 # back to interactive
pauseData$jumpComplete <- TRUE # let compPlot.R do jump cleanup
pauseData <<- pauseData
# not quite sure what program structure requires this <<-
# approach... no time (yet) for further investigation
}
# numArrivals is the latest customer id number
idsInSystem <- append(idsInSystem, numArrivals)
setDrawCurrQueue(shiftInQueue = TRUE)
# Double jobImage to facilitate job images
if (!is.na(jobImage) && length(idsInSystem) > length(pictype))
pictype <<- c(pictype, pictype)
# Add new time interval and system population size to times and nums
SetSystemState(time = currTime, numInSystem = numInSystem)
# If current queue had only one element, get new service time
if (numInSystem == 1)
{
# Plots generation w/ inversion plot
# process 1: arrival with server idle
if (exists("DEBUG_")) message("process = 1")
currSvcTime <- specifyCurrentEventSteps(process = 1,
isArrival = FALSE)
userOptions <- CheckMenuChoice(pauseData$menuChoice, userOptions)
if (userOptions$userQuits) {
if (exists("DEBUG_")) message("quitting: process = 1!")
break
}
serverCal$time <<- currTime + currSvcTime
serverCal$state <<- 1 # INDICATE SERVER IS BUSY
# Add service time/wait time statistics for current element in queue
SetJobStateSvc(waitTime = 0,
serviceTime = currSvcTime,
state = "in service")
numStarted <- numStarted + 1
# Update timesServer and numsServer to reflect current time state
SetServerState(time = currTime, numInService = 1)
}
#}
#else # Otherwise, process rejection
#{
# # Add new rejection statistics into jobs list
# UpdateJobStateArr(arrivalTime = currTime,
# interarrivalTime = currIntArrTime,
# state = "rejected")
# numArrivals <- numArrivals + 1
# idLastEntered <- numArrivals
#
# numRejects <- numRejects + 1
# idJustRejected <- numArrivals
#
# # Special mapping routine for job rejection
# # process 7: rejection of customr
# if (exists("DEBUG_")) message("process = 7")
# specifyCurrentEventSteps(process = 7, isArrival = FALSE)
# userOptions <- CheckMenuChoice(pauseData$menuChoice, userOptions)
# if (userOptions$userQuits) {
# if (exists("DEBUG_")) message("quitting: process = 7!")
# break
# }
#
# currArr <- Inf
# currIntArrTime <- Inf
#
# # Add new time interval and system population size to times and nums
# SetSystemState(time = currTime, numInSystem = numInSystem)
#}
## Add new time interval and system population size to times and nums
#SetSystemState(time = currTime, numInSystem = numInSystem)
# handle end-of-simulation scenarios
if (arrivalsCal$time >= maxTime || numArrivals == maxArrivals)
arrivalsCal$state <<- 0 # NO MORE ARRIVALS PERMITTED
if (numArrivals < maxArrivals)
{
if (numInSystem == 1) {
# process 2: generate the next arrival after customer
# arrived to an empty system & entered service immediately
if (exists("DEBUG_")) message("process = 2")
currIntArrTime <- specifyCurrentEventSteps(process = 2,
isArrival = TRUE, advanceTime = FALSE)
userOptions <- CheckMenuChoice(pauseData$menuChoice, userOptions)
if (userOptions$userQuits) {
if (exists("DEBUG_")) message("quitting: process = 2!")
break
}
} else {
# process 3: generate the next arrival when customer
# arrived to a non-empty system & entered queue
if (exists("DEBUG_")) message("process = 3")
currIntArrTime <- specifyCurrentEventSteps(process = 3,
isArrival = TRUE)
userOptions <- CheckMenuChoice(pauseData$menuChoice, userOptions)
if (userOptions$userQuits) {
if (exists("DEBUG_")) message("quitting: process = 3!")
break
}
}
arrivalsCal$time <<- arrivalsCal$time + currIntArrTime
SetJobStateArr(arrivalTime = arrivalsCal$time,
interarrivalTime = currIntArrTime,
state = "not arrived")
} else {
# process 6: no more arrivals allowed, so put Inf in
# arrival slot in calendar
if (exists("DEBUG_")) message("process = 6")
specifyCurrentEventSteps(process = 6, isArrival = TRUE)
userOptions <- CheckMenuChoice(pauseData$menuChoice, userOptions)
if (userOptions$userQuits) {
if (exists("DEBUG_")) message("quitting: process = 6!")
break
}
currIntArrTime <- Inf
currArr <- Inf
}
}
else # event type 's': process a departure
{
pauseData$isJumpStep <- FALSE
numDepartures <- numDepartures + 1
numInSystem <- numInSystem - 1
jobs$currState[idsInSystem[1]] <- "served"
idJustServed <- idsInSystem[1]
idsInSystem <- idsInSystem[-1]
# Update time intervals and nums in system to current state
SetSystemState(time = currTime, numInSystem = numInSystem)
# customers waiting, so begin serving the next
# immediately put first in queue into service
if (numInSystem > 0)
{
# Generate the current service time for queue
# process 4: customer departs with other customers
# waiting in the queue
if (exists("DEBUG_")) message("process = 4")
currSvcTime <- specifyCurrentEventSteps(process = 4, isArrival = FALSE)
userOptions <- CheckMenuChoice(pauseData$menuChoice, userOptions)
if (userOptions$userQuits) {
if (exists("DEBUG_")) message("quitting: process = 4!")
break
}
serverCal$time <<- currTime + currSvcTime # Update server calendar
# Update wait times and service times for next element to be serviced
SetJobStateSvc(waitTime = currTime - jobs$arrTimes[idsInSystem[1]],
serviceTime = currSvcTime,
state ="in service")
numStarted <- numStarted + 1
# Update server values to reflect departure
SetServerState(time = currTime, numInService = 1)
}
else
{
# nothing waiting in queue -- server goes idle
serverCal$state <<- 0 # INDICATE SERVER IS NOW IDLE
SetServerState(time = currTime, numInService = 0)
if (pauseData$plotDelay != 0) {
# process 5: customer departed and queue is empty
if (exists("DEBUG_")) message("process = 5")
specifyCurrentEventSteps(process = 5, isArrival = FALSE)
userOptions <- CheckMenuChoice(pauseData$menuChoice, userOptions)
if (userOptions$userQuits) {
if (exists("DEBUG_")) message("quitting: process = 5!")
break
}
}
currSvcTime <- Inf
currCmp <- Inf
}
}
########################################################################
########################################################################
## Segment to animate all output at end of cycle
########################################################################
# Handles outdated calendar values if not overridden
currArr <- if (currTime >= arrivalsCal$time) Inf else arrivalsCal$time
currCmp <- if (currTime >= serverCal$time) Inf else serverCal$time
setDrawCurrTimeline(
time = currTime,
arrivalTime = currArr,
completionTime = currCmp)
setDrawCurrCalendar(
arrivalTime = currArr,
completionTime = currCmp)
DrawCurrTimeline()
drawQueueSkylineStats()
#pauseData <- PauseCurrPlot(pauseData, "end of while loop")
#pauseData <<- pauseData
# using <- and <<- is a hack that, on experimentation works
# (whereas <<- on the first assignment alone doesn't), as some fcns
# need ssqvis-global update... clearly a scoping issue, but I don't
# have time to investigate and properly reorganize... *sigh*
pauseData <<- PauseCurrPlot("end of while loop")
userOptions <- CheckMenuChoice(pauseData$menuChoice, userOptions)
if (userOptions$userQuits) {
message("quitting: end of while loop!")
break
}
if (pauseData$plotDelay != 0 && pauseData$plotDelay != -2) {
ResetPlot()
if (showTitle) title(titleText, cex.main = 0.975, line = -1)
}
########################################################################
} # while (...)
# need to reset plotDelay to handle case of user having chosen 'e'
# or having jumped beyond the end of the simulation
if (pauseData$plotDelay == 0 && pauseData$menuChoice == 'e') {
# call PauseCurrPlot once more just to close out the progess bar
pauseData <<- PauseCurrPlot("after while loop")
pauseData$plotDelay <- -1
}
if (pauseData$plotDelay == -2) {
pauseData$plotDelay <- -1
pauseData$jumpComplete <- TRUE # let compPlot.R do jump cleanup
pauseData$endOfSim <- TRUE
pauseData <<- PauseCurrPlot("after while loop")
}
ResetPlot()
if (showTitle) title(titleText, cex.main = 0.975, line = -1)
drawQueueSkylineStats(forceDraw = TRUE, endOfSim = TRUE)
DrawEmptyIDF()
SetChyron("Execution Finished")
DrawCurrCalendar(forceDraw = TRUE)
setDrawCurrTimeline(time = currTime,
arrivalTime = NA,
completionTime = NA)
DrawCurrTimeline(forceDraw = TRUE)
dev.flush(dev.hold())
# this will automatically flush all, regardless of hold level
##############################################################################
## Conduct final saves, formats, and returns
##############################################################################
# "growing" per-customer vectors may leave NA values at ends -- remove them
jobs$arrTimes <- jobs$arrTimes [!is.na(jobs$arrTimes)]
jobs$intArrTimes <- jobs$intArrTimes [!is.na(jobs$intArrTimes)]
jobs$waitTimes <- jobs$waitTimes [!is.na(jobs$waitTimes)]
jobs$serviceTimes <- jobs$serviceTimes [!is.na(jobs$serviceTimes)]
if (numDepartures > 0)
{
jobs$arrTimes <- jobs$arrTimes [1:numDepartures]
jobs$intArrTimes <- jobs$intArrTimes [1:numDepartures]
jobs$waitTimes <- jobs$waitTimes [1:numDepartures]
jobs$serviceTimes <- jobs$serviceTimes[1:numDepartures]
}
times <- times [!is.na(times)]
numsInSys <- numsInSys [!is.na(numsInSys)]
numsInQue <- numsInQue [!is.na(numsInQue)]
numsInSvr <- numsInSvr [!is.na(numsInSvr)]
#numsInQue <- sapply(numsInSys, function(num) max(0, num - 1))
#numsInSvr <- ifelse(numsInSys > 0, 1, 0)
timesServer <- timesServer [!is.na(timesServer)]
numsServer <- numsServer [!is.na(numsServer)]
if (pauseData$menuChoice == 'q' && currTime > 0)
{
# user quit midstream, so we need to add the last block of
# busy/idle time for the server
timesServer <- c(timesServer, currTime)
numsServer <- c(numsServer, numsServer[length(numsServer)])
}
# if there is a job still in service at the end of the simulation, it will
# have received a wait time and a service time, but we should not include
# its (automatically computed, via the sum below) sojourn time, as the job
# has not actually completed service
if (numDepartures > 0) {
jobs$sojournTimes <-
(jobs$waitTimes + jobs$serviceTimes)[1:numDepartures]
} else {
jobs$sojournTimes <- NA
}
avgWait <- 0
avgSojourn <- 0
avgNumInSys <- 0
util <- 0
avgNumInQue <- 0
# the length and !is.na checks are for when the user bails out early
if (length(jobs$waitTimes[!is.na(jobs$waitTimes)]) > 0)
avgWait <- mean(jobs$waitTimes[!is.na(jobs$waitTimes)])
if (length(jobs$sojournTimes[!is.na(jobs$sojournTimes)]) > 0)
avgSojourn<- mean(jobs$sojournTimes[!is.na(jobs$sojournTimes)])
if (length(numsInSys) > 1) avgNumInSys <- meanTPS(times, numsInSys)
if (length(numsServer) > 1) util <- meanTPS(timesServer, numsServer)
if (length(numsInQue) > 1) avgNumInQue <- meanTPS(times, numsInQue)
# note that numsQueue will potentially have adjacent zero entries, e.g.,
# when the system toggles back and forth b/w 1 and 0 in the system;
# we leave these in intentionally, as the entries in numsQueue line up
# perfectly with the entries in nums (system), appearing on a
# job-started/job-completed basis; while some of this extra may be
# considered unnecessary, we want to allow the user to decide whether
# she would like to see things on a per-job-start/complete basis;
# same for numsServer
# make sure the default output is printed
{
printed <- ""
printed <- paste(printed, "$customerArrivals\n[1]", sep = "")
printed <- paste(printed, numArrivals)
printed <- paste(printed, "\n\n$customerDepartures\n[1]", sep = "")
printed <- paste(printed, numDepartures)
printed <- paste(printed, "\n\n$simulationEndTime\n[1]", sep = "")
printed <- paste(printed, round(min(currTime, maxTime), digits = 5))
printed <- paste(printed, "\n\n$avgWait\n[1]", sep = "")
printed <- paste(printed, signif(avgWait, digits = 5))
printed <- paste(printed, "\n\n$avgSojourn\n[1]", sep = "")
printed <- paste(printed, signif(avgSojourn, digits = 5))
printed <- paste(printed, "\n\n$avgNumInSystem\n[1]", sep = "")
printed <- paste(printed, signif(avgNumInSys, digits = 5))
printed <- paste(printed, "\n\n$avgNumInQueue\n[1]", sep = "")
printed <- paste(printed, signif(avgNumInQue, digits = 5))
printed <- paste(printed, "\n\n$utilization\n[1]", sep = "")
printed <- paste(printed, signif(util, digits = 5))
printed <- paste(printed, "\n\n", sep = "")
if (showOutput) on.exit(message(printed))
}
# create a list of the output, to be returned to the user
ssq <- list(customerArrivals = numArrivals,
customerDepartures = numDepartures,
simulationEndTime = min(currTime, maxTime),
avgWait = avgWait,
avgSojourn = avgSojourn,
avgNumInSystem = avgNumInSys,
avgNumInQueue = avgNumInQue,
utilization = util)
# note that computing interarrivals as diff(c(0, jobs$arrTimes)) gives
# _slightly_ different times than storing interarrivals when generated;
# similar for having computed sojurns as waits + services above
{
if (length(times) <= 1) {
jobs$intArrTimes <- NULL
jobs$serviceTimes <- NULL
jobs$waitTimes <- NULL
jobs$sojournTimes <- NULL
}
if (saveInterarrivalTimes) ssq$interarrivalTimes <- jobs$intArrTimes
if (saveServiceTimes) ssq$serviceTimes <- jobs$serviceTimes
if (saveWaitTimes) ssq$waitTimes <- jobs$waitTimes
if (saveSojournTimes) ssq$sojournTimes <- jobs$sojournTimes
if (saveNumInSystem) {
ssq$numInSystemT <- times
ssq$numInSystemN <- numsInSys
}
if (saveNumInQueue) {
ssq$numInQueueT <- times
ssq$numInQueueN <- numsInQue
}
if (saveServerStatus) {
ssq$serverStatusT <- timesServer
ssq$serverStatusN <- numsServer
}
}
##############################################################################
# resetting par and warning settings
#options(warn = warnVal$warn) # remove RE CRAN req't (del 22 Nov 2023)
## using on.exit() for par per CRAN suggestion (del 22 Nov 2023)
#par(mfrow = c(1,1)) # may be a better general solution, but saving par at
# # the front end messes up Vadim's font scaling...
return(invisible(ssq)) # invisible ensures big list of times aren't printed!
} # main
##############################################################################
# *********************************************************
# * CALL THE MAIN ssq FUNCTION, executing the simulation. *
# * This passes a list back to the R user. *
# *********************************************************
return(main(seed))
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.