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
- 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
- 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=