Introduction
The two core plotting and graphics packages in the base R are:
graphics: contains plotting functions for the
“base” graphing systems, including plot, hist, boxplot, and many
others.
grDevices: contains all the code implementing
the various graphics devices, including X11, PDF, PostScript, PNG,
etc.
The grDevices
package contains the functionality for
sending plots to various output devices. The graphics
package contains the code for actually constructing and annotating
plots.
In this note, we focus on using the base plotting system to create
graphics on the screen device.
Working Data -iris
Objectives: Identify meaningful
patterns and make visual representations
- What information does the data set have?
- How to find the information of interest?
- descriptive?
- algorithm-based?
- model-based?
- Too many variables or too many data points?
- What form of visualization to use?
- What visual design? Single pattern or multiple patterns?
Base Graphics
Base graphics are used most commonly and are a very powerful system
for creating data graphics. There are two phases to creating a base
plot:
For example, calling the base plot functions plot(x, y)
or hist(x)
will launch a graphics device (if one is not
already open) and draw a new plot on the device. The based plot function
has many arguments, letting us set the title, x-axis label, y-axis
label, etc.
The base graphics system has many global parameters that can be set
and tweaked. These parameters are documented in ?par
and
are used to control the global behavior of plots, such as the margins,
axis orientation, and other details.
Simple Base
Graphics
This section explains how to use the base plotting functions to make
basic statistical graphics.
Histogram
Here is an example of a simple histogram made using the
hist()
function in the graphics package. If we run this
code and our graphics window is not already open, it should open once
you call the hist()
function.
## Draw a new plot on the screen device
hist(iris$Sepal.Length,
xlab = "Sepal Length",
ylab = "Counts",
main = "Frequency distribution of sepal length")
Boxplot
Boxplots can be made in R using the boxplot()
function,
which takes as its first argument a formula. The formula has a form of
y-axis ~ x-axis
. Anytime you see a ~
in R,
it’s a formula. Here, we are plotting the sepal length of iris flowers
and the right-hand side of ~
the Species.
boxplot(Sepal.Length ~ Species, data = iris, xlab = "Species", ylab = "Sepal Length")
Each boxplot shows the median, 25th, and 75th percentiles of the data
(the “box”), as well as +/- 1.5 times the interquartile range (IQR) of
the data (the “whiskers”). Any data points beyond 1.5 times the IQR of
the data are indicated separately with circles.
We can see that Virginica has an outlier.
Scatterplot
Here is a simple scatter-plot made with the plot()
function.
plot(iris$Sepal.Length, iris$Sepal.Width,
xlab = "sepal length",
ylab = "sepal width",
main = " ")
Generally, the plot()
function takes two vectors of
numbers: one for the x-axis coordinates and one for the y-axis
coordinates. However, plot()
is a generic function in R,
which means its behavior can change depending on what kinds of data are
passed to the function.
One thing to note here is that we have provided labels for the x- and
the y-axis and title as well. If they were not specified, the plot
function will provide this information automatically using the names of
the variables.
Some Important Base
Graphics Parameters
Many base plotting functions share a set of global parameters. Here
are a few key ones.
pch
: the plotting symbol (default is an open
circle).
lty
: the line type (default is a solid line), can be
dashed, dotted, etc.
lwd
: the line width, specified as an integer
multiple.
col
: the plotting color, specified as a number, string,
or hex code; the colors() function gives you a vector of colors by
name.
xlab
: character string for the x-axis label.
ylab
: character string for the y-axis label.
The par()
function is used to specify the global
graphics parameters that affect all plots in an R session. These
parameters can be overridden when they are specified as arguments to
specific plotting functions.
las
: the orientation of the axis labels on the
plot.
bg
: the background color.
mar
: the margin size.
oma
: the outer margin size (default is 0 for all
sides).
mfrow
: number of plots per row, column (plots are
filled row-wise).
mfcol
: number of plots per row, column (plots are
filled column-wise).
We can see the default values for global graphics parameters by
calling the par()
function and passing the name of the
parameter in quotes.
par("lty")
[1] "solid"
par("col")
[1] "black"
par("pch")
[1] 1
Here are some more default values for global graphics parameters.
par("bg")
[1] "white"
par("mar")
[1] 5.1 4.1 4.1 2.1
par("mfrow")
[1] 1 1
For the most part, we usually don’t have to modify these when making
quick plots. However, we might need to tweak them for finalizing
finished plots.
Base Plotting
Functions
The most basic base plotting function is plot()
. The
plot()
function makes a scatter-plot, or other types of
plot depending on the class of the object being plotted. Calling
plot()
will draw a plot on the screen device (and open the
screen device if not already open). After that, annotation functions can
be called to add to the already-made plot.
Some key annotation functions are
lines()
: add lines to a plot, given a vector of x
values and a corresponding vector of y values (or a 2-column matrix);
this function just connects the dots
points()
: add points to a plot
text()
: add text labels to a plot using specified x, y
coordinates
title()
: add annotations to x, y-axis labels, title,
subtitle, outer margin
mtext()
: add arbitrary text to the margins (inner or
outer) of the plot
axis()
: adding axis ticks/labels
Here’s an example of creating a base plot and adding some annotation.
First we make the plot with the plot()
function and then
add a title to the top of the plot with the title()
function.
## Make the initial plot
plot(iris$Sepal.Length, iris$Sepal.Width)
## Add a title
title(main = "Sepal length and width of iris")
Here, I start with the same plot as above (although I add the title
right away using the main argument to plot()
) and then
annotate it by coloring blue the data points corresponding.
sepal.length = iris$Sepal.Length
sepal.width = iris$Sepal.Width
species = iris$Species
## identifying the ID of Virginica
virginca.id = which(species=="virginica") # value are case sensitive!
## making scatter plot
plot(sepal.length, sepal.width, main = "Sepal legnth vs sepal width")
##
points(sepal.length[virginca.id], sepal.width[virginca.id], pch = 19, col = "red")
The following plot colors the data points with different colors based
on species. legend()
function explains the meaning of the
different colors in the plot.
sepal.length = iris$Sepal.Length
sepal.width = iris$Sepal.Width
species = iris$Species
## identifying the ID of Virginica
virginca.id = which(species=="virginica") # value are case sensitive!
setosa.id = which(species=="setosa")
versicolor.id = which(species=="versicolor")
## making an empty plot: type = "n" ==> no point
plot(sepal.length, sepal.width, main = "Sepal legnth vs sepal width", type = "n")
##
points(sepal.length[virginca.id], sepal.width[virginca.id], pch = 18, col = "red")
points(sepal.length[setosa.id], sepal.width[setosa.id], pch = 19, col = "blue")
points(sepal.length[versicolor.id], sepal.width[versicolor.id], pch = 20, col = "cyan")
legend("topleft", c("virginica", "setosa", "versicolor"),
col=c("red", "blue", "cyan"),
pch=c(18, 19, 20))
Base Plot with
Regression Line
It’s fairly common to make a scatterplot and then want to draw a
simple linear regression line through the data. This can be done with
the abline()
function.
Below, we first make the plot (as above). Then we fit a simple linear
regression model using the lm()
function. Here, we try to
model Ozone as a function of Wind. Then we take the output of
lm()
and pass it to the abline()
function
which automatically takes the information from the model object and
calculates the corresponding regression line.
Note that in the call to plot()
below, we set
pch = 20
to change the plotting symbol to a filled
circle.
sepal.length = iris$Sepal.Length
sepal.width = iris$Sepal.Width
## making a plot
plot(sepal.length, sepal.width, main = "Sepal legnth vs sepal width", pch = 21)
##
model <- lm( sepal.width ~ sepal.length)
## Draw regression line on plot
abline(model, lwd = 2, col = "red")
Multiple Base
Plots
Making multiple plots side by side is a useful way to visualize many
relationships between variables with static 2-D plots. Often the
repetition of data across a single plot window can be a useful way to
identify patterns in the data. In order to do this, the
mfrow
and mfcol
parameters set by the
par()
function are critical.
Both the mfrow
and mfcol
parameters take
two numbers: the number of rows of plots followed by the number of
columns. The multiple plots will be arranged in a matrix-like pattern.
The only difference between the two parameters is that if
mfrow
is set, then the plots will be drawn row-wise; if
mfcol
is set, the plots will be drawn column-wise.
In the example below, we make two plots: sepal length vs sepal width
and petal length vs petal width. We set
par(mfrow = c(1, 2))
, which indicates that we have one row
of plots and two columns of plots.
par(mfrow = c(1, 2))
sepal.length = iris$Sepal.Length
sepal.width = iris$Sepal.Width
petal.length = iris$Petal.Length
petal.width = iris$Petal.Width
## making a plot
plot(sepal.length, sepal.width, main = "Sepal legnth vs sepal width", pch = 20)
plot(petal.length, petal.width, main = "Petal legnth vs petal width", pch = 21)
The example below creates three plots in a row by setting
par(mfrow = c(1, 3))
. Here we also change the plot margins
with the mar parameter. The various margin parameters, like
mar
, are specified by setting a value for each side of the
plot. Side 1
is the bottom of the plot, side 2
is the left-hand side, side 3
is the top, and
side 4
is the right-hand side.
In the example below we also modify the outer margin via the
oma
parameter to create a little more space for the plots
and to place them closer together.
# layout of the plot
par(mfrow = c(1, 3),
mar = c(4, 4, 2, 1),
oma = c(0, 0, 2, 0))
# extract variables from the data frame
sepal.length = iris$Sepal.Length
sepal.width = iris$Sepal.Width
species = iris$Species
## identifying the ID of Virginica
virginca.id = which(species=="virginica") # value are case sensitive!
setosa.id = which(species=="setosa")
versicolor.id = which(species=="versicolor")
## making three plots
plot(sepal.length[virginca.id], sepal.width[virginca.id], main = "virginca")
plot(sepal.length[setosa.id], sepal.width[setosa.id], main = "setosa")
plot(sepal.length[versicolor.id], sepal.width[versicolor.id], main = "versicolor")
##
mtext("Sepal length vs width: Iris", outer = TRUE)
In the above example, the mtext()
function was used to
create an overall title for the panel of plots. Hence, each individual
plot has a title, while the overall set of plots also has a summary
title. The mtext()
function is important for adding text
annotations that aren’t specific to a single plot.
Notice that the scales of the vertical axes of the three plots are
not the same. To misleading visual comparison, we should make the scales
of both axes the same.
# layout of the plot
par(mfrow = c(1, 3),
mar = c(4, 4, 2, 1),
oma = c(0, 0, 2, 0))
# extract variables from the data frame
sepal.length = iris$Sepal.Length
sepal.width = iris$Sepal.Width
species = iris$Species
## identifying the ID of Virginica
virginca.id = which(species=="virginica") # value are case sensitive!
setosa.id = which(species=="setosa")
versicolor.id = which(species=="versicolor")
## making three plots
plot(sepal.length[virginca.id], sepal.width[virginca.id], xlim=c(4,8), ylim=c(2, 4.5), main = "virginca")
plot(sepal.length[setosa.id], sepal.width[setosa.id], xlim=c(4,8), ylim=c(2, 4.5),main = "setosa")
plot(sepal.length[versicolor.id], sepal.width[versicolor.id], xlim=c(4,8), ylim=c(2, 4.5),main = "versicolor")
##
mtext("Sepal length vs width: Iris", outer = TRUE)
We now can visualize the mean sepal width and sepal length among the
species.
Controlling Point Size
and Transparency
we use cex=
and alpha=
to control the point
size according to the value of a variable and the level of transparency
of the point.
sepal.length = iris$Sepal.Length
sepal.width = iris$Sepal.Width
size.petal = iris$Petal.Width
species = iris$Species
## identifying the ID of Virginica
virginca.id = which(species=="virginica") # value are case sensitive!
setosa.id = which(species=="setosa")
versicolor.id = which(species=="versicolor")
## color code
col.code = c(alpha("red",0.5),alpha("blue",0.5),alpha("cyan",0.5))
## making an empty plot: type = "n" ==> no point
plot(sepal.length, sepal.width, main = "Sepal legnth vs sepal width", type = "n")
## change the point size based on their average of sepal length and width
points(sepal.length[virginca.id], sepal.width[virginca.id],
pch = 19, col = col.code[1], cex = size.petal[virginca.id])
points(sepal.length[setosa.id], sepal.width[setosa.id],
pch = 19, col = col.code[2], cex = size.petal[setosa.id])
points(sepal.length[versicolor.id], sepal.width[versicolor.id],
pch = 19, col = col.code[3], cex = size.petal[versicolor.id])
legend("topleft", c("virginica", "setosa", "versicolor"),
col=col.code,
pch=c(19, 19, 19))
Annotations
We add annotations to the base R plot. The annotations could be plain
texts, images, and mathematical expressions.
iris.img <- "https://pengdsci.github.io/STA553VIZ/w03/img/iris.jpeg"
my.iris <- readJPEG(getURLContent(iris.img))
raster.iris <- as.raster(my.iris)
# Use the code in the precious section
sepal.length = iris$Sepal.Length
sepal.width = iris$Sepal.Width
size.petal = iris$Petal.Width
species = iris$Species
## identifying the ID of Virginica
virginca.id = which(species=="virginica") # value are case sensitive!
setosa.id = which(species=="setosa")
versicolor.id = which(species=="versicolor")
## color code
# col.code = c(alpha("red",0.5),alpha("blue",0.5),alpha("cyan",0.5))
## making an empty plot: type = "n" ==> no point
plot(sepal.length, sepal.width, main = "Sepal legnth vs sepal width", type = "n")
## change the point size based on their average of sepal length and width
points(sepal.length[virginca.id], sepal.width[virginca.id],
pch = 19, col = "purple", cex = size.petal[virginca.id], alpha = 0.6)
points(sepal.length[setosa.id], sepal.width[setosa.id],
pch = 19, col = "navy", cex = size.petal[setosa.id], alpha = 0.6)
points(sepal.length[versicolor.id], sepal.width[versicolor.id],
pch = 19, col = "cyan", cex = size.petal[versicolor.id], alpha = 0.6)
legend("topleft", c("virginica", "setosa", "versicolor"),
col=c("purple", "navy", "cyan"),
pch=c(19, 19, 19))
## various annotations
#specify the position of the image through bottom-left and top-right coords
rasterImage(raster.iris,6,3.5,7,4.4)
text(7.25, 2.25, "The size is proportional to petal length", col = "purple", cex = 0.5)
What Story To
Tell?
We have discussed various graphic functions and techniques in base R
to create graphic representations of data. To conclude this note, let’s
look at what information general audience might be interested in.
Relationship between
variables?
pairs(iris[, -5])
Distribution of
variables?
## Data Partition
Sepal.L.set = iris$Sepal.Length[which(iris$Species=="setosa")]
Sepal.L.ver = iris$Sepal.Length[which(iris$Species=="versicolor")]
Sepal.L.vir = iris$Sepal.Length[which(iris$Species=="virginica")]
#
Sepal.W.set = iris$Sepal.Width[which(iris$Species=="setosa")]
Sepal.W.ver = iris$Sepal.Width[which(iris$Species=="versicolor")]
Sepal.W.vir = iris$Sepal.Width[which(iris$Species=="virginica")]
#
Petal.L.set = iris$Petal.Length[which(iris$Species=="setosa")]
Petal.L.ver = iris$Petal.Length[which(iris$Species=="versicolor")]
Petal.L.vir = iris$Petal.Length[which(iris$Species=="virginica")]
#
Petal.W.set = iris$Petal.Width[which(iris$Species=="setosa")]
Petal.W.ver = iris$Petal.Width[which(iris$Species=="versicolor")]
Petal.W.vir = iris$Petal.Width[which(iris$Species=="virginica")]
###
par(mfrow=c(2,2))
###
########################## plot #1:
plot(density(Sepal.L.set), col="brown4", lty=1, lwd=2, xlim=c(3,9), ylim=c(0,1.8), xlab="Sepal Length", main="Distributions Sepal Length")
lines(density(Sepal.L.ver), col="blue", lty=1, lwd=2)
lines(density(Sepal.L.vir), col="darkcyan", lty=1, lwd=2)
legend("topright", c("setosa", "versicolor","virginica"),
col=c("brown4","blue","darkcyan"), lty=c(1,1,1),
lwd=c(1,1,1), bty="n", cex=0.7)
##
########################## plot #1:
plot(density(Sepal.W.set), col="brown4", lty=1, lwd=2, xlim=c(1,5), ylim=c(0,2), xlab="Sepal Width", main="Distributions Sepal Width")
lines(density(Sepal.W.ver), col="blue", lty=1, lwd=2)
lines(density(Sepal.W.vir), col="darkcyan", lty=1, lwd=2)
legend("topright", c("setosa", "versicolor","virginica"),
col=c("brown4","blue","darkcyan"), lty=c(1,1,1),
lwd=c(1,1,1), bty="n", cex=0.7)
##
########################## plot #1:
plot(density(Petal.L.set), col="brown4", lty=1, lwd=2, xlim=c(1,8), ylim=c(0,2.8), xlab="Petal Length", main="Distributions Petal Length")
lines(density(Petal.L.ver), col="blue", lty=1, lwd=2)
lines(density(Petal.L.vir), col="darkcyan", lty=1, lwd=2)
legend("topright", c("setosa", "versicolor","virginica"),
col=c("brown4","blue","darkcyan"), lty=c(1,1,1),
lwd=c(1,1,1), bty="n", cex=0.7)
##
########################## plot #1:
plot(density(Petal.W.set), col="brown4", lty=1, lwd=2, xlim=c(0,3), ylim=c(0,8), xlab="Petal Width", main="Distributions Petal Width")
lines(density(Petal.W.ver), col="blue", lty=1, lwd=2)
lines(density(Petal.W.vir), col="darkcyan", lty=1, lwd=2)
legend("topright", c("setosa", "versicolor","virginica"),
col=c("brown4","blue","darkcyan"), lty=c(1,1,1),
lwd=c(1,1,1), bty="n", cex=0.7)
Modeling -
Classification and Regression?
When building regression models, one of the issues is the potential
collinearity.
\(\pi_1 = Pr[Y = \text{setosa}]\),
\(\pi_2 = Pr[Y = \text{versicolor}]\),
and \(\pi_1 = Pr[Y =
\text{virginica}]\). The well known baseline logit model is
defined to be the following system of two odds regression models
\[
\log(\pi_2/ \pi_1) = \alpha_0 + \alpha_1x_1 + \cdots + \alpha_kx_k
\] \[
\log(\pi_3/ \pi_1) = \beta_0 + \beta_1x_1 + \cdots + \beta_kx_k
\] Note that \(\pi_1 + \pi_2 + \pi_3 =
1\). Solve for \(\pi_1, \pi_2\),
and \(\pi_3\), we have
\[
\pi_1 = \frac{1}{1+\exp(\alpha_0 + \alpha_1x_1 + \cdots + \alpha_kx_k) +
\exp(\beta_0 + \beta_1x_1 + \cdots + \beta_kx_k)}
\]
\[
\pi_2 = \frac{\exp(\alpha_0 + \alpha_1x_1 + \cdots +
\alpha_kx_k)}{1+\exp(\alpha_0 + \alpha_1x_1 + \cdots + \alpha_kx_k) +
\exp(\beta_0 + \beta_1x_1 + \cdots + \beta_kx_k)}
\]
\[
\pi_3 = \frac{\exp(\beta_0 + \beta_1x_1 + \cdots +
\beta_kx_k)}{1+\exp(\alpha_0 + \alpha_1x_1 + \cdots + \alpha_kx_k) +
\exp(\beta_0 + \beta_1x_1 + \cdots + \beta_kx_k)}
\]
versicolor |
18.69037 |
-5.458424 |
-8.707401 |
14.24477 |
-3.097684 |
virginica |
-23.83628 |
-7.923634 |
-15.370769 |
23.65978 |
15.135300 |
How to interpret the results? Can we make a visual representation of
the resulting model? How?
The following is one example of representation of how individual
features impact membership prediction (classification).
setosa = summary(iris[which(iris$Species=="setosa"),])
versicolor = summary(iris[which(iris$Species=="versicolor"),])
virginica = summary(iris[which(iris$Species=="virginica"),])
list(setosa = setosa, versicolor = versicolor, virginica = virginica)
$setosa
Sepal.Length Sepal.Width Petal.Length Petal.Width
Min. :4.300 Min. :2.300 Min. :1.000 Min. :0.100
1st Qu.:4.800 1st Qu.:3.200 1st Qu.:1.400 1st Qu.:0.200
Median :5.000 Median :3.400 Median :1.500 Median :0.200
Mean :5.006 Mean :3.428 Mean :1.462 Mean :0.246
3rd Qu.:5.200 3rd Qu.:3.675 3rd Qu.:1.575 3rd Qu.:0.300
Max. :5.800 Max. :4.400 Max. :1.900 Max. :0.600
Species
setosa :50
versicolor: 0
virginica : 0
$versicolor
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
Min. :4.900 Min. :2.000 Min. :3.00 Min. :1.000 setosa : 0
1st Qu.:5.600 1st Qu.:2.525 1st Qu.:4.00 1st Qu.:1.200 versicolor:50
Median :5.900 Median :2.800 Median :4.35 Median :1.300 virginica : 0
Mean :5.936 Mean :2.770 Mean :4.26 Mean :1.326
3rd Qu.:6.300 3rd Qu.:3.000 3rd Qu.:4.60 3rd Qu.:1.500
Max. :7.000 Max. :3.400 Max. :5.10 Max. :1.800
$virginica
Sepal.Length Sepal.Width Petal.Length Petal.Width
Min. :4.900 Min. :2.200 Min. :4.500 Min. :1.400
1st Qu.:6.225 1st Qu.:2.800 1st Qu.:5.100 1st Qu.:1.800
Median :6.500 Median :3.000 Median :5.550 Median :2.000
Mean :6.588 Mean :2.974 Mean :5.552 Mean :2.026
3rd Qu.:6.900 3rd Qu.:3.175 3rd Qu.:5.875 3rd Qu.:2.300
Max. :7.900 Max. :3.800 Max. :6.900 Max. :2.500
Species
setosa : 0
versicolor: 0
virginica :50
Next, we show the relationship between Sepal.Length
and
the classification probabilities \(\pi_1,
\pi_2\) and \(\pi_3\) by fixing
the values of other variables at their corresponding means.
SepalLength = seq(4, 9, length=50)
pi01 = 1/(1+exp(18.69 - 5.46*SepalLength - 8.71*3 + 14.25*4 - 3.10*1)+ exp(-23.84 - 7.92**SepalLength - 15.37*3 + 23.66*4 + 15.14*1 ))
##
pi02 = exp(18.69 - 5.46*SepalLength - 8.71*3 + 14.25*4 - 3.10*1 )/(1+exp(18.69 - 5.46*SepalLength - 8.71*3 + 14.25*4 - 3.10*1 )+ exp(-23.84 - 7.92**SepalLength - 15.37*3 +23.66*4 + 15.14*1 ))
##
##
pi03 = exp(-23.84 - 7.92**SepalLength - 15.37*3 +23.66*4 + 15.14*1 )/(1+exp(18.69 - 5.46*SepalLength - 8.71*3 + 14.25*4 - 3.10*1)+ exp(-23.84 - 7.92**SepalLength - 15.37*3 +23.66*4 + 15.14*1 ))
##
#par(mfrow=c(1,3))
plot(SepalLength, pi01, main="Setosa",
type = "l",
ylim = c(0,1),
xlab = "Sepal Length",
ylab = "Classification Probability",
col = "blue",
lwd = 2)
lines(SepalLength, pi02, main = "Versicolor", ylab="Classification Probability",
type = "l", col = "purple", lwd = 2)
lines(SepalLength, pi03, main = "Virginica", ylab="Classification Probability",
type="l", col = "brown4", lwd = 2)
abline(v=8.51, lty = 2)
points(8.51, 0.495, pch=19, col = "darkred", dex = 1.5)
text(8, 0.495, "(8.51, 0.495)", cex = 0.6)
legend("left", c("Setosa", "Versicolor", "Virginica"), lwd=rep(2,3),
col=c("blue", "purple", "brown"), lty = rep(1,3), cex=0.9, bty="n")
The above three figures show how sepal length
is associated
with the three classification probabilities. For example, among setosa,
under the same conditions (i.e., sepal width, petal length, and petal
width are the same), as sepal length increases, the classification
probability also increases. The opposite pattern is observed among
versicolor iris flowers. The sepal length and the classification
probability are not associated.
For all iris flowers with Sepal Width 3, Petal Length 4, and Petal
Width 1.5, when Sepal Length < 8.51, \(P(\text{Versicolor}) >
P(\text{Setosa})\) meaning that it is more likely to be
Versicolor iris
. Otherwise, it is more likely to be
setosa
. Sepal length does not have predictive
power for virginica
.
We can also look at the relationship between the classification
probability and other numerical variables in the data set in a similar
way.
Clustering?
Another interesting problem we may explore is the see whether there
are some clusters based on the four numerical variables. There are many
clustering algorithms for practitioners. For illustrative purposes, we
use the popular k-means algorithm in the following example.
tot.withinss <- vector(mode="character", length=10)
for (i in 1:10){
irisCluster <- kmeans(iris[,1:4], center=i, nstart=20)
tot.withinss[i] <- irisCluster$tot.withinss
}
plot(1:10, tot.withinss, main="Elbow Plot for Optimal Cluster Determination",
type="b", pch=19)
irisCluster <- kmeans(iris[,1:4], center=3, nstart=20)
irisCluster
K-means clustering with 3 clusters of sizes 62, 38, 50
Cluster means:
Sepal.Length Sepal.Width Petal.Length Petal.Width
1 5.901613 2.748387 4.393548 1.433871
2 6.850000 3.073684 5.742105 2.071053
3 5.006000 3.428000 1.462000 0.246000
Clustering vector:
[1] 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
[38] 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
[75] 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2 2 2 2 1 2 2 2 2
[112] 2 2 1 1 2 2 2 2 1 2 1 2 1 2 2 1 1 2 2 2 2 2 1 2 2 2 2 1 2 2 2 1 2 2 2 1 2
[149] 2 1
Within cluster sum of squares by cluster:
[1] 39.82097 23.87947 15.15100
(between_SS / total_SS = 88.4 %)
Available components:
[1] "cluster" "centers" "totss" "withinss" "tot.withinss"
[6] "betweenss" "size" "iter" "ifault"
clusplot(iris, irisCluster$cluster, color=T, shade=T, labels=0, lines=0)
LS0tDQp0aXRsZTogIkdldHRpbmcgU3RhcnRlZCB3aXRoIEJhc2UgUiBHcmFwaGljcyINCmF1dGhvcjogIkNoZW5nIFBlbmciDQpkYXRlOiAiV2VzdCBDaGVzdGVyIFVuaXZlcnNpdHkiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jX2NvbGxhcHNlZDogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgc21vb3RoX3Njcm9sbDogeWVzDQogICAgdGhlbWU6IGx1bWVuDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KZWRpdG9yX29wdGlvbnM6DQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCi0tLQ0KDQoNCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoNCi8qIFRhYmxlIG9mIGNvbnRlbnQgLSBuYXZpZ2F0aW9uICovDQpkaXYjVE9DIGxpIHsNCiAgICBsaXN0LXN0eWxlOm5vbmU7DQogICAgYmFja2dyb3VuZC1jb2xvcjpsaWdodGdyYXk7DQogICAgYmFja2dyb3VuZC1pbWFnZTpub25lOw0KICAgIGJhY2tncm91bmQtcmVwZWF0Om5vbmU7DQogICAgYmFja2dyb3VuZC1wb3NpdGlvbjowOw0KICAgIGZvbnQtZmFtaWx5OiBBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmOw0KICAgIGNvbG9yOiAjNzgwYzBjOw0KfQ0KDQoNCi8qIFRpdGxlIGZvbnRzICovDQpoMS50aXRsZSB7DQogIGZvbnQtc2l6ZTogMjRweDsNCiAgY29sb3I6IGRhcmtibHVlOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogIGZvbnQtZmFtaWx5OiBBcmlhbCwgSGVsdmV0aWNhLCBzYW5zLXNlcmlmOw0KICBmb250LXZhcmlhbnQtY2Fwczogbm9ybWFsOw0KfQ0KaDQuYXV0aG9yIHsgDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC1mYW1pbHk6IEFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWY7DQogIGNvbG9yOiBuYXZ5Ow0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoNC5kYXRlIHsgDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC1mYW1pbHk6IEFyaWFsLCBIZWx2ZXRpY2EsIHNhbnMtc2VyaWY7DQogIGNvbG9yOiBkYXJrYmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KDQovKiBTZWN0aW9uIGhlYWRlcnMgKi8NCmgxIHsNCiAgICBmb250LXNpemU6IDIycHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IGRhcmtyZWQ7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDIgew0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoMyB7IA0KICAgIGZvbnQtc2l6ZTogMTVweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoNCB7DQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBkYXJrcmVkOw0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCi8qIERlY29yYXRpb24gb2YgaHlwZXJsaW5rcyAgKi8NCg0KLyogdW52aXNpdGVkIGxpbmsgKi8NCmE6bGluayB7DQogIGNvbG9yOiBncmVlbjsNCn0NCg0KLyogdmlzaXRlZCBsaW5rICovDQphOnZpc2l0ZWQgew0KICBjb2xvcjogcHVycGxlOw0KfQ0KDQovKiBtb3VzZSBvdmVyIGxpbmsgKi8NCmE6aG92ZXIgew0KICBjb2xvcjogcmVkOw0KfQ0KDQovKiBzZWxlY3RlZCBsaW5rICovDQphOmFjdGl2ZSB7DQogIGNvbG9yOiB5ZWxsb3c7DQp9DQo8L3N0eWxlPg0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCm9wdGlvbnMocmVwb3MgPSBsaXN0KENSQU49Imh0dHA6Ly9jcmFuLnJzdHVkaW8uY29tLyIpKQ0KIyBjb2RlIGNodW5rIHNwZWNpZmllcyB3aGV0aGVyIHRoZSBSIGNvZGUsIHdhcm5pbmdzLCBhbmQgb3V0cHV0IA0KIyB3aWxsIGJlIGluY2x1ZGVkIGluIHRoZSBvdXRwdXQgZmlsZXMuDQppZiAoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKQ0KICAgbGlicmFyeSh0aWR5dmVyc2UpDQp9DQppZiAoIXJlcXVpcmUoImtuaXRyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImtuaXRyIikNCiAgIGxpYnJhcnkoa25pdHIpDQp9DQppZiAoIXJlcXVpcmUoImNvd3Bsb3QiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygiY293cGxvdCIpDQogICBsaWJyYXJ5KGNvd3Bsb3QpDQp9DQppZiAoIXJlcXVpcmUoImxhdGV4MmV4cCIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJsYXRleDJleHAiKQ0KICAgbGlicmFyeShsYXRleDJleHApDQp9DQppZiAoIXJlcXVpcmUoInBsb3RseSIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJwbG90bHkiKQ0KICAgbGlicmFyeShwbG90bHkpDQp9DQppZiAoIXJlcXVpcmUoImdhcG1pbmRlciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJnYXBtaW5kZXIiKQ0KICAgbGlicmFyeShnYXBtaW5kZXIpDQp9DQppZiAoIXJlcXVpcmUoInBuZyIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygicG5nIikgICAgICAgICAgICAgIyBJbnN0YWxsIHBuZyBwYWNrYWdlDQogICAgbGlicmFyeSgicG5nIikNCn0NCmlmICghcmVxdWlyZSgiUkN1cmwiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoIlJDdXJsIikgICAgICAgICAgICAgIyBJbnN0YWxsIFJDdXJsIHBhY2thZ2UNCiAgICBsaWJyYXJ5KCJSQ3VybCIpDQp9DQppZiAoIXJlcXVpcmUoImNvbG91cnBpY2tlciIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiY29sb3VycGlja2VyIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImNvbG91cnBpY2tlciIpDQp9DQppZiAoIXJlcXVpcmUoImdnYW5pbWF0ZSIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiZ2dhbmltYXRlIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImdnYW5pbWF0ZSIpDQp9DQppZiAoIXJlcXVpcmUoImdpZnNraSIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiZ2lmc2tpIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImdpZnNraSIpDQp9DQppZiAoIXJlcXVpcmUoIm1hZ2ljayIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygibWFnaWNrIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoIm1hZ2ljayIpDQp9DQppZiAoIXJlcXVpcmUoImdyRGV2aWNlcyIpKSB7DQogICAgaW5zdGFsbC5wYWNrYWdlcygiZ3JEZXZpY2VzIikgICAgICAgICAgICAgIA0KICAgIGxpYnJhcnkoImdyRGV2aWNlcyIpDQp9DQppZiAoIXJlcXVpcmUoImpwZWciKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImpwZWciKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgianBlZyIpDQp9DQppZiAoIXJlcXVpcmUoIlZHQU0iKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoIlZHQU0iKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiVkdBTSIpDQp9DQppZiAoIXJlcXVpcmUoIk1BU1MiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoIk1BU1MiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiTUFTUyIpDQp9DQppZiAoIXJlcXVpcmUoIm5uZXQiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoIm5uZXQiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgibm5ldCIpDQp9DQppZiAoIXJlcXVpcmUoImNsdXN0ZXIiKSkgew0KICAgIGluc3RhbGwucGFja2FnZXMoImNsdXN0ZXIiKSAgICAgICAgICAgICAgDQogICAgbGlicmFyeSgiY2x1c3RlciIpDQp9DQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLCAgIA0KICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdCA9IFRSVUUsICAgDQogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgIGNvbW1lbnQgPSBOQSkNCmBgYA0KDQoNCiMgSW50cm9kdWN0aW9uIA0KDQpUaGUgdHdvIGNvcmUgcGxvdHRpbmcgYW5kIGdyYXBoaWNzIHBhY2thZ2VzIGluIHRoZSBiYXNlIFIgYXJlOg0KDQoqICoqZ3JhcGhpY3MqKjogY29udGFpbnMgcGxvdHRpbmcgZnVuY3Rpb25zIGZvciB0aGUg4oCcYmFzZeKAnSBncmFwaGluZyBzeXN0ZW1zLCBpbmNsdWRpbmcgcGxvdCwgaGlzdCwgYm94cGxvdCwgYW5kIG1hbnkgb3RoZXJzLg0KDQoqICoqZ3JEZXZpY2VzKio6IGNvbnRhaW5zIGFsbCB0aGUgY29kZSBpbXBsZW1lbnRpbmcgdGhlIHZhcmlvdXMgZ3JhcGhpY3MgZGV2aWNlcywgaW5jbHVkaW5nIFgxMSwgUERGLCBQb3N0U2NyaXB0LCBQTkcsIGV0Yy4NCg0KVGhlIGBnckRldmljZXNgIHBhY2thZ2UgY29udGFpbnMgdGhlIGZ1bmN0aW9uYWxpdHkgZm9yIHNlbmRpbmcgcGxvdHMgdG8gdmFyaW91cyBvdXRwdXQgZGV2aWNlcy4gVGhlIGBncmFwaGljc2AgcGFja2FnZSBjb250YWlucyB0aGUgY29kZSBmb3IgYWN0dWFsbHkgY29uc3RydWN0aW5nIGFuZCBhbm5vdGF0aW5nIHBsb3RzLg0KDQpJbiB0aGlzIG5vdGUsIHdlIGZvY3VzIG9uIHVzaW5nIHRoZSBiYXNlIHBsb3R0aW5nIHN5c3RlbSB0byBjcmVhdGUgZ3JhcGhpY3Mgb24gdGhlIHNjcmVlbiBkZXZpY2UuDQoNCg0KIyBXb3JraW5nIERhdGEgLWlyaXMNCg0KDQpgYGB7ciBlY2hvPUZBTFNFfQ0KRFQ6OmRhdGF0YWJsZShpcmlzLCBmaWxsQ29udGFpbmVyID0gRkFMU0UsIG9wdGlvbnMgPSBsaXN0KHBhZ2VMZW5ndGggPSAxMCkpDQpgYGANCg0KPGZvbnQgY29sb3IgPSAiZGFya3JlZCI+PGI+T2JqZWN0aXZlczwvYj48L2ZvbnQ+OiBJZGVudGlmeSBtZWFuaW5nZnVsIHBhdHRlcm5zIGFuZCBtYWtlIHZpc3VhbCByZXByZXNlbnRhdGlvbnMNCg0KKiBXaGF0IGluZm9ybWF0aW9uIGRvZXMgdGhlIGRhdGEgc2V0IGhhdmU/DQoqIEhvdyB0byBmaW5kIHRoZSBpbmZvcm1hdGlvbiBvZiBpbnRlcmVzdD8gDQogIC0gZGVzY3JpcHRpdmU/IA0KICAtIGFsZ29yaXRobS1iYXNlZD8gDQogIC0gbW9kZWwtYmFzZWQ/DQoqIFRvbyBtYW55IHZhcmlhYmxlcyBvciB0b28gbWFueSBkYXRhIHBvaW50cz8NCiogV2hhdCBmb3JtIG9mIHZpc3VhbGl6YXRpb24gdG8gdXNlPw0KKiBXaGF0IHZpc3VhbCBkZXNpZ24/IFNpbmdsZSBwYXR0ZXJuIG9yIG11bHRpcGxlIHBhdHRlcm5zPw0KDQoNCiMgQmFzZSBHcmFwaGljcw0KDQpCYXNlIGdyYXBoaWNzIGFyZSB1c2VkIG1vc3QgY29tbW9ubHkgYW5kIGFyZSBhIHZlcnkgcG93ZXJmdWwgc3lzdGVtIGZvciBjcmVhdGluZyBkYXRhIGdyYXBoaWNzLiBUaGVyZSBhcmUgdHdvIHBoYXNlcyB0byBjcmVhdGluZyBhIGJhc2UgcGxvdDoNCg0KKiBJbml0aWFsaXppbmcgYSBuZXcgcGxvdC4NCg0KKiBBbm5vdGF0aW5nIChhZGRpbmcgdG8pIGFuIGV4aXN0aW5nIHBsb3QuDQoNCkZvciBleGFtcGxlLCBjYWxsaW5nIHRoZSBiYXNlIHBsb3QgZnVuY3Rpb25zIGBwbG90KHgsIHkpYCBvciBgaGlzdCh4KWAgd2lsbCBsYXVuY2ggYSBncmFwaGljcyBkZXZpY2UgKGlmIG9uZSBpcyBub3QgYWxyZWFkeSBvcGVuKSBhbmQgZHJhdyBhIG5ldyBwbG90IG9uIHRoZSBkZXZpY2UuIFRoZSBiYXNlZCBwbG90IGZ1bmN0aW9uIGhhcyBtYW55IGFyZ3VtZW50cywgbGV0dGluZyB1cyBzZXQgdGhlIHRpdGxlLCB4LWF4aXMgbGFiZWwsIHktYXhpcyBsYWJlbCwgZXRjLg0KDQpUaGUgYmFzZSBncmFwaGljcyBzeXN0ZW0gaGFzIG1hbnkgZ2xvYmFsIHBhcmFtZXRlcnMgdGhhdCBjYW4gYmUgc2V0IGFuZCB0d2Vha2VkLiBUaGVzZSBwYXJhbWV0ZXJzIGFyZSBkb2N1bWVudGVkIGluIGA/cGFyYCBhbmQgYXJlIHVzZWQgdG8gY29udHJvbCB0aGUgZ2xvYmFsIGJlaGF2aW9yIG9mIHBsb3RzLCBzdWNoIGFzIHRoZSBtYXJnaW5zLCBheGlzIG9yaWVudGF0aW9uLCBhbmQgb3RoZXIgZGV0YWlscy4gDQoNCiMgU2ltcGxlIEJhc2UgR3JhcGhpY3MNCg0KVGhpcyBzZWN0aW9uIGV4cGxhaW5zIGhvdyB0byB1c2UgdGhlIGJhc2UgcGxvdHRpbmcgZnVuY3Rpb25zIHRvIG1ha2UgYmFzaWMgc3RhdGlzdGljYWwgZ3JhcGhpY3MuDQoNCiMjIEhpc3RvZ3JhbQ0KDQpIZXJlIGlzIGFuIGV4YW1wbGUgb2YgYSBzaW1wbGUgaGlzdG9ncmFtIG1hZGUgdXNpbmcgdGhlIGBoaXN0KClgIGZ1bmN0aW9uIGluIHRoZSBncmFwaGljcyBwYWNrYWdlLiBJZiB3ZSBydW4gdGhpcyBjb2RlIGFuZCBvdXIgZ3JhcGhpY3Mgd2luZG93IGlzIG5vdCBhbHJlYWR5IG9wZW4sIGl0IHNob3VsZCBvcGVuIG9uY2UgeW91IGNhbGwgdGhlIGBoaXN0KClgIGZ1bmN0aW9uLg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuY2FwPSJGaWd1cmUgMS4gU2VwYWwgd2lkdGggb2YgaXJpcyJ9DQojIyBEcmF3IGEgbmV3IHBsb3Qgb24gdGhlIHNjcmVlbiBkZXZpY2UNCmhpc3QoaXJpcyRTZXBhbC5MZW5ndGgsDQogICAgIHhsYWIgPSAiU2VwYWwgTGVuZ3RoIiwNCiAgICAgeWxhYiA9ICJDb3VudHMiLA0KICAgICBtYWluID0gIkZyZXF1ZW5jeSBkaXN0cmlidXRpb24gb2Ygc2VwYWwgbGVuZ3RoIikgDQpgYGANCg0KDQojIyBCb3hwbG90DQoNCkJveHBsb3RzIGNhbiBiZSBtYWRlIGluIFIgdXNpbmcgdGhlIGBib3hwbG90KClgIGZ1bmN0aW9uLCB3aGljaCB0YWtlcyBhcyBpdHMgZmlyc3QgYXJndW1lbnQgYSBmb3JtdWxhLiBUaGUgZm9ybXVsYSBoYXMgYSBmb3JtIG9mIGB5LWF4aXMgfiB4LWF4aXNgLiBBbnl0aW1lIHlvdSBzZWUgYSBgfmAgaW4gUiwgaXTigJlzIGEgZm9ybXVsYS4gSGVyZSwgd2UgYXJlIHBsb3R0aW5nIHRoZSBzZXBhbCBsZW5ndGggb2YgaXJpcyBmbG93ZXJzIGFuZCB0aGUgcmlnaHQtaGFuZCBzaWRlIG9mIGB+YCB0aGUgU3BlY2llcy4NCg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcuY2FwPSJCb3ggcGxvdCBvZiBzZXBhbCBsZW5ndGggYnkgc3BlY2llcyJ9DQpib3hwbG90KFNlcGFsLkxlbmd0aCB+IFNwZWNpZXMsIGRhdGEgPSBpcmlzLCB4bGFiID0gIlNwZWNpZXMiLCB5bGFiID0gIlNlcGFsIExlbmd0aCIpDQpgYGANCg0KRWFjaCBib3hwbG90IHNob3dzIHRoZSBtZWRpYW4sIDI1dGgsIGFuZCA3NXRoIHBlcmNlbnRpbGVzIG9mIHRoZSBkYXRhICh0aGUg4oCcYm944oCdKSwgYXMgd2VsbCBhcyArLy0gMS41IHRpbWVzIHRoZSBpbnRlcnF1YXJ0aWxlIHJhbmdlIChJUVIpIG9mIHRoZSBkYXRhICh0aGUg4oCcd2hpc2tlcnPigJ0pLiBBbnkgZGF0YSBwb2ludHMgYmV5b25kIDEuNSB0aW1lcyB0aGUgSVFSIG9mIHRoZSBkYXRhIGFyZSBpbmRpY2F0ZWQgc2VwYXJhdGVseSB3aXRoIGNpcmNsZXMuDQoNCldlIGNhbiBzZWUgdGhhdCBWaXJnaW5pY2EgaGFzIGFuIG91dGxpZXIuDQoNCiMjIFNjYXR0ZXJwbG90DQoNCkhlcmUgaXMgYSBzaW1wbGUgc2NhdHRlci1wbG90IG1hZGUgd2l0aCB0aGUgYHBsb3QoKWAgZnVuY3Rpb24uDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5jYXA9IlNjYXR0ZXIgcGxvdCBvZiBzZXBhbCBsZW5ndGggYW5kIHNlcGFsIHdpZHRoIn0NCnBsb3QoaXJpcyRTZXBhbC5MZW5ndGgsIGlyaXMkU2VwYWwuV2lkdGgsIA0KICAgICB4bGFiID0gInNlcGFsIGxlbmd0aCIsDQogICAgIHlsYWIgPSAic2VwYWwgd2lkdGgiLA0KICAgICBtYWluID0gIiAiKQ0KYGBgDQoNCkdlbmVyYWxseSwgdGhlIGBwbG90KClgIGZ1bmN0aW9uIHRha2VzIHR3byB2ZWN0b3JzIG9mIG51bWJlcnM6IG9uZSBmb3IgdGhlIHgtYXhpcyBjb29yZGluYXRlcyBhbmQgb25lIGZvciB0aGUgeS1heGlzIGNvb3JkaW5hdGVzLiBIb3dldmVyLCBgcGxvdCgpYCBpcyBhIGdlbmVyaWMgZnVuY3Rpb24gaW4gUiwgd2hpY2ggbWVhbnMgaXRzIGJlaGF2aW9yIGNhbiBjaGFuZ2UgZGVwZW5kaW5nIG9uIHdoYXQga2luZHMgb2YgZGF0YSBhcmUgcGFzc2VkIHRvIHRoZSBmdW5jdGlvbi4gDQoNCk9uZSB0aGluZyB0byBub3RlIGhlcmUgaXMgdGhhdCB3ZSBoYXZlIHByb3ZpZGVkIGxhYmVscyBmb3IgdGhlIHgtIGFuZCB0aGUgeS1heGlzIGFuZCB0aXRsZSBhcyB3ZWxsLiBJZiB0aGV5IHdlcmUgbm90IHNwZWNpZmllZCwgdGhlIHBsb3QgZnVuY3Rpb24gd2lsbCBwcm92aWRlIHRoaXMgaW5mb3JtYXRpb24gYXV0b21hdGljYWxseSB1c2luZyB0aGUgbmFtZXMgb2YgdGhlIHZhcmlhYmxlcy4NCg0KIyBTb21lIEltcG9ydGFudCBCYXNlIEdyYXBoaWNzIFBhcmFtZXRlcnMNCg0KTWFueSBiYXNlIHBsb3R0aW5nIGZ1bmN0aW9ucyBzaGFyZSBhIHNldCBvZiBnbG9iYWwgcGFyYW1ldGVycy4gSGVyZSBhcmUgYSBmZXcga2V5IG9uZXMuDQoNCiogYHBjaGA6IHRoZSBwbG90dGluZyBzeW1ib2wgKGRlZmF1bHQgaXMgYW4gb3BlbiBjaXJjbGUpLg0KKiBgbHR5YDogdGhlIGxpbmUgdHlwZSAoZGVmYXVsdCBpcyBhIHNvbGlkIGxpbmUpLCBjYW4gYmUgZGFzaGVkLCBkb3R0ZWQsIGV0Yy4NCiogYGx3ZGA6IHRoZSBsaW5lIHdpZHRoLCBzcGVjaWZpZWQgYXMgYW4gaW50ZWdlciBtdWx0aXBsZS4NCiogYGNvbGA6IHRoZSBwbG90dGluZyBjb2xvciwgc3BlY2lmaWVkIGFzIGEgbnVtYmVyLCBzdHJpbmcsIG9yIGhleCBjb2RlOyB0aGUgY29sb3JzKCkgZnVuY3Rpb24gZ2l2ZXMgeW91IGEgdmVjdG9yIG9mIGNvbG9ycyBieSBuYW1lLg0KKiBgeGxhYmA6IGNoYXJhY3RlciBzdHJpbmcgZm9yIHRoZSB4LWF4aXMgbGFiZWwuDQoqIGB5bGFiYDogY2hhcmFjdGVyIHN0cmluZyBmb3IgdGhlIHktYXhpcyBsYWJlbC4NCg0KVGhlIGBwYXIoKWAgZnVuY3Rpb24gaXMgdXNlZCB0byBzcGVjaWZ5IHRoZSBnbG9iYWwgZ3JhcGhpY3MgcGFyYW1ldGVycyB0aGF0IGFmZmVjdCBhbGwgcGxvdHMgaW4gYW4gUiBzZXNzaW9uLiBUaGVzZSBwYXJhbWV0ZXJzIGNhbiBiZSBvdmVycmlkZGVuIHdoZW4gdGhleSBhcmUgc3BlY2lmaWVkIGFzIGFyZ3VtZW50cyB0byBzcGVjaWZpYyBwbG90dGluZyBmdW5jdGlvbnMuDQoNCiogYGxhc2A6IHRoZSBvcmllbnRhdGlvbiBvZiB0aGUgYXhpcyBsYWJlbHMgb24gdGhlIHBsb3QuDQoqIGBiZ2A6IHRoZSBiYWNrZ3JvdW5kIGNvbG9yLg0KKiBgbWFyYDogdGhlIG1hcmdpbiBzaXplLg0KKiBgb21hYDogdGhlIG91dGVyIG1hcmdpbiBzaXplIChkZWZhdWx0IGlzIDAgZm9yIGFsbCBzaWRlcykuDQoqIGBtZnJvd2A6IG51bWJlciBvZiBwbG90cyBwZXIgcm93LCBjb2x1bW4gKHBsb3RzIGFyZSBmaWxsZWQgcm93LXdpc2UpLg0KKiBgbWZjb2xgOiBudW1iZXIgb2YgcGxvdHMgcGVyIHJvdywgY29sdW1uIChwbG90cyBhcmUgZmlsbGVkIGNvbHVtbi13aXNlKS4NCg0KDQpXZSBjYW4gc2VlIHRoZSBkZWZhdWx0IHZhbHVlcyBmb3IgZ2xvYmFsIGdyYXBoaWNzIHBhcmFtZXRlcnMgYnkgY2FsbGluZyB0aGUgYHBhcigpYCBmdW5jdGlvbiBhbmQgcGFzc2luZyB0aGUgbmFtZSBvZiB0aGUgcGFyYW1ldGVyIGluIHF1b3Rlcy4NCg0KYGBge3J9DQpwYXIoImx0eSIpDQpwYXIoImNvbCIpDQpwYXIoInBjaCIpDQpgYGANCg0KSGVyZSBhcmUgc29tZSBtb3JlIGRlZmF1bHQgdmFsdWVzIGZvciBnbG9iYWwgZ3JhcGhpY3MgcGFyYW1ldGVycy4NCg0KYGBge3J9DQpwYXIoImJnIikNCnBhcigibWFyIikNCnBhcigibWZyb3ciKQ0KYGBgDQoNCkZvciB0aGUgbW9zdCBwYXJ0LCB3ZSB1c3VhbGx5IGRvbuKAmXQgaGF2ZSB0byBtb2RpZnkgdGhlc2Ugd2hlbiBtYWtpbmcgcXVpY2sgcGxvdHMuIEhvd2V2ZXIsIHdlIG1pZ2h0IG5lZWQgdG8gdHdlYWsgdGhlbSBmb3IgZmluYWxpemluZyBmaW5pc2hlZCBwbG90cy4NCg0KDQojIEJhc2UgUGxvdHRpbmcgRnVuY3Rpb25zDQoNClRoZSBtb3N0IGJhc2ljIGJhc2UgcGxvdHRpbmcgZnVuY3Rpb24gaXMgYHBsb3QoKWAuIFRoZSBgcGxvdCgpYCBmdW5jdGlvbiBtYWtlcyBhIHNjYXR0ZXItcGxvdCwgb3Igb3RoZXIgdHlwZXMgb2YgcGxvdCBkZXBlbmRpbmcgb24gdGhlIGNsYXNzIG9mIHRoZSBvYmplY3QgYmVpbmcgcGxvdHRlZC4gQ2FsbGluZyBgcGxvdCgpYCB3aWxsIGRyYXcgYSBwbG90IG9uIHRoZSBzY3JlZW4gZGV2aWNlIChhbmQgb3BlbiB0aGUgc2NyZWVuIGRldmljZSBpZiBub3QgYWxyZWFkeSBvcGVuKS4gQWZ0ZXIgdGhhdCwgYW5ub3RhdGlvbiBmdW5jdGlvbnMgY2FuIGJlIGNhbGxlZCB0byBhZGQgdG8gdGhlIGFscmVhZHktbWFkZSBwbG90Lg0KDQpTb21lIGtleSBhbm5vdGF0aW9uIGZ1bmN0aW9ucyBhcmUNCg0KKiBgbGluZXMoKWA6IGFkZCBsaW5lcyB0byBhIHBsb3QsIGdpdmVuIGEgdmVjdG9yIG9mIHggdmFsdWVzIGFuZCBhIGNvcnJlc3BvbmRpbmcgdmVjdG9yIG9mIHkgdmFsdWVzIChvciBhIDItY29sdW1uIG1hdHJpeCk7IHRoaXMgZnVuY3Rpb24ganVzdCBjb25uZWN0cyB0aGUgZG90cw0KKiBgcG9pbnRzKClgOiBhZGQgcG9pbnRzIHRvIGEgcGxvdA0KKiBgdGV4dCgpYDogYWRkIHRleHQgbGFiZWxzIHRvIGEgcGxvdCB1c2luZyBzcGVjaWZpZWQgeCwgeSBjb29yZGluYXRlcw0KKiBgdGl0bGUoKWA6IGFkZCBhbm5vdGF0aW9ucyB0byB4LCB5LWF4aXMgbGFiZWxzLCB0aXRsZSwgc3VidGl0bGUsIG91dGVyIG1hcmdpbg0KKiBgbXRleHQoKWA6IGFkZCBhcmJpdHJhcnkgdGV4dCB0byB0aGUgbWFyZ2lucyAoaW5uZXIgb3Igb3V0ZXIpIG9mIHRoZSBwbG90DQoqIGBheGlzKClgOiBhZGRpbmcgYXhpcyB0aWNrcy9sYWJlbHMNCg0KSGVyZeKAmXMgYW4gZXhhbXBsZSBvZiBjcmVhdGluZyBhIGJhc2UgcGxvdCBhbmQgYWRkaW5nIHNvbWUgYW5ub3RhdGlvbi4gRmlyc3Qgd2UgbWFrZSB0aGUgcGxvdCB3aXRoIHRoZSBgcGxvdCgpYCBmdW5jdGlvbiBhbmQgdGhlbiBhZGQgYSB0aXRsZSB0byB0aGUgdG9wIG9mIHRoZSBwbG90IHdpdGggdGhlIGB0aXRsZSgpYCBmdW5jdGlvbi4NCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLmNhcD0iQmFzZSBwbG90IHdpdGggYW5ub3RhdGlvbjogSXJpcyJ9DQojIyBNYWtlIHRoZSBpbml0aWFsIHBsb3QNCnBsb3QoaXJpcyRTZXBhbC5MZW5ndGgsIGlyaXMkU2VwYWwuV2lkdGgpDQojIyBBZGQgYSB0aXRsZQ0KdGl0bGUobWFpbiA9ICJTZXBhbCBsZW5ndGggYW5kIHdpZHRoIG9mIGlyaXMiKSAgDQpgYGANCg0KDQoNCkhlcmUsIEkgc3RhcnQgd2l0aCB0aGUgc2FtZSBwbG90IGFzIGFib3ZlIChhbHRob3VnaCBJIGFkZCB0aGUgdGl0bGUgcmlnaHQgYXdheSB1c2luZyB0aGUgbWFpbiBhcmd1bWVudCB0byBgcGxvdCgpYCkgYW5kIHRoZW4gYW5ub3RhdGUgaXQgYnkgY29sb3JpbmcgYmx1ZSB0aGUgZGF0YSBwb2ludHMgY29ycmVzcG9uZGluZy4NCg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0Kc2VwYWwubGVuZ3RoID0gaXJpcyRTZXBhbC5MZW5ndGgNCnNlcGFsLndpZHRoID0gaXJpcyRTZXBhbC5XaWR0aA0Kc3BlY2llcyA9IGlyaXMkU3BlY2llcw0KIyMgaWRlbnRpZnlpbmcgdGhlIElEIG9mIFZpcmdpbmljYQ0KdmlyZ2luY2EuaWQgPSB3aGljaChzcGVjaWVzPT0idmlyZ2luaWNhIikgICMgdmFsdWUgYXJlIGNhc2Ugc2Vuc2l0aXZlIQ0KIyMgbWFraW5nIHNjYXR0ZXIgcGxvdA0KcGxvdChzZXBhbC5sZW5ndGgsIHNlcGFsLndpZHRoLCBtYWluID0gIlNlcGFsIGxlZ250aCB2cyBzZXBhbCB3aWR0aCIpDQojIw0KcG9pbnRzKHNlcGFsLmxlbmd0aFt2aXJnaW5jYS5pZF0sIHNlcGFsLndpZHRoW3ZpcmdpbmNhLmlkXSwgcGNoID0gMTksIGNvbCA9ICJyZWQiKQ0KYGBgDQoNClRoZSBmb2xsb3dpbmcgcGxvdCBjb2xvcnMgdGhlIGRhdGEgcG9pbnRzIHdpdGggZGlmZmVyZW50IGNvbG9ycyBiYXNlZCBvbiBzcGVjaWVzLiBgbGVnZW5kKClgIGZ1bmN0aW9uIGV4cGxhaW5zIHRoZSBtZWFuaW5nIG9mIHRoZSBkaWZmZXJlbnQgY29sb3JzIGluIHRoZSBwbG90Lg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0Kc2VwYWwubGVuZ3RoID0gaXJpcyRTZXBhbC5MZW5ndGgNCnNlcGFsLndpZHRoID0gaXJpcyRTZXBhbC5XaWR0aA0Kc3BlY2llcyA9IGlyaXMkU3BlY2llcw0KIyMgaWRlbnRpZnlpbmcgdGhlIElEIG9mIFZpcmdpbmljYQ0KdmlyZ2luY2EuaWQgPSB3aGljaChzcGVjaWVzPT0idmlyZ2luaWNhIikgICMgdmFsdWUgYXJlIGNhc2Ugc2Vuc2l0aXZlIQ0Kc2V0b3NhLmlkID0gd2hpY2goc3BlY2llcz09InNldG9zYSIpDQp2ZXJzaWNvbG9yLmlkID0gd2hpY2goc3BlY2llcz09InZlcnNpY29sb3IiKQ0KIyMgbWFraW5nIGFuIGVtcHR5IHBsb3Q6IHR5cGUgPSAibiIgPT0+IG5vIHBvaW50DQpwbG90KHNlcGFsLmxlbmd0aCwgc2VwYWwud2lkdGgsIG1haW4gPSAiU2VwYWwgbGVnbnRoIHZzIHNlcGFsIHdpZHRoIiwgdHlwZSA9ICJuIikNCiMjDQpwb2ludHMoc2VwYWwubGVuZ3RoW3ZpcmdpbmNhLmlkXSwgc2VwYWwud2lkdGhbdmlyZ2luY2EuaWRdLCBwY2ggPSAxOCwgY29sID0gInJlZCIpDQpwb2ludHMoc2VwYWwubGVuZ3RoW3NldG9zYS5pZF0sIHNlcGFsLndpZHRoW3NldG9zYS5pZF0sIHBjaCA9IDE5LCBjb2wgPSAiYmx1ZSIpDQpwb2ludHMoc2VwYWwubGVuZ3RoW3ZlcnNpY29sb3IuaWRdLCBzZXBhbC53aWR0aFt2ZXJzaWNvbG9yLmlkXSwgcGNoID0gMjAsIGNvbCA9ICJjeWFuIikNCmxlZ2VuZCgidG9wbGVmdCIsIGMoInZpcmdpbmljYSIsICJzZXRvc2EiLCAidmVyc2ljb2xvciIpLCANCiAgICAgICAgICAgICAgICAgIGNvbD1jKCJyZWQiLCAiYmx1ZSIsICJjeWFuIiksDQogICAgICAgICAgICAgICAgICBwY2g9YygxOCwgMTksIDIwKSkNCmBgYA0KDQoNCiMgIEJhc2UgUGxvdCB3aXRoIFJlZ3Jlc3Npb24gTGluZQ0KDQpJdOKAmXMgZmFpcmx5IGNvbW1vbiB0byBtYWtlIGEgc2NhdHRlcnBsb3QgYW5kIHRoZW4gd2FudCB0byBkcmF3IGEgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIGxpbmUgdGhyb3VnaCB0aGUgZGF0YS4gVGhpcyBjYW4gYmUgZG9uZSB3aXRoIHRoZSBgYWJsaW5lKClgIGZ1bmN0aW9uLg0KDQpCZWxvdywgd2UgZmlyc3QgbWFrZSB0aGUgcGxvdCAoYXMgYWJvdmUpLiBUaGVuIHdlIGZpdCBhIHNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCB1c2luZyB0aGUgYGxtKClgIGZ1bmN0aW9uLiBIZXJlLCB3ZSB0cnkgdG8gbW9kZWwgT3pvbmUgYXMgYSBmdW5jdGlvbiBvZiBXaW5kLiBUaGVuIHdlIHRha2UgdGhlIG91dHB1dCBvZiBgbG0oKWAgYW5kIHBhc3MgaXQgdG8gdGhlIGBhYmxpbmUoKWAgZnVuY3Rpb24gd2hpY2ggYXV0b21hdGljYWxseSB0YWtlcyB0aGUgaW5mb3JtYXRpb24gZnJvbSB0aGUgbW9kZWwgb2JqZWN0IGFuZCBjYWxjdWxhdGVzIHRoZSBjb3JyZXNwb25kaW5nIHJlZ3Jlc3Npb24gbGluZS4NCg0KTm90ZSB0aGF0IGluIHRoZSBjYWxsIHRvIGBwbG90KClgIGJlbG93LCB3ZSBzZXQgYHBjaCA9IDIwYCB0byBjaGFuZ2UgdGhlIHBsb3R0aW5nIHN5bWJvbCB0byBhIGZpbGxlZCBjaXJjbGUuDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcid9DQpzZXBhbC5sZW5ndGggPSBpcmlzJFNlcGFsLkxlbmd0aA0Kc2VwYWwud2lkdGggPSBpcmlzJFNlcGFsLldpZHRoDQojIyBtYWtpbmcgYSBwbG90IA0KcGxvdChzZXBhbC5sZW5ndGgsIHNlcGFsLndpZHRoLCBtYWluID0gIlNlcGFsIGxlZ250aCB2cyBzZXBhbCB3aWR0aCIsIHBjaCA9IDIxKQ0KIyMNCiBtb2RlbCA8LSBsbSggc2VwYWwud2lkdGggfiBzZXBhbC5sZW5ndGgpDQogIyMgRHJhdyByZWdyZXNzaW9uIGxpbmUgb24gcGxvdA0KIGFibGluZShtb2RlbCwgbHdkID0gMiwgY29sID0gInJlZCIpDQpgYGANCg0KIyBNdWx0aXBsZSBCYXNlIFBsb3RzDQoNCk1ha2luZyBtdWx0aXBsZSBwbG90cyBzaWRlIGJ5IHNpZGUgaXMgYSB1c2VmdWwgd2F5IHRvIHZpc3VhbGl6ZSBtYW55IHJlbGF0aW9uc2hpcHMgYmV0d2VlbiB2YXJpYWJsZXMgd2l0aCBzdGF0aWMgMi1EIHBsb3RzLiBPZnRlbiB0aGUgcmVwZXRpdGlvbiBvZiBkYXRhIGFjcm9zcyBhIHNpbmdsZSBwbG90IHdpbmRvdyBjYW4gYmUgYSB1c2VmdWwgd2F5IHRvIGlkZW50aWZ5IHBhdHRlcm5zIGluIHRoZSBkYXRhLiBJbiBvcmRlciB0byBkbyB0aGlzLCB0aGUgYG1mcm93YCBhbmQgYG1mY29sYCBwYXJhbWV0ZXJzIHNldCBieSB0aGUgYHBhcigpYCBmdW5jdGlvbiBhcmUgY3JpdGljYWwuDQoNCkJvdGggdGhlIGBtZnJvd2AgYW5kIGBtZmNvbGAgcGFyYW1ldGVycyB0YWtlIHR3byBudW1iZXJzOiB0aGUgbnVtYmVyIG9mIHJvd3Mgb2YgcGxvdHMgZm9sbG93ZWQgYnkgdGhlIG51bWJlciBvZiBjb2x1bW5zLiBUaGUgbXVsdGlwbGUgcGxvdHMgd2lsbCBiZSBhcnJhbmdlZCBpbiBhIG1hdHJpeC1saWtlIHBhdHRlcm4uIFRoZSBvbmx5IGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgdHdvIHBhcmFtZXRlcnMgaXMgdGhhdCBpZiBgbWZyb3dgIGlzIHNldCwgdGhlbiB0aGUgcGxvdHMgd2lsbCBiZSBkcmF3biByb3ctd2lzZTsgaWYgYG1mY29sYCBpcyBzZXQsIHRoZSBwbG90cyB3aWxsIGJlIGRyYXduIGNvbHVtbi13aXNlLg0KDQpJbiB0aGUgZXhhbXBsZSBiZWxvdywgd2UgbWFrZSB0d28gcGxvdHM6IHNlcGFsIGxlbmd0aCB2cyBzZXBhbCB3aWR0aCBhbmQgcGV0YWwgbGVuZ3RoIHZzIHBldGFsIHdpZHRoLiBXZSBzZXQgYHBhcihtZnJvdyA9IGMoMSwgMikpYCwgd2hpY2ggaW5kaWNhdGVzIHRoYXQgd2UgaGF2ZSBvbmUgcm93IG9mIHBsb3RzIGFuZCB0d28gY29sdW1ucyBvZiBwbG90cy4NCg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KcGFyKG1mcm93ID0gYygxLCAyKSkNCnNlcGFsLmxlbmd0aCA9IGlyaXMkU2VwYWwuTGVuZ3RoDQpzZXBhbC53aWR0aCA9IGlyaXMkU2VwYWwuV2lkdGgNCnBldGFsLmxlbmd0aCA9IGlyaXMkUGV0YWwuTGVuZ3RoDQpwZXRhbC53aWR0aCA9IGlyaXMkUGV0YWwuV2lkdGgNCiMjIG1ha2luZyBhIHBsb3QgDQpwbG90KHNlcGFsLmxlbmd0aCwgc2VwYWwud2lkdGgsIG1haW4gPSAiU2VwYWwgbGVnbnRoIHZzIHNlcGFsIHdpZHRoIiwgcGNoID0gMjApDQpwbG90KHBldGFsLmxlbmd0aCwgcGV0YWwud2lkdGgsIG1haW4gPSAiUGV0YWwgbGVnbnRoIHZzIHBldGFsIHdpZHRoIiwgcGNoID0gMjEpDQpgYGANCg0KVGhlIGV4YW1wbGUgYmVsb3cgY3JlYXRlcyB0aHJlZSBwbG90cyBpbiBhIHJvdyBieSBzZXR0aW5nIGBwYXIobWZyb3cgPSBjKDEsIDMpKWAuIEhlcmUgd2UgYWxzbyBjaGFuZ2UgdGhlIHBsb3QgbWFyZ2lucyB3aXRoIHRoZSBtYXIgcGFyYW1ldGVyLiBUaGUgdmFyaW91cyBtYXJnaW4gcGFyYW1ldGVycywgbGlrZSBgbWFyYCwgYXJlIHNwZWNpZmllZCBieSBzZXR0aW5nIGEgdmFsdWUgZm9yIGVhY2ggc2lkZSBvZiB0aGUgcGxvdC4gYFNpZGUgMWAgaXMgdGhlIGJvdHRvbSBvZiB0aGUgcGxvdCwgYHNpZGUgMmAgaXMgdGhlIGxlZnQtaGFuZCBzaWRlLCBgc2lkZSAzYCBpcyB0aGUgdG9wLCBhbmQgYHNpZGUgNGAgaXMgdGhlIHJpZ2h0LWhhbmQgc2lkZS4gDQoNCkluIHRoZSBleGFtcGxlIGJlbG93IHdlIGFsc28gbW9kaWZ5IHRoZSBvdXRlciBtYXJnaW4gdmlhIHRoZSBgb21hYCBwYXJhbWV0ZXIgdG8gY3JlYXRlIGEgbGl0dGxlIG1vcmUgc3BhY2UgZm9yIHRoZSBwbG90cyBhbmQgdG8gcGxhY2UgdGhlbSBjbG9zZXIgdG9nZXRoZXIuDQoNCmBgYHtyLCBmaWcuYWxpZ249J2NlbnRlcid9DQojIGxheW91dCBvZiB0aGUgcGxvdA0KcGFyKG1mcm93ID0gYygxLCAzKSwgDQogICAgbWFyID0gYyg0LCA0LCAyLCAxKSwgDQogICAgb21hID0gYygwLCAwLCAyLCAwKSkNCiMgZXh0cmFjdCB2YXJpYWJsZXMgZnJvbSB0aGUgZGF0YSBmcmFtZQ0Kc2VwYWwubGVuZ3RoID0gaXJpcyRTZXBhbC5MZW5ndGgNCnNlcGFsLndpZHRoID0gaXJpcyRTZXBhbC5XaWR0aA0Kc3BlY2llcyA9IGlyaXMkU3BlY2llcw0KIyMgaWRlbnRpZnlpbmcgdGhlIElEIG9mIFZpcmdpbmljYQ0KdmlyZ2luY2EuaWQgPSB3aGljaChzcGVjaWVzPT0idmlyZ2luaWNhIikgICMgdmFsdWUgYXJlIGNhc2Ugc2Vuc2l0aXZlIQ0Kc2V0b3NhLmlkID0gd2hpY2goc3BlY2llcz09InNldG9zYSIpDQp2ZXJzaWNvbG9yLmlkID0gd2hpY2goc3BlY2llcz09InZlcnNpY29sb3IiKQ0KIyMgbWFraW5nIHRocmVlIHBsb3RzDQpwbG90KHNlcGFsLmxlbmd0aFt2aXJnaW5jYS5pZF0sIHNlcGFsLndpZHRoW3ZpcmdpbmNhLmlkXSwgbWFpbiA9ICJ2aXJnaW5jYSIpDQpwbG90KHNlcGFsLmxlbmd0aFtzZXRvc2EuaWRdLCBzZXBhbC53aWR0aFtzZXRvc2EuaWRdLCBtYWluID0gInNldG9zYSIpDQpwbG90KHNlcGFsLmxlbmd0aFt2ZXJzaWNvbG9yLmlkXSwgc2VwYWwud2lkdGhbdmVyc2ljb2xvci5pZF0sIG1haW4gPSAidmVyc2ljb2xvciIpDQojIw0KbXRleHQoIlNlcGFsIGxlbmd0aCB2cyB3aWR0aDogSXJpcyIsIG91dGVyID0gVFJVRSkNCmBgYA0KDQpJbiB0aGUgYWJvdmUgZXhhbXBsZSwgdGhlIGBtdGV4dCgpYCBmdW5jdGlvbiB3YXMgdXNlZCB0byBjcmVhdGUgYW4gb3ZlcmFsbCB0aXRsZSBmb3IgdGhlIHBhbmVsIG9mIHBsb3RzLiBIZW5jZSwgZWFjaCBpbmRpdmlkdWFsIHBsb3QgaGFzIGEgdGl0bGUsIHdoaWxlIHRoZSBvdmVyYWxsIHNldCBvZiBwbG90cyBhbHNvIGhhcyBhIHN1bW1hcnkgdGl0bGUuIFRoZSBgbXRleHQoKWAgZnVuY3Rpb24gaXMgaW1wb3J0YW50IGZvciBhZGRpbmcgdGV4dCBhbm5vdGF0aW9ucyB0aGF0IGFyZW7igJl0IHNwZWNpZmljIHRvIGEgc2luZ2xlIHBsb3QuDQoNCk5vdGljZSB0aGF0IHRoZSBzY2FsZXMgb2YgdGhlIHZlcnRpY2FsIGF4ZXMgb2YgdGhlIHRocmVlIHBsb3RzIGFyZSBub3QgdGhlIHNhbWUuIFRvIG1pc2xlYWRpbmcgdmlzdWFsIGNvbXBhcmlzb24sIHdlIHNob3VsZCBtYWtlIHRoZSBzY2FsZXMgb2YgYm90aCBheGVzIHRoZSBzYW1lLg0KDQpgYGB7ciwgZmlnLmFsaWduPSdjZW50ZXInfQ0KIyBsYXlvdXQgb2YgdGhlIHBsb3QNCnBhcihtZnJvdyA9IGMoMSwgMyksIA0KICAgIG1hciA9IGMoNCwgNCwgMiwgMSksIA0KICAgIG9tYSA9IGMoMCwgMCwgMiwgMCkpDQojIGV4dHJhY3QgdmFyaWFibGVzIGZyb20gdGhlIGRhdGEgZnJhbWUNCnNlcGFsLmxlbmd0aCA9IGlyaXMkU2VwYWwuTGVuZ3RoDQpzZXBhbC53aWR0aCA9IGlyaXMkU2VwYWwuV2lkdGgNCnNwZWNpZXMgPSBpcmlzJFNwZWNpZXMNCiMjIGlkZW50aWZ5aW5nIHRoZSBJRCBvZiBWaXJnaW5pY2ENCnZpcmdpbmNhLmlkID0gd2hpY2goc3BlY2llcz09InZpcmdpbmljYSIpICAjIHZhbHVlIGFyZSBjYXNlIHNlbnNpdGl2ZSENCnNldG9zYS5pZCA9IHdoaWNoKHNwZWNpZXM9PSJzZXRvc2EiKQ0KdmVyc2ljb2xvci5pZCA9IHdoaWNoKHNwZWNpZXM9PSJ2ZXJzaWNvbG9yIikNCiMjIG1ha2luZyB0aHJlZSBwbG90cw0KcGxvdChzZXBhbC5sZW5ndGhbdmlyZ2luY2EuaWRdLCBzZXBhbC53aWR0aFt2aXJnaW5jYS5pZF0sIHhsaW09Yyg0LDgpLCB5bGltPWMoMiwgNC41KSwgbWFpbiA9ICJ2aXJnaW5jYSIpDQpwbG90KHNlcGFsLmxlbmd0aFtzZXRvc2EuaWRdLCBzZXBhbC53aWR0aFtzZXRvc2EuaWRdLCB4bGltPWMoNCw4KSwgeWxpbT1jKDIsIDQuNSksbWFpbiA9ICJzZXRvc2EiKQ0KcGxvdChzZXBhbC5sZW5ndGhbdmVyc2ljb2xvci5pZF0sIHNlcGFsLndpZHRoW3ZlcnNpY29sb3IuaWRdLCB4bGltPWMoNCw4KSwgeWxpbT1jKDIsIDQuNSksbWFpbiA9ICJ2ZXJzaWNvbG9yIikNCiMjDQptdGV4dCgiU2VwYWwgbGVuZ3RoIHZzIHdpZHRoOiBJcmlzIiwgb3V0ZXIgPSBUUlVFKQ0KYGBgDQoNCldlIG5vdyBjYW4gdmlzdWFsaXplIHRoZSBtZWFuIHNlcGFsIHdpZHRoIGFuZCBzZXBhbCBsZW5ndGggYW1vbmcgdGhlIHNwZWNpZXMuDQoNCiMgQ29udHJvbGxpbmcgUG9pbnQgU2l6ZSBhbmQgVHJhbnNwYXJlbmN5DQoNCndlIHVzZSBgY2V4PWAgYW5kIGBhbHBoYT1gIHRvIGNvbnRyb2wgdGhlIHBvaW50IHNpemUgYWNjb3JkaW5nIHRvIHRoZSB2YWx1ZSBvZiBhIHZhcmlhYmxlIGFuZCB0aGUgbGV2ZWwgb2YgdHJhbnNwYXJlbmN5IG9mIHRoZSBwb2ludC4NCg0KYGBge3IsIGZpZy5hbGlnbj0nY2VudGVyJ30NCnNlcGFsLmxlbmd0aCA9IGlyaXMkU2VwYWwuTGVuZ3RoDQpzZXBhbC53aWR0aCA9IGlyaXMkU2VwYWwuV2lkdGgNCnNpemUucGV0YWwgPSBpcmlzJFBldGFsLldpZHRoDQpzcGVjaWVzID0gaXJpcyRTcGVjaWVzDQojIyBpZGVudGlmeWluZyB0aGUgSUQgb2YgVmlyZ2luaWNhDQp2aXJnaW5jYS5pZCA9IHdoaWNoKHNwZWNpZXM9PSJ2aXJnaW5pY2EiKSAgIyB2YWx1ZSBhcmUgY2FzZSBzZW5zaXRpdmUhDQpzZXRvc2EuaWQgPSB3aGljaChzcGVjaWVzPT0ic2V0b3NhIikNCnZlcnNpY29sb3IuaWQgPSB3aGljaChzcGVjaWVzPT0idmVyc2ljb2xvciIpDQojIyBjb2xvciBjb2RlDQpjb2wuY29kZSA9IGMoYWxwaGEoInJlZCIsMC41KSxhbHBoYSgiYmx1ZSIsMC41KSxhbHBoYSgiY3lhbiIsMC41KSkNCiMjIG1ha2luZyBhbiBlbXB0eSBwbG90OiB0eXBlID0gIm4iID09PiBubyBwb2ludA0KcGxvdChzZXBhbC5sZW5ndGgsIHNlcGFsLndpZHRoLCBtYWluID0gIlNlcGFsIGxlZ250aCB2cyBzZXBhbCB3aWR0aCIsIHR5cGUgPSAibiIpDQojIyBjaGFuZ2UgdGhlIHBvaW50IHNpemUgYmFzZWQgb24gdGhlaXIgYXZlcmFnZSBvZiBzZXBhbCBsZW5ndGggYW5kIHdpZHRoDQpwb2ludHMoc2VwYWwubGVuZ3RoW3ZpcmdpbmNhLmlkXSwgc2VwYWwud2lkdGhbdmlyZ2luY2EuaWRdLCANCiAgICAgICBwY2ggPSAxOSwgY29sID0gY29sLmNvZGVbMV0sIGNleCA9IHNpemUucGV0YWxbdmlyZ2luY2EuaWRdKQ0KcG9pbnRzKHNlcGFsLmxlbmd0aFtzZXRvc2EuaWRdLCBzZXBhbC53aWR0aFtzZXRvc2EuaWRdLCANCiAgICAgICBwY2ggPSAxOSwgY29sID0gY29sLmNvZGVbMl0sIGNleCA9IHNpemUucGV0YWxbc2V0b3NhLmlkXSkNCnBvaW50cyhzZXBhbC5sZW5ndGhbdmVyc2ljb2xvci5pZF0sIHNlcGFsLndpZHRoW3ZlcnNpY29sb3IuaWRdLCANCiAgICAgICBwY2ggPSAxOSwgY29sID0gY29sLmNvZGVbM10sIGNleCA9IHNpemUucGV0YWxbdmVyc2ljb2xvci5pZF0pDQpsZWdlbmQoInRvcGxlZnQiLCBjKCJ2aXJnaW5pY2EiLCAic2V0b3NhIiwgInZlcnNpY29sb3IiKSwgDQogICAgICAgICAgICAgICAgICBjb2w9Y29sLmNvZGUsDQogICAgICAgICAgICAgICAgICBwY2g9YygxOSwgMTksIDE5KSkNCmBgYA0KDQojIEFubm90YXRpb25zDQoNCldlIGFkZCBhbm5vdGF0aW9ucyB0byB0aGUgYmFzZSBSIHBsb3QuIFRoZSBhbm5vdGF0aW9ucyBjb3VsZCBiZSBwbGFpbiB0ZXh0cywgaW1hZ2VzLCBhbmQgbWF0aGVtYXRpY2FsIGV4cHJlc3Npb25zLg0KDQpgYGB7cn0NCg0KaXJpcy5pbWcgPC0gImh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL1NUQTU1M1ZJWi93MDMvaW1nL2lyaXMuanBlZyINCm15LmlyaXMgPC0gcmVhZEpQRUcoZ2V0VVJMQ29udGVudChpcmlzLmltZykpDQpyYXN0ZXIuaXJpcyA8LSBhcy5yYXN0ZXIobXkuaXJpcykgDQojIFVzZSB0aGUgY29kZSBpbiB0aGUgcHJlY2lvdXMgc2VjdGlvbg0Kc2VwYWwubGVuZ3RoID0gaXJpcyRTZXBhbC5MZW5ndGgNCnNlcGFsLndpZHRoID0gaXJpcyRTZXBhbC5XaWR0aA0Kc2l6ZS5wZXRhbCA9IGlyaXMkUGV0YWwuV2lkdGgNCnNwZWNpZXMgPSBpcmlzJFNwZWNpZXMNCiMjIGlkZW50aWZ5aW5nIHRoZSBJRCBvZiBWaXJnaW5pY2ENCnZpcmdpbmNhLmlkID0gd2hpY2goc3BlY2llcz09InZpcmdpbmljYSIpICAjIHZhbHVlIGFyZSBjYXNlIHNlbnNpdGl2ZSENCnNldG9zYS5pZCA9IHdoaWNoKHNwZWNpZXM9PSJzZXRvc2EiKQ0KdmVyc2ljb2xvci5pZCA9IHdoaWNoKHNwZWNpZXM9PSJ2ZXJzaWNvbG9yIikNCiMjIGNvbG9yIGNvZGUNCiMgIGNvbC5jb2RlID0gYyhhbHBoYSgicmVkIiwwLjUpLGFscGhhKCJibHVlIiwwLjUpLGFscGhhKCJjeWFuIiwwLjUpKQ0KIyMgbWFraW5nIGFuIGVtcHR5IHBsb3Q6IHR5cGUgPSAibiIgPT0+IG5vIHBvaW50DQpwbG90KHNlcGFsLmxlbmd0aCwgc2VwYWwud2lkdGgsIG1haW4gPSAiU2VwYWwgbGVnbnRoIHZzIHNlcGFsIHdpZHRoIiwgdHlwZSA9ICJuIikNCiMjIGNoYW5nZSB0aGUgcG9pbnQgc2l6ZSBiYXNlZCBvbiB0aGVpciBhdmVyYWdlIG9mIHNlcGFsIGxlbmd0aCBhbmQgd2lkdGgNCnBvaW50cyhzZXBhbC5sZW5ndGhbdmlyZ2luY2EuaWRdLCBzZXBhbC53aWR0aFt2aXJnaW5jYS5pZF0sIA0KICAgICAgIHBjaCA9IDE5LCBjb2wgPSAicHVycGxlIiwgY2V4ID0gc2l6ZS5wZXRhbFt2aXJnaW5jYS5pZF0sIGFscGhhID0gMC42KQ0KcG9pbnRzKHNlcGFsLmxlbmd0aFtzZXRvc2EuaWRdLCBzZXBhbC53aWR0aFtzZXRvc2EuaWRdLCANCiAgICAgICBwY2ggPSAxOSwgY29sID0gIm5hdnkiLCBjZXggPSBzaXplLnBldGFsW3NldG9zYS5pZF0sIGFscGhhID0gMC42KQ0KcG9pbnRzKHNlcGFsLmxlbmd0aFt2ZXJzaWNvbG9yLmlkXSwgc2VwYWwud2lkdGhbdmVyc2ljb2xvci5pZF0sIA0KICAgICAgIHBjaCA9IDE5LCBjb2wgPSAiY3lhbiIsIGNleCA9IHNpemUucGV0YWxbdmVyc2ljb2xvci5pZF0sIGFscGhhID0gMC42KQ0KbGVnZW5kKCJ0b3BsZWZ0IiwgYygidmlyZ2luaWNhIiwgInNldG9zYSIsICJ2ZXJzaWNvbG9yIiksIA0KICAgICAgICAgICAgICAgICAgY29sPWMoInB1cnBsZSIsICJuYXZ5IiwgImN5YW4iKSwNCiAgICAgICAgICAgICAgICAgIHBjaD1jKDE5LCAxOSwgMTkpKQ0KIyMgdmFyaW91cyBhbm5vdGF0aW9ucw0KI3NwZWNpZnkgdGhlIHBvc2l0aW9uIG9mIHRoZSBpbWFnZSB0aHJvdWdoIGJvdHRvbS1sZWZ0IGFuZCB0b3AtcmlnaHQgY29vcmRzDQpyYXN0ZXJJbWFnZShyYXN0ZXIuaXJpcyw2LDMuNSw3LDQuNCkNCnRleHQoNy4yNSwgMi4yNSwgIlRoZSBzaXplIGlzIHByb3BvcnRpb25hbCB0byBwZXRhbCBsZW5ndGgiLCBjb2wgPSAicHVycGxlIiwgY2V4ID0gMC41KQ0KYGBgDQoNCg0KIyBXaGF0IFN0b3J5IFRvIFRlbGw/DQoNCldlIGhhdmUgZGlzY3Vzc2VkIHZhcmlvdXMgZ3JhcGhpYyBmdW5jdGlvbnMgYW5kIHRlY2huaXF1ZXMgaW4gYmFzZSBSIHRvIGNyZWF0ZSBncmFwaGljIHJlcHJlc2VudGF0aW9ucyBvZiBkYXRhLiBUbyBjb25jbHVkZSB0aGlzIG5vdGUsIGxldCdzIGxvb2sgYXQgd2hhdCBpbmZvcm1hdGlvbiBnZW5lcmFsIGF1ZGllbmNlIG1pZ2h0IGJlIGludGVyZXN0ZWQgaW4uDQoNCiMjIFJlbGF0aW9uc2hpcCBiZXR3ZWVuIHZhcmlhYmxlcz8NCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD03LCBmaWcuY2FwPSJQYWlyLXdpc2UgU2NhdHRlciBQbG90IG9mIFNpemVzIG9mIElyaXMifQ0KcGFpcnMoaXJpc1ssIC01XSkNCmBgYA0KDQojIyBEaXN0cmlidXRpb24gb2YgdmFyaWFibGVzPw0KDQoNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD03LCBmaWcuY2FwPSJDb21wYXJpc29ucyBvZiBkaXN0cmlidXRpb25zIn0NCiMjIERhdGEgUGFydGl0aW9uDQpTZXBhbC5MLnNldCA9IGlyaXMkU2VwYWwuTGVuZ3RoW3doaWNoKGlyaXMkU3BlY2llcz09InNldG9zYSIpXQ0KU2VwYWwuTC52ZXIgPSBpcmlzJFNlcGFsLkxlbmd0aFt3aGljaChpcmlzJFNwZWNpZXM9PSJ2ZXJzaWNvbG9yIildDQpTZXBhbC5MLnZpciA9IGlyaXMkU2VwYWwuTGVuZ3RoW3doaWNoKGlyaXMkU3BlY2llcz09InZpcmdpbmljYSIpXQ0KIw0KU2VwYWwuVy5zZXQgPSBpcmlzJFNlcGFsLldpZHRoW3doaWNoKGlyaXMkU3BlY2llcz09InNldG9zYSIpXQ0KU2VwYWwuVy52ZXIgPSBpcmlzJFNlcGFsLldpZHRoW3doaWNoKGlyaXMkU3BlY2llcz09InZlcnNpY29sb3IiKV0NClNlcGFsLlcudmlyID0gaXJpcyRTZXBhbC5XaWR0aFt3aGljaChpcmlzJFNwZWNpZXM9PSJ2aXJnaW5pY2EiKV0NCiMNClBldGFsLkwuc2V0ID0gaXJpcyRQZXRhbC5MZW5ndGhbd2hpY2goaXJpcyRTcGVjaWVzPT0ic2V0b3NhIildDQpQZXRhbC5MLnZlciA9IGlyaXMkUGV0YWwuTGVuZ3RoW3doaWNoKGlyaXMkU3BlY2llcz09InZlcnNpY29sb3IiKV0NClBldGFsLkwudmlyID0gaXJpcyRQZXRhbC5MZW5ndGhbd2hpY2goaXJpcyRTcGVjaWVzPT0idmlyZ2luaWNhIildDQojDQpQZXRhbC5XLnNldCA9IGlyaXMkUGV0YWwuV2lkdGhbd2hpY2goaXJpcyRTcGVjaWVzPT0ic2V0b3NhIildDQpQZXRhbC5XLnZlciA9IGlyaXMkUGV0YWwuV2lkdGhbd2hpY2goaXJpcyRTcGVjaWVzPT0idmVyc2ljb2xvciIpXQ0KUGV0YWwuVy52aXIgPSBpcmlzJFBldGFsLldpZHRoW3doaWNoKGlyaXMkU3BlY2llcz09InZpcmdpbmljYSIpXQ0KIyMjDQpwYXIobWZyb3c9YygyLDIpKQ0KIyMjDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyAgIHBsb3QgIzE6IA0KcGxvdChkZW5zaXR5KFNlcGFsLkwuc2V0KSwgY29sPSJicm93bjQiLCBsdHk9MSwgbHdkPTIsICB4bGltPWMoMyw5KSwgeWxpbT1jKDAsMS44KSwgeGxhYj0iU2VwYWwgTGVuZ3RoIiwgbWFpbj0iRGlzdHJpYnV0aW9ucyBTZXBhbCBMZW5ndGgiKQ0KbGluZXMoZGVuc2l0eShTZXBhbC5MLnZlciksIGNvbD0iYmx1ZSIsIGx0eT0xLCBsd2Q9MikNCmxpbmVzKGRlbnNpdHkoU2VwYWwuTC52aXIpLCBjb2w9ImRhcmtjeWFuIiwgbHR5PTEsIGx3ZD0yKQ0KbGVnZW5kKCJ0b3ByaWdodCIsIGMoInNldG9zYSIsICJ2ZXJzaWNvbG9yIiwidmlyZ2luaWNhIiksDQogICAgICAgY29sPWMoImJyb3duNCIsImJsdWUiLCJkYXJrY3lhbiIpLCBsdHk9YygxLDEsMSksDQogICAgICAgbHdkPWMoMSwxLDEpLCBidHk9Im4iLCBjZXg9MC43KQ0KIyMNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjICAgcGxvdCAjMTogDQpwbG90KGRlbnNpdHkoU2VwYWwuVy5zZXQpLCBjb2w9ImJyb3duNCIsIGx0eT0xLCBsd2Q9MiwgIHhsaW09YygxLDUpLCB5bGltPWMoMCwyKSwgeGxhYj0iU2VwYWwgV2lkdGgiLCBtYWluPSJEaXN0cmlidXRpb25zIFNlcGFsIFdpZHRoIikNCmxpbmVzKGRlbnNpdHkoU2VwYWwuVy52ZXIpLCBjb2w9ImJsdWUiLCBsdHk9MSwgbHdkPTIpDQpsaW5lcyhkZW5zaXR5KFNlcGFsLlcudmlyKSwgY29sPSJkYXJrY3lhbiIsIGx0eT0xLCBsd2Q9MikNCmxlZ2VuZCgidG9wcmlnaHQiLCBjKCJzZXRvc2EiLCAidmVyc2ljb2xvciIsInZpcmdpbmljYSIpLA0KICAgICAgIGNvbD1jKCJicm93bjQiLCJibHVlIiwiZGFya2N5YW4iKSwgbHR5PWMoMSwxLDEpLA0KICAgICAgIGx3ZD1jKDEsMSwxKSwgYnR5PSJuIiwgY2V4PTAuNykNCiMjDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyAgIHBsb3QgIzE6IA0KcGxvdChkZW5zaXR5KFBldGFsLkwuc2V0KSwgY29sPSJicm93bjQiLCBsdHk9MSwgbHdkPTIsICB4bGltPWMoMSw4KSwgeWxpbT1jKDAsMi44KSwgeGxhYj0iUGV0YWwgTGVuZ3RoIiwgbWFpbj0iRGlzdHJpYnV0aW9ucyBQZXRhbCBMZW5ndGgiKQ0KbGluZXMoZGVuc2l0eShQZXRhbC5MLnZlciksIGNvbD0iYmx1ZSIsIGx0eT0xLCBsd2Q9MikNCmxpbmVzKGRlbnNpdHkoUGV0YWwuTC52aXIpLCBjb2w9ImRhcmtjeWFuIiwgbHR5PTEsIGx3ZD0yKQ0KbGVnZW5kKCJ0b3ByaWdodCIsIGMoInNldG9zYSIsICJ2ZXJzaWNvbG9yIiwidmlyZ2luaWNhIiksDQogICAgICAgY29sPWMoImJyb3duNCIsImJsdWUiLCJkYXJrY3lhbiIpLCBsdHk9YygxLDEsMSksDQogICAgICAgbHdkPWMoMSwxLDEpLCBidHk9Im4iLCBjZXg9MC43KQ0KIyMNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjICAgcGxvdCAjMTogDQpwbG90KGRlbnNpdHkoUGV0YWwuVy5zZXQpLCBjb2w9ImJyb3duNCIsIGx0eT0xLCBsd2Q9MiwgIHhsaW09YygwLDMpLCB5bGltPWMoMCw4KSwgeGxhYj0iUGV0YWwgV2lkdGgiLCBtYWluPSJEaXN0cmlidXRpb25zIFBldGFsIFdpZHRoIikNCmxpbmVzKGRlbnNpdHkoUGV0YWwuVy52ZXIpLCBjb2w9ImJsdWUiLCBsdHk9MSwgbHdkPTIpDQpsaW5lcyhkZW5zaXR5KFBldGFsLlcudmlyKSwgY29sPSJkYXJrY3lhbiIsIGx0eT0xLCBsd2Q9MikNCmxlZ2VuZCgidG9wcmlnaHQiLCBjKCJzZXRvc2EiLCAidmVyc2ljb2xvciIsInZpcmdpbmljYSIpLA0KICAgICAgIGNvbD1jKCJicm93bjQiLCJibHVlIiwiZGFya2N5YW4iKSwgbHR5PWMoMSwxLDEpLA0KICAgICAgIGx3ZD1jKDEsMSwxKSwgYnR5PSJuIiwgY2V4PTAuNykNCmBgYA0KDQoNCg0KIyMgTW9kZWxpbmcgLSBDbGFzc2lmaWNhdGlvbiBhbmQgUmVncmVzc2lvbj8NCg0KV2hlbiBidWlsZGluZyByZWdyZXNzaW9uIG1vZGVscywgb25lIG9mIHRoZSBpc3N1ZXMgaXMgdGhlIHBvdGVudGlhbCBjb2xsaW5lYXJpdHkuIA0KDQokXHBpXzEgPSBQcltZID0gXHRleHR7c2V0b3NhfV0kLCAkXHBpXzIgPSBQcltZID0gXHRleHR7dmVyc2ljb2xvcn1dJCwgYW5kICRccGlfMSA9IFByW1kgPSBcdGV4dHt2aXJnaW5pY2F9XSQuIFRoZSB3ZWxsIGtub3duIGJhc2VsaW5lIGxvZ2l0IG1vZGVsIGlzIGRlZmluZWQgdG8gYmUgdGhlIGZvbGxvd2luZyBzeXN0ZW0gb2YgdHdvIG9kZHMgcmVncmVzc2lvbiBtb2RlbHMNCg0KJCQNClxsb2coXHBpXzIvIFxwaV8xKSA9IFxhbHBoYV8wICsgXGFscGhhXzF4XzEgKyBcY2RvdHMgKyBcYWxwaGFfa3hfaw0KJCQNCiQkDQpcbG9nKFxwaV8zLyBccGlfMSkgPSBcYmV0YV8wICsgXGJldGFfMXhfMSArIFxjZG90cyArIFxiZXRhX2t4X2sNCiQkDQpOb3RlIHRoYXQgJFxwaV8xICsgXHBpXzIgKyBccGlfMyA9IDEkLiBTb2x2ZSBmb3IgJFxwaV8xLCBccGlfMiQsIGFuZCAkXHBpXzMkLCB3ZSBoYXZlIA0KDQokJA0KXHBpXzEgPSBcZnJhY3sxfXsxK1xleHAoXGFscGhhXzAgKyBcYWxwaGFfMXhfMSArIFxjZG90cyArIFxhbHBoYV9reF9rKSArIFxleHAoXGJldGFfMCArIFxiZXRhXzF4XzEgKyBcY2RvdHMgKyBcYmV0YV9reF9rKX0NCiQkDQoNCiQkDQpccGlfMiA9IFxmcmFje1xleHAoXGFscGhhXzAgKyBcYWxwaGFfMXhfMSArIFxjZG90cyArIFxhbHBoYV9reF9rKX17MStcZXhwKFxhbHBoYV8wICsgXGFscGhhXzF4XzEgKyBcY2RvdHMgKyBcYWxwaGFfa3hfaykgKyBcZXhwKFxiZXRhXzAgKyBcYmV0YV8xeF8xICsgXGNkb3RzICsgXGJldGFfa3hfayl9DQokJA0KDQokJA0KXHBpXzMgPSBcZnJhY3tcZXhwKFxiZXRhXzAgKyBcYmV0YV8xeF8xICsgXGNkb3RzICsgXGJldGFfa3hfayl9ezErXGV4cChcYWxwaGFfMCArIFxhbHBoYV8xeF8xICsgXGNkb3RzICsgXGFscGhhX2t4X2spICsgXGV4cChcYmV0YV8wICsgXGJldGFfMXhfMSArIFxjZG90cyArIFxiZXRhX2t4X2spfQ0KJCQNCg0KDQoNCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0NCiMgRml0IG11bHRpbm9taWFsIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgDQpmaXQgPC0gbXVsdGlub20oU3BlY2llcyB+ICBTZXBhbC5MZW5ndGggDQogICAgICAgICAgICAgICAgICArIFNlcGFsLldpZHRoDQogICAgICAgICAgICAgICAgICArIFBldGFsLkxlbmd0aCANCiAgICAgICAgICAgICAgICAgICsgUGV0YWwuV2lkdGggLCANCiAgICAgICAgICAgICAgICAgIGRhdGEgPSBpcmlzLCB0cmFjZSA9IEZBTFNFKQ0Ka2FibGUoY29lZmZpY2llbnRzKGZpdCkpDQoNCmBgYA0KDQoNCkhvdyB0byBpbnRlcnByZXQgdGhlIHJlc3VsdHM/IENhbiB3ZSBtYWtlIGEgdmlzdWFsIHJlcHJlc2VudGF0aW9uIG9mIHRoZSByZXN1bHRpbmcgbW9kZWw/IEhvdz8NCg0KVGhlIGZvbGxvd2luZyBpcyBvbmUgZXhhbXBsZSBvZiByZXByZXNlbnRhdGlvbiBvZiBob3cgaW5kaXZpZHVhbCBmZWF0dXJlcyBpbXBhY3QgbWVtYmVyc2hpcCBwcmVkaWN0aW9uIChjbGFzc2lmaWNhdGlvbikuDQoNCmBgYHtyfQ0Kc2V0b3NhID0gc3VtbWFyeShpcmlzW3doaWNoKGlyaXMkU3BlY2llcz09InNldG9zYSIpLF0pDQp2ZXJzaWNvbG9yID0gc3VtbWFyeShpcmlzW3doaWNoKGlyaXMkU3BlY2llcz09InZlcnNpY29sb3IiKSxdKQ0KdmlyZ2luaWNhID0gc3VtbWFyeShpcmlzW3doaWNoKGlyaXMkU3BlY2llcz09InZpcmdpbmljYSIpLF0pDQpsaXN0KHNldG9zYSA9IHNldG9zYSwgdmVyc2ljb2xvciA9IHZlcnNpY29sb3IsIHZpcmdpbmljYSA9IHZpcmdpbmljYSkNCmBgYA0KDQpOZXh0LCB3ZSBzaG93IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBgU2VwYWwuTGVuZ3RoYCBhbmQgdGhlIGNsYXNzaWZpY2F0aW9uIHByb2JhYmlsaXRpZXMgJFxwaV8xLCBccGlfMiQgYW5kICRccGlfMyQgYnkgZml4aW5nIHRoZSB2YWx1ZXMgb2Ygb3RoZXIgdmFyaWFibGVzIGF0IHRoZWlyIGNvcnJlc3BvbmRpbmcgbWVhbnMuDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0NClNlcGFsTGVuZ3RoID0gc2VxKDQsIDksIGxlbmd0aD01MCkNCnBpMDEgPSAxLygxK2V4cCgxOC42OSAtIDUuNDYqU2VwYWxMZW5ndGggLSA4LjcxKjMgKyAxNC4yNSo0IC0gMy4xMCoxKSsgZXhwKC0yMy44NCAgLSA3LjkyKipTZXBhbExlbmd0aCAgIC0gMTUuMzcqMyArIDIzLjY2KjQgICsgMTUuMTQqMSApKQ0KIyMNCnBpMDIgPSBleHAoMTguNjkgLSA1LjQ2KlNlcGFsTGVuZ3RoIC0gOC43MSozICsgMTQuMjUqNCAtIDMuMTAqMSApLygxK2V4cCgxOC42OSAtIDUuNDYqU2VwYWxMZW5ndGggLSA4LjcxKjMgKyAxNC4yNSo0IC0gMy4xMCoxICkrIGV4cCgtMjMuODQgLSA3LjkyKipTZXBhbExlbmd0aCAgLSAxNS4zNyozICsyMy42Nio0ICsgMTUuMTQqMSAgKSkNCiMjDQojIw0KcGkwMyA9IGV4cCgtMjMuODQgIC0gNy45MioqU2VwYWxMZW5ndGggLSAxNS4zNyozICsyMy42Nio0ICsgMTUuMTQqMSApLygxK2V4cCgxOC42OSAtIDUuNDYqU2VwYWxMZW5ndGggLSA4LjcxKjMgKyAxNC4yNSo0IC0gMy4xMCoxKSsgZXhwKC0yMy44NCAgLSA3LjkyKipTZXBhbExlbmd0aCAtIDE1LjM3KjMgKzIzLjY2KjQgKyAxNS4xNCoxICkpDQojIw0KI3BhcihtZnJvdz1jKDEsMykpDQpwbG90KFNlcGFsTGVuZ3RoLCBwaTAxLCBtYWluPSJTZXRvc2EiLA0KICAgICB0eXBlID0gImwiLA0KICAgICB5bGltID0gYygwLDEpLA0KICAgICB4bGFiID0gIlNlcGFsIExlbmd0aCIsDQogICAgIHlsYWIgPSAiQ2xhc3NpZmljYXRpb24gUHJvYmFiaWxpdHkiLA0KICAgICBjb2wgPSAiYmx1ZSIsDQogICAgIGx3ZCA9IDIpDQpsaW5lcyhTZXBhbExlbmd0aCwgcGkwMiwgbWFpbiA9ICJWZXJzaWNvbG9yIiwgeWxhYj0iQ2xhc3NpZmljYXRpb24gUHJvYmFiaWxpdHkiLA0KICAgICB0eXBlID0gImwiLCBjb2wgPSAicHVycGxlIiwgbHdkID0gMikNCmxpbmVzKFNlcGFsTGVuZ3RoLCBwaTAzLCBtYWluID0gIlZpcmdpbmljYSIsICB5bGFiPSJDbGFzc2lmaWNhdGlvbiBQcm9iYWJpbGl0eSIsDQogICAgIHR5cGU9ImwiLCBjb2wgPSAiYnJvd240IiwgbHdkID0gMikNCmFibGluZSh2PTguNTEsIGx0eSA9IDIpDQpwb2ludHMoOC41MSwgMC40OTUsIHBjaD0xOSwgY29sID0gImRhcmtyZWQiLCBkZXggPSAxLjUpDQp0ZXh0KDgsIDAuNDk1LCAiKDguNTEsIDAuNDk1KSIsIGNleCA9IDAuNikNCmxlZ2VuZCgibGVmdCIsIGMoIlNldG9zYSIsICJWZXJzaWNvbG9yIiwgIlZpcmdpbmljYSIpLCBsd2Q9cmVwKDIsMyksDQogICAgICAgY29sPWMoImJsdWUiLCAicHVycGxlIiwgImJyb3duIiksIGx0eSA9IHJlcCgxLDMpLCBjZXg9MC45LCBidHk9Im4iKQ0KYGBgDQpUaGUgYWJvdmUgdGhyZWUgZmlndXJlcyBzaG93IGhvdyBgc2VwYWwgbGVuZ3RoYCBpcyBhc3NvY2lhdGVkIHdpdGggdGhlIHRocmVlIGNsYXNzaWZpY2F0aW9uIHByb2JhYmlsaXRpZXMuIEZvciBleGFtcGxlLCBhbW9uZyBzZXRvc2EsIHVuZGVyIHRoZSBzYW1lIGNvbmRpdGlvbnMgKGkuZS4sIHNlcGFsIHdpZHRoLCBwZXRhbCBsZW5ndGgsIGFuZCBwZXRhbCB3aWR0aCBhcmUgdGhlIHNhbWUpLCBhcyBzZXBhbCBsZW5ndGggaW5jcmVhc2VzLCB0aGUgY2xhc3NpZmljYXRpb24gcHJvYmFiaWxpdHkgYWxzbyBpbmNyZWFzZXMuIFRoZSBvcHBvc2l0ZSBwYXR0ZXJuIGlzIG9ic2VydmVkIGFtb25nIHZlcnNpY29sb3IgaXJpcyBmbG93ZXJzLiBUaGUgc2VwYWwgbGVuZ3RoIGFuZCB0aGUgY2xhc3NpZmljYXRpb24gcHJvYmFiaWxpdHkgYXJlIG5vdCBhc3NvY2lhdGVkLg0KDQpGb3IgYWxsIGlyaXMgZmxvd2VycyB3aXRoIFNlcGFsIFdpZHRoIDMsIFBldGFsIExlbmd0aCA0LCBhbmQgUGV0YWwgV2lkdGggMS41LCB3aGVuIFNlcGFsIExlbmd0aCA8IDguNTEsICRQKFx0ZXh0e1ZlcnNpY29sb3J9KSA+IFAoXHRleHR7U2V0b3NhfSkkIG1lYW5pbmcgdGhhdCBpdCBpcyBtb3JlIGxpa2VseSB0byBiZSBgVmVyc2ljb2xvciBpcmlzYC4gT3RoZXJ3aXNlLCBpdCBpcyBtb3JlIGxpa2VseSB0byBiZSBgc2V0b3NhYC4gU2VwYWwgbGVuZ3RoIGRvZXMgbm90IGhhdmUgKnByZWRpY3RpdmUgcG93ZXIqIGZvciBgdmlyZ2luaWNhYC4NCg0KV2UgY2FuIGFsc28gbG9vayBhdCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGNsYXNzaWZpY2F0aW9uIHByb2JhYmlsaXR5IGFuZCBvdGhlciBudW1lcmljYWwgdmFyaWFibGVzIGluIHRoZSBkYXRhIHNldCBpbiBhIHNpbWlsYXIgd2F5Lg0KDQoNCiMjIENsdXN0ZXJpbmc/DQoNCkFub3RoZXIgaW50ZXJlc3RpbmcgcHJvYmxlbSB3ZSBtYXkgZXhwbG9yZSBpcyB0aGUgc2VlIHdoZXRoZXIgdGhlcmUgYXJlIHNvbWUgY2x1c3RlcnMgYmFzZWQgb24gdGhlIGZvdXIgbnVtZXJpY2FsIHZhcmlhYmxlcy4gVGhlcmUgYXJlIG1hbnkgY2x1c3RlcmluZyBhbGdvcml0aG1zIGZvciBwcmFjdGl0aW9uZXJzLiBGb3IgaWxsdXN0cmF0aXZlIHB1cnBvc2VzLCB3ZSB1c2UgdGhlIHBvcHVsYXIgay1tZWFucyBhbGdvcml0aG0gaW4gdGhlIGZvbGxvd2luZyBleGFtcGxlLg0KDQoNCmBgYHtyfQ0KdG90LndpdGhpbnNzIDwtIHZlY3Rvcihtb2RlPSJjaGFyYWN0ZXIiLCBsZW5ndGg9MTApDQpmb3IgKGkgaW4gMToxMCl7DQogIGlyaXNDbHVzdGVyIDwtIGttZWFucyhpcmlzWywxOjRdLCBjZW50ZXI9aSwgbnN0YXJ0PTIwKQ0KICB0b3Qud2l0aGluc3NbaV0gPC0gaXJpc0NsdXN0ZXIkdG90LndpdGhpbnNzDQp9DQpwbG90KDE6MTAsIHRvdC53aXRoaW5zcywgbWFpbj0iRWxib3cgUGxvdCBmb3IgT3B0aW1hbCBDbHVzdGVyIERldGVybWluYXRpb24iLA0KICAgICB0eXBlPSJiIiwgcGNoPTE5KQ0KYGBgDQoNCg0KYGBge3J9DQppcmlzQ2x1c3RlciA8LSBrbWVhbnMoaXJpc1ssMTo0XSwgY2VudGVyPTMsIG5zdGFydD0yMCkNCmlyaXNDbHVzdGVyDQpgYGANCg0KYGBge3J9DQpjbHVzcGxvdChpcmlzLCBpcmlzQ2x1c3RlciRjbHVzdGVyLCBjb2xvcj1ULCBzaGFkZT1ULCBsYWJlbHM9MCwgbGluZXM9MCkNCmBgYA0KDQoNCg0KDQoNCg==