Question 5

a, b

set.seed(1)
x1 <- runif(500) - 0.5
x2 <- runif(500) - 0.5
y <- 1 * (x1^2 - x2^2 > 0)

plot(x1, x2, col = (y + 1), xlab = "X1", ylab = "X2", main = "Simulated Dataset")

c, d

df <- data.frame(x1 = x1, x2 = x2, y = as.factor(y))
glm.fit <- glm(y ~ x1 + x2, data = df, family = binomial)

glm.prob <- predict(glm.fit, df, type = "response")
glm.pred <- ifelse(glm.prob > 0.5, 1, 0)

plot(x1, x2, col = (glm.pred + 1), xlab = "X1", ylab = "X2", main = "Linear Logistic Regression Predictions")

Linear logistic regression fails to capture the non-linear decision boundary.

e, f

glm.nl.fit <- glm(y ~ x1 + x2 + I(x1^2) + I(x2^2) + I(x1*x2), data = df, family = binomial)

glm.nl.prob <- predict(glm.nl.fit, df, type = "response")
glm.nl.pred <- ifelse(glm.nl.prob > 0.5, 1, 0)

plot(x1, x2, col = (glm.nl.pred + 1), xlab = "X1", ylab = "X2", main = "Nonlinear Logistic Regression Predictions")

Adding quadratic terms allows logistic regression to model the curved boundary.

g, h

library(e1071)

# Support Vector Classifier
svc.fit <- svm(y ~ x1 + x2, data = df, kernel = "linear", cost = 1)
svc.pred <- predict(svc.fit)

plot(x1, x2, col = as.integer(svc.pred), xlab = "X1", ylab = "X2", main = "Support Vector Classifier Predictions")


# Support Vector Machine
svm.fit <- svm(y ~ x1 + x2, data = df, kernel = "radial", cost = 1)
svm.pred <- predict(svm.fit)

plot(x1, x2, col = as.integer(svm.pred), xlab = "X1", ylab = "X2", main = "Radial SVM Predictions")

The Support Vector Classifier with a linear kernel fails to model the non linear decision boundary. The Support Vector Machine with a radial kernel does model the non linear decision boundary.

i

Support Vector Machines with non linear kernels can model non linear boundaries automatically without requiring manual feature transforms. Logistic regression can also model non linear boundaries but requires the user to explicitly specify non linear and interactions in the formula.


Question 7

a

library(ISLR2)

Auto.data <- Auto
Auto.data$mpg01 <- as.factor(ifelse(Auto.data$mpg > median(Auto.data$mpg), 1, 0))
Auto.data$mpg <- NULL

b

set.seed(1)
tune.svc <- tune(svm, mpg01 ~ ., data = Auto.data, kernel = "linear",
                 ranges = list(cost = c(0.01, 0.1, 1, 10, 100)))
summary(tune.svc)

Parameter tuning of ‘svm’:

- sampling method: 10-fold cross validation 

- best parameters:

- best performance: 0.08673077 

- Detailed performance results:
NA

The optimal cost value is 1e-01, which achieves the lowest cross-validation error rate of 0.089.

c

set.seed(1)

# Radial Kernel
tune.rad <- tune(svm, mpg01 ~ ., data = Auto.data, kernel = "radial",
                 ranges = list(cost = c(0.1, 1, 10, 100), gamma = c(0.01, 0.1, 1, 5)))
summary(tune.rad)

Parameter tuning of ‘svm’:

- sampling method: 10-fold cross validation 

- best parameters:

- best performance: 0.07897436 

- Detailed performance results:
# Polynomial Kernel
tune.poly <- tune(svm, mpg01 ~ ., data = Auto.data, kernel = "polynomial",
                  ranges = list(cost = c(0.1, 1, 10, 100), degree = c(2, 3, 4)))
summary(tune.poly)

Parameter tuning of ‘svm’:

- sampling method: 10-fold cross validation 

- best parameters:

- best performance: 0.3163462 

- Detailed performance results:
NA

For the radial kernel, the optimal parameters are cost = 10 and gamma = 1, achieving a cross-validation error rate of 0.079. For the polynomial kernel, the optimal parameters are cost = 100 and degree = 2, achieving a cross-validation error rate of 0.31.

d

best.linear <- tune.svc$best.model
best.radial <- tune.rad$best.model
best.poly <- tune.poly$best.model

plot(best.linear, Auto.data, horsepower ~ weight, main = "Support Vector Classifier Decision Boundary")

plot(best.radial, Auto.data, horsepower ~ weight, main = "Radial SVM Decision Boundary")

plot(best.poly, Auto.data, horsepower ~ weight, main = "Polynomial SVM Decision Boundary")


Question 8

a

set.seed(1)

train_idx <- 1:800
train_set <- OJ[train_idx, ]
test_set <- OJ[-train_idx, ]

b

svc.fit <- svm(Purchase ~ ., data = train_set, kernel = "linear", cost = 0.01)
summary(svc.fit)

Call:
svm(formula = Purchase ~ ., data = train_set, kernel = "linear", cost = 0.01)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  linear 
       cost:  0.01 

Number of Support Vectors:  424

 ( 212 212 )


Number of Classes:  2 

Levels: 
 CH MM

The model selects 424 support vectors

c

train.pred <- predict(svc.fit, train_set)
test.pred <- predict(svc.fit, test_set)

svc.train.err <- mean(train.pred != train_set$Purchase)
svc.test.err <- mean(test.pred != test_set$Purchase)

cat("Support Vector Classifier (cost = 0.01) Training Error Rate:", svc.train.err, "\n")
Support Vector Classifier (cost = 0.01) Training Error Rate: 0.155 
cat("Support Vector Classifier (cost = 0.01) Test Error Rate:", svc.test.err, "\n")
Support Vector Classifier (cost = 0.01) Test Error Rate: 0.2074074 

The training error rate is 15.5% and the test error rate is 20.74%.

d

set.seed(1)
tune.svc <- tune(svm, Purchase ~ ., data = train_set, kernel = "linear",
                 ranges = list(cost = c(0.01, 0.05, 0.1, 0.5, 1, 5, 10)))
summary(tune.svc)

Parameter tuning of ‘svm’:

- sampling method: 10-fold cross validation 

- best parameters:

- best performance: 0.15375 

- Detailed performance results:
NA

The optimal cost value selected by cross-validation is .01.

e

best.svc <- tune.svc$best.model

svc.tuned.train.pred <- predict(best.svc, train_set)
svc.tuned.test.pred <- predict(best.svc, test_set)

svc.tuned.train.err <- mean(svc.tuned.train.pred != train_set$Purchase)
svc.tuned.test.err <- mean(svc.tuned.test.pred != test_set$Purchase)

cat("Tuned Support Vector Classifier Training Error Rate:", svc.tuned.train.err, "\n")
Tuned Support Vector Classifier Training Error Rate: 0.155 
cat("Tuned Support Vector Classifier Test Error Rate:", svc.tuned.test.err, "\n")
Tuned Support Vector Classifier Test Error Rate: 0.2074074 

Tuning the model results in a training error rate to 15.5% and the test error rate to 20.74%.

f

# 1. Fit radial model with cost = 0.01
rad.fit <- svm(Purchase ~ ., data = train_set, kernel = "radial", cost = 0.01)
summary(rad.fit)

Call:
svm(formula = Purchase ~ ., data = train_set, kernel = "radial", cost = 0.01)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  0.01 

Number of Support Vectors:  608

 ( 305 303 )


Number of Classes:  2 

Levels: 
 CH MM
rad.train.err <- mean(predict(rad.fit, train_set) != train_set$Purchase)
rad.test.err <- mean(predict(rad.fit, test_set) != test_set$Purchase)
cat("Radial SVM (cost = 0.01) Training Error Rate:", rad.train.err, "\n")
Radial SVM (cost = 0.01) Training Error Rate: 0.37875 
cat("Radial SVM (cost = 0.01) Test Error Rate:", rad.test.err, "\n")
Radial SVM (cost = 0.01) Test Error Rate: 0.4222222 
# 2. Tune radial model
set.seed(1)
tune.rad <- tune(svm, Purchase ~ ., data = train_set, kernel = "radial",
                 ranges = list(cost = c(0.01, 0.05, 0.1, 0.5, 1, 5, 10)))
summary(tune.rad)

Parameter tuning of ‘svm’:

- sampling method: 10-fold cross validation 

- best parameters:

- best performance: 0.16625 

- Detailed performance results:
best.rad <- tune.rad$best.model
rad.tuned.train.err <- mean(predict(best.rad, train_set) != train_set$Purchase)
rad.tuned.test.err <- mean(predict(best.rad, test_set) != test_set$Purchase)
cat("Tuned Radial SVM Training Error Rate:", rad.tuned.train.err, "\n")
Tuned Radial SVM Training Error Rate: 0.15375 
cat("Tuned Radial SVM Test Error Rate:", rad.tuned.test.err, "\n")
Tuned Radial SVM Test Error Rate: 0.1777778 

For the radial SVM: * The un-tuned model (cost = 0.01) has a training error of 38.38% and a test error of 41.11% (defaulting to the majority class prediction). * Cross-validation selects an optimal cost value of 0.5. * The tuned radial model achieves a training error of 14.88% and a test error of 17.78%.

g


poly.fit <- svm(Purchase ~ ., data = train_set, kernel = "polynomial", degree = 2, cost = 0.01)
summary(poly.fit)

Call:
svm(formula = Purchase ~ ., data = train_set, kernel = "polynomial", degree = 2, 
    cost = 0.01)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  polynomial 
       cost:  0.01 
     degree:  2 
     coef.0:  0 

Number of Support Vectors:  612

 ( 309 303 )


Number of Classes:  2 

Levels: 
 CH MM
poly.train.err <- mean(predict(poly.fit, train_set) != train_set$Purchase)
poly.test.err <- mean(predict(poly.fit, test_set) != test_set$Purchase)
cat("Polynomial SVM (cost = 0.01, degree = 2) Training Error Rate:", poly.train.err, "\n")
Polynomial SVM (cost = 0.01, degree = 2) Training Error Rate: 0.37875 
cat("Polynomial SVM (cost = 0.01, degree = 2) Test Error Rate:", poly.test.err, "\n")
Polynomial SVM (cost = 0.01, degree = 2) Test Error Rate: 0.4222222 
set.seed(1)
tune.poly <- tune(svm, Purchase ~ ., data = train_set, kernel = "polynomial", degree = 2,
                  ranges = list(cost = c(0.01, 0.05, 0.1, 0.5, 1, 5, 10)))
summary(tune.poly)

Parameter tuning of ‘svm’:

- sampling method: 10-fold cross validation 

- best parameters:

- best performance: 0.18 

- Detailed performance results:
best.poly <- tune.poly$best.model
poly.tuned.train.err <- mean(predict(best.poly, train_set) != train_set$Purchase)
poly.tuned.test.err <- mean(predict(best.poly, test_set) != test_set$Purchase)
cat("Tuned Polynomial SVM Training Error Rate:", poly.tuned.train.err, "\n")
Tuned Polynomial SVM Training Error Rate: 0.155 
cat("Tuned Polynomial SVM Test Error Rate:", poly.tuned.test.err, "\n")
Tuned Polynomial SVM Test Error Rate: 0.1851852 

For the polynomial SVM: * The un-tuned model (cost = 0.01) has a training error of 37.87% and a test error of 42.22%. * Cross-validation selects an optimal cost value of 5. The tuned polynomial model achieves a training error of 15.5% and a test error of 18.52%.

h

The tuned radial Support Vector Machine performs best on the test set, achieving the lowest test error rate.

LS0tCnRpdGxlOiAiQ2hyaXMgU2VycmFubyAtIEFzc2lnbm1lbnQgOCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgZWNobzogdHJ1ZQplZGl0b3Jfb3B0aW9uczogCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKLS0tCgojIyBRdWVzdGlvbiA1CgojIyMgYSwgYgoKYGBge3J9CnNldC5zZWVkKDEpCngxIDwtIHJ1bmlmKDUwMCkgLSAwLjUKeDIgPC0gcnVuaWYoNTAwKSAtIDAuNQp5IDwtIDEgKiAoeDFeMiAtIHgyXjIgPiAwKQoKcGxvdCh4MSwgeDIsIGNvbCA9ICh5ICsgMSksIHhsYWIgPSAiWDEiLCB5bGFiID0gIlgyIiwgbWFpbiA9ICJTaW11bGF0ZWQgRGF0YXNldCIpCmBgYAoKIyMjIGMsIGQKCmBgYHtyfQpkZiA8LSBkYXRhLmZyYW1lKHgxID0geDEsIHgyID0geDIsIHkgPSBhcy5mYWN0b3IoeSkpCmdsbS5maXQgPC0gZ2xtKHkgfiB4MSArIHgyLCBkYXRhID0gZGYsIGZhbWlseSA9IGJpbm9taWFsKQoKZ2xtLnByb2IgPC0gcHJlZGljdChnbG0uZml0LCBkZiwgdHlwZSA9ICJyZXNwb25zZSIpCmdsbS5wcmVkIDwtIGlmZWxzZShnbG0ucHJvYiA+IDAuNSwgMSwgMCkKCnBsb3QoeDEsIHgyLCBjb2wgPSAoZ2xtLnByZWQgKyAxKSwgeGxhYiA9ICJYMSIsIHlsYWIgPSAiWDIiLCBtYWluID0gIkxpbmVhciBMb2dpc3RpYyBSZWdyZXNzaW9uIFByZWRpY3Rpb25zIikKYGBgCgpMaW5lYXIgbG9naXN0aWMgcmVncmVzc2lvbiBmYWlscyB0byBjYXB0dXJlIHRoZSBub24tbGluZWFyIGRlY2lzaW9uCmJvdW5kYXJ5LgoKIyMjIGUsIGYKCmBgYHtyfQpnbG0ubmwuZml0IDwtIGdsbSh5IH4geDEgKyB4MiArIEkoeDFeMikgKyBJKHgyXjIpICsgSSh4MSp4MiksIGRhdGEgPSBkZiwgZmFtaWx5ID0gYmlub21pYWwpCgpnbG0ubmwucHJvYiA8LSBwcmVkaWN0KGdsbS5ubC5maXQsIGRmLCB0eXBlID0gInJlc3BvbnNlIikKZ2xtLm5sLnByZWQgPC0gaWZlbHNlKGdsbS5ubC5wcm9iID4gMC41LCAxLCAwKQoKcGxvdCh4MSwgeDIsIGNvbCA9IChnbG0ubmwucHJlZCArIDEpLCB4bGFiID0gIlgxIiwgeWxhYiA9ICJYMiIsIG1haW4gPSAiTm9ubGluZWFyIExvZ2lzdGljIFJlZ3Jlc3Npb24gUHJlZGljdGlvbnMiKQpgYGAKCkFkZGluZyBxdWFkcmF0aWMgdGVybXMgYWxsb3dzIGxvZ2lzdGljIHJlZ3Jlc3Npb24gdG8gbW9kZWwgdGhlIGN1cnZlZApib3VuZGFyeS4KCiMjIyBnLCBoCgpgYGB7cn0KbGlicmFyeShlMTA3MSkKCiMgU3VwcG9ydCBWZWN0b3IgQ2xhc3NpZmllcgpzdmMuZml0IDwtIHN2bSh5IH4geDEgKyB4MiwgZGF0YSA9IGRmLCBrZXJuZWwgPSAibGluZWFyIiwgY29zdCA9IDEpCnN2Yy5wcmVkIDwtIHByZWRpY3Qoc3ZjLmZpdCkKCnBsb3QoeDEsIHgyLCBjb2wgPSBhcy5pbnRlZ2VyKHN2Yy5wcmVkKSwgeGxhYiA9ICJYMSIsIHlsYWIgPSAiWDIiLCBtYWluID0gIlN1cHBvcnQgVmVjdG9yIENsYXNzaWZpZXIgUHJlZGljdGlvbnMiKQoKIyBTdXBwb3J0IFZlY3RvciBNYWNoaW5lCnN2bS5maXQgPC0gc3ZtKHkgfiB4MSArIHgyLCBkYXRhID0gZGYsIGtlcm5lbCA9ICJyYWRpYWwiLCBjb3N0ID0gMSkKc3ZtLnByZWQgPC0gcHJlZGljdChzdm0uZml0KQoKcGxvdCh4MSwgeDIsIGNvbCA9IGFzLmludGVnZXIoc3ZtLnByZWQpLCB4bGFiID0gIlgxIiwgeWxhYiA9ICJYMiIsIG1haW4gPSAiUmFkaWFsIFNWTSBQcmVkaWN0aW9ucyIpCmBgYAoKVGhlIFN1cHBvcnQgVmVjdG9yIENsYXNzaWZpZXIgd2l0aCBhIGxpbmVhciBrZXJuZWwgZmFpbHMgdG8gbW9kZWwgdGhlCm5vbiBsaW5lYXIgZGVjaXNpb24gYm91bmRhcnkuIFRoZSBTdXBwb3J0IFZlY3RvciBNYWNoaW5lIHdpdGggYSByYWRpYWwKa2VybmVsIGRvZXMgbW9kZWwgdGhlIG5vbiBsaW5lYXIgZGVjaXNpb24gYm91bmRhcnkuCgojIyMgaQoKU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMgd2l0aCBub24gbGluZWFyIGtlcm5lbHMgY2FuIG1vZGVsIG5vbiBsaW5lYXIKYm91bmRhcmllcyBhdXRvbWF0aWNhbGx5IHdpdGhvdXQgcmVxdWlyaW5nIG1hbnVhbCBmZWF0dXJlIHRyYW5zZm9ybXMuCkxvZ2lzdGljIHJlZ3Jlc3Npb24gY2FuIGFsc28gbW9kZWwgbm9uIGxpbmVhciBib3VuZGFyaWVzIGJ1dCByZXF1aXJlcwp0aGUgdXNlciB0byBleHBsaWNpdGx5IHNwZWNpZnkgbm9uIGxpbmVhciBhbmQgaW50ZXJhY3Rpb25zIGluIHRoZQpmb3JtdWxhLgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBRdWVzdGlvbiA3CgojIyMgYQoKYGBge3J9CmxpYnJhcnkoSVNMUjIpCgpBdXRvLmRhdGEgPC0gQXV0bwpBdXRvLmRhdGEkbXBnMDEgPC0gYXMuZmFjdG9yKGlmZWxzZShBdXRvLmRhdGEkbXBnID4gbWVkaWFuKEF1dG8uZGF0YSRtcGcpLCAxLCAwKSkKQXV0by5kYXRhJG1wZyA8LSBOVUxMCmBgYAoKIyMjIGIKCmBgYHtyfQpzZXQuc2VlZCgxKQp0dW5lLnN2YyA8LSB0dW5lKHN2bSwgbXBnMDEgfiAuLCBkYXRhID0gQXV0by5kYXRhLCBrZXJuZWwgPSAibGluZWFyIiwKICAgICAgICAgICAgICAgICByYW5nZXMgPSBsaXN0KGNvc3QgPSBjKDAuMDEsIDAuMSwgMSwgMTAsIDEwMCkpKQpzdW1tYXJ5KHR1bmUuc3ZjKQpgYGAKClRoZSBvcHRpbWFsIGNvc3QgdmFsdWUgaXMgMWUtMDEsIHdoaWNoIGFjaGlldmVzIHRoZSBsb3dlc3QKY3Jvc3MtdmFsaWRhdGlvbiBlcnJvciByYXRlIG9mIDAuMDg5LgoKIyMjIGMKCmBgYHtyfQpzZXQuc2VlZCgxKQoKIyBSYWRpYWwgS2VybmVsCnR1bmUucmFkIDwtIHR1bmUoc3ZtLCBtcGcwMSB+IC4sIGRhdGEgPSBBdXRvLmRhdGEsIGtlcm5lbCA9ICJyYWRpYWwiLAogICAgICAgICAgICAgICAgIHJhbmdlcyA9IGxpc3QoY29zdCA9IGMoMC4xLCAxLCAxMCwgMTAwKSwgZ2FtbWEgPSBjKDAuMDEsIDAuMSwgMSwgNSkpKQpzdW1tYXJ5KHR1bmUucmFkKQoKIyBQb2x5bm9taWFsIEtlcm5lbAp0dW5lLnBvbHkgPC0gdHVuZShzdm0sIG1wZzAxIH4gLiwgZGF0YSA9IEF1dG8uZGF0YSwga2VybmVsID0gInBvbHlub21pYWwiLAogICAgICAgICAgICAgICAgICByYW5nZXMgPSBsaXN0KGNvc3QgPSBjKDAuMSwgMSwgMTAsIDEwMCksIGRlZ3JlZSA9IGMoMiwgMywgNCkpKQpzdW1tYXJ5KHR1bmUucG9seSkKYGBgCgpGb3IgdGhlIHJhZGlhbCBrZXJuZWwsIHRoZSBvcHRpbWFsIHBhcmFtZXRlcnMgYXJlIGNvc3QgPSAxMCBhbmQgZ2FtbWEgPQoxLCBhY2hpZXZpbmcgYSBjcm9zcy12YWxpZGF0aW9uIGVycm9yIHJhdGUgb2YgMC4wNzkuIEZvciB0aGUgcG9seW5vbWlhbAprZXJuZWwsIHRoZSBvcHRpbWFsIHBhcmFtZXRlcnMgYXJlIGNvc3QgPSAxMDAgYW5kIGRlZ3JlZSA9IDIsIGFjaGlldmluZwphIGNyb3NzLXZhbGlkYXRpb24gZXJyb3IgcmF0ZSBvZiAwLjMxLgoKIyMjIGQKCmBgYHtyfQpiZXN0LmxpbmVhciA8LSB0dW5lLnN2YyRiZXN0Lm1vZGVsCmJlc3QucmFkaWFsIDwtIHR1bmUucmFkJGJlc3QubW9kZWwKYmVzdC5wb2x5IDwtIHR1bmUucG9seSRiZXN0Lm1vZGVsCgpwbG90KGJlc3QubGluZWFyLCBBdXRvLmRhdGEsIGhvcnNlcG93ZXIgfiB3ZWlnaHQsIG1haW4gPSAiU3VwcG9ydCBWZWN0b3IgQ2xhc3NpZmllciBEZWNpc2lvbiBCb3VuZGFyeSIpCnBsb3QoYmVzdC5yYWRpYWwsIEF1dG8uZGF0YSwgaG9yc2Vwb3dlciB+IHdlaWdodCwgbWFpbiA9ICJSYWRpYWwgU1ZNIERlY2lzaW9uIEJvdW5kYXJ5IikKcGxvdChiZXN0LnBvbHksIEF1dG8uZGF0YSwgaG9yc2Vwb3dlciB+IHdlaWdodCwgbWFpbiA9ICJQb2x5bm9taWFsIFNWTSBEZWNpc2lvbiBCb3VuZGFyeSIpCmBgYAoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBRdWVzdGlvbiA4CgojIyMgYQoKYGBge3J9CnNldC5zZWVkKDEpCgp0cmFpbl9pZHggPC0gMTo4MDAKdHJhaW5fc2V0IDwtIE9KW3RyYWluX2lkeCwgXQp0ZXN0X3NldCA8LSBPSlstdHJhaW5faWR4LCBdCmBgYAoKIyMjIGIKCmBgYHtyfQpzdmMuZml0IDwtIHN2bShQdXJjaGFzZSB+IC4sIGRhdGEgPSB0cmFpbl9zZXQsIGtlcm5lbCA9ICJsaW5lYXIiLCBjb3N0ID0gMC4wMSkKc3VtbWFyeShzdmMuZml0KQpgYGAKClRoZSBtb2RlbCBzZWxlY3RzIDQyNCBzdXBwb3J0IHZlY3RvcnMKCiMjIyBjCgpgYGB7cn0KdHJhaW4ucHJlZCA8LSBwcmVkaWN0KHN2Yy5maXQsIHRyYWluX3NldCkKdGVzdC5wcmVkIDwtIHByZWRpY3Qoc3ZjLmZpdCwgdGVzdF9zZXQpCgpzdmMudHJhaW4uZXJyIDwtIG1lYW4odHJhaW4ucHJlZCAhPSB0cmFpbl9zZXQkUHVyY2hhc2UpCnN2Yy50ZXN0LmVyciA8LSBtZWFuKHRlc3QucHJlZCAhPSB0ZXN0X3NldCRQdXJjaGFzZSkKCmNhdCgiU3VwcG9ydCBWZWN0b3IgQ2xhc3NpZmllciAoY29zdCA9IDAuMDEpIFRyYWluaW5nIEVycm9yIFJhdGU6Iiwgc3ZjLnRyYWluLmVyciwgIlxuIikKY2F0KCJTdXBwb3J0IFZlY3RvciBDbGFzc2lmaWVyIChjb3N0ID0gMC4wMSkgVGVzdCBFcnJvciBSYXRlOiIsIHN2Yy50ZXN0LmVyciwgIlxuIikKYGBgCgpUaGUgdHJhaW5pbmcgZXJyb3IgcmF0ZSBpcyAxNS41JSBhbmQgdGhlIHRlc3QgZXJyb3IgcmF0ZSBpcyAyMC43NCUuCgojIyMgZAoKYGBge3J9CnNldC5zZWVkKDEpCnR1bmUuc3ZjIDwtIHR1bmUoc3ZtLCBQdXJjaGFzZSB+IC4sIGRhdGEgPSB0cmFpbl9zZXQsIGtlcm5lbCA9ICJsaW5lYXIiLAogICAgICAgICAgICAgICAgIHJhbmdlcyA9IGxpc3QoY29zdCA9IGMoMC4wMSwgMC4wNSwgMC4xLCAwLjUsIDEsIDUsIDEwKSkpCnN1bW1hcnkodHVuZS5zdmMpCmBgYAoKVGhlIG9wdGltYWwgY29zdCB2YWx1ZSBzZWxlY3RlZCBieSBjcm9zcy12YWxpZGF0aW9uIGlzIC4wMS4KCiMjIyBlCgpgYGB7cn0KYmVzdC5zdmMgPC0gdHVuZS5zdmMkYmVzdC5tb2RlbAoKc3ZjLnR1bmVkLnRyYWluLnByZWQgPC0gcHJlZGljdChiZXN0LnN2YywgdHJhaW5fc2V0KQpzdmMudHVuZWQudGVzdC5wcmVkIDwtIHByZWRpY3QoYmVzdC5zdmMsIHRlc3Rfc2V0KQoKc3ZjLnR1bmVkLnRyYWluLmVyciA8LSBtZWFuKHN2Yy50dW5lZC50cmFpbi5wcmVkICE9IHRyYWluX3NldCRQdXJjaGFzZSkKc3ZjLnR1bmVkLnRlc3QuZXJyIDwtIG1lYW4oc3ZjLnR1bmVkLnRlc3QucHJlZCAhPSB0ZXN0X3NldCRQdXJjaGFzZSkKCmNhdCgiVHVuZWQgU3VwcG9ydCBWZWN0b3IgQ2xhc3NpZmllciBUcmFpbmluZyBFcnJvciBSYXRlOiIsIHN2Yy50dW5lZC50cmFpbi5lcnIsICJcbiIpCmNhdCgiVHVuZWQgU3VwcG9ydCBWZWN0b3IgQ2xhc3NpZmllciBUZXN0IEVycm9yIFJhdGU6Iiwgc3ZjLnR1bmVkLnRlc3QuZXJyLCAiXG4iKQpgYGAKClR1bmluZyB0aGUgbW9kZWwgcmVzdWx0cyBpbiBhIHRyYWluaW5nIGVycm9yIHJhdGUgdG8gMTUuNSUgYW5kIHRoZSB0ZXN0CmVycm9yIHJhdGUgdG8gMjAuNzQlLgoKIyMjIGYKCmBgYHtyfQojIDEuIEZpdCByYWRpYWwgbW9kZWwgd2l0aCBjb3N0ID0gMC4wMQpyYWQuZml0IDwtIHN2bShQdXJjaGFzZSB+IC4sIGRhdGEgPSB0cmFpbl9zZXQsIGtlcm5lbCA9ICJyYWRpYWwiLCBjb3N0ID0gMC4wMSkKc3VtbWFyeShyYWQuZml0KQoKcmFkLnRyYWluLmVyciA8LSBtZWFuKHByZWRpY3QocmFkLmZpdCwgdHJhaW5fc2V0KSAhPSB0cmFpbl9zZXQkUHVyY2hhc2UpCnJhZC50ZXN0LmVyciA8LSBtZWFuKHByZWRpY3QocmFkLmZpdCwgdGVzdF9zZXQpICE9IHRlc3Rfc2V0JFB1cmNoYXNlKQpjYXQoIlJhZGlhbCBTVk0gKGNvc3QgPSAwLjAxKSBUcmFpbmluZyBFcnJvciBSYXRlOiIsIHJhZC50cmFpbi5lcnIsICJcbiIpCmNhdCgiUmFkaWFsIFNWTSAoY29zdCA9IDAuMDEpIFRlc3QgRXJyb3IgUmF0ZToiLCByYWQudGVzdC5lcnIsICJcbiIpCgojIDIuIFR1bmUgcmFkaWFsIG1vZGVsCnNldC5zZWVkKDEpCnR1bmUucmFkIDwtIHR1bmUoc3ZtLCBQdXJjaGFzZSB+IC4sIGRhdGEgPSB0cmFpbl9zZXQsIGtlcm5lbCA9ICJyYWRpYWwiLAogICAgICAgICAgICAgICAgIHJhbmdlcyA9IGxpc3QoY29zdCA9IGMoMC4wMSwgMC4wNSwgMC4xLCAwLjUsIDEsIDUsIDEwKSkpCnN1bW1hcnkodHVuZS5yYWQpCgpiZXN0LnJhZCA8LSB0dW5lLnJhZCRiZXN0Lm1vZGVsCnJhZC50dW5lZC50cmFpbi5lcnIgPC0gbWVhbihwcmVkaWN0KGJlc3QucmFkLCB0cmFpbl9zZXQpICE9IHRyYWluX3NldCRQdXJjaGFzZSkKcmFkLnR1bmVkLnRlc3QuZXJyIDwtIG1lYW4ocHJlZGljdChiZXN0LnJhZCwgdGVzdF9zZXQpICE9IHRlc3Rfc2V0JFB1cmNoYXNlKQpjYXQoIlR1bmVkIFJhZGlhbCBTVk0gVHJhaW5pbmcgRXJyb3IgUmF0ZToiLCByYWQudHVuZWQudHJhaW4uZXJyLCAiXG4iKQpjYXQoIlR1bmVkIFJhZGlhbCBTVk0gVGVzdCBFcnJvciBSYXRlOiIsIHJhZC50dW5lZC50ZXN0LmVyciwgIlxuIikKYGBgCgpGb3IgdGhlIHJhZGlhbCBTVk06IFwqIFRoZSB1bi10dW5lZCBtb2RlbCAoY29zdCA9IDAuMDEpIGhhcyBhIHRyYWluaW5nCmVycm9yIG9mIDM4LjM4JSBhbmQgYSB0ZXN0IGVycm9yIG9mIDQxLjExJSAoZGVmYXVsdGluZyB0byB0aGUgbWFqb3JpdHkKY2xhc3MgcHJlZGljdGlvbikuIFwqIENyb3NzLXZhbGlkYXRpb24gc2VsZWN0cyBhbiBvcHRpbWFsIGNvc3QgdmFsdWUgb2YKMC41LiBcKiBUaGUgdHVuZWQgcmFkaWFsIG1vZGVsIGFjaGlldmVzIGEgdHJhaW5pbmcgZXJyb3Igb2YgMTQuODglIGFuZCBhCnRlc3QgZXJyb3Igb2YgMTcuNzglLgoKIyMjIGcKCmBgYHtyfQoKcG9seS5maXQgPC0gc3ZtKFB1cmNoYXNlIH4gLiwgZGF0YSA9IHRyYWluX3NldCwga2VybmVsID0gInBvbHlub21pYWwiLCBkZWdyZWUgPSAyLCBjb3N0ID0gMC4wMSkKc3VtbWFyeShwb2x5LmZpdCkKCnBvbHkudHJhaW4uZXJyIDwtIG1lYW4ocHJlZGljdChwb2x5LmZpdCwgdHJhaW5fc2V0KSAhPSB0cmFpbl9zZXQkUHVyY2hhc2UpCnBvbHkudGVzdC5lcnIgPC0gbWVhbihwcmVkaWN0KHBvbHkuZml0LCB0ZXN0X3NldCkgIT0gdGVzdF9zZXQkUHVyY2hhc2UpCmNhdCgiUG9seW5vbWlhbCBTVk0gKGNvc3QgPSAwLjAxLCBkZWdyZWUgPSAyKSBUcmFpbmluZyBFcnJvciBSYXRlOiIsIHBvbHkudHJhaW4uZXJyLCAiXG4iKQpjYXQoIlBvbHlub21pYWwgU1ZNIChjb3N0ID0gMC4wMSwgZGVncmVlID0gMikgVGVzdCBFcnJvciBSYXRlOiIsIHBvbHkudGVzdC5lcnIsICJcbiIpCgpzZXQuc2VlZCgxKQp0dW5lLnBvbHkgPC0gdHVuZShzdm0sIFB1cmNoYXNlIH4gLiwgZGF0YSA9IHRyYWluX3NldCwga2VybmVsID0gInBvbHlub21pYWwiLCBkZWdyZWUgPSAyLAogICAgICAgICAgICAgICAgICByYW5nZXMgPSBsaXN0KGNvc3QgPSBjKDAuMDEsIDAuMDUsIDAuMSwgMC41LCAxLCA1LCAxMCkpKQpzdW1tYXJ5KHR1bmUucG9seSkKCmJlc3QucG9seSA8LSB0dW5lLnBvbHkkYmVzdC5tb2RlbApwb2x5LnR1bmVkLnRyYWluLmVyciA8LSBtZWFuKHByZWRpY3QoYmVzdC5wb2x5LCB0cmFpbl9zZXQpICE9IHRyYWluX3NldCRQdXJjaGFzZSkKcG9seS50dW5lZC50ZXN0LmVyciA8LSBtZWFuKHByZWRpY3QoYmVzdC5wb2x5LCB0ZXN0X3NldCkgIT0gdGVzdF9zZXQkUHVyY2hhc2UpCmNhdCgiVHVuZWQgUG9seW5vbWlhbCBTVk0gVHJhaW5pbmcgRXJyb3IgUmF0ZToiLCBwb2x5LnR1bmVkLnRyYWluLmVyciwgIlxuIikKY2F0KCJUdW5lZCBQb2x5bm9taWFsIFNWTSBUZXN0IEVycm9yIFJhdGU6IiwgcG9seS50dW5lZC50ZXN0LmVyciwgIlxuIikKYGBgCgpGb3IgdGhlIHBvbHlub21pYWwgU1ZNOiBcKiBUaGUgdW4tdHVuZWQgbW9kZWwgKGNvc3QgPSAwLjAxKSBoYXMgYQp0cmFpbmluZyBlcnJvciBvZiAzNy44NyUgYW5kIGEgdGVzdCBlcnJvciBvZiA0Mi4yMiUuIFwqIENyb3NzLXZhbGlkYXRpb24Kc2VsZWN0cyBhbiBvcHRpbWFsIGNvc3QgdmFsdWUgb2YgNS4gVGhlIHR1bmVkIHBvbHlub21pYWwgbW9kZWwgYWNoaWV2ZXMKYSB0cmFpbmluZyBlcnJvciBvZiAxNS41JSBhbmQgYSB0ZXN0IGVycm9yIG9mIDE4LjUyJS4KCiMjIyBoCgpUaGUgdHVuZWQgcmFkaWFsIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUgcGVyZm9ybXMgYmVzdCBvbiB0aGUgdGVzdCBzZXQsCmFjaGlldmluZyB0aGUgbG93ZXN0IHRlc3QgZXJyb3IgcmF0ZS4K