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==