🔎 How to download & run the codes?

All the source codes of the aggregation methods are available here . To run the codes, you can clone the repository directly or simply load the R script source file from the repository using devtools package in Rstudio as follow:

  1. Install devtools package using command:

    install.packages("devtools")

  2. Loading the source codes from GitHub repository using source_url function by:

    devtools::source_url("https://raw.githubusercontent.com/hassothea/AggregationMethods/main/MixCOBRARegressor.R")


✎ Note: All codes contained in this Rmarkdown are built with recent version of (version \(>\) 4.1, available here) and Rstudio (version > 2022.02.2+485, available here). Note also that the code chucks are hidden by default.

To see the codes, you can:


1 MixCobra & important packages

1.1 MixCobra method

This Rmarkdown provides the implementation of an aggregation method using input and output trade-off by Fischer and Mougeot (2019). Let \(\mathcal{D}_n=\{(x_1,y_1),...,(x_n,y_n)\}\) be a training data of size \(n\), where the input-output couples \((x_i,y_i)\in\mathbb{R}^d\times\mathbb{R}\) for all \(i=1,...,n\). \(\mathcal{D}_{n}\) is first randomly partitioned into \(\mathcal{D}_{k}\) and \(\mathcal{D}_{\ell}\) of size \(k\) and \(\ell\) respectively such that \(k+\ell=n\). We construct \(M\) regression estimators (machines) \(r_1,...,r_M\) using only \(\mathcal{D}_{k}\). Let \({\bf r}(x)=(r_1(x),...,r_M(x))^T\in\mathbb{R}^M\) be the vector of predictions of \(x\in\mathbb{R}^d\), the aggregation method evaluated at point \(x\) is defined by

\[\begin{equation} g_n(x)=\frac{\sum_{i=1}^{\ell}y_iK_{\alpha,\beta}(x-x_i,{\bf r}(x)-{\bf r}(x_i))}{\sum_{j=1}^{\ell}K_{\alpha,\beta}(x-x_j,{\bf r}(x)-{\bf r}(x_j))} \end{equation}\] where \(K:\mathbb{R}^{d+M}\to\mathbb{R}_+\) is a non-increasing kernel function with \(K_{\alpha,\beta}(u,v)=K(\frac{u}{\alpha},\frac{v}{\beta})\) for some smoothing parameter \(\alpha,\beta>0\) to be tuned, with the convention \(0/0=0\).

1.2 Important packages

We prepare all the necessary tools for this Rmarkdown. pacman package allows us to load (if exists) or install (if does not exist) any available packages from The Comprehensive R Archive Network (CRAN) of .

# Check if package "pacman" is already installed 

lookup_packages <- installed.packages()[,1]
if(!("pacman" %in% lookup_packages)){
  install.packages("pacman")
}

# To be installed or loaded
pacman::p_load(magrittr)
pacman::p_load(ggplot2)
pacman::p_load(tidyverse)

## package for "generateMachines"
pacman::p_load(tree)
pacman::p_load(glmnet)
pacman::p_load(randomForest)
pacman::p_load(FNN)
pacman::p_load(xgboost)
pacman::p_load(keras)
pacman::p_load(pracma)
pacman::p_load(latex2exp)
pacman::p_load(plotly)
pacman::p_load(dash)
rm(lookup_packages)

2 Basic Machine generator

This section provides functions to generate basic machines (regressors) to be aggregated.

2.1 Function : setBasicParameter_Mix

This function allows us to set the values of some key parameters of the basic machines.

  • Argument:

    • lambda : the penalty parameter \(\lambda\) used in penalized linear models: ridge or lasso.
    • k : the parameter \(k\) of \(k\)NN (knn) regression model and the default value is \(k=10\).
    • ntree : the number of trees in random forest (rf). By default, ntree = 300.
    • mtry : the number of random features chosen in each split of random forest procedure. By default, mtry = NULL and the default value of mtry of randomForest function from randomForest library is used.
    • eta_xgb : the learning rate \(\eta>0\) in gradient step of extreme gradient boosting method (xgb) of xgboost library.
    • nrounds_xgb : the parameter nrounds indicating the max number of boosting iterations. By default, nrounds_xgb = 100.
    • early_stop_xgb : the early stopping round criterion of xgboost function. By, default, early_stop_xgb = NULL and the early stopping function is not triggered.
    • max_depth_xgb : maximum depth of trees constructed in xgboost.
  • Value:

    This function returns a list of all the parameters given in its arguments, to be fed to the basicMachineParam argument of function generateMachines_Mix defined in the next section.


🧾 Remark.1: lambda, k, ntree can be a single value or a vector. In other words, each type of models can be constructed several times according to the values of the parameters \((\alpha, \beta)\) of the method.


setBasicParameter_Mix <- function(lambda = NULL,
                              k = 5, 
                              ntree = 300, 
                              mtry = NULL, 
                              eta_xgb = 1, 
                              nrounds_xgb = 100, 
                              early_stop_xgb = NULL,
                              max_depth_xgb = 3){
  return(list(
    lambda = lambda,
    k = k,
    ntree = ntree, 
    mtry = mtry, 
    eta_xgb = eta_xgb, 
    nrounds_xgb = nrounds_xgb, 
    early_stop_xgb = early_stop_xgb,
    max_depth_xgb = max_depth_xgb)
  )
}

2.2 Function : generateMachines_Mix

This function generates all the basic machines to be aggregated.

  • Argument:

    • train_input : a matrix or data frame of the training input data.
    • train_response : a vector of training response variable corresponding to the train_input.
    • scale_input : logical value specifying whether to scale the input data (to be between \(0\) and \(1\)) or not. By default, scale_input = FALSE.
    • scale_machine : logical value specifying whether to scale the predictions of the remaining part \(\mathcal{D}_{\ell}\) of the training data (to be between \(0\) and \(1\)) or not. By default, scale_machine = FALSE.
    • machines : types of basic machines to be constructed. It is a subset of {“lasso”, “ridge”, “knn”, “tree”, “rf”, “xgb”}. By default, machines = NULL and all the six types of basic machines are built.
    • splits : real number between \(0\) and \(1\) specifying the proportion of training data used to train the basic machines (\(\mathcal{D}_k\)). The remaining proportion of (\(1-\) splits) is used for the aggregation (\(\mathcal{D}_{\ell}\)). By default, splits = 0.5.
    • basicMachineParam : the option used to setup the values of parameters of each machines. One should feed the function setBasicParameter_Mix() defined above to this argument.
    • silent : a logical value to silent all the messages or information to be printed during the process of the method. By default, silent = FALSE.
  • Value:

    This function returns a list of the following objects.

    • fitted_remain : the predictions of the remaining part (\(\mathcal{D}_{\ell}\)) of the training data, used for the aggregation.
    • models : all the constructed basic machines (it contains only the values of parameter \(k\) for knn).
    • id2 : a logical vector of size equals to the number of lines of the training data indicating the location of the points, used to build the basic machines (FALSE) and the remaining ones (TRUE).
    • train_data : a list of the following objects:
      • train_input : training input data (scale or non-scaling accordingly).
      • predict_remain_org : predictions of the second part \({\cal D}_{\ell}\) of the training data without scaling.
      • train_response : the trainging response variable.
      • min_machine, max_machine : vectors of minimum and maximum predicted values of the remaining part \(\mathcal{D}_{\ell}\) of the training data. They are NULL if scale_machine = FALSE.
      • min_input, max_input : vectors of minimum and maximum values of each variable of the training input. They are NULL if scale_input = FALSE.

✎ Note: You may need to modify the function accordingly if you want to build different types of basic machines.


generateMachines_Mix <- function(train_input, 
                             train_response,
                             scale_input = FALSE,
                             scale_machine = FALSE,
                             machines = NULL, 
                             splits = 0.5, 
                             basicMachineParam = setBasicParameter_Mix()){
  lambda = basicMachineParam$lambda
  k <- basicMachineParam$k 
  ntree <- basicMachineParam$ntree 
  mtry <- basicMachineParam$mtry
  eta_xgb <- basicMachineParam$eta_xgb 
  nrounds_xgb <- basicMachineParam$nrounds_xgb
  early_stop_xgb <- basicMachineParam$early_stop_xgb
  max_depth_xgb <- basicMachineParam$max_depth_xgb
  
  # Packages
  pacman::p_load(tree)
  pacman::p_load(glmnet)
  pacman::p_load(randomForest)
  pacman::p_load(FNN)
  pacman::p_load(xgboost)
  # pacman::p_load(keras)
  
  # Preparing data
  input_names <- colnames(train_input)
  input_size <- dim(train_input)
  df_input <- train_input_scale <- train_input
  maxs <- mins <- NULL
  if(scale_input){
    maxs <- map_dbl(.x = df_input, .f = max)
    mins <- map_dbl(.x = df_input, .f = min)
    train_input_scale <- scale(train_input, center = mins, scale = maxs - mins)
  }
  if(is.matrix(train_input_scale)){
    df_input <- as_tibble(train_input_scale)
    matrix_input <- train_input_scale
  } else{
    df_input <- train_input_scale
    matrix_input <- as.matrix(train_input_scale)
  }
  
  # Machines
  lasso_machine <- function(x, lambda0){
    if(is.null(lambda)){
      cv <- cv.glmnet(matrix_train_x1, train_y1, alpha = 1, lambda = 10^(seq(-3,2,length.out = 50)))
      mod <- glmnet(matrix_train_x1, train_y1, alpha = 1, lambda = cv$lambda.min)
    } else{
      mod <- glmnet(matrix_train_x1, train_y1, alpha = 1, lambda = lambda0)
    }
    res <- predict.glmnet(mod, newx = x)
    return(list(pred = res,
                model = mod))
  }
  ridge_machine <- function(x, lambda0){
    if(is.null(lambda)){
      cv <- cv.glmnet(matrix_train_x1, train_y1, alpha = 0, lambda = 10^(seq(-3,2,length.out = 50)))
      mod <- glmnet(matrix_train_x1, train_y1, alpha = 0, lambda = cv$lambda.min)
    } else{
      mod <- glmnet(matrix_train_x1, train_y1, alpha = 0, lambda = lambda0)
    }
    res <- predict.glmnet(mod, newx = x)
    return(list(pred = res,
                model = mod))
  }
  tree_machine <- function(x, pa = NULL) {
    mod <- tree(as.formula(paste("train_y1~", 
                                 paste(input_names, sep = "", collapse = "+"), 
                                 collapse = "", 
                                 sep = "")), 
                data = df_train_x1)
    res <- as.vector(predict(mod, x))
    return(list(pred = res,
                model = mod))
  }
  knn_machine <- function(x, k0) {
    mod <- knn.reg(train = matrix_train_x1, test = x, y = train_y1, k = k0)
    res = mod$pred
    return(list(pred = res,
                model = k0))
  }
  RF_machine <- function(x, ntree0) {
    if(is.null(mtry)){
      mod <- randomForest(x = df_train_x1, y = train_y1, ntree = ntree0)
    }else{
      mod <- randomForest(x = df_train_x1, y = train_y1, ntree = ntree0, mtry = mtry)
    }
    res <- as.vector(predict(mod, x))
    return(list(pred = res,
                model = mod))
  }
  xgb_machine = function(x, nrounds_xgb0){
    mod <- xgboost(data = matrix_train_x1, 
                   label = train_y1, 
                   eta = eta_xgb,
                   nrounds = nrounds_xgb0,
                   objective = "reg:squarederror",
                   early_stopping_rounds = early_stop_xgb,
                   max_depth = max_depth_xgb,
                   verbose = 0)
    res <- predict(mod, x)
    return(list(pred = res,
                model = mod))
  }
  
  # All machines
  all_machines <- list(lasso = lasso_machine, 
                       ridge = ridge_machine, 
                       knn = knn_machine, 
                       tree = tree_machine, 
                       rf = RF_machine,
                       xgb = xgb_machine)
  # All parameters
  all_parameters <- list(lasso = lambda, 
                         ridge = lambda, 
                         knn = k, 
                         tree = 1, 
                         rf = ntree,
                         xgb = nrounds_xgb)
  if(is.null(machines)){
    mach <- c("lasso", "ridge", "knn", "tree", "rf", "xgb")
  }else{
    mach <- machines
  }
  # Extracting data
  M <- length(mach)
  size_D1 <- floor(splits*input_size[1])
  id_D1 <- logical(input_size[1])
  id_D1[sample(input_size[1], size_D1)] <- TRUE
  
  df_train_x1 <- df_input[id_D1,]
  matrix_train_x1 <- matrix_input[id_D1,]
  train_y1 <- train_response[id_D1]
  df_train_x2 <- df_input[!id_D1,]
  matrix_train_x2 <- matrix_input[!id_D1,]
  
  # Function to extract df and model from 'map' function
  extr_df <- function(x, id){
    return(tibble("r_{{id}}":= as.vector(pred_m[[x]]$pred)))
  }
  extr_mod <- function(x, id){
    return(pred_m[[x]]$model)
  }
  
  pred_D2 <- c()
  all_mod <- c()
  cat("\n* Building basic machines ...\n")
  cat("\t~ Progress:")
  for(m in 1:M){
    if(mach[m] %in% c("tree", "rf")){
      x0_test <- df_train_x2
    } else {
      x0_test <- matrix_train_x2
    }
    if(is.null(all_parameters[[mach[m]]])){
      para_ <- 1
    }else{
      para_ <- all_parameters[[mach[m]]]
    }
    pred_m <-  map(para_, 
                   .f = ~ all_machines[[mach[m]]](x0_test, .x))
    tem0 <- imap_dfc(.x = 1:length(para_), 
                     .f = extr_df)
    tem1 <- imap(.x = 1:length(para_), 
                 .f = extr_mod)
    names(tem0) <- names(tem1) <- paste0(mach[m], 1:length(para_))
    pred_D2 <- bind_cols(pred_D2, as_tibble(tem0))
    all_mod[[mach[m]]] <- tem1
    cat(" ... ", round(m/M, 2)*100L,"%", sep = "")
  }
  max_M <- min_M <- NULL
  pred_D2_ <- pred_D2
  if(scale_machine){
    max_M <- map_dbl(.x = pred_D2, .f = max)
    min_M <- map_dbl(.x = pred_D2, .f = min)
    pred_D2 <- scale(pred_D2, center = min_M, scale = max_M - min_M)
  }
  return(list(fitted_remain = pred_D2,
              models = all_mod,
              id2 = !id_D1,
              train_data = list(train_input = train_input_scale, 
                                train_response = train_response,
                                predict_remain_org = pred_D2_,
                                min_machine = min_M,
                                max_machine = max_M,
                                min_input = mins,
                                max_input = maxs)))
}

Example.1: In this example, the method is implemented on Boston data of MASS library. The basic machines “rf”, “knn” and “xgb” are built on the first part of the training data (\(\mathcal{D}_{k}\)), and the Root Mean Square Errors (RMSE) evaluated on the second part of the training data (\(\mathcal{D}_{\ell}\)) used for aggregation) are reported.


pacman::p_load(MASS)
df <- Boston
basic_machines <- generateMachines_Mix(train_input = df[,1:13],
                 train_response = df[,14],
                 scale_input = TRUE,
                 machines = c("rf", "knn", "xgb"),
                 basicMachineParam = setBasicParameter_Mix(lambda = 1:10/10, 
                                                ntree = 10:20 * 25,
                                                k = c(2:10)))

* Building basic machines ...
    ~ Progress: ... 33% ... 67% ... 100%
basic_machines$train_data$predict_remain_org %>%
  sweep(1, df[basic_machines$id2, "medv"]) %>%
  .^2 %>%
  colMeans %>%
  t %>%
  sqrt %>%
  as_tibble

3 Optimization algorithm

This part provides functions to approximate the key parameters \((\alpha,\beta)\in(\mathbb{R}_+^*)^2\) of the aggregation. Two important optimization methods are implemented: gradient descent algorithm (grad) and grid search (grid).

3.1 Gradient descent algorithm

3.1.1 Function : setGradParameter_Mix

This function allows us to set the values of parameters needed to process the gradient descent algorithm to approximate the hyperparameter of the method.

  • Argument:

    • val_init : a 2D vector of initial values of gradient descent iteration. By default, val_init = NULL and the algorithm will select the best value (with smallest cost function) among alpha_range and beta_range values of parameters.
    • rate : the 2D real-valued vector or a string of learning rate in gradent descent algorithm. By default, rate = NULL (or “auto”) and the value coef_auto = c(1, 1) will be used. It can also be a functional rate, which is a string, an element of {“logarithm”, “sqrtroot”, “linear”, “polynomial”, “exponential”}. Each rate is defined according to coef_ type arguments bellow.
    • alpha_range : a range vector of \(\alpha\) values to be considered as the initial value in gradient step. By default, alpha_range = seq(0.0001, 10, length.out = 5).
    • beta_range : a range vector of \(\beta\) values to be considered as the initial value in gradient step. By default, beta_range = seq(0.0001, 50, length.out = 5).
    • max_iter : maximum itertaion of gradient descent algorithm. By default, max_iter = 100.
    • print_step : a logical value controlling whether to print the result of each gradient step or not in order to keep track of the algorithm. By default, print_step = TRUE.
    • print_result : a logical value controlling whether to print the result of the algorithm or not. By default, print_result = TRUE.
    • figure : a logical value controlling whether to plot a graphic of the result or not. By default, figure = TRUE.
    • coef_auto : the constant learning rate when rate = NULL. By default, coef_auto = c(1, 1).
    • coef_log : the coefficinet multiplying to the logarithmic increment of the learning rate, i.e., rate \(=\) coef_log\(\times \log(1+t)\) where \(t\) is the numer number of iteration. By default, coef_log = 1.
    • coef_sqrt : the coefficinet multiplying to the square root increment of the learning rate, i.e., rate \(=\) coef_sqrt\(\times \sqrt{t}\). By default, coef_sqrt = 1.
    • coef_lm : the coefficinet multiplying to the linear increment of the learning rate, i.e., rate \(=\) coef_lm\(\times t\). By default, coef_lm = 1.
    • deg_poly : the degree of the polynomial increment of the learning rate, i.e., rate \(=t^{\texttt{deg_poly}}\). By default, deg_poly = 2.
    • base_exp : the base of the exponential increment of the learning rate, i.e., rate \(=\) base_exp\(^t\). By default, base_exp = 1.5.
    • axes : names of \(x,y\) and \(z\)-axis respectively. By default, axes = c("alpha", "beta", "L1 norm of gradient").
    • title : the title of the plot. By default, title = NULL and the default title is Gradient step.
    • threshold : the threshold to stop the algorithm what relative change is smaller than this value. By default, threshold = 1e-10.
  • Value:

    This function returns a list of all the parameters given in its arguments.

setGradParameter_Mix <- function(val_init = NULL,
                             rate = NULL, 
                             alpha_range = seq(0.0001, 10, length.out = 5),
                             beta_range = seq(0.0001, 50, length.out = 5),
                             max_iter = 100, 
                             print_step = TRUE, 
                             print_result = TRUE,
                             figure = TRUE, 
                             coef_auto = c(1, 1),
                             coef_log = 1,
                             coef_sqrt = 1,
                             coef_lm = 1,
                             deg_poly = 2,
                             base_exp = 1.5,
                             axes = c("alpha", "beta", "L1 norm of gradient"),
                             title = NULL,
                             threshold = 1e-10) {
  return(
    list(val_init = val_init,
      rate = rate,
      alpha_range = alpha_range,
      beta_range = beta_range,
      max_iter = max_iter,
      print_step = print_step,
      print_result = print_result,
      figure = figure,
      coef_auto = coef_auto,
      coef_log = coef_log,
      coef_sqrt = coef_sqrt,
      coef_lm = coef_lm,
      deg_poly = deg_poly,
      base_exp = base_exp,
      axes = axes,
      title = title,
      threshold = threshold
    )
  )
}

3.1.2 Function : gradOptimizer_Mix

This function performs gradient descent algorithm to approximate the minimizer of any given functions (convex or locally convex around its optimizer).

  • Argument:

    • obj_fun : the objective function for which its minimizer is to be estimated. It should take a 2D vector as an input.
    • setParameter : the control of gradient descent parameters which should be the output of function setGradParameter_Mix() defined earlier.
    • silent : a logical value to silent all the messages or information to be printed during the process of the method. By default, silent = FALSE.
  • Value:

    This function returns a list of the following objects:

    • opt_param : the observed value of the minimizer.
    • opt_error : the value of the optimal risk.
    • all_grad : the matrix of all the gradients collected during the walk of the algorithm (by row).
    • all_param : the matrix of all parameters collected during the walk of the algorithm (by row).
    • run_time : the running time of the algorithm.
gradOptimizer_Mix <- function(obj_fun,
                          setParameter = setGradParameter_Mix(),
                          silent = FALSE) {
  start.time <- Sys.time()
  # Optimization step:
  # ==================
  spec_print <- function(x, dig = 5) return(ifelse(x > 1e-6, 
                                          format(x, digit = dig, nsmall = dig), 
                                          format(x, scientific = TRUE, digit = dig, nsmall = dig)))
  collect_val <- c()
  gradients <- c()
  if (is.null(setParameter$val_init)){
    range_alp <- rep(setParameter$alpha_range, length(setParameter$beta_range))
    range_bet <- rep(setParameter$beta_range, length(setParameter$alpha_range))
    tem <- map2_dbl(.x = range_alp,
                    .y = range_bet,
                    .f = ~ obj_fun(c(.x, .y)))
    id0 <- which.min(tem)
    val <- val0 <- c(range_alp[id0], range_bet[id0])
    grad_ <- pracma::grad(
      f = obj_fun,
      x0 = val0,
      heps = .Machine$double.eps ^ (1 / 3))
  } else{
    val <- val0 <- setParameter$val_init
    grad_ <- pracma::grad(
      f = obj_fun, 
      x0 = val0, 
      heps = .Machine$double.eps ^ (1 / 3))
  }
  if(setParameter$print_step & !silent){
    cat("\n* Gradient descent algorithm ...")
    cat("\n  Step\t|  alpha    ;  beta   \t|  Gradient (alpha ; beta)\t|  Threshold \n")
    cat(" ", rep("-", 80), sep = "")
    cat("\n   0 \t| ", spec_print(val0[1])," ; ", spec_print(val0[2]),
        "\t| ", spec_print(grad_[1], 6), " ; ", spec_print(grad_[2], 5), 
        " \t| ", setParameter$threshold, "\n")
    cat(" ", rep("-",80), sep = "")
  }
  if (is.numeric(setParameter$rate)){
    lambda0 <- setParameter$rate / abs(grad_)
    rate_GD <- "auto"
  } else{
    r0 <- setParameter$coef_auto / abs(grad_)
    # Rate functions
    rate_func <- list(auto = r0, 
                      logarithm = function(i)  setParameter$coef_log * log(2 + i) * r0,
                      sqrtroot = function(i) setParameter$coef_sqrt * sqrt(i) * r0,
                      linear = function(i) setParameter$coef_lm * (i) * r0,
                      polynomial = function(i) i ^ setParameter$deg_poly * r0,
                      exponential = function(i) setParameter$base_exp ^ i * r0)
    rate_GD <- match.arg(setParameter$rate, 
                         c("auto", 
                          "logarithm", 
                          "sqrtroot", 
                          "linear", 
                          "polynomial", 
                          "exponential"))
    lambda0 <- rate_func[[rate_GD]]
  }
  i <- 0
  grad0 <- 10*grad_ 
  if (is.numeric(setParameter$rate) | rate_GD == "auto") {
    while (i < setParameter$max_iter) {
      if(any(is.na(grad_))){
        val0 <- c(runif(1, val0[1]*0.99, val0[1]*1.01), 
                  runif(1, val0[2]*0.99, val0[2]*1.01)) 
        grad_ = pracma::grad(
          f = obj_fun, 
          x0 = val0, 
          heps = .Machine$double.eps ^ (1 / 3)
       )
      }
      val <- val0 - lambda0 * grad_
      if (any(val < 0)){
        val[val < 0] <- val0[val < 0]/2
        lambda0[val < 0] <- lambda0[val < 0] / 2
      }
      if(i > 5){
        sign_ <- sign(grad_) != sign(grad0)
        if(any(sign_)){
          lambda0[sign_] = lambda0[sign_]/2
        }
      }
      relative <- sum(abs(val - val0)) / sum(abs(val0))
      test_threshold <- max(relative, sum(abs(grad_ - grad0)))
      if (test_threshold > setParameter$threshold){
        val0 <- val
        grad0 <- grad_
      } else{
        break
      }
      grad_ <- pracma::grad(
        f = obj_fun, 
        x0 = val0, 
        heps = .Machine$double.eps ^ (1 / 3)
      )
      i <- i + 1
      if(setParameter$print_step & !silent){
        cat("\n  ", i, "\t| ", spec_print(val[1], 4), " ; ", spec_print(val[2], 4), 
            "\t| ", spec_print(grad_[1], 5), " ; ", spec_print(grad_[2], 5), 
            "\t| ", test_threshold, "\r")
      }
      collect_val <- rbind(collect_val, val)
      gradients <- rbind(gradients, grad_)
    }
  }
  else{
    while (i < setParameter$max_iter) {
      if(any(is.na(grad_))){
        val0 <- c(runif(1, val0[1]*0.99, val0[1]*1.01), 
                  runif(1, val0[2]*0.99, val0[2]*1.01)) 
        grad_ = pracma::grad(
          f = obj_fun, 
          x0 = val0, 
          heps = .Machine$double.eps ^ (1 / 3)
       )
      }
      val <- val0 - lambda0(i) * grad_
      if (any(val < 0)){
        val[val < 0] <- val0[val < 0]/2
        r0[val < 0] <- r0[val < 0] / 2
      }
      if(i > 5){
        sign_ <- sign(grad_) != sign(grad0)
        if(any(sign_)){
          r0[sign_] <- r0[sign_] / 2
        }
      }
      relative <- sum(abs(val - val0)) / sum(abs(val0))
      test_threshold <- max(relative, sum(abs(grad_ - grad0)))
      if (test_threshold > setParameter$threshold){
        val0 <- val
        grad0 <- grad_
      }else{
        break
      }
      grad_ <- pracma::grad(
        f = obj_fun, 
        x0 = val0, 
        heps = .Machine$double.eps ^ (1 / 3)
      )
      if(setParameter$print_step & !silent){
        cat("\n  ", i, "\t| ", spec_print(val[1], 4), " ; ", spec_print(val[2], 4), 
            "\t| ", spec_print(grad_[1], 5), " ; ", spec_print(grad_[2], 5), 
            "\t| ", test_threshold, "\r")
      }
      i <- i + 1
      collect_val <- rbind(collect_val, val)
      gradients <- rbind(gradients, grad_)
    }
  }
  opt_ep <- val
  opt_risk <- obj_fun(opt_ep)
  if(setParameter$print_step & !silent){
    cat(rep("-", 80), sep = "")
    if(sum(abs(grad_)) == 0){
      cat("\n Stopped| ", spec_print(val[1], 4), " ; ", spec_print(val[2], 4), 
        "\t|\t ", 0, 
        "\t\t| ", test_threshold)
    }else{
      cat("\n Stopped| ", spec_print(val[1], 4), " ; ", spec_print(val[2], 4), 
        "\t| ", spec_print(grad_[1]), " ; ", spec_print(grad_[2]), 
        "\t| ", test_threshold)
    } 
  }
  if(setParameter$print_result & !silent){
    cat("\n ~ Observed parameter: (alpha, beta) = (", opt_ep[1], ", ", opt_ep[2], ") in",i, "itertaions.")
  }
  if (setParameter$figure) {
    if(is.null(setParameter$title)){
      tit <- paste("<b> L1 norm of gradient as a function of</b> (",
      setParameter$axes[1],",", 
      setParameter$axes[2], 
      ")")
    } else{
      tit <- setParameter$title
    }
    siz = length(collect_val[,1])
    fig <- tibble(x = collect_val[,1],
           y = collect_val[,2],
           z = apply(abs(gradients), 1, sum)) %>%
      plot_ly(x = ~x, y = ~y) %>% 
      add_trace(z = ~z,
                type = "scatter3d",
                mode = "lines",
                line = list(width = 6, 
                            color = ~z, 
                            colorscale = 'Viridis'),
                name = "Gradient step") %>%
      add_trace(x = c(opt_ep[1], opt_ep[1]),
                y = c(0, opt_ep[2]),
                z = ~c(z[siz], z[siz]),
                type = "scatter3d",
                mode = 'lines+markers',
                line = list( 
                  width = 2,
                  color = "#5E88FC", 
                  dash = TRUE),
                marker = list(
                  size = 4,
                  color = ~c("#5E88FC", "#38DE25")),
                name = paste("Optimal",setParameter$axes[1])) %>%
      add_trace(x = c(0, opt_ep[1]),
                y = c(opt_ep[2], opt_ep[2]),
                z = ~c(z[siz], z[siz]),
                type = "scatter3d",
                mode = 'lines+markers',
                line = list( 
                  width = 2,
                  color = "#F31536", 
                  dash = TRUE),
                marker = list(
                  size = 4,
                  color = ~c("#F31536", "#38DE25")),
                name = paste("Optimal",setParameter$axes[2]))  %>%
      add_trace(x = opt_ep[1],
                y = opt_ep[2],
                z = ~z[siz],
                type = "scatter3d",
                mode = 'markers',
                marker = list(
                  size = 5,
                  color = "#38DE25"),
                name = "Optimal point") %>%
      layout(title = list(text = tit,
                          x = 0.075, 
                          y = 0.925,
                          font = list(family = "Verdana",
                                      color = "#5E88FC")),
             legend = list(x = 100, y = 0.5),
             scene = list(
               xaxis = list(title = setParameter$axes[1]),
               yaxis = list(title = setParameter$axes[2]),
               zaxis = list( title = setParameter$axes[3])))
    fig %>% print
  }
  end.time = Sys.time()
  return(list(
    opt_param = opt_ep,
    opt_error = opt_risk,
    all_grad = gradients,
    all_param = collect_val,
    run_time = difftime(end.time, 
                        start.time, 
                        units = "secs")[[1]]
  ))
}

Example.2: Approximate \[(x^*,y^*)=\text{arg}\min_{x,y)\in\mathbb{R}^2}f(x,y),\] where \[f(x,y)=(x-1)^2(1+\sin^2(2.5(x-1)))+(y-1)^2(1+\sin^2(2.5(y-1)))\] Note that argument val_init is crucial since \(f\) is not convex.


object_func <- function(x) sum((x-1)^2*(1+sin(2.5*(x-1))^2))
p <- tibble::tibble(x = rep(seq(-4,6, length.out = 30),30), 
                      y = rep(seq(-3,7, length.out = 30), each = 30)) %>%
  mutate(z = map2_dbl(.x = x, .y = y, .f = ~ object_func(c(.x,.y)))) %>%
  plot_ly(x = ~x, y = ~y, z = ~z, type = "mesh3d") %>%
  add_trace(x = 1,
            y = 1,
            z = 0,
            type = "scatter3d", mode = "markers",
            name = "Optimal point") %>%
  layout(title = "Cost function")
show(p)
gd <- gradOptimizer_Mix(obj_fun = object_func,
                  setParameter = setGradParameter_Mix(val_init = c(2.4, 3.5),
                                                rate = "log",
                                                coef_auto = c(0.7, 0.7),
                                                print_step = TRUE,
                                                figure = TRUE,
                                                axes = c("x", "y")))

* Gradient descent algorithm ...
  Step  |  alpha    ;  beta     |  Gradient (alpha ; beta)  |  Threshold 
 --------------------------------------------------------------------------------
   0    |  2.40000  ;  3.50000  |  6.363771  ;  3.96922     |  1e-10 
 --------------------------------------------------------------------------------
   0    |  1.9148  ;  3.0148    |  0.79847  ;  1.51397  |  92.99696 

   1    |  1.8183  ;  2.7215    |  1.56931  ;  11.74586     |  8.020555 

   2    |  1.5790  ;  1.3607    |  2.50307  ;  1.48199  |  11.00273 

   3    |  1.1359  ;  0.9401    |  0.33091  ;  -1.2513e-01  |  11.19763 

   4    |  1.0707  ;  0.9796    |  0.14999  ;  -4.0947e-02  |  3.779282 

   5    |  1.0385  ;  0.9937    |  0.078525  ;  -1.2638e-02     |  0.2651093 

   6    |  1.0206  ;  0.9983    |  0.041395  ;  -3.3626e-03     |  0.09977258 

   7    |  1.0106  ;  0.9996    |  0.021197  ;  -7.565e-04  |  0.04640585 

   8    |  1.0052  ;  0.9999    |  0.010433  ;  -1.421e-04  |  0.02280361 

   9    |  1.0025  ;  1.0000    |  0.0049263  ;  -2.1917e-05    |  0.01137799 

   10   |  1.0011  ;  1.0000    |  0.0022329  ;  -2.7076e-06    |  0.005627254 

   11   |  1.0005  ;  1.0000    |  0.00097291  ;  -2.5805e-07   |  0.002712624 

   12   |  1.0002  ;  1.0000    |  0.00040805  ;  -1.7849e-08   |  0.001262474 

   13   |  1.0001  ;  1.0000    |  0.00016495  ;  -8.0023e-10   |  0.0005650944 

   14   |  1.0000  ;  1.0000    |  6.4339e-05  ;  -1.7661e-11   |  0.000243119 

   15   |  1.0000  ;  1.0000    |  2.4237e-05  ;  -1.2212e-14   |  0.0001006146 

   16   |  1.0000  ;  1.0000    |  8.8254e-06  ;  3.3307e-16    |  4.010183e-05 

   17   |  1.0000  ;  1.0000    |  3.1086e-06  ;  -1.1102e-16   |  1.541137e-05 

   18   |  1.0000  ;  1.0000    |  1.0599e-06  ;  -1.1102e-16   |  5.716742e-06 

   19   |  1.0000  ;  1.0000    |  3.5e-07  ;  -1.1102e-16  |  2.048728e-06 

   20   |  1.0000  ;  1.0000    |  1.1199e-07  ;  -1.1102e-16   |  7.09896e-07 

   21   |  1.0000  ;  1.0000    |  3.4741e-08  ;  -1.1102e-16   |  2.380033e-07 

   22   |  1.0000  ;  1.0000    |  1.0452e-08  ;  -1.1102e-16   |  7.72527e-08 

   23   |  1.0000  ;  1.0000    |  3.0504e-09  ;  -1.1102e-16   |  2.428952e-08 

   24   |  1.0000  ;  1.0000    |  8.6399e-10  ;  -1.1102e-16   |  7.401194e-09 

   25   |  1.0000  ;  1.0000    |  2.3754e-10  ;  -1.1102e-16   |  2.18645e-09 

   26   |  1.0000  ;  1.0000    |  6.3406e-11  ;  -1.1102e-16   |  6.264504e-10 

   27   |  1.0000  ;  1.0000    |  1.6436e-11  ;  -1.1102e-16   |  1.741309e-10 
--------------------------------------------------------------------------------
 Stopped|  1.0000  ;  1.0000    |  1.6436e-11  ;  -1.1102e-16   |  4.697043e-11
 ~ Observed parameter: (alpha, beta) = ( 1 ,  1 ) in 28 itertaions.

3.2 Grid search algorithm

3.2.1 Function : setGridParameter_Mix

This function allows us to set the values of parameters needed to process the grid search algorithm to approximate the hyperparameter of the method.

  • Argument:

    • min_alpha : mininum value of \(\alpha\) in the grid. By defualt, min_alpha = 1e-5.
    • max_alpha : maxinum value of \(\alpha\) in the grid. By defualt, max_alpha = 10.
    • min_beta : mininum value of \(\beta\) in the grid. By defualt, min_beta = 1e-5.
    • max_beta : maximum value of \(\beta\) in the grid. By defualt, max_alpha = 50.
    • n_alpha, n_beta = 30 : the number of \(\alpha\) and \(\beta\) respectively in the grid. By defualt, n_alpha = n_beta = 30.
    • parameters : the list of parameter \(\alpha\) and \(\beta\) in case non-uniform grid is considered. It should be a list of two vectors containing the values of \(\alpha\) and \(\beta\) respectively. By default, parameters = NULL and the default uniform grid is used.
    • axes : names of \(x,y\) and \(z\)-axis respectively. By default, axes = c("alpha", "beta", "Risk").
    • title : the title of the plot. By default, title = NULL and the default title is Cross-validation risk VS \((\alpha, \beta)\).
    • print_result : a logical value specifying whether to print the observed result or not.
    • figure : a logical value specifying whether to plot the graphic of cross-validation error or not.
    • silent : a logical value to silent all the messages or information to be printed during the process of the method. By default, silent = FALSE.
  • Value:

    This function returns a list of all the parameters given in its arguments.

setGridParameter_Mix <- function(min_alpha = 1e-5,
                                 max_alpha = 10,
                                 min_beta = 0.1,
                                 max_beta = 50,
                                 n_alpha = 30,
                                 n_beta = 30,
                                 parameters = NULL,
                                 axes = c("alpha", "beta", "Risk"),
                                 title = NULL,
                                 print_result = TRUE,
                                 figure = TRUE,
                                 silent = FALSE){
  return(list(min_alpha = min_alpha,
              max_alpha = max_alpha,
              min_beta = min_beta,
              max_beta = max_beta,
              n_alpha = n_alpha,
              n_beta = n_beta,
              axes = axes,
              title = title,
              parameters = parameters,
              print_result = print_result,
              figure = figure))
}

3.2.2 Function : gridOptimizer_Mix

This function performs grid search algorithm in approximating the values of parameters \(\alpha, \beta\) of the method.

  • Argument:

    • obj_fun : the objective function for which its minimizer is to be estimated. It should be a univarate function of real positive variables.
    • setParameter : the control of grid search algorithm parameters which should be the function setGridParameter_Mix() defined above.
    • silent : a logical value to silent all the messages or information to be printed during the process of the method. By default, silent = FALSE.
  • Value:

    This function returns a list of the following objects:

    • opt_param : the observed value of the minimizer.
    • opt_error : the value of optimal risk.
    • all_risk : the vector of all the errors evaluated at all the values of the considered parameters.
    • run_time : the running time of the algorithm.
gridOptimizer_Mix <- function(obj_func,
                         setParameter = setGridParameter_Mix(),
                         silent = FALSE){
  t0 <- Sys.time()
  if(is.null(setParameter$parameters)){
    param_list <- list(alpha =  rep(seq(setParameter$min_alpha, 
                                        setParameter$max_alpha,
                                        length.out = setParameter$n_alpha), 
                                    setParameter$n_beta),
                       beta =  rep(seq(setParameter$min_beta, 
                                       setParameter$max_beta,
                                       length.out = setParameter$n_beta),
                                   each = setParameter$n_alpha))
  } else{
    param_list <- list(alpha = rep(setParameter$parameters[[1]], 
                                   length(setParameter$parameters[[2]])),
                       beta = rep(setParameter$parameters[[2]], 
                                   each = length(setParameter$parameters[[1]])))
  }
  risk <- map2_dbl(.x = param_list$alpha,
                      .y = param_list$beta,
                      .f = ~ obj_func(c(.x, .y)))
  id_opt <- which.min(risk)
  opt_ep <- c(param_list$alpha[id_opt], param_list$beta[id_opt])
  opt_risk <- risk[id_opt]
  if(setParameter$print_result & !silent){
    cat("\n* Grid search algorithm...", "\n ~ Observed parameter: (alpha, beta) = (", opt_ep[1], 
        ", ", 
        opt_ep[2], ")", 
        sep = "")
  }
  if(setParameter$figure){
    if(is.null(setParameter$title)){
      tit <- paste("<b> Cross-validation risk as a function of</b> (",
                   setParameter$axes[1],",", 
                   setParameter$axes[2],
                   ")")
    } else{
      tit <- setParameter$title
    }
    fig <- tibble(alpha = param_list$alpha, 
                  beta = param_list$beta,
                  risk = risk) %>%
      plot_ly(x = ~alpha, y = ~beta, z = ~risk, type = "mesh3d") %>%
      add_trace(x = c(opt_ep[1], opt_ep[1]),
                y = c(0, opt_ep[2]),
                z = c(opt_risk, opt_risk),
                type = "scatter3d",
                mode = 'lines+markers',
                line = list( 
                  width = 2,
                  color = "#5E88FC", 
                  dash = TRUE),
                marker = list(
                  size = 4,
                  color = ~c("#5E88FC", "#38DE25")),
                name = paste("Optimal",setParameter$axes[1])) %>%
      add_trace(x = c(0, opt_ep[1]),
                y = c(opt_ep[2], opt_ep[2]),
                z = c(opt_risk, opt_risk),
                type = "scatter3d",
                mode = 'lines+markers',
                line = list( 
                  width = 2,
                  color = "#F31536", 
                  dash = TRUE),
                marker = list(
                  size = 4,
                  color = ~c("#F31536", "#38DE25")),
                name = paste("Optimal",setParameter$axes[2]))  %>%
      add_trace(x = opt_ep[1],
                y = opt_ep[2],
                z = opt_risk,
                type = "scatter3d",
                mode = 'markers',
                marker = list(
                  size = 5,
                  color = "#38DE25"),
                name = "Optimal point") %>%
      layout(title = list(text = tit,
                          x = 0.075, 
                          y = 0.925,
                          font = list(family = "Verdana",
                                      color = "#5E88FC")),
             legend = list(x = 100, y = 0.5),
             scene = list(xaxis = list(title = setParameter$axes[1]),
                          yaxis = list(title = setParameter$axes[2]),
                          zaxis = list( title = setParameter$axes[3])))
    print(fig)
  }
  t1 <- Sys.time()
  return(list(opt_param = opt_ep,
              opt_error = opt_risk,
              all_risk = risk,
              run_time = difftime(t1, 
                        t0, 
                        units = "secs")[[1]])
  )
}

Example.2: Again with grid search.


grid <- gridOptimizer_Mix(obj_fun = object_func,
                     setParameter = setGridParameter_Mix(min_alpha = -2,
                                                     max_alpha = 4,
                                                     min_beta = -2,
                                                     max_beta = 4,
                                                     n_alpha = 150,
                                                     n_beta = 150,
                                                     axes = c("x", "y", "z"),
                                                     title = "z = f(x,y)",
                                                     figure = TRUE))

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (1.020134, 1.020134)

3.3 \(\kappa\)-cross validation lost function

Constructing aggregation method is equivalent to approximating the optimal value of parameter \((\alpha,\beta)\in(\mathbb{R}_+^*)^2\) introduced in section 1.1 by minimizing some lost function. In this study, we propose \(\kappa\)-fold cross validation lost function defined by

\[\begin{equation} \label{eq:kappa} \varphi^{\kappa}(\alpha, \beta)=\frac{1}{\kappa}\sum_{k=1}^{\kappa}\sum_{(x_j,y_j)\in F_k}(g_n(x_j)-y_j)^2 \end{equation}\] where

  • for any \(k=1,...,\kappa\), \(F_k\) denotes the \(k\)th validation fold.
  • \(g_n({\bf r}(x_j))\) is the prediction of \(x_j\) of \(F_k\), computed using the data points from the remaining part \({\cal D}_{\ell}-F_k\) by, \[g_n({\bf r}(x_j))=\frac{\sum_{(x_i,y_i)\in{\cal D}_{\ell}-F_k}y_iK_{\alpha, \beta}(x_j-x_i,{\bf r}(x_j)- {\bf r}(x_i))}{\sum_{(x_i,y_i)\in{\cal D}_{\ell}-F_k}K_{\alpha, \beta}(x_j-x_i,{\bf r}(x_j)- {\bf r}(x_i))}\]

3.4 Function: dist_matrix_Mix

This function computes different distances between data points of each training folds (\(\mathcal{D}_{\ell}-F_k\)) and the corresponding validation fold \(F_k\) for any \(k=1,\dots,\kappa\). The \(\kappa\) distance matrices (between input) \(D_k=(d[{\bf r}(x_i),{\bf r}(x_j)])_{i,j}\) for \(k=1,\dots,\kappa\), are computed, where the distance \(d\) is defined according to different types of kernel functions.

  • Argument:

    • basicMachines : the basic machine object, which is an output of generateMachines_Mix function.
    • n_cv : the number \(\kappa\) of cross-validation folds. By default, n_cv = 5.
    • kernel : the kernel function used for the aggregation, which is an element of {“gaussian”, “epanechnikov”, “biweight”, “triweight”, “triangular”, “naive”}. By default, kernel = "gaussian".
  • Value:

    This functions returns a list of the following objects:

    • dist_input : a list of sublists corresponding to kernel functions used for the aggregation. Each sublist contains n_cv numbers of matrices \(D_k=(d[{\bf r}(x_i),{\bf r}(x_j)])_{i,j}\), for \(k=1,\dots,\kappa\), containing distances between the data points in validation fold (along the columns) and the \(\kappa-1\) remaining folds of training data (along the rows). The type of distance matrices depends on the kernel function:
      • If kernel = naive, the distance matrices contain the maximum distance between data points, i.e., \[D_k=(\|{\bf r}(x_i)-{\bf r}(x_j)\|_{\max})_{i,j}\text{ for }k=1,\dots,\kappa.\]
      • If kernel = triangular, the distance matrices contain the \(L_1\) distance between data points, i.e., \[D_k=(\|{\bf r}(x_i)-{\bf r}(x_j)\|_1)_{i,j}\text{ for }k=1,\dots,\kappa.\]
      • Otherwise, the distance matrices contain the squared \(L_2\) distance between data points, i.e., \[D_k=(\|{\bf r}(x_i)-{\bf r}(x_j)\|_ 2^2)_{i,j}\text{ for }k=1,\dots,\kappa.\]
    • dist_machine : a list of n_cv data frames corresponding to \(\kappa\)-fold cross-validation. Each data frame contains the Hamming distances between data points in \(F_k\) (by column) and in \(\mathcal{D}_{\ell}-F_k\) (by row) for \(k=1,...,\kappa\).
    • id_shuffle : the shuffled indices in cross-validation.
    • n_cv : the number \(\kappa\) of cross-validation folds.
dist_matrix_Mix <- function(basicMachines,
                        n_cv = 5,
                        kernel = "gausian",
                        id_shuffle = NULL){
  n <- nrow(basicMachines$fitted_remain)
  n_each_fold <- floor(n/n_cv)
  # shuffled indices
  if(is.null(id_shuffle)){
    shuffle <- 1:(n_cv-1) %>%
    rep(n_each_fold) %>%
    c(., rep(n_cv, n - n_each_fold * (n_cv - 1))) %>%
    sample
  }else{
    shuffle <- id_shuffle
  }
  # the prediction matrix D_l
  df_mach <- as.matrix(basicMachines$fitted_remain)
  df_input <- as.matrix(basicMachines$train_data$train_input[basicMachines$id2,])
  if(! (kernel %in% c("naive", "triangular"))){
    pair_dist <- function(M, N){
      n_N <- dim(N)
      n_M <- dim(M)
      res_ <- 1:nrow(N) %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums((M - matrix(rep(N[id,], n_M[1]), ncol = n_M[2], byrow = TRUE))^2)))))
      return(res_)
    }
  }
  if(kernel == "triangular"){
    pair_dist <- function(M, N){
      n_N <- dim(N)
      n_M <- dim(M)
      res_ <- 1:nrow(N) %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums(abs(M - matrix(rep(N[id,], n_M[1]), ncol = n_M[2], byrow = TRUE)))))))
      return(res_)
    }
  }
  if(kernel == "naive"){
    pair_dist <- function(M, N){
      n_N <- dim(N)
      n_M <- dim(M)
      res_ <- 1:nrow(N) %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(apply(abs(M - matrix(rep(N[id,], n_M[1]), ncol = n_M[2], byrow = TRUE)), 1, max)))))
      return(res_)
    }
  }
  L1 <- 1:n_cv %>%
      map(.f = (\(x) pair_dist(df_input[shuffle != x,],
                                df_input[shuffle == x,])))
  L2 <- 1:n_cv %>%
      map(.f = (\(x) pair_dist(df_mach[shuffle != x,],
                                df_mach[shuffle == x,])))
  return(list(dist_input = L1,
              dist_machine = L2,
              id_shuffle = shuffle,
              n_cv = n_cv))
}

Example.3: The method dist_matrix_Mix is implemented on the obtained basic machines built in Example.1 with the corresponding Gaussian kernel function.


dis <- dist_matrix_Mix(basicMachines = basic_machines,
            n_cv = 3,
            kernel = "gaussian")
dis$n_cv
[1] 3

Example.4: From the distance matrix, we can compute the error corresponding to Gaussian kernel function, then use both of the optimization methods to approximate the smoothing paramter in this case.


# Gaussian kernel
gaussian_kern <- function(.ep = c(.05, 0.005),
                          .dist_matrix,
                          .train_response2,
                          .inv_sigma = sqrt(.5),
                          .alpha = 2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(exp(- (x[1]*D1+x[2]*D2)^(.alpha/2)*.inv_sigma^.alpha))
    y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0/colSums(tem0)
    return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = .dist_matrix$dist_machine[[.x]]))
  return(Reduce("+", temp))
}

# Kappa cross-validation error
cost_fun <- function(x,
                     .dist_matrix = dis,
                     .kernel_func = gaussian_kern,
                     .train_response2 = basic_machines$train_data$train_response[basic_machines$id2],
                     .inv_sigma = sqrt(.5),
                     .alpha = 2){
  return(.kernel_func(.ep = x,
                      .dist_matrix = .dist_matrix,
                      .train_response2 = .train_response2,
                      .inv_sigma = .inv_sigma,
                      .alpha = .alpha))
}
  • Gradient descent
# Optimization
opt_param_gd <- gradOptimizer_Mix(obj_fun = cost_fun,
                              setParameter = setGradParameter_Mix(rate = "linear",
                                                              print_step = TRUE,
                                                              print_result = TRUE,
                                                              figure = TRUE))

* Gradient descent algorithm ...
  Step  |  alpha    ;  beta     |  Gradient (alpha ; beta)  |  Threshold 
 --------------------------------------------------------------------------------
   0    |  2.50008  ;  12.57500     |  814.991255  ;  30.47029      |  1e-10 
 --------------------------------------------------------------------------------
   0    |  2.5001  ;  12.5750   |  814.99125  ;  30.47029   |  7609.154 

   1    |  2.4001  ;  12.4750   |  813.80540  ;  27.59426   |  0.01326693 

   2    |  2.2004  ;  12.2939   |  797.36331  ;  21.19662   |  4.061877 

   3    |  1.9069  ;  12.0852   |  739.40969  ;  10.92453   |  22.83973 

   4    |  1.5440  ;  11.9418   |  619.17286  ;  -1.4531e+00    |  68.22571 

   5    |  1.1641  ;  11.9656   |  460.99729  ;  -1.1812e+01    |  132.6145 

   6    |  0.8247  ;  12.1982   |  325.28348  ;  -1.7511e+01    |  168.5342 

   7    |  0.5453  ;  12.6005   |  234.77194  ;  -1.9486e+01    |  141.4133 

   8    |  0.3149  ;  13.1121   |  177.46993  ;  -1.9536e+01    |  92.48655 

   9    |  0.1189  ;  13.6891   |  139.73801  ;  -1.8766e+01    |  57.35153 

   10   |  0.05944  ;  14.3050  |  132.00474  ;  -1.721e+01     |  38.5016 

   11   |  0.02972  ;  14.9263  |  129.75559  ;  -1.5735e+01    |  9.289188 

   12   |  0.01486  ;  15.5460  |  130.16449  ;  -1.4399e+01    |  3.723879 

   13   |  0.00743  ;  16.1603  |  131.94900  ;  -1.3202e+01    |  1.745649 

   14   |  0.003715  ;  16.7669     |  134.48365  ;  -1.2132e+01    |  2.981046 

   15   |  0.001857  ;  17.3642     |  137.44866  ;  -1.1172e+01    |  3.604488 

   16   |  0.0009287  ;  17.9508    |  140.67331  ;  -1.0307e+01    |  3.924898 

   17   |  0.0004644  ;  18.5259    |  144.06119  ;  -9.5214e+00    |  4.090395 

   18   |  0.0002322  ;  19.0883    |  147.55338  ;  -8.8052e+00    |  4.173159 

   19   |  0.0001161  ;  19.6374    |  151.11012  ;  -8.1489e+00    |  4.208369 

   20   |  5.804e-05  ;  20.1723    |  154.70164  ;  -7.5449e+00    |  4.213066 

   21   |  2.902e-05  ;  20.6923    |  158.30370  ;  -6.9873e+00    |  4.195479 

   22   |  1.451e-05  ;  21.1967    |  161.89545  ;  -6.471e+00     |  4.159704 

   23   |  7.256e-06  ;  21.6852    |  165.45847  ;  -5.9921e+00    |  4.108007 

   24   |  3.628e-06  ;  22.1572    |  168.97637  ;  -5.5472e+00    |  4.041933 

   25   |  1.814e-06  ;  22.6123    |  172.43458  ;  -5.1334e+00    |  3.962818 

   26   |  9.069e-07  ;  23.0503    |  175.82033  ;  -4.7483e+00    |  3.871989 

   27   |  4.535e-07  ;  23.4711    |  179.12258  ;  -4.3899e+00    |  3.770819 

   28   |  2.267e-07  ;  23.8745    |  182.33196  ;  -4.0561e+00    |  3.660727 

   29   |  1.134e-07  ;  24.2605    |  185.44067  ;  -3.7453e+00    |  3.543141 

   30   |  5.668e-08  ;  24.6293    |  188.44245  ;  -3.4561e+00    |  3.419468 

   31   |  2.834e-08  ;  24.9809    |  191.33243  ;  -3.1869e+00    |  3.291055 

   32   |  1.417e-08  ;  25.3156    |  194.10700  ;  -2.9365e+00    |  3.159172 

   33   |  7.086e-09  ;  25.6336    |  196.76372  ;  -2.7036e+00    |  3.024992 

   34   |  3.543e-09  ;  25.9353    |  199.30119  ;  -2.4872e+00    |  2.889581 

   35   |  1.771e-09  ;  26.2210    |  201.71894  ;  -2.2861e+00    |  2.753895 

   36   |  8.857e-10  ;  26.4911    |  204.01730  ;  -2.0995e+00    |  2.618783 

   37   |  4.428e-10  ;  26.7460    |  206.19729  ;  -1.9264e+00    |  2.484988 

   38   |  2.214e-10  ;  26.9863    |  208.26057  ;  -1.7658e+00    |  2.353152 

   39   |  1.107e-10  ;  27.2123    |  210.20928  ;  -1.617e+00     |  2.223826 

   40   |  5.536e-11  ;  27.4246    |  212.04604  ;  -1.4793e+00    |  2.097477 

   41   |  2.768e-11  ;  27.6236    |  213.77381  ;  -1.3519e+00    |  1.974494 

   42   |  1.384e-11  ;  27.8099    |  215.39585  ;  -1.2341e+00    |  1.855197 

   43   |  6.919e-12  ;  27.9841    |  216.91569  ;  -1.1253e+00    |  1.739842 

   44   |  3.46e-12  ;  28.1466     |  218.33701  ;  -1.0249e+00    |  1.628634 

   45   |  1.73e-12  ;  28.2980     |  219.66366  ;  -9.3231e-01    |  1.521724 

   46   |  8.649e-13  ;  28.4387    |  220.89961  ;  -8.4705e-01    |  1.419224 

   47   |  4.325e-13  ;  28.5694    |  222.04887  ;  -7.6861e-01    |  1.321203 

   48   |  2.162e-13  ;  28.6904    |  223.11549  ;  -6.9652e-01    |  1.227699 

   49   |  1.081e-13  ;  28.8024    |  224.10355  ;  -6.3034e-01    |  1.138718 

   50   |  5.406e-14  ;  28.9059    |  225.01710  ;  -5.6965e-01    |  1.054241 

   51   |  2.703e-14  ;  29.0012    |  225.86013  ;  -5.1408e-01    |  0.9742259 

   52   |  1.351e-14  ;  29.0890    |  226.63662  ;  -4.6325e-01    |  0.8986095 

   53   |  6.757e-15  ;  29.1695    |  227.35042  ;  -4.1683e-01    |  0.8273103 

   54   |  3.379e-15  ;  29.2434    |  228.00535  ;  -3.7449e-01    |  0.7602317 

   55   |  1.689e-15  ;  29.3110    |  228.60507  ;  -3.3592e-01    |  0.6972661 

   56   |  8.447e-16  ;  29.3727    |  229.15318  ;  -3.0085e-01    |  0.6382913 

   57   |  4.223e-16  ;  29.4290    |  229.65312  ;  -2.69e-01  |  0.5831785 

   58   |  2.112e-16  ;  29.4802    |  230.10822  ;  -2.4013e-01    |  0.5317869 

   59   |  1.056e-16  ;  29.5267    |  230.52168  ;  -2.14e-01  |  0.4839742 

   60   |  5.279e-17  ;  29.5689    |  230.89655  ;  -1.9039e-01    |  0.4395896 

   61   |  2.64e-17  ;  29.6070     |  231.23573  ;  -1.6908e-01    |  0.3984804 

   62   |  1.32e-17  ;  29.6414     |  231.54202  ;  -1.499e-01     |  0.3604903 

   63   |  6.599e-18  ;  29.6724    |  231.81801  ;  -1.3266e-01    |  0.3254641 

   64   |  3.299e-18  ;  29.7002    |  232.06621  ;  -1.1719e-01    |  0.2932405 

   65   |  1.65e-18  ;  29.7252     |  232.28894  ;  -1.0333e-01    |  0.2636668 

   66   |  8.249e-19  ;  29.7476    |  232.48840  ;  -9.0944e-02    |  0.236585 

   67   |  4.124e-19  ;  29.7676    |  232.66664  ;  -7.9894e-02    |  0.2118453 

   68   |  2.062e-19  ;  29.7855    |  232.82559  ;  -7.0053e-02    |  0.1892938 

   69   |  1.031e-19  ;  29.8013    |  232.96703  ;  -6.1307e-02    |  0.1687891 

   70   |  5.155e-20  ;  29.8154    |  233.09262  ;  -5.355e-02     |  0.150188 

   71   |  2.578e-20  ;  29.8279    |  233.20391  ;  -4.6683e-02    |  0.1333514 

   72   |  1.289e-20  ;  29.8389    |  233.30230  ;  -4.0618e-02    |  0.1181491 

   73   |  6.444e-21  ;  29.8486    |  233.38910  ;  -3.5271e-02    |  0.1044558 

   74   |  3.222e-21  ;  29.8572    |  233.46552  ;  -3.0567e-02    |  0.09215096 

   75   |  1.611e-21  ;  29.8647    |  233.53264  ;  -2.6438e-02    |  0.08111911 

   76   |  8.055e-22  ;  29.8713    |  233.59148  ;  -2.2821e-02    |  0.07125375 

   77   |  4.028e-22  ;  29.8771    |  233.64293  ;  -1.9658e-02    |  0.06245214 

   78   |  2.014e-22  ;  29.8821    |  233.68783  ;  -1.69e-02  |  0.05461838 

   79   |  1.007e-22  ;  29.8865    |  233.72693  ;  -1.4499e-02    |  0.04766063 

   80   |  5.035e-23  ;  29.8903    |  233.76090  ;  -1.2413e-02    |  0.04149966 

   81   |  2.517e-23  ;  29.8936    |  233.79035  ;  -1.0606e-02    |  0.03605504 

   82   |  1.259e-23  ;  29.8965    |  233.81582  ;  -9.0427e-03    |  0.03125491 

   83   |  6.293e-24  ;  29.8989    |  233.83780  ;  -7.694e-03     |  0.027034 

   84   |  3.147e-24  ;  29.9010    |  233.85673  ;  -6.5332e-03    |  0.02333178 

   85   |  1.573e-24  ;  29.9029    |  233.87300  ;  -5.5354e-03    |  0.02009066 

   86   |  7.867e-25  ;  29.9044    |  233.88694  ;  -4.6806e-03    |  0.01726348 

   87   |  3.933e-25  ;  29.9058    |  233.89887  ;  -3.9491e-03    |  0.01479849 

   88   |  1.967e-25  ;  29.9069    |  233.90905  ;  -3.3252e-03    |  0.01265898 

   89   |  9.833e-26  ;  29.9079    |  233.91772  ;  -2.7938e-03    |  0.0108034 

   90   |  4.917e-26  ;  29.9087    |  233.92508  ;  -2.3419e-03    |  0.009199476 

   91   |  2.458e-26  ;  29.9094    |  233.93133  ;  -1.9594e-03    |  0.007817463 

   92   |  1.229e-26  ;  29.9100    |  233.93661  ;  -1.6358e-03    |  0.00662477 

   93   |  6.146e-27  ;  29.9105    |  233.94106  ;  -1.3628e-03    |  0.005603899 

   94   |  3.073e-27  ;  29.9109    |  233.94481  ;  -1.1325e-03    |  0.004729318 

   95   |  1.536e-27  ;  29.9113    |  233.94797  ;  -9.3954e-04    |  0.003982552 

   96   |  7.682e-28  ;  29.9116    |  233.95061  ;  -7.7748e-04    |  0.003344677 

   97   |  3.841e-28  ;  29.9118    |  233.95282  ;  -6.4208e-04    |  0.002804203 

   98   |  1.921e-28  ;  29.9120    |  233.95466  ;  -5.2921e-04    |  0.002344683 

   99   |  9.603e-29  ;  29.9122    |  233.95620  ;  -4.3526e-04    |  0.00195598 

   100  |  4.801e-29  ;  29.9123    |  233.95747  ;  -3.5694e-04    |  0.001628782 

   101  |  2.401e-29  ;  29.9125    |  233.95853  ;  -2.9228e-04    |  0.001353401 

   102  |  1.2e-29  ;  29.9126  |  233.95940  ;  -2.3896e-04    |  0.001120525 

   103  |  6.002e-30  ;  29.9126    |  233.96012  ;  -1.948e-04     |  0.0009266237 

   104  |  3.001e-30  ;  29.9127    |  233.96071  ;  -1.5808e-04    |  0.000765315 

   105  |  1.5e-30  ;  29.9128  |  233.96120  ;  -1.2857e-04    |  0.0006299149 

   106  |  7.502e-31  ;  29.9128    |  233.96160  ;  -1.0386e-04    |  0.0005159174 

   107  |  3.751e-31  ;  29.9128    |  233.96193  ;  -8.4484e-05    |  0.0004239985 

   108  |  1.876e-31  ;  29.9129    |  233.96219  ;  -6.7963e-05    |  0.0003448461 

   109  |  9.378e-32  ;  29.9129    |  233.96241  ;  -5.4671e-05    |  0.000283717 

   110  |  4.689e-32  ;  29.9129    |  233.96259  ;  -4.3707e-05    |  0.0002302479 

   111  |  2.344e-32  ;  29.9129    |  233.96273  ;  -3.477e-05     |  0.0001872923 

   112  |  1.172e-32  ;  29.9129    |  233.96284  ;  -2.7936e-05    |  0.0001508702 

   113  |  5.861e-33  ;  29.9129    |  233.96293  ;  -2.2229e-05    |  0.0001209815 

   114  |  2.931e-33  ;  29.9130    |  233.96301  ;  -1.7948e-05    |  9.837726e-05 

   115  |  1.465e-33  ;  29.9130    |  233.96307  ;  -1.4043e-05    |  7.802594e-05 

   116  |  7.326e-34  ;  29.9130    |  233.96312  ;  -1.0964e-05    |  6.450845e-05 

   117  |  3.663e-34  ;  29.9130    |  233.96315  ;  -8.9366e-06    |  5.106606e-05 

   118  |  1.832e-34  ;  29.9130    |  233.96319  ;  -6.984e-06     |  3.9426e-05 

   119  |  9.158e-35  ;  29.9130    |  233.96321  ;  -5.407e-06     |  3.296765e-05 

   120  |  4.579e-35  ;  29.9130    |  233.96323  ;  -4.0552e-06    |  2.568322e-05 

   121  |  2.289e-35  ;  29.9130    |  233.96324  ;  -3.3043e-06    |  2.050152e-05 

   122  |  1.145e-35  ;  29.9130    |  233.96326  ;  -2.7035e-06    |  1.509453e-05 

   123  |  5.724e-36  ;  29.9130    |  233.96326  ;  -2.1027e-06    |  1.261632e-05 

   124  |  2.862e-36  ;  29.9130    |  233.96327  ;  -1.6521e-06    |  9.98792e-06 

   125  |  1.431e-36  ;  29.9130    |  233.96328  ;  -1.1265e-06    |  8.110492e-06 

   126  |  7.155e-37  ;  29.9130    |  233.96328  ;  -8.2607e-07    |  6.83384e-06 

   127  |  3.577e-37  ;  29.9130    |  233.96329  ;  -8.2607e-07    |  4.280537e-06 

   128  |  1.789e-37  ;  29.9130    |  233.96329  ;  -5.2568e-07    |  3.15408e-06 

   129  |  8.943e-38  ;  29.9130    |  233.96329  ;  -5.2568e-07    |  3.379372e-06 

   130  |  4.472e-38  ;  29.9130    |  233.96329  ;  -3.0039e-07    |  1.802332e-06 

   131  |  2.236e-38  ;  29.9130    |  233.96329  ;  -2.2529e-07    |  2.553303e-06 

   132  |  1.118e-38  ;  29.9130    |  233.96329  ;  -7.5097e-08    |  1.126457e-06 

   133  |  5.589e-39  ;  29.9130    |  233.96330  ;  -7.5097e-08    |  8.260686e-07 

   134  |  2.795e-39  ;  29.9130    |  233.96330  ;  -2.2529e-07    |  6.007772e-07 

   135  |  1.397e-39  ;  29.9130    |  233.96330  ;  0e+00  |  2.252914e-07 

   136  |  6.987e-40  ;  29.9130    |  233.96330  ;  0e+00  |  1.501943e-06 
--------------------------------------------------------------------------------
 Stopped|  3.493e-40  ;  29.9130    |  233.96330  ;  0e+00  |  1.167866e-41
 ~ Observed parameter: (alpha, beta) = ( 3.493436e-40 ,  29.91299 ) in 137 itertaions.
  • Grid search
# Optimization
opt_param_grid <- gridOptimizer_Mix(obj_fun = cost_fun,
                                setParameter = setGridParameter_Mix(min_alpha = 0.0001,
                                                                max_alpha = 20,
                                                                min_beta = 0.001,
                                                                max_beta = 100,
                                                                n_beta = 30,
                                                                n_alpha = 30,
                                                                figure = TRUE))

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (1e-04, 31.03517)
cat('* Observed parameter:\n\t - Gradient descent\t: (alpha, beta) = (', 
    opt_param_gd$opt_param[1],",",opt_param_gd$opt_param[2], ")",
    '\t with error:', cost_fun(opt_param_gd$opt_param),
    '\n\t - Grid search\t\t: (alpha, beta) = (',opt_param_grid$opt_param[1],",",
    opt_param_grid$opt_param[2],
    ')  \t with error:', cost_fun(opt_param_grid$opt_param))
* Observed parameter:
     - Gradient descent : (alpha, beta) = ( 3.493436e-40 , 29.91299 )    with error: 4657.818 
     - Grid search      : (alpha, beta) = ( 1e-04 , 31.03517 )       with error: 4658.179

3.5 Fitting parameter

This function gathers the constructed machines and performs an optimization algorithm to approximate the smoothing parameter for the aggregation, using only the remaining part \({\cal D}_{\ell}\) of the training data.

  • Argument:

    • train_input, : a matrix or data frame of the training input data.
    • train_response : a vector of the corresponding response variable of the train_input.
    • machines : a vector of basic machines to be constructed. It must be a subset of {“lasso”, “ridge”, “knn”, “tree”, “rf”, “xgb”}. By default, machines = NULL and all the six types of basic machines are built.
    • scale_input : a logical value specifying whether or not to scale the input data before building the basic regression predictors. By default, scale_input = TRUE.
    • scale_machine : a logical value specifying whether or not to scale the predicted features given by all the basic regression predictors, for aggregation. By default, scale_machine = TRUE.
    • splits : the proportion of training data (the proportion of \({\cal D}_k\subset {\cal D}_n\)), used to build the basic machines. By default, splits = .5.
    • n_cv : the number of cross-validation folds, used to tune the smoothing parameter.
    • inv_sigma, alpha : the inverse normalized constant \(\sigma^{-1}>0\) and the exponent \(\alpha>0\) of exponential kernel: \(K(x)=e^{-\|x/\sigma\|^{\alpha}}\) for any \(x\in\mathbb{R}^d\). By default, inv_sigma =\(\sqrt{1/2}\) and alpha = 2 which corresponds to the Gaussian kernel.
    • kernels : the kernel function or vector of kernel functions used for the aggregation. By fault, kernels = "gaussian".
    • optimizeMethod : the optimization methods used to learn the smoothing parameter. By default, optimizeMethod = "grad" which stands for gradient descent algorithm, and if optimizeMethod = "grid", then the grid search algorithm is used. Note that this should be of the same size as the kernels argument, otherwise “grid” method will be used for all the kernel functions.
    • setBasicMachineParam : an option used to set the values of the parameters of the basic machines. setBasicParameter_Mix function should be fed to this argument.
    • setGradParam : an option used to set the values of the parameters of the gradient descent algorithm. setGradParameter_Mix function should be fed to it.
    • setGridParam : an option used to set the values of the parameters of the grid search algorithm. setGridParameter_Mix function should be fed to it.
    • silent : a logical value to silent all the messages or information to be printed during the process of the method. By default, silent = FALSE.
  • Value:

    This function returns a list of the following objects:

    • opt_parameters : the observed optimal parameters.
    • add_parameters : other aditional parameters such as scaling options, parameters of kernel functions and the optimization methods used.
    • basic_machines : the list of basic machine object.
fit_parameter_Mix <- function(train_input, 
                              train_response,
                              train_predictions = NULL,
                              machines = NULL, 
                              scale_input = TRUE,
                              scale_machine = TRUE,
                              splits = 0.5, 
                              n_cv = 5,
                              inv_sigma = sqrt(.5),
                              alp = 2,
                              kernels = "gaussian",
                              optimizeMethod = "grad",
                              setBasicMachineParam = setBasicParameter_Mix(),
                              setGradParam = setGradParameter_Mix(),
                              setGridParam = setGridParameter_Mix(),
                              silent = FALSE){
  kernels_lookup <- c("gaussian", "epanechnikov", "biweight", "triweight", "triangular", "naive")
  kernel_real <- kernels %>%
    sapply(FUN = function(x) return(match.arg(x, kernels_lookup)))
  if(is.null(train_predictions)){
    mach2 <- generateMachines_Mix(train_input = train_input,
                              train_response = train_response,
                              scale_input = scale_input,
                              scale_machine = scale_machine,
                              machines = machines,
                              splits = splits,
                              basicMachineParam = setBasicMachineParam,
                              silent = silent)
  }else{
    mach2 <- list(fitted_remain = train_predictions,
                  models = NULL,
                  id2 = rep(TRUE, nrow(train_input)),
                  train_data = list(train_input = train_input, 
                                    train_response = train_response,
                                    predict_remain_org = train_predictions,
                                    min_machine = NULL,
                                    max_machine = NULL,
                                    min_input = NULL,
                                    max_input = NULL))
    if(scale_machine){
      min_ <- map_dbl(train_predictions, .f = min)
      max_ <- map_dbl(train_predictions, .f = max)
      mach2$train_data$min_machine = min_
      mach2$train_data$max_amchine = max_
      mach2$fitted_remain <- scale(train_predictions, 
                                   center = min_, 
                                   scale = max_ - min_)
    }
    if(scale_input){
      min_ <- map_dbl(train_input, .f = min)
      max_ <- map_dbl(train_input, .f = max)
      mach2$train_data$min_input = min_
      mach2$train_data$max_input = max_
      mach2$train_data$train_input <- scale(train_input, 
                                            center = min_, 
                                            scale = max_ - min_)
    }
  }
  # distance matrix to compute loss function
  if_euclid <- FALSE
  id_euclid <- NULL
  n_ker <- length(kernels)
  dist_all <- list()
  id_shuf <- NULL
  for (k_ in 1:n_ker){
    ker <- kernel_real[k_]
    if(ker == "naive"){
      dist_all[["naive"]] <- dist_matrix_Mix(basicMachines = mach2,
                                         n_cv = n_cv,
                                         kernel = "naive",
                                         id_shuffle = id_shuf)
    } else{
      if(ker == "triangular"){
        dist_all[["triangular"]] <- dist_matrix_Mix(basicMachines = mach2,
                                                n_cv = n_cv,
                                                kernel = "triangular",
                                                id_shuffle = id_shuf)
      } else{
        if(if_euclid){
          dist_all[[ker]] <- dist_all[[id_euclid]]
        } else{
          dist_all[[ker]] <- dist_matrix_Mix(basicMachines = mach2,
                                         n_cv = n_cv,
                                         kernel = ker,
                                         id_shuffle = id_shuf)
          id_euclid <- ker
          if_euclid <- TRUE
        }
      }
    }
    id_shuf <- dist_all[[1]]$id_shuffle
  }
  # Kernel functions 
  # ================
  # Gaussian
  gaussian_kernel <- function(.ep,
                              .dist_matrix,
                              .train_response2,
                              .inv_sigma = inv_sigma,
                              .alpha = alp){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(exp(- (x[1]*D1+x[2]*D2)^(.alpha/2)*.inv_sigma^.alpha))
    y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0/colSums(tem0)
    return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = .dist_matrix$dist_machine[[.x]]))
  return(Reduce("+", temp))
}

# Epanechnikov
  epanechnikov_kernel <- function(.ep,
                                  .dist_matrix,
                                  .train_response2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(1- (x[1]*D1+x[2]*D))
    tem0[tem0 < 0] = 0
    y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0/colSums(tem0)
    return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = .dist_matrix$dist_machine[[.x]]))
  return(Reduce("+", temp))
  }

# Biweight
  biweight_kernel <- function(.ep,
                              .dist_matrix,
                              .train_response2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(1- (x[1]*D1+x[2]*D2))
    tem0[tem0 < 0] = 0
    y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0^2/colSums(tem0^2)
    y_hat[is.na(y_hat)] <- 0
    return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = .dist_matrix$dist_machine[[.x]]))
  return(Reduce("+", temp))
  }

# Triweight
  triweight_kernel <- function(.ep,
                               .dist_matrix,
                               .train_response2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(1- (x[1]*D1+x[2]*D2))
    tem0[tem0 < 0] = 0
    y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0^3/colSums(tem0^3)
    y_hat[is.na(y_hat)] <- 0
    return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = .dist_matrix$dist_machine[[.x]]))
  return(Reduce("+", temp))
  }

# Triangular
  triangular_kernel <- function(.ep,
                                .dist_matrix,
                                .train_response2){
  kern_fun <- function(x, id, D1, D2){
    tem0 <- as.matrix(1- (x[1]*D1+x[2]*D2))
    tem0[tem0 < 0] <- 0
    y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0/colSums(tem0)
    y_hat[is.na(y_hat)] = 0
    return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
  }
  temp <- map(.x = 1:.dist_matrix$n_cv, 
              .f = ~ kern_fun(x = .ep, 
                              id = .x,
                              D1 = .dist_matrix$dist_input[[.x]], 
                              D2 = .dist_matrix$dist_machine[[.x]]))
  return(Reduce("+", temp))
  }

  # Naive
  naive_kernel <- function(.ep,
                                .dist_matrix,
                                .train_response2){
    kern_fun <- function(x, id, D1, D2){
      tem0 <- (as.matrix((x[1]*D1+x[2]*D2)) < 1)
      y_hat <- .train_response2[.dist_matrix$id_shuffle != id] %*% tem0/colSums(tem0)
      y_hat[is.na(y_hat)] = 0
      return(sum((y_hat - .train_response2[.dist_matrix$id_shuffle == id])^2))
    }
    temp <- map(.x = 1:.dist_matrix$n_cv, 
                .f = ~ kern_fun(x = .ep, 
                                id = .x,
                                D1 = .dist_matrix$dist_input[[.x]], 
                                D2 = .dist_matrix$dist_machine[[.x]]))
    return(Reduce("+", temp))
  }
  
  # list of kernel functions
  list_funs <- list(gaussian = gaussian_kernel,
                    epanechnikov = epanechnikov_kernel,
                    biweight = biweight_kernel,
                    triweight = triweight_kernel,
                    triangular = triangular_kernel,
                    naive = naive_kernel)
  
  # error for all kernel functions
  error_func <- kernel_real %>%
    map(.f = ~ (\(x) list_funs[[.x]](.ep = x,
                                     .dist_matrix = dist_all[[.x]],
                                     .train_response2 = train_response[mach2$id2])/n_cv))
  names(error_func) <- kernels
  # list of prameter setup
  list_param <- list(grad = setGradParam,
                     GD = setGradParam,
                     grid = setGridParam)
  # list of optimizer
  list_optimizer <- list(grad = gradOptimizer_Mix,
                         GD = gradOptimizer_Mix,
                         grid = gridOptimizer_Mix)
  optMethods <- optimizeMethod
  if(length(kernels) != length(optMethods)){
    warning("* kernels and optimization methods differ in sides! Grid search will be used!")
    optMethods = rep("grid", length(kernels))
  }

  # Optimization
  parameters <- map2(.x = kernels,
                     .y = optMethods, 
                     .f = ~ list_optimizer[[.y]](obj_fun = error_func[[.x]],
                                                 setParameter = list_param[[.y]],
                                                 silent = silent))
  names(parameters) <- paste0(kernel_real, "_", optMethods)
  return(list(opt_parameters = parameters,
              add_parameters = list(inv_sigma = inv_sigma,
                                    alp = alp,
                                    opt_methods = optimizeMethod),
              basic_machines = mach2))
}

Example.5: We approximate the smoothing parameter of Boston data.


param <- fit_parameter_Mix(train_input = df[train, 1:13],
                       train_response = df[train, 14],
                       machines = c("knn", "rf", "xgb"),
                       splits = .50,
                       kernels = c("gaussian", "biweight", "triangular"),
                       optimizeMethod = c("grad", "grid", "grid"),
                       setBasicMachineParam = setBasicParameter_Mix(k = 2:6,
                                                                ntree = 1:5*100,
                                                                nrounds_xgb = 1:5*100),
                       setGradParam = setGradParameter_Mix(rate = "linear",
                                                       print_step = FALSE,
                                                       coef_auto = c(0.3, 0.3)),
                       setGridParam = setGridParameter_Mix(min_alpha = 0.0001,
                                                       max_alpha = 3,
                                                       min_beta = 0.0001,
                                                       max_beta = 3))

* Building basic machines ...
    ~ Progress: ... 33% ... 67% ... 100%
 ~ Observed parameter: (alpha, beta) = ( 0.2723926 ,  34.52247 ) in 112 itertaions.

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (0.1035448, 1.862107)

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (1e-04, 0.4138793)

4 Prediction

The smoothing parameter obtained from the previous section can be used to make the final predictions.

4.1 Kernel functions

Several types of kernel functions used for the aggregation are defined in this section.

  • Argument:

    • theta : a 2D-vector of the parameter \((\alpha, \beta)\) used.
    • .y2 : the vector of response variable of the second part \(\mathcal{D}_{\ell}\) of the training data, which is used for the aggregation.
    • .distance : the distance matrix object obtained from dist_matrix function.
    • .kern : the string specifying the kernel function. By default, .kern = "gaussian".
    • .inv_sig, .alp : the parameters of exponential kernel function.
    • .meth : the string of optimization methods to be linked to the name of the kernel functions if any perticular kernels are used more than once (maybe with different optimiztaion method such as “gaussian” kernel, can be used with both “grad” and “grid” optimization methods).
  • Value:

    This function returns the predictions of the aggregation method evaluated with the given parameter.

kernel_pred_Mix <- function(theta,
                            .y2, 
                            .dist1,
                            .dist2,
                            .kern = "gaussian",
                            .inv_sig = sqrt(.5), 
                            .alp = 2,
                            .meth = NA){
  distD <- as.matrix(theta[1]*.dist1+theta[2]*.dist1)
  # Kernel functions
  # ================
  gaussian_kernel <- function(D,
                              .inv_sigma = .inv_sig,
                              .alpha = .alp){
    tem0 <- exp(- D^(.alpha/2)*.inv_sig^.alpha)
    y_hat <- .y2 %*% tem0/colSums(tem0)
    return(t(y_hat))
  }

  # Epanechnikov
  epanechnikov_kernel <- function(D){
    tem0 <- 1- D
    tem0[tem0 < 0] = 0
    y_hat <- .y2 %*% tem0/colSums(tem0)
    return(t(y_hat))
  }
  # Biweight
  biweight_kernel <- function(D){
    tem0 <- 1- D
    tem0[tem0 < 0] = 0
    y_hat <- .y2 %*% tem0^2/colSums(tem0^2)
    y_hat[is.na(y_hat)] <- 0
    return(t(y_hat))
  }

  # Triweight
  triweight_kernel <- function(D){
    tem0 <- 1- D
    tem0[tem0 < 0] = 0
    y_hat <- .y2 %*% tem0^3/colSums(tem0^3)
    y_hat[is.na(y_hat)] <- 0
    return(t(y_hat))
  }

  # Triangular
  triangular_kernel <- function(D){
    tem0 <- 1- D
    tem0[tem0 < 0] <- 0
    y_hat <- .y2 %*% tem0/colSums(tem0)
    y_hat[is.na(y_hat)] = 0
    return(t(y_hat))
 }
  # Naive
  naive_kernel <- function(D){
      tem0 <- (D < 1)
      y_hat <- .y2 %*% tem0/colSums(tem0)
      y_hat[is.na(y_hat)] = 0
      return(t(y_hat))
  }
  # Prediction
  kernel_list <- list(gaussian = gaussian_kernel,
                      epanechnikov = epanechnikov_kernel,
                      biweight = biweight_kernel,
                      triweight = triweight_kernel,
                      triangular = triangular_kernel,
                      naive = naive_kernel)
  res <- tibble(as.vector(kernel_list[[.kern]](D = distD)))
  names(res) <- ifelse(is.na(.meth), 
                       .kern, 
                       paste0(.kern, '_', .meth))
  return(res)
}

4.2 Functions: predict_Mix

    This function allows us to predict new data points.

  • Argument:

    • fitted_models : the object obtained from fit_parameter_Mix function.
    • new_data : the testing data to be predicted.
    • new_pred : the predictions of the testing data new_data (when the basic machines are not constructed).
    • test_response : the testing response variable, it is optional. If it is given, the mean square error on the testing data is also computed. By default, test_response = NULL.
  • Value:

    This function returns a list of the following objects:

    • fitted_aggregate : the predictions by the aggregation methods.
    • fitted_machine : the predictions given by all the basic machines.
    • mse : the mean square error computed only if the test_reponse argument is not NULL.
# Prediction
predict_Mix <- function(fitted_models,
                        new_data,
                        new_pred = NULL,
                        test_response = NULL){
  opt_param <- fitted_models$opt_parameters
  add_param <- fitted_models$add_parameters
  basic_mach <- fitted_models$basic_machines
  kern0 <- names(opt_param)
  kernel0 <- stringr::str_split(kern0, "_") %>%
    imap_dfc(.f = ~ tibble("{.y}" := .x)) 
  kerns <- kernel0[1,] %>%
    as.character
  opt_meths <- kernel0[2,] %>%
    as.character
  new_data_ <- new_data
  mat_input <- as.matrix(basic_mach$train_data$train_input)
  if(!is.null(basic_mach$train_data$min_input)){
    new_data_ <- scale(new_data, 
                       center = basic_mach$train_data$min_input, 
                       scale = basic_mach$train_data$max_input - basic_mach$train_data$min_input)
  }
  if(is.matrix(new_data_)){
    mat_test <- new_data_
    df_test <- as_tibble(new_data_)
  } else {
    mat_test <- as.matrix(new_data_)
    df_test <- new_data_
  }
  if(is.null(basic_mach$models)){
    pred_test_all <- new_pred
    pred_test0 <- new_pred
  } else{
    # Prediction test by basic machines
    built_models <- basic_mach$models
    pred_test <- function(meth){
      if(meth == "knn"){
        pre <- 1:length(built_models[[meth]]) %>%
          map_dfc(.f = (\(k) tibble('{{k}}' := FNN::knn.reg(train = mat_input[!basic_mach$id2,], 
                                                            test = mat_test, 
                                                            y = basic_mach$train_data$train_response[!basic_mach$id2],
                                                            k = built_models[[meth]][[k]])$pred)))
      }
      if(meth %in% c("tree", "rf")){
        pre <- 1:length(built_models[[meth]]) %>%
          map_dfc(.f = (\(k) tibble('{{k}}' := as.vector(predict(built_models[[meth]][[k]], df_test)))))
      }
      if(meth %in% c("lasso", "ridge")){
        pre <- 1:length(built_models[[meth]]) %>%
          map_dfc(.f = (\(k) tibble('{{k}}' := as.vector(predict.glmnet(built_models[[meth]][[k]], mat_test)))))
      }
      if(meth == "xgb"){
        pre <- 1:length(built_models[[meth]]) %>%
          map_dfc(.f = (\(k) tibble('{{k}}' := as.vector(predict(built_models[[meth]][[k]], mat_test)))))
      }
      names(pre) <- names(built_models[[meth]])
      return(pre)
    }
    pred_test_all <- names(built_models) %>%
      map_dfc(.f = pred_test)
    pred_test0 <- pred_test_all
  }
  if(!is.null(basic_mach$train_data$min_machine)){
        pred_test_all <- scale(pred_test0, 
                               center = basic_mach$train_data$min_machine,
                               scale = basic_mach$train_data$max_machine - basic_mach$train_data$min_machine)
  }
  # Prediction train2
  pred_train_all <- basic_mach$fitted_remain
  colnames(pred_test_all) <- colnames(pred_train_all)
  d_train <- dim(pred_train_all)
  d_test <- dim(pred_test_all)
  d_train_input <- dim(mat_input[basic_mach$id2,])
  d_test_input <- dim(new_data_)
  pred_test_mat <- as.matrix(pred_test_all)
  pred_train_mat <- as.matrix(pred_train_all)
  # Distance matrix
  dist_mat <- function(kernel = "gausian"){
    res_1 <- res_2 <- NULL
    if(!(kernel %in% c("naive", "triangular"))){
      res_1 <- 1:d_test_input[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums((mat_input[basic_mach$id2,] - matrix(rep(new_data_[id,], 
                                                                                              d_train_input[1]), 
                                                                                          ncol = d_train_input[2], 
                                                                                          byrow = TRUE))^2)))))
      res_2 <- 1:d_test[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums((pred_train_mat - matrix(rep(pred_test_mat[id,], 
                                                                                              d_train[1]), 
                                                                                          ncol = d_train[2], 
                                                                                          byrow = TRUE))^2)))))
    }
    if(kernel == "triangular"){
      res_1 <- 1:d_test_input[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums(abs(mat_input[basic_mach$id2,] - matrix(rep(new_data_[id,], 
                                                                                        d_train_input[1]), 
                                                                                     ncol = d_train_input[2], 
                                                                                     byrow = TRUE)))))))
      res_2 <- 1:d_test[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(rowSums(abs(pred_train_mat - matrix(rep(pred_test_mat[id,], 
                                                                                                 d_train[1]), 
                                                                                             ncol = d_train[2], 
                                                                                             byrow = TRUE)))))))
    }
    if(kernel == "naive"){
      res_1 <- 1:d_test_input[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(apply(abs(mat_input[basic_mach$id2,] - matrix(rep(new_data_[id,], 
                                                                                        d_train_input[1]),
                                                                                     ncol = d_train_input[2], 
                                                                                     byrow = TRUE)), 1, max)))))
      res_2 <- 1:d_test[1] %>%
        map_dfc(.f = (\(id) tibble('{{id}}' := as.vector(apply(abs(pred_train_mat - matrix(rep(pred_test_mat[id,], d_train[1]), 
                                                                                           ncol = d_train[2], 
                                                                                           byrow = TRUE)), 1, max)))))
    }
    return(list(dist_input = res_1,
                dist_machine = res_2))
  }

  dists <- 1:length(kerns) %>%
      map(.f = ~ dist_mat(kerns[.x]))
  tab_nam <- table(kerns)
  nam <- names(tab_nam[tab_nam > 1])
  vec <- rep(NA, length(kerns))
  for(id in nam){
    id_ <- kerns == id
    if(!is.null(id_)){
      vec[id_] = add_param$opt_methods[id_]
    }
  }
  prediction <- 1:length(kerns) %>% 
    map_dfc(.f = ~ kernel_pred_Mix(theta = opt_param[[kern0[.x]]]$opt_param,
                                   .y2 = basic_mach$train_data$train_response[basic_mach$id2],
                                   .dist1 = dists[[.x]]$dist_input,
                                   .dist2 = dists[[.x]]$dist_machine,
                                   .kern = kerns[.x], 
                                   .inv_sig = add_param$inv_sigma, 
                                   .alp = add_param$alp,
                                   .meth = vec[.x]))
  if(is.null(test_response)){
    return(list(fitted_aggregate = prediction,
                fitted_machine = pred_test0))
  } else{
    error <- cbind(pred_test0, prediction) %>%
      dplyr::mutate(y_test = test_response) %>%
      dplyr::summarise_all(.funs = ~ (. - y_test)) %>%
      dplyr::select(-y_test) %>%
      dplyr::summarise_all(.funs = ~ mean(.^2))
    return(list(fitted_aggregate = prediction,
                fitted_machine = pred_test0,
                mse = error))
  }
}

Example.6 Aggregation on Boston dataset.


5 Function : MixCOBRARegressor (direct aggregation)

This function puts together all the functions above and provides the desire result of MixCobra aggregation method.

MixCOBRARegressor <- function(train_input, 
                        train_response,
                        test_input,
                        train_predictions = NULL,
                        test_predictions = NULL,
                        test_response = NULL,
                        machines = NULL, 
                        scale_input = TRUE,
                        scale_machine = TRUE,
                        splits = 0.5, 
                        n_cv = 5,
                        inv_sigma = sqrt(.5),
                        alp = 2,
                        kernels = "gaussian",
                        optimizeMethod = "grad",
                        setBasicMachineParam = setBasicParameter_Mix(),
                        setGradParam = setGradParameter_Mix(),
                        setGridParam = setGridParameter_Mix(),
                        silent = FALSE){
  # build machines + tune parameter
  fit_mod <- fit_parameter_Mix(train_input = train_input, 
                               train_response = train_response,
                               train_predictions = train_predictions,
                               machines = machines, 
                               scale_input = scale_input,
                               scale_machine = scale_machine,
                               splits = splits, 
                               n_cv = n_cv,
                               inv_sigma = inv_sigma,
                               alp = alp,
                               kernels = kernels,
                               optimizeMethod = optimizeMethod,
                               setBasicMachineParam = setBasicMachineParam,
                               setGradParam = setGradParam,
                               setGridParam = setGridParam,
                               silent = silent)
  # prediction
  pred <- predict_Mix(fitted_models = fit_mod,
                      new_data = test_input,
                      new_pred = test_predictions,
                      test_response = test_response)
  return(list(fitted_aggregate = pred$fitted_aggregate,
              fitted_machine = pred$fitted_machine,
              pred_train2 = fit_mod$basic_machines$fitted_remain,
              opt_parameter = fit_mod$opt_parameters,
              mse = pred$mse,
              kernels = kernels,
              ind_D2 = fit_mod$basic_machines$id2))
}

Example.7 A complete aggregation is implemented on Abalone dataset. Three types of basic machines, and three different kernel functions are used.


agg <- MixCOBRARegressor(train_input = df[train, 2:8],
                   train_response = df$Rings[train],
                   test_input = df[!train,2:8],
                   test_response = df$Rings[!train],
                   n_cv = 3,
                   machines = c("knn", "rf", "xgb"),
                   splits = .5,
                   kernels = c("gaussian", 
                               "naive", 
                               "triangular"),
                   optimizeMethod = c("grad", 
                                      "grid", 
                                      "grid"),
                   setBasicMachineParam = setBasicParameter_Mix(k = c(2,5,7,10),
                                                              ntree = 2:5*100,
                                                              nrounds_xgb = c(1,3,5)*100),
                   setGradParam = setGradParameter_Mix(rate = "linear",
                                                       coef_auto = c(0.5, 0.5),
                                                       print_step = TRUE,
                                                       print_result = TRUE,
                                                       figure = TRUE),
                   setGridParam = setGridParameter_Mix(parameters = list(alpha = seq(0.0001, 3, length.out = 20),
                                                                     beta = seq(0.0001, 5, length.out = 20))))

* Building basic machines ...
    ~ Progress: ... 33% ... 67% ... 100%
* Gradient descent algorithm ...
  Step  |  alpha    ;  beta     |  Gradient (alpha ; beta)  |  Threshold 
 --------------------------------------------------------------------------------
   0    |  10.00000  ;  50.00000    |  -1.28882e+00  ;  0.25457     |  1e-10 
 --------------------------------------------------------------------------------
   0    |  10.0000  ;  50.0000  |  -1.2888e+00  ;  0.25457  |  13.89048 

   1    |  10.5000  ;  49.5000  |  -1.3154e+00  ;  0.30085  |  0.01666667 

   2    |  11.5206  ;  48.3182  |  -1.3902e+00  ;  0.38099  |  0.07286875 

   3    |  13.1386  ;  46.0733  |  -1.4596e+00  ;  0.42388  |  0.154925 

   4    |  15.4036  ;  42.7431  |  -1.201e+00  ;  0.22624   |  0.1122472 

   5    |  17.7331  ;  40.5213  |  -6.9773e-01  ;  -7.5933e-02  |  0.4562241 

   6    |  19.3573  ;  41.4162  |  -4.52e-01  ;  -4.1175e-02    |  0.8054029 

   7    |  20.5848  ;  41.6992  |  -2.7488e-01  ;  -5.52e-02    |  0.2804899 

   8    |  21.4379  ;  42.1329  |  -1.7249e-01  ;  -3.9204e-02  |  0.191143 

   9    |  22.0402  ;  42.4794  |  -1.0728e-01  ;  -2.3637e-02  |  0.1183893 

   10   |  22.4564  ;  42.7115  |  -6.475e-02  ;  -1.345e-02    |  0.08077813 

   11   |  22.7327  ;  42.8568  |  -3.7509e-02  ;  -7.4236e-03  |  0.05271782 

   12   |  22.9073  ;  42.9443  |  -2.0689e-02  ;  -3.9679e-03  |  0.03326777 

   13   |  23.0116  ;  42.9950  |  -1.0786e-02  ;  -2.0311e-03  |  0.02027503 

   14   |  23.0702  ;  43.0229  |  -5.2749e-03  ;  -9.8347e-04  |  0.01184012 

   15   |  23.1009  ;  43.0374  |  -2.4012e-03  ;  -4.457e-04   |  0.006558684 

   16   |  23.1158  ;  43.0444  |  -1.0097e-03  ;  -1.8688e-04  |  0.003411438 

   17   |  23.1225  ;  43.0475  |  -3.8859e-04  ;  -7.1943e-05  |  0.001650372 

   18   |  23.1252  ;  43.0488  |  -1.357e-04  ;  -2.5007e-05   |  0.0007360271 

   19   |  23.1262  ;  43.0492  |  -4.2317e-05  ;  -7.8852e-06  |  0.0002998254 

   20   |  23.1265  ;  43.0494  |  -1.194e-05  ;  -2.1778e-06   |  0.0001105055 

   21   |  23.1266  ;  43.0494  |  -2.7786e-06  ;  -4.5058e-07  |  3.608418e-05 

   22   |  23.1266  ;  43.0495  |  -5.2568e-07  ;  -2.6284e-07  |  1.088909e-05 

   23   |  23.1267  ;  43.0495  |  -2.2529e-07  ;  1.8774e-07   |  2.440657e-06 

   24   |  23.1267  ;  43.0495  |  7.5097e-08  ;  -3.0039e-07   |  7.509715e-07 

   25   |  23.1267  ;  43.0495  |  -7.5097e-08  ;  1.1265e-07   |  7.8852e-07 

   26   |  23.1267  ;  43.0495  |  0e+00  ;  0e+00  |  5.632286e-07 

   27   |  23.1267  ;  43.0495  |  0e+00  ;  0e+00  |  1.877429e-07 
--------------------------------------------------------------------------------
 Stopped|  23.1267  ;  43.0495  |     0         |  0
 ~ Observed parameter: (alpha, beta) = ( 23.12665 ,  43.04946 ) in 28 itertaions.

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (0.9474368, 4.736847)

* Grid search algorithm...
 ~ Observed parameter: (alpha, beta) = (0.4737684, 0.5264053)
sqrt(agg$mse)

References



LS0tDQp0aXRsZTogIjxzcGFuIHN0eWxlPSdjb2xvcjogIzFDODFBQTsnPioqTWl4Q29icmEqKiAtPC9zcGFuPiBbRmlzY2hlciBhbmQgTW91Z2VvdCAoMjAxOSldKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAzNzgzNzU4MTgzMDIzNDkpIg0KYXV0aG9yOiAiU290aGVhIEhhcyINCmRhdGU6ICI1LzIvMjAyMiINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjc3M6IGhpZGVPdXRwdXQuY3NzDQogICAgaW5jbHVkZXM6DQogICAgICBpbl9oZWFkZXI6IGhpZGVPdXRwdXQuc2NyaXB0DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogJzInDQogICAgdG9jZGVwdGg6IDINCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjc3M6IGhpZGVPdXRwdXQuY3NzDQogICAgaW5jbHVkZXM6DQogICAgICBpbl9oZWFkZXI6IGhpZGVPdXRwdXQuc2NyaXB0DQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogMg0KICAgIHRvY2RlcHRoOiAyDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogJzInDQotLS0NCg0KPHN0eWxlPg0KICAuYnRuIHsNCiAgICBib3JkZXItd2lkdGg6IDAgMHB4IDBweCAwcHg7DQogICAgZm9udC13ZWlnaHQ6IG5vcm1hbDsNCiAgICB0ZXh0LXRyYW5zZm9ybTogOw0KICB9DQouYnRuLWRlZmF1bHQgew0KICBjb2xvcjogIzJlY2M3MTsNCiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjZmZmZmZmOw0KICAgIGJvcmRlci1jb2xvcjogI2ZmZmZmZjsNCn0NCjwvc3R5bGU+DQoNCjwhLS0gQ29sb3JzDQpibHVlIDogIzFGQUFFMw0KeWVsbG93IDogI0YwQUUxNA0KZ3JlZW4gOiAjNTREMzE5IA0KcmVkIDogI0U2MTgwQQ0KLS0+DQoNCg0KYGBge3IsIGVjaG89RkFMU0V9DQojIENoZWNrIGlmIHBhY2thZ2UgImZvbnRhd2Vzb21lIiBpcyBhbHJlYWR5IGluc3RhbGxlZCANCg0KbG9va3VwX3BhY2thZ2VzIDwtIGluc3RhbGxlZC5wYWNrYWdlcygpWywxXQ0KaWYoISgiZm9udGF3ZXNvbWUiICVpbiUgbG9va3VwX3BhY2thZ2VzKSkNCiAgaW5zdGFsbC5wYWNrYWdlcygiZm9udGF3ZXNvbWUiKQ0KYGBgDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPiYjMTI4MjcwOzx1PiBIb3cgdG8gZG93bmxvYWQgJiBydW4gdGhlIGNvZGVzPzwvdT48L3NwYW4+ey19DQo9PT0NCg0KQWxsIHRoZSBzb3VyY2UgY29kZXMgb2YgdGhlIGFnZ3JlZ2F0aW9uIG1ldGhvZHMgYXJlIGF2YWlsYWJsZSBbaGVyZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPiBgciBmb250YXdlc29tZTo6ZmEoImdpdGh1YiIpYDwvc3Bhbj5dKGh0dHBzOi8vZ2l0aHViLmNvbS9oYXNzb3RoZWEvQWdncmVnYXRpb25NZXRob2RzKS4gVG8gcnVuIHRoZSBjb2RlcywgeW91IGNhbiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPmBjbG9uZWA8L3NwYW4+IHRoZSByZXBvc2l0b3J5IGRpcmVjdGx5IG9yIHNpbXBseSBsb2FkIHRoZSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPmBSIHNjcmlwdGA8L3NwYW4+IHNvdXJjZSBmaWxlIGZyb20gdGhlIHJlcG9zaXRvcnkgdXNpbmcgW2RldnRvb2xzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGV2dG9vbHMvaW5kZXguaHRtbCkgcGFja2FnZSBpbiA8c3BhbiBzdHlsZT0iY29sb3I6ICMwMjg3RDg7Ij4gKipSc3R1ZGlvKiogPC9zcGFuPiBhcyBmb2xsb3c6DQoNCjEuIEluc3RhbGwgW2RldnRvb2xzXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGV2dG9vbHMvaW5kZXguaHRtbCkgcGFja2FnZSB1c2luZyBjb21tYW5kOiANCg0KICAgIGBpbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIpYA0KDQoyLiBMb2FkaW5nIHRoZSBzb3VyY2UgY29kZXMgZnJvbSA8c3BhbiBzdHlsZT0iY29sb3I6ICMwOTdCQzEiPkdpdEh1YiBgciBmb250YXdlc29tZTo6ZmEoImdpdGh1YiIpYDwvc3Bhbj4gcmVwb3NpdG9yeSB1c2luZyBgc291cmNlX3VybGAgZnVuY3Rpb24gYnk6IA0KDQogICAgYGRldnRvb2xzOjpzb3VyY2VfdXJsKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vaGFzc290aGVhL0FnZ3JlZ2F0aW9uTWV0aG9kcy9tYWluL01peENvYnJhUmVnLlIiKWANCg0KLS0tDQoNCj4qKiYjOTk5ODsgTm90ZSoqOiBBbGwgY29kZXMgY29udGFpbmVkIGluIHRoaXMgYFJtYXJrZG93bmAgYXJlIGJ1aWx0IHdpdGggcmVjZW50IHZlcnNpb24gb2YgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDk3QkMxOyI+YHIgZm9udGF3ZXNvbWU6OmZhKCJyLXByb2plY3QiKWA8L3NwYW4+ICh2ZXJzaW9uICQ+JCA0LjEsIGF2YWlsYWJsZSBbaGVyZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvYmluL3dpbmRvd3MvYmFzZS8pKSBhbmQgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDI4N0Q4OyI+ICoqUnN0dWRpbyoqIDwvc3Bhbj4gKHZlcnNpb24gPiBgMjAyMi4wMi4yKzQ4NWAsIGF2YWlsYWJsZSBbaGVyZV0oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vcHJvZHVjdHMvcnN0dWRpby9kb3dubG9hZC8jZG93bmxvYWQpKS4gTm90ZSBhbHNvIHRoYXQgdGhlIGNvZGUgY2h1Y2tzIGFyZSA8c3BhbiBzdHlsZT0iY29sb3I6ICNFNjE4MEE7Ij5oaWRkZW48L3NwYW4+IGJ5IGRlZmF1bHQuDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNCI+ICoqVG8gc2VlIHRoZSBjb2RlcywgeW91IGNhbjoqKiA8L3NwYW4+DQoNCi0gY2xpY2sgb24gdGhlIHRvcC1yaWdodCA8c3BhbiBzdHlsZT0iY29sb3I6ICM1NEQzMTkgOyI+YENvZGVgPC9zcGFuPiBidXR0b24gb2YgdGhlIHBhZ2UsIHRoZW4gY2hvb3NlICoqU2hvdyBBbGwgQ29kZSoqIHRvIHNob3cgYWxsIHRoZSBjb2Rlcywgb3IgDQotIHNpbXBseSBjbGljayBvbiB0aGUgcmlnaHQtY29ybmVyIDxzcGFuIHN0eWxlPSJjb2xvcjogIzU0RDMxOSA7Ij5gQ29kZWA8L3NwYW4+IGJ1dHRvbiBhdCBlYWNoIHNlY3Rpb24gdG8gc2hvdyB0aGUgY29kZXMgb2YgdGhhdCBzcGVjaWZpYyBzZWN0aW9uLg0KDQotLS0NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+IE1peENvYnJhICYgaW1wb3J0YW50IHBhY2thZ2VzIDwvdT48L3NwYW4+DQo9PT0NCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+IE1peENvYnJhIG1ldGhvZDwvdT48L3NwYW4+DQotLS0NCg0KVGhpcyBgUm1hcmtkb3duYCBwcm92aWRlcyB0aGUgaW1wbGVtZW50YXRpb24gb2YgYW4gYWdncmVnYXRpb24gbWV0aG9kIHVzaW5nIGlucHV0IGFuZCBvdXQgdHJhZGUtb2ZmIGJ5IDxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPltGaXNjaGVyIGFuZCBNb3VnZW90ICgyMDE5KV0oaHR0cHM6Ly93d3cuc2NpZW5jZWRpcmVjdC5jb20vc2NpZW5jZS9hcnRpY2xlL3BpaS9TMDM3ODM3NTgxODMwMjM0OSk8L3NwYW4+Lg0KTGV0ICRcbWF0aGNhbHtEfV9uPVx7KHhfMSx5XzEpLC4uLiwoeF9uLHlfbilcfSQgYmUgYSB0cmFpbmluZyBkYXRhIG9mIHNpemUgJG4kLCB3aGVyZSB0aGUgaW5wdXQtb3V0cHV0IGNvdXBsZXMgJCh4X2kseV9pKVxpblxtYXRoYmJ7Un1eZFx0aW1lc1xtYXRoYmJ7Un0kIGZvciBhbGwgJGk9MSwuLi4sbiQuICRcbWF0aGNhbHtEfV97bn0kIGlzIGZpcnN0IHJhbmRvbWx5IHBhcnRpdGlvbmVkIGludG8gJFxtYXRoY2Fse0R9X3trfSQgYW5kICRcbWF0aGNhbHtEfV97XGVsbH0kIG9mIHNpemUgJGskIGFuZCAkXGVsbCQgcmVzcGVjdGl2ZWx5IHN1Y2ggdGhhdCAkaytcZWxsPW4kLiBXZSBjb25zdHJ1Y3QgJE0kIHJlZ3Jlc3Npb24gZXN0aW1hdG9ycyAobWFjaGluZXMpICAkcl8xLC4uLixyX00kIHVzaW5nIG9ubHkgJFxtYXRoY2Fse0R9X3trfSQuIExldCAke1xiZiByfSh4KT0ocl8xKHgpLC4uLixyX00oeCkpXlRcaW5cbWF0aGJie1J9Xk0kIGJlIHRoZSB2ZWN0b3Igb2YgcHJlZGljdGlvbnMgb2YgJHhcaW5cbWF0aGJie1J9XmQkLCB0aGUga2VybmVsLWJhc2VkIGNvbnNlbnN1YWwgYWdncmVnYXRpb24gbWV0aG9kIGV2YWx1YXRlZCBhdCBwb2ludCAkeCQgaXMgZGVmaW5lZCBieQ0KDQpcYmVnaW57ZXF1YXRpb259DQpnX24oeCk9XGZyYWN7XHN1bV97aT0xfV57XGVsbH15X2lLX3tcYWxwaGEsXGJldGF9KHgteF9pLHtcYmYgcn0oeCkte1xiZiByfSh4X2kpKX17XHN1bV97aj0xfV57XGVsbH1LX3tcYWxwaGEsXGJldGF9KHgteF9qLHtcYmYgcn0oeCkte1xiZiByfSh4X2opKX0NClxlbmR7ZXF1YXRpb259DQp3aGVyZSAkSzpcbWF0aGJie1J9XntkK019XHRvXG1hdGhiYntSfV8rJCBpcyBhIG5vbi1pbmNyZWFzaW5nIGtlcm5lbCBmdW5jdGlvbiB3aXRoICRLX3tcYWxwaGEsXGJldGF9KHUsdik9SyhcZnJhY3t1fXtcYWxwaGF9LFxmcmFje3Z9e1xiZXRhfSkkIGZvciBzb21lIHNtb290aGluZyBwYXJhbWV0ZXIgJFxhbHBoYSxcYmV0YT4wJCB0byBiZSB0dW5lZCwgd2l0aCB0aGUgY29udmVudGlvbiAkMC8wPTAkLiANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij4gPHU+IEltcG9ydGFudCBwYWNrYWdlczwvdT48L3NwYW4+DQotLS0NCg0KV2UgcHJlcGFyZSBhbGwgdGhlIG5lY2Vzc2FyeSB0b29scyBmb3IgdGhpcyBgUm1hcmtkb3duYC4gYHBhY21hbmAgcGFja2FnZSBhbGxvd3MgdXMgdG8gbG9hZCAoaWYgZXhpc3RzKSBvciBpbnN0YWxsIChpZiBkb2VzIG5vdCBleGlzdCkgYW55IGF2YWlsYWJsZSBwYWNrYWdlcyBmcm9tIFtUaGUgQ29tcHJlaGVuc2l2ZSBSIEFyY2hpdmUgTmV0d29yayAoQ1JBTildKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnLykgb2YgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMDk3QkMxIj5gciBmb250YXdlc29tZTo6ZmEoInItcHJvamVjdCIpYDwvc3Bhbj4uIA0KDQoNCmBgYHtyfQ0KIyBDaGVjayBpZiBwYWNrYWdlICJwYWNtYW4iIGlzIGFscmVhZHkgaW5zdGFsbGVkIA0KDQpsb29rdXBfcGFja2FnZXMgPC0gaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdDQppZighKCJwYWNtYW4iICVpbiUgbG9va3VwX3BhY2thZ2VzKSl7DQogIGluc3RhbGwucGFja2FnZXMoInBhY21hbiIpDQp9DQoNCiMgVG8gYmUgaW5zdGFsbGVkIG9yIGxvYWRlZA0KcGFjbWFuOjpwX2xvYWQobWFncml0dHIpDQpwYWNtYW46OnBfbG9hZChnZ3Bsb3QyKQ0KcGFjbWFuOjpwX2xvYWQodGlkeXZlcnNlKQ0KDQojIyBwYWNrYWdlIGZvciAiZ2VuZXJhdGVNYWNoaW5lcyINCnBhY21hbjo6cF9sb2FkKHRyZWUpDQpwYWNtYW46OnBfbG9hZChnbG1uZXQpDQpwYWNtYW46OnBfbG9hZChyYW5kb21Gb3Jlc3QpDQpwYWNtYW46OnBfbG9hZChGTk4pDQpwYWNtYW46OnBfbG9hZCh4Z2Jvb3N0KQ0KcGFjbWFuOjpwX2xvYWQoa2VyYXMpDQpwYWNtYW46OnBfbG9hZChwcmFjbWEpDQpwYWNtYW46OnBfbG9hZChsYXRleDJleHApDQpwYWNtYW46OnBfbG9hZChwbG90bHkpDQpwYWNtYW46OnBfbG9hZChkYXNoKQ0Kcm0obG9va3VwX3BhY2thZ2VzKQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+PHU+QmFzaWMgTWFjaGluZSBnZW5lcmF0b3I8L3U+PC9zcGFuPg0KPT09DQoNClRoaXMgc2VjdGlvbiBwcm92aWRlcyBmdW5jdGlvbnMgdG8gZ2VuZXJhdGUgYmFzaWMgbWFjaGluZXMgKHJlZ3Jlc3NvcnMpIHRvIGJlIGFnZ3JlZ2F0ZWQuDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkZ1bmN0aW9uPC91Pjwvc3Bhbj4gOiBgc2V0QmFzaWNQYXJhbWV0ZXJfTWl4YA0KLS0tLQ0KDQpUaGlzIGZ1bmN0aW9uIGFsbG93cyB1cyB0byBzZXQgdGhlIHZhbHVlcyBvZiBzb21lIGtleSBwYXJhbWV0ZXJzIG9mIHRoZSBiYXNpYyBtYWNoaW5lcy4NCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGBsYW1iZGFgIDogdGhlIHBlbmFsdHkgcGFyYW1ldGVyICRcbGFtYmRhJCB1c2VkIGluIHBlbmFsaXplZCBsaW5lYXIgbW9kZWxzOiBgcmlkZ2VgIG9yIGBsYXNzb2AuDQogICAgLSBga2AgOiB0aGUgcGFyYW1ldGVyICRrJCBvZiAkayROTiAoYGtubmApIHJlZ3Jlc3Npb24gbW9kZWwgYW5kIHRoZSBkZWZhdWx0IHZhbHVlIGlzICRrPTEwJC4NCiAgICAtIGBudHJlZWAgOiB0aGUgbnVtYmVyIG9mIHRyZWVzIGluIHJhbmRvbSBmb3Jlc3QgKGByZmApLiBCeSBkZWZhdWx0LCBgbnRyZWUgPSAzMDBgLg0KICAgIC0gYG10cnlgIDogdGhlIG51bWJlciBvZiByYW5kb20gZmVhdHVyZXMgY2hvc2VuIGluIGVhY2ggc3BsaXQgb2YgcmFuZG9tIGZvcmVzdCBwcm9jZWR1cmUuIEJ5IGRlZmF1bHQsIGBtdHJ5ID0gTlVMTGAgYW5kIHRoZSBkZWZhdWx0IHZhbHVlIG9mIGBtdHJ5YCBvZiAqcmFuZG9tRm9yZXN0KiBmdW5jdGlvbiBmcm9tIFtyYW5kb21Gb3Jlc3RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9yYW5kb21Gb3Jlc3QvaW5kZXguaHRtbCkgbGlicmFyeSBpcyB1c2VkLg0KICAgIC0gYGV0YV94Z2JgIDogdGhlIGxlYXJuaW5nIHJhdGUgJFxldGE+MCQgaW4gZ3JhZGllbnQgc3RlcCBvZiAqZXh0cmVtZSBncmFkaWVudCBib29zdGluZyogbWV0aG9kIChgeGdiYCkgb2YgW3hnYm9vc3RdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy94Z2Jvb3N0L2luZGV4Lmh0bWwpIGxpYnJhcnkuDQogICAgLSBgbnJvdW5kc194Z2JgIDogdGhlIHBhcmFtZXRlciBgbnJvdW5kc2AgaW5kaWNhdGluZyB0aGUgbWF4IG51bWJlciBvZiBib29zdGluZyBpdGVyYXRpb25zLiBCeSBkZWZhdWx0LCBgbnJvdW5kc194Z2IgPSAxMDBgLg0KICAgIC0gYGVhcmx5X3N0b3BfeGdiYCA6IHRoZSBlYXJseSBzdG9wcGluZyByb3VuZCBjcml0ZXJpb24gb2YgYHhnYm9vc3RgIGZ1bmN0aW9uLiBCeSwgZGVmYXVsdCwgYGVhcmx5X3N0b3BfeGdiID0gTlVMTGAgYW5kIHRoZSBlYXJseSBzdG9wcGluZyBmdW5jdGlvbiBpcyBub3QgdHJpZ2dlcmVkLg0KICAgIC0gYG1heF9kZXB0aF94Z2JgIDogbWF4aW11bSBkZXB0aCBvZiB0cmVlcyBjb25zdHJ1Y3RlZCBpbiBgeGdib29zdGAuIA0KDQotICoqVmFsdWUqKjogDQogICAgDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIGEgKmxpc3QqIG9mIGFsbCB0aGUgcGFyYW1ldGVycyBnaXZlbiBpbiBpdHMgYXJndW1lbnRzLCB0byBiZSBmZWQgdG8gdGhlIGBiYXNpY01hY2hpbmVQYXJhbWAgYXJndW1lbnQgb2YgZnVuY3Rpb24gYGdlbmVyYXRlTWFjaGluZXNfTWl4YCBkZWZpbmVkIGluIHRoZSBuZXh0IHNlY3Rpb24uDQoNCi0tLQ0KDQo+KipSZW1hcmsuMSoqOiANCmBsYW1iZGFgLCBga2AsIGBudHJlZWAgY2FuIGJlIGEgc2luZ2xlIHZhbHVlIG9yIGEgdmVjdG9yLiBJbiBvdGhlciB3b3JkcywgZWFjaCB0eXBlIG9mIG1vZGVscyBjYW4gYmUgY29uc3RydWN0ZWQgc2V2ZXJhbCB0aW1lcyBhY2NvcmRpbmcgdG8gdGhlIHZhbHVlcyBvZiB0aGUgcGFyYW1ldGVycyAkKFxhbHBoYSwgXGJldGEpJCBvZiB0aGUgbWV0aG9kLg0KDQotLS0NCg0KYGBge3J9DQpzZXRCYXNpY1BhcmFtZXRlcl9NaXggPC0gZnVuY3Rpb24obGFtYmRhID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGsgPSA1LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gMzAwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSBOVUxMLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV0YV94Z2IgPSAxLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHNfeGdiID0gMTAwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVhcmx5X3N0b3BfeGdiID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9kZXB0aF94Z2IgPSAzKXsNCiAgcmV0dXJuKGxpc3QoDQogICAgbGFtYmRhID0gbGFtYmRhLA0KICAgIGsgPSBrLA0KICAgIG50cmVlID0gbnRyZWUsIA0KICAgIG10cnkgPSBtdHJ5LCANCiAgICBldGFfeGdiID0gZXRhX3hnYiwgDQogICAgbnJvdW5kc194Z2IgPSBucm91bmRzX3hnYiwgDQogICAgZWFybHlfc3RvcF94Z2IgPSBlYXJseV9zdG9wX3hnYiwNCiAgICBtYXhfZGVwdGhfeGdiID0gbWF4X2RlcHRoX3hnYikNCiAgKQ0KfQ0KYGBgDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+RnVuY3Rpb248L3U+PC9zcGFuPiA6IGBnZW5lcmF0ZU1hY2hpbmVzX01peGANCi0tLQ0KDQpUaGlzIGZ1bmN0aW9uIGdlbmVyYXRlcyBhbGwgdGhlIGJhc2ljIG1hY2hpbmVzIHRvIGJlIGFnZ3JlZ2F0ZWQuIA0KDQotICoqQXJndW1lbnQqKjoNCg0KICAgIC0gYHRyYWluX2lucHV0YCA6IGEgbWF0cml4IG9yIGRhdGEgZnJhbWUgb2YgdGhlIHRyYWluaW5nIGlucHV0IGRhdGEuDQogICAgLSBgdHJhaW5fcmVzcG9uc2VgIDogYSB2ZWN0b3Igb2YgdHJhaW5pbmcgcmVzcG9uc2UgdmFyaWFibGUgY29ycmVzcG9uZGluZyB0byB0aGUgYHRyYWluX2lucHV0YC4NCiAgICAtIGBzY2FsZV9pbnB1dGAgOiBsb2dpY2FsIHZhbHVlIHNwZWNpZnlpbmcgd2hldGhlciB0byBzY2FsZSB0aGUgaW5wdXQgZGF0YSAodG8gYmUgYmV0d2VlbiAkMCQgYW5kICQxJCkgb3Igbm90LiBCeSBkZWZhdWx0LCBgc2NhbGVfaW5wdXQgPSBUUlVFYC4NCiAgICAtIGBzY2FsZV9tYWNoaW5lYCA6IGxvZ2ljYWwgdmFsdWUgc3BlY2lmeWluZyB3aGV0aGVyIHRvIHNjYWxlIHRoZSBwcmVkaWN0aW9ucyBvZiB0aGUgcmVtYWluaW5nIHBhcnQgJFxtYXRoY2Fse0R9X3tcZWxsfSQgb2YgdGhlIHRyYWluaW5nIGRhdGEgKHRvIGJlIGJldHdlZW4gJDAkIGFuZCAkMSQpIG9yIG5vdC4gQnkgZGVmYXVsdCwgYHNjYWxlX21hY2hpbmUgPSBUUlVFYC4NCiAgICAtIGBtYWNoaW5lc2AgOiB0eXBlcyBvZiBiYXNpYyBtYWNoaW5lcyB0byBiZSBjb25zdHJ1Y3RlZC4gSXQgaXMgYSBzdWJzZXQgb2YgeyJsYXNzbyIsICJyaWRnZSIsICJrbm4iLCAidHJlZSIsICJyZiIsICJ4Z2IifS4gQnkgZGVmYXVsdCwgYG1hY2hpbmVzID0gTlVMTGAgYW5kIGFsbCB0aGUgc2l4IHR5cGVzIG9mIGJhc2ljIG1hY2hpbmVzIGFyZSBidWlsdC4NCiAgICAtIGBzcGxpdHNgIDogcmVhbCBudW1iZXIgYmV0d2VlbiAkMCQgYW5kICQxJCBzcGVjaWZ5aW5nIHRoZSBwcm9wb3J0aW9uIG9mIHRyYWluaW5nIGRhdGEgdXNlZCB0byB0cmFpbiB0aGUgYmFzaWMgbWFjaGluZXMgKCRcbWF0aGNhbHtEfV9rJCkuIFRoZSByZW1haW5pbmcgcHJvcG9ydGlvbiBvZiAoJDEtJCBgc3BsaXRzYCkgaXMgdXNlZCBmb3IgdGhlIGFnZ3JlZ2F0aW9uICgkXG1hdGhjYWx7RH1fe1xlbGx9JCkuIEJ5IGRlZmF1bHQsIGBzcGxpdHMgPSAwLjVgLg0KICAgIC0gYGJhc2ljTWFjaGluZVBhcmFtYCA6IHRoZSBvcHRpb24gdXNlZCB0byBzZXR1cCB0aGUgdmFsdWVzIG9mIHBhcmFtZXRlcnMgb2YgZWFjaCBtYWNoaW5lcy4gT25lIHNob3VsZCBmZWVkIHRoZSBmdW5jdGlvbiBgc2V0QmFzaWNQYXJhbWV0ZXJfTWl4KClgIGRlZmluZWQgYWJvdmUgdG8gdGhpcyBhcmd1bWVudC4NCiAgICANCiAgICANCi0gKipWYWx1ZSoqOiANCg0KICAgIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBhIGxpc3Qgb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzLg0KDQogICAgLSBgZml0dGVkX3JlbWFpbmAgOiB0aGUgcHJlZGljdGlvbnMgb2YgdGhlIHJlbWFpbmluZyBwYXJ0ICgkXG1hdGhjYWx7RH1fe1xlbGx9JCkgb2YgdGhlIHRyYWluaW5nIGRhdGEgdXNlZCBmb3IgdGhlIGFnZ3JlZ2F0aW9uLg0KICAgIC0gYG1vZGVsc2AgOiBhbGwgdGhlIGNvbnN0cnVjdGVkIGJhc2ljIG1hY2hpbmVzIChpdCBjb250YWlucyBvbmx5IHRoZSB2YWx1ZXMgb2YgcHJhcGV0ZXIgJGskIGZvciBga25uYCkuDQogICAgLSBgaWQyYCA6IGEgbG9naWNhbCB2ZWN0b3Igb2Ygc2l6ZSBlcXVhbHMgdG8gdGhlIG51bWJlciBvZiBsaW5lcyBvZiB0aGUgdHJhaW5pbmcgZGF0YSBpbmRpY2F0aW5nIHRoZSBsb2NhdGlvbiBvZiB0aGUgcG9pbnRzIHVzZWQgdG8gYnVpbGQgdGhlIGJhc2ljIG1hY2hpbmVzIChgRkFMU0VgKSBhbmQgdGhlIHJlbWFpbmluZyBvbmVzIChgVFJVRWApLg0KICAgIC0gYHRyYWluX2RhdGFgIDogYSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCiAgICAgICAgLSBgdHJhaW5faW5wdXRgIDogdHJhaW5pbmcgaW5wdXQgZGF0YSAoc2NhbGUgb3Igbm9uLXNjYWxpbmcgYWNjb3JkaW5nbHkpLg0KICAgICAgICAtIGBwcmVkaWN0X3JlbWFpbl9vcmdgIDogcHJlZGljdGlvbnMgb2YgdGhlIHNlY29uZCBwYXJ0ICR7XGNhbCBEfV97XGVsbH0kIG9mIHRoZSB0cmFpbmluZyBkYXRhIHdpdGhvdXQgc2NhbGluZy4NCiAgICAgICAgLSBgdHJhaW5fcmVzcG9uc2VgIDogdGhlIHRyYWluZ2luZyByZXNwb25zZSB2YXJpYWJsZS4NCiAgICAgICAgLSBgbWluX21hY2hpbmVgLCBgbWF4X21hY2hpbmVgIDogdmVjdG9ycyBvZiBtaW5pbXVtIGFuZCBtYXhpbXVtIHZhbHVlcyBwcmVkaWN0ZWQgdmFsdWVzIG9mIHRoZSByZW1haW5pbmcgcGFydCAkXG1hdGhjYWx7RH1fe1xlbGx9JCBvZiB0aGUgdHJhaW5pbmcgZGF0YS4gVGhleSBhcmUgYE5VTExgIGlmIGBzY2FsZV9tYWNoaW5lID0gRkFMU0VgLg0KICAgICAgICAtIGBtaW5faW5wdXRgLCBgbWF4X2lucHV0YCA6IHZlY3RvcnMgb2YgbWluaW11bSBhbmQgbWF4aW11bSB2YWx1ZXMgb2YgZWFjaCB2YXJpYWJsZSBvZiB0aGUgdHJhaW5pbmcgaW5wdXQuIFRoZXkgYXJlIGBOVUxMYCBpZiBgc2NhbGVfaW5wdXQgPSBGQUxTRWAuDQogICAgDQotLS0NCg0KPiAqKiYjOTk5ODsgTm90ZSoqOiAqWW91IG1heSBuZWVkIHRvIG1vZGlmeSB0aGUgZnVuY3Rpb24gYWNjb3JkaW5nbHkgaWYgeW91IHdhbnQgdG8gYnVpbGQgZGlmZmVyZW50IHR5cGVzIG9mIGJhc2ljIG1hY2hpbmVzKi4NCg0KLS0tDQoNCmBgYHtyfQ0KZ2VuZXJhdGVNYWNoaW5lc19NaXggPC0gZnVuY3Rpb24odHJhaW5faW5wdXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9yZXNwb25zZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9tYWNoaW5lID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFjaGluZXMgPSBOVUxMLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gMC41LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFzaWNNYWNoaW5lUGFyYW0gPSBzZXRCYXNpY1BhcmFtZXRlcl9NaXgoKSl7DQogIGxhbWJkYSA9IGJhc2ljTWFjaGluZVBhcmFtJGxhbWJkYQ0KICBrIDwtIGJhc2ljTWFjaGluZVBhcmFtJGsgDQogIG50cmVlIDwtIGJhc2ljTWFjaGluZVBhcmFtJG50cmVlIA0KICBtdHJ5IDwtIGJhc2ljTWFjaGluZVBhcmFtJG10cnkNCiAgZXRhX3hnYiA8LSBiYXNpY01hY2hpbmVQYXJhbSRldGFfeGdiIA0KICBucm91bmRzX3hnYiA8LSBiYXNpY01hY2hpbmVQYXJhbSRucm91bmRzX3hnYg0KICBlYXJseV9zdG9wX3hnYiA8LSBiYXNpY01hY2hpbmVQYXJhbSRlYXJseV9zdG9wX3hnYg0KICBtYXhfZGVwdGhfeGdiIDwtIGJhc2ljTWFjaGluZVBhcmFtJG1heF9kZXB0aF94Z2INCiAgDQogICMgUGFja2FnZXMNCiAgcGFjbWFuOjpwX2xvYWQodHJlZSkNCiAgcGFjbWFuOjpwX2xvYWQoZ2xtbmV0KQ0KICBwYWNtYW46OnBfbG9hZChyYW5kb21Gb3Jlc3QpDQogIHBhY21hbjo6cF9sb2FkKEZOTikNCiAgcGFjbWFuOjpwX2xvYWQoeGdib29zdCkNCiAgIyBwYWNtYW46OnBfbG9hZChrZXJhcykNCiAgDQogICMgUHJlcGFyaW5nIGRhdGENCiAgaW5wdXRfbmFtZXMgPC0gY29sbmFtZXModHJhaW5faW5wdXQpDQogIGlucHV0X3NpemUgPC0gZGltKHRyYWluX2lucHV0KQ0KICBkZl9pbnB1dCA8LSB0cmFpbl9pbnB1dF9zY2FsZSA8LSB0cmFpbl9pbnB1dA0KICBtYXhzIDwtIG1pbnMgPC0gTlVMTA0KICBpZihzY2FsZV9pbnB1dCl7DQogICAgbWF4cyA8LSBtYXBfZGJsKC54ID0gZGZfaW5wdXQsIC5mID0gbWF4KQ0KICAgIG1pbnMgPC0gbWFwX2RibCgueCA9IGRmX2lucHV0LCAuZiA9IG1pbikNCiAgICB0cmFpbl9pbnB1dF9zY2FsZSA8LSBzY2FsZSh0cmFpbl9pbnB1dCwgY2VudGVyID0gbWlucywgc2NhbGUgPSBtYXhzIC0gbWlucykNCiAgfQ0KICBpZihpcy5tYXRyaXgodHJhaW5faW5wdXRfc2NhbGUpKXsNCiAgICBkZl9pbnB1dCA8LSBhc190aWJibGUodHJhaW5faW5wdXRfc2NhbGUpDQogICAgbWF0cml4X2lucHV0IDwtIHRyYWluX2lucHV0X3NjYWxlDQogIH0gZWxzZXsNCiAgICBkZl9pbnB1dCA8LSB0cmFpbl9pbnB1dF9zY2FsZQ0KICAgIG1hdHJpeF9pbnB1dCA8LSBhcy5tYXRyaXgodHJhaW5faW5wdXRfc2NhbGUpDQogIH0NCiAgDQogICMgTWFjaGluZXMNCiAgbGFzc29fbWFjaGluZSA8LSBmdW5jdGlvbih4LCBsYW1iZGEwKXsNCiAgICBpZihpcy5udWxsKGxhbWJkYSkpew0KICAgICAgY3YgPC0gY3YuZ2xtbmV0KG1hdHJpeF90cmFpbl94MSwgdHJhaW5feTEsIGFscGhhID0gMSwgbGFtYmRhID0gMTBeKHNlcSgtMywyLGxlbmd0aC5vdXQgPSA1MCkpKQ0KICAgICAgbW9kIDwtIGdsbW5ldChtYXRyaXhfdHJhaW5feDEsIHRyYWluX3kxLCBhbHBoYSA9IDEsIGxhbWJkYSA9IGN2JGxhbWJkYS5taW4pDQogICAgfSBlbHNlew0KICAgICAgbW9kIDwtIGdsbW5ldChtYXRyaXhfdHJhaW5feDEsIHRyYWluX3kxLCBhbHBoYSA9IDEsIGxhbWJkYSA9IGxhbWJkYTApDQogICAgfQ0KICAgIHJlcyA8LSBwcmVkaWN0LmdsbW5ldChtb2QsIG5ld3ggPSB4KQ0KICAgIHJldHVybihsaXN0KHByZWQgPSByZXMsDQogICAgICAgICAgICAgICAgbW9kZWwgPSBtb2QpKQ0KICB9DQogIHJpZGdlX21hY2hpbmUgPC0gZnVuY3Rpb24oeCwgbGFtYmRhMCl7DQogICAgaWYoaXMubnVsbChsYW1iZGEpKXsNCiAgICAgIGN2IDwtIGN2LmdsbW5ldChtYXRyaXhfdHJhaW5feDEsIHRyYWluX3kxLCBhbHBoYSA9IDAsIGxhbWJkYSA9IDEwXihzZXEoLTMsMixsZW5ndGgub3V0ID0gNTApKSkNCiAgICAgIG1vZCA8LSBnbG1uZXQobWF0cml4X3RyYWluX3gxLCB0cmFpbl95MSwgYWxwaGEgPSAwLCBsYW1iZGEgPSBjdiRsYW1iZGEubWluKQ0KICAgIH0gZWxzZXsNCiAgICAgIG1vZCA8LSBnbG1uZXQobWF0cml4X3RyYWluX3gxLCB0cmFpbl95MSwgYWxwaGEgPSAwLCBsYW1iZGEgPSBsYW1iZGEwKQ0KICAgIH0NCiAgICByZXMgPC0gcHJlZGljdC5nbG1uZXQobW9kLCBuZXd4ID0geCkNCiAgICByZXR1cm4obGlzdChwcmVkID0gcmVzLA0KICAgICAgICAgICAgICAgIG1vZGVsID0gbW9kKSkNCiAgfQ0KICB0cmVlX21hY2hpbmUgPC0gZnVuY3Rpb24oeCwgcGEgPSBOVUxMKSB7DQogICAgbW9kIDwtIHRyZWUoYXMuZm9ybXVsYShwYXN0ZSgidHJhaW5feTF+IiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXN0ZShpbnB1dF9uYW1lcywgc2VwID0gIiIsIGNvbGxhcHNlID0gIisiKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICIiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICIiKSksIA0KICAgICAgICAgICAgICAgIGRhdGEgPSBkZl90cmFpbl94MSkNCiAgICByZXMgPC0gYXMudmVjdG9yKHByZWRpY3QobW9kLCB4KSkNCiAgICByZXR1cm4obGlzdChwcmVkID0gcmVzLA0KICAgICAgICAgICAgICAgIG1vZGVsID0gbW9kKSkNCiAgfQ0KICBrbm5fbWFjaGluZSA8LSBmdW5jdGlvbih4LCBrMCkgew0KICAgIG1vZCA8LSBrbm4ucmVnKHRyYWluID0gbWF0cml4X3RyYWluX3gxLCB0ZXN0ID0geCwgeSA9IHRyYWluX3kxLCBrID0gazApDQogICAgcmVzID0gbW9kJHByZWQNCiAgICByZXR1cm4obGlzdChwcmVkID0gcmVzLA0KICAgICAgICAgICAgICAgIG1vZGVsID0gazApKQ0KICB9DQogIFJGX21hY2hpbmUgPC0gZnVuY3Rpb24oeCwgbnRyZWUwKSB7DQogICAgaWYoaXMubnVsbChtdHJ5KSl7DQogICAgICBtb2QgPC0gcmFuZG9tRm9yZXN0KHggPSBkZl90cmFpbl94MSwgeSA9IHRyYWluX3kxLCBudHJlZSA9IG50cmVlMCkNCiAgICB9ZWxzZXsNCiAgICAgIG1vZCA8LSByYW5kb21Gb3Jlc3QoeCA9IGRmX3RyYWluX3gxLCB5ID0gdHJhaW5feTEsIG50cmVlID0gbnRyZWUwLCBtdHJ5ID0gbXRyeSkNCiAgICB9DQogICAgcmVzIDwtIGFzLnZlY3RvcihwcmVkaWN0KG1vZCwgeCkpDQogICAgcmV0dXJuKGxpc3QocHJlZCA9IHJlcywNCiAgICAgICAgICAgICAgICBtb2RlbCA9IG1vZCkpDQogIH0NCiAgeGdiX21hY2hpbmUgPSBmdW5jdGlvbih4LCBucm91bmRzX3hnYjApew0KICAgIG1vZCA8LSB4Z2Jvb3N0KGRhdGEgPSBtYXRyaXhfdHJhaW5feDEsIA0KICAgICAgICAgICAgICAgICAgIGxhYmVsID0gdHJhaW5feTEsIA0KICAgICAgICAgICAgICAgICAgIGV0YSA9IGV0YV94Z2IsDQogICAgICAgICAgICAgICAgICAgbnJvdW5kcyA9IG5yb3VuZHNfeGdiMCwNCiAgICAgICAgICAgICAgICAgICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsDQogICAgICAgICAgICAgICAgICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gZWFybHlfc3RvcF94Z2IsDQogICAgICAgICAgICAgICAgICAgbWF4X2RlcHRoID0gbWF4X2RlcHRoX3hnYiwNCiAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gMCkNCiAgICByZXMgPC0gcHJlZGljdChtb2QsIHgpDQogICAgcmV0dXJuKGxpc3QocHJlZCA9IHJlcywNCiAgICAgICAgICAgICAgICBtb2RlbCA9IG1vZCkpDQogIH0NCiAgDQogICMgQWxsIG1hY2hpbmVzDQogIGFsbF9tYWNoaW5lcyA8LSBsaXN0KGxhc3NvID0gbGFzc29fbWFjaGluZSwgDQogICAgICAgICAgICAgICAgICAgICAgIHJpZGdlID0gcmlkZ2VfbWFjaGluZSwgDQogICAgICAgICAgICAgICAgICAgICAgIGtubiA9IGtubl9tYWNoaW5lLCANCiAgICAgICAgICAgICAgICAgICAgICAgdHJlZSA9IHRyZWVfbWFjaGluZSwgDQogICAgICAgICAgICAgICAgICAgICAgIHJmID0gUkZfbWFjaGluZSwNCiAgICAgICAgICAgICAgICAgICAgICAgeGdiID0geGdiX21hY2hpbmUpDQogICMgQWxsIHBhcmFtZXRlcnMNCiAgYWxsX3BhcmFtZXRlcnMgPC0gbGlzdChsYXNzbyA9IGxhbWJkYSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgcmlkZ2UgPSBsYW1iZGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGtubiA9IGssIA0KICAgICAgICAgICAgICAgICAgICAgICAgIHRyZWUgPSAxLCANCiAgICAgICAgICAgICAgICAgICAgICAgICByZiA9IG50cmVlLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHhnYiA9IG5yb3VuZHNfeGdiKQ0KICBpZihpcy5udWxsKG1hY2hpbmVzKSl7DQogICAgbWFjaCA8LSBjKCJsYXNzbyIsICJyaWRnZSIsICJrbm4iLCAidHJlZSIsICJyZiIsICJ4Z2IiKQ0KICB9ZWxzZXsNCiAgICBtYWNoIDwtIG1hY2hpbmVzDQogIH0NCiAgIyBFeHRyYWN0aW5nIGRhdGENCiAgTSA8LSBsZW5ndGgobWFjaCkNCiAgc2l6ZV9EMSA8LSBmbG9vcihzcGxpdHMqaW5wdXRfc2l6ZVsxXSkNCiAgaWRfRDEgPC0gbG9naWNhbChpbnB1dF9zaXplWzFdKQ0KICBpZF9EMVtzYW1wbGUoaW5wdXRfc2l6ZVsxXSwgc2l6ZV9EMSldIDwtIFRSVUUNCiAgDQogIGRmX3RyYWluX3gxIDwtIGRmX2lucHV0W2lkX0QxLF0NCiAgbWF0cml4X3RyYWluX3gxIDwtIG1hdHJpeF9pbnB1dFtpZF9EMSxdDQogIHRyYWluX3kxIDwtIHRyYWluX3Jlc3BvbnNlW2lkX0QxXQ0KICBkZl90cmFpbl94MiA8LSBkZl9pbnB1dFshaWRfRDEsXQ0KICBtYXRyaXhfdHJhaW5feDIgPC0gbWF0cml4X2lucHV0WyFpZF9EMSxdDQogIA0KICAjIEZ1bmN0aW9uIHRvIGV4dHJhY3QgZGYgYW5kIG1vZGVsIGZyb20gJ21hcCcgZnVuY3Rpb24NCiAgZXh0cl9kZiA8LSBmdW5jdGlvbih4LCBpZCl7DQogICAgcmV0dXJuKHRpYmJsZSgicl97e2lkfX0iOj0gYXMudmVjdG9yKHByZWRfbVtbeF1dJHByZWQpKSkNCiAgfQ0KICBleHRyX21vZCA8LSBmdW5jdGlvbih4LCBpZCl7DQogICAgcmV0dXJuKHByZWRfbVtbeF1dJG1vZGVsKQ0KICB9DQogIA0KICBwcmVkX0QyIDwtIGMoKQ0KICBhbGxfbW9kIDwtIGMoKQ0KICBjYXQoIlxuKiBCdWlsZGluZyBiYXNpYyBtYWNoaW5lcyAuLi5cbiIpDQogIGNhdCgiXHR+IFByb2dyZXNzOiIpDQogIGZvcihtIGluIDE6TSl7DQogICAgaWYobWFjaFttXSAlaW4lIGMoInRyZWUiLCAicmYiKSl7DQogICAgICB4MF90ZXN0IDwtIGRmX3RyYWluX3gyDQogICAgfSBlbHNlIHsNCiAgICAgIHgwX3Rlc3QgPC0gbWF0cml4X3RyYWluX3gyDQogICAgfQ0KICAgIGlmKGlzLm51bGwoYWxsX3BhcmFtZXRlcnNbW21hY2hbbV1dXSkpew0KICAgICAgcGFyYV8gPC0gMQ0KICAgIH1lbHNlew0KICAgICAgcGFyYV8gPC0gYWxsX3BhcmFtZXRlcnNbW21hY2hbbV1dXQ0KICAgIH0NCiAgICBwcmVkX20gPC0gIG1hcChwYXJhXywgDQogICAgICAgICAgICAgICAgICAgLmYgPSB+IGFsbF9tYWNoaW5lc1tbbWFjaFttXV1dKHgwX3Rlc3QsIC54KSkNCiAgICB0ZW0wIDwtIGltYXBfZGZjKC54ID0gMTpsZW5ndGgocGFyYV8pLCANCiAgICAgICAgICAgICAgICAgICAgIC5mID0gZXh0cl9kZikNCiAgICB0ZW0xIDwtIGltYXAoLnggPSAxOmxlbmd0aChwYXJhXyksIA0KICAgICAgICAgICAgICAgICAuZiA9IGV4dHJfbW9kKQ0KICAgIG5hbWVzKHRlbTApIDwtIG5hbWVzKHRlbTEpIDwtIHBhc3RlMChtYWNoW21dLCAxOmxlbmd0aChwYXJhXykpDQogICAgcHJlZF9EMiA8LSBiaW5kX2NvbHMocHJlZF9EMiwgYXNfdGliYmxlKHRlbTApKQ0KICAgIGFsbF9tb2RbW21hY2hbbV1dXSA8LSB0ZW0xDQogICAgY2F0KCIgLi4uICIsIHJvdW5kKG0vTSwgMikqMTAwTCwiJSIsIHNlcCA9ICIiKQ0KICB9DQogIG1heF9NIDwtIG1pbl9NIDwtIE5VTEwNCiAgcHJlZF9EMl8gPC0gcHJlZF9EMg0KICBpZihzY2FsZV9tYWNoaW5lKXsNCiAgICBtYXhfTSA8LSBtYXBfZGJsKC54ID0gcHJlZF9EMiwgLmYgPSBtYXgpDQogICAgbWluX00gPC0gbWFwX2RibCgueCA9IHByZWRfRDIsIC5mID0gbWluKQ0KICAgIHByZWRfRDIgPC0gc2NhbGUocHJlZF9EMiwgY2VudGVyID0gbWluX00sIHNjYWxlID0gbWF4X00gLSBtaW5fTSkNCiAgfQ0KICByZXR1cm4obGlzdChmaXR0ZWRfcmVtYWluID0gcHJlZF9EMiwNCiAgICAgICAgICAgICAgbW9kZWxzID0gYWxsX21vZCwNCiAgICAgICAgICAgICAgaWQyID0gIWlkX0QxLA0KICAgICAgICAgICAgICB0cmFpbl9kYXRhID0gbGlzdCh0cmFpbl9pbnB1dCA9IHRyYWluX2lucHV0X3NjYWxlLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSB0cmFpbl9yZXNwb25zZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlZGljdF9yZW1haW5fb3JnID0gcHJlZF9EMl8sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9tYWNoaW5lID0gbWluX00sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9tYWNoaW5lID0gbWF4X00sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbl9pbnB1dCA9IG1pbnMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9pbnB1dCA9IG1heHMpKSkNCn0NCmBgYA0KDQotLS0NCg0KPiAqKkV4YW1wbGUuMSoqOiBJbiB0aGlzIGV4YW1wbGUsIHRoZSBtZXRob2QgaXMgaW1wbGVtZW50ZWQgb24gYEJvc3RvbmAgZGF0YSBvZiBgTUFTU2AgbGlicmFyeS4gVGhlIGJhc2ljIG1hY2hpbmVzICJyZiIsICJrbm4iIGFuZCAieGdiIiBhcmUgYnVpbHQgb24gdGhlIGZpcnN0IHBhcnQgb2YgdGhlIHRyYWluaW5nIGRhdGEgKCRcbWF0aGNhbHtEfV97a30kKSwgYW5kIHRoZSAqUm9vdCBNZWFuIFNxdWFyZSBFcnJvcnMqIChSTVNFKSBldmFsdWF0ZWQgb24gdGhlIHNlY29uZCBwYXJ0IG9mIHRoZSB0cmFpbmluZyBkYXRhICgkXG1hdGhjYWx7RH1fe1xlbGx9JCkgdXNlZCBmb3IgYWdncmVnYXRpb24pIGFyZSByZXBvcnRlZC4NCg0KLS0tDQoNCmBgYHtyfQ0KcGFjbWFuOjpwX2xvYWQoTUFTUykNCmRmIDwtIEJvc3Rvbg0KYmFzaWNfbWFjaGluZXMgPC0gZ2VuZXJhdGVNYWNoaW5lc19NaXgodHJhaW5faW5wdXQgPSBkZlssMToxM10sDQogICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlID0gZGZbLDE0XSwNCiAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBUUlVFLA0KICAgICAgICAgICAgICAgICBtYWNoaW5lcyA9IGMoInJmIiwgImtubiIsICJ4Z2IiKSwNCiAgICAgICAgICAgICAgICAgYmFzaWNNYWNoaW5lUGFyYW0gPSBzZXRCYXNpY1BhcmFtZXRlcl9NaXgobGFtYmRhID0gMToxMC8xMCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDEwOjIwICogMjUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrID0gYygyOjEwKSkpDQpiYXNpY19tYWNoaW5lcyR0cmFpbl9kYXRhJHByZWRpY3RfcmVtYWluX29yZyAlPiUNCiAgc3dlZXAoMSwgZGZbYmFzaWNfbWFjaGluZXMkaWQyLCAibWVkdiJdKSAlPiUNCiAgLl4yICU+JQ0KICBjb2xNZWFucyAlPiUNCiAgdCAlPiUNCiAgc3FydCAlPiUNCiAgYXNfdGliYmxlDQpgYGANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICMxRkFBRTM7Ij48dT5PcHRpbWl6YXRpb24gYWxnb3JpdGhtPC91Pjwvc3Bhbj4NCj09PQ0KDQpUaGlzIHBhcnQgcHJvdmlkZXMgZnVuY3Rpb25zIHRvIGFwcHJveGltYXRlIHRoZSBrZXkgcGFyYW1ldGVycyAkKFxhbHBoYSxcYmV0YSlcaW4oXG1hdGhiYntSfV8rXiopXjIkIG9mIHRoZSBhZ2dyZWdhdGlvbi4gVHdvIGltcG9ydGFudCBvcHRpbWl6YXRpb24gbWV0aG9kcyBhcmUgaW1wbGVtZW50ZWQ6ICoqZ3JhZGllbnQgZGVzY2VudCBhbGdvcml0aG0qKiAoYGdyYWRgKSBhbmQgKipncmlkIHNlYXJjaCoqIChgZ3JpZGApLiANCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+R3JhZGllbnQgZGVzY2VudCBhbGdvcml0aG08L3U+PC9zcGFuPg0KLS0tDQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij5GdW5jdGlvbjwvc3Bhbj4gOiBgc2V0R3JhZFBhcmFtZXRlcl9NaXhgDQoNClRoaXMgZnVuY3Rpb24gYWxsb3dzIHVzIHRvIHNldCB0aGUgdmFsdWVzIG9mIHBhcmFtZXRlcnMgbmVlZGVkIHRvIHByb2Nlc3MgdGhlIGdyYWRpZW50IGRlc2NlbnQgYWxnb3JpdGhtIHRvIGFwcHJveGltYXRlIHRoZSBoeXBlcnBhcmFtZXRlciBvZiB0aGUgYWdncmVnYXRpb24gbWV0aG9kLiANCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGB2YWxfaW5pdGAgOiBhIDJEIHZlY3RvciBvZiBpbml0aWFsIHZhbHVlIG9mIGdyYWRpZW50IGRlc2NlbnQgaXRlcmF0aW9uLiBCeSBkZWZhdWx0LCBgdmFsX2luaXQgPSBOVUxMYCBhbmQgdGhlIGFsZ29yaXRobSB3aWxsIHNlbGVjdCB0aGUgYmVzdCB2YWx1ZSAod2l0aCBzbWFsbGVzdCBjb3N0IGZ1bmN0aW9uKSBhbW9uZyBgYWxwaGFfcmFuZ2VgIGFuZCBgYmV0YV9yYW5nZWAgdmFsdWVzIG9mIHBhcmFtZXRlcnMuDQogICAgLSBgcmF0ZWAgOiB0aGUgMkQgcmVhbCB2YWx1ZWQgdmVjdG9yIG9yIGEgc3RyaW5nIG9mIGxlYXJuaW5nIHJhdGUgaW4gZ3JhZGVudCBkZXNjZW50IGFsZ29yaXRobS4gQnkgZGVmYXVsdCwgYHJhdGUgPSBOVUxMYCAob3IgImF1dG8iKSBhbmQgdGhlIHZhbHVlIGBjb2VmX2F1dG8gPSBjKDAuMSwgMC4xKWAgd2lsbCBiZSB1c2VkLiBJdCBjYW4gYWxzbyBiZSBhIGZ1bmN0aW9uYWwgcmF0ZSwgd2hpY2ggaXMgYSBzdHJpbmcgZWxlbWVudCBvZiB7ImxvZ2FyaXRobSIsICJzcXJ0cm9vdCIsICJsaW5lYXIiLCAicG9seW5vbWlhbCIsICJleHBvbmVudGlhbCJ9LiBFYWNoIHJhdGUgaXMgZGVmaW5lZCBhY2NvcmRpbmcgdG8gYGNvZWZfYCB0eXBlIGFyZ3VtZW50cyBiZWxsb3cuDQogICAgLSBgYWxwaGFfcmFuZ2VgIDogYSByYW5nZSB2ZWN0b3Igb2YgJFxhbHBoYSQgdmFsdWVzIHRvIGJlIGNvbnNpZGVyZWQgYXMgdGhlIGluaXRpYWwgdmFsdWUgaW4gZ3JhZGllbnQgc3RlcC4gQnkgZGVmYXVsdCwgYGFscGhhX3JhbmdlID0gc2VxKDAuMDAwMSwgMTAsIGxlbmd0aC5vdXQgPSA1KWAuDQogICAgLSBgYmV0YV9yYW5nZWAgOiBhIHJhbmdlIHZlY3RvciBvZiAkXGJldGEkIHZhbHVlcyB0byBiZSBjb25zaWRlcmVkIGFzIHRoZSBpbml0aWFsIHZhbHVlIGluIGdyYWRpZW50IHN0ZXAuIEJ5IGRlZmF1bHQsIGBiZXRhX3JhbmdlID0gc2VxKDAuMSwgNTAsIGxlbmd0aC5vdXQgPSA1KWAuDQogICAgLSBgbWF4X2l0ZXJgIDogbWF4aW11bSBpdGVydGFpb24gb2YgZ3JhZGllbnQgZGVzY2VudCBhbGdvcml0aG0uIEJ5IGRlZmF1bHQsIGBtYXhfaXRlciA9IDMwMGAuDQogICAgLSBgcHJpbnRfc3RlcGAgOiBhIGxvZ2ljYWwgdmFsdWUgY29udHJvbGxpbmcgd2hldGhlciB0byBwcmludCB0aGUgcmVzdWx0IG9mIGVhY2ggZ3JhZGllbnQgc3RlcCBvciBub3QgaW4gb3JkZXIgdG8ga2VlcCB0cmFjayBvZiB0aGUgYWxnb3JpdGhtLiBCeSBkZWZhdWx0LCBgcHJpbnRfc3RlcCA9IFRSVUVgLg0KICAgIC0gYHByaW50X3Jlc3VsdGAgOiBhIGxvZ2ljYWwgdmFsdWUgY29udHJvbGxpbmcgd2hldGhlciB0byBwcmludCB0aGUgcmVzdWx0IG9mIHRoZSBhbGdvcml0aG0gb3Igbm90LiBCeSBkZWZhdWx0LCBgcHJpbnRfcmVzdWx0ID0gVFJVRWAuDQogICAgLSBgZmlndXJlYCA6IGEgbG9naWNhbCB2YWx1ZSBjb250cm9sbGluZyB3aGV0aGVyIHRvIHBsb3QgYSBncmFwaGljIG9mIHRoZSByZXN1bHQgb3Igbm90LiBCeSBkZWZhdWx0LCBgZmlndXJlID0gVFJVRWAuDQogICAgLSBgY29lZl9hdXRvYCA6IHRoZSBjb25zdGFudCBsZWFybmluZyByYXRlIHdoZW4gYHJhdGUgPSBOVUxMYC4gQnkgZGVmYXVsdCwgYGNvZWZfYXV0byA9IGMoMSwgMSlgLg0KICAgIC0gYGNvZWZfbG9nYCA6IHRoZSBjb2VmZmljaW5ldCBtdWx0aXBseWluZyB0byB0aGUgKmxvZ2FyaXRobWljKiBpbmNyZW1lbnQgb2YgdGhlIGxlYXJuaW5nIHJhdGUsIGkuZS4sIHRoZSByYXRlIGlzIGByYXRlYCAkPSQgYGNvZWZfbG9nYCRcdGltZXMgXGxvZygxK3QpJCB3aGVyZSAkdCQgaXMgdGhlIG51bWVyIG51bWJlciBvZiBpdGVyYXRpb24uIEJ5IGRlZmF1bHQsIGBjb2VmX2xvZyA9IDFgLg0KICAgIC0gYGNvZWZfc3FydGAgOiB0aGUgY29lZmZpY2luZXQgbXVsdGlwbHlpbmcgdG8gdGhlICpzcXVhcmUgcm9vdCogaW5jcmVtZW50IG9mIHRoZSBsZWFybmluZyByYXRlLCBpLmUuLCB0aGUgcmF0ZSBpcyBgcmF0ZWAgJD0kIGBjb2VmX3NxcnRgJFx0aW1lcyBcc3FydHt0fSQuIEJ5IGRlZmF1bHQsIGBjb2VmX3NxcnQgPSAxYC4NCiAgICAtIGBjb2VmX2xtYCA6IHRoZSBjb2VmZmljaW5ldCBtdWx0aXBseWluZyB0byB0aGUgKmxpbmVhciogaW5jcmVtZW50IG9mIHRoZSBsZWFybmluZyByYXRlLCBpLmUuLCB0aGUgcmF0ZSBpcyBgcmF0ZWAgJD0kIGBjb2VmX2xtYCRcdGltZXMgdCQuIEJ5IGRlZmF1bHQsIGBjb2VmX2xtID0gMWAuDQogICAgLSBgZGVnX3BvbHlgIDogdGhlIGRlZ3JlZSBvZiB0aGUgKnBvbHlub21pYWwqIGluY3JlbWVudCBvZiB0aGUgbGVhcm5pbmcgcmF0ZSwgaS5lLiwgdGhlIHJhdGUgaXMgYHJhdGVgICQ9dF57XHRleHR0dHtjb2VmX3BvbHl9fSQuIEJ5IGRlZmF1bHQsIGBkZWdfcG9seSA9IDJgLg0KICAgIC0gYGJhc2VfZXhwYCA6IHRoZSBiYXNlIG9mIHRoZSAqZXhwb25lbnRpYWwqIGluY3JlbWVudCBvZiB0aGUgbGVhcm5pbmcgcmF0ZSwgaS5lLiwgdGhlIHJhdGUgaXMgYHJhdGVgICQ9JCBgYmFzZV9leHBgJF50JC4gQnkgZGVmYXVsdCwgYGJhc2VfZXhwID0gMS41YC4NCiAgICAtIGBheGVzYCA6IG5hbWVzIG9mICR4LHkkIGFuZCAkeiQtYXhpcyByZXNwZWN0aXZlbHkuIEJ5IGRlZmF1bHQsIGBheGVzID0gYygiYWxwaGEiLCAiYmV0YSIsICJMMSBub3JtIG9mIGdyYWRpZW50IilgLg0KICAgIC0gYHRpdGxlYCA6IHRoZSB0aXRsZSBvZiB0aGUgcGxvdC4gQnkgZGVmYXVsdCwgYHRpdGxlID0gTlVMTGAgYW5kIHRoZSBkZWZhdWx0IHRpdGxlIGlzIGBHcmFkaWVudCBzdGVwYC4NCiAgICAtIGB0aHJlc2hvbGRgIDogdGhlIHRocmVzaG9sZCB0byBzdG9wIHRoZSBhbGdvcml0aG0gd2hhdCB0aGUgcmVsYXRpdmUgY2hhbmdlIGlzIHNtYWxsZXIgdGhhbiB0aGlzIHZhbHVlLiBCeSBkZWZhdWx0LCBgdGhyZXNob2xkID0gMWUtMTBgLg0KICAgIA0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgYSAqbGlzdCogb2YgYWxsIHRoZSBwYXJhbWV0ZXJzIGdpdmVuIGluIGl0cyBhcmd1bWVudHMuDQoNCmBgYHtyfQ0Kc2V0R3JhZFBhcmFtZXRlcl9NaXggPC0gZnVuY3Rpb24odmFsX2luaXQgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICByYXRlID0gTlVMTCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscGhhX3JhbmdlID0gc2VxKDAuMDAwMSwgMTAsIGxlbmd0aC5vdXQgPSA1KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmV0YV9yYW5nZSA9IHNlcSgwLjEsIDUwLCBsZW5ndGgub3V0ID0gNSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9pdGVyID0gMzAwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnRfc3RlcCA9IFRSVUUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmludF9yZXN1bHQgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWd1cmUgPSBUUlVFLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29lZl9hdXRvID0gYygwLjEsMC4xKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29lZl9sb2cgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2VmX3NxcnQgPSAxLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2VmX2xtID0gMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVnX3BvbHkgPSAyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXNlX2V4cCA9IDEuNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhlcyA9IGMoImFscGhhIiwgImJldGEiLCAiTDEgbm9ybSBvZiBncmFkaWVudCIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRocmVzaG9sZCA9IDFlLTEwKSB7DQogIHJldHVybigNCiAgICBsaXN0KHZhbF9pbml0ID0gdmFsX2luaXQsDQogICAgICByYXRlID0gcmF0ZSwNCiAgICAgIGFscGhhX3JhbmdlID0gYWxwaGFfcmFuZ2UsDQogICAgICBiZXRhX3JhbmdlID0gYmV0YV9yYW5nZSwNCiAgICAgIG1heF9pdGVyID0gbWF4X2l0ZXIsDQogICAgICBwcmludF9zdGVwID0gcHJpbnRfc3RlcCwNCiAgICAgIHByaW50X3Jlc3VsdCA9IHByaW50X3Jlc3VsdCwNCiAgICAgIGZpZ3VyZSA9IGZpZ3VyZSwNCiAgICAgIGNvZWZfYXV0byA9IGNvZWZfYXV0bywNCiAgICAgIGNvZWZfbG9nID0gY29lZl9sb2csDQogICAgICBjb2VmX3NxcnQgPSBjb2VmX3NxcnQsDQogICAgICBjb2VmX2xtID0gY29lZl9sbSwNCiAgICAgIGRlZ19wb2x5ID0gZGVnX3BvbHksDQogICAgICBiYXNlX2V4cCA9IGJhc2VfZXhwLA0KICAgICAgYXhlcyA9IGF4ZXMsDQogICAgICB0aXRsZSA9IHRpdGxlLA0KICAgICAgdGhyZXNob2xkID0gdGhyZXNob2xkDQogICAgKQ0KICApDQp9DQpgYGANCg0KDQojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+RnVuY3Rpb248L3NwYW4+IDogYGdyYWRPcHRpbWl6ZXJfTWl4YA0KDQpUaGlzIGZ1bmN0aW9uIHBlcmZvcm1zIGdyYWRpZW50IGRlc2NlbnQgYWxnb3JpdGhtIHRvIGFwcHJveGltYXRlIHRoZSBtaW5pbWl6ZXIgb2YgYW55IGdpdmVuIGZ1bmN0aW9ucyAoY29udmV4IG9yIGxvY2FsbHkgY29udmV4IGFyb3VuZCBpdHMgb3B0aW1pemVyKS4NCg0KLSAqKkFyZ3VtZW50Kio6DQoNCiAgICAtIGBvYmpfZnVuYCA6IHRoZSBvYmplY3RpdmUgZnVuY3Rpb24gZm9yIHdoaWNoIGl0cyBtaW5pbWl6ZXIgaXMgdG8gYmUgZXN0aW1hdGVkLiBJdCBzaG91bGQgdGFrZSBhIDJEIHZlY3RvciBhcyBhbiBpbnB1dC4NCiAgICAtIGBzZXRQYXJhbWV0ZXJgIDogdGhlIGNvbnRyb2wgb2YgZ3JhZGllbnQgZGVzY2VudCBwYXJhbWV0ZXJzIHdoaWNoIHNob3VsZCBiZSB0aGUgZnVuY3Rpb24gYHNldEdyYWRQYXJhbWV0ZXJfTWl4KClgIGRlZmluZWQgZWFybGllci4NCg0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgYSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCg0KICAgIC0gYG9wdF9wYXJhbWAgOiB0aGUgb2JzZXJ2ZWQgdmFsdWUgb2YgdGhlIG1pbmltaXplci4NCiAgICAtIGBvcHRfZXJyb3JgIDogdGhlIHZhbHVlIG9mIHRoZSBvcHRpbWFsIHJpc2suDQogICAgLSBgYWxsX2dyYWRgIDogdGhlIHZlY3RvciBvZiBhbGwgdGhlIGdyYWRpZW50cyBjb2xsZWN0ZWQgZHVyaW5nIHRoZSB3YWxrIG9mIHRoZSBhbGdvcml0aG0uDQogICAgLSBgYWxsX3BhcmFtYCA6IHRoZSB2ZWN0b3Igb2YgYWxsIHBhcmFtZXRlcnMgY29sbGVjdGVkIGR1cmluZyB0aGUgd2FsayBvZiB0aGUgYWxnb3JpdGhtLg0KICAgIC0gYHJ1bl90aW1lYCA6IHRoZSBydW5uaW5nIHRpbWUgb2YgdGhlIGFsZ29yaXRobS4gDQoNCmBgYHtyIH0NCmdyYWRPcHRpbWl6ZXJfTWl4IDwtIGZ1bmN0aW9uKG9ial9mdW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgIHNldFBhcmFtZXRlciA9IHNldEdyYWRQYXJhbWV0ZXJfTWl4KCkpIHsNCiAgc3RhcnQudGltZSA8LSBTeXMudGltZSgpDQogICMgT3B0aW1pemF0aW9uIHN0ZXA6DQogICMgPT09PT09PT09PT09PT09PT09DQogIHNwZWNfcHJpbnQgPC0gZnVuY3Rpb24oeCwgZGlnID0gNSkgcmV0dXJuKGlmZWxzZSh4ID4gMWUtNiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmb3JtYXQoeCwgZGlnaXQgPSBkaWcsIG5zbWFsbCA9IGRpZyksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9ybWF0KHgsIHNjaWVudGlmaWMgPSBUUlVFLCBkaWdpdCA9IGRpZywgbnNtYWxsID0gZGlnKSkpDQogIGNvbGxlY3RfdmFsIDwtIGMoKQ0KICBncmFkaWVudHMgPC0gYygpDQogIGlmIChpcy5udWxsKHNldFBhcmFtZXRlciR2YWxfaW5pdCkpew0KICAgIHJhbmdlX2FscCA8LSByZXAoc2V0UGFyYW1ldGVyJGFscGhhX3JhbmdlLCBsZW5ndGgoc2V0UGFyYW1ldGVyJGJldGFfcmFuZ2UpKQ0KICAgIHJhbmdlX2JldCA8LSByZXAoc2V0UGFyYW1ldGVyJGJldGFfcmFuZ2UsIGxlbmd0aChzZXRQYXJhbWV0ZXIkYWxwaGFfcmFuZ2UpKQ0KICAgIHRlbSA8LSBtYXAyX2RibCgueCA9IHJhbmdlX2FscCwNCiAgICAgICAgICAgICAgICAgICAgLnkgPSByYW5nZV9iZXQsDQogICAgICAgICAgICAgICAgICAgIC5mID0gfiBvYmpfZnVuKGMoLngsIC55KSkpDQogICAgaWQwIDwtIHdoaWNoLm1pbih0ZW0pDQogICAgdmFsIDwtIHZhbDAgPC0gYyhyYW5nZV9hbHBbaWQwXSwgcmFuZ2VfYmV0W2lkMF0pDQogICAgZ3JhZF8gPC0gcHJhY21hOjpncmFkKA0KICAgICAgZiA9IG9ial9mdW4sDQogICAgICB4MCA9IHZhbDAsDQogICAgICBoZXBzID0gLk1hY2hpbmUkZG91YmxlLmVwcyBeICgxIC8gMykpDQogIH0gZWxzZXsNCiAgICB2YWwgPC0gdmFsMCA8LSBzZXRQYXJhbWV0ZXIkdmFsX2luaXQNCiAgICBncmFkXyA8LSBwcmFjbWE6OmdyYWQoDQogICAgICBmID0gb2JqX2Z1biwgDQogICAgICB4MCA9IHZhbDAsIA0KICAgICAgaGVwcyA9IC5NYWNoaW5lJGRvdWJsZS5lcHMgXiAoMSAvIDMpKQ0KICB9DQogIGlmKHNldFBhcmFtZXRlciRwcmludF9zdGVwKXsNCiAgICBjYXQoIlxuKiBHcmFkaWVudCBkZXNjZW50IGFsZ29yaXRobSAuLi4iKQ0KICAgIGNhdCgiXG4gIFN0ZXBcdHwgIGFscGhhICAgIDsgIGJldGEgICBcdHwgIEdyYWRpZW50IChhbHBoYSA7IGJldGEpXHR8ICBUaHJlc2hvbGQgXG4iKQ0KICAgIGNhdCgiICIsIHJlcCgiLSIsIDgwKSwgc2VwID0gIiIpDQogICAgY2F0KCJcbiAgIDAgXHR8ICIsIHNwZWNfcHJpbnQodmFsMFsxXSksIiA7ICIsIHNwZWNfcHJpbnQodmFsMFsyXSksDQogICAgICAgICJcdHwgIiwgc3BlY19wcmludChncmFkX1sxXSwgNiksICIgOyAiLCBzcGVjX3ByaW50KGdyYWRfWzJdLCA1KSwgDQogICAgICAgICIgXHR8ICIsIHNldFBhcmFtZXRlciR0aHJlc2hvbGQsICJcbiIpDQogICAgY2F0KCIgIiwgcmVwKCItIiw4MCksIHNlcCA9ICIiKQ0KICB9DQogIGlmIChpcy5udW1lcmljKHNldFBhcmFtZXRlciRyYXRlKSl7DQogICAgbGFtYmRhMCA8LSBzZXRQYXJhbWV0ZXIkcmF0ZSAvIGFicyhncmFkXykNCiAgICByYXRlX0dEIDwtICJhdXRvIg0KICB9IGVsc2V7DQogICAgcjAgPC0gc2V0UGFyYW1ldGVyJGNvZWZfYXV0byAvIGFicyhncmFkXykNCiAgICAjIFJhdGUgZnVuY3Rpb25zDQogICAgcmF0ZV9mdW5jIDwtIGxpc3QoYXV0byA9IHIwLCANCiAgICAgICAgICAgICAgICAgICAgICBsb2dhcml0aG0gPSBmdW5jdGlvbihpKSAgc2V0UGFyYW1ldGVyJGNvZWZfbG9nICogbG9nKDIgKyBpKSAqIHIwLA0KICAgICAgICAgICAgICAgICAgICAgIHNxcnRyb290ID0gZnVuY3Rpb24oaSkgc2V0UGFyYW1ldGVyJGNvZWZfc3FydCAqIHNxcnQoaSkgKiByMCwNCiAgICAgICAgICAgICAgICAgICAgICBsaW5lYXIgPSBmdW5jdGlvbihpKSBzZXRQYXJhbWV0ZXIkY29lZl9sbSAqIChpKSAqIHIwLA0KICAgICAgICAgICAgICAgICAgICAgIHBvbHlub21pYWwgPSBmdW5jdGlvbihpKSBpIF4gc2V0UGFyYW1ldGVyJGRlZ19wb2x5ICogcjAsDQogICAgICAgICAgICAgICAgICAgICAgZXhwb25lbnRpYWwgPSBmdW5jdGlvbihpKSBzZXRQYXJhbWV0ZXIkYmFzZV9leHAgXiBpICogcjApDQogICAgcmF0ZV9HRCA8LSBtYXRjaC5hcmcoc2V0UGFyYW1ldGVyJHJhdGUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIGMoImF1dG8iLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgImxvZ2FyaXRobSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAic3FydHJvb3QiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgImxpbmVhciIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAicG9seW5vbWlhbCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAiZXhwb25lbnRpYWwiKSkNCiAgICBsYW1iZGEwIDwtIHJhdGVfZnVuY1tbcmF0ZV9HRF1dDQogIH0NCiAgaSA8LSAwDQogIGdyYWQwIDwtIDEwKmdyYWRfIA0KICBpZiAoaXMubnVtZXJpYyhzZXRQYXJhbWV0ZXIkcmF0ZSkgfCByYXRlX0dEID09ICJhdXRvIikgew0KICAgIHdoaWxlIChpIDwgc2V0UGFyYW1ldGVyJG1heF9pdGVyKSB7DQogICAgICBpZihhbnkoaXMubmEoZ3JhZF8pKSl7DQogICAgICAgIHZhbDAgPC0gYyhydW5pZigxLCB2YWwwWzFdKjAuOTksIHZhbDBbMV0qMS4wMSksIA0KICAgICAgICAgICAgICAgICAgcnVuaWYoMSwgdmFsMFsyXSowLjk5LCB2YWwwWzJdKjEuMDEpKSANCiAgICAgICAgZ3JhZF8gPSBwcmFjbWE6OmdyYWQoDQogICAgICAgICAgZiA9IG9ial9mdW4sIA0KICAgICAgICAgIHgwID0gdmFsMCwgDQogICAgICAgICAgaGVwcyA9IC5NYWNoaW5lJGRvdWJsZS5lcHMgXiAoMSAvIDMpDQogICAgICAgKQ0KICAgICAgfQ0KICAgICAgdmFsIDwtIHZhbDAgLSBsYW1iZGEwICogZ3JhZF8NCiAgICAgIGlmIChhbnkodmFsIDwgMCkpew0KICAgICAgICB2YWxbdmFsIDwgMF0gPC0gdmFsMFt2YWwgPCAwXS8yDQogICAgICAgIGxhbWJkYTBbdmFsIDwgMF0gPC0gbGFtYmRhMFt2YWwgPCAwXSAvIDINCiAgICAgIH0NCiAgICAgIGlmKGkgPiA1KXsNCiAgICAgICAgc2lnbl8gPC0gc2lnbihncmFkXykgIT0gc2lnbihncmFkMCkNCiAgICAgICAgaWYoYW55KHNpZ25fKSl7DQogICAgICAgICAgbGFtYmRhMFtzaWduX10gPSBsYW1iZGEwW3NpZ25fXS8yDQogICAgICAgIH0NCiAgICAgIH0NCiAgICAgIHJlbGF0aXZlIDwtIHN1bShhYnModmFsIC0gdmFsMCkpIC8gc3VtKGFicyh2YWwwKSkNCiAgICAgIHRlc3RfdGhyZXNob2xkIDwtIG1heChyZWxhdGl2ZSwgc3VtKGFicyhncmFkXyAtIGdyYWQwKSkpDQogICAgICBpZiAodGVzdF90aHJlc2hvbGQgPiBzZXRQYXJhbWV0ZXIkdGhyZXNob2xkKXsNCiAgICAgICAgdmFsMCA8LSB2YWwNCiAgICAgICAgZ3JhZDAgPC0gZ3JhZF8NCiAgICAgIH0gZWxzZXsNCiAgICAgICAgYnJlYWsNCiAgICAgIH0NCiAgICAgIGdyYWRfIDwtIHByYWNtYTo6Z3JhZCgNCiAgICAgICAgZiA9IG9ial9mdW4sIA0KICAgICAgICB4MCA9IHZhbDAsIA0KICAgICAgICBoZXBzID0gLk1hY2hpbmUkZG91YmxlLmVwcyBeICgxIC8gMykNCiAgICAgICkNCiAgICAgIGkgPC0gaSArIDENCiAgICAgIGlmKHNldFBhcmFtZXRlciRwcmludF9zdGVwKXsNCiAgICAgICAgY2F0KCJcbiAgIiwgaSwgIlx0fCAiLCBzcGVjX3ByaW50KHZhbFsxXSwgNCksICIgOyAiLCBzcGVjX3ByaW50KHZhbFsyXSwgNCksIA0KICAgICAgICAgICAgIlx0fCAiLCBzcGVjX3ByaW50KGdyYWRfWzFdLCA1KSwgIiA7ICIsIHNwZWNfcHJpbnQoZ3JhZF9bMl0sIDUpLCANCiAgICAgICAgICAgICJcdHwgIiwgdGVzdF90aHJlc2hvbGQsICJcciIpDQogICAgICB9DQogICAgICBjb2xsZWN0X3ZhbCA8LSByYmluZChjb2xsZWN0X3ZhbCwgdmFsKQ0KICAgICAgZ3JhZGllbnRzIDwtIHJiaW5kKGdyYWRpZW50cywgZ3JhZF8pDQogICAgfQ0KICB9DQogIGVsc2V7DQogICAgd2hpbGUgKGkgPCBzZXRQYXJhbWV0ZXIkbWF4X2l0ZXIpIHsNCiAgICAgIGlmKGFueShpcy5uYShncmFkXykpKXsNCiAgICAgICAgdmFsMCA8LSBjKHJ1bmlmKDEsIHZhbDBbMV0qMC45OSwgdmFsMFsxXSoxLjAxKSwgDQogICAgICAgICAgICAgICAgICBydW5pZigxLCB2YWwwWzJdKjAuOTksIHZhbDBbMl0qMS4wMSkpIA0KICAgICAgICBncmFkXyA9IHByYWNtYTo6Z3JhZCgNCiAgICAgICAgICBmID0gb2JqX2Z1biwgDQogICAgICAgICAgeDAgPSB2YWwwLCANCiAgICAgICAgICBoZXBzID0gLk1hY2hpbmUkZG91YmxlLmVwcyBeICgxIC8gMykNCiAgICAgICApDQogICAgICB9DQogICAgICB2YWwgPC0gdmFsMCAtIGxhbWJkYTAoaSkgKiBncmFkXw0KICAgICAgaWYgKGFueSh2YWwgPCAwKSl7DQogICAgICAgIHZhbFt2YWwgPCAwXSA8LSB2YWwwW3ZhbCA8IDBdLzINCiAgICAgICAgcjBbdmFsIDwgMF0gPC0gcjBbdmFsIDwgMF0gLyAyDQogICAgICB9DQogICAgICBpZihpID4gNSl7DQogICAgICAgIHNpZ25fIDwtIHNpZ24oZ3JhZF8pICE9IHNpZ24oZ3JhZDApDQogICAgICAgIGlmKGFueShzaWduXykpew0KICAgICAgICAgIHIwW3NpZ25fXSA8LSByMFtzaWduX10gLyAyDQogICAgICAgIH0NCiAgICAgIH0NCiAgICAgIHJlbGF0aXZlIDwtIHN1bShhYnModmFsIC0gdmFsMCkpIC8gc3VtKGFicyh2YWwwKSkNCiAgICAgIHRlc3RfdGhyZXNob2xkIDwtIG1heChyZWxhdGl2ZSwgc3VtKGFicyhncmFkXyAtIGdyYWQwKSkpDQogICAgICBpZiAodGVzdF90aHJlc2hvbGQgPiBzZXRQYXJhbWV0ZXIkdGhyZXNob2xkKXsNCiAgICAgICAgdmFsMCA8LSB2YWwNCiAgICAgICAgZ3JhZDAgPC0gZ3JhZF8NCiAgICAgIH1lbHNlew0KICAgICAgICBicmVhaw0KICAgICAgfQ0KICAgICAgZ3JhZF8gPC0gcHJhY21hOjpncmFkKA0KICAgICAgICBmID0gb2JqX2Z1biwgDQogICAgICAgIHgwID0gdmFsMCwgDQogICAgICAgIGhlcHMgPSAuTWFjaGluZSRkb3VibGUuZXBzIF4gKDEgLyAzKQ0KICAgICAgKQ0KICAgICAgaWYoc2V0UGFyYW1ldGVyJHByaW50X3N0ZXApew0KICAgICAgICBjYXQoIlxuICAiLCBpLCAiXHR8ICIsIHNwZWNfcHJpbnQodmFsWzFdLCA0KSwgIiA7ICIsIHNwZWNfcHJpbnQodmFsWzJdLCA0KSwgDQogICAgICAgICAgICAiXHR8ICIsIHNwZWNfcHJpbnQoZ3JhZF9bMV0sIDUpLCAiIDsgIiwgc3BlY19wcmludChncmFkX1syXSwgNSksIA0KICAgICAgICAgICAgIlx0fCAiLCB0ZXN0X3RocmVzaG9sZCwgIlxyIikNCiAgICAgIH0NCiAgICAgIGkgPC0gaSArIDENCiAgICAgIGNvbGxlY3RfdmFsIDwtIHJiaW5kKGNvbGxlY3RfdmFsLCB2YWwpDQogICAgICBncmFkaWVudHMgPC0gcmJpbmQoZ3JhZGllbnRzLCBncmFkXykNCiAgICB9DQogIH0NCiAgb3B0X2VwIDwtIHZhbA0KICBvcHRfcmlzayA8LSBvYmpfZnVuKG9wdF9lcCkNCiAgaWYoc2V0UGFyYW1ldGVyJHByaW50X3N0ZXApew0KICAgIGNhdChyZXAoIi0iLCA4MCksIHNlcCA9ICIiKQ0KICAgIGlmKHN1bShhYnMoZ3JhZF8pKSA9PSAwKXsNCiAgICAgIGNhdCgiXG4gU3RvcHBlZHwgIiwgc3BlY19wcmludCh2YWxbMV0sIDQpLCAiIDsgIiwgc3BlY19wcmludCh2YWxbMl0sIDQpLCANCiAgICAgICAgIlx0fFx0ICIsIDAsIA0KICAgICAgICAiXHRcdHwgIiwgdGVzdF90aHJlc2hvbGQpDQogICAgfWVsc2V7DQogICAgICBjYXQoIlxuIFN0b3BwZWR8ICIsIHNwZWNfcHJpbnQodmFsWzFdLCA0KSwgIiA7ICIsIHNwZWNfcHJpbnQodmFsWzJdLCA0KSwgDQogICAgICAgICJcdHwgIiwgc3BlY19wcmludChncmFkX1sxXSksICIgOyAiLCBzcGVjX3ByaW50KGdyYWRfWzJdKSwgDQogICAgICAgICJcdHwgIiwgdGVzdF90aHJlc2hvbGQpDQogICAgfSANCiAgfQ0KICBpZihzZXRQYXJhbWV0ZXIkcHJpbnRfcmVzdWx0KXsNCiAgICBjYXQoIlxuIH4gT2JzZXJ2ZWQgcGFyYW1ldGVyOiAoYWxwaGEsIGJldGEpID0gKCIsIG9wdF9lcFsxXSwgIiwgIiwgb3B0X2VwWzJdLCAiKSBpbiIsaSwgIml0ZXJ0YWlvbnMuIikNCiAgfQ0KICBpZiAoc2V0UGFyYW1ldGVyJGZpZ3VyZSkgew0KICAgIGlmKGlzLm51bGwoc2V0UGFyYW1ldGVyJHRpdGxlKSl7DQogICAgICB0aXQgPC0gcGFzdGUoIjxiPiBMMSBub3JtIG9mIGdyYWRpZW50IGFzIGEgZnVuY3Rpb24gb2Y8L2I+ICgiLA0KICAgICAgc2V0UGFyYW1ldGVyJGF4ZXNbMV0sIiwiLCANCiAgICAgIHNldFBhcmFtZXRlciRheGVzWzJdLCANCiAgICAgICIpIikNCiAgICB9IGVsc2V7DQogICAgICB0aXQgPC0gc2V0UGFyYW1ldGVyJHRpdGxlDQogICAgfQ0KICAgIHNpeiA9IGxlbmd0aChjb2xsZWN0X3ZhbFssMV0pDQogICAgZmlnIDwtIHRpYmJsZSh4ID0gY29sbGVjdF92YWxbLDFdLA0KICAgICAgICAgICB5ID0gY29sbGVjdF92YWxbLDJdLA0KICAgICAgICAgICB6ID0gYXBwbHkoYWJzKGdyYWRpZW50cyksIDEsIHN1bSkpICU+JQ0KICAgICAgcGxvdF9seSh4ID0gfngsIHkgPSB+eSkgJT4lIA0KICAgICAgYWRkX3RyYWNlKHogPSB+eiwNCiAgICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsDQogICAgICAgICAgICAgICAgbW9kZSA9ICJsaW5lcyIsDQogICAgICAgICAgICAgICAgbGluZSA9IGxpc3Qod2lkdGggPSA2LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IH56LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcnNjYWxlID0gJ1ZpcmlkaXMnKSwNCiAgICAgICAgICAgICAgICBuYW1lID0gIkdyYWRpZW50IHN0ZXAiKSAlPiUNCiAgICAgIGFkZF90cmFjZSh4ID0gYyhvcHRfZXBbMV0sIG9wdF9lcFsxXSksDQogICAgICAgICAgICAgICAgeSA9IGMoMCwgb3B0X2VwWzJdKSwNCiAgICAgICAgICAgICAgICB6ID0gfmMoeltzaXpdLCB6W3Npel0pLA0KICAgICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwNCiAgICAgICAgICAgICAgICBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLA0KICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KCANCiAgICAgICAgICAgICAgICAgIHdpZHRoID0gMiwNCiAgICAgICAgICAgICAgICAgIGNvbG9yID0gIiM1RTg4RkMiLCANCiAgICAgICAgICAgICAgICAgIGRhc2ggPSBUUlVFKSwNCiAgICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KA0KICAgICAgICAgICAgICAgICAgc2l6ZSA9IDQsDQogICAgICAgICAgICAgICAgICBjb2xvciA9IH5jKCIjNUU4OEZDIiwgIiMzOERFMjUiKSksDQogICAgICAgICAgICAgICAgbmFtZSA9IHBhc3RlKCJPcHRpbWFsIixzZXRQYXJhbWV0ZXIkYXhlc1sxXSkpICU+JQ0KICAgICAgYWRkX3RyYWNlKHggPSBjKDAsIG9wdF9lcFsxXSksDQogICAgICAgICAgICAgICAgeSA9IGMob3B0X2VwWzJdLCBvcHRfZXBbMl0pLA0KICAgICAgICAgICAgICAgIHogPSB+Yyh6W3Npel0sIHpbc2l6XSksDQogICAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLA0KICAgICAgICAgICAgICAgIG1vZGUgPSAnbGluZXMrbWFya2VycycsDQogICAgICAgICAgICAgICAgbGluZSA9IGxpc3QoIA0KICAgICAgICAgICAgICAgICAgd2lkdGggPSAyLA0KICAgICAgICAgICAgICAgICAgY29sb3IgPSAiI0YzMTUzNiIsIA0KICAgICAgICAgICAgICAgICAgZGFzaCA9IFRSVUUpLA0KICAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoDQogICAgICAgICAgICAgICAgICBzaXplID0gNCwNCiAgICAgICAgICAgICAgICAgIGNvbG9yID0gfmMoIiNGMzE1MzYiLCAiIzM4REUyNSIpKSwNCiAgICAgICAgICAgICAgICBuYW1lID0gcGFzdGUoIk9wdGltYWwiLHNldFBhcmFtZXRlciRheGVzWzJdKSkgICU+JQ0KICAgICAgYWRkX3RyYWNlKHggPSBvcHRfZXBbMV0sDQogICAgICAgICAgICAgICAgeSA9IG9wdF9lcFsyXSwNCiAgICAgICAgICAgICAgICB6ID0gfnpbc2l6XSwNCiAgICAgICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsDQogICAgICAgICAgICAgICAgbW9kZSA9ICdtYXJrZXJzJywNCiAgICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KA0KICAgICAgICAgICAgICAgICAgc2l6ZSA9IDUsDQogICAgICAgICAgICAgICAgICBjb2xvciA9ICIjMzhERTI1IiksDQogICAgICAgICAgICAgICAgbmFtZSA9ICJPcHRpbWFsIHBvaW50IikgJT4lDQogICAgICBsYXlvdXQodGl0bGUgPSBsaXN0KHRleHQgPSB0aXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHggPSAwLjA3NSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSAwLjkyNSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZm9udCA9IGxpc3QoZmFtaWx5ID0gIlZlcmRhbmEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9ICIjNUU4OEZDIikpLA0KICAgICAgICAgICAgIGxlZ2VuZCA9IGxpc3QoeCA9IDEwMCwgeSA9IDAuNSksDQogICAgICAgICAgICAgc2NlbmUgPSBsaXN0KA0KICAgICAgICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gc2V0UGFyYW1ldGVyJGF4ZXNbMV0pLA0KICAgICAgICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gc2V0UGFyYW1ldGVyJGF4ZXNbMl0pLA0KICAgICAgICAgICAgICAgemF4aXMgPSBsaXN0KCB0aXRsZSA9IHNldFBhcmFtZXRlciRheGVzWzNdKSkpDQogICAgZmlnICU+JSBwcmludA0KICB9DQogIGVuZC50aW1lID0gU3lzLnRpbWUoKQ0KICByZXR1cm4obGlzdCgNCiAgICBvcHRfcGFyYW0gPSBvcHRfZXAsDQogICAgb3B0X2Vycm9yID0gb3B0X3Jpc2ssDQogICAgYWxsX2dyYWQgPSBncmFkaWVudHMsDQogICAgYWxsX3BhcmFtID0gY29sbGVjdF92YWwsDQogICAgcnVuX3RpbWUgPSBkaWZmdGltZShlbmQudGltZSwgDQogICAgICAgICAgICAgICAgICAgICAgICBzdGFydC50aW1lLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHVuaXRzID0gInNlY3MiKVtbMV1dDQogICkpDQp9DQpgYGANCg0KLS0tDQoNCj4gKipFeGFtcGxlLjIqKjogQXBwcm94aW1hdGUgJCQoeF4qLHleKik9XHRleHR7YXJnfVxtaW5fe3gseSlcaW5cbWF0aGJie1J9XjJ9Zih4LHkpLCQkIA0Kd2hlcmUgJCRmKHgseSk9KHgtMSleMigxK1xzaW5eMigyLjUoeC0xKSkpKyh5LTEpXjIoMStcc2luXjIoMi41KHktMSkpKSQkDQpOb3RlIHRoYXQgYXJndW1lbnQgYHZhbF9pbml0YCBpcyBjcnVjaWFsIHNpbmNlICRmJCBpcyBub3QgY29udmV4Lg0KDQotLS0NCg0KYGBge3IsIG91dC53aWR0aD0nOTAlJywgZmlnLmFsaWduPSdjZW50ZXInfQ0Kb2JqZWN0X2Z1bmMgPC0gZnVuY3Rpb24oeCkgc3VtKCh4LTEpXjIqKDErc2luKDIuNSooeC0xKSleMikpDQpwIDwtIHRpYmJsZTo6dGliYmxlKHggPSByZXAoc2VxKC00LDYsIGxlbmd0aC5vdXQgPSAzMCksMzApLCANCiAgICAgICAgICAgICAgICAgICAgICB5ID0gcmVwKHNlcSgtMyw3LCBsZW5ndGgub3V0ID0gMzApLCBlYWNoID0gMzApKSAlPiUNCiAgbXV0YXRlKHogPSBtYXAyX2RibCgueCA9IHgsIC55ID0geSwgLmYgPSB+IG9iamVjdF9mdW5jKGMoLngsLnkpKSkpICU+JQ0KICBwbG90X2x5KHggPSB+eCwgeSA9IH55LCB6ID0gfnosIHR5cGUgPSAibWVzaDNkIikgJT4lDQogIGFkZF90cmFjZSh4ID0gMSwNCiAgICAgICAgICAgIHkgPSAxLA0KICAgICAgICAgICAgeiA9IDAsDQogICAgICAgICAgICB0eXBlID0gInNjYXR0ZXIzZCIsIG1vZGUgPSAibWFya2VycyIsDQogICAgICAgICAgICBuYW1lID0gIk9wdGltYWwgcG9pbnQiKSAlPiUNCiAgbGF5b3V0KHRpdGxlID0gIkNvc3QgZnVuY3Rpb24iKQ0Kc2hvdyhwKQ0KZ2QgPC0gZ3JhZE9wdGltaXplcl9NaXgob2JqX2Z1biA9IG9iamVjdF9mdW5jLA0KICAgICAgICAgICAgICAgICAgc2V0UGFyYW1ldGVyID0gc2V0R3JhZFBhcmFtZXRlcl9NaXgodmFsX2luaXQgPSBjKDIuNCwgMy41KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJhdGUgPSAibG9nIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvZWZfYXV0byA9IGMoMC43LCAwLjcpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnRfc3RlcCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWd1cmUgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYXhlcyA9IGMoIngiLCAieSIpKSkNCmBgYA0KDQoNCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij48dT5HcmlkIHNlYXJjaCBhbGdvcml0aG08L3U+PC9zcGFuPg0KLS0tDQoNCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6ICNGMEFFMTQ7Ij5GdW5jdGlvbjwvc3Bhbj4gOiBgc2V0R3JpZFBhcmFtZXRlcl9NaXhgDQoNCi0gKipBcmd1bWVudCoqOg0KDQogICAgLSBgbWluX2FscGhhYCA6IG1pbmludW0gdmFsdWUgb2YgJFxhbHBoYSQgaW4gdGhlIGdyaWQuIEJ5IGRlZnVhbHQsIGBtaW5fYWxwaGEgPSAxZS01YC4NCiAgICAtIGBtYXhfYWxwaGFgIDogbWF4aW51bSB2YWx1ZSBvZiAkXGFscGhhJCBpbiB0aGUgZ3JpZC4gQnkgZGVmdWFsdCwgYG1heF9hbHBoYSA9IDVgLg0KICAgIC0gYG1pbl9iZXRhYCA6IG1pbmludW0gdmFsdWUgb2YgJFxiZXRhJCBpbiB0aGUgZ3JpZC4gQnkgZGVmdWFsdCwgYG1pbl9iZXRhID0gMC4xYC4NCiAgICAtIGBtYXhfYmV0YWAgOiBtYXhpbXVtIHZhbHVlIG9mICRcYmV0YSQgaW4gdGhlIGdyaWQuIEJ5IGRlZnVhbHQsIGBtYXhfYWxwaGEgPSA1MGAuDQogICAgLSBgbl9hbHBoYWAsIGBuX2JldGEgPSAzMGAgOiB0aGUgbnVtYmVyIG9mICRcYWxwaGEkIGFuZCAkXGJldGEkIHJlc3BlY3RpdmVseSBpbiB0aGUgZ3JpZC4gQnkgZGVmdWFsdCwgYG5fYWxwaGEgPSBuX2JldGEgPSAzMGAuDQogICAgLSBgcGFyYW1ldGVyc2AgOiB0aGUgbGlzdCBvZiBwYXJhbWV0ZXIgJGFscGhhJCBhbmQgJFxiZXRhJCBpbiBjYXNlIG5vbi11bmlmb3JtIGdyaWQgaXMgY29uc2lkZXJlZC4gSXQgc2hvdWxkIGJlIGEgbGlzdCBvZiB0d28gdmVjdG9ycyBjb250YWluaW5nIHRoZSB2YWx1ZXMgb2YgJFxhbHBoYSQgYW5kICRcYmV0YSQgcmVzcGVjdGl2ZWx5LiBCeSBkZWZhdWx0LCBgcGFyYW1ldGVycyA9IE5VTExgIGFuZCB0aGUgZGVmYXVsdCB1bmlmb3JtIGdyaWQgaXMgdXNlZC4NCiAgICAgLSBgYXhlc2AgOiBuYW1lcyBvZiAkeCx5JCBhbmQgJHokLWF4aXMgcmVzcGVjdGl2ZWx5LiBCeSBkZWZhdWx0LCBgYXhlcyA9IGMoImFscGhhIiwgImJldGEiLCAiUmlzayIpYC4NCiAgICAtIGB0aXRsZWAgOiB0aGUgdGl0bGUgb2YgdGhlIHBsb3QuIEJ5IGRlZmF1bHQsIGB0aXRsZSA9IE5VTExgIGFuZCB0aGUgZGVmYXVsdCB0aXRsZSBpcyBgQ3Jvc3MtdmFsaWRhdGlvbiByaXNrIGFzIGEgZnVuY3Rpb24gb2ZgICQoXGFscGhhLCBcYmV0YSkkLg0KICAgIC0gYHByaW50X3Jlc3VsdGAgOiBhIGxvZ2ljYWwgdmFsdWUgc3BlY2lmeWluZyB3aGV0aGVyIHRvIHByaW50IHRoZSBvYnNlcnZlZCByZXN1bHQgb3Igbm90Lg0KICAgIC0gYGZpZ3VyZWAgOiBhIGxvZ2ljYWwgdmFsdWUgc3BlY2lmeWluZyB3aGV0aGVyIHRvIHBsb3QgdGhlIGdyYXBoaWMgb2YgY3Jvc3MtdmFsaWRhdGlvbiBlcnJvciBvciBub3QuDQoNCi0gKipWYWx1ZSoqOg0KDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm5zIGEgKmxpc3QqIG9mIGFsbCB0aGUgcGFyYW1ldGVycyBnaXZlbiBpbiBpdHMgYXJndW1lbnRzLg0KDQpgYGB7cn0NCnNldEdyaWRQYXJhbWV0ZXJfTWl4IDwtIGZ1bmN0aW9uKG1pbl9hbHBoYSA9IDFlLTUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfYWxwaGEgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluX2JldGEgPSAwLjEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfYmV0YSA9IDUwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9hbHBoYSA9IDMwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9iZXRhID0gMzAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbWV0ZXJzID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4ZXMgPSBjKCJhbHBoYSIsICJiZXRhIiwgIlJpc2siKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50X3Jlc3VsdCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWd1cmUgPSBUUlVFKXsNCiAgcmV0dXJuKGxpc3QobWluX2FscGhhID0gbWluX2FscGhhLA0KICAgICAgICAgICAgICBtYXhfYWxwaGEgPSBtYXhfYWxwaGEsDQogICAgICAgICAgICAgIG1pbl9iZXRhID0gbWluX2JldGEsDQogICAgICAgICAgICAgIG1heF9iZXRhID0gbWF4X2JldGEsDQogICAgICAgICAgICAgIG5fYWxwaGEgPSBuX2FscGhhLA0KICAgICAgICAgICAgICBuX2JldGEgPSBuX2JldGEsDQogICAgICAgICAgICAgIGF4ZXMgPSBheGVzLA0KICAgICAgICAgICAgICB0aXRsZSA9IHRpdGxlLA0KICAgICAgICAgICAgICBwYXJhbWV0ZXJzID0gcGFyYW1ldGVycywNCiAgICAgICAgICAgICAgcHJpbnRfcmVzdWx0ID0gcHJpbnRfcmVzdWx0LA0KICAgICAgICAgICAgICBmaWd1cmUgPSBmaWd1cmUpKQ0KfQ0KYGBgDQoNCg0KIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPkZ1bmN0aW9uPC9zcGFuPiA6IGBncmlkT3B0aW1pemVyX01peGANCg0KLSAqKkFyZ3VtZW50Kio6DQogICAgDQogICAgLSBgb2JqX2Z1bmAgOiB0aGUgb2JqZWN0aXZlIGZ1bmN0aW9uIGZvciB3aGljaCBpdHMgbWluaW1pemVyIGlzIHRvIGJlIGVzdGltYXRlZC4gSXQgc2hvdWxkIGJlIGEgdW5pdmFyYXRlIGZ1bmN0aW9uIG9mIHJlYWwgcG9zaXRpdmUgdmFyaWFibGVzLg0KICAgIC0gYHNldFBhcmFtZXRlcmAgOiB0aGUgY29udHJvbCBvZiBncmlkIHNlYXJjaCBhbGdvcml0aG0gcGFyYW1ldGVycyB3aGljaCBzaG91bGQgYmUgdGhlIGZ1bmN0aW9uIGBzZXRHcmlkUGFyYW1ldGVyX01peCgpYCBkZWZpbmVkIGFib3ZlLg0KDQotICoqVmFsdWUqKjoNCg0KICAgIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBhIGxpc3Qgb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzOg0KDQogICAgLSBgb3B0X3BhcmFtYCA6IHRoZSBvYnNlcnZlZCB2YWx1ZSBvZiB0aGUgbWluaW1pemVyLg0KICAgIC0gYG9wdF9lcnJvcmAgOiB0aGUgdmFsdWUgb2Ygb3B0aW1hbCByaXNrLg0KICAgIC0gYGFsbF9yaXNrYCA6IHRoZSB2ZWN0b3Igb2YgYWxsIHRoZSBlcnJvcnMgZXZhbHVhdGVkIGF0IGFsbCB0aGUgdmFsdWVzIG9mIGNvbnNpZGVyZWQgcGFyYW1ldGVycy4NCiAgICAtIGBydW4udGltZWAgOiB0aGUgcnVubmluZyB0aW1lIG9mIHRoZSBhbGdvcml0aG0uIA0KDQpgYGB7cn0NCmdyaWRPcHRpbWl6ZXJfTWl4IDwtIGZ1bmN0aW9uKG9ial9mdW5jLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHNldFBhcmFtZXRlciA9IHNldEdyaWRQYXJhbWV0ZXJfTWl4KCkpew0KICB0MCA8LSBTeXMudGltZSgpDQogIGlmKGlzLm51bGwoc2V0UGFyYW1ldGVyJHBhcmFtZXRlcnMpKXsNCiAgICBwYXJhbV9saXN0IDwtIGxpc3QoYWxwaGEgPSAgcmVwKHNlcShzZXRQYXJhbWV0ZXIkbWluX2FscGhhLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRQYXJhbWV0ZXIkbWF4X2FscGhhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aC5vdXQgPSBzZXRQYXJhbWV0ZXIkbl9hbHBoYSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0UGFyYW1ldGVyJG5fYmV0YSksDQogICAgICAgICAgICAgICAgICAgICAgIGJldGEgPSAgcmVwKHNlcShzZXRQYXJhbWV0ZXIkbWluX2JldGEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0UGFyYW1ldGVyJG1heF9iZXRhLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGVuZ3RoLm91dCA9IHNldFBhcmFtZXRlciRuX2JldGEpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlYWNoID0gc2V0UGFyYW1ldGVyJG5fYWxwaGEpKQ0KICB9IGVsc2V7DQogICAgcGFyYW1fbGlzdCA8LSBsaXN0KGFscGhhID0gcmVwKHNldFBhcmFtZXRlciRwYXJhbWV0ZXJzW1sxXV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZW5ndGgoc2V0UGFyYW1ldGVyJHBhcmFtZXRlcnNbWzJdXSkpLA0KICAgICAgICAgICAgICAgICAgICAgICBiZXRhID0gcmVwKHNldFBhcmFtZXRlciRwYXJhbWV0ZXJzW1syXV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBlYWNoID0gbGVuZ3RoKHNldFBhcmFtZXRlciRwYXJhbWV0ZXJzW1sxXV0pKSkNCiAgfQ0KICByaXNrIDwtIG1hcDJfZGJsKC54ID0gcGFyYW1fbGlzdCRhbHBoYSwNCiAgICAgICAgICAgICAgICAgICAueSA9IHBhcmFtX2xpc3QkYmV0YSwNCiAgICAgICAgICAgICAgICAgICAuZiA9IH4gb2JqX2Z1bmMoYygueCwgLnkpKSkNCiAgaWRfb3B0IDwtIHdoaWNoLm1pbihyaXNrKQ0KICBvcHRfZXAgPC0gYyhwYXJhbV9saXN0JGFscGhhW2lkX29wdF0sIHBhcmFtX2xpc3QkYmV0YVtpZF9vcHRdKQ0KICBvcHRfcmlzayA8LSByaXNrW2lkX29wdF0NCiAgaWYoc2V0UGFyYW1ldGVyJHByaW50X3Jlc3VsdCl7DQogICAgY2F0KCJcbiogR3JpZCBzZWFyY2ggYWxnb3JpdGhtLi4uIiwgIlxuIH4gT2JzZXJ2ZWQgcGFyYW1ldGVyOiAoYWxwaGEsIGJldGEpID0gKCIsIG9wdF9lcFsxXSwgDQogICAgICAgICIsICIsIA0KICAgICAgICBvcHRfZXBbMl0sICIpIiwgDQogICAgICAgIHNlcCA9ICIiKQ0KICB9DQogIGlmKHNldFBhcmFtZXRlciRmaWd1cmUpew0KICAgIGlmKGlzLm51bGwoc2V0UGFyYW1ldGVyJHRpdGxlKSl7DQogICAgICB0aXQgPC0gcGFzdGUoIjxiPiBDcm9zcy12YWxpZGF0aW9uIHJpc2sgYXMgYSBmdW5jdGlvbiBvZjwvYj4gKCIsDQogICAgICAgICAgICAgICAgICAgc2V0UGFyYW1ldGVyJGF4ZXNbMV0sIiwiLCANCiAgICAgICAgICAgICAgICAgICBzZXRQYXJhbWV0ZXIkYXhlc1syXSwNCiAgICAgICAgICAgICAgICAgICAiKSIpDQogICAgfSBlbHNlew0KICAgICAgdGl0IDwtIHNldFBhcmFtZXRlciR0aXRsZQ0KICAgIH0NCiAgICBmaWcgPC0gdGliYmxlKGFscGhhID0gcGFyYW1fbGlzdCRhbHBoYSwgDQogICAgICAgICAgICAgICAgICBiZXRhID0gcGFyYW1fbGlzdCRiZXRhLA0KICAgICAgICAgICAgICAgICAgcmlzayA9IHJpc2spICU+JQ0KICAgICAgcGxvdF9seSh4ID0gfmFscGhhLCB5ID0gfmJldGEsIHogPSB+cmlzaywgdHlwZSA9ICJtZXNoM2QiKSAlPiUNCiAgICAgIGFkZF90cmFjZSh4ID0gYyhvcHRfZXBbMV0sIG9wdF9lcFsxXSksDQogICAgICAgICAgICAgICAgeSA9IGMoMCwgb3B0X2VwWzJdKSwNCiAgICAgICAgICAgICAgICB6ID0gYyhvcHRfcmlzaywgb3B0X3Jpc2spLA0KICAgICAgICAgICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwNCiAgICAgICAgICAgICAgICBtb2RlID0gJ2xpbmVzK21hcmtlcnMnLA0KICAgICAgICAgICAgICAgIGxpbmUgPSBsaXN0KCANCiAgICAgICAgICAgICAgICAgIHdpZHRoID0gMiwNCiAgICAgICAgICAgICAgICAgIGNvbG9yID0gIiM1RTg4RkMiLCANCiAgICAgICAgICAgICAgICAgIGRhc2ggPSBUUlVFKSwNCiAgICAgICAgICAgICAgICBtYXJrZXIgPSBsaXN0KA0KICAgICAgICAgICAgICAgICAgc2l6ZSA9IDQsDQogICAgICAgICAgICAgICAgICBjb2xvciA9IH5jKCIjNUU4OEZDIiwgIiMzOERFMjUiKSksDQogICAgICAgICAgICAgICAgbmFtZSA9IHBhc3RlKCJPcHRpbWFsIixzZXRQYXJhbWV0ZXIkYXhlc1sxXSkpICU+JQ0KICAgICAgYWRkX3RyYWNlKHggPSBjKDAsIG9wdF9lcFsxXSksDQogICAgICAgICAgICAgICAgeSA9IGMob3B0X2VwWzJdLCBvcHRfZXBbMl0pLA0KICAgICAgICAgICAgICAgIHogPSBjKG9wdF9yaXNrLCBvcHRfcmlzayksDQogICAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLA0KICAgICAgICAgICAgICAgIG1vZGUgPSAnbGluZXMrbWFya2VycycsDQogICAgICAgICAgICAgICAgbGluZSA9IGxpc3QoIA0KICAgICAgICAgICAgICAgICAgd2lkdGggPSAyLA0KICAgICAgICAgICAgICAgICAgY29sb3IgPSAiI0YzMTUzNiIsIA0KICAgICAgICAgICAgICAgICAgZGFzaCA9IFRSVUUpLA0KICAgICAgICAgICAgICAgIG1hcmtlciA9IGxpc3QoDQogICAgICAgICAgICAgICAgICBzaXplID0gNCwNCiAgICAgICAgICAgICAgICAgIGNvbG9yID0gfmMoIiNGMzE1MzYiLCAiIzM4REUyNSIpKSwNCiAgICAgICAgICAgICAgICBuYW1lID0gcGFzdGUoIk9wdGltYWwiLHNldFBhcmFtZXRlciRheGVzWzJdKSkgICU+JQ0KICAgICAgYWRkX3RyYWNlKHggPSBvcHRfZXBbMV0sDQogICAgICAgICAgICAgICAgeSA9IG9wdF9lcFsyXSwNCiAgICAgICAgICAgICAgICB6ID0gb3B0X3Jpc2ssDQogICAgICAgICAgICAgICAgdHlwZSA9ICJzY2F0dGVyM2QiLA0KICAgICAgICAgICAgICAgIG1vZGUgPSAnbWFya2VycycsDQogICAgICAgICAgICAgICAgbWFya2VyID0gbGlzdCgNCiAgICAgICAgICAgICAgICAgIHNpemUgPSA1LA0KICAgICAgICAgICAgICAgICAgY29sb3IgPSAiIzM4REUyNSIpLA0KICAgICAgICAgICAgICAgIG5hbWUgPSAiT3B0aW1hbCBwb2ludCIpICU+JQ0KICAgICAgbGF5b3V0KHRpdGxlID0gbGlzdCh0ZXh0ID0gdGl0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICB4ID0gMC4wNzUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gMC45MjUsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGZvbnQgPSBsaXN0KGZhbWlseSA9ICJWZXJkYW5hIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAiIzVFODhGQyIpKSwNCiAgICAgICAgICAgICBsZWdlbmQgPSBsaXN0KHggPSAxMDAsIHkgPSAwLjUpLA0KICAgICAgICAgICAgIHNjZW5lID0gbGlzdCh4YXhpcyA9IGxpc3QodGl0bGUgPSBzZXRQYXJhbWV0ZXIkYXhlc1sxXSksDQogICAgICAgICAgICAgICAgICAgICAgICAgIHlheGlzID0gbGlzdCh0aXRsZSA9IHNldFBhcmFtZXRlciRheGVzWzJdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgemF4aXMgPSBsaXN0KCB0aXRsZSA9IHNldFBhcmFtZXRlciRheGVzWzNdKSkpDQogICAgcHJpbnQoZmlnKQ0KICB9DQogIHQxIDwtIFN5cy50aW1lKCkNCiAgcmV0dXJuKGxpc3Qob3B0X3BhcmFtID0gb3B0X2VwLA0KICAgICAgICAgICAgICBvcHRfZXJyb3IgPSBvcHRfcmlzaywNCiAgICAgICAgICAgICAgYWxsX3Jpc2sgPSByaXNrLA0KICAgICAgICAgICAgICBydW4udGltZSA9IGRpZmZ0aW1lKHQxLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHQwLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHVuaXRzID0gInNlY3MiKVtbMV1dKQ0KICApDQp9DQpgYGANCg0KDQotLS0NCg0KPiAqKkV4YW1wbGUuMioqOiBBZ2FpbiB3aXRoIGdyaWQgc2VhcmNoLg0KDQotLS0NCg0KYGBge3IsIG91dC53aWR0aD0nOTAlJywgZmlnLmFsaWduPSdjZW50ZXInfQ0KZ3JpZCA8LSBncmlkT3B0aW1pemVyX01peChvYmpfZnVuID0gb2JqZWN0X2Z1bmMsDQogICAgICAgICAgICAgICAgICAgICBzZXRQYXJhbWV0ZXIgPSBzZXRHcmlkUGFyYW1ldGVyX01peChtaW5fYWxwaGEgPSAtMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2FscGhhID0gNCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluX2JldGEgPSAtMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2JldGEgPSA0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2FscGhhID0gMTUwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2JldGEgPSAxNTAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4ZXMgPSBjKCJ4IiwgInkiLCAieiIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJ6ID0gZih4LHkpIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlndXJlID0gVFJVRSkpDQpgYGANCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+JFxrYXBwYSQtY3Jvc3MgdmFsaWRhdGlvbiBsb3N0IGZ1bmN0aW9uPC91Pjwvc3Bhbj4NCi0tLQ0KDQpDb25zdHJ1Y3RpbmcgYWdncmVnYXRpb24gbWV0aG9kIGlzIGVxdWl2YWxlbnQgdG8gYXBwcm94aW1hdGluZyB0aGUgb3B0aW1hbCB2YWx1ZSBvZiBwYXJhbWV0ZXIgJChcYWxwaGEsXGJldGEpXGluKFxtYXRoYmJ7Un1fK14qKV4yJCBpbnRyb2R1Y2VkIGluIHNlY3Rpb24gMS4xIGJ5IG1pbmltaXppbmcgc29tZSBsb3N0IGZ1bmN0aW9uLiBJbiB0aGlzIHN0dWR5LCB3ZSBwcm9wb3NlICRca2FwcGEkLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBsb3N0IGZ1bmN0aW9uIGRlZmluZWQgYnkNCg0KXGJlZ2lue2VxdWF0aW9ufQ0KXGxhYmVse2VxOmthcHBhfQ0KXHZhcnBoaV57XGthcHBhfShoKT1cZnJhY3sxfXtca2FwcGF9XHN1bV97az0xfV57XGthcHBhfVxzdW1feyh4X2oseV9qKVxpbiBGX2t9KGdfbih4X2opLXlfaileMg0KXGVuZHtlcXVhdGlvbn0NCndoZXJlDQoNCi0gZm9yIGFueSAkaz0xLC4uLixca2FwcGEkLCAkRl9rJCBkZW5vdGVzIHRoZSAkayR0aCB2YWxpZGF0aW9uIGZvbGQuDQotICRnX24oe1xiZiByfSh4X2opKSQgaXMgdGhlIHByZWRpY3Rpb24gb2YgJHhfaiQgb2YgJEZfayQsIGNvbXB1dGVkIHVzaW5nIHRoZSBkYXRhIHBvaW50cyBmcm9tIHRoZSByZW1haW5pbmcgcGFydCAke1xjYWwgRH1fe1xlbGx9LUZfayQgYnksDQokJGdfbih7XGJmIHJ9KHhfaikpPVxmcmFje1xzdW1feyh4X2kseV9pKVxpbntcY2FsIER9X3tcZWxsfS1GX2t9eV9pS197XGFscGhhLCBcYmV0YX0oeF9qLXhfaSx7XGJmIHJ9KHhfaiktIHtcYmYgcn0oeF9pKSl9e1xzdW1feyh4X2kseV9pKVxpbntcY2FsIER9X3tcZWxsfS1GX2t9S197XGFscGhhLCBcYmV0YX0oeF9qLXhfaSx7XGJmIHJ9KHhfaiktIHtcYmYgcn0oeF9pKSl9JCQNCg0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkZ1bmN0aW9uPC91Pjwvc3Bhbj46IGBkaXN0X21hdHJpeF9NaXhgDQotLS0NCg0KVGhpcyBmdW5jdGlvbiBjb21wdXRlcyBkaWZmZXJlbnQgZGlzdGFuY2VzIGJldHdlZW4gZGF0YSBwb2ludHMgb2YgZWFjaCB0cmFpbmluZyBmb2xkcyAoJFxtYXRoY2Fse0R9X3tcZWxsfS1GX2skKSBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgdmFsaWRhdGlvbiBmb2xkICRGX2skIGZvciBhbnkgJGs9MSxcZG90cyxca2FwcGEkLiBUaGUgJFxrYXBwYSQgZGlzdGFuY2UgbWF0cmljZXMgJERfaz0oZFt7XGJmIHJ9KHhfaSkse1xiZiByfSh4X2opXSlfe2ksan0kIGZvciAkaz0xLFxkb3RzLFxrYXBwYSQsIGFyZSBjb21wdXRlZCwgd2hlcmUgdGhlIGRpc3RhbmNlICRkJCBpcyBkZWZpbmVkIGFjY29yZGluZyB0byBkaWZmZXJlbnQgdHlwZXMga2VybmVsIGZ1bmN0aW9ucy4NCg0KLSAqKkFyZ3VtZW50Kio6DQogICAgDQogICAgLSBgYmFzaWNNYWNoaW5lc2AgOiB0aGUgYmFzaWMgbWFjaGluZSBvYmplY3QsIHdoaWNoIGlzIGFuIG91dHB1dCBvZiBgZ2VuZXJhdGVNYWNoaW5lc19NaXhgIGZ1bmN0aW9uLg0KICAgIC0gYG5fY3ZgIDogdGhlIG51bWJlciAkXGthcHBhJCBvZiBjcm9zcy12YWxpZGF0aW9uIGZvbGRzLiBCeSBkZWZhdWx0LCBgbl9jdiA9IDVgLg0KICAgIC0gYGtlcm5lbGAgOiB0aGUga2VybmVsIGZ1bmN0aW9uIHVzZWQgZm9yIHRoZSBhZ2dyZWdhdGlvbiwgd2hpY2ggaXMgYW4gZWxlbWVudCBvZiB7ImdhdXNzaWFuIiwgImVwYW5lY2huaWtvdiIsICJiaXdlaWdodCIsICJ0cml3ZWlnaHQiLCAidHJpYW5ndWxhciIsICJuYWl2ZSJ9LiBCeSBkZWZhdWx0LCBga2VybmVsID0gImdhdXNzaWFuImAuDQogICAgDQotICoqVmFsdWUqKjoNCiAgICANCiAgICBUaGlzIGZ1bmN0aW9ucyByZXR1cm5zIGEgKmxpc3QqIG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCiAgICANCiAgICAtIGBkaXN0YCA6IGEgbGlzdCBvZiBkYXRhIGZyYW1lIChgdGliYmxlYCkgY29udGFpbmluZyBzdWJsaXN0cyBjb3JyZXNwb25kaW5nIHRvIGtlcm5lbCBmdW5jdGlvbnMgdXNlZCBmb3IgdGhlIGFnZ3JlZ2F0aW9uLiBFYWNoIHN1Ymxpc3QgY29udGFpbnMgYG5fY3ZgIG51bWJlcnMgb2YgZGlzdGFuY2UgbWF0cmljZXMgJERfaz0oZFt7XGJmIHJ9KHhfaSkse1xiZiByfSh4X2opXSlfe2ksan0kLCBmb3IgJGs9MSxcZG90cyxca2FwcGEkLCBjb250YWluaW5nIGRpc3RhbmNlcyBiZXR3ZWVuIHRoZSBkYXRhIHBvaW50cyBpbiB2YWxpYXRpb24gZm9sZCAoYWxvbmcgdGhlIGNvbHVtbnMpIGFuZCB0aGUgJDEtXGthcHBhJCByZW1haW5pbmcgZm9sZHMgb2YgdHJhaW5pbmcgZGF0YSAoYWxvbmcgdGhlIHJvd3MpLiBUaGUgdHlwZSBvZiBkaXN0YW5jZSBtYXRyaWNlcyBkZXBlbmRzIG9uIHRoZSBrZXJuZWwgdXNlZDoNCiAgICAgICAgLSBJZiBga2VybmVsID0gbmFpdmVgLCB0aGUgZGlzdGFuY2UgbWF0cmljZXMgY29udGFpbiB0aGUgbWF4aW11bSBkaXN0YW5jZSBiZXR3ZWVuIGRhdGEgcG9pbnRzLCBpLmUuLCAkJERfaz0oXHx7XGJmIHJ9KHhfaSkte1xiZiByfSh4X2opXHxfe1xtYXh9KV97aSxqfVx0ZXh0eyBmb3IgfWs9MSxcZG90cyxca2FwcGEuJCQNCiAgICAgICAgLSBJZiBga2VybmVsID0gdHJpYW5ndWxhcmAsIHRoZSBkaXN0YW5jZSBtYXRyaWNlcyBjb250YWluIHRoZSAkTF8xJCBkaXN0YW5jZSBiZXR3ZWVuIGRhdGEgcG9pbnRzLCBpLmUuLCBgZGlzdF9tYXRyaXhfTWl4YCAkJERfaz0oXHx7XGJmIHJ9KHhfaSkte1xiZiByfSh4X2opXHxfMSlfe2ksan1cdGV4dHsgZm9yIH1rPTEsXGRvdHMsXGthcHBhLiQkDQogICAgICAgIC0gT3RoZXJ3aXNlLCB0aGUgZGlzdGFuY2UgbWF0cmljZXMgY29udGFpbiB0aGUgc3F1YXJlZCAkTF8yJCBkaXN0YW5jZSBiZXR3ZWVuIGRhdGEgcG9pbnRzLCBpLmUuLCAkJERfaz0oXHx7XGJmIHJ9KHhfaSkte1xiZiByfSh4X2opXHxfIDJeMilfe2ksan1cdGV4dHsgZm9yIH1rPTEsXGRvdHMsXGthcHBhLiQkDQogICAgLSBgaWRfc2h1ZmZsZWAgOiB0aGUgc2h1ZmZsZWQgaW5kaWNlcyBpbiBjcm9zcy12YWxpZGF0aW9uLg0KICAgIC0gYG5fY3ZgIDogdGhlIG51bWJlciAkXGthcHBhJCBvZiBjcm9zcy12YWxpZGF0aW9uIGZvbGRzLg0KICAgIA0KYGBge3J9DQpkaXN0X21hdHJpeF9NaXggPC0gZnVuY3Rpb24oYmFzaWNNYWNoaW5lcywNCiAgICAgICAgICAgICAgICAgICAgICAgIG5fY3YgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAga2VybmVsID0gImdhdXNpYW4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgaWRfc2h1ZmZsZSA9IE5VTEwpew0KICBuIDwtIG5yb3coYmFzaWNNYWNoaW5lcyRmaXR0ZWRfcmVtYWluKQ0KICBuX2VhY2hfZm9sZCA8LSBmbG9vcihuL25fY3YpDQogICMgc2h1ZmZsZWQgaW5kaWNlcw0KICBpZihpcy5udWxsKGlkX3NodWZmbGUpKXsNCiAgICBzaHVmZmxlIDwtIDE6KG5fY3YtMSkgJT4lDQogICAgcmVwKG5fZWFjaF9mb2xkKSAlPiUNCiAgICBjKC4sIHJlcChuX2N2LCBuIC0gbl9lYWNoX2ZvbGQgKiAobl9jdiAtIDEpKSkgJT4lDQogICAgc2FtcGxlDQogIH1lbHNlew0KICAgIHNodWZmbGUgPC0gaWRfc2h1ZmZsZQ0KICB9DQogICMgdGhlIHByZWRpY3Rpb24gbWF0cml4IERfbA0KICBkZl9tYWNoIDwtIGFzLm1hdHJpeChiYXNpY01hY2hpbmVzJGZpdHRlZF9yZW1haW4pDQogIGRmX2lucHV0IDwtIGFzLm1hdHJpeChiYXNpY01hY2hpbmVzJHRyYWluX2RhdGEkdHJhaW5faW5wdXRbYmFzaWNNYWNoaW5lcyRpZDIsXSkNCiAgaWYoISAoa2VybmVsICVpbiUgYygibmFpdmUiLCAidHJpYW5ndWxhciIpKSl7DQogICAgcGFpcl9kaXN0IDwtIGZ1bmN0aW9uKE0sIE4pew0KICAgICAgbl9OIDwtIGRpbShOKQ0KICAgICAgbl9NIDwtIGRpbShNKQ0KICAgICAgcmVzXyA8LSAxOm5yb3coTikgJT4lDQogICAgICAgIG1hcF9kZmMoLmYgPSAoXChpZCkgdGliYmxlKCd7e2lkfX0nIDo9IGFzLnZlY3Rvcihyb3dTdW1zKChNIC0gbWF0cml4KHJlcChOW2lkLF0sIG5fTVsxXSksIG5jb2wgPSBuX01bMl0sIGJ5cm93ID0gVFJVRSkpXjIpKSkpKQ0KICAgICAgcmV0dXJuKHJlc18pDQogICAgfQ0KICB9DQogIGlmKGtlcm5lbCA9PSAidHJpYW5ndWxhciIpew0KICAgIHBhaXJfZGlzdCA8LSBmdW5jdGlvbihNLCBOKXsNCiAgICAgIG5fTiA8LSBkaW0oTikNCiAgICAgIG5fTSA8LSBkaW0oTSkNCiAgICAgIHJlc18gPC0gMTpucm93KE4pICU+JQ0KICAgICAgICBtYXBfZGZjKC5mID0gKFwoaWQpIHRpYmJsZSgne3tpZH19JyA6PSBhcy52ZWN0b3Iocm93U3VtcyhhYnMoTSAtIG1hdHJpeChyZXAoTltpZCxdLCBuX01bMV0pLCBuY29sID0gbl9NWzJdLCBieXJvdyA9IFRSVUUpKSkpKSkpDQogICAgICByZXR1cm4ocmVzXykNCiAgICB9DQogIH0NCiAgaWYoa2VybmVsID09ICJuYWl2ZSIpew0KICAgIHBhaXJfZGlzdCA8LSBmdW5jdGlvbihNLCBOKXsNCiAgICAgIG5fTiA8LSBkaW0oTikNCiAgICAgIG5fTSA8LSBkaW0oTSkNCiAgICAgIHJlc18gPC0gMTpucm93KE4pICU+JQ0KICAgICAgICBtYXBfZGZjKC5mID0gKFwoaWQpIHRpYmJsZSgne3tpZH19JyA6PSBhcy52ZWN0b3IoYXBwbHkoYWJzKE0gLSBtYXRyaXgocmVwKE5baWQsXSwgbl9NWzFdKSwgbmNvbCA9IG5fTVsyXSwgYnlyb3cgPSBUUlVFKSksIDEsIG1heCkpKSkpDQogICAgICByZXR1cm4ocmVzXykNCiAgICB9DQogIH0NCiAgTDEgPC0gMTpuX2N2ICU+JQ0KICAgICAgbWFwKC5mID0gKFwoeCkgcGFpcl9kaXN0KGRmX2lucHV0W3NodWZmbGUgIT0geCxdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZl9pbnB1dFtzaHVmZmxlID09IHgsXSkpKQ0KICBMMiA8LSAxOm5fY3YgJT4lDQogICAgICBtYXAoLmYgPSAoXCh4KSBwYWlyX2Rpc3QoZGZfbWFjaFtzaHVmZmxlICE9IHgsXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGZfbWFjaFtzaHVmZmxlID09IHgsXSkpKQ0KICByZXR1cm4obGlzdChkaXN0X2lucHV0ID0gTDEsDQogICAgICAgICAgICAgIGRpc3RfbWFjaGluZSA9IEwyLA0KICAgICAgICAgICAgICBpZF9zaHVmZmxlID0gc2h1ZmZsZSwNCiAgICAgICAgICAgICAgbl9jdiA9IG5fY3YpKQ0KfQ0KYGBgDQoNCg0KLS0tDQoNCj4gKipFeGFtcGxlLjMqKjogVGhlIG1ldGhvZCBgZGlzdF9tYXRyaXhfTWl4YCBpcyBpbXBsZW1lbnRlZCBvbiB0aGUgb2J0YWluZWQgYmFzaWMgbWFjaGluZXMgYnVpbHQgaW4gKkV4YW1wbGUuMSogd2l0aCB0aGUgY29ycmVzcG9uZGluZyBHYXVzc2lhbiBrZXJuZWwgZnVuY3Rpb24uDQoNCi0tLQ0KDQpgYGB7cn0NCmRpcyA8LSBkaXN0X21hdHJpeF9NaXgoYmFzaWNNYWNoaW5lcyA9IGJhc2ljX21hY2hpbmVzLA0KICAgICAgICAgICAgbl9jdiA9IDMsDQogICAgICAgICAgICBrZXJuZWwgPSAiZ2F1c3NpYW4iKQ0KZGlzJG5fY3YNCmBgYA0KDQotLS0NCg0KPiAqKkV4YW1wbGUuNCoqOiBGcm9tIHRoZSBkaXN0YW5jZSBtYXRyaXgsIHdlIGNhbiBjb21wdXRlIHRoZSBlcnJvciBjb3JyZXNwb25kaW5nIHRvIEdhdXNzaWFuIGtlcm5lbCBmdW5jdGlvbiwgdGhlbiB1c2UgYm90aCBvZiB0aGUgb3B0aW1pemF0aW9uIG1ldGhvZHMgdG8gYXBwcm94aW1hdGUgdGhlIHNtb290aGluZyBwYXJhbXRlciBpbiB0aGlzIGNhc2UuDQoNCi0tLQ0KDQpgYGB7cn0NCiMgR2F1c3NpYW4ga2VybmVsDQpnYXVzc2lhbl9rZXJuIDwtIGZ1bmN0aW9uKC5lcCA9IGMoLjA1LCAwLjAwNSksDQogICAgICAgICAgICAgICAgICAgICAgICAgIC5kaXN0X21hdHJpeCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgLnRyYWluX3Jlc3BvbnNlMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgLmludl9zaWdtYSA9IHNxcnQoLjUpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAuYWxwaGEgPSAyKXsNCiAga2Vybl9mdW4gPC0gZnVuY3Rpb24oeCwgaWQsIEQxLCBEMil7DQogICAgdGVtMCA8LSBhcy5tYXRyaXgoZXhwKC0gKHhbMV0qRDEreFsyXSpEMileKC5hbHBoYS8yKSouaW52X3NpZ21hXi5hbHBoYSkpDQogICAgeV9oYXQgPC0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSAhPSBpZF0gJSolIHRlbTAvY29sU3Vtcyh0ZW0wKQ0KICAgIHJldHVybihzdW0oKHlfaGF0IC0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSA9PSBpZF0pXjIpKQ0KICB9DQogIHRlbXAgPC0gbWFwKC54ID0gMTouZGlzdF9tYXRyaXgkbl9jdiwgDQogICAgICAgICAgICAgIC5mID0gfiBrZXJuX2Z1bih4ID0gLmVwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkID0gLngsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEMSA9IC5kaXN0X21hdHJpeCRkaXN0X2lucHV0W1sueF1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQyID0gLmRpc3RfbWF0cml4JGRpc3RfbWFjaGluZVtbLnhdXSkpDQogIHJldHVybihSZWR1Y2UoIisiLCB0ZW1wKSkNCn0NCg0KIyBLYXBwYSBjcm9zcy12YWxpZGF0aW9uIGVycm9yDQpjb3N0X2Z1biA8LSBmdW5jdGlvbih4LA0KICAgICAgICAgICAgICAgICAgICAgLmRpc3RfbWF0cml4ID0gZGlzLA0KICAgICAgICAgICAgICAgICAgICAgLmtlcm5lbF9mdW5jID0gZ2F1c3NpYW5fa2VybiwNCiAgICAgICAgICAgICAgICAgICAgIC50cmFpbl9yZXNwb25zZTIgPSBiYXNpY19tYWNoaW5lcyR0cmFpbl9kYXRhJHRyYWluX3Jlc3BvbnNlW2Jhc2ljX21hY2hpbmVzJGlkMl0sDQogICAgICAgICAgICAgICAgICAgICAuaW52X3NpZ21hID0gc3FydCguNSksDQogICAgICAgICAgICAgICAgICAgICAuYWxwaGEgPSAyKXsNCiAgcmV0dXJuKC5rZXJuZWxfZnVuYyguZXAgPSB4LA0KICAgICAgICAgICAgICAgICAgICAgIC5kaXN0X21hdHJpeCA9IC5kaXN0X21hdHJpeCwNCiAgICAgICAgICAgICAgICAgICAgICAudHJhaW5fcmVzcG9uc2UyID0gLnRyYWluX3Jlc3BvbnNlMiwNCiAgICAgICAgICAgICAgICAgICAgICAuaW52X3NpZ21hID0gLmludl9zaWdtYSwNCiAgICAgICAgICAgICAgICAgICAgICAuYWxwaGEgPSAuYWxwaGEpKQ0KfQ0KYGBgDQoNCi0gKipHcmFkaWVudCBkZXNjZW50KioNCg0KYGBge3IsIG91dC53aWR0aD0nOTAlJywgZmlnLmFsaWduPSdjZW50ZXInfQ0KIyBPcHRpbWl6YXRpb24NCm9wdF9wYXJhbV9nZCA8LSBncmFkT3B0aW1pemVyX01peChvYmpfZnVuID0gY29zdF9mdW4sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRQYXJhbWV0ZXIgPSBzZXRHcmFkUGFyYW1ldGVyX01peChyYXRlID0gImxpbmVhciIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByaW50X3N0ZXAgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmludF9yZXN1bHQgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWd1cmUgPSBUUlVFKSkNCmBgYA0KDQotICoqR3JpZCBzZWFyY2gqKg0KDQpgYGB7ciwgb3V0LndpZHRoPSc5MCUnLCBmaWcuYWxpZ249J2NlbnRlcid9DQojIE9wdGltaXphdGlvbg0Kb3B0X3BhcmFtX2dyaWQgPC0gZ3JpZE9wdGltaXplcl9NaXgob2JqX2Z1biA9IGNvc3RfZnVuLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRQYXJhbWV0ZXIgPSBzZXRHcmlkUGFyYW1ldGVyX01peChtaW5fYWxwaGEgPSAwLjAwMDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2FscGhhID0gMjAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluX2JldGEgPSAwLjAwMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfYmV0YSA9IDEwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2JldGEgPSAzMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX2FscGhhID0gMzAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlndXJlID0gVFJVRSkpDQpgYGANCg0KYGBge3J9DQpjYXQoJyogT2JzZXJ2ZWQgcGFyYW1ldGVyOlxuXHQgLSBHcmFkaWVudCBkZXNjZW50XHQ6IChhbHBoYSwgYmV0YSkgPSAoJywgDQogICAgb3B0X3BhcmFtX2dkJG9wdF9wYXJhbVsxXSwiLCIsb3B0X3BhcmFtX2dkJG9wdF9wYXJhbVsyXSwgIikiLA0KICAgICdcdCB3aXRoIGVycm9yOicsIGNvc3RfZnVuKG9wdF9wYXJhbV9nZCRvcHRfcGFyYW0pLA0KICAgICdcblx0IC0gR3JpZCBzZWFyY2hcdFx0OiAoYWxwaGEsIGJldGEpID0gKCcsb3B0X3BhcmFtX2dyaWQkb3B0X3BhcmFtWzFdLCIsIiwNCiAgICBvcHRfcGFyYW1fZ3JpZCRvcHRfcGFyYW1bMl0sDQogICAgJykgIFx0IHdpdGggZXJyb3I6JywgY29zdF9mdW4ob3B0X3BhcmFtX2dyaWQkb3B0X3BhcmFtKSkNCmBgYA0KDQoNCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+Rml0dGluZyBwYXJhbWV0ZXI8L3U+PC9zcGFuPg0KLS0tDQoNClRoaXMgZnVuY3Rpb24gZ2F0aGVycyB0aGUgY29uc3RydWN0ZWQgbWFjaGluZXMgYW5kIHBlcmZvcm0gYW4gb3B0aW1pemF0aW9uIGFsZ29yaXRobSB0byBhcHByb3hpbWF0ZSB0aGUgc21vb3RoaW5nIHBhcmFtZXRlciBmb3IgdGhlIGFnZ3JlZ2F0aW9uLCB1c2luZyBvbmx5IHRoZSByZW1haW5pbmcgcGFydCAke1xjYWwgRH1fe1xlbGx9JCBvZiB0aGUgdHJhaW5pbmcgZGF0YS4NCg0KLSAqKkFyZ3VtZW50Kio6DQogICAgDQogICAgLSBgdHJhaW5faW5wdXRgLCA6IGEgbWF0cml4IG9yIGRhdGEgZnJhbWUgb2YgdGhlIHRyYWluaW5nICppbnB1dCBkYXRhKi4NCiAgICAtIGB0cmFpbl9yZXNwb25zZWAgOiBhIHZlY3RvciBvZiB0aGUgY29ycmVzcG9uZGluZyByZXNwb25zZSB2YXJpYWJsZSBvZiB0aGUgYHRyYWluX2lucHV0YC4NCiAgICAtIGBtYWNoaW5lc2AgOiBhIHZlY3RvciBvZiBiYXNpYyBtYWNoaW5lcyB0byBiZSBjb25zdHJ1Y3RlZC4gSXQgbXVzdCBiZSBhIHN1YnNldCBvZiB7Imxhc3NvIiwgInJpZGdlIiwgImtubiIsICJ0cmVlIiwgInJmIiwgInhnYiJ9LiBCeSBkZWZhdWx0LCBgbWFjaGluZXMgPSBOVUxMYCBhbmQgYWxsIHRoZSBzaXggdHlwZXMgb2YgYmFzaWMgbWFjaGluZXMgYXJlIGJ1aWx0Lg0KICAgIC0gYHNjYWxlX2lucHV0YCA6IGEgbG9naWNhbCB2YWx1ZSBzcGVjaWZ5aW5nIHdoZXRoZXIgb3Igbm90IHRvIHNjYWxlIHRoZSBpbnB1dCBkYXRhIGJlZm9yZSBidWlsZGluZyB0aGUgYmFzaWMgcmVncmVzc2lvbiBwcmVkaWN0b3JzLiBCeSBkZWZhdWx0LCBgc2NhbGVfaW5wdXQgPSBUUlVFYC4NCiAgICAtIGBzY2FsZV9tYWNoaW5lYCA6IGEgbG9naWNhbCB2YWx1ZSBzcGVjaWZ5aW5nIHdoZXRoZXIgb3Igbm90IHRvIHNjYWxlIHRoZSBwcmVkaWN0ZWQgZmVhdHVyZXMgZ2l2ZW4gYnkgYWxsIHRoZSBiYXNpYyByZWdyZXNzaW9uIHByZWRpY3RvcnMsIGZvciBhZ2dyZWdhdGlvbi4gQnkgZGVmYXVsdCwgYHNjYWxlX21hY2hpbmUgPSBUUlVFYC4NCiAgICAtIGBzcGxpdHNgIDogdGhlIHByb3BvcnRpb24gb2YgdHJhaW5pbmcgZGF0YSAodGhlIHByb3BvcnRpb24gb2YgJHtcY2FsIER9X2skIGluICR7XGNhbCBEfV9uJCkgdXNlZCB0byBidWlsZCB0aGUgYmFzaWMgbWFjaGluZXMuIEJ5IGRlZmF1bHQsIGBzcGxpdHMgPSAuNWAuDQogICAgLSBgbl9jdmAgOiB0aGUgbnVtYmVyIG9mIGNyb3NzLXZhbGlkYXRpb24gZm9sZHMgdXNlZCB0byB0dW5lIHRoZSBzbW9vdGhpbmcgcGFyYW1ldGVyLg0KICAgIC0gYGludl9zaWdtYSwgYWxwaGFgIDogdGhlIGludmVyc2Ugbm9ybWFsaXplZCBjb25zdGFudCAkXHNpZ21hXnstMX0+MCQgYW5kIHRoZSBleHBvbmVudCAkXGFscGhhPjAkIG9mIGV4cG9uZW50aWFsIGtlcm5lbDogJEsoeCk9ZV57LVx8eC9cc2lnbWFcfF57XGFscGhhfX0kIGZvciBhbnkgJHhcaW5cbWF0aGJie1J9XmQkLiBCeSBkZWZhdWx0LCBgaW52X3NpZ21hID0gYCRcc3FydHsxLzJ9JCBhbmQgYGFscGhhID0gMmAgd2hpY2ggY29ycmVzcG9uZHMgdG8gdGhlIEdhdXNzaWFuIGtlcm5lbC4NCiAgICAtIGBrZXJuZWxzYCA6IHRoZSBrZXJuZWwgZnVuY3Rpb24gb3IgdmVjdG9yIG9mIGtlcm5lbCBmdW5jdGlvbnMgdXNlZCBmb3IgdGhlIGFnZ3JlZ2F0aW9uLiBCeSBmYXVsdCwgYGtlcm5lbHMgPSAiZ2F1c3NpYW4iYC4NCiAgICAtIGBvcHRpbWl6ZU1ldGhvZGAgOiB0aGUgb3B0aW1pemF0aW9uIG1ldGhvZHMgdXNlZCB0byBsZWFybiB0aGUgc21vb3RoaW5nIHBhcmFtZXRlci4gQnkgZGVmYXVsdCwgYG9wdGltaXplTWV0aG9kID0gImdyYWQiYCB3aGljaCBzdGFuZHMgZm9yIGdyYWRpZW50IGRlc2NlbnQgYWxnb3JpdGhtLCBhbmQgaWYgYG9wdGltaXplTWV0aG9kID0gImdyaWQiYCwgdGhlbiB0aGUgZ3JpZCBzZWFyY2ggYWxnb3JpdGhtIGlzIHVzZWQuIDxzcGFuIHN0eWxlPSJjb2xvcjogIzFGQUFFMzsiPipOb3RlIHRoYXQgdGhpcyBzaG91bGQgYmUgb2YgdGhlIHNhbWUgc2l6ZSBhcyB0aGUqPC9zcGFuPiBga2VybmVsc2AgPHNwYW4gc3R5bGU9ImNvbG9yOiAjMUZBQUUzOyI+KmFyZ3VtZW50LCBvdGhlcndpc2UgImdyaWQiIG1ldGhvZCB3aWxsIGJlIHVzZWQgZm9yIGFsbCB0aGUga2VybmVscyo8L3NwYW4+Lg0KICAgIC0gYHNldEJhc2ljTWFjaGluZVBhcmFtYCA6IGFuIG9wdGlvbiB1c2VkIHRvIHNldCB0aGUgdmFsdWVzIG9mIHRoZSBwYXJhbWV0ZXJzIG9mIHRoZSBiYXNpYyBtYWNoaW5lcy4gYHNldEJhc2ljUGFyYW1ldGVyX01peGAgZnVuY3Rpb24gc2hvdWxkIGJlIGZlZCB0byB0aGlzIGFyZ3VtZW50Lg0KICAgIC0gYHNldEdyYWRQYXJhbWAgOiBhbiBvcHRpb24gdXNlZCB0byBzZXQgdGhlIHZhbHVlcyBvZiB0aGUgcGFyYW1ldGVycyBvZiB0aGUgZ3JhZGllbnQgZGVzY2VudCBhbGdvcml0aG0uIGBzZXRHcmFkUGFyYW1ldGVyX01peGAgZnVuY3Rpb24gc2hvdWxkIGJlIGZlZCB0byBpdC4NCiAgICAtIGBzZXRHcmlkUGFyYW1gIDogYW4gb3B0aW9uIHVzZWQgdG8gc2V0IHRoZSB2YWx1ZXMgb2YgdGhlIHBhcmFtZXRlcnMgb2YgdGhlIGdyaWQgc2VhcmNoIGFsZ29yaXRobS4gYHNldEdyaWRQYXJhbWV0ZXJfTWl4YCBmdW5jdGlvbiBzaG91bGQgYmUgZmVkIHRvIGl0Lg0KICAgIA0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgYSBsaXN0IG9mIHRoZSBmb2xsb3dpbmcgb2JqZWN0czoNCg0KICAgIC0gYG9wdF9wYXJhbWV0ZXJzYCA6IHRoZSBvYnNlcnZlZCBvcHRpbWFsIHBhcmFtZXRlcnMuDQogICAgLSBgYWRkX3BhcmFtZXRlcnNgIDogb3RoZXIgYWRpdGlvbmFsIHBhcmFtZXRlcnMgc3VjaCBhcyBzY2FsaW5nIG9wdGlvbnMsIHBhcmFtZXRlcnMgb2Yga2VybmVsIGZ1bmN0aW9ucyBhbmQgdGhlIG9wdGltaXphdGlvbiBtZXRob2RzIHVzZWQuDQogICAgLSBgYmFzaWNfbWFjaGluZXNgIDogdGhlIGxpc3Qgb2YgYmFzaWMgbWFjaGluZSBvYmplY3QuDQoNCmBgYHtyfQ0KZml0X3BhcmFtZXRlcl9NaXggPC0gZnVuY3Rpb24odHJhaW5faW5wdXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9wcmVkaWN0aW9ucyA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWNoaW5lcyA9IE5VTEwsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfaW5wdXQgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfbWFjaGluZSA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGxpdHMgPSAwLjUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jdiA9IDUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnZfc2lnbWEgPSBzcXJ0KC41KSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscCA9IDIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWxzID0gImdhdXNzaWFuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9wdGltaXplTWV0aG9kID0gImdyYWQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0QmFzaWNNYWNoaW5lUGFyYW0gPSBzZXRCYXNpY1BhcmFtZXRlcl9NaXgoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldEdyYWRQYXJhbSA9IHNldEdyYWRQYXJhbWV0ZXJfTWl4KCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXRHcmlkUGFyYW0gPSBzZXRHcmlkUGFyYW1ldGVyX01peCgpKXsNCiAga2VybmVsc19sb29rdXAgPC0gYygiZ2F1c3NpYW4iLCAiZXBhbmVjaG5pa292IiwgImJpd2VpZ2h0IiwgInRyaXdlaWdodCIsICJ0cmlhbmd1bGFyIiwgIm5haXZlIikNCiAga2VybmVsX3JlYWwgPC0ga2VybmVscyAlPiUNCiAgICBzYXBwbHkoRlVOID0gZnVuY3Rpb24oeCkgcmV0dXJuKG1hdGNoLmFyZyh4LCBrZXJuZWxzX2xvb2t1cCkpKQ0KICBpZihpcy5udWxsKHRyYWluX3ByZWRpY3Rpb25zKSl7DQogICAgbWFjaDIgPC0gZ2VuZXJhdGVNYWNoaW5lc19NaXgodHJhaW5faW5wdXQgPSB0cmFpbl9pbnB1dCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlID0gdHJhaW5fcmVzcG9uc2UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IHNjYWxlX2lucHV0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGVfbWFjaGluZSA9IHNjYWxlX21hY2hpbmUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWNoaW5lcyA9IG1hY2hpbmVzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gc3BsaXRzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmFzaWNNYWNoaW5lUGFyYW0gPSBzZXRCYXNpY01hY2hpbmVQYXJhbSkNCiAgfWVsc2V7DQogICAgbWFjaDIgPC0gbGlzdChmaXR0ZWRfcmVtYWluID0gdHJhaW5fcHJlZGljdGlvbnMsDQogICAgICAgICAgICAgICAgICBtb2RlbHMgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgaWQyID0gcmVwKFRSVUUsIG5yb3codHJhaW5faW5wdXQpKSwNCiAgICAgICAgICAgICAgICAgIHRyYWluX2RhdGEgPSBsaXN0KHRyYWluX2lucHV0ID0gdHJhaW5faW5wdXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSB0cmFpbl9yZXNwb25zZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWRpY3RfcmVtYWluX29yZyA9IHRyYWluX3ByZWRpY3Rpb25zLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluX21hY2hpbmUgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X21hY2hpbmUgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluX2lucHV0ID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9pbnB1dCA9IE5VTEwpKQ0KICAgIGlmKHNjYWxlX21hY2hpbmUpew0KICAgICAgbWluXyA8LSBtYXBfZGJsKHRyYWluX3ByZWRpY3Rpb25zLCAuZiA9IG1pbikNCiAgICAgIG1heF8gPC0gbWFwX2RibCh0cmFpbl9wcmVkaWN0aW9ucywgLmYgPSBtYXgpDQogICAgICBtYWNoMiR0cmFpbl9kYXRhJG1pbl9tYWNoaW5lID0gbWluXw0KICAgICAgbWFjaDIkdHJhaW5fZGF0YSRtYXhfYW1jaGluZSA9IG1heF8NCiAgICAgIG1hY2gyJGZpdHRlZF9yZW1haW4gPC0gc2NhbGUodHJhaW5fcHJlZGljdGlvbnMsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZW50ZXIgPSBtaW5fLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGUgPSBtYXhfIC0gbWluXykNCiAgICB9DQogICAgaWYoc2NhbGVfaW5wdXQpew0KICAgICAgbWluXyA8LSBtYXBfZGJsKHRyYWluX2lucHV0LCAuZiA9IG1pbikNCiAgICAgIG1heF8gPC0gbWFwX2RibCh0cmFpbl9pbnB1dCwgLmYgPSBtYXgpDQogICAgICBtYWNoMiR0cmFpbl9kYXRhJG1pbl9pbnB1dCA9IG1pbl8NCiAgICAgIG1hY2gyJHRyYWluX2RhdGEkbWF4X2lucHV0ID0gbWF4Xw0KICAgICAgbWFjaDIkdHJhaW5fZGF0YSR0cmFpbl9pbnB1dCA8LSBzY2FsZSh0cmFpbl9pbnB1dCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNlbnRlciA9IG1pbl8sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZSA9IG1heF8gLSBtaW5fKQ0KICAgIH0NCiAgfQ0KICAjIGRpc3RhbmNlIG1hdHJpeCB0byBjb21wdXRlIGxvc3MgZnVuY3Rpb24NCiAgaWZfZXVjbGlkIDwtIEZBTFNFDQogIGlkX2V1Y2xpZCA8LSBOVUxMDQogIG5fa2VyIDwtIGxlbmd0aChrZXJuZWxzKQ0KICBkaXN0X2FsbCA8LSBsaXN0KCkNCiAgaWRfc2h1ZiA8LSBOVUxMDQogIGZvciAoa18gaW4gMTpuX2tlcil7DQogICAga2VyIDwtIGtlcm5lbF9yZWFsW2tfXQ0KICAgIGlmKGtlciA9PSAibmFpdmUiKXsNCiAgICAgIGRpc3RfYWxsW1sibmFpdmUiXV0gPC0gZGlzdF9tYXRyaXhfTWl4KGJhc2ljTWFjaGluZXMgPSBtYWNoMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jdiA9IG5fY3YsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbCA9ICJuYWl2ZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBpZF9zaHVmKQ0KICAgIH0gZWxzZXsNCiAgICAgIGlmKGtlciA9PSAidHJpYW5ndWxhciIpew0KICAgICAgICBkaXN0X2FsbFtbInRyaWFuZ3VsYXIiXV0gPC0gZGlzdF9tYXRyaXhfTWl4KGJhc2ljTWFjaGluZXMgPSBtYWNoMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fY3YgPSBuX2N2LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmVsID0gInRyaWFuZ3VsYXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWRfc2h1ZmZsZSA9IGlkX3NodWYpDQogICAgICB9IGVsc2V7DQogICAgICAgIGlmKGlmX2V1Y2xpZCl7DQogICAgICAgICAgZGlzdF9hbGxbW2tlcl1dIDwtIGRpc3RfYWxsW1tpZF9ldWNsaWRdXQ0KICAgICAgICB9IGVsc2V7DQogICAgICAgICAgZGlzdF9hbGxbW2tlcl1dIDwtIGRpc3RfbWF0cml4X01peChiYXNpY01hY2hpbmVzID0gbWFjaDIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fY3YgPSBuX2N2LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWwgPSBrZXIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkX3NodWZmbGUgPSBpZF9zaHVmKQ0KICAgICAgICAgIGlkX2V1Y2xpZCA8LSBrZXINCiAgICAgICAgICBpZl9ldWNsaWQgPC0gVFJVRQ0KICAgICAgICB9DQogICAgICB9DQogICAgfQ0KICAgIGlkX3NodWYgPC0gZGlzdF9hbGxbWzFdXSRpZF9zaHVmZmxlDQogIH0NCiAgIyBLZXJuZWwgZnVuY3Rpb25zIA0KICAjID09PT09PT09PT09PT09PT0NCiAgIyBHYXVzc2lhbg0KICBnYXVzc2lhbl9rZXJuZWwgPC0gZnVuY3Rpb24oLmVwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmRpc3RfbWF0cml4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLnRyYWluX3Jlc3BvbnNlMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5pbnZfc2lnbWEgPSBpbnZfc2lnbWEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuYWxwaGEgPSBhbHApew0KICBrZXJuX2Z1biA8LSBmdW5jdGlvbih4LCBpZCwgRDEsIEQyKXsNCiAgICB0ZW0wIDwtIGFzLm1hdHJpeChleHAoLSAoeFsxXSpEMSt4WzJdKkQyKV4oLmFscGhhLzIpKi5pbnZfc2lnbWFeLmFscGhhKSkNCiAgICB5X2hhdCA8LSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlICE9IGlkXSAlKiUgdGVtMC9jb2xTdW1zKHRlbTApDQogICAgcmV0dXJuKHN1bSgoeV9oYXQgLSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlID09IGlkXSleMikpDQogIH0NCiAgdGVtcCA8LSBtYXAoLnggPSAxOi5kaXN0X21hdHJpeCRuX2N2LCANCiAgICAgICAgICAgICAgLmYgPSB+IGtlcm5fZnVuKHggPSAuZXAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQgPSAueCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQxID0gLmRpc3RfbWF0cml4JGRpc3RfaW5wdXRbWy54XV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDIgPSAuZGlzdF9tYXRyaXgkZGlzdF9tYWNoaW5lW1sueF1dKSkNCiAgcmV0dXJuKFJlZHVjZSgiKyIsIHRlbXApKQ0KfQ0KDQojIEVwYW5lY2huaWtvdg0KICBlcGFuZWNobmlrb3Zfa2VybmVsIDwtIGZ1bmN0aW9uKC5lcCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLnRyYWluX3Jlc3BvbnNlMil7DQogIGtlcm5fZnVuIDwtIGZ1bmN0aW9uKHgsIGlkLCBEMSwgRDIpew0KICAgIHRlbTAgPC0gYXMubWF0cml4KDEtICh4WzFdKkQxK3hbMl0qRCkpDQogICAgdGVtMFt0ZW0wIDwgMF0gPSAwDQogICAgeV9oYXQgPC0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSAhPSBpZF0gJSolIHRlbTAvY29sU3Vtcyh0ZW0wKQ0KICAgIHJldHVybihzdW0oKHlfaGF0IC0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSA9PSBpZF0pXjIpKQ0KICB9DQogIHRlbXAgPC0gbWFwKC54ID0gMTouZGlzdF9tYXRyaXgkbl9jdiwgDQogICAgICAgICAgICAgIC5mID0gfiBrZXJuX2Z1bih4ID0gLmVwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkID0gLngsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEMSA9IC5kaXN0X21hdHJpeCRkaXN0X2lucHV0W1sueF1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQyID0gLmRpc3RfbWF0cml4JGRpc3RfbWFjaGluZVtbLnhdXSkpDQogIHJldHVybihSZWR1Y2UoIisiLCB0ZW1wKSkNCiAgfQ0KDQojIEJpd2VpZ2h0DQogIGJpd2VpZ2h0X2tlcm5lbCA8LSBmdW5jdGlvbiguZXAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAudHJhaW5fcmVzcG9uc2UyKXsNCiAga2Vybl9mdW4gPC0gZnVuY3Rpb24oeCwgaWQsIEQxLCBEMil7DQogICAgdGVtMCA8LSBhcy5tYXRyaXgoMS0gKHhbMV0qRDEreFsyXSpEMikpDQogICAgdGVtMFt0ZW0wIDwgMF0gPSAwDQogICAgeV9oYXQgPC0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSAhPSBpZF0gJSolIHRlbTBeMi9jb2xTdW1zKHRlbTBeMikNCiAgICB5X2hhdFtpcy5uYSh5X2hhdCldIDwtIDANCiAgICByZXR1cm4oc3VtKCh5X2hhdCAtIC50cmFpbl9yZXNwb25zZTJbLmRpc3RfbWF0cml4JGlkX3NodWZmbGUgPT0gaWRdKV4yKSkNCiAgfQ0KICB0ZW1wIDwtIG1hcCgueCA9IDE6LmRpc3RfbWF0cml4JG5fY3YsIA0KICAgICAgICAgICAgICAuZiA9IH4ga2Vybl9mdW4oeCA9IC5lcCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZCA9IC54LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDEgPSAuZGlzdF9tYXRyaXgkZGlzdF9pbnB1dFtbLnhdXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEMiA9IC5kaXN0X21hdHJpeCRkaXN0X21hY2hpbmVbWy54XV0pKQ0KICByZXR1cm4oUmVkdWNlKCIrIiwgdGVtcCkpDQogIH0NCg0KIyBUcml3ZWlnaHQNCiAgdHJpd2VpZ2h0X2tlcm5lbCA8LSBmdW5jdGlvbiguZXAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmRpc3RfbWF0cml4LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC50cmFpbl9yZXNwb25zZTIpew0KICBrZXJuX2Z1biA8LSBmdW5jdGlvbih4LCBpZCwgRDEsIEQyKXsNCiAgICB0ZW0wIDwtIGFzLm1hdHJpeCgxLSAoeFsxXSpEMSt4WzJdKkQyKSkNCiAgICB0ZW0wW3RlbTAgPCAwXSA9IDANCiAgICB5X2hhdCA8LSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlICE9IGlkXSAlKiUgdGVtMF4zL2NvbFN1bXModGVtMF4zKQ0KICAgIHlfaGF0W2lzLm5hKHlfaGF0KV0gPC0gMA0KICAgIHJldHVybihzdW0oKHlfaGF0IC0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSA9PSBpZF0pXjIpKQ0KICB9DQogIHRlbXAgPC0gbWFwKC54ID0gMTouZGlzdF9tYXRyaXgkbl9jdiwgDQogICAgICAgICAgICAgIC5mID0gfiBrZXJuX2Z1bih4ID0gLmVwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlkID0gLngsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEMSA9IC5kaXN0X21hdHJpeCRkaXN0X2lucHV0W1sueF1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQyID0gLmRpc3RfbWF0cml4JGRpc3RfbWFjaGluZVtbLnhdXSkpDQogIHJldHVybihSZWR1Y2UoIisiLCB0ZW1wKSkNCiAgfQ0KDQojIFRyaWFuZ3VsYXINCiAgdHJpYW5ndWxhcl9rZXJuZWwgPC0gZnVuY3Rpb24oLmVwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC50cmFpbl9yZXNwb25zZTIpew0KICBrZXJuX2Z1biA8LSBmdW5jdGlvbih4LCBpZCwgRDEsIEQyKXsNCiAgICB0ZW0wIDwtIGFzLm1hdHJpeCgxLSAoeFsxXSpEMSt4WzJdKkQyKSkNCiAgICB0ZW0wW3RlbTAgPCAwXSA8LSAwDQogICAgeV9oYXQgPC0gLnRyYWluX3Jlc3BvbnNlMlsuZGlzdF9tYXRyaXgkaWRfc2h1ZmZsZSAhPSBpZF0gJSolIHRlbTAvY29sU3Vtcyh0ZW0wKQ0KICAgIHlfaGF0W2lzLm5hKHlfaGF0KV0gPSAwDQogICAgcmV0dXJuKHN1bSgoeV9oYXQgLSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlID09IGlkXSleMikpDQogIH0NCiAgdGVtcCA8LSBtYXAoLnggPSAxOi5kaXN0X21hdHJpeCRuX2N2LCANCiAgICAgICAgICAgICAgLmYgPSB+IGtlcm5fZnVuKHggPSAuZXAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWQgPSAueCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEQxID0gLmRpc3RfbWF0cml4JGRpc3RfaW5wdXRbWy54XV0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDIgPSAuZGlzdF9tYXRyaXgkZGlzdF9tYWNoaW5lW1sueF1dKSkNCiAgcmV0dXJuKFJlZHVjZSgiKyIsIHRlbXApKQ0KICB9DQoNCiAgIyBOYWl2ZQ0KICBuYWl2ZV9rZXJuZWwgPC0gZnVuY3Rpb24oLmVwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC50cmFpbl9yZXNwb25zZTIpew0KICAgIGtlcm5fZnVuIDwtIGZ1bmN0aW9uKHgsIGlkLCBEMSwgRDIpew0KICAgICAgdGVtMCA8LSAoYXMubWF0cml4KCh4WzFdKkQxK3hbMl0qRDIpKSA8IDEpDQogICAgICB5X2hhdCA8LSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlICE9IGlkXSAlKiUgdGVtMC9jb2xTdW1zKHRlbTApDQogICAgICB5X2hhdFtpcy5uYSh5X2hhdCldID0gMA0KICAgICAgcmV0dXJuKHN1bSgoeV9oYXQgLSAudHJhaW5fcmVzcG9uc2UyWy5kaXN0X21hdHJpeCRpZF9zaHVmZmxlID09IGlkXSleMikpDQogICAgfQ0KICAgIHRlbXAgPC0gbWFwKC54ID0gMTouZGlzdF9tYXRyaXgkbl9jdiwgDQogICAgICAgICAgICAgICAgLmYgPSB+IGtlcm5fZnVuKHggPSAuZXAsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZCA9IC54LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEMSA9IC5kaXN0X21hdHJpeCRkaXN0X2lucHV0W1sueF1dLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRDIgPSAuZGlzdF9tYXRyaXgkZGlzdF9tYWNoaW5lW1sueF1dKSkNCiAgICByZXR1cm4oUmVkdWNlKCIrIiwgdGVtcCkpDQogIH0NCiAgDQogICMgbGlzdCBvZiBrZXJuZWwgZnVuY3Rpb25zDQogIGxpc3RfZnVucyA8LSBsaXN0KGdhdXNzaWFuID0gZ2F1c3NpYW5fa2VybmVsLA0KICAgICAgICAgICAgICAgICAgICBlcGFuZWNobmlrb3YgPSBlcGFuZWNobmlrb3Zfa2VybmVsLA0KICAgICAgICAgICAgICAgICAgICBiaXdlaWdodCA9IGJpd2VpZ2h0X2tlcm5lbCwNCiAgICAgICAgICAgICAgICAgICAgdHJpd2VpZ2h0ID0gdHJpd2VpZ2h0X2tlcm5lbCwNCiAgICAgICAgICAgICAgICAgICAgdHJpYW5ndWxhciA9IHRyaWFuZ3VsYXJfa2VybmVsLA0KICAgICAgICAgICAgICAgICAgICBuYWl2ZSA9IG5haXZlX2tlcm5lbCkNCiAgDQogICMgZXJyb3IgZm9yIGFsbCBrZXJuZWwgZnVuY3Rpb25zDQogIGVycm9yX2Z1bmMgPC0ga2VybmVsX3JlYWwgJT4lDQogICAgbWFwKC5mID0gfiAoXCh4KSBsaXN0X2Z1bnNbWy54XV0oLmVwID0geCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdF9tYXRyaXggPSBkaXN0X2FsbFtbLnhdXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAudHJhaW5fcmVzcG9uc2UyID0gdHJhaW5fcmVzcG9uc2VbbWFjaDIkaWQyXSkvbl9jdikpDQogIG5hbWVzKGVycm9yX2Z1bmMpIDwtIGtlcm5lbHMNCiAgIyBsaXN0IG9mIHByYW1ldGVyIHNldHVwDQogIGxpc3RfcGFyYW0gPC0gbGlzdChncmFkID0gc2V0R3JhZFBhcmFtLA0KICAgICAgICAgICAgICAgICAgICAgR0QgPSBzZXRHcmFkUGFyYW0sDQogICAgICAgICAgICAgICAgICAgICBncmlkID0gc2V0R3JpZFBhcmFtKQ0KICAjIGxpc3Qgb2Ygb3B0aW1pemVyDQogIGxpc3Rfb3B0aW1pemVyIDwtIGxpc3QoZ3JhZCA9IGdyYWRPcHRpbWl6ZXJfTWl4LA0KICAgICAgICAgICAgICAgICAgICAgICAgIEdEID0gZ3JhZE9wdGltaXplcl9NaXgsDQogICAgICAgICAgICAgICAgICAgICAgICAgZ3JpZCA9IGdyaWRPcHRpbWl6ZXJfTWl4KQ0KICBvcHRNZXRob2RzIDwtIG9wdGltaXplTWV0aG9kDQogIGlmKGxlbmd0aChrZXJuZWxzKSAhPSBsZW5ndGgob3B0TWV0aG9kcykpew0KICAgIHdhcm5pbmcoIioga2VybmVscyBhbmQgb3B0aW1pemF0aW9uIG1ldGhvZHMgZGlmZmVyIGluIHNpZGVzISBHcmlkIHNlYXJjaCB3aWxsIGJlIHVzZWQhIikNCiAgICBvcHRNZXRob2RzID0gcmVwKCJncmlkIiwgbGVuZ3RoKGtlcm5lbHMpKQ0KICB9DQoNCiAgIyBPcHRpbWl6YXRpb24NCiAgcGFyYW1ldGVycyA8LSBtYXAyKC54ID0ga2VybmVscywNCiAgICAgICAgICAgICAgICAgICAgIC55ID0gb3B0TWV0aG9kcywgDQogICAgICAgICAgICAgICAgICAgICAuZiA9IH4gbGlzdF9vcHRpbWl6ZXJbWy55XV0ob2JqX2Z1biA9IGVycm9yX2Z1bmNbWy54XV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0UGFyYW1ldGVyID0gbGlzdF9wYXJhbVtbLnldXSkpDQogIG5hbWVzKHBhcmFtZXRlcnMpIDwtIHBhc3RlMChrZXJuZWxfcmVhbCwgIl8iLCBvcHRNZXRob2RzKQ0KICByZXR1cm4obGlzdChvcHRfcGFyYW1ldGVycyA9IHBhcmFtZXRlcnMsDQogICAgICAgICAgICAgIGFkZF9wYXJhbWV0ZXJzID0gbGlzdChpbnZfc2lnbWEgPSBpbnZfc2lnbWEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbHAgPSBhbHAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcHRfbWV0aG9kcyA9IG9wdGltaXplTWV0aG9kKSwNCiAgICAgICAgICAgICAgYmFzaWNfbWFjaGluZXMgPSBtYWNoMikpDQp9DQpgYGANCg0KLS0tDQoNCj4gKipFeGFtcGxlLjUqKjogV2UgYXBwcm94aW1hdGUgdGhlIHNtb290aGluZyBwYXJhbWV0ZXIgb2YgYEJvc3RvbmAgZGF0YS4NCg0KLS0tDQoNCmBgYHtyfQ0KZGYgPC0gTUFTUzo6Qm9zdG9uDQp0cmFpbiA8LSBsb2dpY2FsKG5yb3coZGYpKQ0KdHJhaW5bc2FtcGxlKGxlbmd0aCh0cmFpbiksIGZsb29yKDAuNzUqbnJvdyhkZikpKV0gPC0gVFJVRQ0KDQpwYXJhbSA8LSBmaXRfcGFyYW1ldGVyX01peCh0cmFpbl9pbnB1dCA9IGRmW3RyYWluLCAxOjEzXSwNCiAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSBkZlt0cmFpbiwgMTRdLA0KICAgICAgICAgICAgICAgICAgICAgICBtYWNoaW5lcyA9IGMoImtubiIsICJyZiIsICJ4Z2IiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gLjUwLA0KICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWxzID0gYygiZ2F1c3NpYW4iLCAiYml3ZWlnaHQiLCAidHJpYW5ndWxhciIpLA0KICAgICAgICAgICAgICAgICAgICAgICBvcHRpbWl6ZU1ldGhvZCA9IGMoImdyYWQiLCAiZ3JpZCIsICJncmlkIiksDQogICAgICAgICAgICAgICAgICAgICAgIHNldEJhc2ljTWFjaGluZVBhcmFtID0gc2V0QmFzaWNQYXJhbWV0ZXJfTWl4KGsgPSAyOjYsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSAxOjUqMTAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHNfeGdiID0gMTo1KjEwMCksDQogICAgICAgICAgICAgICAgICAgICAgIHNldEdyYWRQYXJhbSA9IHNldEdyYWRQYXJhbWV0ZXJfTWl4KHJhdGUgPSAibGluZWFyIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmludF9zdGVwID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29lZl9hdXRvID0gYygwLjMsIDAuMykpLA0KICAgICAgICAgICAgICAgICAgICAgICBzZXRHcmlkUGFyYW0gPSBzZXRHcmlkUGFyYW1ldGVyX01peChtaW5fYWxwaGEgPSAwLjAwMDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2FscGhhID0gMywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fYmV0YSA9IDAuMDAwMSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfYmV0YSA9IDMpKQ0KYGBgDQoNCmBgYHtyfQ0KcGFyYW0kb3B0X3BhcmFtZXRlcnMgJT4lDQogIG1hcF9kZmMoLmYgPSB+IC54JG9wdF9wYXJhbSkgJT4lDQogIHByaW50DQpgYGANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICMxRkFBRTM7Ij48dT5QcmVkaWN0aW9uPC91Pjwvc3Bhbj4NCj09PQ0KDQpUaGUgc21vb3RoaW5nIHBhcmFtZXRlciBvYnRhaW5lZCBmcm9tIHRoZSBwcmV2aW91cyBzZWN0aW9uIGNhbiBiZSB1c2VkIHRvIG1ha2UgdGhlIGZpbmFsIHByZWRpY3Rpb25zLiANCg0KPHNwYW4gc3R5bGU9ImNvbG9yOiAjRjBBRTE0OyI+PHU+S2VybmVsIGZ1bmN0aW9uczwvdT48L3NwYW4+DQotLS0NCg0KU2V2ZXJhbCB0eXBlcyBvZiBrZXJuZWwgZnVuY3Rpb25zIHVzZWQgZm9yIHRoZSBhZ2dyZWdhdGlvbiBhcmUgZGVmaW5lZCBpbiB0aGlzIHNlY3Rpb24uDQoNCi0gKipBcmd1bWVudCoqOg0KICAgIA0KICAgIC0gYHRoZXRhYCA6IGEgMkQtdmVjdG9yIG9mIHRoZSBwYXJhbWV0ZXIgJChcYWxwaGEsIFxiZXRhKSQgdXNlZC4NCiAgICAtIGAueTJgIDogdGhlIHZlY3RvciBvZiByZXNwb25zZSB2YXJpYWJsZSBvZiB0aGUgc2Vjb25kIHBhcnQgJFxtYXRoY2Fse0R9X3tcZWxsfSQgb2YgdGhlIHRyYWluaW5nIGRhdGEsIHdoaWNoIGlzIHVzZWQgZm9yIHRoZSBhZ2dyZWdhdGlvbi4NCiAgICAtIGAuZGlzdGFuY2VgIDogdGhlIGRpc3RhbmNlIG1hdHJpeCBvYmplY3Qgb2J0YWluZWQgZnJvbSBgZGlzdF9tYXRyaXhgIGZ1bmN0aW9uLg0KICAgIC0gYC5rZXJuYCA6IHRoZSBzdHJpbmcgc3BlY2lmeWluZyB0aGUga2VybmVsIGZ1bmN0aW9uLiBCeSBkZWZhdWx0LCBgLmtlcm4gPSAiZ2F1c3NpYW4iYC4NCiAgICAtIGAuaW52X3NpZywgLmFscGAgOiB0aGUgcGFyYW1ldGVycyBvZiBleHBvbmVudGlhbCBrZXJuZWwgZnVuY3Rpb24uDQogICAgLSBgLm1ldGhgIDogdGhlIHN0cmluZyBvZiBvcHRpbWl6YXRpb24gbWV0aG9kcyB0byBiZSBsaW5rZWQgdG8gdGhlIG5hbWUgb2YgdGhlIGtlcm5lbCBmdW5jdGlvbnMgaWYgYW55IHBlcnRpY3VsYXIga2VybmVscyBhcmUgdXNlZCBtb3JlIHRoYW4gb25jZSAobWF5YmUgd2l0aCBkaWZmZXJlbnQgb3B0aW1penRhaW9uIG1ldGhvZCBzdWNoIGFzICJnYXVzc2lhbiIga2VybmVsLCBjYW4gYmUgdXNlZCB3aXRoIGJvdGggImdyYWQiIGFuZCAiZ3JpZCIgb3B0aW1pemF0aW9uIG1ldGhvZHMpLg0KICAgIA0KLSAqKlZhbHVlKio6DQoNCiAgICBUaGlzIGZ1bmN0aW9uIHJldHVybnMgdGhlIHByZWRpY3Rpb25zIG9mIHRoZSBhZ2dyZWdhdGlvbiBtZXRob2QgZXZhbHVhdGVkIHdpdGggdGhlIGdpdmVuIHBhcmFtZXRlci4NCg0KDQpgYGB7cn0NCmtlcm5lbF9wcmVkX01peCA8LSBmdW5jdGlvbih0aGV0YSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAueTIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5kaXN0MSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZGlzdDIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgLmtlcm4gPSAiZ2F1c3NpYW4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5pbnZfc2lnID0gc3FydCguNSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5hbHAgPSAyLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5tZXRoID0gTkEpew0KICBkaXN0RCA8LSBhcy5tYXRyaXgodGhldGFbMV0qLmRpc3QxK3RoZXRhWzJdKi5kaXN0MSkNCiAgIyBLZXJuZWwgZnVuY3Rpb25zDQogICMgPT09PT09PT09PT09PT09PQ0KICBnYXVzc2lhbl9rZXJuZWwgPC0gZnVuY3Rpb24oRCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5pbnZfc2lnbWEgPSAuaW52X3NpZywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5hbHBoYSA9IC5hbHApew0KICAgIHRlbTAgPC0gZXhwKC0gRF4oLmFscGhhLzIpKi5pbnZfc2lnXi5hbHBoYSkNCiAgICB5X2hhdCA8LSAueTIgJSolIHRlbTAvY29sU3Vtcyh0ZW0wKQ0KICAgIHJldHVybih0KHlfaGF0KSkNCiAgfQ0KDQogICMgRXBhbmVjaG5pa292DQogIGVwYW5lY2huaWtvdl9rZXJuZWwgPC0gZnVuY3Rpb24oRCl7DQogICAgdGVtMCA8LSAxLSBEDQogICAgdGVtMFt0ZW0wIDwgMF0gPSAwDQogICAgeV9oYXQgPC0gLnkyICUqJSB0ZW0wL2NvbFN1bXModGVtMCkNCiAgICByZXR1cm4odCh5X2hhdCkpDQogIH0NCiAgIyBCaXdlaWdodA0KICBiaXdlaWdodF9rZXJuZWwgPC0gZnVuY3Rpb24oRCl7DQogICAgdGVtMCA8LSAxLSBEDQogICAgdGVtMFt0ZW0wIDwgMF0gPSAwDQogICAgeV9oYXQgPC0gLnkyICUqJSB0ZW0wXjIvY29sU3Vtcyh0ZW0wXjIpDQogICAgeV9oYXRbaXMubmEoeV9oYXQpXSA8LSAwDQogICAgcmV0dXJuKHQoeV9oYXQpKQ0KICB9DQoNCiAgIyBUcml3ZWlnaHQNCiAgdHJpd2VpZ2h0X2tlcm5lbCA8LSBmdW5jdGlvbihEKXsNCiAgICB0ZW0wIDwtIDEtIEQNCiAgICB0ZW0wW3RlbTAgPCAwXSA9IDANCiAgICB5X2hhdCA8LSAueTIgJSolIHRlbTBeMy9jb2xTdW1zKHRlbTBeMykNCiAgICB5X2hhdFtpcy5uYSh5X2hhdCldIDwtIDANCiAgICByZXR1cm4odCh5X2hhdCkpDQogIH0NCg0KICAjIFRyaWFuZ3VsYXINCiAgdHJpYW5ndWxhcl9rZXJuZWwgPC0gZnVuY3Rpb24oRCl7DQogICAgdGVtMCA8LSAxLSBEDQogICAgdGVtMFt0ZW0wIDwgMF0gPC0gMA0KICAgIHlfaGF0IDwtIC55MiAlKiUgdGVtMC9jb2xTdW1zKHRlbTApDQogICAgeV9oYXRbaXMubmEoeV9oYXQpXSA9IDANCiAgICByZXR1cm4odCh5X2hhdCkpDQogfQ0KICAjIE5haXZlDQogIG5haXZlX2tlcm5lbCA8LSBmdW5jdGlvbihEKXsNCiAgICAgIHRlbTAgPC0gKEQgPCAxKQ0KICAgICAgeV9oYXQgPC0gLnkyICUqJSB0ZW0wL2NvbFN1bXModGVtMCkNCiAgICAgIHlfaGF0W2lzLm5hKHlfaGF0KV0gPSAwDQogICAgICByZXR1cm4odCh5X2hhdCkpDQogIH0NCiAgIyBQcmVkaWN0aW9uDQogIGtlcm5lbF9saXN0IDwtIGxpc3QoZ2F1c3NpYW4gPSBnYXVzc2lhbl9rZXJuZWwsDQogICAgICAgICAgICAgICAgICAgICAgZXBhbmVjaG5pa292ID0gZXBhbmVjaG5pa292X2tlcm5lbCwNCiAgICAgICAgICAgICAgICAgICAgICBiaXdlaWdodCA9IGJpd2VpZ2h0X2tlcm5lbCwNCiAgICAgICAgICAgICAgICAgICAgICB0cml3ZWlnaHQgPSB0cml3ZWlnaHRfa2VybmVsLA0KICAgICAgICAgICAgICAgICAgICAgIHRyaWFuZ3VsYXIgPSB0cmlhbmd1bGFyX2tlcm5lbCwNCiAgICAgICAgICAgICAgICAgICAgICBuYWl2ZSA9IG5haXZlX2tlcm5lbCkNCiAgcmVzIDwtIHRpYmJsZShhcy52ZWN0b3Ioa2VybmVsX2xpc3RbWy5rZXJuXV0oRCA9IGRpc3REKSkpDQogIG5hbWVzKHJlcykgPC0gaWZlbHNlKGlzLm5hKC5tZXRoKSwgDQogICAgICAgICAgICAgICAgICAgICAgIC5rZXJuLCANCiAgICAgICAgICAgICAgICAgICAgICAgcGFzdGUwKC5rZXJuLCAnXycsIC5tZXRoKSkNCiAgcmV0dXJuKHJlcykNCn0NCmBgYA0KDQoNCjxzcGFuIHN0eWxlPSJjb2xvcjogI0YwQUUxNDsiPjx1PkZ1bmN0aW9uczwvdT48L3NwYW4+OiBgcHJlZGljdF9NaXhgDQotLS0NCg0KLSAqKkFyZ3VtZW50Kio6DQogICAgDQogICAgLSBgZml0dGVkX21vZGVsc2AgOiB0aGUgb2JqZWN0IG9idGFpbmVkIGZyb20gYGZpdF9wYXJhbWV0ZXJfTWl4YCBmdW5jdGlvbi4NCiAgICAtIGBuZXdfZGF0YWAgOiB0aGUgdGVzdGluZyBkYXRhIHRvIGJlIHByZWRpY3RlZC4NCiAgICAtIGBuZXdfcHJlZGAgOiB0aGUgcHJlZGljdGlvbnMgb2YgdGhlIHRlc3RpbmcgZGF0YSBgbmV3X2RhdGFgICh3aGVuIHRoZSBiYXNpYyBtYWNoaW5lcyBhcmUgbm90IGNvbnN0cnVjdGVkKS4NCiAgICAtIGB0ZXN0X3Jlc3BvbnNlYCA6IHRoZSB0ZXN0aW5nIHJlc3BvbnNlIHZhcmlhYmxlLCBpdCBpcyBvcHRpb25hbC4gSWYgaXQgaXMgZ2l2ZW4sIHRoZSBtZWFuIHNxdWFyZSBlcnJvciBvbiB0aGUgdGVzdGluZyBkYXRhIGlzIGFsc28gY29tcHV0ZWQuIEJ5IGRlZmF1bHQsIGB0ZXN0X3Jlc3BvbnNlID0gTlVMTGAuDQogICAgDQotICoqVmFsdWUqKjoNCg0KICAgIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBhICpsaXN0KiBvZiB0aGUgZm9sbG93aW5nIG9iamVjdHM6DQogICAgDQogICAgLSBgZml0dGVkX2FnZ3JlZ2F0ZWAgOiB0aGUgcHJlZGljdGlvbnMgYnkgdGhlIGFnZ3JlZ2F0aW9uIG1ldGhvZHMuDQogICAgLSBgZml0dGVkX21hY2hpbmVgIDogdGhlIHByZWRpY3Rpb25zIGdpdmVuIGJ5IGFsbCB0aGUgYmFzaWMgbWFjaGluZXMuDQogICAgLSBgbXNlYCA6IHRoZSBtZWFuIHNxdWFyZSBlcnJvciBjb21wdXRlZCBvbmx5IGlmIHRoZSBgdGVzdF9yZXBvbnNlYCBhcmd1bWVudCBpcyBub3RlIGBOVUxMYC4NCg0KDQpgYGB7cn0NCiMgUHJlZGljdGlvbg0KcHJlZGljdF9NaXggPC0gZnVuY3Rpb24oZml0dGVkX21vZGVscywNCiAgICAgICAgICAgICAgICAgICAgICAgIG5ld19kYXRhLA0KICAgICAgICAgICAgICAgICAgICAgICAgbmV3X3ByZWQgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgdGVzdF9yZXNwb25zZSA9IE5VTEwpew0KICBvcHRfcGFyYW0gPC0gZml0dGVkX21vZGVscyRvcHRfcGFyYW1ldGVycw0KICBhZGRfcGFyYW0gPC0gZml0dGVkX21vZGVscyRhZGRfcGFyYW1ldGVycw0KICBiYXNpY19tYWNoIDwtIGZpdHRlZF9tb2RlbHMkYmFzaWNfbWFjaGluZXMNCiAga2VybjAgPC0gbmFtZXMob3B0X3BhcmFtKQ0KICBrZXJuZWwwIDwtIHN0cmluZ3I6OnN0cl9zcGxpdChrZXJuMCwgIl8iKSAlPiUNCiAgICBpbWFwX2RmYyguZiA9IH4gdGliYmxlKCJ7Lnl9IiA6PSAueCkpIA0KICBrZXJucyA8LSBrZXJuZWwwWzEsXSAlPiUNCiAgICBhcy5jaGFyYWN0ZXINCiAgb3B0X21ldGhzIDwtIGtlcm5lbDBbMixdICU+JQ0KICAgIGFzLmNoYXJhY3Rlcg0KICBuZXdfZGF0YV8gPC0gbmV3X2RhdGENCiAgbWF0X2lucHV0IDwtIGFzLm1hdHJpeChiYXNpY19tYWNoJHRyYWluX2RhdGEkdHJhaW5faW5wdXQpDQogIGlmKCFpcy5udWxsKGJhc2ljX21hY2gkdHJhaW5fZGF0YSRtaW5faW5wdXQpKXsNCiAgICBuZXdfZGF0YV8gPC0gc2NhbGUobmV3X2RhdGEsIA0KICAgICAgICAgICAgICAgICAgICAgICBjZW50ZXIgPSBiYXNpY19tYWNoJHRyYWluX2RhdGEkbWluX2lucHV0LCANCiAgICAgICAgICAgICAgICAgICAgICAgc2NhbGUgPSBiYXNpY19tYWNoJHRyYWluX2RhdGEkbWF4X2lucHV0IC0gYmFzaWNfbWFjaCR0cmFpbl9kYXRhJG1pbl9pbnB1dCkNCiAgfQ0KICBpZihpcy5tYXRyaXgobmV3X2RhdGFfKSl7DQogICAgbWF0X3Rlc3QgPC0gbmV3X2RhdGFfDQogICAgZGZfdGVzdCA8LSBhc190aWJibGUobmV3X2RhdGFfKQ0KICB9IGVsc2Ugew0KICAgIG1hdF90ZXN0IDwtIGFzLm1hdHJpeChuZXdfZGF0YV8pDQogICAgZGZfdGVzdCA8LSBuZXdfZGF0YV8NCiAgfQ0KICBpZihpcy5udWxsKGJhc2ljX21hY2gkbW9kZWxzKSl7DQogICAgcHJlZF90ZXN0X2FsbCA8LSBuZXdfcHJlZA0KICAgIHByZWRfdGVzdDAgPC0gbmV3X3ByZWQNCiAgfSBlbHNlew0KICAgICMgUHJlZGljdGlvbiB0ZXN0IGJ5IGJhc2ljIG1hY2hpbmVzDQogICAgYnVpbHRfbW9kZWxzIDwtIGJhc2ljX21hY2gkbW9kZWxzDQogICAgcHJlZF90ZXN0IDwtIGZ1bmN0aW9uKG1ldGgpew0KICAgICAgaWYobWV0aCA9PSAia25uIil7DQogICAgICAgIHByZSA8LSAxOmxlbmd0aChidWlsdF9tb2RlbHNbW21ldGhdXSkgJT4lDQogICAgICAgICAgbWFwX2RmYyguZiA9IChcKGspIHRpYmJsZSgne3trfX0nIDo9IEZOTjo6a25uLnJlZyh0cmFpbiA9IG1hdF9pbnB1dFshYmFzaWNfbWFjaCRpZDIsXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0ID0gbWF0X3Rlc3QsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGJhc2ljX21hY2gkdHJhaW5fZGF0YSR0cmFpbl9yZXNwb25zZVshYmFzaWNfbWFjaCRpZDJdLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgayA9IGJ1aWx0X21vZGVsc1tbbWV0aF1dW1trXV0pJHByZWQpKSkNCiAgICAgIH0NCiAgICAgIGlmKG1ldGggJWluJSBjKCJ0cmVlIiwgInJmIikpew0KICAgICAgICBwcmUgPC0gMTpsZW5ndGgoYnVpbHRfbW9kZWxzW1ttZXRoXV0pICU+JQ0KICAgICAgICAgIG1hcF9kZmMoLmYgPSAoXChrKSB0aWJibGUoJ3t7a319JyA6PSBhcy52ZWN0b3IocHJlZGljdChidWlsdF9tb2RlbHNbW21ldGhdXVtba11dLCBkZl90ZXN0KSkpKSkNCiAgICAgIH0NCiAgICAgIGlmKG1ldGggJWluJSBjKCJsYXNzbyIsICJyaWRnZSIpKXsNCiAgICAgICAgcHJlIDwtIDE6bGVuZ3RoKGJ1aWx0X21vZGVsc1tbbWV0aF1dKSAlPiUNCiAgICAgICAgICBtYXBfZGZjKC5mID0gKFwoaykgdGliYmxlKCd7e2t9fScgOj0gYXMudmVjdG9yKHByZWRpY3QuZ2xtbmV0KGJ1aWx0X21vZGVsc1tbbWV0aF1dW1trXV0sIG1hdF90ZXN0KSkpKSkNCiAgICAgIH0NCiAgICAgIGlmKG1ldGggPT0gInhnYiIpew0KICAgICAgICBwcmUgPC0gMTpsZW5ndGgoYnVpbHRfbW9kZWxzW1ttZXRoXV0pICU+JQ0KICAgICAgICAgIG1hcF9kZmMoLmYgPSAoXChrKSB0aWJibGUoJ3t7a319JyA6PSBhcy52ZWN0b3IocHJlZGljdChidWlsdF9tb2RlbHNbW21ldGhdXVtba11dLCBtYXRfdGVzdCkpKSkpDQogICAgICB9DQogICAgICBuYW1lcyhwcmUpIDwtIG5hbWVzKGJ1aWx0X21vZGVsc1tbbWV0aF1dKQ0KICAgICAgcmV0dXJuKHByZSkNCiAgICB9DQogICAgcHJlZF90ZXN0X2FsbCA8LSBuYW1lcyhidWlsdF9tb2RlbHMpICU+JQ0KICAgICAgbWFwX2RmYyguZiA9IHByZWRfdGVzdCkNCiAgICBwcmVkX3Rlc3QwIDwtIHByZWRfdGVzdF9hbGwNCiAgfQ0KICBpZighaXMubnVsbChiYXNpY19tYWNoJHRyYWluX2RhdGEkbWluX21hY2hpbmUpKXsNCiAgICAgICAgcHJlZF90ZXN0X2FsbCA8LSBzY2FsZShwcmVkX3Rlc3QwLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZW50ZXIgPSBiYXNpY19tYWNoJHRyYWluX2RhdGEkbWluX21hY2hpbmUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGUgPSBiYXNpY19tYWNoJHRyYWluX2RhdGEkbWF4X21hY2hpbmUgLSBiYXNpY19tYWNoJHRyYWluX2RhdGEkbWluX21hY2hpbmUpDQogIH0NCiAgIyBQcmVkaWN0aW9uIHRyYWluMg0KICBwcmVkX3RyYWluX2FsbCA8LSBiYXNpY19tYWNoJGZpdHRlZF9yZW1haW4NCiAgY29sbmFtZXMocHJlZF90ZXN0X2FsbCkgPC0gY29sbmFtZXMocHJlZF90cmFpbl9hbGwpDQogIGRfdHJhaW4gPC0gZGltKHByZWRfdHJhaW5fYWxsKQ0KICBkX3Rlc3QgPC0gZGltKHByZWRfdGVzdF9hbGwpDQogIGRfdHJhaW5faW5wdXQgPC0gZGltKG1hdF9pbnB1dFtiYXNpY19tYWNoJGlkMixdKQ0KICBkX3Rlc3RfaW5wdXQgPC0gZGltKG5ld19kYXRhXykNCiAgcHJlZF90ZXN0X21hdCA8LSBhcy5tYXRyaXgocHJlZF90ZXN0X2FsbCkNCiAgcHJlZF90cmFpbl9tYXQgPC0gYXMubWF0cml4KHByZWRfdHJhaW5fYWxsKQ0KICAjIERpc3RhbmNlIG1hdHJpeA0KICBkaXN0X21hdCA8LSBmdW5jdGlvbihrZXJuZWwgPSAiZ2F1c2lhbiIpew0KICAgIHJlc18xIDwtIHJlc18yIDwtIE5VTEwNCiAgICBpZighKGtlcm5lbCAlaW4lIGMoIm5haXZlIiwgInRyaWFuZ3VsYXIiKSkpew0KICAgICAgcmVzXzEgPC0gMTpkX3Rlc3RfaW5wdXRbMV0gJT4lDQogICAgICAgIG1hcF9kZmMoLmYgPSAoXChpZCkgdGliYmxlKCd7e2lkfX0nIDo9IGFzLnZlY3Rvcihyb3dTdW1zKChtYXRfaW5wdXRbYmFzaWNfbWFjaCRpZDIsXSAtIG1hdHJpeChyZXAobmV3X2RhdGFfW2lkLF0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRfdHJhaW5faW5wdXRbMV0pLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2wgPSBkX3RyYWluX2lucHV0WzJdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gVFJVRSkpXjIpKSkpKQ0KICAgICAgcmVzXzIgPC0gMTpkX3Rlc3RbMV0gJT4lDQogICAgICAgIG1hcF9kZmMoLmYgPSAoXChpZCkgdGliYmxlKCd7e2lkfX0nIDo9IGFzLnZlY3Rvcihyb3dTdW1zKChwcmVkX3RyYWluX21hdCAtIG1hdHJpeChyZXAocHJlZF90ZXN0X21hdFtpZCxdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkX3RyYWluWzFdKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gZF90cmFpblsyXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieXJvdyA9IFRSVUUpKV4yKSkpKSkNCiAgICB9DQogICAgaWYoa2VybmVsID09ICJ0cmlhbmd1bGFyIil7DQogICAgICByZXNfMSA8LSAxOmRfdGVzdF9pbnB1dFsxXSAlPiUNCiAgICAgICAgbWFwX2RmYyguZiA9IChcKGlkKSB0aWJibGUoJ3t7aWR9fScgOj0gYXMudmVjdG9yKHJvd1N1bXMoYWJzKG1hdF9pbnB1dFtiYXNpY19tYWNoJGlkMixdIC0gbWF0cml4KHJlcChuZXdfZGF0YV9baWQsXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZF90cmFpbl9pbnB1dFsxXSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2wgPSBkX3RyYWluX2lucHV0WzJdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieXJvdyA9IFRSVUUpKSkpKSkpDQogICAgICByZXNfMiA8LSAxOmRfdGVzdFsxXSAlPiUNCiAgICAgICAgbWFwX2RmYyguZiA9IChcKGlkKSB0aWJibGUoJ3t7aWR9fScgOj0gYXMudmVjdG9yKHJvd1N1bXMoYWJzKHByZWRfdHJhaW5fbWF0IC0gbWF0cml4KHJlcChwcmVkX3Rlc3RfbWF0W2lkLF0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRfdHJhaW5bMV0pLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5jb2wgPSBkX3RyYWluWzJdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gVFJVRSkpKSkpKSkNCiAgICB9DQogICAgaWYoa2VybmVsID09ICJuYWl2ZSIpew0KICAgICAgcmVzXzEgPC0gMTpkX3Rlc3RfaW5wdXRbMV0gJT4lDQogICAgICAgIG1hcF9kZmMoLmYgPSAoXChpZCkgdGliYmxlKCd7e2lkfX0nIDo9IGFzLnZlY3RvcihhcHBseShhYnMobWF0X2lucHV0W2Jhc2ljX21hY2gkaWQyLF0gLSBtYXRyaXgocmVwKG5ld19kYXRhX1tpZCxdLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkX3RyYWluX2lucHV0WzFdKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuY29sID0gZF90cmFpbl9pbnB1dFsyXSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnlyb3cgPSBUUlVFKSksIDEsIG1heCkpKSkpDQogICAgICByZXNfMiA8LSAxOmRfdGVzdFsxXSAlPiUNCiAgICAgICAgbWFwX2RmYyguZiA9IChcKGlkKSB0aWJibGUoJ3t7aWR9fScgOj0gYXMudmVjdG9yKGFwcGx5KGFicyhwcmVkX3RyYWluX21hdCAtIG1hdHJpeChyZXAocHJlZF90ZXN0X21hdFtpZCxdLCBkX3RyYWluWzFdKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IGRfdHJhaW5bMl0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5cm93ID0gVFJVRSkpLCAxLCBtYXgpKSkpKQ0KICAgIH0NCiAgICByZXR1cm4obGlzdChkaXN0X2lucHV0ID0gcmVzXzEsDQogICAgICAgICAgICAgICAgZGlzdF9tYWNoaW5lID0gcmVzXzIpKQ0KICB9DQoNCiAgZGlzdHMgPC0gMTpsZW5ndGgoa2VybnMpICU+JQ0KICAgICAgbWFwKC5mID0gfiBkaXN0X21hdChrZXJuc1sueF0pKQ0KICB0YWJfbmFtIDwtIHRhYmxlKGtlcm5zKQ0KICBuYW0gPC0gbmFtZXModGFiX25hbVt0YWJfbmFtID4gMV0pDQogIHZlYyA8LSByZXAoTkEsIGxlbmd0aChrZXJucykpDQogIGZvcihpZCBpbiBuYW0pew0KICAgIGlkXyA8LSBrZXJucyA9PSBpZA0KICAgIGlmKCFpcy5udWxsKGlkXykpew0KICAgICAgdmVjW2lkX10gPSBhZGRfcGFyYW0kb3B0X21ldGhvZHNbaWRfXQ0KICAgIH0NCiAgfQ0KICBwcmVkaWN0aW9uIDwtIDE6bGVuZ3RoKGtlcm5zKSAlPiUgDQogICAgbWFwX2RmYyguZiA9IH4ga2VybmVsX3ByZWRfTWl4KHRoZXRhID0gb3B0X3BhcmFtW1trZXJuMFsueF1dXSRvcHRfcGFyYW0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC55MiA9IGJhc2ljX21hY2gkdHJhaW5fZGF0YSR0cmFpbl9yZXNwb25zZVtiYXNpY19tYWNoJGlkMl0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5kaXN0MSA9IGRpc3RzW1sueF1dJGRpc3RfaW5wdXQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5kaXN0MiA9IGRpc3RzW1sueF1dJGRpc3RfbWFjaGluZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmtlcm4gPSBrZXJuc1sueF0sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuaW52X3NpZyA9IGFkZF9wYXJhbSRpbnZfc2lnbWEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuYWxwID0gYWRkX3BhcmFtJGFscCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLm1ldGggPSB2ZWNbLnhdKSkNCiAgaWYoaXMubnVsbCh0ZXN0X3Jlc3BvbnNlKSl7DQogICAgcmV0dXJuKGxpc3QoZml0dGVkX2FnZ3JlZ2F0ZSA9IHByZWRpY3Rpb24sDQogICAgICAgICAgICAgICAgZml0dGVkX21hY2hpbmUgPSBwcmVkX3Rlc3QwKSkNCiAgfSBlbHNlew0KICAgIGVycm9yIDwtIGNiaW5kKHByZWRfdGVzdDAsIHByZWRpY3Rpb24pICU+JQ0KICAgICAgZHBseXI6Om11dGF0ZSh5X3Rlc3QgPSB0ZXN0X3Jlc3BvbnNlKSAlPiUNCiAgICAgIGRwbHlyOjpzdW1tYXJpc2VfYWxsKC5mdW5zID0gfiAoLiAtIHlfdGVzdCkpICU+JQ0KICAgICAgZHBseXI6OnNlbGVjdCgteV90ZXN0KSAlPiUNCiAgICAgIGRwbHlyOjpzdW1tYXJpc2VfYWxsKC5mdW5zID0gfiBtZWFuKC5eMikpDQogICAgcmV0dXJuKGxpc3QoZml0dGVkX2FnZ3JlZ2F0ZSA9IHByZWRpY3Rpb24sDQogICAgICAgICAgICAgICAgZml0dGVkX21hY2hpbmUgPSBwcmVkX3Rlc3QwLA0KICAgICAgICAgICAgICAgIG1zZSA9IGVycm9yKSkNCiAgfQ0KfQ0KYGBgDQoNCi0tLQ0KDQo+ICoqRXhhbXBsZS42KiogQWdncmVnYXRpb24gb24gYEJvc3RvbmAgZGF0YXNldC4NCg0KLS0tDQoNCmBgYHtyfQ0KcHJlZCA8LSBwcmVkaWN0X01peChwYXJhbSwNCiAgICAgICAgICAgICAgICAgICAgbmV3X2RhdGEgPSBkZlshdHJhaW4sIC0xNF0sDQogICAgICAgICAgICAgICAgICAgIHRlc3RfcmVzcG9uc2UgPSBkZlshdHJhaW4sIDE0XSkNCnNxcnQocHJlZCRtc2UpDQpgYGANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICMxRkFBRTM7Ij48dT5GdW5jdGlvbjwvdT48L3NwYW4+IDogYE1peENvYnJhUmVnYCAoZGlyZWN0IGFnZ3JlZ2F0aW9uKQ0KPT09DQoNClRoaXMgZnVuY3Rpb24gcHV0cyB0b2dldGhlciBhbGwgdGhlIGZ1bmN0aW9ucyBhYm92ZSBhbmQgcHJvdmlkZXMgdGhlIGRlc2lyZSByZXN1bHQgb2YgTWl4Q29icmEgYWdncmVnYXRpb24gbWV0aG9kLg0KDQotICoqQXJndW1lbnQqKjogYWxsIG9mIGl0cyBhcmd1bWVudHMgYXJlIGFscmVhZHkgZGVzY3JpYmVkIGluIHRoZSBwcmV2aW91cyBwYXJ0cy4NCiAgICANCiAgICANCi0gKipWYWx1ZSoqOg0KDQogICAgVGhpcyBmdW5jdGlvbiByZXR1cm4gYSAqbGlzdCogb2YgdGhlIGZvbGxvd2luZyBvYmplY3RzOg0KICAgIA0KICAgIC0gYGZpdHRlZF9hZ2dyZWdhdGVgIDogdGhlIHByZWRpY3RlZCB2YWx1ZXMgb2YgdGhlIGFnZ3JlZ2F0aW9uIG1ldGhvZC4NCiAgICAtIGBmaXR0ZWRfbWFjaGluZWAgOiB0aGUgcHJlZGljdGVkIHZhbHVlcyBvZiBhbGwgdGhlIGJhc2ljIG1hY2hpbmVzIGJ1aWx0IG9uICRcbWF0aGNhbHtEfV97a30kLg0KICAgIC0gYHByZWRfdHJhaW4yYCA6IHRoZSBwcmVkaWN0aW9uIG9mICRcbWF0aGNhbHtEfV97XGVsbH0kLCBnaXZlbiBieSBhbGwgdGhlIGJhc2ljIG1hY2hpZW5zLg0KICAgIC0gYG9wdF9wYXJhbWV0ZXJgIDogIHRoZSBvYnNlcnZlZCBvcHRpbWFsIHBhcmFtZXRlcnMuDQogICAgLSBgbXNlYCA6IHRoZSBtZWFzIHNxdWFyZSBlcnJvciBldmFsdWF0ZWQgb24gdGhlIHRlc3RpbmcgZGF0YSBpZiBgdGVzdF9yZXNwb25zZWAgaXMgZ2l2ZW4uDQogICAgLSBga2VybmVsc2AgOiBhIHZlY3RvciBvZiBrZXJuZWwgZnVuY3Rpb24gdXNlZC4NCiAgICAtIGBpbmRfRDJgIDogYSBsb2dpY2FsIHZlY3RvciBpbmRpY2F0aW5nIHRoZSBwYXJ0IG9mIHRyYWluaW5nIGRhdGEgY29ycmVzcG9uZGluZyB0byAkXG1hdGhjYWx7RH1fe1xlbGx9JCAoYFRSVUVgKS4NCiAgICANCg0KYGBge3J9DQpNaXhDb2JyYVJlZyA8LSBmdW5jdGlvbih0cmFpbl9pbnB1dCwgDQogICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9yZXNwb25zZSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RfaW5wdXQsDQogICAgICAgICAgICAgICAgICAgICAgICB0cmFpbl9wcmVkaWN0aW9ucyA9IE5VTEwsDQogICAgICAgICAgICAgICAgICAgICAgICB0ZXN0X3ByZWRpY3Rpb25zID0gTlVMTCwNCiAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RfcmVzcG9uc2UgPSBOVUxMLA0KICAgICAgICAgICAgICAgICAgICAgICAgbWFjaGluZXMgPSBOVUxMLCANCiAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX2lucHV0ID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX21hY2hpbmUgPSBUUlVFLA0KICAgICAgICAgICAgICAgICAgICAgICAgc3BsaXRzID0gMC41LCANCiAgICAgICAgICAgICAgICAgICAgICAgIG5fY3YgPSA1LA0KICAgICAgICAgICAgICAgICAgICAgICAgaW52X3NpZ21hID0gc3FydCguNSksDQogICAgICAgICAgICAgICAgICAgICAgICBhbHAgPSAyLA0KICAgICAgICAgICAgICAgICAgICAgICAga2VybmVscyA9ICJnYXVzc2lhbiIsDQogICAgICAgICAgICAgICAgICAgICAgICBvcHRpbWl6ZU1ldGhvZCA9ICJncmFkIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIHNldEJhc2ljTWFjaGluZVBhcmFtID0gc2V0QmFzaWNQYXJhbWV0ZXJfTWl4KCksDQogICAgICAgICAgICAgICAgICAgICAgICBzZXRHcmFkUGFyYW0gPSBzZXRHcmFkUGFyYW1ldGVyX01peCgpLA0KICAgICAgICAgICAgICAgICAgICAgICAgc2V0R3JpZFBhcmFtID0gc2V0R3JpZFBhcmFtZXRlcl9NaXgoKSl7DQogICMgYnVpbGQgbWFjaGluZXMgKyB0dW5lIHBhcmFtZXRlcg0KICBmaXRfbW9kIDwtIGZpdF9wYXJhbWV0ZXJfTWl4KHRyYWluX2lucHV0ID0gdHJhaW5faW5wdXQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyYWluX3Jlc3BvbnNlID0gdHJhaW5fcmVzcG9uc2UsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5fcHJlZGljdGlvbnMgPSB0cmFpbl9wcmVkaWN0aW9ucywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYWNoaW5lcyA9IG1hY2hpbmVzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY2FsZV9pbnB1dCA9IHNjYWxlX2lucHV0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNjYWxlX21hY2hpbmUgPSBzY2FsZV9tYWNoaW5lLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0cyA9IHNwbGl0cywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jdiA9IG5fY3YsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW52X3NpZ21hID0gaW52X3NpZ21hLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFscCA9IGFscCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWxzID0ga2VybmVscywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcHRpbWl6ZU1ldGhvZCA9IG9wdGltaXplTWV0aG9kLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldEJhc2ljTWFjaGluZVBhcmFtID0gc2V0QmFzaWNNYWNoaW5lUGFyYW0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2V0R3JhZFBhcmFtID0gc2V0R3JhZFBhcmFtLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNldEdyaWRQYXJhbSA9IHNldEdyaWRQYXJhbSkNCiAgIyBwcmVkaWN0aW9uDQogIHByZWQgPC0gcHJlZGljdF9NaXgoZml0dGVkX21vZGVscyA9IGZpdF9tb2QsDQogICAgICAgICAgICAgICAgICAgICAgbmV3X2RhdGEgPSB0ZXN0X2lucHV0LA0KICAgICAgICAgICAgICAgICAgICAgIG5ld19wcmVkID0gdGVzdF9wcmVkaWN0aW9ucywNCiAgICAgICAgICAgICAgICAgICAgICB0ZXN0X3Jlc3BvbnNlID0gdGVzdF9yZXNwb25zZSkNCiAgcmV0dXJuKGxpc3QoZml0dGVkX2FnZ3JlZ2F0ZSA9IHByZWQkZml0dGVkX2FnZ3JlZ2F0ZSwNCiAgICAgICAgICAgICAgZml0dGVkX21hY2hpbmUgPSBwcmVkJGZpdHRlZF9tYWNoaW5lLA0KICAgICAgICAgICAgICBwcmVkX3RyYWluMiA9IGZpdF9tb2QkYmFzaWNfbWFjaGluZXMkZml0dGVkX3JlbWFpbiwNCiAgICAgICAgICAgICAgb3B0X3BhcmFtZXRlciA9IGZpdF9tb2Qkb3B0X3BhcmFtZXRlcnMsDQogICAgICAgICAgICAgIG1zZSA9IHByZWQkbXNlLA0KICAgICAgICAgICAgICBrZXJuZWxzID0ga2VybmVscywNCiAgICAgICAgICAgICAgaW5kX0QyID0gZml0X21vZCRiYXNpY19tYWNoaW5lcyRpZDIpKQ0KfQ0KYGBgDQoNCi0tLQ0KDQo+KipFeGFtcGxlLjcqKiBBIGNvbXBsZXRlIGFnZ3JlZ2F0aW9uIGlzIGltcGxlbWVudGVkIG9uIFtBYmFsb25lXShodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvZGF0YXNldHMvYWJhbG9uZSkgZGF0YXNldC4gVGhyZWUgdHlwZXMgb2YgYmFzaWMgbWFjaGluZXMsIGFuZCB0aHJlZSBkaWZmZXJlbnQga2VybmVsIGZ1bmN0aW9ucyBhcmUgdXNlZC4gDQoNCi0tLQ0KDQpgYGB7cn0NCnBhY21hbjo6cF9sb2FkKHJlYWRyKQ0KY29sbmFtZSA8LSBjKCJUeXBlIiwgIkxvbmdlc3RTaGVsbCIsICJEaWFtZXRlciIsICJIZWlnaHQiLCAiV2hvbGVXZWlnaHQiLCAiU2h1Y2tlZFdlaWdodCIsICJWaXNjZXJhV2VpZ2h0IiwgIlNoZWxsV2VpZ2h0IiwgIlJpbmdzIikNCmRmIDwtIHJlYWRyOjpyZWFkX2RlbGltKCJodHRwczovL2FyY2hpdmUuaWNzLnVjaS5lZHUvbWwvbWFjaGluZS1sZWFybmluZy1kYXRhYmFzZXMvYWJhbG9uZS9hYmFsb25lLmRhdGEiLCBjb2xfbmFtZXMgPSBjb2xuYW1lLCBkZWxpbSA9ICIsIiwgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkNCg0KdHJhaW4gPC0gbG9naWNhbChucm93KGRmKSkNCnRyYWluW3NhbXBsZShsZW5ndGgodHJhaW4pLCBmbG9vcigwLjc1Km5yb3coZGYpKSldIDwtIFRSVUUNCg0KYWdnIDwtIE1peENvYnJhUmVnKHRyYWluX2lucHV0ID0gZGZbdHJhaW4sIDI6OF0sDQogICAgICAgICAgICAgICAgICAgdHJhaW5fcmVzcG9uc2UgPSBkZiRSaW5nc1t0cmFpbl0sDQogICAgICAgICAgICAgICAgICAgdGVzdF9pbnB1dCA9IGRmWyF0cmFpbiwyOjhdLA0KICAgICAgICAgICAgICAgICAgIHRlc3RfcmVzcG9uc2UgPSBkZiRSaW5nc1shdHJhaW5dLA0KICAgICAgICAgICAgICAgICAgIG5fY3YgPSAzLA0KICAgICAgICAgICAgICAgICAgIG1hY2hpbmVzID0gYygia25uIiwgInJmIiwgInhnYiIpLA0KICAgICAgICAgICAgICAgICAgIHNwbGl0cyA9IC41LA0KICAgICAgICAgICAgICAgICAgIGtlcm5lbHMgPSBjKCJnYXVzc2lhbiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJuYWl2ZSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ0cmlhbmd1bGFyIiksDQogICAgICAgICAgICAgICAgICAgb3B0aW1pemVNZXRob2QgPSBjKCJncmFkIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJncmlkIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJncmlkIiksDQogICAgICAgICAgICAgICAgICAgc2V0QmFzaWNNYWNoaW5lUGFyYW0gPSBzZXRCYXNpY1BhcmFtZXRlcl9NaXgoayA9IGMoMiw1LDcsMTApLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDI6NSoxMDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5yb3VuZHNfeGdiID0gYygxLDMsNSkqMTAwKSwNCiAgICAgICAgICAgICAgICAgICBzZXRHcmFkUGFyYW0gPSBzZXRHcmFkUGFyYW1ldGVyX01peChyYXRlID0gImxpbmVhciIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29lZl9hdXRvID0gYygwLjUsIDAuNSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnRfc3RlcCA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnRfcmVzdWx0ID0gVFJVRSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWd1cmUgPSBUUlVFKSwNCiAgICAgICAgICAgICAgICAgICBzZXRHcmlkUGFyYW0gPSBzZXRHcmlkUGFyYW1ldGVyX01peChwYXJhbWV0ZXJzID0gbGlzdChhbHBoYSA9IHNlcSgwLjAwMDEsIDMsIGxlbmd0aC5vdXQgPSAyMCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiZXRhID0gc2VxKDAuMDAwMSwgNSwgbGVuZ3RoLm91dCA9IDIwKSkpKQ0KYGBgDQoNCg0KYGBge3J9DQpzcXJ0KGFnZyRtc2UpDQpgYGANCg0KDQo8c3BhbiBzdHlsZT0iY29sb3I6ICMxRkFBRTM7Ij5SZWZlcmVuY2VzPC9zcGFuPnstfQ0KPT09DQoNCi0tLQ0KDQotIFtCaWF1IGV0IGFsLiAoMjAxNildKGh0dHBzOi8vd3d3LnNjaWVuY2VkaXJlY3QuY29tL3NjaWVuY2UvYXJ0aWNsZS9waWkvUzAwNDcyNTlYMTUwMDA5NTApDQotIFtIYXMgKDIwMjEpXShodHRwczovL2hhbC5hcmNoaXZlcy1vdXZlcnRlcy5mci9oYWwtMDI4ODQzMzN2NSkNCi0gLi4uDQotIFtkcGx5ciB2aWRlb3NdKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2hhc2h0YWcvZHBseXIpIGByIGZvbnRhd2Vzb21lOjpmYSgidmlkZW8iKWANCi0gW2dncGxvdDIgdmlkZW8gdHV0b3JpYWxdKGh0dHBzOi8vd3d3LnlvdXR1YmUuY29tL2hhc2h0YWcvZ2dwbG90MikgYHIgZm9udGF3ZXNvbWU6OmZhKCJ2aWRlbyIpYA0KLSBbUiBmb3IgZGF0YSBzY2llbmNlXShodHRwczovL3I0ZHMuaGFkLmNvLm56LykNCg0KLS0tDQoNCg==

📖 Read also MixCobraClass, KernelAggReg and KernelAggClass methods.