Example I
Data Generation
Set the model parameters like dimension and standard error, and store
the two centers (each is a two-dim vector) in m1
and
m2
.
p=2;
s=1; # sd for generating the centers
m1=c(1,0);
m0=c(0,1);
Next we generate \(n\) samples from
each normal component.
- First, we generate a \(2n\)-by-\(p\) matrix with entries being iid samples
form a normal dist with mean 0 and standard deviation
s
.
- Then we form a \(2n\)-by-\(p\) matrix with the first \(n\) rows being the two-dimensional vector
m1
(the center of the first normal component) and the next
\(n\) rows being m2
. The
command rbind
is used to stack two matrices vertically
together.
n=100; # generate the train data; n for each class
traindata=matrix(rnorm(2*n*p), 2*n, p)*s +
rbind(matrix(rep(m1, n), nrow=n,byrow=TRUE),
matrix(rep(m0, n), nrow=n, byrow=TRUE))
dim(traindata)
Ytrain=factor(c(rep(1,n), rep(0,n)))
Generate \(2N\) test samples
similarly.
N=5000;
testdata=matrix(rnorm(2*N*p), 2*N, p)*s+
rbind(matrix(rep(m1, N), nrow=N,byrow=TRUE),
matrix(rep(m0, N), nrow=N, byrow=TRUE))
Ytest=factor(c(rep(1,N), rep(0,N)))
Visulization
Before running any analysis, we can take a look of the data. In the
figure generated by the code below, points from two groups are colored
in red and blue, respectively; the two centers are plotted as +, and a
legend is added to explain the association of each color.
The option type=n
tells not to print anything; just the
plotting area, then we add points group by group.
plot(traindata[,1], traindata[,2], type="n", xlab="", ylab="")
# add points from class 1
points(traindata[1:n,1], traindata[1:n,2], col="blue");
# add points from class 0
points(traindata[(n+1):(2*n),1], traindata[(n+1):(2*n),2],
col="red");
# add the 10 centers for class 1
points(m1[1], m1[2], pch="+", cex=1.5, col="blue");
# add the 10 centers for class 0
points(m0[1], m0[2], pch="+", cex=1.5, col="red");
legend("bottomright", pch = c(1,1), col = c("red", "blue"),
legend = c("class 1", "class 0"))
Visulization with ggplot2
(Feel free to skip this part if you’ve known how to use
ggplot2.)
Previously I plot the data in the old “Artist Palette” way: I start
with a canvas (plotting region), and subsequently add individual
elements step by steps, beginning with points, followed by colors and
legends. But here’s the catch: when I wanted to toss in a point outside
the current plotting region, I had to go back to square one. I’d have to
scrap the current setup and start fresh with a bigger plotting region,
just to make sure there’s enough room for those extra points I wanted to
add later on.
A better graphical package for R is ggplot2
; gg stands
for grammar for graphics http://ggplot2.org/
Two major commands: qplot
and ggplot
. You
can find many examples and tutorials easily online.
Run the following code (each line gives your a slightly different
plot) to get familiar with various arguments in qplot
.
#install.package("ggplot2")
library("ggplot2")
## ggplot only accepts dataframe
mytraindata = data.frame(X1=traindata[,1],
X2=traindata[,2],
Y=Ytrain)
## plot the data and color by the Y label
qplot(X1, X2, data=mytraindata, colour=Y)
## change the shape and size of the points
qplot(X1, X2, data=mytraindata, colour=Y, shape=I(1), size=I(3))
## change x,y labels and add titles
qplot(X1, X2, data=mytraindata, colour=Y, shape=I(1), size=I(3),
main="Scatter Plot (Training Data)", xlab="", ylab="")
## want to remove the gray background? Change theme
qplot(X1, X2, data=mytraindata, colour=Y, shape=I(1), size=I(3)) +
theme_bw()
## more theme options
## A single command would be a little lengthy. We can save
## the output of qplot into an object "myplot"
myplot=qplot(X1, X2, data=mytraindata, colour=Y, shape=I(1), size=I(3))
myplot = myplot + theme_bw();
myplot + theme(legend.position="left")
?theme
## How to adding the two centers on
## existing plot, we need to use the command "ggplot"
?ggplot
myplot = ggplot(mytraindata,aes(X1, X2))
## use user-specified color
myplot+ geom_point(aes(color=Y), shape = 1, size=3) +
scale_color_manual(values = c("red", "blue"))
## add the two centers;
## change shape and size;
## use user-sepecified color
myplot = ggplot(mytraindata,aes(X1, X2)) +
geom_point(aes(colour=Y), shape = 3, size=2) +
scale_color_manual(values = c("red", "blue"))
myplot +
geom_point(data=data.frame(X1=m1[1], X2=m1[2]),
aes(X1, X2), colour="blue", shape=2,size=5)+
geom_point(data=data.frame(X1=m0[1], X2=m0[2]),
aes(X1, X2), colour="red", shape=2, size=5)
K-NN method
For choices of the neighborhood size, I just use the values from the
textbook.
library("class")
myk=c(151, 101, 69, 45, 31, 21, 11, 7, 5, 3, 1)
m=length(myk);
train.err.knn = rep(0,m);
test.err.knn = rep(0, m);
for( j in 1:m){
Ytrain.pred = knn(traindata, traindata, Ytrain, k=myk[j])
train.err.knn[j]= sum(Ytrain != Ytrain.pred)/(2*n)
Ytest.pred = knn(traindata, testdata, Ytrain,k=myk[j])
test.err.knn[j] = sum(Ytest != Ytest.pred)/(2*N)
}
cbind(train.err.knn, test.err.knn)
Use the code below if you want to use 5-fold CV to select the optimal
K, i.e., the k value in “myk” that minimizes the CV error.
The 5-fold CV error for each k is a sum of 5 prediction errors, one
for each fold. In the code below, we have an outside loop from 1 to 5,
and an inside loop from 1 to m (all possible values for k). Inside the
loop, we use 80% (i.e., four folds) of the data as training and predict
on the 20% (i.e., one fold) holdout set.
## set a vector cv.err to store the CV error for each k.
cv.err=rep(0,m);
id=sample(1:(2*n),(2*n), replace=FALSE);
fold=c(0, 40, 80, 120, 160, 200)
for(i in 1:5)
for(j in 1:m){
## ith.fold = rows which are in the i-th fold
ith.fold = id[(fold[i]+1):fold[i+1]];
tmp=knn(traindata[-ith.fold,], traindata[ith.fold,], Ytrain[-ith.fold], k=myk[j]);
cv.err[j]=cv.err[j]+sum(tmp != Ytrain[ith.fold])
}
## find the best k value based 5-fold CV
k.star=myk[order(cv.err)[1]]
## Error of KNN where K is chosen by 5-fold CV
Ytrain.pred = knn(traindata, traindata, Ytrain, k=k.star)
train.err.knn.CV= sum(Ytrain != Ytrain.pred)/(2*n)
Ytest.pred = knn(traindata, testdata, Ytrain,k=k.star)
test.err.knn.CV = sum(Ytest != Ytest.pred)/(2*N)
Least Sqaure Method
RegModel = lm(as.numeric(Ytrain)-1 ~ traindata)
Ytrain_pred_LS = as.numeric(RegModel$fitted > 0.5)
Ytest_pred_LS = RegModel$coef[1] + RegModel$coef[2] * testdata[,1] + RegModel$coef[3] * testdata[,2]
Ytest_pred_LS = as.numeric(Ytest_pred_LS > 0.5 )
## cross tab for training data and training error
table(Ytrain, Ytrain_pred_LS);
train.err.LS = sum(Ytrain != Ytrain_pred_LS) / (2*n);
## cross tab for test data and test error
table(Ytest, Ytest_pred_LS);
test.err.LS = sum(Ytest != Ytest_pred_LS) / (2*N);
Bayes Error
Ytest_pred_Bayes = as.numeric(2*testdata %*% matrix(m1-m0, nrow=2) >
(sum(m1^2)-sum(m0^2)))
test.err.Bayes = sum(Ytest != Ytest_pred_Bayes) / (2*N)
Plot the Performance
Test errors are in red and training errors are in blue. The upper
x-coordinate indicates the K values, and the lower x-coordinate
indicates the degree-of-freedom of the KNN procedures so the labels are
reciprocally related to K.
The training and test errors for linear regression are plotted at
df=3 (corresponding to K = (2n)/3), since the linear model has 3
parameters, i.e., 3 dfs.
The training and test errors for KNN with K chosen by CV are plotted
at the chose K values.
plot(c(0.5,m), range(test.err.LS, train.err.LS, test.err.knn, train.err.knn),
type="n", xlab="Degree of Freedom", ylab="Error", xaxt="n")
df=round((2*n)/myk)
axis(1, at=1:m, labels=df)
axis(3, at=1:m, labels=myk)
points(1:m, test.err.knn, col="red", pch=1)
lines(1:m, test.err.knn, col="red", lty=1)
points(1:m, train.err.knn, col="blue", pch=1)
lines(1:m, train.err.knn, col="blue", lty=2)
points(3, train.err.LS, pch=2, cex=2, col="blue")
points(3, test.err.LS, pch=2, cex=2, col="red")
abline(test.err.Bayes, 0, col="purple")
points((1:m)[myk == k.star], train.err.knn.CV, col="blue", pch=3, cex=2)
points((1:m)[myk == k.star], test.err.knn.CV, col="red", pch=3, cex=2)
Example II
Data Generation
Generate the 20 centers, 10 for each group.
csize = 10 # number of centers
p = 2
s = 1 # sd for generating the centers within each class
m1 = matrix(rnorm(csize*p), csize, p)*s + cbind( rep(1,csize), rep(0,csize) )
m0 = matrix(rnorm(csize*p), csize, p)*s + cbind( rep(0,csize), rep(1,csize) )
Generate training data.
n = 100
# Allocate the n samples for class 1 to the 10 clusters
id1 = sample(1:csize, n, replace=TRUE)
# Allocate the n samples for class 1 to the 10 clusters
id0 = sample(1:csize, n, replace=TRUE)
s = sqrt(1/5); # sd for generating data.
traindata = matrix(rnorm(2*n*p), 2*n, p)*s +
rbind(m1[id1,], m0[id0,])
dim(traindata)
Ytrain = factor(c(rep(1,n), rep(0,n)))
Visulization
plot(traindata[,1], traindata[,2], type="n", xlab="", ylab="")
points(traindata[1:n,1], traindata[1:n,2], col="blue")
points(traindata[(n+1):(2*n),1], traindata[(n+1):(2*n),2], col="red")
points(m1[1:csize,1], m1[1:csize,2], pch="+", cex=1.5, col="blue")
points(m0[1:csize,1], m0[1:csize,2], pch="+", cex=1.5, col="red");
legend("bottomright", pch = c(1,1), col = c("red", "blue"), legend = c("class 1", "class 0"))
LS0tCnRpdGxlOiAiUFNMOiBrTk4gdnMgTGluZWFyIFJlZ3Jlc3Npb24iCmRhdGU6ICJGYWxsIDIwMjMiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdGhlbWU6IHJlYWRhYmxlCiAgICB0b2M6IFRSVUUKICAgIHRvY19mbG9hdDogVFJVRQotLS0KCiMjIEV4YW1wbGUgSQoKIyMjIERhdGEgR2VuZXJhdGlvbgoKU2V0IHRoZSBtb2RlbCBwYXJhbWV0ZXJzIGxpa2UgZGltZW5zaW9uIGFuZCBzdGFuZGFyZCBlcnJvciwgYW5kIHN0b3JlIHRoZSB0d28gY2VudGVycyAoZWFjaCBpcyBhIHR3by1kaW0gdmVjdG9yKSBpbiBgbTFgIGFuZCBgbTJgLiAKCmBgYHtyfQpwPTI7IAkJCnM9MTsgCQkjIHNkIGZvciBnZW5lcmF0aW5nIHRoZSBjZW50ZXJzCQkJCSAKbTE9YygxLDApOwptMD1jKDAsMSk7CmBgYAoKTmV4dCB3ZSBnZW5lcmF0ZSAkbiQgc2FtcGxlcyBmcm9tIGVhY2ggbm9ybWFsIGNvbXBvbmVudC4gCgoqIEZpcnN0LCB3ZSBnZW5lcmF0ZSBhICQybiQtYnktJHAkIG1hdHJpeCB3aXRoIGVudHJpZXMgYmVpbmcgaWlkIHNhbXBsZXMgZm9ybSBhIG5vcm1hbCBkaXN0IHdpdGggbWVhbiAwIGFuZCBzdGFuZGFyZCBkZXZpYXRpb24gYHNgLiAKKiBUaGVuIHdlIGZvcm0gYSAkMm4kLWJ5LSRwJCBtYXRyaXggd2l0aCB0aGUgZmlyc3QgJG4kIHJvd3MgYmVpbmcgdGhlIHR3by1kaW1lbnNpb25hbCB2ZWN0b3IgYG0xYCAodGhlIGNlbnRlciBvZiB0aGUgZmlyc3Qgbm9ybWFsIGNvbXBvbmVudCkgYW5kIHRoZSBuZXh0ICRuJCByb3dzIGJlaW5nIGBtMmAuIFRoZSBjb21tYW5kIGByYmluZGAgaXMgdXNlZCB0byBzdGFjayB0d28gbWF0cmljZXMgdmVydGljYWxseSB0b2dldGhlci4gCgpgYGB7cn0Kbj0xMDA7ICAjIGdlbmVyYXRlIHRoZSB0cmFpbiBkYXRhOyBuIGZvciBlYWNoIGNsYXNzCnRyYWluZGF0YT1tYXRyaXgocm5vcm0oMipuKnApLCAyKm4sIHApKnMgKyAKICByYmluZChtYXRyaXgocmVwKG0xLCBuKSwgbnJvdz1uLGJ5cm93PVRSVUUpLCAKICAgICAgICBtYXRyaXgocmVwKG0wLCBuKSwgbnJvdz1uLCBieXJvdz1UUlVFKSkKCmRpbSh0cmFpbmRhdGEpCll0cmFpbj1mYWN0b3IoYyhyZXAoMSxuKSwgcmVwKDAsbikpKQpgYGAKCkdlbmVyYXRlICQyTiQgdGVzdCBzYW1wbGVzIHNpbWlsYXJseS4gCgpgYGB7cn0KTj01MDAwOyAKdGVzdGRhdGE9bWF0cml4KHJub3JtKDIqTipwKSwgMipOLCBwKSpzKyAKICByYmluZChtYXRyaXgocmVwKG0xLCBOKSwgbnJvdz1OLGJ5cm93PVRSVUUpLCAKICAgICAgICBtYXRyaXgocmVwKG0wLCBOKSwgbnJvdz1OLCBieXJvdz1UUlVFKSkKWXRlc3Q9ZmFjdG9yKGMocmVwKDEsTiksIHJlcCgwLE4pKSkKYGBgCgoKIyMjIFZpc3VsaXphdGlvbiAKCkJlZm9yZSBydW5uaW5nIGFueSBhbmFseXNpcywgd2UgY2FuIHRha2UgYSBsb29rIG9mIHRoZSBkYXRhLiBJbiB0aGUgZmlndXJlIGdlbmVyYXRlZCBieSB0aGUgY29kZSBiZWxvdywgcG9pbnRzIGZyb20gdHdvIGdyb3VwcyBhcmUgY29sb3JlZCBpbiByZWQgYW5kIGJsdWUsIHJlc3BlY3RpdmVseTsgdGhlIHR3byBjZW50ZXJzIGFyZSBwbG90dGVkIGFzICssIGFuZCBhIGxlZ2VuZCBpcyBhZGRlZCB0byBleHBsYWluIHRoZSBhc3NvY2lhdGlvbiBvZiBlYWNoIGNvbG9yLiAKClRoZSBvcHRpb24gYHR5cGU9bmAgdGVsbHMgbm90IHRvIHByaW50IGFueXRoaW5nOyBqdXN0IHRoZSBwbG90dGluZyBhcmVhLCB0aGVuIHdlIGFkZCBwb2ludHMgZ3JvdXAgYnkgZ3JvdXAuIAoKYGBge3J9CnBsb3QodHJhaW5kYXRhWywxXSwgdHJhaW5kYXRhWywyXSwgdHlwZT0ibiIsIHhsYWI9IiIsIHlsYWI9IiIpCgojIGFkZCBwb2ludHMgZnJvbSBjbGFzcyAxCnBvaW50cyh0cmFpbmRhdGFbMTpuLDFdLCB0cmFpbmRhdGFbMTpuLDJdLCBjb2w9ImJsdWUiKTsgICAKCiMgYWRkIHBvaW50cyBmcm9tIGNsYXNzIDAKcG9pbnRzKHRyYWluZGF0YVsobisxKTooMipuKSwxXSwgdHJhaW5kYXRhWyhuKzEpOigyKm4pLDJdLCAKICAgICAgIGNvbD0icmVkIik7IAojIGFkZCB0aGUgMTAgY2VudGVycyBmb3IgY2xhc3MgMQpwb2ludHMobTFbMV0sIG0xWzJdLCBwY2g9IisiLCBjZXg9MS41LCBjb2w9ImJsdWUiKTsgICAgCiMgYWRkIHRoZSAxMCBjZW50ZXJzIGZvciBjbGFzcyAwCnBvaW50cyhtMFsxXSwgbTBbMl0sIHBjaD0iKyIsIGNleD0xLjUsIGNvbD0icmVkIik7ICAgCgpsZWdlbmQoImJvdHRvbXJpZ2h0IiwgcGNoID0gYygxLDEpLCBjb2wgPSBjKCJyZWQiLCAiYmx1ZSIpLCAKICAgICAgIGxlZ2VuZCA9IGMoImNsYXNzIDEiLCAiY2xhc3MgMCIpKQpgYGAKCiMjIyBWaXN1bGl6YXRpb24gd2l0aCBnZ3Bsb3QyCgoqKihGZWVsIGZyZWUgdG8gc2tpcCB0aGlzIHBhcnQgaWYgeW91J3ZlIGtub3duIGhvdyB0byB1c2UgZ2dwbG90Mi4pKioKClByZXZpb3VzbHkgSSBwbG90IHRoZSBkYXRhIGluIHRoZSBvbGQgIkFydGlzdCBQYWxldHRlIiB3YXk6Ckkgc3RhcnQgd2l0aCBhIGNhbnZhcyAocGxvdHRpbmcgcmVnaW9uKSwgYW5kIHN1YnNlcXVlbnRseSBhZGQgaW5kaXZpZHVhbCBlbGVtZW50cyBzdGVwIGJ5IHN0ZXBzLCBiZWdpbm5pbmcgd2l0aCBwb2ludHMsIGZvbGxvd2VkIGJ5IGNvbG9ycyBhbmQgbGVnZW5kcy4gQnV0IGhlcmUncyB0aGUgY2F0Y2g6IHdoZW4gSSB3YW50ZWQgdG8gdG9zcyBpbiBhIHBvaW50IG91dHNpZGUgdGhlIGN1cnJlbnQgcGxvdHRpbmcgcmVnaW9uLCBJIGhhZCB0byBnbyBiYWNrIHRvIHNxdWFyZSBvbmUuIEknZCBoYXZlIHRvIHNjcmFwIHRoZSBjdXJyZW50IHNldHVwIGFuZCBzdGFydCBmcmVzaCB3aXRoIGEgYmlnZ2VyIHBsb3R0aW5nIHJlZ2lvbiwganVzdCB0byBtYWtlIHN1cmUgdGhlcmUncyBlbm91Z2ggcm9vbSBmb3IgdGhvc2UgZXh0cmEgcG9pbnRzIEkgd2FudGVkIHRvIGFkZCBsYXRlciBvbi4KCkEgYmV0dGVyIGdyYXBoaWNhbCBwYWNrYWdlIGZvciBSIGlzIGBnZ3Bsb3QyYDsgZ2cgc3RhbmRzIGZvciBncmFtbWFyIGZvciBncmFwaGljcwpbaHR0cDovL2dncGxvdDIub3JnL10oaHR0cDovL2dncGxvdDIub3JnLykKClR3byBtYWpvciBjb21tYW5kczogYHFwbG90YCBhbmQgYGdncGxvdGAuIFlvdSBjYW4gZmluZCBtYW55IGV4YW1wbGVzIGFuZCB0dXRvcmlhbHMgZWFzaWx5IG9ubGluZS4gCgpSdW4gdGhlIGZvbGxvd2luZyBjb2RlIChlYWNoIGxpbmUgZ2l2ZXMgeW91ciBhIHNsaWdodGx5IGRpZmZlcmVudCBwbG90KSB0byBnZXQgZmFtaWxpYXIgd2l0aCB2YXJpb3VzIGFyZ3VtZW50cyBpbiBgcXBsb3RgLgoKYGBge3J9CiNpbnN0YWxsLnBhY2thZ2UoImdncGxvdDIiKQpsaWJyYXJ5KCJnZ3Bsb3QyIikKCiMjIGdncGxvdCBvbmx5IGFjY2VwdHMgZGF0YWZyYW1lCm15dHJhaW5kYXRhID0gZGF0YS5mcmFtZShYMT10cmFpbmRhdGFbLDFdLCAKICAgICAgICAgICAgICAgICAgICAgICAgIFgyPXRyYWluZGF0YVssMl0sIAogICAgICAgICAgICAgICAgICAgICAgICAgWT1ZdHJhaW4pCgojIyBwbG90IHRoZSBkYXRhIGFuZCBjb2xvciBieSB0aGUgWSBsYWJlbApxcGxvdChYMSwgWDIsIGRhdGE9bXl0cmFpbmRhdGEsIGNvbG91cj1ZKQoKIyMgY2hhbmdlIHRoZSBzaGFwZSBhbmQgc2l6ZSBvZiB0aGUgcG9pbnRzCnFwbG90KFgxLCBYMiwgZGF0YT1teXRyYWluZGF0YSwgY29sb3VyPVksIHNoYXBlPUkoMSksIHNpemU9SSgzKSkKCiMjIGNoYW5nZSB4LHkgbGFiZWxzIGFuZCBhZGQgdGl0bGVzCnFwbG90KFgxLCBYMiwgZGF0YT1teXRyYWluZGF0YSwgY29sb3VyPVksIHNoYXBlPUkoMSksIHNpemU9SSgzKSwKICAgICAgbWFpbj0iU2NhdHRlciBQbG90IChUcmFpbmluZyBEYXRhKSIsIHhsYWI9IiIsIHlsYWI9IiIpCgojIyB3YW50IHRvIHJlbW92ZSB0aGUgZ3JheSBiYWNrZ3JvdW5kPyBDaGFuZ2UgdGhlbWUKcXBsb3QoWDEsIFgyLCBkYXRhPW15dHJhaW5kYXRhLCBjb2xvdXI9WSwgc2hhcGU9SSgxKSwgc2l6ZT1JKDMpKSArIAogIHRoZW1lX2J3KCkKCiMjIG1vcmUgdGhlbWUgb3B0aW9ucwojIyBBIHNpbmdsZSBjb21tYW5kIHdvdWxkIGJlIGEgbGl0dGxlIGxlbmd0aHkuIFdlIGNhbiBzYXZlIAojIyB0aGUgb3V0cHV0IG9mIHFwbG90IGludG8gYW4gb2JqZWN0ICJteXBsb3QiCgpteXBsb3Q9cXBsb3QoWDEsIFgyLCBkYXRhPW15dHJhaW5kYXRhLCBjb2xvdXI9WSwgc2hhcGU9SSgxKSwgc2l6ZT1JKDMpKQpteXBsb3QgPSBteXBsb3QgKyB0aGVtZV9idygpOwpteXBsb3QgKyB0aGVtZShsZWdlbmQucG9zaXRpb249ImxlZnQiKQo/dGhlbWUKCiMjIEhvdyB0byBhZGRpbmcgdGhlIHR3byBjZW50ZXJzIG9uIAojIyBleGlzdGluZyBwbG90LCB3ZSBuZWVkIHRvIHVzZSB0aGUgY29tbWFuZCAiZ2dwbG90Igo/Z2dwbG90Cm15cGxvdCA9IGdncGxvdChteXRyYWluZGF0YSxhZXMoWDEsIFgyKSkKCiMjIHVzZSB1c2VyLXNwZWNpZmllZCBjb2xvcgpteXBsb3QrIGdlb21fcG9pbnQoYWVzKGNvbG9yPVkpLCBzaGFwZSA9IDEsIHNpemU9MykgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygicmVkIiwgImJsdWUiKSkKCiMjIGFkZCB0aGUgdHdvIGNlbnRlcnM7IAojIyBjaGFuZ2Ugc2hhcGUgYW5kIHNpemU7CiMjIHVzZSB1c2VyLXNlcGVjaWZpZWQgY29sb3IKbXlwbG90ID0gZ2dwbG90KG15dHJhaW5kYXRhLGFlcyhYMSwgWDIpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyPVkpLCBzaGFwZSA9IDMsIHNpemU9MikgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygicmVkIiwgImJsdWUiKSkgICAKCm15cGxvdCArIAogIGdlb21fcG9pbnQoZGF0YT1kYXRhLmZyYW1lKFgxPW0xWzFdLCBYMj1tMVsyXSksIAogICAgICAgICAgICAgICAgICAgIGFlcyhYMSwgWDIpLCBjb2xvdXI9ImJsdWUiLCBzaGFwZT0yLHNpemU9NSkrCiAgZ2VvbV9wb2ludChkYXRhPWRhdGEuZnJhbWUoWDE9bTBbMV0sIFgyPW0wWzJdKSwgCiAgICAgICAgICAgICBhZXMoWDEsIFgyKSwgY29sb3VyPSJyZWQiLCBzaGFwZT0yLCBzaXplPTUpCmBgYAoKCiMjIyBLLU5OIG1ldGhvZCAKCkZvciBjaG9pY2VzIG9mIHRoZSBuZWlnaGJvcmhvb2Qgc2l6ZSwgSSBqdXN0IHVzZSB0aGUgdmFsdWVzIGZyb20gdGhlIHRleHRib29rLgoKYGBge3J9CmxpYnJhcnkoImNsYXNzIikgCgpteWs9YygxNTEsIDEwMSwgNjksICA0NSwgMzEsIDIxLCAxMSwgNywgNSwgMywgMSkKbT1sZW5ndGgobXlrKTsKdHJhaW4uZXJyLmtubiA9IHJlcCgwLG0pOwp0ZXN0LmVyci5rbm4gPSByZXAoMCwgbSk7CmZvciggaiBpbiAxOm0pewogIFl0cmFpbi5wcmVkID0ga25uKHRyYWluZGF0YSwgdHJhaW5kYXRhLCBZdHJhaW4sIGs9bXlrW2pdKQogIHRyYWluLmVyci5rbm5bal09IHN1bShZdHJhaW4gIT0gWXRyYWluLnByZWQpLygyKm4pCiAgWXRlc3QucHJlZCA9IGtubih0cmFpbmRhdGEsIHRlc3RkYXRhLCBZdHJhaW4saz1teWtbal0pCiAgdGVzdC5lcnIua25uW2pdID0gc3VtKFl0ZXN0ICE9IFl0ZXN0LnByZWQpLygyKk4pCn0KY2JpbmQodHJhaW4uZXJyLmtubiwgdGVzdC5lcnIua25uKQpgYGAKClVzZSB0aGUgY29kZSBiZWxvdyBpZiB5b3Ugd2FudCB0byB1c2UgNS1mb2xkIENWIHRvIHNlbGVjdCB0aGUgb3B0aW1hbCBLLCBpLmUuLCB0aGUgayB2YWx1ZSBpbiDigJxteWvigJ0gdGhhdCBtaW5pbWl6ZXMgdGhlIENWIGVycm9yLgoKVGhlIDUtZm9sZCBDViBlcnJvciBmb3IgZWFjaCBrIGlzIGEgc3VtIG9mIDUgcHJlZGljdGlvbiBlcnJvcnMsIG9uZSBmb3IgZWFjaCBmb2xkLiBJbiB0aGUgY29kZSBiZWxvdywgd2UgaGF2ZSBhbiBvdXRzaWRlIGxvb3AgZnJvbSAxIHRvIDUsIGFuZCBhbiBpbnNpZGUgbG9vcCBmcm9tIDEgdG8gbSAoYWxsIHBvc3NpYmxlIHZhbHVlcyBmb3IgaykuIEluc2lkZSB0aGUgbG9vcCwgd2UgdXNlIDgwJSAoaS5lLiwgZm91ciBmb2xkcykgb2YgdGhlIGRhdGEgYXMgdHJhaW5pbmcgYW5kIHByZWRpY3Qgb24gdGhlIDIwJSAoaS5lLiwgb25lIGZvbGQpIGhvbGRvdXQgc2V0LgoKYGBge3J9CiMjIHNldCBhIHZlY3RvciBjdi5lcnIgdG8gc3RvcmUgdGhlIENWIGVycm9yIGZvciBlYWNoIGsuIApjdi5lcnIgPSByZXAoMCxtKQoKaWQgPSBzYW1wbGUoMTooMipuKSwoMipuKSwgcmVwbGFjZT1GQUxTRSkKZm9sZD1jKDAsICA0MCwgIDgwLCAxMjAsIDE2MCwgMjAwKQpmb3IoaSBpbiAxOjUpCiAgICBmb3IoaiBpbiAxOm0pewogICAgICAKICAgICAgIyMgaXRoLmZvbGQgPSByb3dzIHdoaWNoIGFyZSBpbiB0aGUgaS10aCBmb2xkCiAgICAgIGl0aC5mb2xkID0gaWRbKGZvbGRbaV0rMSk6Zm9sZFtpKzFdXSAgIAogICAgICB0bXAgPSBrbm4odHJhaW5kYXRhWy1pdGguZm9sZCxdLCB0cmFpbmRhdGFbaXRoLmZvbGQsXSwgCiAgICAgICAgICAgICAgICBZdHJhaW5bLWl0aC5mb2xkXSwgaz1teWtbal0pCiAgICAgIGN2LmVycltqXSA9IGN2LmVycltqXSArIHN1bSh0bXAgIT0gWXRyYWluW2l0aC5mb2xkXSkKICAgIH0KCiMjIGZpbmQgdGhlIGJlc3QgayB2YWx1ZSBiYXNlZCA1LWZvbGQgQ1YKay5zdGFyID0gbXlrW29yZGVyKGN2LmVycilbMV1dICAKCiMjIEVycm9yIG9mIEtOTiB3aGVyZSBLIGlzIGNob3NlbiBieSA1LWZvbGQgQ1YKWXRyYWluLnByZWQgPSBrbm4odHJhaW5kYXRhLCB0cmFpbmRhdGEsIFl0cmFpbiwgaz1rLnN0YXIpCnRyYWluLmVyci5rbm4uQ1Y9IHN1bShZdHJhaW4gIT0gWXRyYWluLnByZWQpLygyKm4pCll0ZXN0LnByZWQgPSBrbm4odHJhaW5kYXRhLCB0ZXN0ZGF0YSwgWXRyYWluLGs9ay5zdGFyKQp0ZXN0LmVyci5rbm4uQ1YgPSBzdW0oWXRlc3QgIT0gWXRlc3QucHJlZCkvKDIqTikgICAKYGBgCgoKIyMjIExlYXN0IFNxYXVyZSBNZXRob2QgCgpgYGB7cn0KUmVnTW9kZWwgPSBsbShhcy5udW1lcmljKFl0cmFpbiktMSB+IHRyYWluZGF0YSkKWXRyYWluX3ByZWRfTFMgPSBhcy5udW1lcmljKFJlZ01vZGVsJGZpdHRlZCA+IDAuNSkKWXRlc3RfcHJlZF9MUyA9IFJlZ01vZGVsJGNvZWZbMV0gKyBSZWdNb2RlbCRjb2VmWzJdICogdGVzdGRhdGFbLDFdICsgUmVnTW9kZWwkY29lZlszXSAqIHRlc3RkYXRhWywyXQpZdGVzdF9wcmVkX0xTID0gYXMubnVtZXJpYyhZdGVzdF9wcmVkX0xTID4gMC41ICkKCiMjIGNyb3NzIHRhYiBmb3IgdHJhaW5pbmcgZGF0YSBhbmQgdHJhaW5pbmcgZXJyb3IKdGFibGUoWXRyYWluLCBZdHJhaW5fcHJlZF9MUyk7ICAgCnRyYWluLmVyci5MUyA9IHN1bShZdHJhaW4gIT0gIFl0cmFpbl9wcmVkX0xTKSAvICgyKm4pOyAgCgojIyBjcm9zcyB0YWIgZm9yIHRlc3QgZGF0YSBhbmQgdGVzdCBlcnJvcgp0YWJsZShZdGVzdCwgWXRlc3RfcHJlZF9MUyk7ICAgICAKdGVzdC5lcnIuTFMgPSBzdW0oWXRlc3QgIT0gIFl0ZXN0X3ByZWRfTFMpIC8gKDIqTik7CmBgYAoKIyMjIEJheWVzIEVycm9yIAoKYGBge3J9Cll0ZXN0X3ByZWRfQmF5ZXMgPSBhcy5udW1lcmljKDIqdGVzdGRhdGEgJSolIG1hdHJpeChtMS1tMCwgbnJvdz0yKSA+IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChzdW0obTFeMiktc3VtKG0wXjIpKSkKdGVzdC5lcnIuQmF5ZXMgPSBzdW0oWXRlc3QgIT0gIFl0ZXN0X3ByZWRfQmF5ZXMpIC8gKDIqTikKYGBgCgojIyMgUGxvdCB0aGUgUGVyZm9ybWFuY2UKClRlc3QgZXJyb3JzIGFyZSBpbiByZWQgYW5kIHRyYWluaW5nIGVycm9ycyBhcmUgaW4gYmx1ZS4gVGhlIHVwcGVyIHgtY29vcmRpbmF0ZSBpbmRpY2F0ZXMgdGhlIEsgdmFsdWVzLCBhbmQgdGhlIGxvd2VyIHgtY29vcmRpbmF0ZSBpbmRpY2F0ZXMgdGhlIGRlZ3JlZS1vZi1mcmVlZG9tIG9mIHRoZSBLTk4gcHJvY2VkdXJlcyBzbyB0aGUgbGFiZWxzIGFyZSByZWNpcHJvY2FsbHkgcmVsYXRlZCB0byBLLiAKClRoZSB0cmFpbmluZyBhbmQgdGVzdCBlcnJvcnMgZm9yIGxpbmVhciByZWdyZXNzaW9uIGFyZSBwbG90dGVkIGF0IGRmPTMgKGNvcnJlc3BvbmRpbmcgdG8gSyA9ICgybikvMyksIHNpbmNlIHRoZSBsaW5lYXIgbW9kZWwgaGFzIDMgcGFyYW1ldGVycywgaS5lLiwgMyBkZnMuIAoKVGhlIHRyYWluaW5nIGFuZCB0ZXN0IGVycm9ycyBmb3IgS05OIHdpdGggSyBjaG9zZW4gYnkgQ1YgYXJlIHBsb3R0ZWQgYXQgdGhlIGNob3NlIEsgdmFsdWVzLgoKYGBge3J9CnBsb3QoYygwLjUsbSksIHJhbmdlKHRlc3QuZXJyLkxTLCB0cmFpbi5lcnIuTFMsIHRlc3QuZXJyLmtubiwgdHJhaW4uZXJyLmtubiksCiAgICAgdHlwZT0ibiIsIHhsYWI9IkRlZ3JlZSBvZiBGcmVlZG9tIiwgeWxhYj0iRXJyb3IiLCB4YXh0PSJuIikKZGY9cm91bmQoKDIqbikvbXlrKQpheGlzKDEsIGF0PTE6bSwgbGFiZWxzPWRmKQpheGlzKDMsIGF0PTE6bSwgbGFiZWxzPW15aykKCnBvaW50cygxOm0sIHRlc3QuZXJyLmtubiwgY29sPSJyZWQiLCBwY2g9MSkKbGluZXMoMTptLCB0ZXN0LmVyci5rbm4sIGNvbD0icmVkIiwgbHR5PTEpCnBvaW50cygxOm0sIHRyYWluLmVyci5rbm4sIGNvbD0iYmx1ZSIsIHBjaD0xKQpsaW5lcygxOm0sIHRyYWluLmVyci5rbm4sIGNvbD0iYmx1ZSIsIGx0eT0yKQoKcG9pbnRzKDMsIHRyYWluLmVyci5MUywgcGNoPTIsIGNleD0yLCBjb2w9ImJsdWUiKQpwb2ludHMoMywgdGVzdC5lcnIuTFMsIHBjaD0yLCBjZXg9MiwgY29sPSJyZWQiKQoKYWJsaW5lKHRlc3QuZXJyLkJheWVzLCAwLCBjb2w9InB1cnBsZSIpCgpwb2ludHMoKDE6bSlbbXlrID09IGsuc3Rhcl0sIHRyYWluLmVyci5rbm4uQ1YsIGNvbD0iYmx1ZSIsIHBjaD0zLCBjZXg9MikKcG9pbnRzKCgxOm0pW215ayA9PSBrLnN0YXJdLCB0ZXN0LmVyci5rbm4uQ1YsIGNvbD0icmVkIiwgcGNoPTMsIGNleD0yKQpgYGAKCiMjIEV4YW1wbGUgSUkKCiMjIyBEYXRhIEdlbmVyYXRpb24KCkdlbmVyYXRlIHRoZSAyMCBjZW50ZXJzLCAxMCBmb3IgZWFjaCBncm91cC4KCmBgYHtyfQpjc2l6ZSA9IDEwICAgICAgICMgbnVtYmVyIG9mIGNlbnRlcnMKcCA9IDIgICAgICAgIApzID0gMSAgICAgICAgIyBzZCBmb3IgZ2VuZXJhdGluZyB0aGUgY2VudGVycyB3aXRoaW4gZWFjaCBjbGFzcyAgICAgICAgICAgICAgICAgICAgCm0xID0gbWF0cml4KHJub3JtKGNzaXplKnApLCBjc2l6ZSwgcCkqcyArIGNiaW5kKCByZXAoMSxjc2l6ZSksIHJlcCgwLGNzaXplKSApCm0wID0gbWF0cml4KHJub3JtKGNzaXplKnApLCBjc2l6ZSwgcCkqcyArIGNiaW5kKCByZXAoMCxjc2l6ZSksIHJlcCgxLGNzaXplKSApCmBgYAoKR2VuZXJhdGUgdHJhaW5pbmcgZGF0YS4KCmBgYHtyfQpuID0gMTAwCgojIEFsbG9jYXRlIHRoZSBuIHNhbXBsZXMgZm9yIGNsYXNzIDEgIHRvIHRoZSAxMCBjbHVzdGVycwppZDEgPSBzYW1wbGUoMTpjc2l6ZSwgbiwgcmVwbGFjZT1UUlVFKQojIEFsbG9jYXRlIHRoZSBuIHNhbXBsZXMgZm9yIGNsYXNzIDEgdG8gdGhlIDEwIGNsdXN0ZXJzCmlkMCA9IHNhbXBsZSgxOmNzaXplLCBuLCByZXBsYWNlPVRSVUUpICAKcyA9IHNxcnQoMS81KTsgICAgICAgICAgICAgICMgc2QgZm9yIGdlbmVyYXRpbmcgZGF0YS4gCgp0cmFpbmRhdGEgPSBtYXRyaXgocm5vcm0oMipuKnApLCAyKm4sIHApKnMgKyAKICByYmluZChtMVtpZDEsXSwgbTBbaWQwLF0pCmRpbSh0cmFpbmRhdGEpCll0cmFpbiA9IGZhY3RvcihjKHJlcCgxLG4pLCByZXAoMCxuKSkpCmBgYAoKIyMjIFZpc3VsaXphdGlvbgoKYGBge3J9CnBsb3QodHJhaW5kYXRhWywxXSwgdHJhaW5kYXRhWywyXSwgdHlwZT0ibiIsIHhsYWI9IiIsIHlsYWI9IiIpCnBvaW50cyh0cmFpbmRhdGFbMTpuLDFdLCB0cmFpbmRhdGFbMTpuLDJdLCBjb2w9ImJsdWUiKQpwb2ludHModHJhaW5kYXRhWyhuKzEpOigyKm4pLDFdLCB0cmFpbmRhdGFbKG4rMSk6KDIqbiksMl0sIGNvbD0icmVkIikKcG9pbnRzKG0xWzE6Y3NpemUsMV0sIG0xWzE6Y3NpemUsMl0sIHBjaD0iKyIsIGNleD0xLjUsIGNvbD0iYmx1ZSIpCnBvaW50cyhtMFsxOmNzaXplLDFdLCBtMFsxOmNzaXplLDJdLCBwY2g9IisiLCBjZXg9MS41LCBjb2w9InJlZCIpOyAgIApsZWdlbmQoImJvdHRvbXJpZ2h0IiwgcGNoID0gYygxLDEpLCBjb2wgPSBjKCJyZWQiLCAiYmx1ZSIpLCBsZWdlbmQgPSBjKCJjbGFzcyAxIiwgImNsYXNzIDAiKSkKYGBgCg==