Goals:

  • Handcode policy learning with classification trees and Lasso

  • Apply the policytree package


Nonlinear heterogeneity and discrete policy rule

We revisit the DGP from Notebooks Causal ML: AIPW Double ML (ATE) and Causal ML: Group Average Treatment Effects with zero ATE but highly nonlinear effect heterogeneity:

  • \(p=10\) independent covariates \(X_1,...,X_k,...,X_{10}\) drawn from a uniform distribution: \(X_k \sim uniform(-\pi,\pi)\)

  • The treatment model is \(W \sim Bernoulli(\underbrace{\Phi(sin(X_1))}_{e(X)})\), where \(\Phi(\cdot)\) is the standard normal cumulative density function

  • The potential outcome model of the controls is \(Y(0) = \underbrace{cos(X_1+1/2\pi)}_{m_0(X)}+ \varepsilon\), with \(\varepsilon \sim N(0,1)\)

  • The potential outcome model of the treated is \(\underbrace{sin(X_1)}_{m_1(X)} + \varepsilon\), with \(\varepsilon \sim N(0,1)\)

  • The treatment effect function is \(\tau(X) = sin(X_1) - cos(X_1+1/2\pi)\)

This means that some simulated individuals benefit from the treatment (assuming that higher outcome is better) and some do not. The optimal policy is given by \(\pi^*(x) = \mathbf{1}[X_1 > 0]\) and should be recovered by the methods we discussed in the lecture.

if (!require("rpart")) install.packages("rpart", dependencies = TRUE); library(rpart)
if (!require("rpart.plot")) install.packages("rpart.plot", dependencies = TRUE); library(rpart.plot)
if (!require("glmnet")) install.packages("glmnet", dependencies = TRUE); library(glmnet)
if (!require("tidyverse")) install.packages("tidyverse", dependencies = TRUE); library(tidyverse)
if (!require("patchwork")) install.packages("patchwork", dependencies = TRUE); library(patchwork)
if (!require("policytree")) install.packages("policytree", dependencies = TRUE); library(policytree)
if (!require("ggridges")) install.packages("ggridges", dependencies = TRUE); library(ggridges)
if (!require("DiagrammeR")) install.packages("DiagrammeR", dependencies = TRUE); library(DiagrammeR)
if (!require("causalDML")) {
  if (!require("devtools")) install.packages("devtools", dependencies = TRUE); library(devtools)
  install_github(repo="MCKnaus/causalDML") 
}; library(causalDML)

set.seed(1234)

# define parameters for the DGP
n = 1000
p = 10

# Generate data as usual, but save both Y0 and Y1
x = matrix(runif(n*p,-pi,pi),ncol=p)
e = function(x){pnorm(sin(x))}
m1 = function(x){sin(x)}
m0 = function(x){cos(x+1/2*pi)}
tau = function(x){m1(x) - m0(x)}
w = rbinom(n,1,e(x[,1]))
y0 = m0(x[,1]) + rnorm(n,0,1)
y1 = m1(x[,1]) + rnorm(n,0,1)
y = w*y1 + (1-w)*y0
# Define optimal policy additionally
pi_star = ifelse(x[,1]>0,"Treat","Don't treat")

# plot the DGP
g1 = data.frame(x = c(-pi, pi)) %>% ggplot(aes(x)) + stat_function(fun=e,size=1) + ylab("e") + xlab("X1")
g2 = data.frame(x = c(-pi, pi)) %>% ggplot(aes(x)) + stat_function(fun=m1,size=1,aes(colour="Y1")) + 
  stat_function(fun=m0,size=1,aes(colour="Y0")) + ylab("Y") + xlab("X1")
g3 = data.frame(x = c(-pi, pi)) %>% ggplot(aes(x)) + stat_function(fun=tau,size=1) + ylab(expression(tau)) + 
      xlab("X1") + geom_vline(xintercept=0) + annotate("text",-pi/2,0,label="Don't treat") + 
      annotate("text",pi/2,0,label="Treat")
g1 / g2 / g3

We see that the best policy assigns everybody with \(X>0\) to treatment and leaves everybody with \(X\leq0\) untreated.


Policy learning as a weighted classification problem

  1. Estimate the AIPW w/ 5-fold cross-fitting

We draw a sample of \(N=1000\) and and estimate the 5-fold cross-fit nuisance parameters \(e(X)=E[W|X]\), \(m(0,X)=E[Y|W=0,X]\) and \(m(1,X)=E[Y|W=1,X]\) using random forest with honesty and plug the predictions into the pseudo outcome: \[\tilde{Y}_{ATE} = \underbrace{\hat{m}(1,X) - \hat{m}(0,X)}_{\text{outcome predictions}} + \underbrace{\frac{W (Y - \hat{m}(1,X))}{\hat{e}(X)} - \frac{(1-W) (Y - \hat{m}(0,X))}{1-\hat{e}(X)}}_{\text{weighted residuals}}.\]

# Get the pseudo-outcome
# 5-fold cross-fitting with causalDML package
# Create learner
forest = create_method("forest_grf",args=list(tune.parameters = "all"))
# Run the AIPW estimation
aipw = DML_aipw(y,w,x,ml_w=list(forest),ml_y=list(forest),cf=5)
# average potential outcomes
summary(aipw$APO)
         APO         SE
0 0.07740962 0.05177078
1 0.06786870 0.05627277
# average treatment effect
summary(aipw$ATE)
             ATE         SE       t      p
1 - 0 -0.0095409  0.0813622 -0.1173 0.9067
  1. Estimate weighted classification

We want to classify the sign of the CATE while favoring correct classifications with larger absolute CATEs. Instead of the unobserved CATE, we use the sign of the pseudo outcomes \(\tilde{Y}_{ATE}\) from the Double ML AIPW to proxy the sign of the CATE and the absolute value of the pseudo-outcome to proxy the absolute value of the CATE. We then learn the policy by classifying the sign of the pseudo outcome using the available covariates/policy variables weighted by its absolute value: \[\hat{\pi}=\underset{\pi\in \Pi}{\operatorname{arg max}} \bigg\{ \frac{1}{N} \sum_{i=1}^{N} \mid \tilde{Y}_{i,ATE} \mid sign\big(\tilde{Y}_{i,ATE}\big) \big(2\pi(X_i)-1\big)\bigg\}.\]


Classification tree

In the following, we estimate a policy assignment rule \(\hat{\pi}^{tree}\) using a weighted classification tree. The advantage of the tree is its interpretability, but observe that it becomes overly complicated in this sample:

# Define the sign of the pseudo-outcome
sign_y_tilde = sign(aipw$ATE$delta)
sign_y_tilde = factor(sign_y_tilde, labels = c("Don't treat","Treat"))

# Define the weight as absolute value of the pseudo-outcome
abs_y_tilde = abs(aipw$ATE$delta)

# Build classification tree
class_tree = rpart(sign_y_tilde ~ x,      # Predict sign of treatment
                  weights = abs_y_tilde,  # with weight absolute pseudo outcome
                  method = "class")      # tell we want to classify

# Plot the tree
rpart.plot(class_tree,digits=3)

We check the confusion matrix as a cross-table of the optimal and the estimates policy rule. We hope to find most observations on the diagonal

# Predict the policy for everybody in the sample
pi_hat_tree = predict(class_tree,type="class")
# Compare to the optimal rule
table(pi_star,pi_hat_tree)
             pi_hat_tree
pi_star       Don't treat Treat
  Don't treat         444    38
  Treat                20   498
# compute the accuracy
paste0("The classification accuracy of the tree is ", (sum(diag(table(pi_star,pi_hat_tree))) / n) * 100, "%")
[1] "The classification accuracy of the tree is 94.2%"

We see that the majority is correctly classified, although the tree is overly complicated in this particular draw. It should have stopped after the first split.

As we know both potential outcomes in our simulated dataset, we can calculate (i) the value function under the optimal policy \(Q(\pi^*) = E[Y(\pi^*)]\), (ii) the value function of the estimated assignment rule \(Q(\hat{\pi}^{tree}) = E[Y(\hat{\pi}^{tree})]\) and (iii) the regret \(R(\hat{\pi}^{tree}) = Q(\pi^*) - Q(\hat{\pi}^{tree})\)

# Calculate values and regret
Q_pi_star = sum( (pi_star == "Treat") * m1(x[,1]) + (pi_star == "Don't treat") * m0(x[,1]) )
Q_pi_hat_tree = sum( (pi_hat_tree == "Treat") * m1(x[,1]) + (pi_hat_tree == "Don't treat") * m0(x[,1]) )
regret_tree = Q_pi_star - Q_pi_hat_tree
# Print
paste0("Q(pi*): ", round(Q_pi_star,1))
[1] "Q(pi*): 627.1"
paste0("Q(pi^tree): ", round(Q_pi_hat_tree,1))
[1] "Q(pi^tree): 609.4"
paste0("R(pi^tree): ", round(regret_tree,1))
[1] "R(pi^tree): 17.7"


Classification via logistic Lasso

Let us compare this to a different classification algorithm, the logistic Lasso. This can be implemented with the glmnet package.

# Use logistic Lasso for classification
class_lasso = cv.glmnet(x, sign_y_tilde,       
                  family = "binomial",       # tell that it is a binary variable
                  type.measure = "class",    # tell that we want to classify
                  weights = abs_y_tilde)      # using the abs pseudo-outcome as weights
plot(class_lasso)

# Predict the policy for everybody in the sample
pi_hat_lasso = predict(class_lasso, newx = x, s = "lambda.min", type = "class")

We check again accuracy, value and regret:

# Compare to the optimal rule
table(pi_star,pi_hat_lasso)
             pi_hat_lasso
pi_star       Don't treat Treat
  Don't treat         482     0
  Treat                 4   514
# compute the accuracy
paste0("The classification accuracy of the Lasso is ", (sum(diag(table(pi_star,pi_hat_lasso))) / n) * 100, "%")
[1] "The classification accuracy of the Lasso is 99.6%"
# Calculate value and regret
Q_pi_hat_lasso = sum( (pi_hat_lasso == "Treat") * m1(x[,1]) + (pi_hat_lasso == "Don't treat") * m0(x[,1]) )
regret_lasso = Q_pi_star - Q_pi_hat_lasso
# Print
paste0("Q(pi*): ", round(Q_pi_star,1))
[1] "Q(pi*): 627.1"
paste0("Q(pi^lasso): ", round(Q_pi_hat_lasso,1))
[1] "Q(pi^lasso): 627"
paste0("R(pi^lasso): ", round(regret_lasso,1))
[1] "R(pi^lasso): 0.1"

In this draw the Lasso achieves a better classification accuracy than the regression tree and this is also reflected in a lower regret.


Policy learning via optimal decision trees

The classification tree was cross-validated and build greedily. This means that that depth (number of splits) is chosen in a data-driven way. An alternative way is to fix the tree depth and to find the optimal splitting via grid search. This is most important for multiple treatments where the classification trick fails, but also works for binary treatments.

We use the policytree package to estimate a decision tree with one split and two splits. Instead of the ATE pseudo-outcome \(\tilde{Y}_{ATE}\) that was stored in the object$ATE$delta, we have to pass the two columns containing the pseudo-outcomes for the two APOs \(\tilde{Y}_{\gamma_0}\) and \(\tilde{Y}_{\gamma_1}\) to the policy_tree function, which are stored in object$APO$gamma. But again we only reuse stuff that was needed to get the ATE in the first place.


One split

# Run policy tree
pt1 = policy_tree(x,aipw$APO$gamma,depth=1)
pi_hat_pt1 = predict(pt1,newdata=x)
plot(pt1)

Action 1 means no treatment, action 2 means treatment. We see that it finds the perfect split. Let’s see this in the performance metrics:

# Compare to the optimal rule
table(pi_star,pi_hat_pt1)
             pi_hat_pt1
pi_star         1   2
  Don't treat 482   0
  Treat         0 518
# compute the accuracy
paste0("The classification accuracy of the one split tree is ", (sum(diag(table(pi_star,pi_hat_pt1))) / n) * 100, "%")
[1] "The classification accuracy of the one split tree is 100%"
# Calculate value and regret
Q_pi_hat_pt1 = sum( (pi_hat_pt1 == 2) * m1(x[,1]) + (pi_hat_pt1 == 1) * m0(x[,1]) )
regret_pt1 = Q_pi_star - Q_pi_hat_pt1
# Print
paste0("Q(pi*): ", round(Q_pi_star,1))
[1] "Q(pi*): 627.1"
paste0("Q(pi^pt1): ", round(Q_pi_hat_pt1,1))
[1] "Q(pi^pt1): 627.1"
paste0("R(pi^pt1): ", round(regret_pt1,1))
[1] "R(pi^pt1): 0"

It nails it and therefore produces no regret.


Two splits

Let’s do the same with setting the depth to two:

# Run policy tree
pt2 = policy_tree(x,aipw$APO$gamma,depth=2)
pi_hat_pt2 = predict(pt2,newdata=x)
plot(pt2)

The optimal tree depth is known to us and would only use one split. By forcing the tree to split twice, we deteriorate its performance:

# Compare to the optimal rule
table(pi_star,pi_hat_pt2)
             pi_hat_pt2
pi_star         1   2
  Don't treat 455  27
  Treat        14 504
# compute the accuracy
paste0("The classification accuracy of the two split tree is ", (sum(diag(table(pi_star,pi_hat_pt2))) / n) * 100, "%")
[1] "The classification accuracy of the two split tree is 95.9%"
# Calculate value and regret
Q_pi_hat_pt2 = sum( (pi_hat_pt2 == 2) * m1(x[,1]) + (pi_hat_pt2 == 1) * m0(x[,1]) )
regret_pt2 = Q_pi_star - Q_pi_hat_pt2
# Print
paste0("Q(pi*): ", round(Q_pi_star,1))
[1] "Q(pi*): 627.1"
paste0("Q(pi^pt2): ", round(Q_pi_hat_pt2,1))
[1] "Q(pi^pt2): 621.8"
paste0("R(pi^pt2): ", round(regret_pt2,1))
[1] "R(pi^pt2): 5.3"



Simulation study

To be sure that the results do not depend on one particular draw, we run 100 replications and plot the classification accuracy and the regret for the different methods.

reps = 100

# Initialize results containers
results_ca = results_reg = matrix(NA,reps,4)
colnames(results_ca) = colnames(results_reg) = c("Classification tree","Lasso","Policy tree 1","Policy tree 2")

for (i in 1:reps) {
  
  # Draw sample
  
  x = matrix(runif(n*p,-pi,pi),ncol=p)
  w = rbinom(n,1,e(x[,1]))
  y0 = m0(x[,1]) + rnorm(n,0,1)
  y1 = m1(x[,1]) + rnorm(n,0,1)
  y = w*y1 + (1-w)*y0 + rnorm(n,0,1)
  pi_star = ifelse(x[,1]>0,"Treat","Don't treat")
  Q_star = sum( (pi_star == "Treat") * m1(x[,1]) + (pi_star == "Don't treat") * m0(x[,1]) )
  
  # Get pseudo-outcome
  aipw = DML_aipw(y,w,x,ml_w=list(forest),ml_y=list(forest),cf=2)
  
  # Define the sign of the pseudo-outcome
  sign_y_tilde = sign(aipw$ATE$delta)
  sign_y_tilde = factor(sign_y_tilde, labels = c("Don't treat","Treat"))

  # Define the weight as absolute value of the pseudo-outcome
  abs_y_tilde = abs(aipw$ATE$delta)

  # Classification tree
  class_tree = rpart(sign_y_tilde ~ x,    
                  weights = abs_y_tilde,
                  method = "class")
  pi_hat_tree = predict(class_tree,type="class")
  results_ca[i,1] = sum(diag(table(pi_star,pi_hat_tree))) / n * 100
  results_reg[i,1] = Q_star - sum( (pi_hat_tree == "Treat") * m1(x[,1]) + (pi_hat_tree == "Don't treat") * m0(x[,1]) )
  
  # Lasso
  class_lasso = cv.glmnet(x, sign_y_tilde,       
                  family = "binomial",       # tell that it is a binary variable
                  type.measure = "class",    # tell that we want to classify
                  weights = abs_y_tilde)     # using the abs pseudo-outcome as weights
  pi_hat_lasso = predict(class_lasso, newx = x, s = "lambda.min", type = "class")
  results_ca[i,2] = sum(diag(table(pi_star,pi_hat_lasso))) / n * 100
  results_reg[i,2] = Q_star - sum( (pi_hat_lasso == "Treat") * m1(x[,1]) + (pi_hat_lasso == "Don't treat") * m0(x[,1]) )
  
  # Policy tree depth 1
  pt1 = policy_tree(x,aipw$APO$gamma,depth=1)
  pi_hat_pt1 = predict(pt1,newdata=x)
  results_ca[i,3] = sum(diag(table(pi_star,pi_hat_pt1))) / n * 100
  results_reg[i,3] = Q_star - sum( (pi_hat_pt1 == 2) * m1(x[,1]) + (pi_hat_pt1 == 1) * m0(x[,1]) )
  
  # Policy tree depth 2
  pt2 = policy_tree(x,aipw$APO$gamma,depth=2)
  pi_hat_pt2 = predict(pt2,newdata=x)
  results_ca[i,4] = sum(diag(table(pi_star,pi_hat_pt2))) / n * 100
  results_reg[i,4] = Q_star - sum( (pi_hat_pt2 == 2) * m1(x[,1]) + (pi_hat_pt2 == 1) * m0(x[,1]) )
}

round(colMeans(results_ca),1)
Classification tree               Lasso       Policy tree 1       Policy tree 2 
               91.3                96.5                97.6                94.2 
round(colMeans(results_reg),1)
Classification tree               Lasso       Policy tree 1       Policy tree 2 
               47.2                12.0                 5.3                24.1 

We see that the depth one policy tree performs best in terms of classification accuracy and regret. This is not surprising as the fixed depth of one is basically the oracle.

Plot the results:

# Plot the data ready
as_tibble(results_ca) %>% pivot_longer(cols = everything(), names_to = "Method", values_to = "CA") %>% 
  ggplot(aes(x = CA, y = fct_rev(Method), fill = Method)) +
  geom_density_ridges(stat = "binline", bins = 20, draw_baseline = FALSE) + xlab("Classification accuracy in percent")

colMeans(results_ca)
Classification tree               Lasso       Policy tree 1       Policy tree 2 
             91.349              96.481              97.638              94.217 
# Plot the data ready
as_tibble(results_reg) %>% pivot_longer(cols = everything(), names_to = "Method", values_to = "CA") %>% 
  ggplot(aes(x = CA, y = fct_rev(Method), fill = Method)) +
  geom_density_ridges(stat = "binline", bins = 20, draw_baseline = FALSE) + xlab("Regret")

colMeans(results_reg)
Classification tree               Lasso       Policy tree 1       Policy tree 2 
          47.154065           12.006349            5.337177           24.087355 

We observe that the policy tree and Lasso perform quite well, while the classification tree performs surprisingly bad given that the optimal policy requires just one split.

Take-away

  • The pseudo-outcome from the Double ML AIPW estimator can also be used to estimate policy rules in a weighted classification problem and in a policy tree algorithm.



Suggestions to play with the toy model

Some suggestions:

  • Use different methods for the classification problem

  • Create different CATE and nuisance functions

  • Change the treatment shares

  • Experience how long a depth three tree is running

LS0tDQp0aXRsZTogIkNhdXNhbCBNTDogT2ZmbGluZSBwb2xpY3kgbGVhcm5pbmciDQpzdWJ0aXRsZTogIlNpbXVsYXRpb24gbm90ZWJvb2siDQphdXRob3I6ICJNaWNoYWVsIEtuYXVzIg0KZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJW0vJXknKWAiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KLS0tDQoNCg0KR29hbHM6DQoNCi0gSGFuZGNvZGUgcG9saWN5IGxlYXJuaW5nIHdpdGggY2xhc3NpZmljYXRpb24gdHJlZXMgYW5kIExhc3NvIA0KDQotIEFwcGx5IHRoZSBgcG9saWN5dHJlZWAgcGFja2FnZQ0KDQo8YnI+DQoNCg0KIyBOb25saW5lYXIgaGV0ZXJvZ2VuZWl0eSBhbmQgZGlzY3JldGUgcG9saWN5IHJ1bGUNCg0KV2UgcmV2aXNpdCB0aGUgREdQIGZyb20gTm90ZWJvb2tzIFtDYXVzYWwgTUw6IEFJUFcgRG91YmxlIE1MIChBVEUpXShodHRwczovL21ja25hdXMuZ2l0aHViLmlvL2Fzc2V0cy9ub3RlYm9va3MvU05CL1NOQl9BSVBXX0RNTC5uYi5odG1sKSBhbmQgW0NhdXNhbCBNTDogR3JvdXAgQXZlcmFnZSBUcmVhdG1lbnQgRWZmZWN0c10oaHR0cHM6Ly9tY2tuYXVzLmdpdGh1Yi5pby9hc3NldHMvbm90ZWJvb2tzL1NOQi9TTkJfR0FURS5uYi5odG1sKSB3aXRoIHplcm8gQVRFIGJ1dCBoaWdobHkgbm9ubGluZWFyIGVmZmVjdCBoZXRlcm9nZW5laXR5Og0KDQotICRwPTEwJCBpbmRlcGVuZGVudCBjb3ZhcmlhdGVzICRYXzEsLi4uLFhfaywuLi4sWF97MTB9JCBkcmF3biBmcm9tIGEgdW5pZm9ybSBkaXN0cmlidXRpb246ICRYX2sgXHNpbSB1bmlmb3JtKC1ccGksXHBpKSQNCg0KLSBUaGUgdHJlYXRtZW50IG1vZGVsIGlzICRXIFxzaW0gQmVybm91bGxpKFx1bmRlcmJyYWNle1xQaGkoc2luKFhfMSkpfV97ZShYKX0pJCwgd2hlcmUgJFxQaGkoXGNkb3QpJCBpcyB0aGUgc3RhbmRhcmQgbm9ybWFsIGN1bXVsYXRpdmUgZGVuc2l0eSBmdW5jdGlvbg0KDQotIFRoZSBwb3RlbnRpYWwgb3V0Y29tZSBtb2RlbCBvZiB0aGUgY29udHJvbHMgaXMgJFkoMCkgPSBcdW5kZXJicmFjZXtjb3MoWF8xKzEvMlxwaSl9X3ttXzAoWCl9KyBcdmFyZXBzaWxvbiQsIHdpdGggJFx2YXJlcHNpbG9uIFxzaW0gTigwLDEpJA0KDQotIFRoZSBwb3RlbnRpYWwgb3V0Y29tZSBtb2RlbCBvZiB0aGUgdHJlYXRlZCBpcyAkXHVuZGVyYnJhY2V7c2luKFhfMSl9X3ttXzEoWCl9ICsgXHZhcmVwc2lsb24kLCB3aXRoICRcdmFyZXBzaWxvbiBcc2ltIE4oMCwxKSQNCg0KLSBUaGUgdHJlYXRtZW50IGVmZmVjdCBmdW5jdGlvbiBpcyAkXHRhdShYKSA9IHNpbihYXzEpIC0gY29zKFhfMSsxLzJccGkpJA0KDQpUaGlzIG1lYW5zIHRoYXQgc29tZSBzaW11bGF0ZWQgaW5kaXZpZHVhbHMgYmVuZWZpdCBmcm9tIHRoZSB0cmVhdG1lbnQgKGFzc3VtaW5nIHRoYXQgaGlnaGVyIG91dGNvbWUgaXMgYmV0dGVyKSBhbmQgc29tZSBkbyBub3QuIFRoZSBvcHRpbWFsIHBvbGljeSBpcyBnaXZlbiBieSAkXHBpXiooeCkgPSBcbWF0aGJmezF9W1hfMSA+IDBdJCBhbmQgc2hvdWxkIGJlIHJlY292ZXJlZCBieSB0aGUgbWV0aG9kcyB3ZSBkaXNjdXNzZWQgaW4gdGhlIGxlY3R1cmUuDQoNCg0KYGBge3IsIHdhcm5pbmcgPSBGLCBtZXNzYWdlID0gRn0NCmlmICghcmVxdWlyZSgicnBhcnQiKSkgaW5zdGFsbC5wYWNrYWdlcygicnBhcnQiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeShycGFydCkNCmlmICghcmVxdWlyZSgicnBhcnQucGxvdCIpKSBpbnN0YWxsLnBhY2thZ2VzKCJycGFydC5wbG90IiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkocnBhcnQucGxvdCkNCmlmICghcmVxdWlyZSgiZ2xtbmV0IikpIGluc3RhbGwucGFja2FnZXMoImdsbW5ldCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KGdsbW5ldCkNCmlmICghcmVxdWlyZSgidGlkeXZlcnNlIikpIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KHRpZHl2ZXJzZSkNCmlmICghcmVxdWlyZSgicGF0Y2h3b3JrIikpIGluc3RhbGwucGFja2FnZXMoInBhdGNod29yayIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KHBhdGNod29yaykNCmlmICghcmVxdWlyZSgicG9saWN5dHJlZSIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwb2xpY3l0cmVlIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkocG9saWN5dHJlZSkNCmlmICghcmVxdWlyZSgiZ2dyaWRnZXMiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dyaWRnZXMiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeShnZ3JpZGdlcykNCmlmICghcmVxdWlyZSgiRGlhZ3JhbW1lUiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJEaWFncmFtbWVSIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoRGlhZ3JhbW1lUikNCmlmICghcmVxdWlyZSgiY2F1c2FsRE1MIikpIHsNCiAgaWYgKCFyZXF1aXJlKCJkZXZ0b29scyIpKSBpbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KGRldnRvb2xzKQ0KICBpbnN0YWxsX2dpdGh1YihyZXBvPSJNQ0tuYXVzL2NhdXNhbERNTCIpIA0KfTsgbGlicmFyeShjYXVzYWxETUwpDQoNCnNldC5zZWVkKDEyMzQpDQoNCiMgZGVmaW5lIHBhcmFtZXRlcnMgZm9yIHRoZSBER1ANCm4gPSAxMDAwDQpwID0gMTANCg0KIyBHZW5lcmF0ZSBkYXRhIGFzIHVzdWFsLCBidXQgc2F2ZSBib3RoIFkwIGFuZCBZMQ0KeCA9IG1hdHJpeChydW5pZihuKnAsLXBpLHBpKSxuY29sPXApDQplID0gZnVuY3Rpb24oeCl7cG5vcm0oc2luKHgpKX0NCm0xID0gZnVuY3Rpb24oeCl7c2luKHgpfQ0KbTAgPSBmdW5jdGlvbih4KXtjb3MoeCsxLzIqcGkpfQ0KdGF1ID0gZnVuY3Rpb24oeCl7bTEoeCkgLSBtMCh4KX0NCncgPSByYmlub20obiwxLGUoeFssMV0pKQ0KeTAgPSBtMCh4WywxXSkgKyBybm9ybShuLDAsMSkNCnkxID0gbTEoeFssMV0pICsgcm5vcm0obiwwLDEpDQp5ID0gdyp5MSArICgxLXcpKnkwDQojIERlZmluZSBvcHRpbWFsIHBvbGljeSBhZGRpdGlvbmFsbHkNCnBpX3N0YXIgPSBpZmVsc2UoeFssMV0+MCwiVHJlYXQiLCJEb24ndCB0cmVhdCIpDQoNCiMgcGxvdCB0aGUgREdQDQpnMSA9IGRhdGEuZnJhbWUoeCA9IGMoLXBpLCBwaSkpICU+JSBnZ3Bsb3QoYWVzKHgpKSArIHN0YXRfZnVuY3Rpb24oZnVuPWUsc2l6ZT0xKSArIHlsYWIoImUiKSArIHhsYWIoIlgxIikNCmcyID0gZGF0YS5mcmFtZSh4ID0gYygtcGksIHBpKSkgJT4lIGdncGxvdChhZXMoeCkpICsgc3RhdF9mdW5jdGlvbihmdW49bTEsc2l6ZT0xLGFlcyhjb2xvdXI9IlkxIikpICsgDQogIHN0YXRfZnVuY3Rpb24oZnVuPW0wLHNpemU9MSxhZXMoY29sb3VyPSJZMCIpKSArIHlsYWIoIlkiKSArIHhsYWIoIlgxIikNCmczID0gZGF0YS5mcmFtZSh4ID0gYygtcGksIHBpKSkgJT4lIGdncGxvdChhZXMoeCkpICsgc3RhdF9mdW5jdGlvbihmdW49dGF1LHNpemU9MSkgKyB5bGFiKGV4cHJlc3Npb24odGF1KSkgKyANCiAgICAgIHhsYWIoIlgxIikgKyBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MCkgKyBhbm5vdGF0ZSgidGV4dCIsLXBpLzIsMCxsYWJlbD0iRG9uJ3QgdHJlYXQiKSArIA0KICAgICAgYW5ub3RhdGUoInRleHQiLHBpLzIsMCxsYWJlbD0iVHJlYXQiKQ0KZzEgLyBnMiAvIGczDQpgYGANCldlIHNlZSB0aGF0IHRoZSBiZXN0IHBvbGljeSBhc3NpZ25zIGV2ZXJ5Ym9keSB3aXRoICRYPjAkIHRvIHRyZWF0bWVudCBhbmQgbGVhdmVzIGV2ZXJ5Ym9keSB3aXRoICRYXGxlcTAkIHVudHJlYXRlZC4NCg0KPGJyPiANCg0KIyMgUG9saWN5IGxlYXJuaW5nIGFzIGEgd2VpZ2h0ZWQgY2xhc3NpZmljYXRpb24gcHJvYmxlbQ0KDQoxLiBFc3RpbWF0ZSB0aGUgQUlQVyB3LyA1LWZvbGQgY3Jvc3MtZml0dGluZw0KDQpXZSBkcmF3IGEgc2FtcGxlIG9mICROPTEwMDAkIGFuZCBhbmQgZXN0aW1hdGUgdGhlIDUtZm9sZCBjcm9zcy1maXQgbnVpc2FuY2UgcGFyYW1ldGVycyAkZShYKT1FW1d8WF0kLCAkbSgwLFgpPUVbWXxXPTAsWF0kIGFuZCAkbSgxLFgpPUVbWXxXPTEsWF0kIHVzaW5nIHJhbmRvbSBmb3Jlc3Qgd2l0aCBob25lc3R5IGFuZCBwbHVnIHRoZSBwcmVkaWN0aW9ucyBpbnRvIHRoZSBwc2V1ZG8gb3V0Y29tZToNCiQkXHRpbGRle1l9X3tBVEV9ID0gXHVuZGVyYnJhY2V7XGhhdHttfSgxLFgpIC0gXGhhdHttfSgwLFgpfV97XHRleHR7b3V0Y29tZSBwcmVkaWN0aW9uc319ICsgXHVuZGVyYnJhY2V7XGZyYWN7VyAoWSAtIFxoYXR7bX0oMSxYKSl9e1xoYXR7ZX0oWCl9IC0gXGZyYWN7KDEtVykgKFkgLSBcaGF0e219KDAsWCkpfXsxLVxoYXR7ZX0oWCl9fV97XHRleHR7d2VpZ2h0ZWQgcmVzaWR1YWxzfX0uJCQNCg0KYGBge3J9DQojIEdldCB0aGUgcHNldWRvLW91dGNvbWUNCiMgNS1mb2xkIGNyb3NzLWZpdHRpbmcgd2l0aCBjYXVzYWxETUwgcGFja2FnZQ0KIyBDcmVhdGUgbGVhcm5lcg0KZm9yZXN0ID0gY3JlYXRlX21ldGhvZCgiZm9yZXN0X2dyZiIsYXJncz1saXN0KHR1bmUucGFyYW1ldGVycyA9ICJhbGwiKSkNCiMgUnVuIHRoZSBBSVBXIGVzdGltYXRpb24NCmFpcHcgPSBETUxfYWlwdyh5LHcseCxtbF93PWxpc3QoZm9yZXN0KSxtbF95PWxpc3QoZm9yZXN0KSxjZj01KQ0KIyBhdmVyYWdlIHBvdGVudGlhbCBvdXRjb21lcw0Kc3VtbWFyeShhaXB3JEFQTykNCiMgYXZlcmFnZSB0cmVhdG1lbnQgZWZmZWN0DQpzdW1tYXJ5KGFpcHckQVRFKQ0KYGBgDQoNCjIuIEVzdGltYXRlIHdlaWdodGVkIGNsYXNzaWZpY2F0aW9uDQoNCldlIHdhbnQgdG8gY2xhc3NpZnkgdGhlIHNpZ24gb2YgdGhlIENBVEUgd2hpbGUgZmF2b3JpbmcgY29ycmVjdCBjbGFzc2lmaWNhdGlvbnMgd2l0aCBsYXJnZXIgYWJzb2x1dGUgQ0FURXMuIEluc3RlYWQgb2YgdGhlIHVub2JzZXJ2ZWQgQ0FURSwgd2UgdXNlIHRoZSBzaWduIG9mIHRoZSBwc2V1ZG8gb3V0Y29tZXMgJFx0aWxkZXtZfV97QVRFfSQgZnJvbSB0aGUgRG91YmxlIE1MIEFJUFcgdG8gcHJveHkgdGhlIHNpZ24gb2YgdGhlIENBVEUgYW5kIHRoZSBhYnNvbHV0ZSB2YWx1ZSBvZiB0aGUgcHNldWRvLW91dGNvbWUgdG8gcHJveHkgdGhlIGFic29sdXRlIHZhbHVlIG9mIHRoZSBDQVRFLiBXZSB0aGVuIGxlYXJuIHRoZSBwb2xpY3kgYnkgY2xhc3NpZnlpbmcgdGhlIHNpZ24gb2YgdGhlIHBzZXVkbyBvdXRjb21lIHVzaW5nIHRoZSBhdmFpbGFibGUgY292YXJpYXRlcy9wb2xpY3kgdmFyaWFibGVzIHdlaWdodGVkIGJ5IGl0cyBhYnNvbHV0ZSB2YWx1ZToNCiQkXGhhdHtccGl9PVx1bmRlcnNldHtccGlcaW4gXFBpfXtcb3BlcmF0b3JuYW1le2FyZyBtYXh9fSBcYmlnZ1x7IFxmcmFjezF9e059IFxzdW1fe2k9MX1ee059IFxtaWQgXHRpbGRle1l9X3tpLEFURX0gXG1pZCBzaWduXGJpZyhcdGlsZGV7WX1fe2ksQVRFfVxiaWcpIFxiaWcoMlxwaShYX2kpLTFcYmlnKVxiaWdnXH0uJCQNCg0KPGJyPiANCg0KIyMjIENsYXNzaWZpY2F0aW9uIHRyZWUNCg0KSW4gdGhlIGZvbGxvd2luZywgd2UgZXN0aW1hdGUgYSBwb2xpY3kgYXNzaWdubWVudCBydWxlICRcaGF0e1xwaX1ee3RyZWV9JCB1c2luZyBhIHdlaWdodGVkIGNsYXNzaWZpY2F0aW9uIHRyZWUuIFRoZSBhZHZhbnRhZ2Ugb2YgdGhlIHRyZWUgaXMgaXRzIGludGVycHJldGFiaWxpdHksIGJ1dCBvYnNlcnZlIHRoYXQgaXQgYmVjb21lcyBvdmVybHkgY29tcGxpY2F0ZWQgaW4gdGhpcyBzYW1wbGU6DQoNCmBgYHtyfQ0KIyBEZWZpbmUgdGhlIHNpZ24gb2YgdGhlIHBzZXVkby1vdXRjb21lDQpzaWduX3lfdGlsZGUgPSBzaWduKGFpcHckQVRFJGRlbHRhKQ0Kc2lnbl95X3RpbGRlID0gZmFjdG9yKHNpZ25feV90aWxkZSwgbGFiZWxzID0gYygiRG9uJ3QgdHJlYXQiLCJUcmVhdCIpKQ0KDQojIERlZmluZSB0aGUgd2VpZ2h0IGFzIGFic29sdXRlIHZhbHVlIG9mIHRoZSBwc2V1ZG8tb3V0Y29tZQ0KYWJzX3lfdGlsZGUgPSBhYnMoYWlwdyRBVEUkZGVsdGEpDQoNCiMgQnVpbGQgY2xhc3NpZmljYXRpb24gdHJlZQ0KY2xhc3NfdHJlZSA9IHJwYXJ0KHNpZ25feV90aWxkZSB+IHgsICAgICAgIyBQcmVkaWN0IHNpZ24gb2YgdHJlYXRtZW50DQogICAgICAgICAgICAgICAgICB3ZWlnaHRzID0gYWJzX3lfdGlsZGUsICAjIHdpdGggd2VpZ2h0IGFic29sdXRlIHBzZXVkbyBvdXRjb21lDQogICAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2xhc3MiKSAgICAgICMgdGVsbCB3ZSB3YW50IHRvIGNsYXNzaWZ5DQoNCiMgUGxvdCB0aGUgdHJlZQ0KcnBhcnQucGxvdChjbGFzc190cmVlLGRpZ2l0cz0zKQ0KYGBgDQoNCldlIGNoZWNrIHRoZSBjb25mdXNpb24gbWF0cml4IGFzIGEgY3Jvc3MtdGFibGUgb2YgdGhlIG9wdGltYWwgYW5kIHRoZSBlc3RpbWF0ZXMgcG9saWN5IHJ1bGUuIFdlIGhvcGUgdG8gZmluZCBtb3N0IG9ic2VydmF0aW9ucyBvbiB0aGUgZGlhZ29uYWwNCg0KYGBge3J9DQojIFByZWRpY3QgdGhlIHBvbGljeSBmb3IgZXZlcnlib2R5IGluIHRoZSBzYW1wbGUNCnBpX2hhdF90cmVlID0gcHJlZGljdChjbGFzc190cmVlLHR5cGU9ImNsYXNzIikNCiMgQ29tcGFyZSB0byB0aGUgb3B0aW1hbCBydWxlDQp0YWJsZShwaV9zdGFyLHBpX2hhdF90cmVlKQ0KIyBjb21wdXRlIHRoZSBhY2N1cmFjeQ0KcGFzdGUwKCJUaGUgY2xhc3NpZmljYXRpb24gYWNjdXJhY3kgb2YgdGhlIHRyZWUgaXMgIiwgKHN1bShkaWFnKHRhYmxlKHBpX3N0YXIscGlfaGF0X3RyZWUpKSkgLyBuKSAqIDEwMCwgIiUiKQ0KYGBgDQoNCldlIHNlZSB0aGF0IHRoZSBtYWpvcml0eSBpcyBjb3JyZWN0bHkgY2xhc3NpZmllZCwgYWx0aG91Z2ggdGhlIHRyZWUgaXMgb3Zlcmx5IGNvbXBsaWNhdGVkIGluIHRoaXMgcGFydGljdWxhciBkcmF3LiBJdCBzaG91bGQgaGF2ZSBzdG9wcGVkIGFmdGVyIHRoZSBmaXJzdCBzcGxpdC4NCg0KQXMgd2Uga25vdyBib3RoIHBvdGVudGlhbCBvdXRjb21lcyBpbiBvdXIgc2ltdWxhdGVkIGRhdGFzZXQsIHdlIGNhbiBjYWxjdWxhdGUgKGkpIHRoZSB2YWx1ZSBmdW5jdGlvbiB1bmRlciB0aGUgb3B0aW1hbCBwb2xpY3kgJFEoXHBpXiopID0gRVtZKFxwaV4qKV0kLCAoaWkpIHRoZSB2YWx1ZSBmdW5jdGlvbiBvZiB0aGUgZXN0aW1hdGVkIGFzc2lnbm1lbnQgcnVsZSAkUShcaGF0e1xwaX1ee3RyZWV9KSA9IEVbWShcaGF0e1xwaX1ee3RyZWV9KV0kIGFuZCAoaWlpKSB0aGUgcmVncmV0ICRSKFxoYXR7XHBpfV57dHJlZX0pID0gUShccGleKikgLSBRKFxoYXR7XHBpfV57dHJlZX0pJA0KDQpgYGB7cn0NCiMgQ2FsY3VsYXRlIHZhbHVlcyBhbmQgcmVncmV0DQpRX3BpX3N0YXIgPSBzdW0oIChwaV9zdGFyID09ICJUcmVhdCIpICogbTEoeFssMV0pICsgKHBpX3N0YXIgPT0gIkRvbid0IHRyZWF0IikgKiBtMCh4WywxXSkgKQ0KUV9waV9oYXRfdHJlZSA9IHN1bSggKHBpX2hhdF90cmVlID09ICJUcmVhdCIpICogbTEoeFssMV0pICsgKHBpX2hhdF90cmVlID09ICJEb24ndCB0cmVhdCIpICogbTAoeFssMV0pICkNCnJlZ3JldF90cmVlID0gUV9waV9zdGFyIC0gUV9waV9oYXRfdHJlZQ0KIyBQcmludA0KcGFzdGUwKCJRKHBpKik6ICIsIHJvdW5kKFFfcGlfc3RhciwxKSkNCnBhc3RlMCgiUShwaV50cmVlKTogIiwgcm91bmQoUV9waV9oYXRfdHJlZSwxKSkNCnBhc3RlMCgiUihwaV50cmVlKTogIiwgcm91bmQocmVncmV0X3RyZWUsMSkpDQpgYGANCg0KPGJyPg0KDQojIyMgQ2xhc3NpZmljYXRpb24gdmlhIGxvZ2lzdGljIExhc3NvDQoNCkxldCB1cyBjb21wYXJlIHRoaXMgdG8gYSBkaWZmZXJlbnQgY2xhc3NpZmljYXRpb24gYWxnb3JpdGhtLCB0aGUgbG9naXN0aWMgTGFzc28uIFRoaXMgY2FuIGJlIGltcGxlbWVudGVkIHdpdGggdGhlICpnbG1uZXQqIHBhY2thZ2UuDQoNCg0KYGBge3J9DQojIFVzZSBsb2dpc3RpYyBMYXNzbyBmb3IgY2xhc3NpZmljYXRpb24NCmNsYXNzX2xhc3NvID0gY3YuZ2xtbmV0KHgsIHNpZ25feV90aWxkZSwgICAgICAgDQogICAgICAgICAgICAgICAgICBmYW1pbHkgPSAiYmlub21pYWwiLCAgICAgICAjIHRlbGwgdGhhdCBpdCBpcyBhIGJpbmFyeSB2YXJpYWJsZQ0KICAgICAgICAgICAgICAgICAgdHlwZS5tZWFzdXJlID0gImNsYXNzIiwgICAgIyB0ZWxsIHRoYXQgd2Ugd2FudCB0byBjbGFzc2lmeQ0KICAgICAgICAgICAgICAgICAgd2VpZ2h0cyA9IGFic195X3RpbGRlKSAgICAgICMgdXNpbmcgdGhlIGFicyBwc2V1ZG8tb3V0Y29tZSBhcyB3ZWlnaHRzDQpwbG90KGNsYXNzX2xhc3NvKQ0KIyBQcmVkaWN0IHRoZSBwb2xpY3kgZm9yIGV2ZXJ5Ym9keSBpbiB0aGUgc2FtcGxlDQpwaV9oYXRfbGFzc28gPSBwcmVkaWN0KGNsYXNzX2xhc3NvLCBuZXd4ID0geCwgcyA9ICJsYW1iZGEubWluIiwgdHlwZSA9ICJjbGFzcyIpDQpgYGANCg0KV2UgY2hlY2sgYWdhaW4gYWNjdXJhY3ksIHZhbHVlIGFuZCByZWdyZXQ6DQoNCmBgYHtyfQ0KIyBDb21wYXJlIHRvIHRoZSBvcHRpbWFsIHJ1bGUNCnRhYmxlKHBpX3N0YXIscGlfaGF0X2xhc3NvKQ0KIyBjb21wdXRlIHRoZSBhY2N1cmFjeQ0KcGFzdGUwKCJUaGUgY2xhc3NpZmljYXRpb24gYWNjdXJhY3kgb2YgdGhlIExhc3NvIGlzICIsIChzdW0oZGlhZyh0YWJsZShwaV9zdGFyLHBpX2hhdF9sYXNzbykpKSAvIG4pICogMTAwLCAiJSIpDQojIENhbGN1bGF0ZSB2YWx1ZSBhbmQgcmVncmV0DQpRX3BpX2hhdF9sYXNzbyA9IHN1bSggKHBpX2hhdF9sYXNzbyA9PSAiVHJlYXQiKSAqIG0xKHhbLDFdKSArIChwaV9oYXRfbGFzc28gPT0gIkRvbid0IHRyZWF0IikgKiBtMCh4WywxXSkgKQ0KcmVncmV0X2xhc3NvID0gUV9waV9zdGFyIC0gUV9waV9oYXRfbGFzc28NCiMgUHJpbnQNCnBhc3RlMCgiUShwaSopOiAiLCByb3VuZChRX3BpX3N0YXIsMSkpDQpwYXN0ZTAoIlEocGlebGFzc28pOiAiLCByb3VuZChRX3BpX2hhdF9sYXNzbywxKSkNCnBhc3RlMCgiUihwaV5sYXNzbyk6ICIsIHJvdW5kKHJlZ3JldF9sYXNzbywxKSkNCmBgYA0KDQpJbiB0aGlzIGRyYXcgdGhlIExhc3NvIGFjaGlldmVzIGEgYmV0dGVyIGNsYXNzaWZpY2F0aW9uIGFjY3VyYWN5IHRoYW4gdGhlIHJlZ3Jlc3Npb24gdHJlZSBhbmQgdGhpcyBpcyBhbHNvIHJlZmxlY3RlZCBpbiBhIGxvd2VyIHJlZ3JldC4NCg0KPGJyPg0KDQojIyMgUG9saWN5IGxlYXJuaW5nIHZpYSBvcHRpbWFsIGRlY2lzaW9uIHRyZWVzDQoNClRoZSBjbGFzc2lmaWNhdGlvbiB0cmVlIHdhcyBjcm9zcy12YWxpZGF0ZWQgYW5kIGJ1aWxkIGdyZWVkaWx5LiBUaGlzIG1lYW5zIHRoYXQgdGhhdCBkZXB0aCAobnVtYmVyIG9mIHNwbGl0cykgaXMgY2hvc2VuIGluIGEgZGF0YS1kcml2ZW4gd2F5LiBBbiBhbHRlcm5hdGl2ZSB3YXkgaXMgdG8gZml4IHRoZSB0cmVlIGRlcHRoIGFuZCB0byBmaW5kIHRoZSBvcHRpbWFsIHNwbGl0dGluZyB2aWEgZ3JpZCBzZWFyY2guIFRoaXMgaXMgbW9zdCBpbXBvcnRhbnQgZm9yIG11bHRpcGxlIHRyZWF0bWVudHMgd2hlcmUgdGhlIGNsYXNzaWZpY2F0aW9uIHRyaWNrIGZhaWxzLCBidXQgYWxzbyB3b3JrcyBmb3IgYmluYXJ5IHRyZWF0bWVudHMuDQoNCldlIHVzZSB0aGUgYHBvbGljeXRyZWVgIHBhY2thZ2UgdG8gZXN0aW1hdGUgYSBkZWNpc2lvbiB0cmVlIHdpdGggb25lIHNwbGl0IGFuZCB0d28gc3BsaXRzLiBJbnN0ZWFkIG9mIHRoZSBBVEUgcHNldWRvLW91dGNvbWUgJFx0aWxkZXtZfV97QVRFfSQgdGhhdCB3YXMgc3RvcmVkIGluIHRoZSBgb2JqZWN0JEFURSRkZWx0YWAsIHdlIGhhdmUgdG8gcGFzcyB0aGUgdHdvIGNvbHVtbnMgY29udGFpbmluZyB0aGUgcHNldWRvLW91dGNvbWVzIGZvciB0aGUgdHdvIEFQT3MgJFx0aWxkZXtZfV97XGdhbW1hXzB9JCBhbmQgJFx0aWxkZXtZfV97XGdhbW1hXzF9JCB0byB0aGUgYHBvbGljeV90cmVlYCBmdW5jdGlvbiwgd2hpY2ggYXJlIHN0b3JlZCBpbiBgb2JqZWN0JEFQTyRnYW1tYWAuIEJ1dCBhZ2FpbiB3ZSBvbmx5IHJldXNlIHN0dWZmIHRoYXQgd2FzIG5lZWRlZCB0byBnZXQgdGhlIEFURSBpbiB0aGUgZmlyc3QgcGxhY2UuDQoNCjxicj4NCg0KIyMjIyBPbmUgc3BsaXQNCg0KYGBge3J9DQojIFJ1biBwb2xpY3kgdHJlZQ0KcHQxID0gcG9saWN5X3RyZWUoeCxhaXB3JEFQTyRnYW1tYSxkZXB0aD0xKQ0KcGlfaGF0X3B0MSA9IHByZWRpY3QocHQxLG5ld2RhdGE9eCkNCnBsb3QocHQxKQ0KYGBgDQoNCkFjdGlvbiAxIG1lYW5zIG5vIHRyZWF0bWVudCwgYWN0aW9uIDIgIG1lYW5zIHRyZWF0bWVudC4gV2Ugc2VlIHRoYXQgaXQgZmluZHMgdGhlIHBlcmZlY3Qgc3BsaXQuIExldCdzIHNlZSB0aGlzIGluIHRoZSBwZXJmb3JtYW5jZSBtZXRyaWNzOiANCg0KYGBge3J9DQojIENvbXBhcmUgdG8gdGhlIG9wdGltYWwgcnVsZQ0KdGFibGUocGlfc3RhcixwaV9oYXRfcHQxKQ0KIyBjb21wdXRlIHRoZSBhY2N1cmFjeQ0KcGFzdGUwKCJUaGUgY2xhc3NpZmljYXRpb24gYWNjdXJhY3kgb2YgdGhlIG9uZSBzcGxpdCB0cmVlIGlzICIsIChzdW0oZGlhZyh0YWJsZShwaV9zdGFyLHBpX2hhdF9wdDEpKSkgLyBuKSAqIDEwMCwgIiUiKQ0KIyBDYWxjdWxhdGUgdmFsdWUgYW5kIHJlZ3JldA0KUV9waV9oYXRfcHQxID0gc3VtKCAocGlfaGF0X3B0MSA9PSAyKSAqIG0xKHhbLDFdKSArIChwaV9oYXRfcHQxID09IDEpICogbTAoeFssMV0pICkNCnJlZ3JldF9wdDEgPSBRX3BpX3N0YXIgLSBRX3BpX2hhdF9wdDENCiMgUHJpbnQNCnBhc3RlMCgiUShwaSopOiAiLCByb3VuZChRX3BpX3N0YXIsMSkpDQpwYXN0ZTAoIlEocGlecHQxKTogIiwgcm91bmQoUV9waV9oYXRfcHQxLDEpKQ0KcGFzdGUwKCJSKHBpXnB0MSk6ICIsIHJvdW5kKHJlZ3JldF9wdDEsMSkpDQpgYGANCkl0IG5haWxzIGl0IGFuZCB0aGVyZWZvcmUgcHJvZHVjZXMgbm8gcmVncmV0Lg0KDQo8YnI+DQoNCiMjIyMgVHdvIHNwbGl0cw0KDQpMZXQncyBkbyB0aGUgc2FtZSB3aXRoIHNldHRpbmcgdGhlIGRlcHRoIHRvIHR3bzoNCg0KYGBge3J9DQojIFJ1biBwb2xpY3kgdHJlZQ0KcHQyID0gcG9saWN5X3RyZWUoeCxhaXB3JEFQTyRnYW1tYSxkZXB0aD0yKQ0KcGlfaGF0X3B0MiA9IHByZWRpY3QocHQyLG5ld2RhdGE9eCkNCnBsb3QocHQyKQ0KYGBgDQoNClRoZSBvcHRpbWFsIHRyZWUgZGVwdGggaXMga25vd24gdG8gdXMgYW5kIHdvdWxkIG9ubHkgdXNlIG9uZSBzcGxpdC4gQnkgZm9yY2luZyB0aGUgdHJlZSB0byBzcGxpdCB0d2ljZSwgd2UgZGV0ZXJpb3JhdGUgaXRzIHBlcmZvcm1hbmNlOiANCg0KYGBge3J9DQojIENvbXBhcmUgdG8gdGhlIG9wdGltYWwgcnVsZQ0KdGFibGUocGlfc3RhcixwaV9oYXRfcHQyKQ0KIyBjb21wdXRlIHRoZSBhY2N1cmFjeQ0KcGFzdGUwKCJUaGUgY2xhc3NpZmljYXRpb24gYWNjdXJhY3kgb2YgdGhlIHR3byBzcGxpdCB0cmVlIGlzICIsIChzdW0oZGlhZyh0YWJsZShwaV9zdGFyLHBpX2hhdF9wdDIpKSkgLyBuKSAqIDEwMCwgIiUiKQ0KIyBDYWxjdWxhdGUgdmFsdWUgYW5kIHJlZ3JldA0KUV9waV9oYXRfcHQyID0gc3VtKCAocGlfaGF0X3B0MiA9PSAyKSAqIG0xKHhbLDFdKSArIChwaV9oYXRfcHQyID09IDEpICogbTAoeFssMV0pICkNCnJlZ3JldF9wdDIgPSBRX3BpX3N0YXIgLSBRX3BpX2hhdF9wdDINCiMgUHJpbnQNCnBhc3RlMCgiUShwaSopOiAiLCByb3VuZChRX3BpX3N0YXIsMSkpDQpwYXN0ZTAoIlEocGlecHQyKTogIiwgcm91bmQoUV9waV9oYXRfcHQyLDEpKQ0KcGFzdGUwKCJSKHBpXnB0Mik6ICIsIHJvdW5kKHJlZ3JldF9wdDIsMSkpDQpgYGANCg0KPGJyPg0KPGJyPg0KDQojIFNpbXVsYXRpb24gc3R1ZHkNCg0KVG8gYmUgc3VyZSB0aGF0IHRoZSByZXN1bHRzIGRvIG5vdCBkZXBlbmQgb24gb25lIHBhcnRpY3VsYXIgZHJhdywgd2UgcnVuIDEwMCByZXBsaWNhdGlvbnMgYW5kIHBsb3QgdGhlIGNsYXNzaWZpY2F0aW9uIGFjY3VyYWN5IGFuZCB0aGUgcmVncmV0IGZvciB0aGUgZGlmZmVyZW50IG1ldGhvZHMuDQoNCmBgYHtyfQ0KcmVwcyA9IDEwMA0KDQojIEluaXRpYWxpemUgcmVzdWx0cyBjb250YWluZXJzDQpyZXN1bHRzX2NhID0gcmVzdWx0c19yZWcgPSBtYXRyaXgoTkEscmVwcyw0KQ0KY29sbmFtZXMocmVzdWx0c19jYSkgPSBjb2xuYW1lcyhyZXN1bHRzX3JlZykgPSBjKCJDbGFzc2lmaWNhdGlvbiB0cmVlIiwiTGFzc28iLCJQb2xpY3kgdHJlZSAxIiwiUG9saWN5IHRyZWUgMiIpDQoNCmZvciAoaSBpbiAxOnJlcHMpIHsNCiAgDQogICMgRHJhdyBzYW1wbGUNCiAgDQogIHggPSBtYXRyaXgocnVuaWYobipwLC1waSxwaSksbmNvbD1wKQ0KICB3ID0gcmJpbm9tKG4sMSxlKHhbLDFdKSkNCiAgeTAgPSBtMCh4WywxXSkgKyBybm9ybShuLDAsMSkNCiAgeTEgPSBtMSh4WywxXSkgKyBybm9ybShuLDAsMSkNCiAgeSA9IHcqeTEgKyAoMS13KSp5MCArIHJub3JtKG4sMCwxKQ0KICBwaV9zdGFyID0gaWZlbHNlKHhbLDFdPjAsIlRyZWF0IiwiRG9uJ3QgdHJlYXQiKQ0KICBRX3N0YXIgPSBzdW0oIChwaV9zdGFyID09ICJUcmVhdCIpICogbTEoeFssMV0pICsgKHBpX3N0YXIgPT0gIkRvbid0IHRyZWF0IikgKiBtMCh4WywxXSkgKQ0KICANCiAgIyBHZXQgcHNldWRvLW91dGNvbWUNCiAgYWlwdyA9IERNTF9haXB3KHksdyx4LG1sX3c9bGlzdChmb3Jlc3QpLG1sX3k9bGlzdChmb3Jlc3QpLGNmPTIpDQogIA0KICAjIERlZmluZSB0aGUgc2lnbiBvZiB0aGUgcHNldWRvLW91dGNvbWUNCiAgc2lnbl95X3RpbGRlID0gc2lnbihhaXB3JEFURSRkZWx0YSkNCiAgc2lnbl95X3RpbGRlID0gZmFjdG9yKHNpZ25feV90aWxkZSwgbGFiZWxzID0gYygiRG9uJ3QgdHJlYXQiLCJUcmVhdCIpKQ0KDQogICMgRGVmaW5lIHRoZSB3ZWlnaHQgYXMgYWJzb2x1dGUgdmFsdWUgb2YgdGhlIHBzZXVkby1vdXRjb21lDQogIGFic195X3RpbGRlID0gYWJzKGFpcHckQVRFJGRlbHRhKQ0KDQogICMgQ2xhc3NpZmljYXRpb24gdHJlZQ0KICBjbGFzc190cmVlID0gcnBhcnQoc2lnbl95X3RpbGRlIH4geCwgICAgDQogICAgICAgICAgICAgICAgICB3ZWlnaHRzID0gYWJzX3lfdGlsZGUsDQogICAgICAgICAgICAgICAgICBtZXRob2QgPSAiY2xhc3MiKQ0KICBwaV9oYXRfdHJlZSA9IHByZWRpY3QoY2xhc3NfdHJlZSx0eXBlPSJjbGFzcyIpDQogIHJlc3VsdHNfY2FbaSwxXSA9IHN1bShkaWFnKHRhYmxlKHBpX3N0YXIscGlfaGF0X3RyZWUpKSkgLyBuICogMTAwDQogIHJlc3VsdHNfcmVnW2ksMV0gPSBRX3N0YXIgLSBzdW0oIChwaV9oYXRfdHJlZSA9PSAiVHJlYXQiKSAqIG0xKHhbLDFdKSArIChwaV9oYXRfdHJlZSA9PSAiRG9uJ3QgdHJlYXQiKSAqIG0wKHhbLDFdKSApDQogIA0KICAjIExhc3NvDQogIGNsYXNzX2xhc3NvID0gY3YuZ2xtbmV0KHgsIHNpZ25feV90aWxkZSwgICAgICAgDQogICAgICAgICAgICAgICAgICBmYW1pbHkgPSAiYmlub21pYWwiLCAgICAgICAjIHRlbGwgdGhhdCBpdCBpcyBhIGJpbmFyeSB2YXJpYWJsZQ0KICAgICAgICAgICAgICAgICAgdHlwZS5tZWFzdXJlID0gImNsYXNzIiwgICAgIyB0ZWxsIHRoYXQgd2Ugd2FudCB0byBjbGFzc2lmeQ0KICAgICAgICAgICAgICAgICAgd2VpZ2h0cyA9IGFic195X3RpbGRlKSAgICAgIyB1c2luZyB0aGUgYWJzIHBzZXVkby1vdXRjb21lIGFzIHdlaWdodHMNCiAgcGlfaGF0X2xhc3NvID0gcHJlZGljdChjbGFzc19sYXNzbywgbmV3eCA9IHgsIHMgPSAibGFtYmRhLm1pbiIsIHR5cGUgPSAiY2xhc3MiKQ0KICByZXN1bHRzX2NhW2ksMl0gPSBzdW0oZGlhZyh0YWJsZShwaV9zdGFyLHBpX2hhdF9sYXNzbykpKSAvIG4gKiAxMDANCiAgcmVzdWx0c19yZWdbaSwyXSA9IFFfc3RhciAtIHN1bSggKHBpX2hhdF9sYXNzbyA9PSAiVHJlYXQiKSAqIG0xKHhbLDFdKSArIChwaV9oYXRfbGFzc28gPT0gIkRvbid0IHRyZWF0IikgKiBtMCh4WywxXSkgKQ0KICANCiAgIyBQb2xpY3kgdHJlZSBkZXB0aCAxDQogIHB0MSA9IHBvbGljeV90cmVlKHgsYWlwdyRBUE8kZ2FtbWEsZGVwdGg9MSkNCiAgcGlfaGF0X3B0MSA9IHByZWRpY3QocHQxLG5ld2RhdGE9eCkNCiAgcmVzdWx0c19jYVtpLDNdID0gc3VtKGRpYWcodGFibGUocGlfc3RhcixwaV9oYXRfcHQxKSkpIC8gbiAqIDEwMA0KICByZXN1bHRzX3JlZ1tpLDNdID0gUV9zdGFyIC0gc3VtKCAocGlfaGF0X3B0MSA9PSAyKSAqIG0xKHhbLDFdKSArIChwaV9oYXRfcHQxID09IDEpICogbTAoeFssMV0pICkNCiAgDQogICMgUG9saWN5IHRyZWUgZGVwdGggMg0KICBwdDIgPSBwb2xpY3lfdHJlZSh4LGFpcHckQVBPJGdhbW1hLGRlcHRoPTIpDQogIHBpX2hhdF9wdDIgPSBwcmVkaWN0KHB0MixuZXdkYXRhPXgpDQogIHJlc3VsdHNfY2FbaSw0XSA9IHN1bShkaWFnKHRhYmxlKHBpX3N0YXIscGlfaGF0X3B0MikpKSAvIG4gKiAxMDANCiAgcmVzdWx0c19yZWdbaSw0XSA9IFFfc3RhciAtIHN1bSggKHBpX2hhdF9wdDIgPT0gMikgKiBtMSh4WywxXSkgKyAocGlfaGF0X3B0MiA9PSAxKSAqIG0wKHhbLDFdKSApDQp9DQoNCnJvdW5kKGNvbE1lYW5zKHJlc3VsdHNfY2EpLDEpDQpyb3VuZChjb2xNZWFucyhyZXN1bHRzX3JlZyksMSkNCmBgYA0KDQpXZSBzZWUgdGhhdCB0aGUgZGVwdGggb25lIHBvbGljeSB0cmVlIHBlcmZvcm1zIGJlc3QgaW4gdGVybXMgb2YgY2xhc3NpZmljYXRpb24gYWNjdXJhY3kgYW5kIHJlZ3JldC4gVGhpcyBpcyBub3Qgc3VycHJpc2luZyBhcyB0aGUgZml4ZWQgZGVwdGggb2Ygb25lIGlzIGJhc2ljYWxseSB0aGUgb3JhY2xlLg0KDQpQbG90IHRoZSByZXN1bHRzOg0KDQpgYGB7cn0NCiMgUGxvdCB0aGUgZGF0YSByZWFkeQ0KYXNfdGliYmxlKHJlc3VsdHNfY2EpICU+JSBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwgbmFtZXNfdG8gPSAiTWV0aG9kIiwgdmFsdWVzX3RvID0gIkNBIikgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBDQSwgeSA9IGZjdF9yZXYoTWV0aG9kKSwgZmlsbCA9IE1ldGhvZCkpICsNCiAgZ2VvbV9kZW5zaXR5X3JpZGdlcyhzdGF0ID0gImJpbmxpbmUiLCBiaW5zID0gMjAsIGRyYXdfYmFzZWxpbmUgPSBGQUxTRSkgKyB4bGFiKCJDbGFzc2lmaWNhdGlvbiBhY2N1cmFjeSBpbiBwZXJjZW50IikNCmBgYA0KDQpgYGB7cn0NCmNvbE1lYW5zKHJlc3VsdHNfY2EpDQpgYGANCg0KYGBge3J9DQojIFBsb3QgdGhlIGRhdGEgcmVhZHkNCmFzX3RpYmJsZShyZXN1bHRzX3JlZykgJT4lIHBpdm90X2xvbmdlcihjb2xzID0gZXZlcnl0aGluZygpLCBuYW1lc190byA9ICJNZXRob2QiLCB2YWx1ZXNfdG8gPSAiQ0EiKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IENBLCB5ID0gZmN0X3JldihNZXRob2QpLCBmaWxsID0gTWV0aG9kKSkgKw0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzKHN0YXQgPSAiYmlubGluZSIsIGJpbnMgPSAyMCwgZHJhd19iYXNlbGluZSA9IEZBTFNFKSArIHhsYWIoIlJlZ3JldCIpDQpgYGANCg0KYGBge3J9DQpjb2xNZWFucyhyZXN1bHRzX3JlZykNCmBgYA0KDQpXZSBvYnNlcnZlIHRoYXQgdGhlIHBvbGljeSB0cmVlIGFuZCBMYXNzbyBwZXJmb3JtIHF1aXRlIHdlbGwsIHdoaWxlIHRoZSBjbGFzc2lmaWNhdGlvbiB0cmVlIHBlcmZvcm1zIHN1cnByaXNpbmdseSBiYWQgZ2l2ZW4gdGhhdCB0aGUgb3B0aW1hbCBwb2xpY3kgcmVxdWlyZXMganVzdCBvbmUgc3BsaXQuDQoNCg0KDQojIyBUYWtlLWF3YXkNCiANCiAtIFRoZSBwc2V1ZG8tb3V0Y29tZSBmcm9tIHRoZSBEb3VibGUgTUwgQUlQVyBlc3RpbWF0b3IgY2FuIGFsc28gYmUgdXNlZCB0byBlc3RpbWF0ZSBwb2xpY3kgcnVsZXMgaW4gYSB3ZWlnaHRlZCBjbGFzc2lmaWNhdGlvbiBwcm9ibGVtIGFuZCBpbiBhIHBvbGljeSB0cmVlIGFsZ29yaXRobS4NCiANCjxicj4NCjxicj4NCiANCiANCiMjIFN1Z2dlc3Rpb25zIHRvIHBsYXkgd2l0aCB0aGUgdG95IG1vZGVsDQoNClNvbWUgc3VnZ2VzdGlvbnM6DQogDQotIFVzZSBkaWZmZXJlbnQgbWV0aG9kcyBmb3IgdGhlIGNsYXNzaWZpY2F0aW9uIHByb2JsZW0NCg0KLSBDcmVhdGUgZGlmZmVyZW50IENBVEUgYW5kIG51aXNhbmNlIGZ1bmN0aW9ucw0KDQotIENoYW5nZSB0aGUgdHJlYXRtZW50IHNoYXJlcw0KDQotIEV4cGVyaWVuY2UgaG93IGxvbmcgYSBkZXB0aCB0aHJlZSB0cmVlIGlzIHJ1bm5pbmcNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=