Here I will demonstrate how to produce Contol Charts using the qicharts package. I’ll start by modifying and exploring the data using the ggplot2 package then I’ll get into how to quickly produce control charts.
# Tools

For this demo I will use a series of data manipulation and visualiztion tools in addition to a useful helper function

Packages

  • readxl
  • ggplot2
  • tidyverse
  • lubridate
  • dplyr
  • qicharts
library(readxl) # Contains read_excel function for importing data
library(ggplot2) # General package for data visualization
library(tidyverse) # Group of packages for data manipulation
library(lubridate) # Date manipulation
library(qicharts) # Control Charts package
library(dplyr) # Data Manipulation

Helper Function

We use the multiplot function, courtesy of R Cookbooks to create multi-panel plots.

# Multiple plot function
#
# ggplot objects can be passed in ..., or to plotlist (as a list of ggplot objects)
# - cols:   Number of columns in layout
# - layout: A matrix specifying the layout. If present, 'cols' is ignored.
#
# If the layout is something like matrix(c(1,2,3,3), nrow=2, byrow=TRUE),
# then plot 1 will go in the upper left, 2 will go in the upper right, and
# 3 will go all the way across the bottom.
#
multiplot <- function(..., plotlist=NULL, file, cols=1, layout=NULL) {
  library(grid)
  # Make a list from the ... arguments and plotlist
  plots <- c(list(...), plotlist)
  numPlots = length(plots)
  # If layout is NULL, then use 'cols' to determine layout
  if (is.null(layout)) {
    # Make the panel
    # ncol: Number of columns of plots
    # nrow: Number of rows needed, calculated from # of cols
    layout <- matrix(seq(1, cols * ceiling(numPlots/cols)),
                    ncol = cols, nrow = ceiling(numPlots/cols))
  }
 if (numPlots==1) {
    print(plots[[1]])
  } else {
    # Set up the page
    grid.newpage()
    pushViewport(viewport(layout = grid.layout(nrow(layout), ncol(layout))))
    # Make each plot, in the correct location
    for (i in 1:numPlots) {
      # Get the i,j matrix positions of the regions that contain this subplot
      matchidx <- as.data.frame(which(layout == i, arr.ind = TRUE))
      print(plots[[i]], vp = viewport(layout.pos.row = matchidx$row,
                                      layout.pos.col = matchidx$col))
    }
  }
}

Data Exploration

Loading Data

Here we load the golden_gate.xlsx file found here.

We can get a feel for the contents of the file using the glimpse, first, and last functions

golden_gate <- read_excel(paste(path, "Golden Gate Bridge traffic TS//golden_gate.xlsx", sep = ''))
glimpse(golden_gate)
Observations: 168
Variables: 3
$ Row     <dbl> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 5...
$ Month   <dttm> 1968-01-01, 1968-02-01, 1968-03-01, 1968-04-01, 1968-05-01, 1968-06-01, 1968-07-01, 1968-08-01, 1968-09-01, 1968-10-01, 1968-11-01, 1968-12-01, 1969-01-01, 1969-02-01, 1969-03-01, 1969-04-01, 1969-05-01, 1969-06-01,...
$ Traffic <dbl> 73637, 77136, 81481, 84127, 84562, 91959, 94174, 96087, 88952, 83479, 80814, 77466, 75225, 79418, 84813, 85691, 8749, 92995, 95375, 98396, 92791, 88018, 86899, 83636, 79245, 85536, 89313, 88785, 91307, 96394, 99864, ...
first(golden_gate$Month)
[1] "1968-01-01 UTC"
last(golden_gate$Month)
[1] "1981-12-01 UTC"

We see that the dataset contains three columns.
- The first is an arbitrary row number column.
- The second is the month for which the data was taken. The data appears to span from January 1968 (1968-01-01) to December 1981 (1981-12-01).
- The third column is then the number of cars that travelled across the Golden Gate bridge in the specified month.

Quick Traffic Exploration

Summary of the Traffic Feature

summary(golden_gate$Traffic)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1017   87909   93769   88012   98906  111475 
sd(golden_gate$Traffic)
[1] 23306.58

Here we see that an average day saw 88,012 cars cross the Golden Gate bridge. The slowest day saw only 1,017 cars while the peak observation saw a total of 111,475 cars. With a standard deviation of 23306.58, we should expect to find a few outliers to towards the bottom.

p1 <- ggplot(golden_gate, aes(x = Traffic)) +
  geom_density(fill = 'lightblue', alpha = .6) +
  geom_vline(xintercept = mean(golden_gate$Traffic), col = 'red', lty = 2) +
  theme_minimal() +
  labs(y = 'Density') +
  theme(axis.text.y = element_blank(),
        axis.title.x = element_blank(),
        axis.text.x  = element_blank())
p2 <- ggplot(golden_gate, aes(x = 1, y = Traffic)) +
  geom_boxplot()+
  coord_flip() +
  xlab( 'Boxplot') +
  theme_minimal() +
  theme(axis.text.y = element_blank())
layout <- matrix(c(1,1,1,2),4,1, byrow = T)
multiplot(p1, p2, layout = layout)

Viewing the density plot shows a very stongly bi-modal dataset. With a total of 1237 observations in the lower mode, there should be sufficient data to properly use a control chart.

I’ll quickly visualize traffic data in comparison to dates in order to get a better feel of how traffic changes over time.

p1 <- ggplot(golden_gate, aes(x = Month, y = Traffic))+
  geom_line(col = 'midnightblue') +
  theme_minimal() +
  theme(axis.title.x = element_blank()) +
  geom_smooth()
p2 <- golden_gate %>%
  ggplot(aes(Traffic)) +
  geom_histogram(fill = 'blue', bins = 30) +
  theme(axis.text.x= element_text(angle = 90))
p3 <- golden_gate %>%
  mutate(wday = wday(Month, label = T)) %>%
  group_by(wday) %>%
  summarize(DTraffic = median(Traffic)) %>%
  ggplot(aes(wday, DTraffic, fill = wday)) +
  geom_col() +
  labs( y = 'Mean Traffic', x = 'Weekday') +
  theme_minimal() +
  theme(legend.position = 'none', axis.text.x = element_text(angle= 90, hjust = 1, vjust = 0.9)) 
p4 <- golden_gate %>%
  mutate(month = month(Month , label = T)) %>%
  group_by(month) %>%
  summarize(MTraffic = median(Traffic), medianTraffic = median(Traffic)) %>%
  ggplot(aes(month, MTraffic, fill = month)) +
  geom_col() +
  labs(x = 'Month', y = '')+
  theme_minimal() +
  theme(legend.position = 'none', axis.text.x = element_text(angle = 90, hjust = 1, vjust = .5))
layout <- matrix(c(1,1,1,1,2,3,4,4), nrow = 2 ,byrow = TRUE)
multiplot(p1, p2, p3, p4, layout = layout)

From the line graph, it appears there is a slight seasonality in addition to some datapoints that may have some error in collection. Dealing with those errors is outside the scope of this write-up. Traffic seems fairly consistent across weekdays with Wednesday and Thursday seeming to be the slowest days. The seasonality posit seems to be true looking at the median traffic by month. Late summer sees the most traffic with a dip during the winter months.

To make our qicharts demo a little easier to read, lets create a new object ‘gg_no_out’ in which we remove the extreme values.

gg_no_out <- golden_gate %>%
  filter(golden_gate$Traffic > 30000)
dim(gg_no_out)
[1] 156   3

This leaves us with 156 of our original 168 observations. Lets quickly reproduce the above charts using the new set of data

p1 <- ggplot(gg_no_out, aes(x = Month, y = Traffic))+
  geom_line(col = 'midnightblue') +
  theme_minimal() +
  theme(axis.title.x = element_blank()) +
  geom_smooth()
p2 <- gg_no_out %>%
  ggplot(aes(Traffic)) +
  geom_histogram(fill = 'blue', bins = 30) +
  theme(axis.text.x= element_text(angle = 90))
p3 <- gg_no_out %>%
  mutate(wday = wday(Month, label = T)) %>%
  group_by(wday) %>%
  summarize(DTraffic = median(Traffic)) %>%
  ggplot(aes(wday, DTraffic, fill = wday)) +
  geom_col() +
  labs( y = 'Mean Traffic', x = 'Weekday') +
  theme_minimal() +
  theme(legend.position = 'none', axis.text.x = element_text(angle= 90, hjust = 1, vjust = 0.9))
p4 <- gg_no_out %>%
  mutate(month = month(Month , label = T)) %>%
  group_by(month) %>%
  summarize(MTraffic = median(Traffic), medianTraffic = median(Traffic)) %>%
  ggplot(aes(month, MTraffic, fill = month)) +
  geom_col() +
  labs(x = 'Month', y = '')+
  theme_minimal() +
  theme(legend.position = 'none', axis.text.x = element_text(angle = 90, hjust = 1, vjust = .5))
layout <- matrix(c(1,1,1,1,2,3,4,4),nrow = 2, byrow = T)
multiplot(p1, p2, p3, p4, layout = layout)

The seasonality appears to be even stronger and the smoothing curve shows a much stronger positive trend.

All this being said, it seems we have a sufficient dataset for creating a Control Chart.

qicharts

qic Introduction

The qic() function within qicharts2 makes generating control charts very easy. The default control chart is a simple run chart and can be generated by simply passing the Traffic column to the qic function

qic(y = gg_no_out$Traffic )

This can be used to see the general trend but doesn’t contain the control limits. To put a little more information on the chart, simply specify “chart = ‘i’” in the qic function. This will place upper/lower limit lines as well as highlight points that fall outside of the 3-sigma confidence interval.

i Charts

qic(y = gg_no_out$Traffic, x = gg_no_out$Month,chart = 'i')

As you can see, this generates a beautiful control chart! I have additionally specified the x axis to contain date information.

One limitation to the qic function is that it does not allow the user to specify how many observations to use. For instance you cannot natively just select the most recent 30 instances of the observation. Lucky for us, R is very powerful and this effect can be achieved in just one line.

gg_no_out.recent <- tail(gg_no_out, n = 30)
q <-qic(y = gg_no_out.recent$Traffic, x = gg_no_out.recent$Month, chart = 'i')

Accessing Signal Points

Lastly we may want to investigate the points that fall outside of the control limits. We can do that by subsetting using the $signals element of the qic object.

signal <- q$signals
gg_no_out.recent[signal , ]

Implementation

Using the qic function would easily enough be generalized to be applied to truck MPG or some other metric through rstat if we can figure out how to get it installed properly. This is far from the end of qicharts2’s usefulness. Additional information can be found https://cran.r-project.org/web/packages/qicharts/vignettes/controlcharts.html

LS0tDQp0aXRsZTogIkNvbnRyb2wgQ2hhcnRzIHdpdGggcWljaGFydHMiDQphdXRob3I6ICJPcnJpbiBXaGVlbGVyIg0KZGF0ZTogIjEvMjUvMTgiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICB0aGVtZTogZmxhdGx5DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQpIZXJlIEkgd2lsbCBkZW1vbnN0cmF0ZSBob3cgdG8gcHJvZHVjZSBDb250b2wgQ2hhcnRzIHVzaW5nIHRoZSBxaWNoYXJ0cyBwYWNrYWdlLiAgSSdsbCBzdGFydCBieSBtb2RpZnlpbmcgYW5kIGV4cGxvcmluZyB0aGUgZGF0YSB1c2luZyB0aGUgZ2dwbG90MiBwYWNrYWdlIHRoZW4gSSdsbCBnZXQgaW50byBob3cgdG8gcXVpY2tseSBwcm9kdWNlIGNvbnRyb2wgY2hhcnRzLiAgDQojIFRvb2xzDQoNCkZvciB0aGlzIGRlbW8gSSB3aWxsIHVzZSBhIHNlcmllcyBvZiBkYXRhIG1hbmlwdWxhdGlvbiBhbmQgdmlzdWFsaXp0aW9uIHRvb2xzIGluIGFkZGl0aW9uIHRvIGEgdXNlZnVsIGhlbHBlciBmdW5jdGlvbg0KDQojIyMgUGFja2FnZXMNCg0KLSByZWFkeGwNCi0gZ2dwbG90Mg0KLSB0aWR5dmVyc2UNCi0gbHVicmlkYXRlDQotIGRwbHlyDQotIHFpY2hhcnRzDQpgYGB7ciBsb2FkUGFja2FnZSAsd2FybmluZz1GLCBtZXNzYWdlPUZ9DQpsaWJyYXJ5KHJlYWR4bCkgIyBDb250YWlucyByZWFkX2V4Y2VsIGZ1bmN0aW9uIGZvciBpbXBvcnRpbmcgZGF0YQ0KbGlicmFyeShnZ3Bsb3QyKSAjIEdlbmVyYWwgcGFja2FnZSBmb3IgZGF0YSB2aXN1YWxpemF0aW9uDQpsaWJyYXJ5KHRpZHl2ZXJzZSkgIyBHcm91cCBvZiBwYWNrYWdlcyBmb3IgZGF0YSBtYW5pcHVsYXRpb24NCmxpYnJhcnkobHVicmlkYXRlKSAjIERhdGUgbWFuaXB1bGF0aW9uDQpsaWJyYXJ5KHFpY2hhcnRzKSAjIENvbnRyb2wgQ2hhcnRzIHBhY2thZ2UNCmxpYnJhcnkoZHBseXIpICMgRGF0YSBNYW5pcHVsYXRpb24NCmBgYA0KDQojIyMgSGVscGVyIEZ1bmN0aW9uDQpXZSB1c2UgdGhlICptdWx0aXBsb3QqIGZ1bmN0aW9uLCBjb3VydGVzeSBvZiBbUiBDb29rYm9va3NdKGh0dHA6Ly93d3cuY29va2Jvb2stci5jb20vR3JhcGhzL011bHRpcGxlX2dyYXBoc19vbl9vbmVfcGFnZV8oZ2dwbG90MikvKSB0byBjcmVhdGUgbXVsdGktcGFuZWwgcGxvdHMuIA0KDQpgYGB7ciBoZWxwZXJGdW5jdGlvbn0NCiMgTXVsdGlwbGUgcGxvdCBmdW5jdGlvbg0KIw0KIyBnZ3Bsb3Qgb2JqZWN0cyBjYW4gYmUgcGFzc2VkIGluIC4uLiwgb3IgdG8gcGxvdGxpc3QgKGFzIGEgbGlzdCBvZiBnZ3Bsb3Qgb2JqZWN0cykNCiMgLSBjb2xzOiAgIE51bWJlciBvZiBjb2x1bW5zIGluIGxheW91dA0KIyAtIGxheW91dDogQSBtYXRyaXggc3BlY2lmeWluZyB0aGUgbGF5b3V0LiBJZiBwcmVzZW50LCAnY29scycgaXMgaWdub3JlZC4NCiMNCiMgSWYgdGhlIGxheW91dCBpcyBzb21ldGhpbmcgbGlrZSBtYXRyaXgoYygxLDIsMywzKSwgbnJvdz0yLCBieXJvdz1UUlVFKSwNCiMgdGhlbiBwbG90IDEgd2lsbCBnbyBpbiB0aGUgdXBwZXIgbGVmdCwgMiB3aWxsIGdvIGluIHRoZSB1cHBlciByaWdodCwgYW5kDQojIDMgd2lsbCBnbyBhbGwgdGhlIHdheSBhY3Jvc3MgdGhlIGJvdHRvbS4NCiMNCm11bHRpcGxvdCA8LSBmdW5jdGlvbiguLi4sIHBsb3RsaXN0PU5VTEwsIGZpbGUsIGNvbHM9MSwgbGF5b3V0PU5VTEwpIHsNCiAgbGlicmFyeShncmlkKQ0KDQogICMgTWFrZSBhIGxpc3QgZnJvbSB0aGUgLi4uIGFyZ3VtZW50cyBhbmQgcGxvdGxpc3QNCiAgcGxvdHMgPC0gYyhsaXN0KC4uLiksIHBsb3RsaXN0KQ0KDQogIG51bVBsb3RzID0gbGVuZ3RoKHBsb3RzKQ0KDQogICMgSWYgbGF5b3V0IGlzIE5VTEwsIHRoZW4gdXNlICdjb2xzJyB0byBkZXRlcm1pbmUgbGF5b3V0DQogIGlmIChpcy5udWxsKGxheW91dCkpIHsNCiAgICAjIE1ha2UgdGhlIHBhbmVsDQogICAgIyBuY29sOiBOdW1iZXIgb2YgY29sdW1ucyBvZiBwbG90cw0KICAgICMgbnJvdzogTnVtYmVyIG9mIHJvd3MgbmVlZGVkLCBjYWxjdWxhdGVkIGZyb20gIyBvZiBjb2xzDQogICAgbGF5b3V0IDwtIG1hdHJpeChzZXEoMSwgY29scyAqIGNlaWxpbmcobnVtUGxvdHMvY29scykpLA0KICAgICAgICAgICAgICAgICAgICBuY29sID0gY29scywgbnJvdyA9IGNlaWxpbmcobnVtUGxvdHMvY29scykpDQogIH0NCg0KIGlmIChudW1QbG90cz09MSkgew0KICAgIHByaW50KHBsb3RzW1sxXV0pDQoNCiAgfSBlbHNlIHsNCiAgICAjIFNldCB1cCB0aGUgcGFnZQ0KICAgIGdyaWQubmV3cGFnZSgpDQogICAgcHVzaFZpZXdwb3J0KHZpZXdwb3J0KGxheW91dCA9IGdyaWQubGF5b3V0KG5yb3cobGF5b3V0KSwgbmNvbChsYXlvdXQpKSkpDQoNCiAgICAjIE1ha2UgZWFjaCBwbG90LCBpbiB0aGUgY29ycmVjdCBsb2NhdGlvbg0KICAgIGZvciAoaSBpbiAxOm51bVBsb3RzKSB7DQogICAgICAjIEdldCB0aGUgaSxqIG1hdHJpeCBwb3NpdGlvbnMgb2YgdGhlIHJlZ2lvbnMgdGhhdCBjb250YWluIHRoaXMgc3VicGxvdA0KICAgICAgbWF0Y2hpZHggPC0gYXMuZGF0YS5mcmFtZSh3aGljaChsYXlvdXQgPT0gaSwgYXJyLmluZCA9IFRSVUUpKQ0KDQogICAgICBwcmludChwbG90c1tbaV1dLCB2cCA9IHZpZXdwb3J0KGxheW91dC5wb3Mucm93ID0gbWF0Y2hpZHgkcm93LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXlvdXQucG9zLmNvbCA9IG1hdGNoaWR4JGNvbCkpDQogICAgfQ0KICB9DQp9DQpgYGANCg0KDQoNCiMgRGF0YSBFeHBsb3JhdGlvbg0KDQojIyMgTG9hZGluZyBEYXRhDQpIZXJlIHdlIGxvYWQgdGhlIGdvbGRlbl9nYXRlLnhsc3ggZmlsZSBmb3VuZCBbaGVyZV0oaHR0cDovL3d3dy5zdGF0cG9pbnQubmV0L2RlZmF1bHQuYXNweCkuDQoNCldlIGNhbiBnZXQgYSBmZWVsIGZvciB0aGUgY29udGVudHMgb2YgdGhlIGZpbGUgdXNpbmcgdGhlIGdsaW1wc2UsIGZpcnN0LCBhbmQgbGFzdCBmdW5jdGlvbnMNCmBgYHtyIHNldFBhdGggLGVjaG8gPSBGfQ0KcGF0aCA8LSAnQzovL1VzZXJzLy9lMDg1NzI0Ly9EZXNrdG9wLy9SLy8nDQpgYGANCg0KYGBge3IgbG9hZERhdGF9DQpnb2xkZW5fZ2F0ZSA8LSByZWFkX2V4Y2VsKHBhc3RlKHBhdGgsICJHb2xkZW4gR2F0ZSBCcmlkZ2UgdHJhZmZpYyBUUy8vZ29sZGVuX2dhdGUueGxzeCIsIHNlcCA9ICcnKSkNCmdsaW1wc2UoZ29sZGVuX2dhdGUpDQpmaXJzdChnb2xkZW5fZ2F0ZSRNb250aCkNCmxhc3QoZ29sZGVuX2dhdGUkTW9udGgpDQpgYGANCmBgYHtyIGNvbnZlcnRUb0x1YnJpZGF0ZURhdGUgLGVjaG8gPSBGfQ0KZ29sZGVuX2dhdGUkTW9udGggPC0geW1kKGdvbGRlbl9nYXRlJE1vbnRoKQ0KYGBgDQoNCg0KV2Ugc2VlIHRoYXQgdGhlIGRhdGFzZXQgY29udGFpbnMgdGhyZWUgY29sdW1ucy4gIA0KLSBUaGUgZmlyc3QgaXMgYW4gYXJiaXRyYXJ5IHJvdyBudW1iZXIgY29sdW1uLiAgDQotIFRoZSBzZWNvbmQgaXMgdGhlIG1vbnRoIGZvciB3aGljaCB0aGUgZGF0YSB3YXMgdGFrZW4uICBUaGUgZGF0YSBhcHBlYXJzIHRvIHNwYW4gZnJvbSBKYW51YXJ5IDE5NjggKCoqYHIgZmlyc3QoZ29sZGVuX2dhdGUkTW9udGgpYCoqKSB0byBEZWNlbWJlciAxOTgxICgqKmByIGxhc3QoZ29sZGVuX2dhdGUkTW9udGgpYCoqKS4gIA0KLSBUaGUgdGhpcmQgY29sdW1uIGlzIHRoZW4gdGhlIG51bWJlciBvZiBjYXJzIHRoYXQgdHJhdmVsbGVkIGFjcm9zcyB0aGUgR29sZGVuIEdhdGUgYnJpZGdlIGluIHRoZSBzcGVjaWZpZWQgbW9udGguICANCg0KDQojIyMgUXVpY2sgVHJhZmZpYyBFeHBsb3JhdGlvbg0KDQpTdW1tYXJ5IG9mIHRoZSBUcmFmZmljIEZlYXR1cmUNCmBgYHtyIHRyYWZmaWNTdW1tYXJ5fQ0Kc3VtbWFyeShnb2xkZW5fZ2F0ZSRUcmFmZmljKQ0Kc2QoZ29sZGVuX2dhdGUkVHJhZmZpYykNCmBgYA0KDQoNCg0KSGVyZSB3ZSBzZWUgdGhhdCBhbiBhdmVyYWdlIGRheSBzYXcgODgsMDEyIGNhcnMgY3Jvc3MgdGhlIEdvbGRlbiBHYXRlIGJyaWRnZS4gIFRoZSBzbG93ZXN0IGRheSBzYXcgb25seSAxLDAxNyBjYXJzIHdoaWxlIHRoZSBwZWFrIG9ic2VydmF0aW9uIHNhdyBhIHRvdGFsIG9mIDExMSw0NzUgY2Fycy4gIFdpdGggYSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgMjMzMDYuNTgsIHdlIHNob3VsZCBleHBlY3QgdG8gZmluZCBhIGZldyBvdXRsaWVycyB0byB0b3dhcmRzIHRoZSBib3R0b20uICANCmBgYHtyIHRyYWZmaWNEZW5zaXR5Qm94cGxvdH0NCnAxIDwtIGdncGxvdChnb2xkZW5fZ2F0ZSwgYWVzKHggPSBUcmFmZmljKSkgKw0KICBnZW9tX2RlbnNpdHkoZmlsbCA9ICdsaWdodGJsdWUnLCBhbHBoYSA9IC42KSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IG1lYW4oZ29sZGVuX2dhdGUkVHJhZmZpYyksIGNvbCA9ICdyZWQnLCBsdHkgPSAyKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIGxhYnMoeSA9ICdEZW5zaXR5JykgKw0KICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgICBheGlzLnRleHQueCAgPSBlbGVtZW50X2JsYW5rKCkpDQoNCnAyIDwtIGdncGxvdChnb2xkZW5fZ2F0ZSwgYWVzKHggPSAxLCB5ID0gVHJhZmZpYykpICsNCiAgZ2VvbV9ib3hwbG90KCkrDQogIGNvb3JkX2ZsaXAoKSArDQogIHhsYWIoICdCb3hwbG90JykgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSkNCg0KbGF5b3V0IDwtIG1hdHJpeChjKDEsMSwxLDIpLDQsMSwgYnlyb3cgPSBUKQ0KbXVsdGlwbG90KHAxLCBwMiwgbGF5b3V0ID0gbGF5b3V0KQ0KYGBgDQoNClZpZXdpbmcgdGhlIGRlbnNpdHkgcGxvdCBzaG93cyBhIHZlcnkgc3RvbmdseSBiaS1tb2RhbCBkYXRhc2V0LiAgV2l0aCBhIHRvdGFsIG9mIGByIHN1bSh3aGljaChnb2xkZW5fZ2F0ZSRUcmFmZmljIDwgMzAwMDApKSBgIG9ic2VydmF0aW9ucyBpbiB0aGUgbG93ZXIgbW9kZSwgdGhlcmUgc2hvdWxkIGJlIHN1ZmZpY2llbnQgZGF0YSB0byBwcm9wZXJseSB1c2UgYSBjb250cm9sIGNoYXJ0Lg0KDQoNCkknbGwgcXVpY2tseSB2aXN1YWxpemUgdHJhZmZpYyBkYXRhIGluIGNvbXBhcmlzb24gdG8gZGF0ZXMgaW4gb3JkZXIgdG8gZ2V0IGEgYmV0dGVyIGZlZWwgb2YgaG93IHRyYWZmaWMgY2hhbmdlcyBvdmVyIHRpbWUuDQoNCmBgYHtyIHRyYWZmaWNCcmVha2Rvd259DQpwMSA8LSBnZ3Bsb3QoZ29sZGVuX2dhdGUsIGFlcyh4ID0gTW9udGgsIHkgPSBUcmFmZmljKSkrDQogIGdlb21fbGluZShjb2wgPSAnbWlkbmlnaHRibHVlJykgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkpICsNCiAgZ2VvbV9zbW9vdGgoKQ0KDQpwMiA8LSBnb2xkZW5fZ2F0ZSAlPiUNCiAgZ2dwbG90KGFlcyhUcmFmZmljKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gJ2JsdWUnLCBiaW5zID0gMzApICsNCiAgdGhlbWUoYXhpcy50ZXh0Lng9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkNCg0KcDMgPC0gZ29sZGVuX2dhdGUgJT4lDQogIG11dGF0ZSh3ZGF5ID0gd2RheShNb250aCwgbGFiZWwgPSBUKSkgJT4lDQogIGdyb3VwX2J5KHdkYXkpICU+JQ0KICBzdW1tYXJpemUoRFRyYWZmaWMgPSBtZWRpYW4oVHJhZmZpYykpICU+JQ0KICBnZ3Bsb3QoYWVzKHdkYXksIERUcmFmZmljLCBmaWxsID0gd2RheSkpICsNCiAgZ2VvbV9jb2woKSArDQogIGxhYnMoIHkgPSAnTWVhbiBUcmFmZmljJywgeCA9ICdXZWVrZGF5JykgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScsIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IDAuOSkpIA0KDQpwNCA8LSBnb2xkZW5fZ2F0ZSAlPiUNCiAgbXV0YXRlKG1vbnRoID0gbW9udGgoTW9udGggLCBsYWJlbCA9IFQpKSAlPiUNCiAgZ3JvdXBfYnkobW9udGgpICU+JQ0KICBzdW1tYXJpemUoTVRyYWZmaWMgPSBtZWRpYW4oVHJhZmZpYyksIG1lZGlhblRyYWZmaWMgPSBtZWRpYW4oVHJhZmZpYykpICU+JQ0KICBnZ3Bsb3QoYWVzKG1vbnRoLCBNVHJhZmZpYywgZmlsbCA9IG1vbnRoKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgbGFicyh4ID0gJ01vbnRoJywgeSA9ICcnKSsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnLCBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEsIHZqdXN0ID0gLjUpKQ0KDQpsYXlvdXQgPC0gbWF0cml4KGMoMSwxLDEsMSwyLDMsNCw0KSwgbnJvdyA9IDIgLGJ5cm93ID0gVFJVRSkNCm11bHRpcGxvdChwMSwgcDIsIHAzLCBwNCwgbGF5b3V0ID0gbGF5b3V0KQ0KYGBgDQoNCkZyb20gdGhlIGxpbmUgZ3JhcGgsIGl0IGFwcGVhcnMgdGhlcmUgaXMgYSBzbGlnaHQgc2Vhc29uYWxpdHkgaW4gYWRkaXRpb24gdG8gc29tZSBkYXRhcG9pbnRzIHRoYXQgbWF5IGhhdmUgc29tZSBlcnJvciBpbiBjb2xsZWN0aW9uLiAgRGVhbGluZyB3aXRoIHRob3NlIGVycm9ycyBpcyBvdXRzaWRlIHRoZSBzY29wZSBvZiB0aGlzIHdyaXRlLXVwLiBUcmFmZmljIHNlZW1zIGZhaXJseSBjb25zaXN0ZW50IGFjcm9zcyB3ZWVrZGF5cyB3aXRoIFdlZG5lc2RheSBhbmQgVGh1cnNkYXkgc2VlbWluZyB0byBiZSB0aGUgc2xvd2VzdCBkYXlzLiBUaGUgc2Vhc29uYWxpdHkgcG9zaXQgc2VlbXMgdG8gYmUgdHJ1ZSBsb29raW5nIGF0IHRoZSBtZWRpYW4gdHJhZmZpYyBieSBtb250aC4gTGF0ZSBzdW1tZXIgc2VlcyB0aGUgbW9zdCB0cmFmZmljIHdpdGggYSBkaXAgZHVyaW5nIHRoZSB3aW50ZXIgbW9udGhzLiANCg0KVG8gbWFrZSBvdXIgcWljaGFydHMgZGVtbyBhIGxpdHRsZSBlYXNpZXIgdG8gcmVhZCwgbGV0cyBjcmVhdGUgYSBuZXcgb2JqZWN0ICdnZ19ub19vdXQnIGluIHdoaWNoIHdlIHJlbW92ZSB0aGUgZXh0cmVtZSB2YWx1ZXMuDQoNCmBgYHtyIGNyZWF0ZWdnX25vX291dH0NCmdnX25vX291dCA8LSBnb2xkZW5fZ2F0ZSAlPiUNCiAgZmlsdGVyKGdvbGRlbl9nYXRlJFRyYWZmaWMgPiAzMDAwMCkNCmRpbShnZ19ub19vdXQpDQpgYGANClRoaXMgbGVhdmVzIHVzIHdpdGggMTU2IG9mIG91ciBvcmlnaW5hbCAxNjggb2JzZXJ2YXRpb25zLiAgTGV0cyBxdWlja2x5IHJlcHJvZHVjZSB0aGUgYWJvdmUgY2hhcnRzIHVzaW5nIHRoZSBuZXcgc2V0IG9mIGRhdGENCmBgYHtyIHRyYWZmaWNCcmVha2Rvd25nZ19ub19vdXQsIHdhcm5pbmc9VCwgbWVzc2FnZT1UfQ0KcDEgPC0gZ2dwbG90KGdnX25vX291dCwgYWVzKHggPSBNb250aCwgeSA9IFRyYWZmaWMpKSsNCiAgZ2VvbV9saW5lKGNvbCA9ICdtaWRuaWdodGJsdWUnKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGF4aXMudGl0bGUueCA9IGVsZW1lbnRfYmxhbmsoKSkgKw0KICBnZW9tX3Ntb290aCgpDQoNCnAyIDwtIGdnX25vX291dCAlPiUNCiAgZ2dwbG90KGFlcyhUcmFmZmljKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShmaWxsID0gJ2JsdWUnLCBiaW5zID0gMzApICsNCiAgdGhlbWUoYXhpcy50ZXh0Lng9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkNCg0KcDMgPC0gZ2dfbm9fb3V0ICU+JQ0KICBtdXRhdGUod2RheSA9IHdkYXkoTW9udGgsIGxhYmVsID0gVCkpICU+JQ0KICBncm91cF9ieSh3ZGF5KSAlPiUNCiAgc3VtbWFyaXplKERUcmFmZmljID0gbWVkaWFuKFRyYWZmaWMpKSAlPiUNCiAgZ2dwbG90KGFlcyh3ZGF5LCBEVHJhZmZpYywgZmlsbCA9IHdkYXkpKSArDQogIGdlb21fY29sKCkgKw0KICBsYWJzKCB5ID0gJ01lYW4gVHJhZmZpYycsIHggPSAnV2Vla2RheScpICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnLCBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZT0gOTAsIGhqdXN0ID0gMSwgdmp1c3QgPSAwLjkpKQ0KDQpwNCA8LSBnZ19ub19vdXQgJT4lDQogIG11dGF0ZShtb250aCA9IG1vbnRoKE1vbnRoICwgbGFiZWwgPSBUKSkgJT4lDQogIGdyb3VwX2J5KG1vbnRoKSAlPiUNCiAgc3VtbWFyaXplKE1UcmFmZmljID0gbWVkaWFuKFRyYWZmaWMpLCBtZWRpYW5UcmFmZmljID0gbWVkaWFuKFRyYWZmaWMpKSAlPiUNCiAgZ2dwbG90KGFlcyhtb250aCwgTVRyYWZmaWMsIGZpbGwgPSBtb250aCkpICsNCiAgZ2VvbV9jb2woKSArDQogIGxhYnMoeCA9ICdNb250aCcsIHkgPSAnJykrDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJywgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IC41KSkNCg0KDQoNCmxheW91dCA8LSBtYXRyaXgoYygxLDEsMSwxLDIsMyw0LDQpLG5yb3cgPSAyLCBieXJvdyA9IFQpDQptdWx0aXBsb3QocDEsIHAyLCBwMywgcDQsIGxheW91dCA9IGxheW91dCkNCmBgYA0KDQpUaGUgc2Vhc29uYWxpdHkgYXBwZWFycyB0byBiZSBldmVuIHN0cm9uZ2VyIGFuZCB0aGUgc21vb3RoaW5nIGN1cnZlIHNob3dzIGEgbXVjaCBzdHJvbmdlciBwb3NpdGl2ZSB0cmVuZC4gDQoNCkFsbCB0aGlzIGJlaW5nIHNhaWQsIGl0IHNlZW1zIHdlIGhhdmUgYSBzdWZmaWNpZW50IGRhdGFzZXQgZm9yIGNyZWF0aW5nIGEgQ29udHJvbCBDaGFydC4NCg0KDQoNCiMgcWljaGFydHMNCg0KIyMjIHFpYyBJbnRyb2R1Y3Rpb24NCg0KVGhlIHFpYygpIGZ1bmN0aW9uIHdpdGhpbiBxaWNoYXJ0czIgbWFrZXMgZ2VuZXJhdGluZyBjb250cm9sIGNoYXJ0cyB2ZXJ5IGVhc3kuICBUaGUgZGVmYXVsdCBjb250cm9sIGNoYXJ0IGlzIGEgc2ltcGxlIHJ1biBjaGFydCBhbmQgY2FuIGJlIGdlbmVyYXRlZCBieSBzaW1wbHkgcGFzc2luZyB0aGUgVHJhZmZpYyBjb2x1bW4gdG8gdGhlIHFpYyBmdW5jdGlvbg0KDQpgYGB7ciBxaWN9DQpxaWMoeSA9IGdnX25vX291dCRUcmFmZmljICkNCmBgYA0KDQpUaGlzIGNhbiBiZSB1c2VkIHRvIHNlZSB0aGUgZ2VuZXJhbCB0cmVuZCBidXQgZG9lc24ndCBjb250YWluIHRoZSBjb250cm9sIGxpbWl0cy4gIFRvIHB1dCBhIGxpdHRsZSBtb3JlIGluZm9ybWF0aW9uIG9uIHRoZSBjaGFydCwgc2ltcGx5IHNwZWNpZnkgImNoYXJ0ID0gJ2knIiBpbiB0aGUgcWljIGZ1bmN0aW9uLiAgVGhpcyB3aWxsIHBsYWNlIHVwcGVyL2xvd2VyIGxpbWl0IGxpbmVzIGFzIHdlbGwgYXMgaGlnaGxpZ2h0IHBvaW50cyB0aGF0IGZhbGwgb3V0c2lkZSBvZiB0aGUgMy1zaWdtYSBjb25maWRlbmNlIGludGVydmFsLg0KDQojIyMgaSBDaGFydHMNCg0KYGBge3IgcWljSUNoYXJ0fQ0KcWljKHkgPSBnZ19ub19vdXQkVHJhZmZpYywgeCA9IGdnX25vX291dCRNb250aCxjaGFydCA9ICdpJykNCmBgYA0KDQpBcyB5b3UgY2FuIHNlZSwgdGhpcyBnZW5lcmF0ZXMgYSBiZWF1dGlmdWwgY29udHJvbCBjaGFydCEgIEkgaGF2ZSBhZGRpdGlvbmFsbHkgc3BlY2lmaWVkIHRoZSB4IGF4aXMgdG8gY29udGFpbiBkYXRlIGluZm9ybWF0aW9uLiAgDQoNCk9uZSBsaW1pdGF0aW9uIHRvIHRoZSBxaWMgZnVuY3Rpb24gaXMgdGhhdCBpdCBkb2VzIG5vdCBhbGxvdyB0aGUgdXNlciB0byBzcGVjaWZ5IGhvdyBtYW55IG9ic2VydmF0aW9ucyB0byB1c2UuICBGb3IgaW5zdGFuY2UgeW91IGNhbm5vdCBuYXRpdmVseSBqdXN0IHNlbGVjdCB0aGUgbW9zdCByZWNlbnQgMzAgaW5zdGFuY2VzIG9mIHRoZSBvYnNlcnZhdGlvbi4gIEx1Y2t5IGZvciB1cywgUiBpcyB2ZXJ5IHBvd2VyZnVsIGFuZCB0aGlzIGVmZmVjdCBjYW4gYmUgYWNoaWV2ZWQgaW4ganVzdCBvbmUgbGluZS4gIA0KDQoNCmBgYHtyIHJlY2VudFFpY0lDaGFydH0NCmdnX25vX291dC5yZWNlbnQgPC0gdGFpbChnZ19ub19vdXQsIG4gPSAzMCkNCnEgPC1xaWMoeSA9IGdnX25vX291dC5yZWNlbnQkVHJhZmZpYywgeCA9IGdnX25vX291dC5yZWNlbnQkTW9udGgsIGNoYXJ0ID0gJ2knKQ0KYGBgDQoNCg0KIyMjIEFjY2Vzc2luZyBTaWduYWwgUG9pbnRzDQoNCkxhc3RseSB3ZSBtYXkgd2FudCB0byBpbnZlc3RpZ2F0ZSB0aGUgcG9pbnRzIHRoYXQgZmFsbCBvdXRzaWRlIG9mIHRoZSBjb250cm9sIGxpbWl0cy4gIFdlIGNhbiBkbyB0aGF0IGJ5IHN1YnNldHRpbmcgdXNpbmcgdGhlICRzaWduYWxzIGVsZW1lbnQgb2YgdGhlIHFpYyBvYmplY3QuDQoNCmBgYHtyfQ0Kc2lnbmFsIDwtIHEkc2lnbmFscw0KZ2dfbm9fb3V0LnJlY2VudFtzaWduYWwgLCBdDQpgYGANCg0KDQojIEltcGxlbWVudGF0aW9uDQpVc2luZyB0aGUgcWljIGZ1bmN0aW9uIHdvdWxkIGVhc2lseSBlbm91Z2ggYmUgZ2VuZXJhbGl6ZWQgdG8gYmUgYXBwbGllZCB0byB0cnVjayBNUEcgb3Igc29tZSBvdGhlciBtZXRyaWMgdGhyb3VnaCByc3RhdCBpZiB3ZSBjYW4gZmlndXJlIG91dCBob3cgdG8gZ2V0IGl0IGluc3RhbGxlZCBwcm9wZXJseS4gIFRoaXMgaXMgZmFyIGZyb20gdGhlIGVuZCBvZiBxaWNoYXJ0czIncyB1c2VmdWxuZXNzLiAgQWRkaXRpb25hbCBpbmZvcm1hdGlvbiBjYW4gYmUgZm91bmQgaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3FpY2hhcnRzL3ZpZ25ldHRlcy9jb250cm9sY2hhcnRzLmh0bWwgDQoNCg==