Note: Some results may differ from the hard copy book due to the changing of sampling procedures introduced in R 3.6.0. See http://bit.ly/35D1SW7 for more details. Access and run the source code for this notebook here.

Hidden chapter requirements used in the book to set the plotting theme and load packages used in hidden code chunks:

# Set global knitr chunk options
knitr::opts_chunk$set(
  cache = FALSE,
  collapse = TRUE, 
  fig.align = "center",
  fig.height = 3.5,
  message = FALSE, 
  warning = FALSE
)

# Set the graphical theme
ggplot2::theme_set(ggplot2::theme_light())

# Increase output width
options(width = 120)

# Load required packages
library(ggplot2)
library(kernlab)
library(svmpath)

# Colors
dark2 <- RColorBrewer::brewer.pal(8, "Dark2")
set1 <- RColorBrewer::brewer.pal(9, "Set1")

# Plotting function; modified from svmpath::svmpath()
plot_svmpath <- function(x, step = max(x$Step), main = "") {
  
  # Extract model info
  object <- x
  f <- predict(object, lambda = object$lambda[step], type = "function")
  x <- object$x
  y <- object$y
  Elbow <- object$Elbow[[step]]
  alpha <- object$alpha[, step]
  alpha0 <- object$alpha0[step]
  lambda <- object$lambda[step]
  df <- as.data.frame(x[, 1L:2L])
  names(df) <- c("x1", "x2")
  df$y <- norm2d$y
  beta <- (alpha * y) %*% x

  # Construct plot
  ggplot(df, aes(x = x1, y = x2)) +
    geom_point(aes(shape = y, color = y), size = 3, alpha = 0.75) +
    xlab("Income (standardized)") +
    ylab("Lot size (standardized)") +
    xlim(-6, 6) +
    ylim(-6, 6) +
    coord_fixed() +
    theme(legend.position = "none") +
    theme_bw() +
    scale_shape_discrete(
      name = "Owns a riding\nmower?",
      breaks = c(1, 2),
      labels = c("Yes", "No")
    ) +
    scale_color_brewer(
      name = "Owns a riding\nmower?",
      palette = "Dark2",
      breaks = c(1, 2),
      labels = c("Yes", "No")
    ) +
    geom_abline(intercept = -alpha0/beta[2], slope = -beta[1]/beta[2], 
                color = "black") +
    geom_abline(intercept = lambda/beta[2] - alpha0/beta[2], 
                slope = -beta[1]/beta[2], 
                color = "black", linetype = 2) +
    geom_abline(intercept = -lambda/beta[2] - alpha0/beta[2], 
                slope = -beta[1]/beta[2], 
                color = "black", linetype = 2) +
    geom_point(data = df[Elbow, ], size = 3) +
    ggtitle(main)
    
}

Prerequisites

# Helper packages
library(dplyr)    # for data wrangling
library(ggplot2)  # for awesome graphics
library(rsample)  # for data splitting

# Modeling packages
library(caret)    # for classification and regression training
library(kernlab)  # for fitting SVMs

# Model interpretability packages
library(pdp)      # for partial dependence plots, etc.
library(vip)      # for variable importance plots
# Load attrition data
df <- attrition %>% 
  mutate_if(is.ordered, factor, ordered = FALSE)

# Create training (70%) and test (30%) sets
set.seed(123)  # for reproducibility
churn_split <- initial_split(df, prop = 0.7, strata = "Attrition")
churn_train <- training(churn_split)
churn_test  <- testing(churn_split)

Optimal separating hyperplanes

Figure 14.1:

# Construct data for plotting
x1 <- x2 <- seq(from = 0, to = 1, length = 100)
xgrid <- expand.grid(x1 = x1, x2 = x2)
y1 <- 1 + 2 * x1
y2 <- 1 + 2 * xgrid$x1 + 3 * xgrid$x2

# Hyperplane: p = 2
p1 <- lattice::xyplot(
  x = y1 ~ x1, 
  type = "l", 
  col = "black", 
  xlab = expression(X[1]), 
  ylab = expression(X[2]),
  main = expression({f(X)==1+2*X[1]-X[2]}==0),
  scales = list(tck = c(1, 0))
)

# Hyperplane: p = 3
p2 <- lattice::wireframe(
  x = y2 ~ xgrid$x1 * xgrid$x2, 
  xlab = expression(X[1]), 
  ylab = expression(X[2]),
  zlab = expression(X[3]),
  main = expression({f(X)==1+2*X[1]+3*X[2]-X[3]}==0),
  drape = TRUE,
  colorkey = FALSE,
  col = dark2[1],
  scales = list(arrows = FALSE)
  # par.settings = list(axis.line = list(col = "transparent"))
)

# Display plots side by side
gridExtra::grid.arrange(p1, p2, nrow = 1)

The hard margin classifier

Figure 14.2:

# Simulate data
set.seed(805)
norm2d <- as.data.frame(mlbench::mlbench.2dnormals(
  n = 100,
  cl = 2,
  r = 4,
  sd = 1
))
names(norm2d) <- c("x1", "x2", "y")  # rename columns

# Scatterplot
p1 <- ggplot(norm2d, aes(x = x1, y = x2)) +
  geom_point(aes(shape = y, color = y), size = 3, alpha = 0.75) +
  xlab("Income (standardized)") +
  ylab("Lot size (standardized)") +
  xlim(-6, 6) +
  ylim(-6, 6) +
  coord_fixed() +
  theme(legend.position = "none") +
  theme_bw() +
  scale_shape_discrete(
    name = "Owns a riding\nmower?",
    breaks = c(1, 2),
    labels = c("Yes", "No")
  ) +
  scale_color_brewer(
    name = "Owns a riding\nmower?",
    palette = "Dark2",
    breaks = c(1, 2),
    labels = c("Yes", "No")
  )

# Fit a Logistic regression, linear discriminant analysis (LDA), and optimal
# separating hyperplane (OSH). Note: we sometimes refer to the OSH as the hard 
# margin classifier
fit_glm <- glm(as.factor(y) ~ ., data = norm2d, family = binomial)
fit_lda <- MASS::lda(as.factor(y) ~ ., data = norm2d)
invisible(capture.output(fit_hmc <- ksvm(  # use ksvm() to find the OSH
  x = data.matrix(norm2d[c("x1", "x2")]),
  y = as.factor(norm2d$y), 
  kernel = "vanilladot",  # no fancy kernel, just ordinary dot product
  C = Inf,                # to approximate hard margin classifier
  prob.model = TRUE       # needed to obtain predicted probabilities
)))

# Grid over which to evaluate decision boundaries
npts <- 500
xgrid <- expand.grid(
  x1 = seq(from = -6, 6, length = npts),
  x2 = seq(from = -6, 6, length = npts)
)

# Predicted probabilities (as a two-column matrix)
prob_glm <- predict(fit_glm, newdata = xgrid, type = "response")
prob_glm <- cbind("1" = 1 - prob_glm, "2" = prob_glm)
prob_lda <- predict(fit_lda, newdata = xgrid)$posterior
prob_hmc <- predict(fit_hmc, newdata = xgrid, type = "probabilities")

# Add predicted class probabilities
xgrid2 <- xgrid %>%
  cbind("GLM" = prob_glm[, 1L], 
        "LDA" = prob_lda[, 1L], 
        "HMC" = prob_hmc[, 1L]) %>%
  tidyr::gather(Model, Prob, -x1, -x2)

# Scatterplot with decision boundaries
p2 <- p1 + 
  stat_contour(data = xgrid2, aes(x = x1, y = x2, z = Prob, linetype = Model), 
               breaks = 0.5, color = "black")

# Display plots side by side
gridExtra::grid.arrange(p1, p2, nrow = 1)

Figure 14.3:

# Compute convex hull for each class
hpts1 <- chull(norm2d[norm2d$y == 1, c("x1", "x2")])
hpts1 <- c(hpts1, hpts1[1L])
hpts2 <- chull(norm2d[norm2d$y == 2, c("x1", "x2")])
hpts2 <- c(hpts2, hpts2[1L])

# Support vectors
sv <- norm2d[fit_hmc@alphaindex[[1L]], c("x1", "x2")]  # 16-th and 97-th observations

# Compute the perpendicular bisector of the line segment joining the two support 
# vectors
slope <- -1 / ((sv[2L, 2L] - sv[1L, 2L]) / (sv[2L, 1L] - sv[1L, 1L]))
midpoint <- apply(sv, 2, mean)

# Scatterplot with convex hulls, etc.
ggplot(norm2d, aes(x = x1, y = x2)) +
  
  # Convex hulls
  geom_polygon(
    data = norm2d[norm2d$y == 1, c("x1", "x2")][hpts1, c("x1", "x2")],
    color = "black",
    fill = "transparent"
  ) +
  geom_polygon(
    data = norm2d[norm2d$y == 2, c("x1", "x2")][hpts2, c("x1", "x2")],
    color = "black",
    fill = "transparent"
  ) +
  
  # Scatterplot
  geom_point(aes(shape = y, color = y), size = 3, alpha = 0.75) +
  xlab("Income (standardized)") +
  ylab("Lot size (standardized)") +
  xlim(-10, 10) +
  ylim(-10, 10) +
  # coord_fixed() +
  theme(legend.position = "none") +
  theme_bw() +
  scale_shape_discrete(
    name = "Owns a riding\nmower?",
    breaks = c(1, 2),
    labels = c("Yes", "No")
  ) +
  scale_color_brewer(
    name = "Owns a riding\nmower?",
    palette = "Dark2",
    breaks = c(1, 2),
    labels = c("Yes", "No")
  ) +
  
  # Decision boundary
  geom_abline(
    intercept = -slope * midpoint[1L] + midpoint[2L], 
    slope = slope
  ) +
  
  # Margin boundaries (shaded in)
  geom_abline(
    intercept = -slope * sv[1L, 1L] + sv[1L, 2L], 
    slope = slope,
    linetype = 2
  ) +
  geom_abline(
    intercept = -slope * sv[2L, 1L] + sv[2L, 2L], 
    slope = slope,
    linetype = 2
  ) +
  annotate(
    geom = "polygon", 
    x = c(-7, -7, 7, 7), 
    y = c(-slope * sv[1L, 1L] + sv[1L, 2L] - 7 * slope, 
          -slope * midpoint[1L] + midpoint[2L] - 7 * slope, 
          -slope * midpoint[1L] + midpoint[2L] + 7 * slope,
          -slope * sv[1L, 1L] + sv[1L, 2L] + 7 * slope),
    alpha = 0.1, 
    color = "transparent",
    fill = dark2[2]
  ) +
  annotate(
    geom = "polygon", 
    x = c(-7, -7, 7, 7), 
    y = c(-slope * sv[2L, 1L] + sv[2L, 2L] - 7 * slope,
          -slope * midpoint[1L] + midpoint[2L] - 7 * slope,
          -slope * midpoint[1L] + midpoint[2L] + 7 * slope,
          -slope * sv[2L, 1L] + sv[2L, 2L] + 7 * slope), 
    alpha = 0.1, 
    color = "transparent",
    fill = dark2[2]
  ) +
  
  # Arrows, labels, etc.
  annotate("segment",
    x = sv[1L, 1L], y = sv[1L, 2L], xend = sv[2L, 1L], yend = sv[2L, 2L], 
    # alpha = 0.5,
    linetype = 3
    # arrow = arrow(length = unit(0.03, units = "npc"), ends = "both")
  ) +
  geom_curve(x = -3, y = 4.5, xend = 0, yend = 5, 
             arrow = arrow(length = unit(0.03, units = "npc"))) +
  annotate("text", label = "Width = M", x = 0.45, y = 5.45, size = 5) +
  geom_curve(x = 2, y = -3, xend = 0, yend = -5, 
             arrow = arrow(length = unit(0.03, units = "npc"))) +
  annotate("text", label = "Width = M", x = 0, y = -5.35, size = 5) +
  
  # Support vectors
  annotate("point", x = sv$x1[1], y = sv$x2[1], shape = 17, color = "red", 
           size = 3) +
  annotate("point", x = sv$x1[2], y = sv$x2[2], shape = 16, color = "red", 
           size = 3) +
  # geom_point(data = cbind(sv, y = c("2", "1")), aes(shape = y),
  #            size = 4, color = "red") +
  
  # Zoom in
  coord_fixed(xlim = c(-6, 6), ylim = c(-6, 6))

The soft margin classifier

Figure 14.4:

# Add an outlier
norm2d <- rbind(norm2d, data.frame("x1" = 0.5, "x2" = 1, "y" = 2))

# Fit a Logistic regression, linear discriminant analysis (LDA), and optimal
# separating hyperplane (OSH)
#
# Note: we sometimes refer to the OSH as the hard margin classifier
fit_glm <- glm(as.factor(y) ~ ., data = norm2d, family = binomial)
fit_lda <- MASS::lda(as.factor(y) ~ ., data = norm2d)
invisible(capture.output(fit_hmc <- ksvm(  # use ksvm() to find the OSH
  x = data.matrix(norm2d[c("x1", "x2")]),
  y = as.factor(norm2d$y), 
  kernel = "vanilladot",  # no fancy kernel, just ordinary dot product
  C = Inf,                # to approximate maximal margin classifier
  prob.model = TRUE       # needed to obtain predicted probabilities
)))

# Grid over which to evaluate decision boundaries
npts <- 500
xgrid <- expand.grid(
  x1 = seq(from = -6, 6, length = npts),
  x2 = seq(from = -6, 6, length = npts)
)

# Predicted probabilities (as a two-column matrix)
prob_glm <- predict(fit_glm, newdata = xgrid, type = "response")
prob_glm <- cbind("1" = 1 - prob_glm, "2" = prob_glm)
prob_lda <- predict(fit_lda, newdata = xgrid)$posterior
prob_hmc <- predict(fit_hmc, newdata = xgrid, type = "probabilities")

# Add predicted class probabilities
xgrid2 <- xgrid %>%
  cbind("GLM" = prob_glm[, 1L], 
        "LDA" = prob_lda[, 1L], 
        "HMC" = prob_hmc[, 1L]) %>%
  tidyr::gather(Model, Prob, -x1, -x2)

# Scatterplot
ggplot(norm2d, aes(x = x1, y = x2)) +
  
  # Label outlier
  geom_curve(x = tail(norm2d, n = 1)$x1 - 0.2, y = tail(norm2d, n = 1)$x2 - 0.2, 
             xend = -4, yend = 3, curvature = -0.5, angle = 90,
             arrow = arrow(length = unit(0.03, units = "npc"))) +
  annotate("text", label = "Outlier?", x = -4, y = 3.5, size = 5) +

  # Scatterplot, etc.
  geom_point(aes(shape = y, color = y), size = 3, alpha = 0.75) +
  xlab("Income (standardized)") +
  ylab("Lot size (standardized)") +
  xlim(-6, 6) +
  ylim(-6, 6) +
  coord_fixed() +
  theme(legend.position = "none") +
  theme_bw() +
  scale_shape_discrete(
    name = "Owns a riding\nmower?",
    breaks = c(1, 2),
    labels = c("Yes", "No")
  ) +
  scale_color_brewer(
    name = "Owns a riding\nmower?",
    palette = "Dark2",
    breaks = c(1, 2),
    labels = c("Yes", "No")
  ) +
  stat_contour(data = xgrid2, aes(x = x1, y = x2, z = Prob, linetype = Model), 
               breaks = 0.5, color = "black")

Figure 14.5:

# Fit the entire regularization path
fit_smc <- svmpath(
  x = data.matrix(norm2d[c("x1", "x2")]), 
  y = ifelse(norm2d$y == 1, 1, -1)
)
# Plot both extremes
p1 <- plot_svmpath(fit_smc, step = max(fit_smc$Step), main = expression(C == 0))
p2 <- plot_svmpath(fit_smc, step = min(fit_smc$Step), main = expression(C == infinity))
gridExtra::grid.arrange(p1, p2, nrow = 1)

The support vector machine

Figure 14.6:

# Load required packages
library(grid)
library(lattice)

# Simulate data
set.seed(1432)
circle <- as.data.frame(mlbench::mlbench.circle(
  n = 200,
  d = 2
))
names(circle) <- c("x1", "x2", "y")  # rename columns

# Fit a support vector machine (SVM)
fit_svm_poly <- ksvm( 
  x = data.matrix(circle[c("x1", "x2")]),
  y = as.factor(circle$y), 
  kernel = "polydot",       # polynomial kernel
  kpar = list(degree = 2),  # kernel parameters
  C = Inf,                  # to approximate maximal margin classifier
  prob.model = TRUE         # needed to obtain predicted probabilities
)

# Grid over which to evaluate decision boundaries
npts <- 500
xgrid <- expand.grid(
  x1 = seq(from = -1.25, 1.25, length = npts),
  x2 = seq(from = -1.25, 1.25, length = npts)
)

# Predicted probabilities (as a two-column matrix)
prob_svm_poly <- predict(fit_svm_poly, newdata = xgrid, type = "probabilities")

# Scatterplot
p1 <- contourplot(
  x = prob_svm_poly[, 1] ~ x1 * x2, 
  data = xgrid, 
  at = 0, 
  labels = FALSE,
  scales = list(tck = c(1, 0)),
  xlab = "x1",
  ylab = "x2",
  main = "Original feature space",
  panel = function(x, y, z, ...) {
    panel.contourplot(x, y, z, ...)
    panel.xyplot(
      x = circle$x1, 
      y = circle$x2, 
      groups = circle$y, 
      pch = 19, 
      cex = 1,
      col = adjustcolor(dark2[1L:2L], alpha.f = 0.5),
      ...
    )
  }
)

# Enlarge feature space
circle_3d <- circle
circle_3d$x3 <- circle_3d$x1^2 + circle_3d$x2^2

# 3-D scatterplot
p2 <- cloud(
  x = x3 ~ x1 * x2, 
  data = circle_3d, 
  groups = y,
  main = "Enlarged feature space",
  par.settings = list(
    superpose.symbol = list(
      pch = 19,
      cex = 1,
      col = adjustcolor(dark2[1L:2L], alpha.f = 0.5)
    )
  )
) 

# p2 <- scatterplot3d(
#   x = circle_3d[, -3],
#   pch = 19,
#   color = adjustcolor(dark2[1L:2L], alpha.f = 0.5)[circle_3d$y]
# )
# p2$plane3d(0.64, 0, 0, draw_polygon = TRUE)
# p2 <- recordPlot()

# Scatterplot with decision boundary
p3 <- contourplot(
  x = prob_svm_poly[, 1] ~ x1 * x2, 
  data = xgrid, 
  at = 0.5, 
  labels = FALSE,
  scales = list(tck = c(1, 0)),
  xlab = "x1",
  ylab = "x2",
  main = "Non-linear decision boundary",
  panel = function(x, y, z, ...) {
    panel.contourplot(x, y, z, ...)
    panel.xyplot(
      x = circle$x1, 
      y = circle$x2, 
      groups = circle$y, 
      pch = 19, 
      cex = 1,
      col = adjustcolor(dark2[1L:2L], alpha.f = 0.5),
      ...
    )
  }
) 

# Combine plots
gridExtra::grid.arrange(p1, p2, p3, nrow = 1)

# Linear (i.e., soft margin classifier)
caret::getModelInfo("svmLinear")$svmLinear$parameters

# Polynomial kernel
caret::getModelInfo("svmPoly")$svmPoly$parameters

# Radial basis kernel
caret::getModelInfo("svmRadial")$svmRadial$parameters

Figure 14.7:

# Load required packages
library(kernlab)  # for fitting SVMs
library(mlbench)  # for ML benchmark data sets

# Simulate train and test sets
set.seed(0841)
spirals <- as.data.frame(
  mlbench.spirals(300, cycles = 2, sd = 0.09)
)
names(spirals) <- c("x1", "x2", "classes")

# Fit an RF
set.seed(7256)
spirals_rfo <- ranger::ranger(classes ~ ., data = spirals, probability = TRUE)

# Fit an SVM using a radial basis function kernel
spirals_svm <- ksvm(classes ~ x1 + x2, data = spirals, kernel = "rbfdot",
                    C = 500, prob.model = TRUE)

# Grid over which to evaluate decision boundaries
npts <- 500
xgrid <- expand.grid(
  x1 = seq(from = -2, 2, length = npts),
  x2 = seq(from = -2, 2, length = npts)
)

# Predicted probabilities (as a two-column matrix)
prob_rfo <- predict(spirals_rfo, data = xgrid)$predictions
prob_svm <- predict(spirals_svm, newdata = xgrid, type = "probabilities")

# Add predicted class probabilities
xgrid2 <- xgrid %>%
  cbind("RF" = prob_rfo[, 1L], 
        "SVM" = prob_svm[, 1L]) %>%
  tidyr::gather(Model, Prob, -x1, -x2)

# Scatterplots with decision boundaries
ggplot(spirals, aes(x = x1, y = x2)) +
  geom_point(aes(shape = classes, color = classes), size = 3, alpha = 0.75) +
  xlab(expression(X[1])) +
  ylab(expression(X[2])) +
  xlim(-2, 2) +
  ylim(-2, 2) +
  coord_fixed() +
  theme(legend.position = "none") +
  theme_bw() +
  stat_contour(data = xgrid2, aes(x = x1, y = x2, z = Prob), 
               breaks = 0.5, color = "black") +
  facet_wrap( ~ Model)

Support vector regression

Figure 14.8:

ggplot() +
  geom_abline(intercept = 4, slope = 1, linetype = 2, color = dark2[1L]) +
  geom_abline(intercept = 3, slope = 1) +
  geom_abline(intercept = 2, slope = 1, linetype = 2, color = dark2[1L]) +
  xlim(0, 5) +
  ylim(1, 10) +
  xlab(expression(x)) +
  ylab(expression(f(x))) +
  theme_bw() +
  annotate("text", label = "f(x) + epsilon", parse = TRUE, x = 2, y = 6.75, 
           size = 6, color = dark2[1L]) +
  annotate("text", label = "f(x) - epsilon", parse = TRUE, x = 2, y = 3.15, 
           size = 6, color = dark2[1L])

Figure 14.9:

# Simulate data
set.seed(1218)
x <- seq(from = -20, to = 20, by = 0.1)
y <- sin(x) / x + rnorm(length(x), sd = 0.03)
df <- na.omit(data.frame(x = x, y = y))

# Plot results
ggplot(df, aes(x = x, y = y)) +
  geom_point(alpha = 0.5) +
  geom_line(aes(x = x, y = sin(x) / x), size = 1, color = "darkorange") +
  theme_bw() +
  theme(legend.position = "none") 

Figure 14.10:

# SVR model
set.seed(101)
svr <- kernlab::ksvm(y ~ x, data = df, kernel = "rbfdot", kpar = "automatic",
                     type = "eps-svr", epsilon = 0.1)

# MARS model
mars <- earth::earth(y ~ x, data = df)

# Random forest
set.seed(102)
rfo <- ranger::ranger(y ~ x, data = df)

# Gather predictions
df$SVR <- predict(svr, newdata = df)
df$MARS <- predict(mars, newdata = df)[, 1L, drop = TRUE]
df$RF <- predict(rfo, data = df)$predictions
df <- df %>% tidyr::gather(Model, Prediction, -x, -y)

# Plot results
ggplot(df, aes(x = x, y = y)) +
  geom_point(alpha = 0.5) +
  geom_line(aes(x = x, y = Prediction, color = Model), size = 1) +
  facet_wrap( ~ Model) +
  theme_bw() +
  theme(legend.position = "none") 

Job attrition example

# Tune an SVM with radial basis kernel
set.seed(1854)  # for reproducibility
churn_svm <- train(
  Attrition ~ ., 
  data = churn_train,
  method = "svmRadial",               
  preProcess = c("center", "scale"),  
  trControl = trainControl(method = "cv", number = 10),
  tuneLength = 10
)
# Plot results
ggplot(churn_svm) + theme_light()


# Print results
churn_svm$results

Class probabilities

# Control params for SVM
ctrl <- trainControl(
  method = "cv", 
  number = 10, 
  classProbs = TRUE,                 
  summaryFunction = twoClassSummary  # also needed for AUC/ROC
)

# Tune an SVM
set.seed(5628)  # for reproducibility
churn_svm_auc <- train(
  Attrition ~ ., 
  data = churn_train,
  method = "svmRadial",               
  preProcess = c("center", "scale"),  
  metric = "ROC",  # area under ROC curve (AUC)       
  trControl = ctrl,
  tuneLength = 10
)

# Print results
churn_svm_auc$results
confusionMatrix(churn_svm_auc)
Cross-Validated (10 fold) Confusion Matrix 

(entries are percentual average cell counts across resamples)
 
          Reference
Prediction   No  Yes
       No  81.2  9.8
       Yes  2.7  6.3
                            
 Accuracy (average) : 0.8748

Feature interpretation

prob_yes <- function(object, newdata) {
  predict(object, newdata = newdata, type = "prob")[, "Yes"]
}
# Variable importance plot
set.seed(2827)  # for reproducibility
vip(churn_svm_auc, method = "permute", nsim = 5, train = churn_train, 
    target = "Attrition", metric = "auc", reference_class = "Yes", 
    pred_wrapper = prob_yes)

features <- c("OverTime", "WorkLifeBalance", 
              "JobSatisfaction", "JobRole")
pdps <- lapply(features, function(x) {
  partial(churn_svm_auc, pred.var = x, which.class = 2,  
          prob = TRUE, plot = TRUE, plot.engine = "ggplot2") +
    coord_flip()
})
grid.arrange(grobs = pdps,  ncol = 2)

LS0tCnRpdGxlOiAiQ2hhcHRlciAxNDogU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCl9fTm90ZV9fOiBTb21lIHJlc3VsdHMgbWF5IGRpZmZlciBmcm9tIHRoZSBoYXJkIGNvcHkgYm9vayBkdWUgdG8gdGhlIGNoYW5naW5nIG9mIHNhbXBsaW5nIHByb2NlZHVyZXMgaW50cm9kdWNlZCBpbiBSIDMuNi4wLiBTZWUgaHR0cDovL2JpdC5seS8zNUQxU1c3IGZvciBtb3JlIGRldGFpbHMuIEFjY2VzcyBhbmQgcnVuIHRoZSBzb3VyY2UgY29kZSBmb3IgdGhpcyBub3RlYm9vayBbaGVyZV0oaHR0cHM6Ly9yc3R1ZGlvLmNsb3VkL3Byb2plY3QvODAxMTg1KS4gCgpIaWRkZW4gY2hhcHRlciByZXF1aXJlbWVudHMgdXNlZCBpbiB0aGUgYm9vayB0byBzZXQgdGhlIHBsb3R0aW5nIHRoZW1lIGFuZCBsb2FkIHBhY2thZ2VzIHVzZWQgaW4gaGlkZGVuIGNvZGUgY2h1bmtzOgoKYGBge3Igc2V0dXB9CiMgU2V0IGdsb2JhbCBrbml0ciBjaHVuayBvcHRpb25zCmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBjYWNoZSA9IEZBTFNFLAogIGNvbGxhcHNlID0gVFJVRSwgCiAgZmlnLmFsaWduID0gImNlbnRlciIsCiAgZmlnLmhlaWdodCA9IDMuNSwKICBtZXNzYWdlID0gRkFMU0UsIAogIHdhcm5pbmcgPSBGQUxTRQopCgojIFNldCB0aGUgZ3JhcGhpY2FsIHRoZW1lCmdncGxvdDI6OnRoZW1lX3NldChnZ3Bsb3QyOjp0aGVtZV9saWdodCgpKQoKIyBJbmNyZWFzZSBvdXRwdXQgd2lkdGgKb3B0aW9ucyh3aWR0aCA9IDEyMCkKCiMgTG9hZCByZXF1aXJlZCBwYWNrYWdlcwpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoa2VybmxhYikKbGlicmFyeShzdm1wYXRoKQoKIyBDb2xvcnMKZGFyazIgPC0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDgsICJEYXJrMiIpCnNldDEgPC0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDksICJTZXQxIikKCiMgUGxvdHRpbmcgZnVuY3Rpb247IG1vZGlmaWVkIGZyb20gc3ZtcGF0aDo6c3ZtcGF0aCgpCnBsb3Rfc3ZtcGF0aCA8LSBmdW5jdGlvbih4LCBzdGVwID0gbWF4KHgkU3RlcCksIG1haW4gPSAiIikgewogIAogICMgRXh0cmFjdCBtb2RlbCBpbmZvCiAgb2JqZWN0IDwtIHgKICBmIDwtIHByZWRpY3Qob2JqZWN0LCBsYW1iZGEgPSBvYmplY3QkbGFtYmRhW3N0ZXBdLCB0eXBlID0gImZ1bmN0aW9uIikKICB4IDwtIG9iamVjdCR4CiAgeSA8LSBvYmplY3QkeQogIEVsYm93IDwtIG9iamVjdCRFbGJvd1tbc3RlcF1dCiAgYWxwaGEgPC0gb2JqZWN0JGFscGhhWywgc3RlcF0KICBhbHBoYTAgPC0gb2JqZWN0JGFscGhhMFtzdGVwXQogIGxhbWJkYSA8LSBvYmplY3QkbGFtYmRhW3N0ZXBdCiAgZGYgPC0gYXMuZGF0YS5mcmFtZSh4WywgMUw6MkxdKQogIG5hbWVzKGRmKSA8LSBjKCJ4MSIsICJ4MiIpCiAgZGYkeSA8LSBub3JtMmQkeQogIGJldGEgPC0gKGFscGhhICogeSkgJSolIHgKCiAgIyBDb25zdHJ1Y3QgcGxvdAogIGdncGxvdChkZiwgYWVzKHggPSB4MSwgeSA9IHgyKSkgKwogICAgZ2VvbV9wb2ludChhZXMoc2hhcGUgPSB5LCBjb2xvciA9IHkpLCBzaXplID0gMywgYWxwaGEgPSAwLjc1KSArCiAgICB4bGFiKCJJbmNvbWUgKHN0YW5kYXJkaXplZCkiKSArCiAgICB5bGFiKCJMb3Qgc2l6ZSAoc3RhbmRhcmRpemVkKSIpICsKICAgIHhsaW0oLTYsIDYpICsKICAgIHlsaW0oLTYsIDYpICsKICAgIGNvb3JkX2ZpeGVkKCkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgICB0aGVtZV9idygpICsKICAgIHNjYWxlX3NoYXBlX2Rpc2NyZXRlKAogICAgICBuYW1lID0gIk93bnMgYSByaWRpbmdcbm1vd2VyPyIsCiAgICAgIGJyZWFrcyA9IGMoMSwgMiksCiAgICAgIGxhYmVscyA9IGMoIlllcyIsICJObyIpCiAgICApICsKICAgIHNjYWxlX2NvbG9yX2JyZXdlcigKICAgICAgbmFtZSA9ICJPd25zIGEgcmlkaW5nXG5tb3dlcj8iLAogICAgICBwYWxldHRlID0gIkRhcmsyIiwKICAgICAgYnJlYWtzID0gYygxLCAyKSwKICAgICAgbGFiZWxzID0gYygiWWVzIiwgIk5vIikKICAgICkgKwogICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gLWFscGhhMC9iZXRhWzJdLCBzbG9wZSA9IC1iZXRhWzFdL2JldGFbMl0sIAogICAgICAgICAgICAgICAgY29sb3IgPSAiYmxhY2siKSArCiAgICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSBsYW1iZGEvYmV0YVsyXSAtIGFscGhhMC9iZXRhWzJdLCAKICAgICAgICAgICAgICAgIHNsb3BlID0gLWJldGFbMV0vYmV0YVsyXSwgCiAgICAgICAgICAgICAgICBjb2xvciA9ICJibGFjayIsIGxpbmV0eXBlID0gMikgKwogICAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gLWxhbWJkYS9iZXRhWzJdIC0gYWxwaGEwL2JldGFbMl0sIAogICAgICAgICAgICAgICAgc2xvcGUgPSAtYmV0YVsxXS9iZXRhWzJdLCAKICAgICAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIiwgbGluZXR5cGUgPSAyKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBkZltFbGJvdywgXSwgc2l6ZSA9IDMpICsKICAgIGdndGl0bGUobWFpbikKICAgIAp9CmBgYAoKIyMgUHJlcmVxdWlzaXRlcwoKYGBge3Igc3ZtLXBrZ3N9CiMgSGVscGVyIHBhY2thZ2VzCmxpYnJhcnkoZHBseXIpICAgICMgZm9yIGRhdGEgd3JhbmdsaW5nCmxpYnJhcnkoZ2dwbG90MikgICMgZm9yIGF3ZXNvbWUgZ3JhcGhpY3MKbGlicmFyeShyc2FtcGxlKSAgIyBmb3IgZGF0YSBzcGxpdHRpbmcKCiMgTW9kZWxpbmcgcGFja2FnZXMKbGlicmFyeShjYXJldCkgICAgIyBmb3IgY2xhc3NpZmljYXRpb24gYW5kIHJlZ3Jlc3Npb24gdHJhaW5pbmcKbGlicmFyeShrZXJubGFiKSAgIyBmb3IgZml0dGluZyBTVk1zCgojIE1vZGVsIGludGVycHJldGFiaWxpdHkgcGFja2FnZXMKbGlicmFyeShwZHApICAgICAgIyBmb3IgcGFydGlhbCBkZXBlbmRlbmNlIHBsb3RzLCBldGMuCmxpYnJhcnkodmlwKSAgICAgICMgZm9yIHZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdHMKYGBgCgpgYGB7ciAwNS1kYXRhLWltcG9ydH0KIyBMb2FkIGF0dHJpdGlvbiBkYXRhCmRmIDwtIGF0dHJpdGlvbiAlPiUgCiAgbXV0YXRlX2lmKGlzLm9yZGVyZWQsIGZhY3Rvciwgb3JkZXJlZCA9IEZBTFNFKQoKIyBDcmVhdGUgdHJhaW5pbmcgKDcwJSkgYW5kIHRlc3QgKDMwJSkgc2V0cwpzZXQuc2VlZCgxMjMpICAjIGZvciByZXByb2R1Y2liaWxpdHkKY2h1cm5fc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChkZiwgcHJvcCA9IDAuNywgc3RyYXRhID0gIkF0dHJpdGlvbiIpCmNodXJuX3RyYWluIDwtIHRyYWluaW5nKGNodXJuX3NwbGl0KQpjaHVybl90ZXN0ICA8LSB0ZXN0aW5nKGNodXJuX3NwbGl0KQpgYGAKCgojIyBPcHRpbWFsIHNlcGFyYXRpbmcgaHlwZXJwbGFuZXMgeyNoeXBlcnBsYW5lc30KCkZpZ3VyZSAxNC4xOgoKYGBge3IgaHlwZXJwbGFuZXMsIGVjaG89VFJVRSwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTUsIG91dC53aWR0aD0iMTAwJSIsIGZpZy5jYXA9IkV4YW1wbGVzIG9mIGh5cGVycGxhbmVzIGluIDItRCBhbmQgMy1EIGZlYXR1cmUgc3BhY2UuIn0KIyBDb25zdHJ1Y3QgZGF0YSBmb3IgcGxvdHRpbmcKeDEgPC0geDIgPC0gc2VxKGZyb20gPSAwLCB0byA9IDEsIGxlbmd0aCA9IDEwMCkKeGdyaWQgPC0gZXhwYW5kLmdyaWQoeDEgPSB4MSwgeDIgPSB4MikKeTEgPC0gMSArIDIgKiB4MQp5MiA8LSAxICsgMiAqIHhncmlkJHgxICsgMyAqIHhncmlkJHgyCgojIEh5cGVycGxhbmU6IHAgPSAyCnAxIDwtIGxhdHRpY2U6Onh5cGxvdCgKICB4ID0geTEgfiB4MSwgCiAgdHlwZSA9ICJsIiwgCiAgY29sID0gImJsYWNrIiwgCiAgeGxhYiA9IGV4cHJlc3Npb24oWFsxXSksIAogIHlsYWIgPSBleHByZXNzaW9uKFhbMl0pLAogIG1haW4gPSBleHByZXNzaW9uKHtmKFgpPT0xKzIqWFsxXS1YWzJdfT09MCksCiAgc2NhbGVzID0gbGlzdCh0Y2sgPSBjKDEsIDApKQopCgojIEh5cGVycGxhbmU6IHAgPSAzCnAyIDwtIGxhdHRpY2U6OndpcmVmcmFtZSgKICB4ID0geTIgfiB4Z3JpZCR4MSAqIHhncmlkJHgyLCAKICB4bGFiID0gZXhwcmVzc2lvbihYWzFdKSwgCiAgeWxhYiA9IGV4cHJlc3Npb24oWFsyXSksCiAgemxhYiA9IGV4cHJlc3Npb24oWFszXSksCiAgbWFpbiA9IGV4cHJlc3Npb24oe2YoWCk9PTErMipYWzFdKzMqWFsyXS1YWzNdfT09MCksCiAgZHJhcGUgPSBUUlVFLAogIGNvbG9ya2V5ID0gRkFMU0UsCiAgY29sID0gZGFyazJbMV0sCiAgc2NhbGVzID0gbGlzdChhcnJvd3MgPSBGQUxTRSkKICAjIHBhci5zZXR0aW5ncyA9IGxpc3QoYXhpcy5saW5lID0gbGlzdChjb2wgPSAidHJhbnNwYXJlbnQiKSkKKQoKIyBEaXNwbGF5IHBsb3RzIHNpZGUgYnkgc2lkZQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxKQpgYGAKCiMjIyBUaGUgaGFyZCBtYXJnaW4gY2xhc3NpZmllcgoKRmlndXJlIDE0LjI6CgpgYGB7ciBzdm0tc2VwYXJhdGluZy1oeXBlcnBsYW5lcywgZWNobz1UUlVFLCBmaWcud2lkdGg9OCwgZmlnLmFzcD0wLjUsIG91dC53aWR0aD0iMTAwJSIsIGZpZy5jYXA9IlNpbXVsYXRlZCBiaW5hcnkgY2xhc3NpZmljYXRpb24gZGF0YSB3aXRoIHR3byBzZXBhcmFibGUgY2xhc3Nlcy4gX0xlZnQ6XyBSYXcgZGF0YS4gX1JpZ2h0Ol8gUmF3IGRhdGEgd2l0aCBleGFtcGxlIGRlY2lzaW9uIGJvdW5kYXJpZXMgKGluIHRoaXMgY2FzZSwgc2VwYXJhdGluZyBoeXBlcnBsYW5lcykgZnJvbSB2YXJpb3VzIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcy4ifQojIFNpbXVsYXRlIGRhdGEKc2V0LnNlZWQoODA1KQpub3JtMmQgPC0gYXMuZGF0YS5mcmFtZShtbGJlbmNoOjptbGJlbmNoLjJkbm9ybWFscygKICBuID0gMTAwLAogIGNsID0gMiwKICByID0gNCwKICBzZCA9IDEKKSkKbmFtZXMobm9ybTJkKSA8LSBjKCJ4MSIsICJ4MiIsICJ5IikgICMgcmVuYW1lIGNvbHVtbnMKCiMgU2NhdHRlcnBsb3QKcDEgPC0gZ2dwbG90KG5vcm0yZCwgYWVzKHggPSB4MSwgeSA9IHgyKSkgKwogIGdlb21fcG9pbnQoYWVzKHNoYXBlID0geSwgY29sb3IgPSB5KSwgc2l6ZSA9IDMsIGFscGhhID0gMC43NSkgKwogIHhsYWIoIkluY29tZSAoc3RhbmRhcmRpemVkKSIpICsKICB5bGFiKCJMb3Qgc2l6ZSAoc3RhbmRhcmRpemVkKSIpICsKICB4bGltKC02LCA2KSArCiAgeWxpbSgtNiwgNikgKwogIGNvb3JkX2ZpeGVkKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIHRoZW1lX2J3KCkgKwogIHNjYWxlX3NoYXBlX2Rpc2NyZXRlKAogICAgbmFtZSA9ICJPd25zIGEgcmlkaW5nXG5tb3dlcj8iLAogICAgYnJlYWtzID0gYygxLCAyKSwKICAgIGxhYmVscyA9IGMoIlllcyIsICJObyIpCiAgKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKAogICAgbmFtZSA9ICJPd25zIGEgcmlkaW5nXG5tb3dlcj8iLAogICAgcGFsZXR0ZSA9ICJEYXJrMiIsCiAgICBicmVha3MgPSBjKDEsIDIpLAogICAgbGFiZWxzID0gYygiWWVzIiwgIk5vIikKICApCgojIEZpdCBhIExvZ2lzdGljIHJlZ3Jlc3Npb24sIGxpbmVhciBkaXNjcmltaW5hbnQgYW5hbHlzaXMgKExEQSksIGFuZCBvcHRpbWFsCiMgc2VwYXJhdGluZyBoeXBlcnBsYW5lIChPU0gpLiBOb3RlOiB3ZSBzb21ldGltZXMgcmVmZXIgdG8gdGhlIE9TSCBhcyB0aGUgaGFyZCAKIyBtYXJnaW4gY2xhc3NpZmllcgpmaXRfZ2xtIDwtIGdsbShhcy5mYWN0b3IoeSkgfiAuLCBkYXRhID0gbm9ybTJkLCBmYW1pbHkgPSBiaW5vbWlhbCkKZml0X2xkYSA8LSBNQVNTOjpsZGEoYXMuZmFjdG9yKHkpIH4gLiwgZGF0YSA9IG5vcm0yZCkKaW52aXNpYmxlKGNhcHR1cmUub3V0cHV0KGZpdF9obWMgPC0ga3N2bSggICMgdXNlIGtzdm0oKSB0byBmaW5kIHRoZSBPU0gKICB4ID0gZGF0YS5tYXRyaXgobm9ybTJkW2MoIngxIiwgIngyIildKSwKICB5ID0gYXMuZmFjdG9yKG5vcm0yZCR5KSwgCiAga2VybmVsID0gInZhbmlsbGFkb3QiLCAgIyBubyBmYW5jeSBrZXJuZWwsIGp1c3Qgb3JkaW5hcnkgZG90IHByb2R1Y3QKICBDID0gSW5mLCAgICAgICAgICAgICAgICAjIHRvIGFwcHJveGltYXRlIGhhcmQgbWFyZ2luIGNsYXNzaWZpZXIKICBwcm9iLm1vZGVsID0gVFJVRSAgICAgICAjIG5lZWRlZCB0byBvYnRhaW4gcHJlZGljdGVkIHByb2JhYmlsaXRpZXMKKSkpCgojIEdyaWQgb3ZlciB3aGljaCB0byBldmFsdWF0ZSBkZWNpc2lvbiBib3VuZGFyaWVzCm5wdHMgPC0gNTAwCnhncmlkIDwtIGV4cGFuZC5ncmlkKAogIHgxID0gc2VxKGZyb20gPSAtNiwgNiwgbGVuZ3RoID0gbnB0cyksCiAgeDIgPSBzZXEoZnJvbSA9IC02LCA2LCBsZW5ndGggPSBucHRzKQopCgojIFByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIChhcyBhIHR3by1jb2x1bW4gbWF0cml4KQpwcm9iX2dsbSA8LSBwcmVkaWN0KGZpdF9nbG0sIG5ld2RhdGEgPSB4Z3JpZCwgdHlwZSA9ICJyZXNwb25zZSIpCnByb2JfZ2xtIDwtIGNiaW5kKCIxIiA9IDEgLSBwcm9iX2dsbSwgIjIiID0gcHJvYl9nbG0pCnByb2JfbGRhIDwtIHByZWRpY3QoZml0X2xkYSwgbmV3ZGF0YSA9IHhncmlkKSRwb3N0ZXJpb3IKcHJvYl9obWMgPC0gcHJlZGljdChmaXRfaG1jLCBuZXdkYXRhID0geGdyaWQsIHR5cGUgPSAicHJvYmFiaWxpdGllcyIpCgojIEFkZCBwcmVkaWN0ZWQgY2xhc3MgcHJvYmFiaWxpdGllcwp4Z3JpZDIgPC0geGdyaWQgJT4lCiAgY2JpbmQoIkdMTSIgPSBwcm9iX2dsbVssIDFMXSwgCiAgICAgICAgIkxEQSIgPSBwcm9iX2xkYVssIDFMXSwgCiAgICAgICAgIkhNQyIgPSBwcm9iX2htY1ssIDFMXSkgJT4lCiAgdGlkeXI6OmdhdGhlcihNb2RlbCwgUHJvYiwgLXgxLCAteDIpCgojIFNjYXR0ZXJwbG90IHdpdGggZGVjaXNpb24gYm91bmRhcmllcwpwMiA8LSBwMSArIAogIHN0YXRfY29udG91cihkYXRhID0geGdyaWQyLCBhZXMoeCA9IHgxLCB5ID0geDIsIHogPSBQcm9iLCBsaW5ldHlwZSA9IE1vZGVsKSwgCiAgICAgICAgICAgICAgIGJyZWFrcyA9IDAuNSwgY29sb3IgPSAiYmxhY2siKQoKIyBEaXNwbGF5IHBsb3RzIHNpZGUgYnkgc2lkZQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxKQpgYGAKCkZpZ3VyZSAxNC4zOgoKYGBge3Igc3ZtLWhtYywgZWNobz1UUlVFLCBmaWcud2lkdGg9NywgZmlnLmFzcD0wLjYxOCwgZmlnLmNhcD0iSE1DIGZvciB0aGUgc2ltdWxhdGVkIHJpZGluZyBtb3dlciBkYXRhLiBUaGUgc29saWQgYmxhY2sgbGluZSBmb3JtcyB0aGUgZGVjaXNpb24gYm91bmRhcnkgKGluIHRoaXMgY2FzZSwgYSBzZXBhcmF0aW5nIGh5cGVycGxhbmUpLCB3aGlsZSB0aGUgZGFzaGVkIGxpbmVzIGZvcm0gdGhlIGJvdW5kYXJpZXMgb2YgdGhlIG1hcmdpbnMgKHNoYWRlZCByZWdpb25zKSBvbiBlYWNoIHNpZGUgb2YgdGhlIGh5cGVycGxhbmUuIFRoZSBzaG9ydGVzdCBkaXN0YW5jZSBiZXR3ZWVuIHRoZSB0d28gY2xhc3NlcyAoaS5lLiwgdGhlIGRvdHRlZCBsaW5lIGNvbm5lY3RpbmcgdGhlIHR3byBjb252ZXggaHVsbHMpIGhhcyBsZW5ndGggJDJNJC4gVHdvIG9mIHRoZSB0cmFpbmluZyBvYnNlcnZhdGlvbnMgKHNvbGlkIHJlZCBwb2ludHMpIGZhbGwgb24gdGhlIG1hcmdpbiBib3VuZGFyaWVzOyBpbiB0aGUgY29udGV4dCBvZiBTVk1zICh3aGljaCB3ZSBkaXNjdXNzIGxhdGVyKSwgdGhlc2UgdHdvIHBvaW50cyBmb3JtIHRoZSBfc3VwcG9ydCB2ZWN0b3JzXy4ifQojIENvbXB1dGUgY29udmV4IGh1bGwgZm9yIGVhY2ggY2xhc3MKaHB0czEgPC0gY2h1bGwobm9ybTJkW25vcm0yZCR5ID09IDEsIGMoIngxIiwgIngyIildKQpocHRzMSA8LSBjKGhwdHMxLCBocHRzMVsxTF0pCmhwdHMyIDwtIGNodWxsKG5vcm0yZFtub3JtMmQkeSA9PSAyLCBjKCJ4MSIsICJ4MiIpXSkKaHB0czIgPC0gYyhocHRzMiwgaHB0czJbMUxdKQoKIyBTdXBwb3J0IHZlY3RvcnMKc3YgPC0gbm9ybTJkW2ZpdF9obWNAYWxwaGFpbmRleFtbMUxdXSwgYygieDEiLCAieDIiKV0gICMgMTYtdGggYW5kIDk3LXRoIG9ic2VydmF0aW9ucwoKIyBDb21wdXRlIHRoZSBwZXJwZW5kaWN1bGFyIGJpc2VjdG9yIG9mIHRoZSBsaW5lIHNlZ21lbnQgam9pbmluZyB0aGUgdHdvIHN1cHBvcnQgCiMgdmVjdG9ycwpzbG9wZSA8LSAtMSAvICgoc3ZbMkwsIDJMXSAtIHN2WzFMLCAyTF0pIC8gKHN2WzJMLCAxTF0gLSBzdlsxTCwgMUxdKSkKbWlkcG9pbnQgPC0gYXBwbHkoc3YsIDIsIG1lYW4pCgojIFNjYXR0ZXJwbG90IHdpdGggY29udmV4IGh1bGxzLCBldGMuCmdncGxvdChub3JtMmQsIGFlcyh4ID0geDEsIHkgPSB4MikpICsKICAKICAjIENvbnZleCBodWxscwogIGdlb21fcG9seWdvbigKICAgIGRhdGEgPSBub3JtMmRbbm9ybTJkJHkgPT0gMSwgYygieDEiLCAieDIiKV1baHB0czEsIGMoIngxIiwgIngyIildLAogICAgY29sb3IgPSAiYmxhY2siLAogICAgZmlsbCA9ICJ0cmFuc3BhcmVudCIKICApICsKICBnZW9tX3BvbHlnb24oCiAgICBkYXRhID0gbm9ybTJkW25vcm0yZCR5ID09IDIsIGMoIngxIiwgIngyIildW2hwdHMyLCBjKCJ4MSIsICJ4MiIpXSwKICAgIGNvbG9yID0gImJsYWNrIiwKICAgIGZpbGwgPSAidHJhbnNwYXJlbnQiCiAgKSArCiAgCiAgIyBTY2F0dGVycGxvdAogIGdlb21fcG9pbnQoYWVzKHNoYXBlID0geSwgY29sb3IgPSB5KSwgc2l6ZSA9IDMsIGFscGhhID0gMC43NSkgKwogIHhsYWIoIkluY29tZSAoc3RhbmRhcmRpemVkKSIpICsKICB5bGFiKCJMb3Qgc2l6ZSAoc3RhbmRhcmRpemVkKSIpICsKICB4bGltKC0xMCwgMTApICsKICB5bGltKC0xMCwgMTApICsKICAjIGNvb3JkX2ZpeGVkKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIHRoZW1lX2J3KCkgKwogIHNjYWxlX3NoYXBlX2Rpc2NyZXRlKAogICAgbmFtZSA9ICJPd25zIGEgcmlkaW5nXG5tb3dlcj8iLAogICAgYnJlYWtzID0gYygxLCAyKSwKICAgIGxhYmVscyA9IGMoIlllcyIsICJObyIpCiAgKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKAogICAgbmFtZSA9ICJPd25zIGEgcmlkaW5nXG5tb3dlcj8iLAogICAgcGFsZXR0ZSA9ICJEYXJrMiIsCiAgICBicmVha3MgPSBjKDEsIDIpLAogICAgbGFiZWxzID0gYygiWWVzIiwgIk5vIikKICApICsKICAKICAjIERlY2lzaW9uIGJvdW5kYXJ5CiAgZ2VvbV9hYmxpbmUoCiAgICBpbnRlcmNlcHQgPSAtc2xvcGUgKiBtaWRwb2ludFsxTF0gKyBtaWRwb2ludFsyTF0sIAogICAgc2xvcGUgPSBzbG9wZQogICkgKwogIAogICMgTWFyZ2luIGJvdW5kYXJpZXMgKHNoYWRlZCBpbikKICBnZW9tX2FibGluZSgKICAgIGludGVyY2VwdCA9IC1zbG9wZSAqIHN2WzFMLCAxTF0gKyBzdlsxTCwgMkxdLCAKICAgIHNsb3BlID0gc2xvcGUsCiAgICBsaW5ldHlwZSA9IDIKICApICsKICBnZW9tX2FibGluZSgKICAgIGludGVyY2VwdCA9IC1zbG9wZSAqIHN2WzJMLCAxTF0gKyBzdlsyTCwgMkxdLCAKICAgIHNsb3BlID0gc2xvcGUsCiAgICBsaW5ldHlwZSA9IDIKICApICsKICBhbm5vdGF0ZSgKICAgIGdlb20gPSAicG9seWdvbiIsIAogICAgeCA9IGMoLTcsIC03LCA3LCA3KSwgCiAgICB5ID0gYygtc2xvcGUgKiBzdlsxTCwgMUxdICsgc3ZbMUwsIDJMXSAtIDcgKiBzbG9wZSwgCiAgICAgICAgICAtc2xvcGUgKiBtaWRwb2ludFsxTF0gKyBtaWRwb2ludFsyTF0gLSA3ICogc2xvcGUsIAogICAgICAgICAgLXNsb3BlICogbWlkcG9pbnRbMUxdICsgbWlkcG9pbnRbMkxdICsgNyAqIHNsb3BlLAogICAgICAgICAgLXNsb3BlICogc3ZbMUwsIDFMXSArIHN2WzFMLCAyTF0gKyA3ICogc2xvcGUpLAogICAgYWxwaGEgPSAwLjEsIAogICAgY29sb3IgPSAidHJhbnNwYXJlbnQiLAogICAgZmlsbCA9IGRhcmsyWzJdCiAgKSArCiAgYW5ub3RhdGUoCiAgICBnZW9tID0gInBvbHlnb24iLCAKICAgIHggPSBjKC03LCAtNywgNywgNyksIAogICAgeSA9IGMoLXNsb3BlICogc3ZbMkwsIDFMXSArIHN2WzJMLCAyTF0gLSA3ICogc2xvcGUsCiAgICAgICAgICAtc2xvcGUgKiBtaWRwb2ludFsxTF0gKyBtaWRwb2ludFsyTF0gLSA3ICogc2xvcGUsCiAgICAgICAgICAtc2xvcGUgKiBtaWRwb2ludFsxTF0gKyBtaWRwb2ludFsyTF0gKyA3ICogc2xvcGUsCiAgICAgICAgICAtc2xvcGUgKiBzdlsyTCwgMUxdICsgc3ZbMkwsIDJMXSArIDcgKiBzbG9wZSksIAogICAgYWxwaGEgPSAwLjEsIAogICAgY29sb3IgPSAidHJhbnNwYXJlbnQiLAogICAgZmlsbCA9IGRhcmsyWzJdCiAgKSArCiAgCiAgIyBBcnJvd3MsIGxhYmVscywgZXRjLgogIGFubm90YXRlKCJzZWdtZW50IiwKICAgIHggPSBzdlsxTCwgMUxdLCB5ID0gc3ZbMUwsIDJMXSwgeGVuZCA9IHN2WzJMLCAxTF0sIHllbmQgPSBzdlsyTCwgMkxdLCAKICAgICMgYWxwaGEgPSAwLjUsCiAgICBsaW5ldHlwZSA9IDMKICAgICMgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMDMsIHVuaXRzID0gIm5wYyIpLCBlbmRzID0gImJvdGgiKQogICkgKwogIGdlb21fY3VydmUoeCA9IC0zLCB5ID0gNC41LCB4ZW5kID0gMCwgeWVuZCA9IDUsIAogICAgICAgICAgICAgYXJyb3cgPSBhcnJvdyhsZW5ndGggPSB1bml0KDAuMDMsIHVuaXRzID0gIm5wYyIpKSkgKwogIGFubm90YXRlKCJ0ZXh0IiwgbGFiZWwgPSAiV2lkdGggPSBNIiwgeCA9IDAuNDUsIHkgPSA1LjQ1LCBzaXplID0gNSkgKwogIGdlb21fY3VydmUoeCA9IDIsIHkgPSAtMywgeGVuZCA9IDAsIHllbmQgPSAtNSwgCiAgICAgICAgICAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4wMywgdW5pdHMgPSAibnBjIikpKSArCiAgYW5ub3RhdGUoInRleHQiLCBsYWJlbCA9ICJXaWR0aCA9IE0iLCB4ID0gMCwgeSA9IC01LjM1LCBzaXplID0gNSkgKwogIAogICMgU3VwcG9ydCB2ZWN0b3JzCiAgYW5ub3RhdGUoInBvaW50IiwgeCA9IHN2JHgxWzFdLCB5ID0gc3YkeDJbMV0sIHNoYXBlID0gMTcsIGNvbG9yID0gInJlZCIsIAogICAgICAgICAgIHNpemUgPSAzKSArCiAgYW5ub3RhdGUoInBvaW50IiwgeCA9IHN2JHgxWzJdLCB5ID0gc3YkeDJbMl0sIHNoYXBlID0gMTYsIGNvbG9yID0gInJlZCIsIAogICAgICAgICAgIHNpemUgPSAzKSArCiAgIyBnZW9tX3BvaW50KGRhdGEgPSBjYmluZChzdiwgeSA9IGMoIjIiLCAiMSIpKSwgYWVzKHNoYXBlID0geSksCiAgIyAgICAgICAgICAgIHNpemUgPSA0LCBjb2xvciA9ICJyZWQiKSArCiAgCiAgIyBab29tIGluCiAgY29vcmRfZml4ZWQoeGxpbSA9IGMoLTYsIDYpLCB5bGltID0gYygtNiwgNikpCmBgYAoKIyMjIFRoZSBzb2Z0IG1hcmdpbiBjbGFzc2lmaWVyCgpGaWd1cmUgMTQuNDoKCmBgYHtyIHN2bS1ub2lzeSwgZWNobz1UUlVFLCBmaWcud2lkdGg9NywgZmlnLmFzcD0wLjYxOCwgZmlnLmNhcD0iU2ltdWxhdGVkIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiBkYXRhIHdpdGggYW4gb3V0bGllciBhdCB0aGUgcG9pbnQgJFxcbGVmdCgwLjUsIDFcXHJpZ2h0KSQuIn0KIyBBZGQgYW4gb3V0bGllcgpub3JtMmQgPC0gcmJpbmQobm9ybTJkLCBkYXRhLmZyYW1lKCJ4MSIgPSAwLjUsICJ4MiIgPSAxLCAieSIgPSAyKSkKCiMgRml0IGEgTG9naXN0aWMgcmVncmVzc2lvbiwgbGluZWFyIGRpc2NyaW1pbmFudCBhbmFseXNpcyAoTERBKSwgYW5kIG9wdGltYWwKIyBzZXBhcmF0aW5nIGh5cGVycGxhbmUgKE9TSCkKIwojIE5vdGU6IHdlIHNvbWV0aW1lcyByZWZlciB0byB0aGUgT1NIIGFzIHRoZSBoYXJkIG1hcmdpbiBjbGFzc2lmaWVyCmZpdF9nbG0gPC0gZ2xtKGFzLmZhY3Rvcih5KSB+IC4sIGRhdGEgPSBub3JtMmQsIGZhbWlseSA9IGJpbm9taWFsKQpmaXRfbGRhIDwtIE1BU1M6OmxkYShhcy5mYWN0b3IoeSkgfiAuLCBkYXRhID0gbm9ybTJkKQppbnZpc2libGUoY2FwdHVyZS5vdXRwdXQoZml0X2htYyA8LSBrc3ZtKCAgIyB1c2Uga3N2bSgpIHRvIGZpbmQgdGhlIE9TSAogIHggPSBkYXRhLm1hdHJpeChub3JtMmRbYygieDEiLCAieDIiKV0pLAogIHkgPSBhcy5mYWN0b3Iobm9ybTJkJHkpLCAKICBrZXJuZWwgPSAidmFuaWxsYWRvdCIsICAjIG5vIGZhbmN5IGtlcm5lbCwganVzdCBvcmRpbmFyeSBkb3QgcHJvZHVjdAogIEMgPSBJbmYsICAgICAgICAgICAgICAgICMgdG8gYXBwcm94aW1hdGUgbWF4aW1hbCBtYXJnaW4gY2xhc3NpZmllcgogIHByb2IubW9kZWwgPSBUUlVFICAgICAgICMgbmVlZGVkIHRvIG9idGFpbiBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcwopKSkKCiMgR3JpZCBvdmVyIHdoaWNoIHRvIGV2YWx1YXRlIGRlY2lzaW9uIGJvdW5kYXJpZXMKbnB0cyA8LSA1MDAKeGdyaWQgPC0gZXhwYW5kLmdyaWQoCiAgeDEgPSBzZXEoZnJvbSA9IC02LCA2LCBsZW5ndGggPSBucHRzKSwKICB4MiA9IHNlcShmcm9tID0gLTYsIDYsIGxlbmd0aCA9IG5wdHMpCikKCiMgUHJlZGljdGVkIHByb2JhYmlsaXRpZXMgKGFzIGEgdHdvLWNvbHVtbiBtYXRyaXgpCnByb2JfZ2xtIDwtIHByZWRpY3QoZml0X2dsbSwgbmV3ZGF0YSA9IHhncmlkLCB0eXBlID0gInJlc3BvbnNlIikKcHJvYl9nbG0gPC0gY2JpbmQoIjEiID0gMSAtIHByb2JfZ2xtLCAiMiIgPSBwcm9iX2dsbSkKcHJvYl9sZGEgPC0gcHJlZGljdChmaXRfbGRhLCBuZXdkYXRhID0geGdyaWQpJHBvc3Rlcmlvcgpwcm9iX2htYyA8LSBwcmVkaWN0KGZpdF9obWMsIG5ld2RhdGEgPSB4Z3JpZCwgdHlwZSA9ICJwcm9iYWJpbGl0aWVzIikKCiMgQWRkIHByZWRpY3RlZCBjbGFzcyBwcm9iYWJpbGl0aWVzCnhncmlkMiA8LSB4Z3JpZCAlPiUKICBjYmluZCgiR0xNIiA9IHByb2JfZ2xtWywgMUxdLCAKICAgICAgICAiTERBIiA9IHByb2JfbGRhWywgMUxdLCAKICAgICAgICAiSE1DIiA9IHByb2JfaG1jWywgMUxdKSAlPiUKICB0aWR5cjo6Z2F0aGVyKE1vZGVsLCBQcm9iLCAteDEsIC14MikKCiMgU2NhdHRlcnBsb3QKZ2dwbG90KG5vcm0yZCwgYWVzKHggPSB4MSwgeSA9IHgyKSkgKwogIAogICMgTGFiZWwgb3V0bGllcgogIGdlb21fY3VydmUoeCA9IHRhaWwobm9ybTJkLCBuID0gMSkkeDEgLSAwLjIsIHkgPSB0YWlsKG5vcm0yZCwgbiA9IDEpJHgyIC0gMC4yLCAKICAgICAgICAgICAgIHhlbmQgPSAtNCwgeWVuZCA9IDMsIGN1cnZhdHVyZSA9IC0wLjUsIGFuZ2xlID0gOTAsCiAgICAgICAgICAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4wMywgdW5pdHMgPSAibnBjIikpKSArCiAgYW5ub3RhdGUoInRleHQiLCBsYWJlbCA9ICJPdXRsaWVyPyIsIHggPSAtNCwgeSA9IDMuNSwgc2l6ZSA9IDUpICsKCiAgIyBTY2F0dGVycGxvdCwgZXRjLgogIGdlb21fcG9pbnQoYWVzKHNoYXBlID0geSwgY29sb3IgPSB5KSwgc2l6ZSA9IDMsIGFscGhhID0gMC43NSkgKwogIHhsYWIoIkluY29tZSAoc3RhbmRhcmRpemVkKSIpICsKICB5bGFiKCJMb3Qgc2l6ZSAoc3RhbmRhcmRpemVkKSIpICsKICB4bGltKC02LCA2KSArCiAgeWxpbSgtNiwgNikgKwogIGNvb3JkX2ZpeGVkKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIHRoZW1lX2J3KCkgKwogIHNjYWxlX3NoYXBlX2Rpc2NyZXRlKAogICAgbmFtZSA9ICJPd25zIGEgcmlkaW5nXG5tb3dlcj8iLAogICAgYnJlYWtzID0gYygxLCAyKSwKICAgIGxhYmVscyA9IGMoIlllcyIsICJObyIpCiAgKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKAogICAgbmFtZSA9ICJPd25zIGEgcmlkaW5nXG5tb3dlcj8iLAogICAgcGFsZXR0ZSA9ICJEYXJrMiIsCiAgICBicmVha3MgPSBjKDEsIDIpLAogICAgbGFiZWxzID0gYygiWWVzIiwgIk5vIikKICApICsKICBzdGF0X2NvbnRvdXIoZGF0YSA9IHhncmlkMiwgYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0gUHJvYiwgbGluZXR5cGUgPSBNb2RlbCksIAogICAgICAgICAgICAgICBicmVha3MgPSAwLjUsIGNvbG9yID0gImJsYWNrIikKYGBgCgpGaWd1cmUgMTQuNToKCmBgYHtyIHNtYywgZWNobz1UUlVFLCBmaWcud2lkdGg9OCwgZmlnLmFzcD0wLjUsIG91dC53aWR0aD0iMTAwJSIsIGZpZy5jYXA9IlNvZnQgbWFyZ2luIGNsYXNzaWZpZXIuIExlZnQ6IFplcm8gYnVkZ2V0IGZvciBvdmVybGFwIChpLmUuLCB0aGUgSE1DKS4gUmlnaHQ6IE1heGltdW1uIGFsbG93YWJsZSBvdmVybGFwLiBUaGUgc29saWQgYmxhY2sgcG9pbnRzIHJlcHJlc2VudCB0aGUgc3VwcG9ydCB2ZWN0b3JzIHRoYXQgZGVmaW5lIHRoZSBtYXJnaW4gYm91bmRhcmllcy4ifQojIEZpdCB0aGUgZW50aXJlIHJlZ3VsYXJpemF0aW9uIHBhdGgKZml0X3NtYyA8LSBzdm1wYXRoKAogIHggPSBkYXRhLm1hdHJpeChub3JtMmRbYygieDEiLCAieDIiKV0pLCAKICB5ID0gaWZlbHNlKG5vcm0yZCR5ID09IDEsIDEsIC0xKQopCiMgUGxvdCBib3RoIGV4dHJlbWVzCnAxIDwtIHBsb3Rfc3ZtcGF0aChmaXRfc21jLCBzdGVwID0gbWF4KGZpdF9zbWMkU3RlcCksIG1haW4gPSBleHByZXNzaW9uKEMgPT0gMCkpCnAyIDwtIHBsb3Rfc3ZtcGF0aChmaXRfc21jLCBzdGVwID0gbWluKGZpdF9zbWMkU3RlcCksIG1haW4gPSBleHByZXNzaW9uKEMgPT0gaW5maW5pdHkpKQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxKQpgYGAKCiMjIFRoZSBzdXBwb3J0IHZlY3RvciBtYWNoaW5lCgpGaWd1cmUgMTQuNjoKCmBgYHtyIHN2bS1jaXJjbGUsIGVjaG89VFJVRSwgZmlnLndpZHRoPTEyLCBmaWcuYXNwPTEvMywgb3V0LndpZHRoPSIxMDAlIiwgZmlnLmNhcD0iU2ltdWxhdGVkIG5lc3RlZCBjaXJjbGUgZGF0YS4gX0xlZnQ6XyBUaGUgdHdvIGNsYXNzZXMgaW4gdGhlIG9yaWdpbmFsICgyLUQpIGZlYXR1cmUgc3BhY2UuIF9NaWRkbGU6XyBUaGUgdHdvIGNsYXNzZXMgaW4gdGhlIGVubGFyZ2VkICgzLUQpIGZlYXR1cmUgc3BhY2UuIF9SaWdodDpfIFRoZSBkZWNpc2lvbiBib3VuZGFyeSBmcm9tIHRoZSBITUMgaW4gdGhlIGVubGFyZ2VkIGZlYXR1cmUgc3BhY2UgcHJvamVjdGVkIGJhY2sgaW50byB0aGUgb3JpZ2luYWwgZmVhdHVyZSBzcGFjZS4ifQojIExvYWQgcmVxdWlyZWQgcGFja2FnZXMKbGlicmFyeShncmlkKQpsaWJyYXJ5KGxhdHRpY2UpCgojIFNpbXVsYXRlIGRhdGEKc2V0LnNlZWQoMTQzMikKY2lyY2xlIDwtIGFzLmRhdGEuZnJhbWUobWxiZW5jaDo6bWxiZW5jaC5jaXJjbGUoCiAgbiA9IDIwMCwKICBkID0gMgopKQpuYW1lcyhjaXJjbGUpIDwtIGMoIngxIiwgIngyIiwgInkiKSAgIyByZW5hbWUgY29sdW1ucwoKIyBGaXQgYSBzdXBwb3J0IHZlY3RvciBtYWNoaW5lIChTVk0pCmZpdF9zdm1fcG9seSA8LSBrc3ZtKCAKICB4ID0gZGF0YS5tYXRyaXgoY2lyY2xlW2MoIngxIiwgIngyIildKSwKICB5ID0gYXMuZmFjdG9yKGNpcmNsZSR5KSwgCiAga2VybmVsID0gInBvbHlkb3QiLCAgICAgICAjIHBvbHlub21pYWwga2VybmVsCiAga3BhciA9IGxpc3QoZGVncmVlID0gMiksICAjIGtlcm5lbCBwYXJhbWV0ZXJzCiAgQyA9IEluZiwgICAgICAgICAgICAgICAgICAjIHRvIGFwcHJveGltYXRlIG1heGltYWwgbWFyZ2luIGNsYXNzaWZpZXIKICBwcm9iLm1vZGVsID0gVFJVRSAgICAgICAgICMgbmVlZGVkIHRvIG9idGFpbiBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcwopCgojIEdyaWQgb3ZlciB3aGljaCB0byBldmFsdWF0ZSBkZWNpc2lvbiBib3VuZGFyaWVzCm5wdHMgPC0gNTAwCnhncmlkIDwtIGV4cGFuZC5ncmlkKAogIHgxID0gc2VxKGZyb20gPSAtMS4yNSwgMS4yNSwgbGVuZ3RoID0gbnB0cyksCiAgeDIgPSBzZXEoZnJvbSA9IC0xLjI1LCAxLjI1LCBsZW5ndGggPSBucHRzKQopCgojIFByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIChhcyBhIHR3by1jb2x1bW4gbWF0cml4KQpwcm9iX3N2bV9wb2x5IDwtIHByZWRpY3QoZml0X3N2bV9wb2x5LCBuZXdkYXRhID0geGdyaWQsIHR5cGUgPSAicHJvYmFiaWxpdGllcyIpCgojIFNjYXR0ZXJwbG90CnAxIDwtIGNvbnRvdXJwbG90KAogIHggPSBwcm9iX3N2bV9wb2x5WywgMV0gfiB4MSAqIHgyLCAKICBkYXRhID0geGdyaWQsIAogIGF0ID0gMCwgCiAgbGFiZWxzID0gRkFMU0UsCiAgc2NhbGVzID0gbGlzdCh0Y2sgPSBjKDEsIDApKSwKICB4bGFiID0gIngxIiwKICB5bGFiID0gIngyIiwKICBtYWluID0gIk9yaWdpbmFsIGZlYXR1cmUgc3BhY2UiLAogIHBhbmVsID0gZnVuY3Rpb24oeCwgeSwgeiwgLi4uKSB7CiAgICBwYW5lbC5jb250b3VycGxvdCh4LCB5LCB6LCAuLi4pCiAgICBwYW5lbC54eXBsb3QoCiAgICAgIHggPSBjaXJjbGUkeDEsIAogICAgICB5ID0gY2lyY2xlJHgyLCAKICAgICAgZ3JvdXBzID0gY2lyY2xlJHksIAogICAgICBwY2ggPSAxOSwgCiAgICAgIGNleCA9IDEsCiAgICAgIGNvbCA9IGFkanVzdGNvbG9yKGRhcmsyWzFMOjJMXSwgYWxwaGEuZiA9IDAuNSksCiAgICAgIC4uLgogICAgKQogIH0KKQoKIyBFbmxhcmdlIGZlYXR1cmUgc3BhY2UKY2lyY2xlXzNkIDwtIGNpcmNsZQpjaXJjbGVfM2QkeDMgPC0gY2lyY2xlXzNkJHgxXjIgKyBjaXJjbGVfM2QkeDJeMgoKIyAzLUQgc2NhdHRlcnBsb3QKcDIgPC0gY2xvdWQoCiAgeCA9IHgzIH4geDEgKiB4MiwgCiAgZGF0YSA9IGNpcmNsZV8zZCwgCiAgZ3JvdXBzID0geSwKICBtYWluID0gIkVubGFyZ2VkIGZlYXR1cmUgc3BhY2UiLAogIHBhci5zZXR0aW5ncyA9IGxpc3QoCiAgICBzdXBlcnBvc2Uuc3ltYm9sID0gbGlzdCgKICAgICAgcGNoID0gMTksCiAgICAgIGNleCA9IDEsCiAgICAgIGNvbCA9IGFkanVzdGNvbG9yKGRhcmsyWzFMOjJMXSwgYWxwaGEuZiA9IDAuNSkKICAgICkKICApCikgCgojIFNjYXR0ZXJwbG90IHdpdGggZGVjaXNpb24gYm91bmRhcnkKcDMgPC0gY29udG91cnBsb3QoCiAgeCA9IHByb2Jfc3ZtX3BvbHlbLCAxXSB+IHgxICogeDIsIAogIGRhdGEgPSB4Z3JpZCwgCiAgYXQgPSAwLjUsIAogIGxhYmVscyA9IEZBTFNFLAogIHNjYWxlcyA9IGxpc3QodGNrID0gYygxLCAwKSksCiAgeGxhYiA9ICJ4MSIsCiAgeWxhYiA9ICJ4MiIsCiAgbWFpbiA9ICJOb24tbGluZWFyIGRlY2lzaW9uIGJvdW5kYXJ5IiwKICBwYW5lbCA9IGZ1bmN0aW9uKHgsIHksIHosIC4uLikgewogICAgcGFuZWwuY29udG91cnBsb3QoeCwgeSwgeiwgLi4uKQogICAgcGFuZWwueHlwbG90KAogICAgICB4ID0gY2lyY2xlJHgxLCAKICAgICAgeSA9IGNpcmNsZSR4MiwgCiAgICAgIGdyb3VwcyA9IGNpcmNsZSR5LCAKICAgICAgcGNoID0gMTksIAogICAgICBjZXggPSAxLAogICAgICBjb2wgPSBhZGp1c3Rjb2xvcihkYXJrMlsxTDoyTF0sIGFscGhhLmYgPSAwLjUpLAogICAgICAuLi4KICAgICkKICB9CikgCgojIENvbWJpbmUgcGxvdHMKZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UocDEsIHAyLCBwMywgbnJvdyA9IDEpCmBgYAoKYGBge3Igc3ZtLWdldE1vZGVsSW5mb30KIyBMaW5lYXIgKGkuZS4sIHNvZnQgbWFyZ2luIGNsYXNzaWZpZXIpCmNhcmV0OjpnZXRNb2RlbEluZm8oInN2bUxpbmVhciIpJHN2bUxpbmVhciRwYXJhbWV0ZXJzCgojIFBvbHlub21pYWwga2VybmVsCmNhcmV0OjpnZXRNb2RlbEluZm8oInN2bVBvbHkiKSRzdm1Qb2x5JHBhcmFtZXRlcnMKCiMgUmFkaWFsIGJhc2lzIGtlcm5lbApjYXJldDo6Z2V0TW9kZWxJbmZvKCJzdm1SYWRpYWwiKSRzdm1SYWRpYWwkcGFyYW1ldGVycwpgYGAKCkZpZ3VyZSAxNC43OgoKYGBge3IgdHdvLXNwaXJhbHMsIGVjaG89VFJVRSwgZmlnLndpZHRoPTgsIGZpZy5hc3A9MC41LCBvdXQud2lkdGg9IjEwMCUiLCBmaWcuY2FwPSJUd28gc3BpcmFscyBiZW5jaG1hcmsgcHJvYmxlbS4gX0xlZnQ6XyBEZWNpc2lvbiBib3VuZGFyeSBmcm9tIGEgcmFuZG9tIGZvcmVzdC4gX1JpZ2h0Ol8gRGVjaXNpb24gYm91bmRhcnkgZnJvbSBhbiBTVk0gd2l0aCByYWRpYWwgYmFzaXMga2VybmVsLiJ9CiMgTG9hZCByZXF1aXJlZCBwYWNrYWdlcwpsaWJyYXJ5KGtlcm5sYWIpICAjIGZvciBmaXR0aW5nIFNWTXMKbGlicmFyeShtbGJlbmNoKSAgIyBmb3IgTUwgYmVuY2htYXJrIGRhdGEgc2V0cwoKIyBTaW11bGF0ZSB0cmFpbiBhbmQgdGVzdCBzZXRzCnNldC5zZWVkKDA4NDEpCnNwaXJhbHMgPC0gYXMuZGF0YS5mcmFtZSgKICBtbGJlbmNoLnNwaXJhbHMoMzAwLCBjeWNsZXMgPSAyLCBzZCA9IDAuMDkpCikKbmFtZXMoc3BpcmFscykgPC0gYygieDEiLCAieDIiLCAiY2xhc3NlcyIpCgojIEZpdCBhbiBSRgpzZXQuc2VlZCg3MjU2KQpzcGlyYWxzX3JmbyA8LSByYW5nZXI6OnJhbmdlcihjbGFzc2VzIH4gLiwgZGF0YSA9IHNwaXJhbHMsIHByb2JhYmlsaXR5ID0gVFJVRSkKCiMgRml0IGFuIFNWTSB1c2luZyBhIHJhZGlhbCBiYXNpcyBmdW5jdGlvbiBrZXJuZWwKc3BpcmFsc19zdm0gPC0ga3N2bShjbGFzc2VzIH4geDEgKyB4MiwgZGF0YSA9IHNwaXJhbHMsIGtlcm5lbCA9ICJyYmZkb3QiLAogICAgICAgICAgICAgICAgICAgIEMgPSA1MDAsIHByb2IubW9kZWwgPSBUUlVFKQoKIyBHcmlkIG92ZXIgd2hpY2ggdG8gZXZhbHVhdGUgZGVjaXNpb24gYm91bmRhcmllcwpucHRzIDwtIDUwMAp4Z3JpZCA8LSBleHBhbmQuZ3JpZCgKICB4MSA9IHNlcShmcm9tID0gLTIsIDIsIGxlbmd0aCA9IG5wdHMpLAogIHgyID0gc2VxKGZyb20gPSAtMiwgMiwgbGVuZ3RoID0gbnB0cykKKQoKIyBQcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyAoYXMgYSB0d28tY29sdW1uIG1hdHJpeCkKcHJvYl9yZm8gPC0gcHJlZGljdChzcGlyYWxzX3JmbywgZGF0YSA9IHhncmlkKSRwcmVkaWN0aW9ucwpwcm9iX3N2bSA8LSBwcmVkaWN0KHNwaXJhbHNfc3ZtLCBuZXdkYXRhID0geGdyaWQsIHR5cGUgPSAicHJvYmFiaWxpdGllcyIpCgojIEFkZCBwcmVkaWN0ZWQgY2xhc3MgcHJvYmFiaWxpdGllcwp4Z3JpZDIgPC0geGdyaWQgJT4lCiAgY2JpbmQoIlJGIiA9IHByb2JfcmZvWywgMUxdLCAKICAgICAgICAiU1ZNIiA9IHByb2Jfc3ZtWywgMUxdKSAlPiUKICB0aWR5cjo6Z2F0aGVyKE1vZGVsLCBQcm9iLCAteDEsIC14MikKCiMgU2NhdHRlcnBsb3RzIHdpdGggZGVjaXNpb24gYm91bmRhcmllcwpnZ3Bsb3Qoc3BpcmFscywgYWVzKHggPSB4MSwgeSA9IHgyKSkgKwogIGdlb21fcG9pbnQoYWVzKHNoYXBlID0gY2xhc3NlcywgY29sb3IgPSBjbGFzc2VzKSwgc2l6ZSA9IDMsIGFscGhhID0gMC43NSkgKwogIHhsYWIoZXhwcmVzc2lvbihYWzFdKSkgKwogIHlsYWIoZXhwcmVzc2lvbihYWzJdKSkgKwogIHhsaW0oLTIsIDIpICsKICB5bGltKC0yLCAyKSArCiAgY29vcmRfZml4ZWQoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgdGhlbWVfYncoKSArCiAgc3RhdF9jb250b3VyKGRhdGEgPSB4Z3JpZDIsIGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IFByb2IpLCAKICAgICAgICAgICAgICAgYnJlYWtzID0gMC41LCBjb2xvciA9ICJibGFjayIpICsKICBmYWNldF93cmFwKCB+IE1vZGVsKQpgYGAKCgojIyMgU3VwcG9ydCB2ZWN0b3IgcmVncmVzc2lvbgoKRmlndXJlIDE0Ljg6CgpgYGB7ciBlcHMtYmFuZCwgZWNobz1UUlVFLCBmaWcud2lkdGg9NiwgZmlnLmFzcD0wLjYxOCwgZmlnLmNhcD0iJFxcZXBzaWxvbiQtaW5zZW5zaXRpdmUgcmVncmVzc2lvbiBiYW5kLiBUaGUgc29saWQgYmxhY2sgbGluZSByZXByZXNlbnRzIHRoZSBlc3RpbWF0ZWQgcmVncmVzc2lvbiBjdXJ2ZSAkZlxcbGVmdCh4XFxyaWdodCkkLiJ9CmdncGxvdCgpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSA0LCBzbG9wZSA9IDEsIGxpbmV0eXBlID0gMiwgY29sb3IgPSBkYXJrMlsxTF0pICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAzLCBzbG9wZSA9IDEpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAyLCBzbG9wZSA9IDEsIGxpbmV0eXBlID0gMiwgY29sb3IgPSBkYXJrMlsxTF0pICsKICB4bGltKDAsIDUpICsKICB5bGltKDEsIDEwKSArCiAgeGxhYihleHByZXNzaW9uKHgpKSArCiAgeWxhYihleHByZXNzaW9uKGYoeCkpKSArCiAgdGhlbWVfYncoKSArCiAgYW5ub3RhdGUoInRleHQiLCBsYWJlbCA9ICJmKHgpICsgZXBzaWxvbiIsIHBhcnNlID0gVFJVRSwgeCA9IDIsIHkgPSA2Ljc1LCAKICAgICAgICAgICBzaXplID0gNiwgY29sb3IgPSBkYXJrMlsxTF0pICsKICBhbm5vdGF0ZSgidGV4dCIsIGxhYmVsID0gImYoeCkgLSBlcHNpbG9uIiwgcGFyc2UgPSBUUlVFLCB4ID0gMiwgeSA9IDMuMTUsIAogICAgICAgICAgIHNpemUgPSA2LCBjb2xvciA9IGRhcmsyWzFMXSkKYGBgCgpGaWd1cmUgMTQuOToKCmBgYHtyIHNpbmMsIGVjaG89VFJVRSwgZmlnLndpZHRoPTYsIGZpZy5hc3A9MC42MTgsIGZpZy5jYXA9IlNpbXVsYXRlZCBkYXRhIGZyb20gYSBzaW5jIGZ1bmN0aW9uIHdpdGggYWRkZWQgbm9pc2UuIn0KIyBTaW11bGF0ZSBkYXRhCnNldC5zZWVkKDEyMTgpCnggPC0gc2VxKGZyb20gPSAtMjAsIHRvID0gMjAsIGJ5ID0gMC4xKQp5IDwtIHNpbih4KSAvIHggKyBybm9ybShsZW5ndGgoeCksIHNkID0gMC4wMykKZGYgPC0gbmEub21pdChkYXRhLmZyYW1lKHggPSB4LCB5ID0geSkpCgojIFBsb3QgcmVzdWx0cwpnZ3Bsb3QoZGYsIGFlcyh4ID0geCwgeSA9IHkpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fbGluZShhZXMoeCA9IHgsIHkgPSBzaW4oeCkgLyB4KSwgc2l6ZSA9IDEsIGNvbG9yID0gImRhcmtvcmFuZ2UiKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSAKYGBgCgpGaWd1cmUgMTQuMTA6CgpgYGB7ciBzaW5jLXByZWRpY3Rpb25zLCBlY2hvPVRSVUUsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD00LCBvdXQud2lkdGg9IjEwMCUiLCBmaWcuY2FwPSJTaW11bGF0ZWQgc2luZSJ9CiMgU1ZSIG1vZGVsCnNldC5zZWVkKDEwMSkKc3ZyIDwtIGtlcm5sYWI6Omtzdm0oeSB+IHgsIGRhdGEgPSBkZiwga2VybmVsID0gInJiZmRvdCIsIGtwYXIgPSAiYXV0b21hdGljIiwKICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJlcHMtc3ZyIiwgZXBzaWxvbiA9IDAuMSkKCiMgTUFSUyBtb2RlbAptYXJzIDwtIGVhcnRoOjplYXJ0aCh5IH4geCwgZGF0YSA9IGRmKQoKIyBSYW5kb20gZm9yZXN0CnNldC5zZWVkKDEwMikKcmZvIDwtIHJhbmdlcjo6cmFuZ2VyKHkgfiB4LCBkYXRhID0gZGYpCgojIEdhdGhlciBwcmVkaWN0aW9ucwpkZiRTVlIgPC0gcHJlZGljdChzdnIsIG5ld2RhdGEgPSBkZikKZGYkTUFSUyA8LSBwcmVkaWN0KG1hcnMsIG5ld2RhdGEgPSBkZilbLCAxTCwgZHJvcCA9IFRSVUVdCmRmJFJGIDwtIHByZWRpY3QocmZvLCBkYXRhID0gZGYpJHByZWRpY3Rpb25zCmRmIDwtIGRmICU+JSB0aWR5cjo6Z2F0aGVyKE1vZGVsLCBQcmVkaWN0aW9uLCAteCwgLXkpCgojIFBsb3QgcmVzdWx0cwpnZ3Bsb3QoZGYsIGFlcyh4ID0geCwgeSA9IHkpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fbGluZShhZXMoeCA9IHgsIHkgPSBQcmVkaWN0aW9uLCBjb2xvciA9IE1vZGVsKSwgc2l6ZSA9IDEpICsKICBmYWNldF93cmFwKCB+IE1vZGVsKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSAKYGBgCgojIyBKb2IgYXR0cml0aW9uIGV4YW1wbGUKCmBgYHtyIHN2bS1hdHRyaXRpb24tdHJhaW59CiMgVHVuZSBhbiBTVk0gd2l0aCByYWRpYWwgYmFzaXMga2VybmVsCnNldC5zZWVkKDE4NTQpICAjIGZvciByZXByb2R1Y2liaWxpdHkKY2h1cm5fc3ZtIDwtIHRyYWluKAogIEF0dHJpdGlvbiB+IC4sIAogIGRhdGEgPSBjaHVybl90cmFpbiwKICBtZXRob2QgPSAic3ZtUmFkaWFsIiwgICAgICAgICAgICAgICAKICBwcmVQcm9jZXNzID0gYygiY2VudGVyIiwgInNjYWxlIiksICAKICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApLAogIHR1bmVMZW5ndGggPSAxMAopCmBgYAoKCmBgYHtyIDA4LWF0dHJpdGlvbi0wMywgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9Mywgb3V0LndpZHRoID0gIjgwJSJ9CiMgUGxvdCByZXN1bHRzCmdncGxvdChjaHVybl9zdm0pICsgdGhlbWVfbGlnaHQoKQoKIyBQcmludCByZXN1bHRzCmNodXJuX3N2bSRyZXN1bHRzCmBgYAoKIyMjIENsYXNzIHByb2JhYmlsaXRpZXMKCmBgYHtyIGF1Y30KIyBDb250cm9sIHBhcmFtcyBmb3IgU1ZNCmN0cmwgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJjdiIsIAogIG51bWJlciA9IDEwLCAKICBjbGFzc1Byb2JzID0gVFJVRSwgICAgICAgICAgICAgICAgIAogIHN1bW1hcnlGdW5jdGlvbiA9IHR3b0NsYXNzU3VtbWFyeSAgIyBhbHNvIG5lZWRlZCBmb3IgQVVDL1JPQwopCgojIFR1bmUgYW4gU1ZNCnNldC5zZWVkKDU2MjgpICAjIGZvciByZXByb2R1Y2liaWxpdHkKY2h1cm5fc3ZtX2F1YyA8LSB0cmFpbigKICBBdHRyaXRpb24gfiAuLCAKICBkYXRhID0gY2h1cm5fdHJhaW4sCiAgbWV0aG9kID0gInN2bVJhZGlhbCIsICAgICAgICAgICAgICAgCiAgcHJlUHJvY2VzcyA9IGMoImNlbnRlciIsICJzY2FsZSIpLCAgCiAgbWV0cmljID0gIlJPQyIsICAjIGFyZWEgdW5kZXIgUk9DIGN1cnZlIChBVUMpICAgICAgIAogIHRyQ29udHJvbCA9IGN0cmwsCiAgdHVuZUxlbmd0aCA9IDEwCikKCiMgUHJpbnQgcmVzdWx0cwpjaHVybl9zdm1fYXVjJHJlc3VsdHMKYGBgCgoKYGBge3IgY29uZnVzaW9uLW1hdHJpeC1zdm19CmNvbmZ1c2lvbk1hdHJpeChjaHVybl9zdm1fYXVjKQpgYGAKCgojIyBGZWF0dXJlIGludGVycHJldGF0aW9uCgpgYGB7ciBzdm0tYXR0cml0aW9uLXZpcC1wcmVkaWN0aW9uLWZ1bmN0aW9ufQpwcm9iX3llcyA8LSBmdW5jdGlvbihvYmplY3QsIG5ld2RhdGEpIHsKICBwcmVkaWN0KG9iamVjdCwgbmV3ZGF0YSA9IG5ld2RhdGEsIHR5cGUgPSAicHJvYiIpWywgIlllcyJdCn0KYGBgCgpgYGB7ciBzdm0tYXR0cml0aW9uLXZpcC1wZXJtdXRhdGlvbiwgZmlnLndpZHRoPTYsIGZpZy5hc3A9MC42MTgsIHdhcm5pbmdzPUZBTFNFfQojIFZhcmlhYmxlIGltcG9ydGFuY2UgcGxvdApzZXQuc2VlZCgyODI3KSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CnZpcChjaHVybl9zdm1fYXVjLCBtZXRob2QgPSAicGVybXV0ZSIsIG5zaW0gPSA1LCB0cmFpbiA9IGNodXJuX3RyYWluLCAKICAgIHRhcmdldCA9ICJBdHRyaXRpb24iLCBtZXRyaWMgPSAiYXVjIiwgcmVmZXJlbmNlX2NsYXNzID0gIlllcyIsIAogICAgcHJlZF93cmFwcGVyID0gcHJvYl95ZXMpCmBgYAoKYGBge3Igc3ZtLWF0dHJpdGlvbi1wZHBzLCBmaWcud2lkdGg9NiwgZmlnLmFzcD0wLjYxOCwgb3V0LndpZHRoPSIxMDAlIiwgd2FybmluZ3M9RkFMU0V9CmZlYXR1cmVzIDwtIGMoIk92ZXJUaW1lIiwgIldvcmtMaWZlQmFsYW5jZSIsIAogICAgICAgICAgICAgICJKb2JTYXRpc2ZhY3Rpb24iLCAiSm9iUm9sZSIpCnBkcHMgPC0gbGFwcGx5KGZlYXR1cmVzLCBmdW5jdGlvbih4KSB7CiAgcGFydGlhbChjaHVybl9zdm1fYXVjLCBwcmVkLnZhciA9IHgsIHdoaWNoLmNsYXNzID0gMiwgIAogICAgICAgICAgcHJvYiA9IFRSVUUsIHBsb3QgPSBUUlVFLCBwbG90LmVuZ2luZSA9ICJnZ3Bsb3QyIikgKwogICAgY29vcmRfZmxpcCgpCn0pCmdyaWQuYXJyYW5nZShncm9icyA9IHBkcHMsICBuY29sID0gMikKYGBg