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