Bregman divergences (BD)
Definition Let
\(\phi:\mathcal{C}\rightarrow\mathbb{R}\) be
a strictly convex and continuously differentiable function defined on a
measurable convex subset \(\mathcal{C}\subset\mathbb{R}^d\) . Let \(int(\mathcal{C})\) denote its relative
interior. A Bregman divergence indexed by \(\phi\) is a dissimilarity measure \(d_{\phi}:\mathcal{C}\times
int(\mathcal{C})\rightarrow\mathbb{R}\) defined for any pair
\((x,y)\in \mathcal{C}\times
int(\mathcal{C})\) by, \[\begin{equation}
\label{eq:1.10}
d_{\phi}(x,y)=\phi(x)-\phi(y)-\langle x-y,\nabla\phi(y)\rangle
\end{equation}\] where \(\nabla\phi(y)\) denotes the gradient of
\(\phi\) computed at a point \(y\in int(\mathcal{C})\) . A Bregman
divergence is not necessarily a metric as it may not be symmetric and
the triangular inequality might not be satisfied.
This section defines all the Bregman divergences used. The list of
all the Bregman divergences is given in the table below:
Euclidean
\({\|x\|_2^2}=\sum_{i=1}^dx_i^2\)
\(\|x-y\|_2^2\)
\(\mathbb{R}^d\)
General Kullback-Leibler
\(\sum_{i=1}^d x_i\ln(
x_i)\)
\(\sum_{i=1}^d( x_i\ln(\frac{
x_i}{y_i})-(x_i-y_i))\)
\((0,+\infty)^d\)
Logistic
\(\sum_{i=1}^d(x_i\ln(
x_i)+(1- x_i)\ln(1- x_i))\)
\(\sum_{i=1}^d\Big(
x_i\ln(\frac{x_i}{y_i})+(1- x_i)\ln(\frac{1-
x_i}{1-y_i})\Big)\)
\((0,1)^d\)
Itakura-Saito
\(-\sum_{i=1}^d\ln(
x_i)\)
\(\sum_{i=1}^d\Big(\frac{
x_i}{y_i}-\ln(\frac{ x_i}{y_i})-1\Big)\)
\((0,+\infty)^d\)
Exponential
\(\sum_{i=1}^de^{x_i}\)
\(\sum_{i=1}^d(e^{x_i}-e^{y_i}-e^{y_i}(x_i-y_i))\)
\(\mathbb{R}^d\)
Polynomial
\(\sum_{i=1}^d|x|^p,p>2\)
\(\sum_{k=1}^d(|x_k|^p-|y_k|^p-\text{sign}(y_k)^pp(x_k-y_k)y_k^{p-1})\)
\(\mathbb{R}^d\)
Look-up list of Bregman
divergences
The codes below provide a look-up list of all the BDs defined in the
table above.
euclidDiv <- function(X., y., deg = NULL){
res <- sweep(X., 2, y.)
return(rowSums(res^2))
}
gklDiv <- function(X., y., deg = NULL){
res <- c("/", "-") %>%
map(.f = ~ sweep(X., 2, y., FUN = .x))
return(rowSums(X.*log(res[[1]]) - res[[2]]))
}
logDiv <- function(X., y., deg = NULL){
res <- map2(.x = list(X., 1-X.),
.y = list(y., 1-y.),
.f = ~ sweep(.x, 2, .y, FUN = "/"))
return(rowSums(X.*log(res[[1]])+(1-X.)*log(res[[2]])))
}
itaDiv <- function(X., y., deg = NULL){
res <- sweep(X., 2, y., FUN = "/")
return(rowSums(res-log(res) - 1))
}
expDiv <- function(X., y., deg = NULL){
exp_y <- exp(y.)
res <- sweep(1+X., 2, y.) %>%
sweep(2, exp_y, FUN = "*")
return(rowSums(exp(X.)-res))
}
polyDiv <- function(X., y., deg = 3){
S <- map2(.x = list(X., X.^deg),
.y = list(y., y.^deg),
.f = ~ sweep(.x,
MARGIN = 2,
STATS = .y,
FUN = "-"))
if(deg %% 2 == 0){
Tem <- sweep(S[[1]], 2, y.^(deg-1), FUN = "*")
res <- rowSums(S[[2]] - deg * Tem)
}
else{
Tem <- sweep(S[[1]], 2, sign(y.) * y.^(deg-1), FUN = "*")
res <- rowSums(S[[2]] - deg * Tem)
}
return(res)
}
lookup_div <- list(
euclidean = euclidDiv,
gkl = gklDiv,
logistic = logDiv,
itakura = itaDiv,
exponential = expDiv,
polynomial = polyDiv
)
Function :
BregmanDiv
This function computes Bregman divergence matrix between two sets of
data points. Each set of data points should be represented by a matrix,
data frame, or tibble object where each row corresponds to each
individual data point.
BregmanDiv <- function(X.,
C.,
div = c("euclidean",
"gkl",
"logistic",
"itakura",
"exponential",
"polynomial"),
deg = 3){
div <- match.arg(div)
d_c <- dim(C.)
if(is.null(d_c)){
C <- matrix(C., nrow = 1, byrow = TRUE)
} else{
C <- as.matrix(C.)
}
if(is.null(dim(X.))){
X <- matrix(X., nrow = 1, byrow = TRUE)
} else{
X <- as.matrix(X.)
}
dis <- map_dfc(.x = 1:dim(C)[1],
.f = ~ tibble('{{.x}}' := lookup_div[[div]](X, C[.x,], deg = deg)))
return(dis)
}
🧾 Remark.2 : Note that “logistic” Bregman divergence
can handle only data points with domain \({\cal C}=(0,1)^d\) , therefore, it should be
used only in suitable cases.
Step \(K\) :
\(K\) -means with Bregman
divergences
This section implements \(K\) -means
algorithm using Bregman divergences which corresponds to the step \(K\) of KFC procedure.
Function :
findClosestCentroid
and newCentroids
These two functions perform the main steps of \(K\) -means algorithm. Function
findClosestCentroid
assigns any data points to some cluster
according to the smallest divergence between the data point and the
centroid. It provides a vector of clusters of all the data points. From
there, function newCentroids
computes new centroids given
the cluster labels of all data points.
findClosestCentroid <- function(x., centroids., div, deg = 3){
dist <- BregmanDiv(x., centroids., div, deg)
clust <- 1:nrow(x.) %>%
map_int(.f = ~ which.min(dist[.x,]))
return(clust)
}
newCentroids <- function(x., clusters.){
centroids <- unique(clusters.) %>%
map_dfr(.f = ~ colMeans(x.[clusters. == .x, ]))
return(centroids)
}
Function :
kmeansBD
This function performs \(K\) -means
algorithm with BDs.
kmeansBD <- function(train_input,
K,
n_start = 5,
maxIter = 500,
deg = 3,
scale_input = FALSE,
div = "euclidean",
splits = 1,
epsilon = 1e-10,
center_ = NULL,
scale_ = NULL,
id_shuffle = NULL){
start_time <- Sys.time()
# Distortion function
X <- as.matrix(train_input)
N <- dim(X)
if(scale_input){
if(!(is.null(center_) & is.null(scale_))){
if(length(center_) == 1){
center_ <- rep(center_, N[2])
}
if(length(scale_) == 1){
scale_ <- rep(scale_, N[2])
}
} else{
min_ <- apply(X, 2, FUN = min)
c_ <- abs(colMeans(X)/5)
center_ <- min_ - c_
scale_ <- apply(X, 2, FUN = max) - center_ + 1
}
X <- scale(X, center = center_, scale = scale_)
}
if(is.null(id_shuffle)){
train_id <- rep(TRUE, N[1])
if(splits < 1){
train_id[sample(N[1], floor(N[1]*(1-splits)))] <- FALSE
}
} else{
train_id <- id_shuffle
}
X_train1 <- X[train_id,]
X_train2 <- X[!train_id,]
mu <- as.matrix(colMeans(X_train1))
distortion <- function(clus){
cent <- newCentroids(X_train1, clus)
var_within <- 1:K %>%
map(.f = ~ BregmanDiv(X_train1[clus == .x,],
cent[.x,],
div,
deg)) %>%
map(.f = sum) %>%
Reduce("+", .)
return(var_within)
}
# Kmeans algorithm
kmeansWithBD <- function(x., k., maxiter., eps.) {
n. <- nrow(x.)
# initialization
init <- sample(n., k.)
centroids_old <- x.[init,]
i <- 0
while(i < maxIter){
# Assignment step
clusters <- findClosestCentroid(x., centroids_old, div, deg)
# Recompute centroids
centroids_new <- newCentroids(x., clusters)
if ((sum(is.na(centroids_new)) > 0) |
(nrow(centroids_new) != k.)) {
init <- sample(n., k.)
centroids_old <- x.[init,]
warning("NA produced -> reinitialize centroids...!")
}
else{
if(sum(abs(centroids_new - centroids_old)) > eps.){
centroids_old <- centroids_new
} else{
break
}
}
i <- i + 1
}
return(clusters)
}
results <- 1:n_start %>%
map_dfc(.f = ~ tibble("{{.x}}" := kmeansWithBD(X_train1,
K,
maxIter,
epsilon)))
opt_id <- 1:n_start %>%
map_dbl(.f = ~ distortion(results[[.x]])) %>%
which.min
cluster <- clusters <- results[[opt_id]]
j <- 1
ID <- unique(cluster)
for (i in ID) {
clusters[cluster == i] = j
j = j + 1
}
centroids = newCentroids(X_train1, clusters)
time_taken <- Sys.time() - start_time
return(
list(
centroids = centroids,
clusters = clusters,
train_data = list(X_train = X_train1,
X_remain = X_train2,
id_remain = !train_id),
parameters = list(div = div,
deg = deg,
center_ = center_,
scale_ = scale_),
running_time = time_taken
)
)
}
Example.1 : We perform \(K\) -means algorithm with "gkl"
BD on Abalone
dataset.
pacman::p_load(readr)
colname <- c("Type", "LongestShell", "Diameter", "Height", "WholeWeight", "ShuckedWeight", "VisceraWeight", "ShellWeight", "Rings")
df <- readr::read_delim("https://archive.ics.uci.edu/ml/machine-learning-databases/abalone/abalone.data", col_names = colname, delim = ",", show_col_types = FALSE)
n <- nrow(df)
train <- logical(n)
train[sample(n, floor(n*0.8))] <- TRUE
cl <- df[train,2:(ncol(df)-1)] %>%
kmeansBD(K = 3, div = "gkl", splits = 0.5, scale_input = TRUE)
table(cl$clusters)
1 2 3
655 466 550
Step \(F\) :
Fitting predictive models
This section builds global models by fitting local model on each
given cluster of the obtained partition. This corresponds to the step
\(F\) of the procedure.
Function :
fitLocalModels
This function fits local models \(({\cal
M_{j,k}})_{j,k}\) on all clusters \(k=1,...,K\) of the obtained partition,
given by Bregman divergence \({\cal
B}_j\) , for \(j=1,...,M\) .
fitLocalModels <- function(kmeans_BD,
train_response,
model = "lm",
formula = NULL){
start_time <- Sys.time()
X_train <- kmeans_BD$train_data$X_train
y_train <- train_response[!(kmeans_BD$train_data$id_remain)]
X_remain <- kmeans_BD$train_data$X_remain
y_remain <- NULL
if(!is.null(X_remain)){
y_remain <- train_response[kmeans_BD$train_data$id_remain]
}
pacman::p_load(tree)
pacman::p_load(randomForest)
model_ <- ifelse(model == "tree", tree::tree, model)
K <- nrow(kmeans_BD$centroids)
if (is.null(formula)){
form <- formula(target ~ .)
}
else{
form <- update(formula, target ~ .)
}
data_ <- bind_cols(X_train, "target":= y_train)
fit_lookup <- list(lm = "fitted.values",
rf = "predicted")
if(is.character(model_)){
model_lookup <- list(lm = lm,
rf = randomForest::randomForest)
mod <- map(.x = 1:K,
.f = ~ model_lookup[[model_]](formula = form,
data = data_[kmeans_BD$clusters == .x, ]))
} else{
mod <- map(.x = 1:K,
.f = ~ model_(formula = form,
data = data_[kmeans_BD$clusters == .x,]))
}
pred0 <- NULL
if(!is.null(X_remain)){
pred0 <- vector(mode = "numeric",
length = length(y_remain))
clus <- findClosestCentroid(x. = X_remain,
centroids. = kmeans_BD$centroids,
div = kmeans_BD$parameters$div,
deg = kmeans_BD$parameters$deg)
for(i_ in 1:K){
pred0[clus == i_] <- predict(mod[[i_]],
as.data.frame(X_remain[clus == i_,]))
}
}
time_taken <- Sys.time() - start_time
return(list(
local_models = mod,
kmeans_BD = kmeans_BD,
data_remain = list(fit = pred0,
response = y_remain),
running_time = time_taken
))
}
Example.2 : From Example.1 above,
multiple linear regression models are built on all the obtained
clusters. The mean square error of this model, evaluated on the
remaining \(50\%\) of the training data
is computed.
fit <- fitLocalModels(train_response = df$Rings[train],
kmeans_BD = cl,
model = "lm")
mean((fit$data_remain$response- fit$data_remain$fit)^2)
[1] 4.680465
Function :
localPredict
This function allows us to predict any new observations using the
candidate model \({\cal M}_j=({\cal
M}_{j,k})_{k=1}^M\) corresponding to Bregman divergence \({\cal B}_j\) , for some \(j\in J\subset\{1,...,M\}\) .
localPredict <- function(localModels,
newData){
kmean_BD <- localModels$kmeans_BD
K <- nrow(kmean_BD$centroids)
newData_ <- newData
if(!(is.null(kmean_BD$parameters$center_))){
newData_ <- scale(newData,
center = kmean_BD$parameters$center_,
scale = kmean_BD$parameters$scale_)
id0 <- (newData_ <= 0)
if(sum(id0) > 0){
min_ <- min(newData_[id0])
newData_[id0] <- runif(sum(id0), min(1e-3, min_/10), min_)
}
}
clus <- findClosestCentroid(x. = newData_,
centroids. = kmean_BD$centroids,
div = kmean_BD$parameters$div,
deg = kmean_BD$parameters$deg)
pred0 <- vector(mode = "numeric", length = nrow(newData_))
for(i_ in 1:K){
pred0[clus == i_] <- predict(localModels$local_models[[i_]],
as.data.frame(newData_[clus == i_,]))
}
pred0 <- as_tibble(pred0)
names(pred0) <- ifelse(kmean_BD$parameters$div == "polynomial",
paste0("polynomial", kmean_BD$parameters$deg),
kmean_BD$parameters$div)
return(pred0)
}
Example.3 : The performance of the candidate model
corresponding to "gkl"
divergence is compared to random
forest regression on a \(20\%\) testing
data.
y_hat <- localPredict(fit,
df[!train, 2:(ncol(df)-1),])
rf <- randomForest(Rings ~ ., data = df[train,2:ncol(df)])
mean((predict(rf, newdata = df[!train,2:ncol(df)])- df$Rings[!train])^2)
[1] 4.345826
mean((y_hat$gkl-df$Rings[!train])^2)
[1] 4.524662
Step \(C\) :
Combining methods
The source codes and information of the aggregation methods are
available here
.
The codes below imports the aggregation methods into Rstudio
environment.
pacman::p_load(devtools)
### Kernel based consensual aggregation
source_url("https://raw.githubusercontent.com/hassothea/AggregationMethods/main/KernelAggReg.R")
ℹ SHA-1 hash of file is 813bced0dd2d9aa2431d07b64de08db7a9887bb1
### MixCobra
source_url("https://raw.githubusercontent.com/hassothea/AggregationMethods/main/MixCobraReg.R")
ℹ SHA-1 hash of file is c874cc5e16f484866d07476d002ec77f244989ee
Function : stepK
,
stepF
and stepC
These functions allow to set values of the parameters in the three
steps of the KFC procedure. Each function returns a list of all the
parameters given in its arguments.
stepK = function(K,
n_start = 5,
maxIter = 300,
deg = 3,
scale_input = FALSE,
div = NULL,
splits = 0.75,
epsilon = 1e-10,
center_ = NULL,
scale_ = NULL){
return(list(K = K,
n_start = n_start,
maxIter = maxIter,
deg = deg,
scale_input = scale_input,
div = div,
splits = splits,
epsilon = epsilon,
center_ = center_ ,
scale_ = scale_))
}
stepF = function(formula = NULL,
model = "lm"){
return(list(formula = formula,
model = model))
}
stepC = function(n_cv = 5,
method = c("cobra", "mixcobra"),
opt_methods = c("grad", "grid"),
kernels = "gaussian",
scale_features = FALSE){
return(list(n_cv = n_cv,
method = method,
opt_methods = opt_methods,
kernels = kernels,
scale_features = scale_features))
}
Function : KFCRegressor
This function is the complete implementation of KFC procedure.
🧾 Remark.3 : The parallel
argument
above requires internet connection to load the source codes of \(K\) -means algorithm with BDs from GitHub
.
It is performed on the maximum number of clusters of your machine, and
the speed is at least two times faster than without parallelism,
however, it is not so stable depending on your internet connection or
machine.
For the aggregation methods in step \(C\) :
KFCRegressor = function(train_input,
train_response,
test_input,
test_response = NULL,
n_cv = 5,
parallel = TRUE,
inv_sigma = sqrt(.5),
alp = 2,
K_step = stepK(splits = .5),
F_step = stepF(),
C_step = stepC(),
setGradParamAgg = setGradParameter(),
setGridParamAgg = setGridParameter(),
setGradParamMix = setGradParameter_Mix(),
setGridParamMix = setGridParameter_Mix(),
silent = FALSE){
start_time <- Sys.time()
lookup_div_names <- c("euclidean",
"gkl",
"logistic",
"itakura",
"polynomial")
div_ <- K_step$div
### K step: Kmeans clustering with BDs
if (is.null(K_step$div)){
divergences <- lookup_div_names
warning("No divergence provided! All of them are used!")
}
else{
divergences <- K_step$div %>%
map_chr(.f = ~ match.arg(arg = .x,
choices = lookup_div_names))
}
div_list <- divergences %>%
map(.f = (\(x) if(x != "polynomial") return(x) else return(rep("polynomial", length(K_step$deg))))) %>%
unlist
deg_list <- rep(NA, length(div_))
deg_list[div_list == "polynomial"] <- K_step$deg
div_names <- map2_chr(.x = div_list,
.y = deg_list,
.f = (\(x, y) if(is.na(y)) return(x) else return(paste0(x,y))))
### Step K: Kmeans clustering with Bregman divergences
dm <- dim(train_input)
id_shuffle <- vector(length = dm[1])
n_train <- floor(K_step$splits * dm[1])
id_shuffle[sample(dm[1], n_train)] <- TRUE
if(parallel){
numCores <- parallel::detectCores()
doParallel::registerDoParallel(numCores) # use multicore, set to the number of our cores
kmean_ <- foreach(i=1:length(div_names)) %dopar% {
devtools::source_url("https://raw.githubusercontent.com/hassothea/KFC-Procedure/master/kmeanBD.R")
kmeansBD(train_input = train_input,
K = K_step$K,
div = div_list[i],
n_start = K_step$n_start,
maxIter = K_step$maxIter,
deg = deg_list[i],
scale_input = K_step$scale_input,
splits = K_step$splits,
epsilon = K_step$epsilon,
center_ = K_step$center_,
scale_ = K_step$scale_,
id_shuffle = id_shuffle)
}
doParallel::stopImplicitCluster()
} else{
kmean_ <- map2(.x = div_list,
.y = deg_list,
.f = ~ kmeansBD(train_input = train_input,
K = K_step$K,
div = .x,
n_start = K_step$n_start,
maxIter = K_step$maxIter,
deg = .y,
scale_input = K_step$scale_input,
splits = K_step$splits,
epsilon = K_step$epsilon,
center_ = K_step$center_,
scale_ = K_step$scale_,
id_shuffle = id_shuffle))
}
names(kmean_) <- div_names
### F step: Fitting the corresponding model on each observed cluster
model_ <- div_names %>%
map(.f = ~ fitLocalModels(kmean_[[.x]],
train_response = train_response,
model = F_step$model,
formula = F_step$formula))
names(model_) <- div_names
pred_combine <- model_ %>%
map_dfc(.f = ~ .x$data_remain$fit)
y_remain <- train_response[!id_shuffle]
pred_test <- div_names %>%
map_dfc(.f = ~ localPredict(model_[[.x]],
test_input))
names(pred_test) <- names(pred_combine) <- div_names
# C step: Consensual regression aggregation method with kernel-based COBRA
list_method_agg <- list(mixcobra = function(pred){MixCobraReg(train_input = train_input[!id_shuffle,],
train_response = y_remain,
test_input = test_input,
train_predictions = pred,
test_predictions = pred_test,
test_response = test_response,
scale_input = K_step$scale_input,
scale_machine = C_step$scale_features,
n_cv = C_step$n_cv,
inv_sigma = inv_sigma,
alp = alp,
kernels = C_step$kernels,
optimizeMethod = C_step$opt_methods,
setGradParam = setGradParamMix,
setGridParam = setGridParamMix,
silent = silent)},
cobra = function(pred){kernelAggReg(train_design = pred,
train_response = y_remain,
test_design = pred_test,
test_response = test_response,
scale_input = K_step$scale_input,
scale_machine = C_step$scale_features,
build_machine = FALSE,
machines = NULL,
n_cv = C_step$n_cv,
inv_sigma = sqrt(.5),
alp = 2,
kernels = C_step$kernels,
optimizeMethod = C_step$opt_methods,
setGradParam = setGradParamAgg,
setGridParam = setGridParamAgg,
silent = silent)})
res <- map(.x = C_step$method,
.f = ~ list_method_agg[[.x]](pred_combine))
list_agg_methods <- list(cobra = "cob",
mixcobra = "mix")
names(res) <- C_step$method
ext_fun <- function(L, nam){
tab <- L$fitted_aggregate
names(tab) <- paste0(names(tab), "_", nam)
return(tab)
}
pred_fin <- C_step$method %>%
map_dfc(.f = ~ ext_fun(res[[.x]], list_agg_methods[[.x]]))
time.taken <- Sys.time() - start_time
### To finish
if(is.null(test_response)){
return(list(
predict_final = pred_fin,
predict_local = pred_test,
agg_method = res,
running_time = time.taken
))
} else{
error <- cbind(pred_test, pred_fin) %>%
dplyr::mutate(y_test = test_response) %>%
dplyr::summarise_all(.funs = ~ (. - y_test)) %>%
dplyr::select(-y_test) %>%
dplyr::summarise_all(.funs = ~ mean(.^2))
return(list(
predict_final = pred_fin,
predict_local = pred_test,
agg_method = res,
mse = error,
running_time = time.taken
))
}
}
Example.4 : A complete KFC procedure is implemented
on the same Abalone data, using \(5\)
BDs "euclidean"
, "itakura"
, "gkl"
and "polynomial"
(of degree \(3\) and \(6\) ). Both aggregation methods are used in
the step \(C\) . Two kernel functions
are used for each aggregation method: "gaussian"
(with
gradient descent algorithm) and "epanechnikov"
(with grid
search algorithm).
train1 <- logical(n)
train1[sample(n, floor(n*0.8))] <- TRUE
kfc1 <- KFCRegressor(train_input = df[train1,2:ncol(df)],
train_response = df$Rings[train1],
test_input = df[!train1,2:ncol(df)],
K_step = stepK(K = 3,
scale_input = TRUE,
div = c("eucl", "ita", "gkl", "poly"),
deg = c(3, 6),
splits = .5),
C_step = stepC(method = c("cobra", "mixcobra"),
opt_methods = c("grad", "grid"),
kernels = c("gaussian", "gaussian"),
scale_features = FALSE),
setGradParamAgg = setGradParameter(rate = 0.2),
#coef_lm = 2),
setGridParamAgg = setGridParameter(min_val = .00001,
max_val = 10,
n_val = 100),
setGradParamMix = setGradParameter_Mix(rate = "linear",
coef_auto = c(.5,.5)),
setGridParamMix = setGridParameter_Mix(min_alpha = 1e-10,
max_alpha = 0.5,
min_beta = 1e-10,
max_beta = 1,
n_alpha = 20,
n_beta = 20))
* Gradient descent algorithm ...
Step | Parameter | Gradient | Threshold
---------------------------------------------------
0 | 7.777800 | -4.40022e-10 | 1e-10
---------------------------------------------------
1 | 7.977800 | 2.5668e-10 | 0.02571421
2 | 7.777800 | -4.40022e-10 | 0.02506957
3 | 7.977800 | 2.5668e-10 | 0.02571421
4 | 7.777800 | -4.40022e-10 | 0.02506957
5 | 7.977800 | 2.5668e-10 | 0.02571421
6 | 7.777800 | -4.40022e-10 | 0.02506957
7 | 7.977800 | 2.5668e-10 | 0.02571421
8 | 7.911133 | 0e+00 | 0.008356523
-------------------------------------------------------
Stopped| 7.911133 | 0 | 0
~ Observed parameter: 7.911133 in 8 iterations.
* Grid search algorithm...
~ Observed parameter : 8.08081
MixCobra for regression
-----------------------
* Gradient descent algorithm ...
Step | alpha ; beta | Gradient (alpha ; beta) | Threshold
--------------------------------------------------------------------------------
0 | 5.00005 ; 25.05000 | -4.68285e-04 ; 7.3337e-11 | 1e-10
--------------------------------------------------------------------------------
0 | 5.0000 ; 25.0500 | -4.6828e-04 ; 7.3337e-11 | 0.004214561
1 | 5.5000 ; 24.5500 | -3.6509e-04 ; -1.1001e-10 | 0.03327781
2 | 6.2797 ; 26.0500 | -2.2769e-04 ; 2.2001e-10 | 0.07586129
3 | 7.0090 ; 21.5500 | -1.2144e-04 ; -3.6669e-11 | 0.1617499
4 | 7.5277 ; 22.5500 | -5.7064e-05 ; 3.6669e-10 | 0.05317693
5 | 7.8323 ; 10.0500 | -2.3067e-05 ; -1.1001e-10 | 0.425719
6 | 7.9801 ; 14.5500 | -7.5142e-06 ; 1.4667e-10 | 0.2599088
7 | 8.0363 ; 11.0500 | -1.7573e-06 ; -1.1001e-10 | 0.1578404
8 | 8.0513 ; 12.5500 | -2.3244e-07 ; 2.5668e-10 | 0.07937698
9 | 8.0535 ; 10.5813 | -6.3437e-09 ; -4.4002e-10 | 0.09567286
10 | 8.0536 ; 12.4563 | -7.3337e-11 ; 0e+00 | 0.100622
11 | 8.0536 ; 12.4563 | 8.4338e-10 ; 0e+00 | 4.199661e-08
12 | 8.0536 ; 12.4563 | -3.6669e-10 ; -1.4667e-10 | 5.268665e-07
13 | 8.0536 ; 12.6594 | -3.3002e-10 ; 1.1001e-10 | 0.009903915
14 | 8.0536 ; 12.5773 | -5.1336e-10 ; 3.6669e-11 | 0.003960444
15 | 8.0536 ; 12.5627 | -2.5668e-10 ; 0e+00 | 0.0007101231
16 | 8.0536 ; 12.5627 | 7.3337e-11 ; 3.3002e-10 | 5.317427e-08
17 | 8.0536 ; 12.4880 | -3.6669e-11 ; -1.1001e-10 | 0.003623708
18 | 8.0536 ; 12.5012 | 7.3337e-11 ; 3.6669e-10 | 0.000641805
19 | 8.0536 ; 12.4780 | 7.3337e-11 ; -7.3337e-11 | 0.001128374
20 | 8.0536 ; 12.4804 | 1.8334e-10 ; -1.1001e-10 | 0.0001189123
21 | 8.0536 ; 12.4823 | -3.6669e-11 ; 3.3002e-10 | 9.363669e-05
22 | 8.0536 ; 12.4763 | 3.6669e-11 ; 3.6669e-11 | 0.0002942408
23 | 8.0536 ; 12.4759 | 1.4667e-10 ; 2.2001e-10 | 1.709539e-05
24 | 8.0536 ; 12.4738 | 0e+00 ; 0e+00 | 0.0001070309
25 | 8.0536 ; 12.4738 | 0e+00 ; 0e+00 | 3.666853e-10
--------------------------------------------------------------------------------
Stopped| 8.0536 ; 12.4738 | 0 | 0
~ Observed parameter: (alpha, beta) = ( 8.05358 , 12.47375 ) in 26 itertaions.
* Grid search algorithm...
~ Observed parameter: (alpha, beta) = (0.5, 1)
The mean square errors evaluated on \(20\%\) -testing data of the above
computation are reported below.
rf1 <- randomForest::randomForest(Rings ~ ., data = df[train1,2:ncol(df)])
kfc1$predict_final %>%
mutate(rf = predict(rf1, newdata = df[!train1,2:ncol(df)])) %>%
sweep(MARGIN = 1, STATS = df$Rings[!train1], FUN = "-") %>%
.^2 %>%
colMeans
gaussian_grad_cob gaussian_grid_cob gaussian_grad_mix gaussian_grid_mix rf
0.005980861 0.005980861 4.701859057 9.531297947 5.096848245
We can see that KFC procedure performs really well on this real-life
dataset.
📖 Read also KernelAggReg
and MixCobraReg methods.
LS0tDQp0aXRsZTogIjxzcGFuIHN0eWxlPSdjb2xvcjogIzFDODFBQTsnPioqS0ZDIHByb2NlZHVyZSBmb3IgcmVncmVzc2lvbiAtPC9zcGFuPiBbSGFzIGV0IGFsLiAoMjAyMSldKGh0dHBzOi8vd3d3LnRhbmRmb25saW5lLmNvbS9lcHJpbnQvWUtHUzhHVEtEQktZRlhFR0ZXU0IvZnVsbD90YXJnZXQ9MTAuMTA4MC8wMDk0OTY1NS4yMDIxLjE4OTE1MzkpKioiDQphdXRob3I6ICI8c3BhbiBzdHlsZT0nY29sb3I6ICNENEE1MUM7Jz4qKipTb3RoZWEgSGFzKioqPC9zcGFuPiINCmRhdGU6ICI1LzIwLzIwMjIiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY3NzOiBoaWRlT3V0cHV0LmNzcw0KICAgIGluY2x1ZGVzOg0KICAgICAgaW5faGVhZGVyOiBoaWRlT3V0cHV0LnNjcmlwdA0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICcyJw0KICAgIHRvY2RlcHRoOiAyDQogIGh0bWxfbm90ZWJvb2s6DQogICAgY3NzOiBoaWRlT3V0cHV0LmNzcw0KICAgIGluY2x1ZGVzOg0KICAgICAgaW5faGVhZGVyOiBoaWRlT3V0cHV0LnNjcmlwdA0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDINCiAgICB0b2NkZXB0aDogMg0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6ICcyJw0KLS0tDQoNCjxzdHlsZT4NCiAgLmJ0biB7DQogICAgYm9yZGVyLXdpZHRoOiAwIDBweCAwcHggMHB4Ow0KICAgIGZvbnQtd2VpZ2h0OiBub3JtYWw7DQogICAgdGV4dC10cmFuc2Zvcm06IDsNCiAgfQ0KLmJ0bi1kZWZhdWx0IHsNCiAgY29sb3I6ICMyZWNjNzE7DQogICAgYmFja2dyb3VuZC1jb2xvcjogI2ZmZmZmZjsNCiAgICBib3JkZXItY29sb3I6ICNmZmZmZmY7DQp9DQo8L3N0eWxlPg0KDQo8IS0tIENvbG9ycw0KYmx1ZSA6ICMxRkFBRTMNCnllbGxvdyA6ICNGMEFFMTQNCmdyZWVuIDogIzU0RDMxOSANCnJlZCA6ICNFNjE4MEENCi0tPg0KDQoNCmBgYHtyLCBlY2hvPUZBTFNFfQ0KIyBDaGVjayBpZiBwYWNrYWdlICJmb250YXdlc29tZSIgaXMgYWxyZWFkeSBpbnN0YWxsZWQgDQoNCmxvb2t1cF9wYWNrYWdlcyA8LSBpbnN0YWxsZWQucGFja2FnZXMoKVssMV0NCmlmKCEoImZvbnRhd2Vzb21lIiAlaW4lIGxvb2t1cF9wYWNrYWdlcykpDQogIGluc3RhbGwucGFja2FnZXMoImZvbnRhd2Vzb21lIikNCmBgYA0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPiYjMTI4MjcwOzx1PiBIb3cgdG8gZG93bmxvYWQgJiBydW4gdGhlIGNvZGVzPzwvdT48L3NwYW4+ey19DQo9PT0NCg0KQWxsIHRoZSBzb3VyY2UgY29kZXMgb2YgdGhlIGFnZ3JlZ2F0aW9uIG1ldGhvZHMgYXJlIGF2YWlsYWJsZSBbaGVyZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPiBgciBmb250YXdlc29tZTo6ZmEoImdpdGh1YiIpYDwvc3Bhbj5dKGh0dHBzOi8vZ2l0aHViLmNvbS9oYXNzb3RoZWEvQWdncmVnYXRpb25NZXRob2RzKS4gVG8gcnVuIHRoZSBjb2RlcywgeW91IGNhbiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPmBjbG9uZWA8L3NwYW4+IHRoZSByZXBvc2l0b3J5IGRpcmVjdGx5IG9yIHNpbXBseSBsb2FkIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPmBSIHNjcmlwdGA8L3NwYW4+IHNvdXJjZSBmaWxlIGZyb20gdGhlIHJlcG9zaXRvcnkgdXNpbmcgW2RldnRvb2xzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGV2dG9vbHMvaW5kZXguaHRtbCkgcGFja2FnZSBpbiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwMjg3RDg7Ij4gKipSc3R1ZGlvKiogPC9zcGFuPiBhcyBmb2xsb3c6DQoNCjEuIEluc3RhbGwgW2RldnRvb2xzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGV2dG9vbHMvaW5kZXguaHRtbCkgcGFja2FnZSB1c2luZyBjb21tYW5kOiANCg0KICAgIGBpbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIpYA0KDQoyLiBMb2FkaW5nIHRoZSBzb3VyY2UgY29kZXMgZnJvbSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPkdpdEh1YiBgciBmb250YXdlc29tZTo6ZmEoImdpdGh1YiIpYDwvc3Bhbj4gcmVwb3NpdG9yeSB1c2luZyBgc291cmNlX3VybGAgZnVuY3Rpb24gYnk6IA0KDQogICAgYGRldnRvb2xzOjpzb3VyY2VfdXJsKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vaGFzc290aGVhL0tGQy1Qcm9jZWR1cmUvbWFzdGVyL0tGQ1JlZy5SIilgDQoNCi0tLQ0KDQo+ICoqJiM5OTk4OyBOb3RlKio6IEFsbCBjb2RlcyBjb250YWluZWQgaW4gdGhpcyBgUm1hcmtkb3duYCBhcmUgYnVpbHQgd2l0aCByZWNlbnQgdmVyc2lvbiBvZiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPmByIGZvbnRhd2Vzb21lOjpmYSgici1wcm9qZWN0IilgPC9zcGFuPiAodmVyc2lvbiAkPiQgNC4xLCBhdmFpbGFibGUgW2hlcmVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL2Jpbi93aW5kb3dzL2Jhc2UvKSkgYW5kIDxzcGFuIHN0eWxlPSJjb2xvcjogIzAyODdEODsiPiAqKlJzdHVkaW8qKiA8L3NwYW4+ICh2ZXJzaW9uID4gYDIwMjIuMDIuMis0ODVgLCBhdmFpbGFibGUgW2hlcmVdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Byb2R1Y3RzL3JzdHVkaW8vZG93bmxvYWQvI2Rvd25sb2FkKSkuIE5vdGUgYWxzbyB0aGF0IHRoZSBjb2RlIGNodWNrcyBhcmUgPHNwYW4gc3R5bGU9ImNvbG9yOiAjRTYxODBBOyI+aGlkZGVuPC9zcGFuPiBieSBkZWZhdWx0Lg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQiPiAqKlRvIHNlZSB0aGUgY29kZXMsIHlvdSBjYW46KiogPC9zcGFuPg0KDQotIGNsaWNrIG9uIHRoZSB0b3AtcmlnaHQgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNTREMzE5IDsiPkNvZGU8L3NwYW4+IGJ1dHRvbiBvZiB0aGUgcGFnZSwgdGhlbiBjaG9vc2UgKipTaG93IEFsbCBDb2RlKiogdG8gc2hvdyBhbGwgdGhlIGNvZGVzLCBvciANCi0gc2ltcGx5IGNsaWNrIG9uIHRoZSByaWdodC1jb3JuZXIgPHNwYW4gc3R5bGU9ImNvbG9yOiAjNTREMzE5IDsiPkNvZGU8L3NwYW4+IGJ1dHRvbiBhdCBlYWNoIHNlY3Rpb24gdG8gc2hvdyB0aGUgY29kZXMgb2YgdGhhdCBzcGVjaWZpYyBzZWN0aW9uLg0KDQotLS0NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+S0ZDIHByb2NlZHVyZSAmIGltcG9ydGFudCBwYWNrYWdlcyA8L3U+PC9zcGFuPg0KPT09DQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PktGQyBwcm9jZWR1cmU8L3U+PC9zcGFuPg0KLS0tDQoNCktGQyBwcm9jZWR1cmUgaXMgYSB0aHJlZS1zdGVwIG1ldGhvZG9sb2d5IHdoaWNoIHB1dHMgdG9nZXRoZXIgY2x1c3RlcmluZyBhbmQgY29uc2Vuc3VhbCBhZ2dyZWdhdGlvbiBtZXRob2RzIGZvciBidWlsZGluZyBwcmVkaWN0aW9ucyBpbiBzdXBlcnZpc2VkIGxlYXJuaW5nIHByb2JsZW1zLiBUaGUgcHJvY2VkdXJlIGlzIGluc3BpcmVkIGJ5IG1hbnkgcmVhbC1saWZlIHByZWRpY3Rpb24gcHJvYmxlbXMgd2hlbiB0aGUgaW4gVGhlIHRocmVlIHN0ZXBzIG9mIHRoZSBwcm9jZWR1cmUgYXJlOg0KDQotICoqU3RlcCAqSyogKio6ICRLJC1tZWFucyBjbHVzdGVyaW5nIGFsZ29yaXRobSBpcyBpbXBsZW1lbnRlZCBvbiB0aGUgaW5wdXQgZGF0YSB1c2luZyBzZXZlcmFsIG9wdGlvbnMgb2YgQnJlZ21hbiBkaXZlcmdlbmNlcyAkKHtcY2FsIEJ9X2opX3tqPTF9Xk0kICgkTSQgaXMgdGhlIG51bWJlciBvZiB0b3RhbCBkaXZlcmdlbmNlcyB1c2VkKSwgdGhlcmVmb3JlLCB0aGUgaW5wdXQgZGF0YSBpcyBwYXJ0aXRpb25lZCBpbnRvIG1hbnkgZGlmZmVyZW50IHN0cnVjdHVyZXMsIGFjY29yZGluZyB0byB0aGUgcHJvcGVydHkgb2YgZWFjaCBCcmVnbWFuIGRpdmVyZ2VuY2UuDQotICoqU3RlcCAqRiogKio6IEZvciBhIHBhcnRpdGlvbiBzdHJ1Y3R1cmUgZ2l2ZW4gYnkgYSBkaXZlcmdlbmNlICR7XGNhbCBCfV9qJCwgd2UgZml0IHNpbXBsZSBtb2RlbHMgKGxpbmVhciwgZm9yIGV4YW1wbGUpIG9uIGFsbCB0aGUgY2x1c3RlcnMgb2YgdGhlIG9idGFpbmVkIHBhcnRpdGlvbi4gVGhlbiwgdGhlIGNvbGxlY3Rpb24gJHtcY2FsIE19X2o9XHt7XGNhbCBNfV97aixrfVx9X3trPTF9XkskIG9mIHRoZXNlIGxvY2FsIG1vZGVscyBpcyBjYWxsZWQgKmNhbmRpZGF0ZSogbW9kZWwsIGNvcnJlc3BvbmRpbmcgdG8gdGhlIEJyZWdtYW4gZGl2ZXJnZW5jZSAke1xjYWwgQn1faiQuIEF0IHRoZSBlbmQgb2YgdGhpcyBzdGVwLCBzZXZlcmFsIGNhbmRpZGF0ZSBtb2RlbHMgYXJlIGNvbnN0cnVjdGVkLiANCi0gKipTdGVwICpDKiAqKjogVGhpcyBzdGVwIGFnZ3JlZ2F0ZXMgdGhlIG9idGFpbmVkIGNhbmRpZGF0ZSBtb2RlbHMgdXNpbmcgY29uc2Vuc3VhbCBhZ2dyZWdhdGlvbiBtZXRob2RzIHN0dWRpZWQgaW4gW0hhcyAoMjAyMSldKGh0dHBzOi8vaGFsLmFyY2hpdmVzLW91dmVydGVzLmZyL2hhbC0wMjg4NDMzM3Y1KSBvciBbRmlzY2hlciBhbmQgTW91Z2VvdCAoMjAxOSldKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAzNzgzNzU4MTgzMDIzNDkpLg0KDQotLS0NCg0KIVtUaGUgZmlndXJlIGFib3ZlIHJlcHJlc2VudHMgdGhlIHN1bW1hcnkgb2YgS0ZDIHByb2NlZHVyZV0oLi9rZmMucG5nKQ0KDQotLS0NCg0KPiDwn6e+ICoqUmVtYXJrLjEqKjogDQpUaGUgcHJlZGljdGlvbiBvZiBhbnkgb2JzZXJ2YXRpb24gJHgkIGdpdmVuIGJ5IGEgY2FuZGlkYXRlIG1vZGVsICR7XGNhbCBNfV9qJCBpcyBkb25lIGluIHR3byBzaW1wbGUgc3RlcHM6DQoNCjEuICR4JCBpcyBjbGFzc2lmaWVkIGludG8gb25lIG9mIHRoZSBvYnRhaW5lZCBjbHVzdGVycyB1c2luZyB0aGUgY29ycmVzcG9uZGluZyBkaXZlcmdlbmNlICR7XGNhbCBCfV9qJCwgaS5lLiwNCiAgJCR4XGlue1xjYWwgQ31fe2teKn0gXExlZnRyaWdodGFycm93IHtcY2FsIEJ9X2ooY197a14qfSx4KT1caW5mX3sxXGxlcSBrXGxlcSBLfXtcY2FsIEJ9X2ooY19rLHgpJCQNCndoZXJlICRce2NfMSwuLi4sY19LXH1fe2s9MX1eSyQgYXJlIHRoZSBjZW50cm9pZHMgb2YgdGhlIGNvcnJlc3BvbmRpbmcgY2x1c3RlcnMgJFx7Q18xLC4uLixDX0tcfSQuDQoNCjIuIFRoZSBwcmVkaWN0aW9uIG9mICR4JCBpcyBnaXZlbiBieSB0aGUgY29ycmVzcG9uZGluZyBsb2NhbCBtb2RlbCAke1xjYWwgTX1fe2osa14qfSQgZGVmaW5lZCBvbiBjbHVzdGVyICRrXiokLCBpLmUuLCAke1xjYWwgTX1faih4KT17XGNhbCBNfV97aixrXip9KHgpJC4NCg0KLS0tDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkltcG9ydGFudCBwYWNrYWdlczwvdT48L3NwYW4+DQotLS0NCg0KV2UgcHJlcGFyZSBhbGwgdGhlIG5lY2Vzc2FyeSB0b29scyBmb3IgdGhpcyBgUm1hcmtkb3duYC4gVGhlIGBwYWNtYW5gIHBhY2thZ2UgYWxsb3dzIHVzIHRvIGxvYWQgKGlmIGV4aXN0cykgb3IgaW5zdGFsbCAoaWYgZG9lcyBub3QgZXhpc3QpIGFueSBhdmFpbGFibGUgcGFja2FnZXMgZnJvbSBbVGhlIENvbXByZWhlbnNpdmUgUiBBcmNoaXZlIE5ldHdvcmsgKENSQU4pXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy8pIG9mIDxzcGFuIHN0eWxlPSJjb2xvcjogIzA5N0JDMSI+YHIgZm9udGF3ZXNvbWU6OmZhKCJyLXByb2plY3QiKWA8L3NwYW4+LiANCg0KDQpgYGB7cn0NCiMgQ2hlY2sgaWYgcGFja2FnZSAicGFjbWFuIiBpcyBhbHJlYWR5IGluc3RhbGxlZCANCg0KbG9va3VwX3BhY2thZ2VzIDwtIGluc3RhbGxlZC5wYWNrYWdlcygpWywxXQ0KaWYoISgicGFjbWFuIiAlaW4lIGxvb2t1cF9wYWNrYWdlcykpDQogIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQoNCg0KIyBUbyBiZSBpbnN0YWxsZWQgb3IgbG9hZGVkDQpwYWNtYW46OnBfbG9hZChtYWdyaXR0cikNCnBhY21hbjo6cF9sb2FkKHRpZHl2ZXJzZSkNCg0KIyMgcGFja2FnZSBmb3IgImdlbmVyYXRlTWFjaGluZXMiDQpwYWNtYW46OnBfbG9hZCh0cmVlKQ0KcGFjbWFuOjpwX2xvYWQoZ2xtbmV0KQ0KcGFjbWFuOjpwX2xvYWQocmFuZG9tRm9yZXN0KQ0KcGFjbWFuOjpwX2xvYWQoRk5OKQ0KcGFjbWFuOjpwX2xvYWQoeGdib29zdCkNCnBhY21hbjo6cF9sb2FkKGtlcmFzKQ0KcGFjbWFuOjpwX2xvYWQocHJhY21hKQ0KcGFjbWFuOjpwX2xvYWQobGF0ZXgyZXhwKQ0KcGFjbWFuOjpwX2xvYWQocGxvdGx5KQ0KcGFjbWFuOjpwX2xvYWQocGFyYWxsZWwpDQpwYWNtYW46OnBfbG9hZChmb3JlYWNoKQ0KcGFjbWFuOjpwX2xvYWQoZG9QYXJhbGxlbCkNCnJtKGxvb2t1cF9wYWNrYWdlcykNCmBgYA0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPjx1PkJyZWdtYW4gZGl2ZXJnZW5jZXMgKEJEKTwvdT48L3NwYW4+DQo9PT0NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+KipEZWZpbml0aW9uKio8L3NwYW4+IExldCAkXHBoaTpcbWF0aGNhbHtDfVxyaWdodGFycm93XG1hdGhiYntSfSQgYmUgYSBzdHJpY3RseSBjb252ZXggYW5kIGNvbnRpbnVvdXNseSBkaWZmZXJlbnRpYWJsZSBmdW5jdGlvbiBkZWZpbmVkIG9uIGEgbWVhc3VyYWJsZSBjb252ZXggc3Vic2V0ICRcbWF0aGNhbHtDfVxzdWJzZXRcbWF0aGJie1J9XmQkLiBMZXQgJGludChcbWF0aGNhbHtDfSkkIGRlbm90ZSBpdHMgcmVsYXRpdmUgaW50ZXJpb3IuIEEgQnJlZ21hbiBkaXZlcmdlbmNlIGluZGV4ZWQgYnkgJFxwaGkkIGlzIGEgZGlzc2ltaWxhcml0eSBtZWFzdXJlICRkX3tccGhpfTpcbWF0aGNhbHtDfVx0aW1lcyBpbnQoXG1hdGhjYWx7Q30pXHJpZ2h0YXJyb3dcbWF0aGJie1J9JCBkZWZpbmVkIGZvciBhbnkgcGFpciAkKHgseSlcaW4gXG1hdGhjYWx7Q31cdGltZXMgaW50KFxtYXRoY2Fse0N9KSQgYnksDQpcYmVnaW57ZXF1YXRpb259DQpcbGFiZWx7ZXE6MS4xMH0NCmRfe1xwaGl9KHgseSk9XHBoaSh4KS1ccGhpKHkpLVxsYW5nbGUgeC15LFxuYWJsYVxwaGkoeSlccmFuZ2xlIA0KXGVuZHtlcXVhdGlvbn0NCndoZXJlICRcbmFibGFccGhpKHkpJCBkZW5vdGVzIHRoZSBncmFkaWVudCBvZiAkXHBoaSQgY29tcHV0ZWQgYXQgYSBwb2ludCAkeVxpbiBpbnQoXG1hdGhjYWx7Q30pJC4gQSBCcmVnbWFuIGRpdmVyZ2VuY2UgaXMgbm90IG5lY2Vzc2FyaWx5IGEgbWV0cmljIGFzIGl0IG1heSBub3QgYmUgc3ltbWV0cmljIGFuZCB0aGUgdHJpYW5ndWxhciBpbmVxdWFsaXR5IG1pZ2h0IG5vdCBiZSBzYXRpc2ZpZWQuDQoNClRoaXMgc2VjdGlvbiBkZWZpbmVzIGFsbCB0aGUgQnJlZ21hbiBkaXZlcmdlbmNlcyB1c2VkLiBUaGUgbGlzdCBvZiBhbGwgdGhlIEJyZWdtYW4gZGl2ZXJnZW5jZXMgaXMgZ2l2ZW4gaW4gdGhlIHRhYmxlIGJlbG93Og0KDQotLS0tDQoNCk5hbWUgICAgICAgICAgICAgICAgICAgICAgICAkXHBoaSQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJGRfe1xwaGl9JCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAkXGNhbCBDJA0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSAtLS0tLS0tLS0tLQ0KRXVjbGlkZWFuICAgICAgICAgICAgICAgICAke1x8eFx8XzJeMn09XHN1bV97aT0xfV5keF9pXjIkICAgICAgICAgICAgICAgICAgICRcfHgteVx8XzJeMiQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAkXG1hdGhiYntSfV5kJA0KR2VuZXJhbCBLdWxsYmFjay1MZWlibGVyICAkXHN1bV97aT0xfV5kIHhfaVxsbiggeF9pKSQgICAgICAgICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmQoIHhfaVxsbihcZnJhY3sgeF9pfXt5X2l9KS0oeF9pLXlfaSkpJCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAkKDAsK1xpbmZ0eSleZCQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpMb2dpc3RpYyAgICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmQoeF9pXGxuKCB4X2kpKygxLSB4X2kpXGxuKDEtIHhfaSkpJCAgJFxzdW1fe2k9MX1eZFxCaWcoIHhfaVxsbihcZnJhY3t4X2l9e3lfaX0pKygxLSB4X2kpXGxuKFxmcmFjezEtIHhfaX17MS15X2l9KVxCaWcpJCAgICQoMCwxKV5kJA0KSXRha3VyYS1TYWl0byAgICAgICAgICAgICAkLVxzdW1fe2k9MX1eZFxsbiggeF9pKSQgICAgICAgICAgICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmRcQmlnKFxmcmFjeyB4X2l9e3lfaX0tXGxuKFxmcmFjeyB4X2l9e3lfaX0pLTFcQmlnKSQgICAgICAgICAgICAgICAgICAgICQoMCwrXGluZnR5KV5kJA0KRXhwb25lbnRpYWwgICAgICAgICAgICAgICAkXHN1bV97aT0xfV5kZV57eF9pfSQgICAgICAgICAgICAgICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmQoZV57eF9pfS1lXnt5X2l9LWVee3lfaX0oeF9pLXlfaSkpJCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICRcbWF0aGJie1J9XmQkDQpQb2x5bm9taWFsICAgICAgICAgICAgICAgICRcc3VtX3tpPTF9XmR8eHxecCxwPjIkICAgICAgICAgICAgICAgICAgICAgICAgICAgJFxzdW1fe2s9MX1eZCh8eF9rfF5wLXx5X2t8XnAtXHRleHR7c2lnbn0oeV9rKV5wcCh4X2steV9rKXlfa157cC0xfSkkICAgICAgICAgICAgICRcbWF0aGJie1J9XmQkDQoNCi0tLS0NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+TG9vay11cCBsaXN0IG9mIEJyZWdtYW4gZGl2ZXJnZW5jZXM8L3U+PC9zcGFuPg0KLS0tLQ0KDQpUaGUgY29kZXMgYmVsb3cgcHJvdmlkZSBhIGxvb2stdXAgbGlzdCBvZiBhbGwgdGhlIEJEcyBkZWZpbmVkIGluIHRoZSB0YWJsZSBhYm92ZS4NCg0KYGBge3J9DQpldWNsaWREaXYgPC0gZnVuY3Rpb24oWC4sIHkuLCBkZWcgPSBOVUxMKXsNCiAgICByZXMgPC0gc3dlZXAoWC4sIDIsIHkuKQ0KICAgIHJldHVybihyb3dTdW1zKHJlc14yKSkNCn0NCmdrbERpdiA8LSBmdW5jdGlvbihYLiwgeS4sIGRlZyA9IE5VTEwpew0KICByZXMgPC0gYygiLyIsICItIikgJT4lDQogICAgbWFwKC5mID0gfiBzd2VlcChYLiwgMiwgeS4sIEZVTiA9IC54KSkNCiAgcmV0dXJuKHJvd1N1bXMoWC4qbG9nKHJlc1tbMV1dKSAtIHJlc1tbMl1dKSkNCn0NCmxvZ0RpdiA8LSBmdW5jdGlvbihYLiwgeS4sIGRlZyA9IE5VTEwpew0KICAgIHJlcyA8LSAgbWFwMigueCA9IGxpc3QoWC4sIDEtWC4pLA0KICAgICAgICAgICAgICAgICAueSA9IGxpc3QoeS4sIDEteS4pLA0KICAgICAgICAgICAgICAgICAuZiA9IH4gc3dlZXAoLngsIDIsIC55LCBGVU4gPSAiLyIpKQ0KICAgIHJldHVybihyb3dTdW1zKFguKmxvZyhyZXNbWzFdXSkrKDEtWC4pKmxvZyhyZXNbWzJdXSkpKQ0KfQ0KaXRhRGl2IDwtIGZ1bmN0aW9uKFguLCB5LiwgZGVnID0gTlVMTCl7DQogICAgcmVzIDwtIHN3ZWVwKFguLCAyLCB5LiwgRlVOID0gIi8iKQ0KICAgIHJldHVybihyb3dTdW1zKHJlcy1sb2cocmVzKSAtIDEpKQ0KfQ0KZXhwRGl2IDwtIGZ1bmN0aW9uKFguLCB5LiwgZGVnID0gTlVMTCl7DQogICAgZXhwX3kgPC0gZXhwKHkuKQ0KICAgIHJlcyA8LSBzd2VlcCgxK1guLCAyLCB5LikgJT4lDQogICAgICBzd2VlcCgyLCBleHBfeSwgRlVOID0gIioiKQ0KICAgIHJldHVybihyb3dTdW1zKGV4cChYLiktcmVzKSkNCn0NCnBvbHlEaXYgPC0gZnVuY3Rpb24oWC4sIHkuLCBkZWcgPSAzKXsNCiAgICBTIDwtIG1hcDIoLnggPSBsaXN0KFguLCBYLl5kZWcpLA0KICAgICAgICAgICAgICAueSA9IGxpc3QoeS4sIHkuXmRlZyksDQogICAgICAgICAgICAgIC5mID0gfiBzd2VlcCgueCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBNQVJHSU4gPSAyLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIFNUQVRTID0gLnksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBGVU4gPSAiLSIpKQ0KICAgIGlmKGRlZyAlJSAyID09IDApew0KICAgICAgVGVtIDwtIHN3ZWVwKFNbWzFdXSwgMiwgeS5eKGRlZy0xKSwgRlVOID0gIioiKQ0KICAgICAgcmVzIDwtIHJvd1N1bXMoU1tbMl1dIC0gZGVnICogVGVtKQ0KICAgIH0NCiAgICBlbHNlew0KICAgICAgVGVtIDwtIHN3ZWVwKFNbWzFdXSwgMiwgc2lnbih5LikgKiB5Ll4oZGVnLTEpLCBGVU4gPSAiKiIpDQogICAgICByZXMgPC0gcm93U3VtcyhTW1syXV0gLSBkZWcgKiBUZW0pDQogICAgfQ0KICAgIHJldHVybihyZXMpDQp9DQpsb29rdXBfZGl2IDwtIGxpc3QoDQogIGV1Y2xpZGVhbiA9IGV1Y2xpZERpdiwNCiAgZ2tsID0gZ2tsRGl2LA0KICBsb2dpc3RpYyA9IGxvZ0RpdiwNCiAgaXRha3VyYSA9IGl0YURpdiwNCiAgZXhwb25lbnRpYWwgPSBleHBEaXYsDQogIHBvbHlub21pYWwgPSBwb2x5RGl2DQopDQpgYGANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT5GdW5jdGlvbjwvdT48L3NwYW4+IDogYEJyZWdtYW5EaXZgDQotLS0tDQoNClRoaXMgZnVuY3Rpb24gY29tcHV0ZXMgQnJlZ21hbiBkaXZlcmdlbmNlIG1hdHJpeCBiZXR3ZWVuIHR3byBzZXRzIG9mIGRhdGEgcG9pbnRzLiBFYWNoIHNldCBvZiBkYXRhIHBvaW50cyBzaG91bGQgYmUgcmVwcmVzZW50ZWQgYnkgYSBtYXRyaXgsIGRhdGEgZnJhbWUsIG9yIHRpYmJsZSBvYmplY3Qgd2hlcmUgZWFjaCByb3cgY29ycmVzcG9uZHMgdG8gZWFjaCBpbmRpdmlkdWFsIGRhdGEgcG9pbnQuDQoNCi0gKipBcmd1bWVudCoqOg0KDQogICAgLSBgWC5gLCBgQy5gIDogZGF0YSBtYXRyaWNlcywgdGliYmxlcyBhbmQgZGF0YSBmcmFtZXMsIGNvbnRhaW5pbmcgdGhlIGRhdGEgcG9pbnRzIChieSByb3cpIGZvciB3aGljaCB0aGUgQnJlZ21hbiBkaXZlcmdlbmNlcyBiZXR3ZWVuIHRoZW0gYXJlIHRvIGJlIGNvbXB1dGVkLg0KICAgIC0gYGRpdmAgOiB0aGUgZGl2ZXJnZW5jZSB0eXBlIHRvIGJlIHVzZWQuIEl0IHNob3VsZCBiZSBhIHN1YnNldCBvZiBgeyJldWNsaWRlYW4iLCAiZ2tsIiwgImxvZ2lzdGljIiwgIml0YWt1cmEiLCAiZXhwb25lbnRpYWwiLCAicG9seW5vbWlhbCJ9YC4NCiAgICAtIGBkZWdgIDogdGhlIGRlZ3JlZSBvZiBwb2x5bm9taWFsIEJEIChpZiBvbmUgaXMgdXNlZCkuDQotICoqVmFsdWUqKjogDQogICAgDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIGEgKnRpYmJsZSogb2JqZWN0ICREPShkX3tpLGp9KSQgd2hlcmUgJGRfe2ksan0kIGlzIHRoZSBCcmVnbWFuIGRpdmVyZ2VuY2UgYmV0d2VlbiByb3cgJGkkIG9mIGBYLmAgYW5kIHJvdyAkaiQgb2YgYEMuYC4NCg0KDQpgYGB7cn0NCkJyZWdtYW5EaXYgPC0gZnVuY3Rpb24oWC4sIA0KICAgICAgICAgICAgICAgICAgICAgICBDLiwgDQogICAgICAgICAgICAgICAgICAgICAgIGRpdiA9IGMoImV1Y2xpZGVhbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJna2wiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibG9naXN0aWMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiaXRha3VyYSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJleHBvbmVudGlhbCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJwb2x5bm9taWFsIiksDQogICAgICAgICAgICAgICAgICAgICAgIGRlZyA9IDMpew0KICBkaXYgPC0gbWF0Y2guYXJnKGRpdikNCiAgZF9jIDwtIGRpbShDLikNCiAgaWYoaXMubnVsbChkX2MpKXsNCiAgICBDIDwtIG1hdHJpeChDLiwgbnJvdyA9IDEsIGJ5cm93ID0gVFJVRSkNCiAgfSBlbHNlew0KICAgIEMgPC0gYXMubWF0cml4KEMuKQ0KICB9DQogIGlmKGlzLm51bGwoZGltKFguKSkpew0KICAgIFggPC0gbWF0cml4KFguLCBucm93ID0gMSwgYnlyb3cgPSBUUlVFKQ0KICB9IGVsc2V7DQogICAgWCA8LSBhcy5tYXRyaXgoWC4pDQogIH0NCiAgZGlzIDwtICBtYXBfZGZjKC54ID0gMTpkaW0oQylbMV0sDQogICAgICAgICAgICAgICAgICAuZiA9IH4gdGliYmxlKCd7ey54fX0nIDo9IGxvb2t1cF9kaXZbW2Rpdl1dKFgsIENbLngsXSwgZGVnID0gZGVnKSkpDQogIHJldHVybihkaXMpDQp9DQpgYGANCg0KDQotLS0NCg0KPiDwn6e+ICoqUmVtYXJrLjIqKjogDQpOb3RlIHRoYXQgImxvZ2lzdGljIiBCcmVnbWFuIGRpdmVyZ2VuY2UgY2FuIGhhbmRsZSBvbmx5IGRhdGEgcG9pbnRzIHdpdGggZG9tYWluICR7XGNhbCBDfT0oMCwxKV5kJCwgdGhlcmVmb3JlLCBpdCBzaG91bGQgYmUgdXNlZCBvbmx5IGluIHN1aXRhYmxlIGNhc2VzLg0KDQotLS0NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+U3RlcCAkSyQ6ICRLJC1tZWFucyB3aXRoIEJyZWdtYW4gZGl2ZXJnZW5jZXM8L3U+PC9zcGFuPg0KPT09DQoNClRoaXMgc2VjdGlvbiBpbXBsZW1lbnRzICRLJC1tZWFucyBhbGdvcml0aG0gdXNpbmcgQnJlZ21hbiBkaXZlcmdlbmNlcyB3aGljaCBjb3JyZXNwb25kcyB0byB0aGUgc3RlcCAkSyQgb2YgS0ZDIHByb2NlZHVyZS4NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPiA6IGBmaW5kQ2xvc2VzdENlbnRyb2lkYCBhbmQgYG5ld0NlbnRyb2lkc2ANCi0tLS0NCg0KVGhlc2UgdHdvIGZ1bmN0aW9ucyBwZXJmb3JtIHRoZSBtYWluIHN0ZXBzIG9mICRLJC1tZWFucyBhbGdvcml0aG0uIEZ1bmN0aW9uIGBmaW5kQ2xvc2VzdENlbnRyb2lkYCBhc3NpZ25zIGFueSBkYXRhIHBvaW50cyB0byBzb21lIGNsdXN0ZXIgYWNjb3JkaW5nIHRvIHRoZSBzbWFsbGVzdCBkaXZlcmdlbmNlIGJldHdlZW4gdGhlIGRhdGEgcG9pbnQgYW5kIHRoZSBjZW50cm9pZC4gSXQgcHJvdmlkZXMgYSB2ZWN0b3Igb2YgY2x1c3RlcnMgb2YgYWxsIHRoZSBkYXRhIHBvaW50cy4gRnJvbSB0aGVyZSwgZnVuY3Rpb24gYG5ld0NlbnRyb2lkc2AgY29tcHV0ZXMgbmV3IGNlbnRyb2lkcyBnaXZlbiB0aGUgY2x1c3RlciBsYWJlbHMgb2YgYWxsIGRhdGEgcG9pbnRzLg0KDQotICoqQXJndW1lbnQqKjoNCg0KICAgIC0gYHguYCA6IHRoZSBkYXRhIG1hdHJpY2VzLCB0aWJibGVzIGFuZCBkYXRhIGZyYW1lcywgY29udGFpbmluZyB0aGUgZGF0YSBwb2ludHMgdG8gYmUgYXNzaWduZWQgdG8gc29tZSBjbHVzdGVyLg0KICAgIC0gYGNlbnRyb2lkc2AgOiB0aGUgbWF0cml4IG9yIGRhdGEgZnJhbWUgb2YgY2VudHJvaWRzIChieSByb3cpLg0KICAgIC0gYGRpdmAgOiB0aGUgZGl2ZXJnZW5jZSB0eXBlIHRvIGJlIHVzZWQuDQogICAgLSBgZGVnYCA6IHRoZSBkZWdyZWUgb2YgcG9seW5vbWlhbCBCRCAoaWYgb25lIGlzIHVzZWQpLg0KICAgIA0KLSAqKlZhbHVlKio6IA0KDQogICAgVGhlIGVhY2ggb2YgdGhlIHR3byBmdW5jdGlvbnMgcmV0dXJucyBhcmd1bWVudHMgZm9yIG9uZSBhbm90aGVyOg0KICAgIA0KICAgIC0gYGZpbmRDbG9zZXN0Q2VudHJvaWRgIHJldHVybnMgYSB2ZWN0b3Igb2Ygc2l6ZSBlcXVhbHMgdG8gdGhlIG51bWJlciBvZiByb3dzIG9mIGRhdGEgbWF0cml4IHguLCBjb250YWluaW5nIHRoZSBjbHVzdGVyIGxhYmVscyBvZiB0aGUgZGF0YSBwb2ludHMuDQogICAgLSBgbmV3Q2VudHJvaWRzYCByZXR1cm5zIG5ldyBtYXRyaXggb2YgY2VudHJvaWRzLg0KDQpgYGB7cn0NCmZpbmRDbG9zZXN0Q2VudHJvaWQgPC0gZnVuY3Rpb24oeC4sIGNlbnRyb2lkcy4sIGRpdiwgZGVnID0gMyl7DQogIGRpc3QgPC0gQnJlZ21hbkRpdih4LiwgY2VudHJvaWRzLiwgZGl2LCBkZWcpDQogIGNsdXN0IDwtIDE6bnJvdyh4LikgJT4lDQogICAgbWFwX2ludCguZiA9IH4gd2hpY2gubWluKGRpc3RbLngsXSkpDQogIHJldHVybihjbHVzdCkNCn0NCm5ld0NlbnRyb2lkcyA8LSBmdW5jdGlvbih4LiwgY2x1c3RlcnMuKXsNCiAgY2VudHJvaWRzIDwtIHVuaXF1ZShjbHVzdGVycy4pICU+JQ0KICAgIG1hcF9kZnIoLmYgPSB+IGNvbE1lYW5zKHguW2NsdXN0ZXJzLiA9PSAueCwgXSkpDQogIHJldHVybihjZW50cm9pZHMpDQp9DQpgYGANCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPiA6IGBrbWVhbnNCRGANCi0tLS0NCg0KVGhpcyBmdW5jdGlvbiBwZXJmb3JtcyAkSyQtbWVhbnMgYWxnb3JpdGhtIHdpdGggQkRzLiANCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGB0cmFpbl9pbnB1dGAgOiB0aGUgZGF0YSBtYXRyaWNlcywgdGliYmxlcyBhbmQgZGF0YSBmcmFtZXMsIGNvbnRhaW5pbmcgdGhlIGRhdGEgcG9pbnRzLg0KICAgIC0gYEtgIDogdGhlIG51bWJlciBvZiBjbHVzdGVycy4NCiAgICAtIGBuX3N0YXJ0YCA6IHRoZSBudW1iZXIgb2YgdGltZXMgdG8gcGVyZm9ybSB0aGUgYWxnb3JpdGhtLCBhbmQgdGhlIGJlc3Qgb25lIGFtb25nIHRoZW0gaXMgY2hvc2VuIHRvIGJlIHRoZSBmaW5hbCByZXN1bHQuIFRoaXMgaXMgZG9uZSB0byBhdm9pZCBsb2NhbCBvcHRpbWFsIHNvbHV0aW9ucy4gQnkgZGVmYXVsdCwgYG5fc3RhcnQgPSA1YC4NCiAgICAtIGBtYXhJdGVyYCA6IHRoZSBtYXhpbXVtIG51bWJlciBvZiBpdGVyYXRpb25zIGluIGNhc2UgdGhlIGFsZ29yaXRobSBkb2VzIG5vdCBjb252ZXJnZS4gQnkgZGVmYXVsdCwgYG1heEl0ZXIgPSA1MDBgLg0KICAgIC0gYGRlZ2AgOiB0aGUgZGVncmVlIG9mIHBvbHlub21pYWwgQkQgKGlmIG9uZSBpcyB1c2VkKS4NCiAgICAtIGBzY2FsZV9pbnB1dGAgOiBhIGxvZ2ljYWwgdmFsdWUgY29udHJvbGxpbmcgd2hldGhlciB0byBzY2FsZSB0aGUgaW5wdXQgdG8gYmUgaW4gJCgwLDEpJCBvciBub3QuIEJ5IGRlZmF1bHQsIGBzY2FsZV9pbnB1dCA9IEZBTFNFYC4NCiAgICAtIGBkaXZgIDogdGhlIHR5cGUgb2YgZGl2ZXJnZW5jZSB0byBiZSB1c2VkLiBCeSBkZWZhdWx0LCBgZGl2ID0gImV1Y2xpZGVhbiJgIGFuZCB0aGUgdXN1YWwgJEskLW1lYW5zIGFsZ29yaXRobSBpcyBwZXJmb3JtZWQuDQogICAgLSBgc3BsaXRzYCA6IGEgcmVhbCBudW1iZXIgYmV0d2VlbiAkMCQgYW5kICQxJCBzcGVjaWZ5aW5nIHRoZSBwcm9wb3J0aW9uIG9mIHRyYWluaW5nIGRhdGEgdG8gYmUgdXNlZCB0byBwZXJmb3JtICRLJC1tZWFucyBhbGdvcml0aG0uIFRoZSByZW1haW5pbmcgcGFydCB3aXRoIGJlIHVzZWQgZm9yIHRoZSBhZ2dyZWdhdGlvbi4gQnkgZGVmYXVsdCwgYHNwbGl0cyA9IDFgIGFuZCBhbGwgdGhlIGlucHV0IGRhdGEgYXJlIHVzZWQuDQogICAgLSBgZXBzaWxvbmAgOiB0aGUgc3RvcHBpbmcgY3JpdGVyaW9uIG9mIHRoZSBhbGdvcml0aG0uIEJ5IGRlZmF1bHQsIGBlcHNpbG9uID0gMWUtMTBgLg0KICAgIC0gYGNlbnRlcl9gLCBgc2NhbGVfYCA6IHRoZSBjZW50ZXIgYW5kIHNjYWxlIHRvIGJlIHVzZWQgdG8gc2NhbGUgdGhlIGlucHV0IGRhdGEuIEJ5IGRlZmF1bHQsIHRoZXkgYXJlIGBOVUxMYC4NCiAgICAtIGBpZF9zaHVmZmxlYCA6IGEgbG9naWNhbCB2ZWN0b3Igc3BlY2lmeWluZyB3aGljaCBwYXJ0IG9mIHRoZSB0cmFpbmluZyBkYXRhIHdpbGwgYmUgc2VsZWN0ZWQgdG8gcGVyZm9ybSB0aGUgYWxnb3JpdGhtLiBUaGlzIGlzIGltcG9ydGFudCB3aGVuIHdlIHdhbnQgdG8gcGVyZm9ybSB0aGUgYWxnb3JpdGhtIG9uIHRoZSBzYW1lIHNldCBvZiBkYXRhIHBvaW50cyBidXQgd2l0aCBkaWZmZXJlbnQgQkRzLiANCiAgICANCi0gKipWYWx1ZSoqOiANCg0KICAgIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBhICoqbGlzdCoqIG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCiAgICAtIGBjZW50cm9pZHNgIDogdGhlIG1hdHJpeCBvZiB0aGUgY2VudHJvaWRzIG9idGFpbmVkIGJ5IHRoZSBhbGdvcml0aG0uDQogICAgLSBgY2x1c3RlcnNgIDogYSB2ZWN0b3Igb2YgY2x1c3RlciBsYWJlbHMgb2YgdGhlIGRhdGEgcG9pbnRzLg0KICAgIC0gYHRyYWluX2RhdGFgIDogYSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCiAgICAgICAgLSBgWF90cmFpbmAgOiB0aGUgdHJhaW5pbmcgZGF0YSB1c2VkIGZvciB0aGUgYWxnb3JpdGhtLg0KICAgICAgICAtIGBYX3JlbWFpbmAgOiB0aGUgcmVtYWluaW5nIHBhcnQgb2YgdGhlIGlucHV0IGRhdGEgdXNlZCBmb3IgdGhlIGFnZ3JlZ2F0aW9uLg0KICAgICAgICAtIGBpZF9yZW1haW5gIDogYSBsb2dpY2FsIHZlY3RvciBzcGVjaWZ5aW5nIHRoZSByZW1haW5pbmcgcGFydCAoYFhfcmVtYWluYCkgb2YgdGhlIGlucHV0IGRhdGEuDQogICAgLSBgcGFyYW1ldGVyc2AgOiBhIGxpc3Qgb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzOg0KICAgICAgICAtIGBkaXZgIDogZGl2ZXJnZW5jZSB1c2VkLg0KICAgICAgICAtIGBkZWdgIDogdGhlIGRlZ3JlZSBvZiBwb2x5bm9taWFsIEJEIChpZiBvbmUgaXMgdXNlZCkuDQogICAgICAgIC0gYGNlbnRlcl9gLCBgc2NhbGVfYCA6IHRoZSBjZW50ZXIgYW5kIHNjYWxlIHVzZWQgdG8gc2NhbGUgdGhlIGlucHV0IGRhdGEuDQogICAgLSBgcnVubmluZ190aW1lYDogdGhlIGNvbXB1dGF0aW9uYWwgdGltZSBvZiB0aGUgYWxnb3JpdGhtLg0KDQpgYGB7cn0NCmttZWFuc0JEIDwtIGZ1bmN0aW9uKHRyYWluX2lucHV0LA0KICAgICAgICAgICAgICAgICAgICAgSywNCiAgICAgICAgICAgICAgICAgICAgIG5fc3RhcnQgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgbWF4SXRlciA9IDUwMCwNCiAgICAgICAgICAgICAgICAgICAgIGRlZyA9IDMsDQogICAgICAgICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgZGl2ID0gImV1Y2xpZGVhbiIsDQogICAgICAgICAgICAgICAgICAgICBzcGxpdHMgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgZXBzaWxvbiA9IDFlLTEwLA0KICAgICAgICAgICAgICAgICAgICAgY2VudGVyXyA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICBzY2FsZV8gPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgaWRfc2h1ZmZsZSA9IE5VTEwpew0KICBzdGFydF90aW1lIDwtIFN5cy50aW1lKCkNCiAgIyBEaXN0b3J0aW9uIGZ1bmN0aW9uDQogIFggPC0gYXMubWF0cml4KHRyYWluX2lucHV0KQ0KICBOIDwtIGRpbShYKQ0KICBpZihzY2FsZV9pbnB1dCl7DQogICAgaWYoIShpcy5udWxsKGNlbnRlcl8pICYgaXMubnVsbChzY2FsZV8pKSl7DQogICAgICBpZihsZW5ndGgoY2VudGVyXykgPT0gMSl7DQogICAgICAgIGNlbnRlcl8gPC0gcmVwKGNlbnRlcl8sIE5bMl0pDQogICAgICB9DQogICAgICBpZihsZW5ndGgoc2NhbGVfKSA9PSAxKXsNCiAgICAgICAgc2NhbGVfIDwtIHJlcChzY2FsZV8sIE5bMl0pDQogICAgICB9DQogICAgfSBlbHNlew0KICAgICAgbWluXyA8LSBhcHBseShYLCAyLCBGVU4gPSBtaW4pDQogICAgICBjXyA8LSBhYnMoY29sTWVhbnMoWCkvNSkNCiAgICAgIGNlbnRlcl8gPC0gbWluXyAtIGNfDQogICAgICBzY2FsZV8gPC0gYXBwbHkoWCwgMiwgRlVOID0gbWF4KSAtIGNlbnRlcl8gKyAxDQogICAgfQ0KICAgIFggPC0gc2NhbGUoWCwgY2VudGVyID0gY2VudGVyXywgc2NhbGUgPSBzY2FsZV8pDQogIH0NCiAgaWYoaXMubnVsbChpZF9zaHVmZmxlKSl7DQogICAgdHJhaW5faWQgPC0gcmVwKFRSVUUsIE5bMV0pDQogICAgaWYoc3BsaXRzIDwgMSl7DQogICAgICB0cmFpbl9pZFtzYW1wbGUoTlsxXSwgZmxvb3IoTlsxXSooMS1zcGxpdHMpKSldIDwtIEZBTFNFDQogICAgfQ0KICB9IGVsc2V7DQogICAgdHJhaW5faWQgPC0gaWRfc2h1ZmZsZQ0KICB9DQogIFhfdHJhaW4xIDwtIFhbdHJhaW5faWQsXQ0KICBYX3RyYWluMiA8LSBYWyF0cmFpbl9pZCxdDQogIG11IDwtIGFzLm1hdHJpeChjb2xNZWFucyhYX3RyYWluMSkpDQogIGRpc3RvcnRpb24gPC0gZnVuY3Rpb24oY2x1cyl7DQogICAgY2VudCA8LSBuZXdDZW50cm9pZHMoWF90cmFpbjEsIGNsdXMpDQogICAgdmFyX3dpdGhpbiA8LSAxOksgJT4lDQogICAgICBtYXAoLmYgPSB+IEJyZWdtYW5EaXYoWF90cmFpbjFbY2x1cyA9PSAueCxdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZW50Wy54LF0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpdiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVnKSkgJT4lDQogICAgICBtYXAoLmYgPSBzdW0pICU+JQ0KICAgICAgUmVkdWNlKCIrIiwgLikNCiAgICByZXR1cm4odmFyX3dpdGhpbikNCiAgfQ0KICAjIEttZWFucyBhbGdvcml0aG0NCiAga21lYW5zV2l0aEJEIDwtIGZ1bmN0aW9uKHguLCBrLiwgbWF4aXRlci4sIGVwcy4pIHsNCiAgICBuLiA8LSBucm93KHguKQ0KICAgICMgaW5pdGlhbGl6YXRpb24NCiAgICBpbml0IDwtIHNhbXBsZShuLiwgay4pDQogICAgY2VudHJvaWRzX29sZCA8LSB4Lltpbml0LF0NCiAgICBpIDwtIDANCiAgICB3aGlsZShpIDwgbWF4SXRlcil7DQogICAgICAjIEFzc2lnbm1lbnQgc3RlcA0KICAgICAgY2x1c3RlcnMgPC0gZmluZENsb3Nlc3RDZW50cm9pZCh4LiwgY2VudHJvaWRzX29sZCwgZGl2LCBkZWcpDQogICAgICAjIFJlY29tcHV0ZSBjZW50cm9pZHMNCiAgICAgIGNlbnRyb2lkc19uZXcgPC0gbmV3Q2VudHJvaWRzKHguLCBjbHVzdGVycykNCiAgICAgIGlmICgoc3VtKGlzLm5hKGNlbnRyb2lkc19uZXcpKSA+IDApIHwNCiAgICAgICAgICAobnJvdyhjZW50cm9pZHNfbmV3KSAhPSBrLikpIHsNCiAgICAgICAgaW5pdCA8LSBzYW1wbGUobi4sIGsuKQ0KICAgICAgICBjZW50cm9pZHNfb2xkIDwtIHguW2luaXQsXQ0KICAgICAgICB3YXJuaW5nKCJOQSBwcm9kdWNlZCAtPiByZWluaXRpYWxpemUgY2VudHJvaWRzLi4uISIpDQogICAgICB9DQogICAgICBlbHNlew0KICAgICAgICBpZihzdW0oYWJzKGNlbnRyb2lkc19uZXcgLSBjZW50cm9pZHNfb2xkKSkgPiBlcHMuKXsNCiAgICAgICAgICBjZW50cm9pZHNfb2xkIDwtIGNlbnRyb2lkc19uZXcNCiAgICAgICAgfSBlbHNlew0KICAgICAgICAgIGJyZWFrDQogICAgICAgIH0NCiAgICAgIH0NCiAgICAgIGkgPC0gaSArIDENCiAgICB9DQogICAgcmV0dXJuKGNsdXN0ZXJzKQ0KICB9DQogIHJlc3VsdHMgPC0gMTpuX3N0YXJ0ICU+JSANCiAgICBtYXBfZGZjKC5mID0gfiB0aWJibGUoInt7Lnh9fSIgOj0ga21lYW5zV2l0aEJEKFhfdHJhaW4xLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEssDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhJdGVyLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVwc2lsb24pKSkNCiAgb3B0X2lkIDwtIDE6bl9zdGFydCAlPiUNCiAgICBtYXBfZGJsKC5mID0gfiBkaXN0b3J0aW9uKHJlc3VsdHNbWy54XV0pKSAlPiUNCiAgICB3aGljaC5taW4NCiAgY2x1c3RlciA8LSBjbHVzdGVycyA8LSByZXN1bHRzW1tvcHRfaWRdXQ0KICBqIDwtIDENCiAgSUQgPC0gdW5pcXVlKGNsdXN0ZXIpDQogIGZvciAoaSBpbiBJRCkgew0KICAgIGNsdXN0ZXJzW2NsdXN0ZXIgPT0gaV0gPSBqDQogICAgaiA9ICBqICsgMQ0KICB9DQogIGNlbnRyb2lkcyA9IG5ld0NlbnRyb2lkcyhYX3RyYWluMSwgY2x1c3RlcnMpDQogIHRpbWVfdGFrZW4gPC0gU3lzLnRpbWUoKSAtIHN0YXJ0X3RpbWUNCiAgcmV0dXJuKA0KICAgIGxpc3QoDQogICAgICBjZW50cm9pZHMgPSBjZW50cm9pZHMsDQogICAgICBjbHVzdGVycyA9IGNsdXN0ZXJzLA0KICAgICAgdHJhaW5fZGF0YSA9IGxpc3QoWF90cmFpbiA9IFhfdHJhaW4xLA0KICAgICAgICAgICAgICAgICAgICAgICAgWF9yZW1haW4gPSBYX3RyYWluMiwNCiAgICAgICAgICAgICAgICAgICAgICAgIGlkX3JlbWFpbiA9ICF0cmFpbl9pZCksDQogICAgICBwYXJhbWV0ZXJzID0gbGlzdChkaXYgPSBkaXYsDQogICAgICAgICAgICAgICAgICAgICAgICBkZWcgPSBkZWcsDQogICAgICAgICAgICAgICAgICAgICAgICBjZW50ZXJfID0gY2VudGVyXywNCiAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlXyA9IHNjYWxlXyksDQogICAgICBydW5uaW5nX3RpbWUgPSB0aW1lX3Rha2VuDQogICAgKQ0KICApDQp9DQpgYGANCg0KLS0tLQ0KDQo+ICoqRXhhbXBsZS4xKio6IFdlIHBlcmZvcm0gJEskLW1lYW5zIGFsZ29yaXRobSB3aXRoIGAiZ2tsImAgQkQgb24gW0FiYWxvbmVdKGh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9tYWNoaW5lLWxlYXJuaW5nLWRhdGFiYXNlcy9hYmFsb25lKSBkYXRhc2V0Lg0KDQotLS0tDQoNCmBgYHtyfQ0KcGFjbWFuOjpwX2xvYWQocmVhZHIpDQpjb2xuYW1lIDwtIGMoIlR5cGUiLCAiTG9uZ2VzdFNoZWxsIiwgIkRpYW1ldGVyIiwgIkhlaWdodCIsICJXaG9sZVdlaWdodCIsICJTaHVja2VkV2VpZ2h0IiwgIlZpc2NlcmFXZWlnaHQiLCAiU2hlbGxXZWlnaHQiLCAiUmluZ3MiKQ0KZGYgPC0gcmVhZHI6OnJlYWRfZGVsaW0oImh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9tYWNoaW5lLWxlYXJuaW5nLWRhdGFiYXNlcy9hYmFsb25lL2FiYWxvbmUuZGF0YSIsIGNvbF9uYW1lcyA9IGNvbG5hbWUsIGRlbGltID0gIiwiLCBzaG93X2NvbF90eXBlcyA9IEZBTFNFKQ0KbiA8LSBucm93KGRmKQ0KdHJhaW4gPC0gbG9naWNhbChuKQ0KdHJhaW5bc2FtcGxlKG4sICBmbG9vcihuKjAuOCkpXSA8LSBUUlVFDQpjbCA8LSBkZlt0cmFpbiwyOihuY29sKGRmKS0xKV0gJT4lDQogIGttZWFuc0JEKEsgPSAzLCBkaXYgPSAiZ2tsIiwgc3BsaXRzID0gMC41LCBzY2FsZV9pbnB1dCA9IFRSVUUpDQp0YWJsZShjbCRjbHVzdGVycykNCmBgYA0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPjx1PlN0ZXAgJEYkOiBGaXR0aW5nIHByZWRpY3RpdmUgbW9kZWxzPC91Pjwvc3Bhbj4NCj09PQ0KDQpUaGlzIHNlY3Rpb24gYnVpbGRzIGdsb2JhbCBtb2RlbHMgYnkgZml0dGluZyBsb2NhbCBtb2RlbCBvbiBlYWNoIGdpdmVuIGNsdXN0ZXIgb2YgdGhlIG9idGFpbmVkIHBhcnRpdGlvbi4gVGhpcyBjb3JyZXNwb25kcyB0byB0aGUgc3RlcCAkRiQgb2YgdGhlIHByb2NlZHVyZS4NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPiA6IGBmaXRMb2NhbE1vZGVsc2ANCi0tLS0NCg0KVGhpcyBmdW5jdGlvbiBmaXRzIGxvY2FsIG1vZGVscyAkKHtcY2FsIE1fe2osa319KV97aixrfSQgb24gYWxsIGNsdXN0ZXJzICRrPTEsLi4uLEskIG9mIHRoZSBvYnRhaW5lZCBwYXJ0aXRpb24sIGdpdmVuIGJ5IEJyZWdtYW4gZGl2ZXJnZW5jZSAke1xjYWwgQn1faiQsIGZvciAkaj0xLC4uLixNJC4NCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGBrbWVhbnNfQkRgIDogYW4gb2JqZWN0IG9idGFpbmVkIGZyb20gYGttZWFuc0JEYCBmdW5jdGlvbi4NCiAgICAtIGB0cmFpbl9yZXNwb25zZWAgOiB0aGUgdmVjdG9yIG9mIHJlc3BvbnNlIHZhcmlhYmxlIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGZ1bGwgYGlucHV0X2RhdGFgIGdpdmVuIHRvIGBrbWVhbkJEYCBmdW5jdGlvbi4NCiAgICAtIGBtb2RlbGAgOmEgbG9jYWwgbW9kZWwgdHlwZSB0byBmaXQgb24gYWxsIHRoZSBjbHVzdGVycyBvZiB0aGUgZ2l2ZW4gcGFydGl0aW9uLiBJdCBzaG91bGQgYmUgZWl0aGVyIGEgbW9kZWwgb2JqZWN0ICh3b3JrcyB3aXRoIGZ1bmN0aW9uIGBwcmVkaWN0YCksIG9yIGEgc3RyaW5nIGVsZW1lbnQgb2YgeyJsbSIsICJ0cmVlIiwgInJmIn0gd2hpY2ggY29ycmVzcG9uZHMgdG8gbGluZWFyIHJlZ3Jlc3Npb24sIHRyZWUgYW5kIHJhbmRvbSBmb3Jlc3QgcmVzcGVjdGl2ZWx5LiBCeSBkZWZhdWx0LCBgbW9kZWwgPSAibG0iYC4NCiAgICAtIGBmb3JtdWxhYCA6IHRoZSBkZWdyZWUgb2YgcG9seW5vbWlhbCBCRCAoaWYgb25lIGlzIHVzZWQpLg0KDQotICoqVmFsdWUqKjoNCg0KICAgIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBhIGxpc3Qgb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzOg0KICAgIC0gYGxvY2FsX21vZGVsc2AgOiBhbGwgdGhlIGxvY2FsIG1vZGVscyBmaXR0ZWQgb24gYWxsIGNsdXN0ZXJzIG9mIHRoZSBnaXZlbiBwYXJ0aXRpb24uDQogICAgLSBga21lYW5zX0JEYCA6IHRoZSBga21lYW5zQkRgIG9iamVjdC4NCiAgICAtIGBkYXRhX3JlbWFpbmAgOiBhIGxpc3Qgb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzOg0KICAgICAgICAtIGBmaXRgIDogdGhlIGZpdHRlZCB2YWx1ZXMgb2YgdGhlIHJlbWFpbmluZyBwYXJ0IG9mIHRoZSBpbnB1dCBkYXRhLg0KICAgICAgICAtIGByZXNwb25zZWAgOiB0aGUgYWN0dWFsIHJlc3BvbnNlIHZhbHVlcyBjb3JyZXNwb25kaW5nIHRvIHRoZSByZW1haW5pbmcgaW5wdXQgZGF0YS4NCiAgICAtIGBydW5uaW5nX3RpbWVgIDogdGhlIGNvbXB1dGF0aW9uYWwgdGltZSBvZiB0aGUgYWxnb3JpdGhtLg0KDQpgYGB7cn0NCmZpdExvY2FsTW9kZWxzIDwtIGZ1bmN0aW9uKGttZWFuc19CRCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgPSAibG0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9ybXVsYSA9IE5VTEwpew0KICBzdGFydF90aW1lIDwtIFN5cy50aW1lKCkNCiAgWF90cmFpbiA8LSBrbWVhbnNfQkQkdHJhaW5fZGF0YSRYX3RyYWluDQogIHlfdHJhaW4gPC0gdHJhaW5fcmVzcG9uc2VbIShrbWVhbnNfQkQkdHJhaW5fZGF0YSRpZF9yZW1haW4pXQ0KICBYX3JlbWFpbiA8LSBrbWVhbnNfQkQkdHJhaW5fZGF0YSRYX3JlbWFpbg0KICB5X3JlbWFpbiA8LSBOVUxMDQogIGlmKCFpcy5udWxsKFhfcmVtYWluKSl7DQogICAgeV9yZW1haW4gPC0gdHJhaW5fcmVzcG9uc2Vba21lYW5zX0JEJHRyYWluX2RhdGEkaWRfcmVtYWluXQ0KICB9DQogIHBhY21hbjo6cF9sb2FkKHRyZWUpDQogIHBhY21hbjo6cF9sb2FkKHJhbmRvbUZvcmVzdCkNCiAgbW9kZWxfIDwtIGlmZWxzZShtb2RlbCA9PSAidHJlZSIsIHRyZWU6OnRyZWUsIG1vZGVsKQ0KICBLIDwtIG5yb3coa21lYW5zX0JEJGNlbnRyb2lkcykNCiAgaWYgKGlzLm51bGwoZm9ybXVsYSkpew0KICAgIGZvcm0gPC0gZm9ybXVsYSh0YXJnZXQgfiAuKQ0KICB9DQogIGVsc2V7DQogICAgZm9ybSA8LSB1cGRhdGUoZm9ybXVsYSwgdGFyZ2V0IH4gLikNCiAgfQ0KICBkYXRhXyA8LSBiaW5kX2NvbHMoWF90cmFpbiwgInRhcmdldCI6PSB5X3RyYWluKQ0KICBmaXRfbG9va3VwIDwtIGxpc3QobG0gPSAiZml0dGVkLnZhbHVlcyIsDQogICAgICAgICAgICAgICAgICAgICByZiA9ICJwcmVkaWN0ZWQiKQ0KICBpZihpcy5jaGFyYWN0ZXIobW9kZWxfKSl7DQogICAgbW9kZWxfbG9va3VwIDwtIGxpc3QobG0gPSBsbSwNCiAgICAgICAgICAgICAgICAgICAgICAgICByZiA9IHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KQ0KICAgIG1vZCA8LSBtYXAoLnggPSAxOkssIA0KICAgICAgICAgICAgICAgLmYgPSB+IG1vZGVsX2xvb2t1cFtbbW9kZWxfXV0oZm9ybXVsYSA9IGZvcm0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFfW2ttZWFuc19CRCRjbHVzdGVycyA9PSAueCwgXSkpDQogIH0gZWxzZXsNCiAgICBtb2QgPC0gbWFwKC54ID0gMTpLLCANCiAgICAgICAgICAgICAgIC5mID0gfiBtb2RlbF8oZm9ybXVsYSA9IGZvcm0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0YV9ba21lYW5zX0JEJGNsdXN0ZXJzID09IC54LF0pKQ0KICB9DQogIHByZWQwIDwtIE5VTEwNCiAgaWYoIWlzLm51bGwoWF9yZW1haW4pKXsNCiAgICBwcmVkMCA8LSB2ZWN0b3IobW9kZSA9ICJudW1lcmljIiwgDQogICAgICAgICAgICAgICAgICAgIGxlbmd0aCA9IGxlbmd0aCh5X3JlbWFpbikpDQogICAgY2x1cyA8LSBmaW5kQ2xvc2VzdENlbnRyb2lkKHguID0gWF9yZW1haW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRyb2lkcy4gPSBrbWVhbnNfQkQkY2VudHJvaWRzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXYgPSBrbWVhbnNfQkQkcGFyYW1ldGVycyRkaXYsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlZyA9IGttZWFuc19CRCRwYXJhbWV0ZXJzJGRlZykNCiAgICBmb3IoaV8gaW4gMTpLKXsNCiAgICAgIHByZWQwW2NsdXMgPT0gaV9dIDwtIHByZWRpY3QobW9kW1tpX11dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5kYXRhLmZyYW1lKFhfcmVtYWluW2NsdXMgPT0gaV8sXSkpDQogICAgfQ0KICB9DQogIHRpbWVfdGFrZW4gPC0gU3lzLnRpbWUoKSAtIHN0YXJ0X3RpbWUNCiAgcmV0dXJuKGxpc3QoDQogICAgbG9jYWxfbW9kZWxzID0gbW9kLA0KICAgIGttZWFuc19CRCA9IGttZWFuc19CRCwNCiAgICBkYXRhX3JlbWFpbiA9IGxpc3QoZml0ID0gcHJlZDAsDQogICAgICAgICAgICAgICAgICAgICAgIHJlc3BvbnNlID0geV9yZW1haW4pLA0KICAgIHJ1bm5pbmdfdGltZSA9IHRpbWVfdGFrZW4NCiAgKSkNCn0NCmBgYA0KDQotLS0tIA0KDQo+ICoqRXhhbXBsZS4yKio6IEZyb20gKipFeGFtcGxlLjEqKiBhYm92ZSwgbXVsdGlwbGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWxzIGFyZSBidWlsdCBvbiBhbGwgdGhlIG9idGFpbmVkIGNsdXN0ZXJzLiBUaGUgbWVhbiBzcXVhcmUgZXJyb3Igb2YgdGhpcyBtb2RlbCwgZXZhbHVhdGVkIG9uIHRoZSByZW1haW5pbmcgJDUwXCUkIG9mIHRoZSB0cmFpbmluZyBkYXRhIGlzIGNvbXB1dGVkLg0KDQotLS0tDQoNCmBgYHtyfQ0KZml0IDwtIGZpdExvY2FsTW9kZWxzKHRyYWluX3Jlc3BvbnNlID0gZGYkUmluZ3NbdHJhaW5dLA0KICAgICAgICAgICAgICAgICAgICAgIGttZWFuc19CRCA9IGNsLA0KICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gImxtIikNCg0KbWVhbigoZml0JGRhdGFfcmVtYWluJHJlc3BvbnNlLSBmaXQkZGF0YV9yZW1haW4kZml0KV4yKQ0KYGBgDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkZ1bmN0aW9uPC91Pjwvc3Bhbj4gOiBgbG9jYWxQcmVkaWN0YA0KLS0tLQ0KDQpUaGlzIGZ1bmN0aW9uIGFsbG93cyB1cyB0byBwcmVkaWN0IGFueSBuZXcgb2JzZXJ2YXRpb25zIHVzaW5nIHRoZSBjYW5kaWRhdGUgbW9kZWwgJHtcY2FsIE19X2o9KHtcY2FsIE19X3tqLGt9KV97az0xfV5NJCBjb3JyZXNwb25kaW5nIHRvIEJyZWdtYW4gZGl2ZXJnZW5jZSAke1xjYWwgQn1faiQsIGZvciBzb21lICRqXGluIEpcc3Vic2V0XHsxLC4uLixNXH0kLiANCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGBsb2NhbE1vZGVsc2AgOiBhIGxvY2FsIG1vZGVsIG9iamVjdCBvYnRhaW5lZCBmcm9tIGBmaXRMb2NhbE1vZGVsc2AgZnVuY3Rpb24uDQogICAgLSBgbmV3RGF0YWAgOiBuZXcgaW5wdXQgZGF0YSB0byBiZSBwcmVkaWN0ZWQgdXNpbmcgdGhlIGNhbmRpZGF0ZSBtb2RlbHMgZ2l2ZW4gaW4gYGxvY2FsTW9kZWxzYCBhcmd1bWVudC4NCiAgICANCi0gKipWYWx1ZSoqOg0KDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIGEgcHJlZGljdGVkIHZlY3RvciBvZiB0aGUgYG5ld0RhdGFgLg0KDQpgYGB7cn0NCmxvY2FsUHJlZGljdCA8LSBmdW5jdGlvbihsb2NhbE1vZGVscywNCiAgICAgICAgICAgICAgICAgICAgICAgICBuZXdEYXRhKXsNCiAga21lYW5fQkQgPC0gbG9jYWxNb2RlbHMka21lYW5zX0JEDQogIEsgPC0gbnJvdyhrbWVhbl9CRCRjZW50cm9pZHMpDQogIG5ld0RhdGFfIDwtIG5ld0RhdGENCiAgaWYoIShpcy5udWxsKGttZWFuX0JEJHBhcmFtZXRlcnMkY2VudGVyXykpKXsNCiAgICBuZXdEYXRhXyA8LSBzY2FsZShuZXdEYXRhLA0KICAgICAgICAgICAgICAgICAgICAgIGNlbnRlciA9IGttZWFuX0JEJHBhcmFtZXRlcnMkY2VudGVyXywNCiAgICAgICAgICAgICAgICAgICAgICBzY2FsZSA9IGttZWFuX0JEJHBhcmFtZXRlcnMkc2NhbGVfKQ0KICAgIGlkMCA8LSAobmV3RGF0YV8gPD0gMCkNCiAgICBpZihzdW0oaWQwKSA+IDApew0KICAgICAgbWluXyA8LSBtaW4obmV3RGF0YV9baWQwXSkNCiAgICAgIG5ld0RhdGFfW2lkMF0gPC0gcnVuaWYoc3VtKGlkMCksIG1pbigxZS0zLCBtaW5fLzEwKSwgbWluXykNCiAgICB9DQogIH0NCiAgY2x1cyA8LSBmaW5kQ2xvc2VzdENlbnRyb2lkKHguID0gbmV3RGF0YV8sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZW50cm9pZHMuID0ga21lYW5fQkQkY2VudHJvaWRzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGl2ID0ga21lYW5fQkQkcGFyYW1ldGVycyRkaXYsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWcgPSBrbWVhbl9CRCRwYXJhbWV0ZXJzJGRlZykNCiAgcHJlZDAgPC0gdmVjdG9yKG1vZGUgPSAibnVtZXJpYyIsIGxlbmd0aCA9IG5yb3cobmV3RGF0YV8pKQ0KICBmb3IoaV8gaW4gMTpLKXsNCiAgICBwcmVkMFtjbHVzID09IGlfXSA8LSBwcmVkaWN0KGxvY2FsTW9kZWxzJGxvY2FsX21vZGVsc1tbaV9dXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUobmV3RGF0YV9bY2x1cyA9PSBpXyxdKSkNCiAgfQ0KICBwcmVkMCA8LSBhc190aWJibGUocHJlZDApDQogIG5hbWVzKHByZWQwKSA8LSBpZmVsc2Uoa21lYW5fQkQkcGFyYW1ldGVycyRkaXYgPT0gInBvbHlub21pYWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHBhc3RlMCgicG9seW5vbWlhbCIsIGttZWFuX0JEJHBhcmFtZXRlcnMkZGVnKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBrbWVhbl9CRCRwYXJhbWV0ZXJzJGRpdikNCiAgcmV0dXJuKHByZWQwKQ0KfQ0KYGBgDQoNCi0tLS0NCg0KPiAqKkV4YW1wbGUuMyoqOiBUaGUgcGVyZm9ybWFuY2Ugb2YgdGhlIGNhbmRpZGF0ZSBtb2RlbCBjb3JyZXNwb25kaW5nIHRvIGAiZ2tsImAgZGl2ZXJnZW5jZSBpcyBjb21wYXJlZCB0byByYW5kb20gZm9yZXN0IHJlZ3Jlc3Npb24gb24gYSAkMjBcJSQgdGVzdGluZyBkYXRhLg0KDQotLS0tDQoNCmBgYHtyfQ0KeV9oYXQgPC0gbG9jYWxQcmVkaWN0KGZpdCwNCiAgICAgICAgICAgICAgICAgICAgICBkZlshdHJhaW4sIDI6KG5jb2woZGYpLTEpLF0pDQpyZiA8LSByYW5kb21Gb3Jlc3QoUmluZ3MgfiAuLCBkYXRhID0gZGZbdHJhaW4sMjpuY29sKGRmKV0pDQptZWFuKChwcmVkaWN0KHJmLCBuZXdkYXRhID0gZGZbIXRyYWluLDI6bmNvbChkZildKS0gZGYkUmluZ3NbIXRyYWluXSleMikNCm1lYW4oKHlfaGF0JGdrbC1kZiRSaW5nc1shdHJhaW5dKV4yKQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+U3RlcCAkQyQ6IENvbWJpbmluZyBtZXRob2RzPC91Pjwvc3Bhbj4NCj09PQ0KDQpUaGUgc291cmNlIGNvZGVzIGFuZCBpbmZvcm1hdGlvbiBvZiB0aGUgYWdncmVnYXRpb24gbWV0aG9kcyBhcmUgYXZhaWxhYmxlIFtoZXJlIDxzcGFuIHN0eWxlPSJjb2xvcjogIzA5N0JDMSI+IGByIGZvbnRhd2Vzb21lOjpmYSgiZ2l0aHViIilgPC9zcGFuPl0oaHR0cHM6Ly9naXRodWIuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMpLiBUaGUgY29kZXMgYmVsb3cgaW1wb3J0cyB0aGUgYWdncmVnYXRpb24gbWV0aG9kcyBpbnRvIDxzcGFuIHN0eWxlPSJjb2xvcjogIzAyODdEODsiPiAqKlJzdHVkaW8qKiA8L3NwYW4+IGVudmlyb25tZW50Lg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCnBhY21hbjo6cF9sb2FkKGRldnRvb2xzKQ0KIyMjIEtlcm5lbCBiYXNlZCBjb25zZW5zdWFsIGFnZ3JlZ2F0aW9uDQpzb3VyY2VfdXJsKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vaGFzc290aGVhL0FnZ3JlZ2F0aW9uTWV0aG9kcy9tYWluL0tlcm5lbEFnZ1JlZy5SIikNCiMjIyBNaXhDb2JyYQ0Kc291cmNlX3VybCgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMvbWFpbi9NaXhDb2JyYVJlZy5SIikNCmBgYA0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT5GdW5jdGlvbjwvdT48L3NwYW4+IDogYHN0ZXBLYCwgYHN0ZXBGYCBhbmQgYHN0ZXBDYA0KLS0tLQ0KDQpUaGVzZSBmdW5jdGlvbnMgYWxsb3cgdG8gc2V0IHZhbHVlcyBvZiB0aGUgcGFyYW1ldGVycyBpbiB0aGUgdGhyZWUgc3RlcHMgb2YgdGhlIEtGQyBwcm9jZWR1cmUuIEVhY2ggZnVuY3Rpb24gcmV0dXJucyBhIGxpc3Qgb2YgYWxsIHRoZSBwYXJhbWV0ZXJzIGdpdmVuIGluIGl0cyBhcmd1bWVudHMuDQoNCg0KYGBge3J9DQpzdGVwSyA9IGZ1bmN0aW9uKEssDQogICAgICAgICAgICAgICAgIG5fc3RhcnQgPSA1LA0KICAgICAgICAgICAgICAgICBtYXhJdGVyID0gMzAwLA0KICAgICAgICAgICAgICAgICBkZWcgPSAzLA0KICAgICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICBkaXYgPSBOVUxMLA0KICAgICAgICAgICAgICAgICBzcGxpdHMgPSAwLjc1LA0KICAgICAgICAgICAgICAgICBlcHNpbG9uID0gMWUtMTAsDQogICAgICAgICAgICAgICAgIGNlbnRlcl8gPSBOVUxMLA0KICAgICAgICAgICAgICAgICBzY2FsZV8gPSBOVUxMKXsNCiAgcmV0dXJuKGxpc3QoSyA9IEssDQogICAgICAgICAgICAgIG5fc3RhcnQgPSBuX3N0YXJ0LA0KICAgICAgICAgICAgICBtYXhJdGVyID0gbWF4SXRlciwNCiAgICAgICAgICAgICAgZGVnID0gZGVnLA0KICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IHNjYWxlX2lucHV0LA0KICAgICAgICAgICAgICBkaXYgPSBkaXYsDQogICAgICAgICAgICAgIHNwbGl0cyA9IHNwbGl0cywNCiAgICAgICAgICAgICAgZXBzaWxvbiA9IGVwc2lsb24sDQogICAgICAgICAgICAgIGNlbnRlcl8gPSBjZW50ZXJfICwNCiAgICAgICAgICAgICAgc2NhbGVfID0gc2NhbGVfKSkNCn0NCg0Kc3RlcEYgPSBmdW5jdGlvbihmb3JtdWxhID0gTlVMTCwgDQogICAgICAgICAgICAgICAgIG1vZGVsID0gImxtIil7DQogIHJldHVybihsaXN0KGZvcm11bGEgPSBmb3JtdWxhLCANCiAgICAgICAgICAgICAgbW9kZWwgPSBtb2RlbCkpDQp9DQoNCnN0ZXBDID0gZnVuY3Rpb24obl9jdiA9IDUsDQogICAgICAgICAgICAgICAgIG1ldGhvZCA9IGMoImNvYnJhIiwgIm1peGNvYnJhIiksDQogICAgICAgICAgICAgICAgIG9wdF9tZXRob2RzID0gYygiZ3JhZCIsICJncmlkIiksDQogICAgICAgICAgICAgICAgIGtlcm5lbHMgPSAiZ2F1c3NpYW4iLA0KICAgICAgICAgICAgICAgICBzY2FsZV9mZWF0dXJlcyA9IEZBTFNFKXsNCiAgcmV0dXJuKGxpc3Qobl9jdiA9IG5fY3YsDQogICAgICAgICAgICAgIG1ldGhvZCA9IG1ldGhvZCwNCiAgICAgICAgICAgICAgb3B0X21ldGhvZHMgPSBvcHRfbWV0aG9kcywNCiAgICAgICAgICAgICAga2VybmVscyA9IGtlcm5lbHMsDQogICAgICAgICAgICAgIHNjYWxlX2ZlYXR1cmVzID0gc2NhbGVfZmVhdHVyZXMpKQ0KfQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPjogYEtGQ3JlZ2ANCj09PQ0KDQpUaGlzIGZ1bmN0aW9uIGlzIHRoZSBjb21wbGV0ZSBpbXBsZW1lbnRhdGlvbiBvZiBLRkMgcHJvY2VkdXJlLg0KDQotICoqQXJndW1lbnQqKjoNCg0KICAgIC0gYHRyYWluX2lucHV0YCA6IHRoZSBtYXRyaXggb3IgZGF0YSBmcmFtZSBvZiB0cmFpbmluZyBpbnB1dCBkYXRhLg0KICAgIC0gYHRyYWluX3Jlc3BvbnNlYCA6IHRoZSB0cmFpbmluZyByZXNwb25zZSB2YXJpYWJsZS4NCiAgICAtIGB0ZXN0X2lucHV0YDogdGhlIHRlc3RpbmcgaW5wdXQgZGF0YS4NCiAgICAtIGB0ZXN0X3Jlc3BvbnNlYCA6IHRoZSByZXNwb25zZSB2YXJpYWJsZSBvZiB0aGUgdGVzdGluZyBkYXRhLiBJdCBpcyBvcHRpb25hbC4gSWYgaXQgaXMgbm90IGBOVUxMYCwgdGhlIG1lYW4gc3F1YXJlIGVycm9yIChtc2UpIGlzIGNvbXB1dGVkLg0KICAgIC0gYG5fY3ZgIDogdGhlIG51bWJlciBvZiBmb2xkcyBpbiBjcm9zcy12YWxpZGF0aW9uLg0KICAgIC0gYHBhcmFsbGVsYCA6IGEgbG9naWNhbCB2YWx1ZSBzcGVjaWZ5aW5nIHdoZXRoZXIgb3Igbm90IHRvIHBlcmZvcm0gJEskLW1lYW5zIGFsZ29yaXRobSAoc3RlcCAkSyQpIGluIHBhcmFsbGVsLiBCeSBkZWZhdWx0LCBgcGFyYWxsZWwgPSBUUlVFYC4gTm90ZSB0aGF0ICoqaW50ZXJuZXQgY29ubmVjdGlvbioqIGlzIHJlcXVpcmVkIGluIHRoaXMgY2FzZS4NCiAgICAtIGBpbnZfc2lnbWFgLCBgYWxwYCA6IHRoZSBpbnZlcnNlIG5vcm1hbGl6ZWQgY29uc3RhbnQgJFxzaWdtYV57LTF9PjAkIGFuZCB0aGUgZXhwb25lbnQgJFxhbHBoYT4wJCBvZiBleHBvbmVudGlhbCBrZXJuZWw6ICRLKHgpPWVeey1cfHgvXHNpZ21hXHxee1xhbHBoYX19JCBmb3IgYW55ICR4XGluXG1hdGhiYntSfV5kJC4gQnkgZGVmYXVsdCwgYGludl9zaWdtYSA9IGAkXHNxcnR7MS8yfSQgYW5kIGBhbHBoYSA9IDJgIHdoaWNoIGNvcnJlc3BvbmRzIHRvIHRoZSBHYXVzc2lhbiBrZXJuZWwuDQogICAgLSBgS19zdGVwYCA6IGFuIG9iamVjdCBvYnRhaW5lZCBmcm9tIGZ1bmN0aW9uIGBzdGVwS2AsIGFsbG93aW5nIHRvIHNldCB0aGUgdmFsdWVzIG9mIHRoZSBwYXJhbXRlcnMgaW4gc3RlcCAkSyQgb2YgdGhlIHByb2NlZHVyZS4NCiAgICAtIGBGX3N0ZXBgIDogYW4gb2JqZWN0IG9idGFpbmVkIGZyb20gZnVuY3Rpb24gYHN0ZXBGYCwgYWxsb3dpbmcgdG8gc2V0IHRoZSB2YWx1ZXMgb2YgdGhlIHBhcmFtdGVycyBpbiBzdGVwICRGJCBvZiB0aGUgcHJvY2VkdXJlLg0KICAgIC0gYENfc3RlcGAgOiBhbiBvYmplY3Qgb2J0YWluZWQgZnJvbSBmdW5jdGlvbiBgc3RlcENgLCBhbGxvd2luZyB0byBzZXQgdGhlIHZhbHVlcyBvZiB0aGUgcGFyYW10ZXJzIGluIHN0ZXAgJEMkIG9mIHRoZSBwcm9jZWR1cmUuDQogICAgLSBgc2V0R3JhZFBhcmFtQWdnYCA6IGFuIG9iamVjdCBmcm9tIHRoZSBgc2V0R3JhZFBhcmFtZXRlcmAgZnVuY3Rpb24sIGFsbG93aW5nIHRvIHNldCB0aGUgdmFsdWVzIG9mIHRoZSBwYXJhbWV0ZXJzIG9mICoqZ3JhZGllbnQgZGVzY2VudCoqIGFsZ29yaXRobSBmb3IgdGhlIDxzcGFuIHN0eWxlPSJjb2xvcjogI0U2MTgwQTsiPioqMXN0IGFnZ3JlZ2F0aW9uIG1ldGhvZCReMSQqKjwvc3Bhbj4uDQogICAgLSBgc2V0R3JpZFBhcmFtQWdnYCA6IGFuIG9iamVjdCBmcm9tIHRoZSBgc2V0R3JpZFBhcmFtZXRlcmAgZnVuY3Rpb24sIGFsbG93aW5nIHRvIHNldCB0aGUgdmFsdWVzIG9mIHRoZSBwYXJhbWV0ZXJzIG9mIHRoZSAqKmdyaWQgc2VhcmNoKiogYWxnb3JpdGhtIGZvciB0aGUgPHNwYW4gc3R5bGU9ImNvbG9yOiAjRTYxODBBOyI+Kioxc3QgYWdncmVnYXRpb24gbWV0aG9kJF4xJCoqPC9zcGFuPi4NCiAgICAtIGBzZXRHcmFkUGFyYW1NaXhgIDogYW4gb2JqZWN0IGZyb20gdGhlIGBzZXRHcmFkUGFyYW1ldGVyX01peGAgZnVuY3Rpb24sIGFsbG93aW5nIHRvIHNldCB0aGUgdmFsdWVzIG9mIHRoZSBwYXJhbWV0ZXJzIG9mIHRoZSAqKmdyYWRpZW50IGRlc2NlbnQqKiBhbGdvcml0aG0gZm9yIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwODMyQ0QiPioqMm5kIGFnZ3JlZ2F0aW9uIG1ldGhvZCReMiQqKjwvc3Bhbj4uDQogICAgLSBgc2V0R3JpZFBhcmFtTWl4YCA6IGFuIG9iamVjdCBmcm9tIHRoZSBgc2V0R3JpZFBhcmFtZXRlcl9NaXhgIGZ1bmN0aW9uLCBhbGxvd2luZyB0byBzZXQgdGhlIHZhbHVlcyBvZiB0aGUgcGFyYW1ldGVycyBvZiB0aGUgKipncmlkIHNlYXJjaCoqIGFsZ29yaXRobSBmb3IgdGhlIDxzcGFuIHN0eWxlPSJjb2xvcjogIzA4MzJDRCI+KioybmQgYWdncmVnYXRpb24gbWV0aG9kJF4yJCoqPC9zcGFuPi4NCiAgICAtIGBzaWxlbnRgIDogYSBsb2dpY2FsIHZhbHVlIHRvIHNpbGVudCBhbGwgdGhlIG1lc3NhZ2VzIGR1cmluZyB0aGUgYWxnb3JpdGhtLg0KICAgIA0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgYSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCiAgICANCiAgICAtIGBwcmVkaWN0X2ZpbmFsYCA6IHRoZSBmaW5hbCBwcmVkaWN0aW9ucyBnaXZlbiBieSB0aGUgYWdncmVnYXRpb24gb2YgYWxsIHRoZSBjYW5kaWRhdGUgbW9kZWxzLg0KICAgIC0gYHByZWRpY3RfbG9jYWxgIDogdGhlIHByZWRpY3Rpb25zIGdpdmVuIGJ5IGFsbCB0aGUgaW5kaXZpZHVhbCBjYW5kaWRhdGUgbW9kZWxzLg0KICAgIC0gYGFnZ19tZXRob2RgIDogdGhlIGxpc3Qgb2YgYWdncmVnYXRpb24gbWV0aG9kcyBvYnRhaW5lZCBpbiB0aGUgc3RlcCAkQyQgb2YgdGhlIHByb2NlZHVyZS4NCiAgICAtIGBydW5uaW5nX3RpbWVgIDogdGhlIGNvbXB1dGF0aW9uYWwgdGltZSBvZiB0aGUgYWxnb3JpdGhtLg0KDQotLS0tDQoNCiAgPiDwn6e+ICoqUmVtYXJrLjIqKjogVGhlIGBwYXJhbGxlbGAgYXJndW1lbnQgYWJvdmUgcmVxdWlyZXMgaW50ZXJuZXQgY29ubmVjdGlvbiB0byBsb2FkIHRoZSBzb3VyY2UgY29kZXMgb2YgJEskLW1lYW5zIGFsZ29yaXRobSB3aXRoIEJEcyBmcm9tIFtHaXRIdWIgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDk3QkMxIj4gYHIgZm9udGF3ZXNvbWU6OmZhKCJnaXRodWIiKWA8L3NwYW4+XShodHRwczovL2dpdGh1Yi5jb20vaGFzc290aGVhL0tGQy1Qcm9jZWR1cmUvYmxvYi9tYXN0ZXIva21lYW5CRC5SKS4gSXQgaXMgcGVyZm9ybWVkIG9uIHRoZSBtYXhpbXVtIG51bWJlciBvZiBjbHVzdGVycyBvZiB5b3VyIG1hY2hpbmUsIGFuZCB0aGUgc3BlZWQgaXMgYXQgbGVhc3QgdHdvIHRpbWVzIGZhc3RlciB0aGFuIHdpdGhvdXQgcGFyYWxsZWxpc20sIGhvd2V2ZXIsIGl0IGlzIG5vdCBzbyBzdGFibGUgZGVwZW5kaW5nIG9uIHlvdXIgaW50ZXJuZXQgY29ubmVjdGlvbiBvciBtYWNoaW5lLiANCg0KPiBGb3IgdGhlIGFnZ3JlZ2F0aW9uIG1ldGhvZHMgaW4gc3RlcCAkQyQ6DQoNCg0KLSA8c3BhbiBzdHlsZT0iY29sb3I6ICNFNjE4MEE7Ij4kXjEkPC9zcGFuPiBpcyB0aGUga2VybmVsLWJhc2VkIGNvbnNlbnN1YWwgYWdncmVnYXRpb24gZm9yIHJlZ3Jlc3Npb24gYnkgW0hhcyAoMjAyMSldKGh0dHBzOi8vaGFsLmFyY2hpdmVzLW91dmVydGVzLmZyL2hhbC0wMjg4NDMzM3Y1KS4gVGhlIHNvdXJjZSBjb2RlcyBvZiB0aGVzZSBmdW5jdGlvbnMgYXJlIGF2YWlsYWJsZSBpbiBbQWdncmVnYXRpb25NZXRob2RzXShodHRwczovL2dpdGh1Yi5jb20vaGFzc290aGVhL0FnZ3JlZ2F0aW9uTWV0aG9kcykgZ2l0aHViIHJlcG9zaXRvcnkgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDk3QkMxIj4gYHIgZm9udGF3ZXNvbWU6OmZhKCJnaXRodWIiKWA8L3NwYW4+IChmaWxlIG5hbWU6IFtLZXJuZWxBZ2dSZWcuUl0oaHR0cHM6Ly9naXRodWIuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMvYmxvYi9tYWluL0tlcm5lbEFnZ1JlZy5SKSksIGFuZCB0aGUgZG9jdW1lbnRhdGlvbiBpcyBhdmFpbGFibGUgW2hlcmVdKGh0dHBzOi8vaGFzc290aGVhLmdpdGh1Yi5pby9maWxlcy9LZXJuZWxBZ2dSZWcvS2VybmVsQWdnUmVnLmh0bWwpLg0KLSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwODMyQ0QiPiReMiQ8L3NwYW4+IGlzIHRoZSBhZ2dyZWdhdGlvbiB1c2luZyBpbnB1dC1vdXRwdXQgdHJhZGUtb2ZmIGJ5IFtGaXNjaGVyIGFuZCBNb3VnZW90ICgyMDE5KV0oaHR0cHM6Ly93d3cuc2NpZW5jZWRpcmVjdC5jb20vc2NpZW5jZS9hcnRpY2xlL3BpaS9TMDM3ODM3NTgxODMwMjM0OSkuIFRoZSBzb3VyY2UgY29kZXMgb2YgdGhlc2UgZnVuY3Rpb25zIGFyZSBhdmFpbGFibGUgaW4gW0FnZ3JlZ2F0aW9uTWV0aG9kc10oaHR0cHM6Ly9naXRodWIuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMpIGdpdGh1YiByZXBvc2l0b3J5IDxzcGFuIHN0eWxlPSJjb2xvcjogIzA5N0JDMSI+IGByIGZvbnRhd2Vzb21lOjpmYSgiZ2l0aHViIilgPC9zcGFuPiAoZmlsZSBuYW1lOiBbTWl4Q29icmFSZWcuUl0oaHR0cHM6Ly9naXRodWIuY29tL2hhc3NvdGhlYS9BZ2dyZWdhdGlvbk1ldGhvZHMvYmxvYi9tYWluL01peENvYnJhUmVnLlIpKSwgYW5kIHRoZSBkb2N1bWVudGF0aW9uIGlzIGF2YWlsYWJsZSBvbiBbaGVyZV0oaHR0cHM6Ly9oYXNzb3RoZWEuZ2l0aHViLmlvL2ZpbGVzL0tlcm5lbEFnZ1JlZy9NaXhDb2JyYVJlZy5odG1sKS4NCg0KLS0tLQ0KDQoNCmBgYHtyfQ0KS0ZDcmVnID0gZnVuY3Rpb24odHJhaW5faW5wdXQsDQogICAgICAgICAgICAgICAgICB0cmFpbl9yZXNwb25zZSwNCiAgICAgICAgICAgICAgICAgIHRlc3RfaW5wdXQsDQogICAgICAgICAgICAgICAgICB0ZXN0X3Jlc3BvbnNlID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgIG5fY3YgPSA1LA0KICAgICAgICAgICAgICAgICAgcGFyYWxsZWwgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgaW52X3NpZ21hID0gc3FydCguNSksDQogICAgICAgICAgICAgICAgICBhbHAgPSAyLA0KICAgICAgICAgICAgICAgICAgS19zdGVwID0gc3RlcEsoc3BsaXRzID0gLjUpLA0KICAgICAgICAgICAgICAgICAgRl9zdGVwID0gc3RlcEYoKSwNCiAgICAgICAgICAgICAgICAgIENfc3RlcCA9IHN0ZXBDKCksDQogICAgICAgICAgICAgICAgICBzZXRHcmFkUGFyYW1BZ2cgPSBzZXRHcmFkUGFyYW1ldGVyKCksDQogICAgICAgICAgICAgICAgICBzZXRHcmlkUGFyYW1BZ2cgPSBzZXRHcmlkUGFyYW1ldGVyKCksDQogICAgICAgICAgICAgICAgICBzZXRHcmFkUGFyYW1NaXggPSBzZXRHcmFkUGFyYW1ldGVyX01peCgpLA0KICAgICAgICAgICAgICAgICAgc2V0R3JpZFBhcmFtTWl4ID0gc2V0R3JpZFBhcmFtZXRlcl9NaXgoKSwNCiAgICAgICAgICAgICAgICAgIHNpbGVudCA9IEZBTFNFKXsNCiAgc3RhcnRfdGltZSA8LSBTeXMudGltZSgpDQogIGxvb2t1cF9kaXZfbmFtZXMgPC0gYygiZXVjbGlkZWFuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAiZ2tsIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAibG9naXN0aWMiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICJpdGFrdXJhIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAicG9seW5vbWlhbCIpDQogIGRpdl8gPC0gS19zdGVwJGRpdg0KICAjIyMgSyBzdGVwOiBLbWVhbnMgY2x1c3RlcmluZyB3aXRoIEJEcw0KICBpZiAoaXMubnVsbChLX3N0ZXAkZGl2KSl7DQogICAgZGl2ZXJnZW5jZXMgPC0gbG9va3VwX2Rpdl9uYW1lcw0KICAgIHdhcm5pbmcoIk5vIGRpdmVyZ2VuY2UgcHJvdmlkZWQhIEFsbCBvZiB0aGVtIGFyZSB1c2VkISIpDQogIH0NCiAgZWxzZXsNCiAgICBkaXZlcmdlbmNlcyA8LSBLX3N0ZXAkZGl2ICU+JSANCiAgICAgIG1hcF9jaHIoLmYgPSB+IG1hdGNoLmFyZyhhcmcgPSAueCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2hvaWNlcyA9IGxvb2t1cF9kaXZfbmFtZXMpKQ0KICB9DQogIGRpdl9saXN0IDwtIGRpdmVyZ2VuY2VzICU+JSANCiAgICBtYXAoLmYgPSAoXCh4KSBpZih4ICE9ICJwb2x5bm9taWFsIikgcmV0dXJuKHgpIGVsc2UgcmV0dXJuKHJlcCgicG9seW5vbWlhbCIsIGxlbmd0aChLX3N0ZXAkZGVnKSkpKSkgJT4lDQogICAgdW5saXN0DQogIGRlZ19saXN0IDwtIHJlcChOQSwgbGVuZ3RoKGRpdl8pKQ0KICBkZWdfbGlzdFtkaXZfbGlzdCA9PSAicG9seW5vbWlhbCJdIDwtIEtfc3RlcCRkZWcNCiAgZGl2X25hbWVzIDwtIG1hcDJfY2hyKC54ID0gZGl2X2xpc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAueSA9IGRlZ19saXN0LA0KICAgICAgICAgICAgICAgICAgICAgICAgLmYgPSAoXCh4LCB5KSBpZihpcy5uYSh5KSkgcmV0dXJuKHgpIGVsc2UgcmV0dXJuKHBhc3RlMCh4LHkpKSkpDQogICMjIyBTdGVwIEs6IEttZWFucyBjbHVzdGVyaW5nIHdpdGggQnJlZ21hbiBkaXZlcmdlbmNlcw0KICBkbSA8LSBkaW0odHJhaW5faW5wdXQpDQogIGlkX3NodWZmbGUgPC0gdmVjdG9yKGxlbmd0aCA9IGRtWzFdKQ0KICBuX3RyYWluIDwtIGZsb29yKEtfc3RlcCRzcGxpdHMgKiBkbVsxXSkNCiAgaWRfc2h1ZmZsZVtzYW1wbGUoZG1bMV0sIG5fdHJhaW4pXSA8LSBUUlVFDQogIGlmKHBhcmFsbGVsKXsNCiAgICBudW1Db3JlcyA8LSBwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKQ0KICAgIGRvUGFyYWxsZWw6OnJlZ2lzdGVyRG9QYXJhbGxlbChudW1Db3JlcykgIyB1c2UgbXVsdGljb3JlLCBzZXQgdG8gdGhlIG51bWJlciBvZiBvdXIgY29yZXMNCiAgICBrbWVhbl8gPC0gZm9yZWFjaChpPTE6bGVuZ3RoKGRpdl9uYW1lcykpICVkb3BhciUgew0KICAgICAgZGV2dG9vbHM6OnNvdXJjZV91cmwoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9oYXNzb3RoZWEvS0ZDLVByb2NlZHVyZS9tYXN0ZXIva21lYW5CRC5SIikNCiAgICAgIGttZWFuc0JEKHRyYWluX2lucHV0ID0gdHJhaW5faW5wdXQsDQogICAgICAgICAgICAgICBLID0gS19zdGVwJEssDQogICAgICAgICAgICAgICBkaXYgPSBkaXZfbGlzdFtpXSwNCiAgICAgICAgICAgICAgIG5fc3RhcnQgPSBLX3N0ZXAkbl9zdGFydCwNCiAgICAgICAgICAgICAgIG1heEl0ZXIgPSBLX3N0ZXAkbWF4SXRlciwNCiAgICAgICAgICAgICAgIGRlZyA9IGRlZ19saXN0W2ldLA0KICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBLX3N0ZXAkc2NhbGVfaW5wdXQsDQogICAgICAgICAgICAgICBzcGxpdHMgPSBLX3N0ZXAkc3BsaXRzLA0KICAgICAgICAgICAgICAgZXBzaWxvbiA9IEtfc3RlcCRlcHNpbG9uLA0KICAgICAgICAgICAgICAgY2VudGVyXyA9IEtfc3RlcCRjZW50ZXJfLA0KICAgICAgICAgICAgICAgc2NhbGVfID0gS19zdGVwJHNjYWxlXywNCiAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBpZF9zaHVmZmxlKQ0KICAgIH0NCiAgICBkb1BhcmFsbGVsOjpzdG9wSW1wbGljaXRDbHVzdGVyKCkNCiAgfSBlbHNlew0KICAgIGttZWFuXyA8LSBtYXAyKC54ID0gZGl2X2xpc3QsDQogICAgICAgICAgICAgICAgICAgLnkgPSBkZWdfbGlzdCwNCiAgICAgICAgICAgICAgICAgICAuZiA9IH4ga21lYW5zQkQodHJhaW5faW5wdXQgPSB0cmFpbl9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSyA9IEtfc3RlcCRLLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXYgPSAueCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9zdGFydCA9IEtfc3RlcCRuX3N0YXJ0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhJdGVyID0gS19zdGVwJG1heEl0ZXIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlZyA9IC55LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IEtfc3RlcCRzY2FsZV9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gS19zdGVwJHNwbGl0cywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXBzaWxvbiA9IEtfc3RlcCRlcHNpbG9uLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZW50ZXJfID0gS19zdGVwJGNlbnRlcl8sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlXyA9IEtfc3RlcCRzY2FsZV8sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBpZF9zaHVmZmxlKSkNCiAgfQ0KICBuYW1lcyhrbWVhbl8pIDwtIGRpdl9uYW1lcw0KICAjIyMgRiBzdGVwOiBGaXR0aW5nIHRoZSBjb3JyZXNwb25kaW5nIG1vZGVsIG9uIGVhY2ggb2JzZXJ2ZWQgY2x1c3Rlcg0KICBtb2RlbF8gPC0gZGl2X25hbWVzICU+JQ0KICAgIG1hcCguZiA9IH4gZml0TG9jYWxNb2RlbHMoa21lYW5fW1sueF1dLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSB0cmFpbl9yZXNwb25zZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsID0gRl9zdGVwJG1vZGVsLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9ybXVsYSA9IEZfc3RlcCRmb3JtdWxhKSkNCiAgbmFtZXMobW9kZWxfKSA8LSBkaXZfbmFtZXMNCiAgcHJlZF9jb21iaW5lIDwtIG1vZGVsXyAlPiUNCiAgICBtYXBfZGZjKC5mID0gfiAueCRkYXRhX3JlbWFpbiRmaXQpDQogIHlfcmVtYWluIDwtIHRyYWluX3Jlc3BvbnNlWyFpZF9zaHVmZmxlXQ0KICBwcmVkX3Rlc3QgPC0gZGl2X25hbWVzICU+JQ0KICAgIG1hcF9kZmMoLmYgPSB+IGxvY2FsUHJlZGljdChtb2RlbF9bWy54XV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RfaW5wdXQpKQ0KICBuYW1lcyhwcmVkX3Rlc3QpIDwtIG5hbWVzKHByZWRfY29tYmluZSkgPC0gZGl2X25hbWVzDQogICMgQyBzdGVwOiBDb25zZW5zdWFsIHJlZ3Jlc3Npb24gYWdncmVnYXRpb24gbWV0aG9kIHdpdGgga2VybmVsLWJhc2VkIENPQlJBDQogIGxpc3RfbWV0aG9kX2FnZyA8LSBsaXN0KG1peGNvYnJhID0gZnVuY3Rpb24ocHJlZCl7TWl4Q29icmFSZWcodHJhaW5faW5wdXQgPSB0cmFpbl9pbnB1dFshaWRfc2h1ZmZsZSxdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlID0geV9yZW1haW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9pbnB1dCA9IHRlc3RfaW5wdXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcHJlZGljdGlvbnMgPSBwcmVkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RfcHJlZGljdGlvbnMgPSBwcmVkX3Rlc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9yZXNwb25zZSA9IHRlc3RfcmVzcG9uc2UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBLX3N0ZXAkc2NhbGVfaW5wdXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfbWFjaGluZSA9IENfc3RlcCRzY2FsZV9mZWF0dXJlcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2N2ID0gQ19zdGVwJG5fY3YsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW52X3NpZ21hID0gaW52X3NpZ21hLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscCA9IGFscCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWxzID0gQ19zdGVwJGtlcm5lbHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3B0aW1pemVNZXRob2QgPSBDX3N0ZXAkb3B0X21ldGhvZHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0R3JhZFBhcmFtID0gc2V0R3JhZFBhcmFtTWl4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldEdyaWRQYXJhbSA9IHNldEdyaWRQYXJhbU1peCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaWxlbnQgPSBzaWxlbnQpfSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29icmEgPSBmdW5jdGlvbihwcmVkKXtrZXJuZWxBZ2dSZWcodHJhaW5fZGVzaWduID0gcHJlZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSB5X3JlbWFpbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9kZXNpZ24gPSBwcmVkX3Rlc3QsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RfcmVzcG9uc2UgPSB0ZXN0X3Jlc3BvbnNlLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IEtfc3RlcCRzY2FsZV9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfbWFjaGluZSA9IENfc3RlcCRzY2FsZV9mZWF0dXJlcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnVpbGRfbWFjaGluZSA9IEZBTFNFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWNoaW5lcyA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fY3YgPSBDX3N0ZXAkbl9jdiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW52X3NpZ21hID0gc3FydCguNSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscCA9IDIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbHMgPSBDX3N0ZXAka2VybmVscywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3B0aW1pemVNZXRob2QgPSBDX3N0ZXAkb3B0X21ldGhvZHMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldEdyYWRQYXJhbSA9IHNldEdyYWRQYXJhbUFnZywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0R3JpZFBhcmFtID0gc2V0R3JpZFBhcmFtQWdnLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzaWxlbnQgPSBzaWxlbnQpfSkNCiAgcmVzIDwtIG1hcCgueCA9IENfc3RlcCRtZXRob2QsDQogICAgICAgICAgICAgLmYgPSB+IGxpc3RfbWV0aG9kX2FnZ1tbLnhdXShwcmVkX2NvbWJpbmUpKQ0KICBsaXN0X2FnZ19tZXRob2RzIDwtIGxpc3QoY29icmEgPSAiY29iIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1peGNvYnJhID0gIm1peCIpDQogIG5hbWVzKHJlcykgPC0gQ19zdGVwJG1ldGhvZA0KICBleHRfZnVuIDwtIGZ1bmN0aW9uKEwsIG5hbSl7DQogICAgdGFiIDwtIEwkZml0dGVkX2FnZ3JlZ2F0ZQ0KICAgIG5hbWVzKHRhYikgPC0gcGFzdGUwKG5hbWVzKHRhYiksICJfIiwgbmFtKQ0KICAgIHJldHVybih0YWIpDQogIH0NCiAgcHJlZF9maW4gPC0gQ19zdGVwJG1ldGhvZCAlPiUNCiAgICBtYXBfZGZjKC5mID0gfiBleHRfZnVuKHJlc1tbLnhdXSwgbGlzdF9hZ2dfbWV0aG9kc1tbLnhdXSkpDQogIHRpbWUudGFrZW4gPC0gU3lzLnRpbWUoKSAtIHN0YXJ0X3RpbWUNCiAgIyMjIFRvIGZpbmlzaA0KICBpZihpcy5udWxsKHRlc3RfcmVzcG9uc2UpKXsNCiAgICByZXR1cm4obGlzdCgNCiAgICBwcmVkaWN0X2ZpbmFsID0gcHJlZF9maW4sDQogICAgcHJlZGljdF9sb2NhbCA9IHByZWRfdGVzdCwNCiAgICBhZ2dfbWV0aG9kID0gcmVzLA0KICAgIHJ1bm5pbmdfdGltZSA9IHRpbWUudGFrZW4NCiAgKSkNCiAgfSBlbHNlew0KICAgIGVycm9yIDwtIGNiaW5kKHByZWRfdGVzdCwgcHJlZF9maW4pICU+JQ0KICAgICAgZHBseXI6Om11dGF0ZSh5X3Rlc3QgPSB0ZXN0X3Jlc3BvbnNlKSAlPiUNCiAgICAgIGRwbHlyOjpzdW1tYXJpc2VfYWxsKC5mdW5zID0gfiAoLiAtIHlfdGVzdCkpICU+JQ0KICAgICAgZHBseXI6OnNlbGVjdCgteV90ZXN0KSAlPiUNCiAgICAgIGRwbHlyOjpzdW1tYXJpc2VfYWxsKC5mdW5zID0gfiBtZWFuKC5eMikpDQogICAgcmV0dXJuKGxpc3QoDQogICAgICBwcmVkaWN0X2ZpbmFsID0gcHJlZF9maW4sDQogICAgICBwcmVkaWN0X2xvY2FsID0gcHJlZF90ZXN0LA0KICAgICAgYWdnX21ldGhvZCA9IHJlcywNCiAgICAgIG1zZSA9IGVycm9yLA0KICAgICAgcnVubmluZ190aW1lID0gdGltZS50YWtlbg0KICApKQ0KICB9DQp9DQpgYGANCg0KPiAqKkV4YW1wbGUuNCoqOiBBIGNvbXBsZXRlIEtGQyBwcm9jZWR1cmUgaXMgaW1wbGVtZW50ZWQgb24gdGhlIHNhbWUgQWJhbG9uZSBkYXRhLCB1c2luZyAkNSQgQkRzIGAiZXVjbGlkZWFuImAsIGAiaXRha3VyYSJgLCBgImdrbCJgIGFuZCBgInBvbHlub21pYWwiYCAob2YgZGVncmVlICQzJCBhbmQgJDYkKS4gQm90aCBhZ2dyZWdhdGlvbiBtZXRob2RzIGFyZSB1c2VkIGluIHRoZSBzdGVwICRDJC4gVHdvIGtlcm5lbCBmdW5jdGlvbnMgYXJlIHVzZWQgZm9yIGVhY2ggYWdncmVnYXRpb24gbWV0aG9kOiBgImdhdXNzaWFuImAgKHdpdGggZ3JhZGllbnQgZGVzY2VudCBhbGdvcml0aG0pIGFuZCBgImVwYW5lY2huaWtvdiJgICh3aXRoIGdyaWQgc2VhcmNoIGFsZ29yaXRobSkuDQoNCmBgYHtyfQ0KdHJhaW4xIDwtIGxvZ2ljYWwobikNCnRyYWluMVtzYW1wbGUobiwgIGZsb29yKG4qMC44KSldIDwtIFRSVUUNCmtmYzEgPC0gS0ZDcmVnKHRyYWluX2lucHV0ID0gZGZbdHJhaW4xLDI6bmNvbChkZildLA0KICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlID0gZGYkUmluZ3NbdHJhaW4xXSwNCiAgICAgICAgICAgICAgICB0ZXN0X2lucHV0ID0gZGZbIXRyYWluMSwyOm5jb2woZGYpXSwNCiAgICAgICAgICAgICAgICBLX3N0ZXAgPSBzdGVwSyhLID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGl2ID0gYygiZXVjbCIsICJpdGEiLCAiZ2tsIiwgInBvbHkiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWcgPSBjKDMsIDYpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0cyA9IC41KSwNCiAgICAgICAgICAgICAgICBDX3N0ZXAgPSBzdGVwQyhtZXRob2QgPSBjKCJjb2JyYSIsICJtaXhjb2JyYSIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9wdF9tZXRob2RzID0gYygiZ3JhZCIsICJncmlkIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmVscyA9IGMoImdhdXNzaWFuIiwgImdhdXNzaWFuIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfZmVhdHVyZXMgPSBGQUxTRSksDQogICAgICAgICAgICAgICAgc2V0R3JhZFBhcmFtQWdnID0gc2V0R3JhZFBhcmFtZXRlcihyYXRlID0gMC4yKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICNjb2VmX2xtID0gMiksDQogICAgICAgICAgICAgICAgc2V0R3JpZFBhcmFtQWdnID0gc2V0R3JpZFBhcmFtZXRlcihtaW5fdmFsID0gLjAwMDAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X3ZhbCA9IDEwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl92YWwgPSAxMDApLA0KICAgICAgICAgICAgICAgIHNldEdyYWRQYXJhbU1peCA9IHNldEdyYWRQYXJhbWV0ZXJfTWl4KHJhdGUgPSAibGluZWFyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2VmX2F1dG8gPSBjKC41LC41KSksDQogICAgICAgICAgICAgICAgc2V0R3JpZFBhcmFtTWl4ID0gc2V0R3JpZFBhcmFtZXRlcl9NaXgobWluX2FscGhhID0gMWUtMTAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2FscGhhID0gMC41LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9iZXRhID0gMWUtMTAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2JldGEgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fYWxwaGEgPSAyMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2JldGEgPSAyMCkpDQpgYGANCg0KPiBUaGUgbWVhbiBzcXVhcmUgZXJyb3JzIGV2YWx1YXRlZCBvbiAkMjBcJSQtdGVzdGluZyBkYXRhIG9mIHRoZSBhYm92ZSBjb21wdXRhdGlvbiBhcmUgcmVwb3J0ZWQgYmVsb3cuDQoNCmBgYHtyfQ0KcmYxIDwtIHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KFJpbmdzIH4gLiwgZGF0YSA9IGRmW3RyYWluMSwyOm5jb2woZGYpXSkNCmtmYzEkcHJlZGljdF9maW5hbCAlPiUNCiAgbXV0YXRlKHJmID0gcHJlZGljdChyZjEsIG5ld2RhdGEgPSBkZlshdHJhaW4xLDI6bmNvbChkZildKSkgJT4lDQogIHN3ZWVwKE1BUkdJTiA9IDEsIFNUQVRTID0gZGYkUmluZ3NbIXRyYWluMV0sIEZVTiA9ICItIikgJT4lDQogIC5eMiAgJT4lDQogIGNvbE1lYW5zDQpgYGANCg0KPiBXZSBjYW4gc2VlIHRoYXQgS0ZDIHByb2NlZHVyZSBwZXJmb3JtcyByZWFsbHkgd2VsbCBvbiB0aGlzIHJlYWwtbGlmZSBkYXRhc2V0Lg0KDQotIFtIYXMgZXQgYWwuICgyMDIxKV0oaHR0cHM6Ly93d3cudGFuZGZvbmxpbmUuY29tL2VwcmludC9ZS0dTOEdUS0RCS1lGWEVHRldTQi9mdWxsP3RhcmdldD0xMC4xMDgwLzAwOTQ5NjU1LjIwMjEuMTg5MTUzOSkNCi0gW0Zpc2NoZXIgYW5kIE1vdWdlb3QgKDIwMTkpXShodHRwczovL3d3dy5zY2llbmNlZGlyZWN0LmNvbS9zY2llbmNlL2FydGljbGUvcGlpL1MwMzc4Mzc1ODE4MzAyMzQ5KQ0KLSBbSGFzICgyMDIxKV0oaHR0cHM6Ly9oYWwuYXJjaGl2ZXMtb3V2ZXJ0ZXMuZnIvaGFsLTAyODg0MzMzdjUpDQotIFtCaWF1IGV0IGFsLiAoMjAxNildKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAwNDcyNTlYMTUwMDA5NTApDQotIC4uLg0KLSBbZHBseXIgdmlkZW9zXShodHRwczovL3d3dy55b3V0dWJlLmNvbS9oYXNodGFnL2RwbHlyKSBgciBmb250YXdlc29tZTo6ZmEoInZpZGVvIilgDQotIFtnZ3Bsb3QyIHZpZGVvIHR1dG9yaWFsXShodHRwczovL3d3dy55b3V0dWJlLmNvbS9oYXNodGFnL2dncGxvdDIpIGByIGZvbnRhd2Vzb21lOjpmYSgidmlkZW8iKWANCi0gW1IgZm9yIGRhdGEgc2NpZW5jZV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei8pDQoNCi0tLQ0KDQo+IDxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPiYjMTI4MjE0OyBSZWFkIGFsc28gW0tlcm5lbEFnZ1JlZ10oaHR0cHM6Ly9oYXNzb3RoZWEuZ2l0aHViLmlvL2ZpbGVzL0NvZGVzUGhEL0tlcm5lbEFnZ1JlZy5odG1sKSBhbmQgW01peENvYnJhUmVnXShodHRwczovL2hhc3NvdGhlYS5naXRodWIuaW8vZmlsZXMvQ29kZXNQaEQvTWl4Q29icmFSZWcuaHRtbCk8L3NwYW4+Lg0KDQotLS0=