Goals:

  • Illustrate why the T-learner is not the best idea

  • Illustrate how causal tree and causal forest improve on naive estimation

  • Visualize the weighted residual-on-residual regression underlying causal forests


From “naive trees” to Causal Forests

DGP

Consider the following experimental DGP with discrete heterogeneous treatment effects, but otherwise similar to those in previous notebooks:

  • \(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 Bernuolli(\underbrace{2/3}_{e(X)})\)

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

  • The CATE function is an indicator function \(\tau(X) = 1[X_1 > -0.5\pi]\)

  • The potential outcome model of the treated is \(Y(1) = m_0(X) + \tau(X) + \varepsilon\), with \(\varepsilon \sim N(0,1/2)\)

if (!require("grf")) install.packages("grf", dependencies = TRUE); library(grf)
if (!require("tidyverse")) install.packages("tidyverse", dependencies = TRUE); library(tidyverse)
if (!require("patchwork")) install.packages("patchwork", dependencies = TRUE); library(patchwork)
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("partykit")) install.packages("partykit", dependencies = TRUE); library(partykit)
if (!require("causalTree")) {
  if (!require("devtools")) install.packages("devtools", dependencies = TRUE); library(devtools)
  install_github("susanathey/causalTree")
}; library(causalTree)

set.seed(12345) # Admittedly a bit seed-hacked in favor of clean illustration

# Set parameters
n = 1000
p = 10

# Draw sample
x = matrix(runif(n*p,-pi,pi),ncol=p)
e = function(x){2/3}
m0 = function(x){sin(x)}
tau = function(x){1*(x>-0.5*pi)}
m1 = function(x){m0(x) + tau(x)}
w = rbinom(n,1,e(x))
y = m0(x[,1]) + w*tau(x[,1]) + rnorm(n,0,1/2)

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")
g1 / g2 / g3


T-learner with regression trees

First we consider to estimate the conditional means in the treated sample and the control separately and taking the difference of the predicted outcomes as estimates for the CATE (see slide 19).

  1. Use regression tree to fit model in control subsample
df = data.frame(x=x,y=y)
tree0 = rpart(y~x,data = df,subset = (w==0))
rpart.plot(tree0)

  1. Use regression tree to fit model in treated subsample
tree1 = rpart(y~x,data = df,subset= (w==1))
rpart.plot(tree1)

  1. Plot predicted outcomes and CATEs:
df$apo_tree0 = predict(tree0,newdata=data.frame(x))
df$apo_tree1 = predict(tree1,newdata=data.frame(x))
df$cate_tree = df$apo_tree1 - df$apo_tree0

g1 = ggplot(df) + stat_function(fun=m1,size=1) + ylab("m1") + 
  geom_point(aes(x=x[,1],y=apo_tree1),shape="square",color="blue")
g2 = ggplot(df) + stat_function(fun=m0,size=1) + ylab("m0") + 
  geom_point(aes(x=x[,1],y=apo_tree0),shape="square",color="blue") 
g3 = ggplot(df) + stat_function(fun=tau,size=1) + ylab(expression(tau)) + 
  geom_point(aes(x=x[,1],y=cate_tree),shape="square",color="blue") 
g1 / g2 / g3

We observe that the predicted CATEs are quite erratic. Approximating the outcomes is very challenging for the tree and it fails quite dramatically especially in the control sample where there are fewer observations available. This spills over to the downstream CATE estimates. The CATE is in principle tailored to be found by a tree structure, but the more complicated outcome functions distract this “naive” procedure.


Causal Tree

Handcoded

Causal Trees are build to directly estimate CATEs and to not be distracted by potentially more complicated outcome functions.

Before using the package let’s handcode the main idea to see how and that it is working.

Recall from the lecture that the splitting criterion can be expressed as \(\max \sum_i \hat{\tau}^{tree}(X_i)^2\). Below we write code that searches for the first split. To this end we iterate over a grid of potential split point \(s\). At each split point and variable \(j\) we proceed as follows

  1. Calculate the effect in the left leaf as mean difference between treated and control units \(\hat{\tau}_{L(j,s)}\)

  2. Calculate the effect in the right leaf as mean difference between treated and control units \(\hat{\tau}_{R(j,s)}\)

  3. Calculate the criterion as \(N_L \hat{\tau}_{L(j,s)}^2 + N_R \hat{\tau}_{R(j,s)}^2\), where \(N_L\) and \(N_R\) are the number of units observed in the left leaf and the right leaf, respectively.

Finally, we pick the variable and the splitting point that maximizes the criterion. In our DGP it should split at \(X_1 = -0.5\pi \approx -1.6\).

# Hand-coded causal tree
grid = seq(-3,3,0.01)
criterion = matrix(NA,length(grid),p)
colnames(criterion) = paste0("X",1:p)
for (j in 1:p) {
  for (i in 1:length(grid)) {
    # Indicator for being right of cut-off
    right = (x[,j] > grid[i])
    # Calculate the effect as mean differences in the two leaves
    cate_left = mean(y[w==1 & !right]) - mean(y[w==0 & !right])
    cate_right = mean(y[w==1 & right]) - mean(y[w==0 & right])
    # Calculate and store criterion
    criterion[i,j] = (n-sum(right)) * (cate_left)^2 + sum(right) * (cate_right)^2
  }
}
# Find maximum
index_max = which(criterion == max(criterion), arr.ind = TRUE)

# Plot criteria
data.frame(x=grid,criterion) %>% 
  pivot_longer(cols=-x,names_to = "Variable",values_to = "Criterion") %>%
  ggplot(aes(x=x ,y=Criterion,colour=Variable)) + geom_line(size=1) + geom_vline(xintercept=-0.5*pi) + 
  geom_vline(xintercept=grid[index_max[1]],linetype = "dashed")

The detected splitting point (dashed line) is very close to the correct splitting point (solid line). The other nine variables show quite flat criteria along the grid, as it should be the case. Only the criterion along \(X_1\) rises the closer the candidate split comes to the correct one.


Package

The causalTree package implements the procedure including cross-validation:

# Implemented causalTree adapting specification from R example
ctree = causalTree(y~x, data = df, treatment = w,
                   split.Rule = "CT", cv.option = "CT", split.Honest = T,split.Bucket = F, xval = 5, 
                   cp = 0, minsize = 20)
[1] 2
[1] "CT"
opcp = ctree$cptable[,1][which.min(ctree$cptable[,4])]
opfit = prune(ctree, opcp)
df$cate_ct = predict(opfit)
rpart.plot(opfit)

ggplot(df) + stat_function(fun=tau,size=1) + ylab(expression(tau)) + 
  geom_point(aes(x=x[,1],y=cate_ct),shape="square",color="blue") 

The package version does not know that only one split is required, but finds the correct split value and prunes the tree appropriately via cross-validation.


T-learner with random forest

Regression trees are obviously not well-suited to estimate smooth funtions. Thus, we use next a self-tuned honest regression forest to form the separate prediction models:

rf0 = regression_forest(x[w==0,], y[w==0],tune.parameters = "all")
rf1 = regression_forest(x[w==1,], y[w==1],tune.parameters = "all")

df$apo_rf0 = predict(rf0,newdata=x)$predictions
df$apo_rf1 = predict(rf1,newdata=x)$predictions
df$cate_rf = df$apo_rf1 - df$apo_rf0

g1 = ggplot(df) + stat_function(fun=m1,size=1) + ylab("m1") + 
  geom_point(aes(x=x[,1],y=apo_rf1),shape="square",color="blue")
g2 = ggplot(df) + stat_function(fun=m0,size=1) + ylab("m0") + 
  geom_point(aes(x=x[,1],y=apo_rf0),shape="square",color="blue") 
g3 = ggplot(df) + stat_function(fun=tau,size=1) + ylab(expression(tau)) + 
  geom_point(aes(x=x[,1],y=cate_rf),shape="square",color="blue") 
g1 / g2 / g3

This looks much better in terms of approximating the simple discrete CATE function. However, some problems in approximating the complicated outcome functions still spills over to the CATE estimates.


Causal Forest

The Causal Forest uses an approximation of the splitting criterion that we handcoded above and exploits the resulting weights of an ensemble of Causal Trees to estimate the CATEs.

Further, we can extract an estimate of the two outcome functions as

  • \(\hat{m}(0,X) = \hat{m}(X) - \hat{e}(X) \hat{\tau}(X)\)

  • \(\hat{m}(1,X) = \hat{m}(X) + (1-\hat{e}(X)) \hat{\tau}(X)\)

As \(\hat{m}(X)\) and \(\hat{e}(X)\) are the nuisance parameters of the Causal Forest, this comes at no additional computational costs.

cf = causal_forest(x, y, w,tune.parameters = "all")

df$cate_cf = predict(cf)$predictions
df$apo_cf0 = cf$Y.hat - cf$W.hat * df$cate_cf
df$apo_cf1 = cf$Y.hat + (1-cf$W.hat) * df$cate_cf

g1 = ggplot(df) + stat_function(fun=m1,size=1) + ylab("m1") + 
  geom_point(aes(x=x[,1],y=apo_cf1),shape="square",color="blue")
g2 = ggplot(df) + stat_function(fun=m0,size=1) + ylab("m0") + 
  geom_point(aes(x=x[,1],y=apo_cf0),shape="square",color="blue") 
g3 = ggplot(df) + stat_function(fun=tau,size=1) + ylab(expression(tau)) + 
  geom_point(aes(x=x[,1],y=cate_cf),shape="square",color="blue") 
g1 / g2 / g3

The Causal Forest pretty much nails the CATE function. Especially it is flat in the regions where the effect is stable, unlike when using the two separate Random Forests above.



Comparison

As we defined the truth, we can calculate the MSE for the different parameters and methods:

mses = matrix(NA,4,3)
colnames(mses) = c("m(0,X)","m(1,X)","tau(X)")

mses[1,1] = mean( (df$apo_tree0 - m0(x[,1]))^2 )
mses[3,1] = mean( (df$apo_rf0 - m0(x[,1]))^2 )
mses[4,1] = mean( (df$apo_cf0 - m0(x[,1]))^2 )

mses[1,2] = mean( (df$apo_tree1 - m1(x[,1]))^2 )
mses[3,2] = mean( (df$apo_rf1 - m1(x[,1]))^2 )
mses[4,2] = mean( (df$apo_cf1 - m1(x[,1]))^2 )

mses[1,3] = mean( (df$cate_tree - tau(x[,1]))^2 )
mses[2,3] = mean( (df$cate_ct - tau(x[,1]))^2 )
mses[3,3] = mean( (df$cate_rf - tau(x[,1]))^2 )
mses[4,3] = mean( (df$cate_cf - tau(x[,1]))^2 )

data.frame(Method = factor(c("Tree","Causal Tree","Forest","Causal Forest"),
                           levels=c("Tree","Causal Tree","Forest","Causal Forest")),
           mses) %>%
  pivot_longer(cols=-Method,names_to = "Target",values_to = "MSE") %>%
  ggplot(aes(x=Method,y=MSE)) + geom_point() + facet_wrap(~Target) + 
  theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1)) + 
  geom_hline(yintercept = 0)

As expected the Causal Forest provides the lowest MSE for the approximation of the CATE.

Interestingly it also approximates the control outcome better than the separate forest. This might be further investigated in a bigger simulation study, but not in this notebook.



Causal Forest behind the scenes

To get a better understanding what Causal Forests are doing, consider the following, now observational, DGP with only two covariates:

  • \(p=2\) independent covariates \(X_1,X_{2}\) 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{sin(X_1)}_{m_0(X)} + \varepsilon\), with \(\varepsilon \sim N(0,1/10)\)

  • The CATE function is an indicator function \(\tau(X) = 1[X_1 > -0.5\pi]\)

  • The potential outcome model of the treated is \(Y(1) = m_0(X) + \tau(X) + \varepsilon\), with \(\varepsilon \sim N(0,1/10)\)

# Set parameters
p = 2
# Draw sample
x = matrix(runif(n*p,-pi,pi),ncol=p)
e = function(x){pnorm(sin(x))}
m0 = function(x){sin(x)}
tau = function(x){0 + 1*(x>-0.5*pi)}
m1 = function(x){m0(x) + tau(x)}
w = rbinom(n,1,e(x))
y = m0(x[,1]) + w*tau(x[,1]) + rnorm(n,0,1/10)
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")
g1 / g2 / g3


Weighted residual-on-residual regression at single point

Recall that the Causal Forest can be written as a residual-on-residual regression with \(x\)-specific weights \(\alpha(x)\):

\[\hat{\tau}^{cf}(x) = argmin_{\breve{\tau}} \left\{ \sum_{i=1}^N \alpha_i(x) \left[(Y_i - \hat{m}(X_i)) - \breve{\tau} (W_i - \hat{e}(X_i)) \right]^2 \right\}\]

The weights can be accessed using the get_forest_weights function on a causal_forest object.

Consider that we are interested in estimating the CATE for \(X_1= -3\) and all other Xs equal to zero.

The estimated value can be obtained via the predict function

# Run CF
cf = causal_forest(x, y, w,tune.parameters = "all")
# Define test point 
testx = matrix(c(-3,rep(0,p-1)),nrow=1)
# Check what package predicts
predict(cf,newdata = testx)$predictions
[1] 0.02728292

which is reasonably close to the true value of zero given the small number of observations.

Alternatively we can handcode it as a weighted residual-on-residual regression after extracting the underlying weights.

# Get residuals
res_y = y - cf$Y.hat
res_w = w - cf$W.hat
# Replicate handcoded
alphax = get_forest_weights(cf,newdata = testx)[1,]
coef(lm(res_y ~ res_w,weights = alphax))
(Intercept)       res_w 
 0.01304695  0.02728292 

The second coefficient (the slope coefficient) is identical to the estimated CATE.

Remark: Note that the intercept is not necessary as we know (and see) that it should be zero. However, the grf package includes it and to numerically match the grf output, we apply it as well. However, note that the differences to the case without constant are negligible:

# Replicate handcoded w/o constant
coef(lm(res_y ~ 0 + res_w,weights = alphax))
     res_w 
0.03039751 


How the weights move for different predictions

Finally, this setting can be used to nicely illustrate what happens at different values of \(X_1\). The two residuals can be plotted against each other. Additionally the size of the residuals is proportional to the weight they receive and the color indicates lower to higher values of the variable:

# Run same over grid an see how weights move
grid = seq(-3,0,1)
gridx = cbind(grid,matrix(0,length(grid),p-1))
grid_hat = predict(cf,newdata = gridx)$predictions
alpha = get_forest_weights(cf,newdata = gridx)
for (i in 1:length(grid)) {
  g1 = data.frame(x=grid,tau_hat=grid_hat) %>%
    ggplot(aes(x=x ,y=tau_hat)) + stat_function(fun=tau,size=1) + 
    geom_line(color="blue") + 
    geom_point(aes(x=grid[i],y=grid_hat[i]),size=4,color="blue",shape=4) 
  
  rorr = lm(res_y ~ res_w,weights = alpha[i,])
  
  g2 = data.frame(res_w,res_y,alpha=alpha[i,],x=x) %>%
    ggplot(aes(x=res_w,y=res_y)) + geom_point(aes(size=alpha,color=x[,1]),alpha=0.5) + 
    geom_abline(intercept=rorr$coefficients[1],slope=rorr$coefficients[2]) + 
    annotate("text", x = -0.25, y = 1, label = paste0("tau(",toString(grid[i]),") = slope of line = ",
                                                      toString(round(rorr$coefficients[2],2))))+
    scale_colour_gradient(low = "black", high = "yellow")
  print(g1 / g2)
}

The jump from \(X = -2\) to \(X=-1\) is most instructive. We see that before the jump of the true CATE function units with low values of \(X_1\) receive most weight. Furthermore, controls (with negative treatment residuals) and treated (with positive treatment residuals) of those with large weights are quite similar. This results in a nearly horizontal slope and thus a CATE estimate of close to zero.

After the jump, units with larger values of \(X_1\) receive most of the weight, resulting in a clearly positive slope. As the slope of the line is the estimated CATE, we (rightly) estimate a substantial positive effect after the jump.


Take-away

  • Targeting CATEs explicitly works.

  • The Causal Forest can be thought of as residual-on-residual regressions where the residuals are always the same, but the weights are individualized.



Suggestions to play with the toy model

Some suggestions:

  • Increase/decrease the number of observations

  • Create different CATE function

  • Change the treatment shares

  • Increase noise level in second part and see how we can’t see much if outcome noise is not very small. In real datasets you won’t see much, but here it helps to build intuition.

LS0tDQp0aXRsZTogIkNhdXNhbCBNTDogQ2F1c2FsIFRyZWUgYW5kIENhdXNhbCBGb3Jlc3QiDQpzdWJ0aXRsZTogIlNpbXVsYXRpb24gbm90ZWJvb2siDQphdXRob3I6ICJNaWNoYWVsIEtuYXVzIg0KZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJW0vJXknKWAiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KLS0tDQoNCg0KR29hbHM6DQoNCi0gSWxsdXN0cmF0ZSB3aHkgdGhlIFQtbGVhcm5lciBpcyBub3QgdGhlIGJlc3QgaWRlYQ0KDQotIElsbHVzdHJhdGUgaG93IGNhdXNhbCB0cmVlIGFuZCBjYXVzYWwgZm9yZXN0IGltcHJvdmUgb24gbmFpdmUgZXN0aW1hdGlvbg0KDQotIFZpc3VhbGl6ZSB0aGUgd2VpZ2h0ZWQgcmVzaWR1YWwtb24tcmVzaWR1YWwgcmVncmVzc2lvbiB1bmRlcmx5aW5nIGNhdXNhbCBmb3Jlc3RzDQoNCjxicj4NCg0KDQojIEZyb20gIm5haXZlIHRyZWVzIiB0byBDYXVzYWwgRm9yZXN0cw0KDQojIyBER1ANCg0KQ29uc2lkZXIgdGhlIGZvbGxvd2luZyBleHBlcmltZW50YWwgREdQIHdpdGggZGlzY3JldGUgaGV0ZXJvZ2VuZW91cyB0cmVhdG1lbnQgZWZmZWN0cywgYnV0IG90aGVyd2lzZSBzaW1pbGFyIHRvIHRob3NlIGluIHByZXZpb3VzIG5vdGVib29rczoNCg0KLSAkcD0xMCQgaW5kZXBlbmRlbnQgY292YXJpYXRlcyAkWF8xLC4uLixYX2ssLi4uLFhfezEwfSQgZHJhd24gZnJvbSBhIHVuaWZvcm0gZGlzdHJpYnV0aW9uOiAkWF9rIFxzaW0gdW5pZm9ybSgtXHBpLFxwaSkkDQoNCi0gVGhlIHRyZWF0bWVudCBtb2RlbCBpcyAkVyBcc2ltIEJlcm51b2xsaShcdW5kZXJicmFjZXsyLzN9X3tlKFgpfSkkDQoNCi0gVGhlIHBvdGVudGlhbCBvdXRjb21lIG1vZGVsIG9mIHRoZSBjb250cm9scyBpcyAkWSgwKSA9IFx1bmRlcmJyYWNle3NpbihYXzEpfV97bV8wKFgpfSArIFx2YXJlcHNpbG9uJCwgd2l0aCAkXHZhcmVwc2lsb24gXHNpbSBOKDAsMS8yKSQNCg0KLSBUaGUgQ0FURSBmdW5jdGlvbiBpcyBhbiBpbmRpY2F0b3IgZnVuY3Rpb24gJFx0YXUoWCkgPSAxW1hfMSA+IC0wLjVccGldJA0KDQotIFRoZSBwb3RlbnRpYWwgb3V0Y29tZSBtb2RlbCBvZiB0aGUgdHJlYXRlZCBpcyAkWSgxKSA9IG1fMChYKSArIFx0YXUoWCkgKyBcdmFyZXBzaWxvbiQsIHdpdGggJFx2YXJlcHNpbG9uIFxzaW0gTigwLDEvMikkDQoNCmBgYHtyLCB3YXJuaW5nID0gRiwgbWVzc2FnZSA9IEZ9DQppZiAoIXJlcXVpcmUoImdyZiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJncmYiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeShncmYpDQppZiAoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKSBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeSh0aWR5dmVyc2UpDQppZiAoIXJlcXVpcmUoInBhdGNod29yayIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwYXRjaHdvcmsiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeShwYXRjaHdvcmspDQppZiAoIXJlcXVpcmUoInJwYXJ0IikpIGluc3RhbGwucGFja2FnZXMoInJwYXJ0IiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkocnBhcnQpDQppZiAoIXJlcXVpcmUoInJwYXJ0LnBsb3QiKSkgaW5zdGFsbC5wYWNrYWdlcygicnBhcnQucGxvdCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KHJwYXJ0LnBsb3QpDQppZiAoIXJlcXVpcmUoInBhcnR5a2l0IikpIGluc3RhbGwucGFja2FnZXMoInBhcnR5a2l0IiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkocGFydHlraXQpDQppZiAoIXJlcXVpcmUoImNhdXNhbFRyZWUiKSkgew0KICBpZiAoIXJlcXVpcmUoImRldnRvb2xzIikpIGluc3RhbGwucGFja2FnZXMoImRldnRvb2xzIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoZGV2dG9vbHMpDQogIGluc3RhbGxfZ2l0aHViKCJzdXNhbmF0aGV5L2NhdXNhbFRyZWUiKQ0KfTsgbGlicmFyeShjYXVzYWxUcmVlKQ0KDQpzZXQuc2VlZCgxMjM0NSkgIyBBZG1pdHRlZGx5IGEgYml0IHNlZWQtaGFja2VkIGluIGZhdm9yIG9mIGNsZWFuIGlsbHVzdHJhdGlvbg0KDQojIFNldCBwYXJhbWV0ZXJzDQpuID0gMTAwMA0KcCA9IDEwDQoNCiMgRHJhdyBzYW1wbGUNCnggPSBtYXRyaXgocnVuaWYobipwLC1waSxwaSksbmNvbD1wKQ0KZSA9IGZ1bmN0aW9uKHgpezIvM30NCm0wID0gZnVuY3Rpb24oeCl7c2luKHgpfQ0KdGF1ID0gZnVuY3Rpb24oeCl7MSooeD4tMC41KnBpKX0NCm0xID0gZnVuY3Rpb24oeCl7bTAoeCkgKyB0YXUoeCl9DQp3ID0gcmJpbm9tKG4sMSxlKHgpKQ0KeSA9IG0wKHhbLDFdKSArIHcqdGF1KHhbLDFdKSArIHJub3JtKG4sMCwxLzIpDQoNCmcxID0gZGF0YS5mcmFtZSh4ID0gYygtcGksIHBpKSkgJT4lIGdncGxvdChhZXMoeCkpICsgc3RhdF9mdW5jdGlvbihmdW49ZSxzaXplPTEpICsgeWxhYigiZSIpICsgeGxhYigiWDEiKQ0KZzIgPSBkYXRhLmZyYW1lKHggPSBjKC1waSwgcGkpKSAlPiUgZ2dwbG90KGFlcyh4KSkgKyBzdGF0X2Z1bmN0aW9uKGZ1bj1tMSxzaXplPTEsYWVzKGNvbG91cj0iWTEiKSkgKyANCiAgc3RhdF9mdW5jdGlvbihmdW49bTAsc2l6ZT0xLGFlcyhjb2xvdXI9IlkwIikpICsgeWxhYigiWSIpICsgeGxhYigiWDEiKQ0KZzMgPSBkYXRhLmZyYW1lKHggPSBjKC1waSwgcGkpKSAlPiUgZ2dwbG90KGFlcyh4KSkgKyBzdGF0X2Z1bmN0aW9uKGZ1bj10YXUsc2l6ZT0xKSArIHlsYWIoZXhwcmVzc2lvbih0YXUpKSArIHhsYWIoIlgxIikNCmcxIC8gZzIgLyBnMw0KYGBgDQoNCjxicj4gDQoNCiMjIFQtbGVhcm5lciB3aXRoIHJlZ3Jlc3Npb24gdHJlZXMNCg0KRmlyc3Qgd2UgY29uc2lkZXIgdG8gZXN0aW1hdGUgdGhlIGNvbmRpdGlvbmFsIG1lYW5zIGluIHRoZSB0cmVhdGVkIHNhbXBsZSBhbmQgdGhlIGNvbnRyb2wgc2VwYXJhdGVseSBhbmQgdGFraW5nIHRoZSBkaWZmZXJlbmNlIG9mIHRoZSBwcmVkaWN0ZWQgb3V0Y29tZXMgYXMgZXN0aW1hdGVzIGZvciB0aGUgQ0FURSAoc2VlIHNsaWRlIDE5KS4NCg0KMS4gVXNlIHJlZ3Jlc3Npb24gdHJlZSB0byBmaXQgbW9kZWwgaW4gY29udHJvbCBzdWJzYW1wbGUNCg0KYGBge3J9DQpkZiA9IGRhdGEuZnJhbWUoeD14LHk9eSkNCnRyZWUwID0gcnBhcnQoeX54LGRhdGEgPSBkZixzdWJzZXQgPSAodz09MCkpDQpycGFydC5wbG90KHRyZWUwKQ0KYGBgDQoNCjIuIFVzZSByZWdyZXNzaW9uIHRyZWUgdG8gZml0IG1vZGVsIGluIHRyZWF0ZWQgc3Vic2FtcGxlDQoNCmBgYHtyfQ0KdHJlZTEgPSBycGFydCh5fngsZGF0YSA9IGRmLHN1YnNldD0gKHc9PTEpKQ0KcnBhcnQucGxvdCh0cmVlMSkNCmBgYA0KDQozLiBQbG90IHByZWRpY3RlZCBvdXRjb21lcyBhbmQgQ0FURXM6DQoNCmBgYHtyfQ0KZGYkYXBvX3RyZWUwID0gcHJlZGljdCh0cmVlMCxuZXdkYXRhPWRhdGEuZnJhbWUoeCkpDQpkZiRhcG9fdHJlZTEgPSBwcmVkaWN0KHRyZWUxLG5ld2RhdGE9ZGF0YS5mcmFtZSh4KSkNCmRmJGNhdGVfdHJlZSA9IGRmJGFwb190cmVlMSAtIGRmJGFwb190cmVlMA0KDQpnMSA9IGdncGxvdChkZikgKyBzdGF0X2Z1bmN0aW9uKGZ1bj1tMSxzaXplPTEpICsgeWxhYigibTEiKSArIA0KICBnZW9tX3BvaW50KGFlcyh4PXhbLDFdLHk9YXBvX3RyZWUxKSxzaGFwZT0ic3F1YXJlIixjb2xvcj0iYmx1ZSIpDQpnMiA9IGdncGxvdChkZikgKyBzdGF0X2Z1bmN0aW9uKGZ1bj1tMCxzaXplPTEpICsgeWxhYigibTAiKSArIA0KICBnZW9tX3BvaW50KGFlcyh4PXhbLDFdLHk9YXBvX3RyZWUwKSxzaGFwZT0ic3F1YXJlIixjb2xvcj0iYmx1ZSIpIA0KZzMgPSBnZ3Bsb3QoZGYpICsgc3RhdF9mdW5jdGlvbihmdW49dGF1LHNpemU9MSkgKyB5bGFiKGV4cHJlc3Npb24odGF1KSkgKyANCiAgZ2VvbV9wb2ludChhZXMoeD14WywxXSx5PWNhdGVfdHJlZSksc2hhcGU9InNxdWFyZSIsY29sb3I9ImJsdWUiKSANCmcxIC8gZzIgLyBnMw0KYGBgDQoNCldlIG9ic2VydmUgdGhhdCB0aGUgcHJlZGljdGVkIENBVEVzIGFyZSBxdWl0ZSBlcnJhdGljLiBBcHByb3hpbWF0aW5nIHRoZSBvdXRjb21lcyBpcyB2ZXJ5IGNoYWxsZW5naW5nIGZvciB0aGUgdHJlZSBhbmQgaXQgZmFpbHMgcXVpdGUgZHJhbWF0aWNhbGx5IGVzcGVjaWFsbHkgaW4gdGhlIGNvbnRyb2wgc2FtcGxlIHdoZXJlIHRoZXJlIGFyZSBmZXdlciBvYnNlcnZhdGlvbnMgYXZhaWxhYmxlLiBUaGlzIHNwaWxscyBvdmVyIHRvIHRoZSBkb3duc3RyZWFtIENBVEUgZXN0aW1hdGVzLiBUaGUgQ0FURSBpcyBpbiBwcmluY2lwbGUgdGFpbG9yZWQgdG8gYmUgZm91bmQgYnkgYSB0cmVlIHN0cnVjdHVyZSwgYnV0IHRoZSBtb3JlIGNvbXBsaWNhdGVkIG91dGNvbWUgZnVuY3Rpb25zIGRpc3RyYWN0IHRoaXMgIm5haXZlIiBwcm9jZWR1cmUuDQoNCg0KPGJyPg0KDQojIyBDYXVzYWwgVHJlZQ0KDQojIyMgSGFuZGNvZGVkDQoNCkNhdXNhbCBUcmVlcyBhcmUgYnVpbGQgdG8gZGlyZWN0bHkgZXN0aW1hdGUgQ0FURXMgYW5kIHRvIG5vdCBiZSBkaXN0cmFjdGVkIGJ5IHBvdGVudGlhbGx5IG1vcmUgY29tcGxpY2F0ZWQgb3V0Y29tZSBmdW5jdGlvbnMuDQoNCkJlZm9yZSB1c2luZyB0aGUgcGFja2FnZSBsZXQncyBoYW5kY29kZSB0aGUgbWFpbiBpZGVhIHRvIHNlZSBob3cgYW5kIHRoYXQgaXQgaXMgd29ya2luZy4NCg0KUmVjYWxsIGZyb20gdGhlIGxlY3R1cmUgdGhhdCB0aGUgc3BsaXR0aW5nIGNyaXRlcmlvbiBjYW4gYmUgZXhwcmVzc2VkIGFzICRcbWF4IFxzdW1faSBcaGF0e1x0YXV9Xnt0cmVlfShYX2kpXjIkLiBCZWxvdyB3ZSB3cml0ZSBjb2RlIHRoYXQgc2VhcmNoZXMgZm9yIHRoZSBmaXJzdCBzcGxpdC4gVG8gdGhpcyBlbmQgd2UgaXRlcmF0ZSBvdmVyIGEgZ3JpZCBvZiBwb3RlbnRpYWwgc3BsaXQgcG9pbnQgJHMkLiBBdCBlYWNoIHNwbGl0IHBvaW50IGFuZCB2YXJpYWJsZSAkaiQgd2UgcHJvY2VlZCBhcyBmb2xsb3dzDQoNCjEuIENhbGN1bGF0ZSB0aGUgZWZmZWN0IGluIHRoZSBsZWZ0IGxlYWYgYXMgbWVhbiBkaWZmZXJlbmNlIGJldHdlZW4gdHJlYXRlZCBhbmQgY29udHJvbCB1bml0cyAkXGhhdHtcdGF1fV97TChqLHMpfSQNCg0KMi4gQ2FsY3VsYXRlIHRoZSBlZmZlY3QgaW4gdGhlIHJpZ2h0IGxlYWYgYXMgbWVhbiBkaWZmZXJlbmNlIGJldHdlZW4gdHJlYXRlZCBhbmQgY29udHJvbCB1bml0cyAkXGhhdHtcdGF1fV97UihqLHMpfSQNCg0KMy4gQ2FsY3VsYXRlIHRoZSBjcml0ZXJpb24gYXMgJE5fTCBcaGF0e1x0YXV9X3tMKGoscyl9XjIgKyBOX1IgXGhhdHtcdGF1fV97UihqLHMpfV4yJCwgd2hlcmUgJE5fTCQgYW5kICROX1IkIGFyZSB0aGUgbnVtYmVyIG9mIHVuaXRzIG9ic2VydmVkIGluIHRoZSBsZWZ0IGxlYWYgYW5kIHRoZSByaWdodCBsZWFmLCByZXNwZWN0aXZlbHkuDQoNCkZpbmFsbHksIHdlIHBpY2sgdGhlIHZhcmlhYmxlIGFuZCB0aGUgc3BsaXR0aW5nIHBvaW50IHRoYXQgbWF4aW1pemVzIHRoZSBjcml0ZXJpb24uIEluIG91ciBER1AgaXQgc2hvdWxkIHNwbGl0IGF0ICRYXzEgPSAtMC41XHBpIFxhcHByb3ggLTEuNiQuDQoNCg0KYGBge3J9DQojIEhhbmQtY29kZWQgY2F1c2FsIHRyZWUNCmdyaWQgPSBzZXEoLTMsMywwLjAxKQ0KY3JpdGVyaW9uID0gbWF0cml4KE5BLGxlbmd0aChncmlkKSxwKQ0KY29sbmFtZXMoY3JpdGVyaW9uKSA9IHBhc3RlMCgiWCIsMTpwKQ0KZm9yIChqIGluIDE6cCkgew0KICBmb3IgKGkgaW4gMTpsZW5ndGgoZ3JpZCkpIHsNCiAgICAjIEluZGljYXRvciBmb3IgYmVpbmcgcmlnaHQgb2YgY3V0LW9mZg0KICAgIHJpZ2h0ID0gKHhbLGpdID4gZ3JpZFtpXSkNCiAgICAjIENhbGN1bGF0ZSB0aGUgZWZmZWN0IGFzIG1lYW4gZGlmZmVyZW5jZXMgaW4gdGhlIHR3byBsZWF2ZXMNCiAgICBjYXRlX2xlZnQgPSBtZWFuKHlbdz09MSAmICFyaWdodF0pIC0gbWVhbih5W3c9PTAgJiAhcmlnaHRdKQ0KICAgIGNhdGVfcmlnaHQgPSBtZWFuKHlbdz09MSAmIHJpZ2h0XSkgLSBtZWFuKHlbdz09MCAmIHJpZ2h0XSkNCiAgICAjIENhbGN1bGF0ZSBhbmQgc3RvcmUgY3JpdGVyaW9uDQogICAgY3JpdGVyaW9uW2ksal0gPSAobi1zdW0ocmlnaHQpKSAqIChjYXRlX2xlZnQpXjIgKyBzdW0ocmlnaHQpICogKGNhdGVfcmlnaHQpXjINCiAgfQ0KfQ0KIyBGaW5kIG1heGltdW0NCmluZGV4X21heCA9IHdoaWNoKGNyaXRlcmlvbiA9PSBtYXgoY3JpdGVyaW9uKSwgYXJyLmluZCA9IFRSVUUpDQoNCiMgUGxvdCBjcml0ZXJpYQ0KZGF0YS5mcmFtZSh4PWdyaWQsY3JpdGVyaW9uKSAlPiUgDQogIHBpdm90X2xvbmdlcihjb2xzPS14LG5hbWVzX3RvID0gIlZhcmlhYmxlIix2YWx1ZXNfdG8gPSAiQ3JpdGVyaW9uIikgJT4lDQogIGdncGxvdChhZXMoeD14ICx5PUNyaXRlcmlvbixjb2xvdXI9VmFyaWFibGUpKSArIGdlb21fbGluZShzaXplPTEpICsgZ2VvbV92bGluZSh4aW50ZXJjZXB0PS0wLjUqcGkpICsgDQogIGdlb21fdmxpbmUoeGludGVyY2VwdD1ncmlkW2luZGV4X21heFsxXV0sbGluZXR5cGUgPSAiZGFzaGVkIikNCg0KYGBgDQoNClRoZSBkZXRlY3RlZCBzcGxpdHRpbmcgcG9pbnQgKGRhc2hlZCBsaW5lKSBpcyB2ZXJ5IGNsb3NlIHRvIHRoZSBjb3JyZWN0IHNwbGl0dGluZyBwb2ludCAoc29saWQgbGluZSkuIFRoZSBvdGhlciBuaW5lIHZhcmlhYmxlcyBzaG93IHF1aXRlIGZsYXQgY3JpdGVyaWEgYWxvbmcgdGhlIGdyaWQsIGFzIGl0IHNob3VsZCBiZSB0aGUgY2FzZS4gT25seSB0aGUgY3JpdGVyaW9uIGFsb25nICRYXzEkIHJpc2VzIHRoZSBjbG9zZXIgdGhlIGNhbmRpZGF0ZSBzcGxpdCBjb21lcyB0byB0aGUgY29ycmVjdCBvbmUuDQoNCjxicj4NCg0KIyMjIFBhY2thZ2UNCg0KVGhlIFtjYXVzYWxUcmVlXShodHRwczovL2dpdGh1Yi5jb20vc3VzYW5hdGhleS9jYXVzYWxUcmVlKSBwYWNrYWdlIGltcGxlbWVudHMgdGhlIHByb2NlZHVyZSBpbmNsdWRpbmcgY3Jvc3MtdmFsaWRhdGlvbjoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRX0NCiMgSW1wbGVtZW50ZWQgY2F1c2FsVHJlZSBhZGFwdGluZyBzcGVjaWZpY2F0aW9uIGZyb20gUiBleGFtcGxlDQpjdHJlZSA9IGNhdXNhbFRyZWUoeX54LCBkYXRhID0gZGYsIHRyZWF0bWVudCA9IHcsDQogICAgICAgICAgICAgICAgICAgc3BsaXQuUnVsZSA9ICJDVCIsIGN2Lm9wdGlvbiA9ICJDVCIsIHNwbGl0LkhvbmVzdCA9IFQsc3BsaXQuQnVja2V0ID0gRiwgeHZhbCA9IDUsIA0KICAgICAgICAgICAgICAgICAgIGNwID0gMCwgbWluc2l6ZSA9IDIwKQ0Kb3BjcCA9IGN0cmVlJGNwdGFibGVbLDFdW3doaWNoLm1pbihjdHJlZSRjcHRhYmxlWyw0XSldDQpvcGZpdCA9IHBydW5lKGN0cmVlLCBvcGNwKQ0KZGYkY2F0ZV9jdCA9IHByZWRpY3Qob3BmaXQpDQpgYGANCmBgYHtyfQ0KcnBhcnQucGxvdChvcGZpdCkNCmdncGxvdChkZikgKyBzdGF0X2Z1bmN0aW9uKGZ1bj10YXUsc2l6ZT0xKSArIHlsYWIoZXhwcmVzc2lvbih0YXUpKSArIA0KICBnZW9tX3BvaW50KGFlcyh4PXhbLDFdLHk9Y2F0ZV9jdCksc2hhcGU9InNxdWFyZSIsY29sb3I9ImJsdWUiKSANCmBgYA0KDQoNClRoZSBwYWNrYWdlIHZlcnNpb24gZG9lcyBub3Qga25vdyB0aGF0IG9ubHkgb25lIHNwbGl0IGlzIHJlcXVpcmVkLCBidXQgZmluZHMgdGhlIGNvcnJlY3Qgc3BsaXQgdmFsdWUgYW5kIHBydW5lcyB0aGUgdHJlZSBhcHByb3ByaWF0ZWx5IHZpYSBjcm9zcy12YWxpZGF0aW9uLg0KDQo8YnI+DQoNCg0KIyMgVC1sZWFybmVyIHdpdGggcmFuZG9tIGZvcmVzdA0KDQpSZWdyZXNzaW9uIHRyZWVzIGFyZSBvYnZpb3VzbHkgbm90IHdlbGwtc3VpdGVkIHRvIGVzdGltYXRlIHNtb290aCBmdW50aW9ucy4gVGh1cywgd2UgdXNlIG5leHQgYSBzZWxmLXR1bmVkIGhvbmVzdCByZWdyZXNzaW9uIGZvcmVzdCB0byBmb3JtIHRoZSBzZXBhcmF0ZSBwcmVkaWN0aW9uIG1vZGVsczoNCg0KYGBge3J9DQpyZjAgPSByZWdyZXNzaW9uX2ZvcmVzdCh4W3c9PTAsXSwgeVt3PT0wXSx0dW5lLnBhcmFtZXRlcnMgPSAiYWxsIikNCnJmMSA9IHJlZ3Jlc3Npb25fZm9yZXN0KHhbdz09MSxdLCB5W3c9PTFdLHR1bmUucGFyYW1ldGVycyA9ICJhbGwiKQ0KDQpkZiRhcG9fcmYwID0gcHJlZGljdChyZjAsbmV3ZGF0YT14KSRwcmVkaWN0aW9ucw0KZGYkYXBvX3JmMSA9IHByZWRpY3QocmYxLG5ld2RhdGE9eCkkcHJlZGljdGlvbnMNCmRmJGNhdGVfcmYgPSBkZiRhcG9fcmYxIC0gZGYkYXBvX3JmMA0KDQpnMSA9IGdncGxvdChkZikgKyBzdGF0X2Z1bmN0aW9uKGZ1bj1tMSxzaXplPTEpICsgeWxhYigibTEiKSArIA0KICBnZW9tX3BvaW50KGFlcyh4PXhbLDFdLHk9YXBvX3JmMSksc2hhcGU9InNxdWFyZSIsY29sb3I9ImJsdWUiKQ0KZzIgPSBnZ3Bsb3QoZGYpICsgc3RhdF9mdW5jdGlvbihmdW49bTAsc2l6ZT0xKSArIHlsYWIoIm0wIikgKyANCiAgZ2VvbV9wb2ludChhZXMoeD14WywxXSx5PWFwb19yZjApLHNoYXBlPSJzcXVhcmUiLGNvbG9yPSJibHVlIikgDQpnMyA9IGdncGxvdChkZikgKyBzdGF0X2Z1bmN0aW9uKGZ1bj10YXUsc2l6ZT0xKSArIHlsYWIoZXhwcmVzc2lvbih0YXUpKSArIA0KICBnZW9tX3BvaW50KGFlcyh4PXhbLDFdLHk9Y2F0ZV9yZiksc2hhcGU9InNxdWFyZSIsY29sb3I9ImJsdWUiKSANCmcxIC8gZzIgLyBnMw0KYGBgDQoNClRoaXMgbG9va3MgbXVjaCBiZXR0ZXIgaW4gdGVybXMgb2YgYXBwcm94aW1hdGluZyB0aGUgc2ltcGxlIGRpc2NyZXRlIENBVEUgZnVuY3Rpb24uIEhvd2V2ZXIsIHNvbWUgcHJvYmxlbXMgaW4gYXBwcm94aW1hdGluZyB0aGUgY29tcGxpY2F0ZWQgb3V0Y29tZSBmdW5jdGlvbnMgc3RpbGwgc3BpbGxzIG92ZXIgdG8gdGhlIENBVEUgZXN0aW1hdGVzLg0KDQoNCjxicj4NCg0KIyMgQ2F1c2FsIEZvcmVzdA0KDQpUaGUgQ2F1c2FsIEZvcmVzdCB1c2VzIGFuIGFwcHJveGltYXRpb24gb2YgdGhlIHNwbGl0dGluZyBjcml0ZXJpb24gdGhhdCB3ZSBoYW5kY29kZWQgYWJvdmUgYW5kIGV4cGxvaXRzIHRoZSByZXN1bHRpbmcgd2VpZ2h0cyBvZiBhbiBlbnNlbWJsZSBvZiBDYXVzYWwgVHJlZXMgdG8gZXN0aW1hdGUgdGhlIENBVEVzLg0KDQpGdXJ0aGVyLCB3ZSBjYW4gZXh0cmFjdCBhbiBlc3RpbWF0ZSBvZiB0aGUgdHdvIG91dGNvbWUgZnVuY3Rpb25zIGFzDQoNCi0gJFxoYXR7bX0oMCxYKSA9IFxoYXR7bX0oWCkgLSBcaGF0e2V9KFgpIFxoYXR7XHRhdX0oWCkkDQoNCi0gJFxoYXR7bX0oMSxYKSA9IFxoYXR7bX0oWCkgKyAoMS1caGF0e2V9KFgpKSBcaGF0e1x0YXV9KFgpJA0KDQpBcyAkXGhhdHttfShYKSQgYW5kICRcaGF0e2V9KFgpJCBhcmUgdGhlIG51aXNhbmNlIHBhcmFtZXRlcnMgb2YgdGhlIENhdXNhbCBGb3Jlc3QsIHRoaXMgY29tZXMgYXQgbm8gYWRkaXRpb25hbCBjb21wdXRhdGlvbmFsIGNvc3RzLg0KDQoNCmBgYHtyfQ0KY2YgPSBjYXVzYWxfZm9yZXN0KHgsIHksIHcsdHVuZS5wYXJhbWV0ZXJzID0gImFsbCIpDQoNCmRmJGNhdGVfY2YgPSBwcmVkaWN0KGNmKSRwcmVkaWN0aW9ucw0KZGYkYXBvX2NmMCA9IGNmJFkuaGF0IC0gY2YkVy5oYXQgKiBkZiRjYXRlX2NmDQpkZiRhcG9fY2YxID0gY2YkWS5oYXQgKyAoMS1jZiRXLmhhdCkgKiBkZiRjYXRlX2NmDQoNCmcxID0gZ2dwbG90KGRmKSArIHN0YXRfZnVuY3Rpb24oZnVuPW0xLHNpemU9MSkgKyB5bGFiKCJtMSIpICsgDQogIGdlb21fcG9pbnQoYWVzKHg9eFssMV0seT1hcG9fY2YxKSxzaGFwZT0ic3F1YXJlIixjb2xvcj0iYmx1ZSIpDQpnMiA9IGdncGxvdChkZikgKyBzdGF0X2Z1bmN0aW9uKGZ1bj1tMCxzaXplPTEpICsgeWxhYigibTAiKSArIA0KICBnZW9tX3BvaW50KGFlcyh4PXhbLDFdLHk9YXBvX2NmMCksc2hhcGU9InNxdWFyZSIsY29sb3I9ImJsdWUiKSANCmczID0gZ2dwbG90KGRmKSArIHN0YXRfZnVuY3Rpb24oZnVuPXRhdSxzaXplPTEpICsgeWxhYihleHByZXNzaW9uKHRhdSkpICsgDQogIGdlb21fcG9pbnQoYWVzKHg9eFssMV0seT1jYXRlX2NmKSxzaGFwZT0ic3F1YXJlIixjb2xvcj0iYmx1ZSIpIA0KZzEgLyBnMiAvIGczDQpgYGANCg0KVGhlIENhdXNhbCBGb3Jlc3QgcHJldHR5IG11Y2ggbmFpbHMgdGhlIENBVEUgZnVuY3Rpb24uIEVzcGVjaWFsbHkgaXQgaXMgZmxhdCBpbiB0aGUgcmVnaW9ucyB3aGVyZSB0aGUgZWZmZWN0IGlzIHN0YWJsZSwgdW5saWtlIHdoZW4gdXNpbmcgdGhlIHR3byBzZXBhcmF0ZSBSYW5kb20gRm9yZXN0cyBhYm92ZS4NCg0KPGJyPg0KPGJyPg0KDQojIENvbXBhcmlzb24NCg0KQXMgd2UgZGVmaW5lZCB0aGUgdHJ1dGgsIHdlIGNhbiBjYWxjdWxhdGUgdGhlIE1TRSBmb3IgdGhlIGRpZmZlcmVudCBwYXJhbWV0ZXJzIGFuZCBtZXRob2RzOiANCg0KYGBge3J9DQptc2VzID0gbWF0cml4KE5BLDQsMykNCmNvbG5hbWVzKG1zZXMpID0gYygibSgwLFgpIiwibSgxLFgpIiwidGF1KFgpIikNCg0KbXNlc1sxLDFdID0gbWVhbiggKGRmJGFwb190cmVlMCAtIG0wKHhbLDFdKSleMiApDQptc2VzWzMsMV0gPSBtZWFuKCAoZGYkYXBvX3JmMCAtIG0wKHhbLDFdKSleMiApDQptc2VzWzQsMV0gPSBtZWFuKCAoZGYkYXBvX2NmMCAtIG0wKHhbLDFdKSleMiApDQoNCm1zZXNbMSwyXSA9IG1lYW4oIChkZiRhcG9fdHJlZTEgLSBtMSh4WywxXSkpXjIgKQ0KbXNlc1szLDJdID0gbWVhbiggKGRmJGFwb19yZjEgLSBtMSh4WywxXSkpXjIgKQ0KbXNlc1s0LDJdID0gbWVhbiggKGRmJGFwb19jZjEgLSBtMSh4WywxXSkpXjIgKQ0KDQptc2VzWzEsM10gPSBtZWFuKCAoZGYkY2F0ZV90cmVlIC0gdGF1KHhbLDFdKSleMiApDQptc2VzWzIsM10gPSBtZWFuKCAoZGYkY2F0ZV9jdCAtIHRhdSh4WywxXSkpXjIgKQ0KbXNlc1szLDNdID0gbWVhbiggKGRmJGNhdGVfcmYgLSB0YXUoeFssMV0pKV4yICkNCm1zZXNbNCwzXSA9IG1lYW4oIChkZiRjYXRlX2NmIC0gdGF1KHhbLDFdKSleMiApDQoNCmRhdGEuZnJhbWUoTWV0aG9kID0gZmFjdG9yKGMoIlRyZWUiLCJDYXVzYWwgVHJlZSIsIkZvcmVzdCIsIkNhdXNhbCBGb3Jlc3QiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscz1jKCJUcmVlIiwiQ2F1c2FsIFRyZWUiLCJGb3Jlc3QiLCJDYXVzYWwgRm9yZXN0IikpLA0KICAgICAgICAgICBtc2VzKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKGNvbHM9LU1ldGhvZCxuYW1lc190byA9ICJUYXJnZXQiLHZhbHVlc190byA9ICJNU0UiKSAlPiUNCiAgZ2dwbG90KGFlcyh4PU1ldGhvZCx5PU1TRSkpICsgZ2VvbV9wb2ludCgpICsgZmFjZXRfd3JhcCh+VGFyZ2V0KSArIA0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpICsgDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDApDQpgYGANCg0KQXMgZXhwZWN0ZWQgdGhlIENhdXNhbCBGb3Jlc3QgcHJvdmlkZXMgdGhlIGxvd2VzdCBNU0UgZm9yIHRoZSBhcHByb3hpbWF0aW9uIG9mIHRoZSBDQVRFLg0KDQpJbnRlcmVzdGluZ2x5IGl0IGFsc28gYXBwcm94aW1hdGVzIHRoZSBjb250cm9sIG91dGNvbWUgYmV0dGVyIHRoYW4gdGhlIHNlcGFyYXRlIGZvcmVzdC4gVGhpcyBtaWdodCBiZSBmdXJ0aGVyIGludmVzdGlnYXRlZCBpbiBhIGJpZ2dlciBzaW11bGF0aW9uIHN0dWR5LCBidXQgbm90IGluIHRoaXMgbm90ZWJvb2suDQoNCjxicj4NCjxicj4NCg0KDQoNCiMgQ2F1c2FsIEZvcmVzdCBiZWhpbmQgdGhlIHNjZW5lcw0KDQpUbyBnZXQgYSBiZXR0ZXIgdW5kZXJzdGFuZGluZyB3aGF0IENhdXNhbCBGb3Jlc3RzIGFyZSBkb2luZywgY29uc2lkZXIgdGhlIGZvbGxvd2luZywgbm93IG9ic2VydmF0aW9uYWwsIERHUCB3aXRoIG9ubHkgdHdvIGNvdmFyaWF0ZXM6DQoNCi0gJHA9MiQgaW5kZXBlbmRlbnQgY292YXJpYXRlcyAkWF8xLFhfezJ9JCBkcmF3biBmcm9tIGEgdW5pZm9ybSBkaXN0cmlidXRpb246ICRYX2sgXHNpbSB1bmlmb3JtKC1ccGksXHBpKSQNCg0KLSBUaGUgdHJlYXRtZW50IG1vZGVsIGlzICRXIFxzaW0gQmVybm91bGxpKFx1bmRlcmJyYWNle1xQaGkoc2luKFhfMSkpfV97ZShYKX0pJCwgd2hlcmUgJFxQaGkoXGNkb3QpJCBpcyB0aGUgc3RhbmRhcmQgbm9ybWFsIGN1bXVsYXRpdmUgZGVuc2l0eSBmdW5jdGlvbg0KDQotIFRoZSBwb3RlbnRpYWwgb3V0Y29tZSBtb2RlbCBvZiB0aGUgY29udHJvbHMgaXMgJFkoMCkgPSBcdW5kZXJicmFjZXtzaW4oWF8xKX1fe21fMChYKX0gKyBcdmFyZXBzaWxvbiQsIHdpdGggJFx2YXJlcHNpbG9uIFxzaW0gTigwLDEvMTApJA0KDQotIFRoZSBDQVRFIGZ1bmN0aW9uIGlzIGFuIGluZGljYXRvciBmdW5jdGlvbiAkXHRhdShYKSA9IDFbWF8xID4gLTAuNVxwaV0kDQoNCi0gVGhlIHBvdGVudGlhbCBvdXRjb21lIG1vZGVsIG9mIHRoZSB0cmVhdGVkIGlzICRZKDEpID0gbV8wKFgpICsgXHRhdShYKSArIFx2YXJlcHNpbG9uJCwgd2l0aCAkXHZhcmVwc2lsb24gXHNpbSBOKDAsMS8xMCkkDQoNCg0KYGBge3J9DQojIFNldCBwYXJhbWV0ZXJzDQpwID0gMg0KIyBEcmF3IHNhbXBsZQ0KeCA9IG1hdHJpeChydW5pZihuKnAsLXBpLHBpKSxuY29sPXApDQplID0gZnVuY3Rpb24oeCl7cG5vcm0oc2luKHgpKX0NCm0wID0gZnVuY3Rpb24oeCl7c2luKHgpfQ0KdGF1ID0gZnVuY3Rpb24oeCl7MCArIDEqKHg+LTAuNSpwaSl9DQptMSA9IGZ1bmN0aW9uKHgpe20wKHgpICsgdGF1KHgpfQ0KdyA9IHJiaW5vbShuLDEsZSh4KSkNCnkgPSBtMCh4WywxXSkgKyB3KnRhdSh4WywxXSkgKyBybm9ybShuLDAsMS8xMCkNCmcxID0gZGF0YS5mcmFtZSh4ID0gYygtcGksIHBpKSkgJT4lIGdncGxvdChhZXMoeCkpICsgc3RhdF9mdW5jdGlvbihmdW49ZSxzaXplPTEpICsgeWxhYigiZSIpICsgeGxhYigiWDEiKQ0KZzIgPSBkYXRhLmZyYW1lKHggPSBjKC1waSwgcGkpKSAlPiUgZ2dwbG90KGFlcyh4KSkgKyBzdGF0X2Z1bmN0aW9uKGZ1bj1tMSxzaXplPTEsYWVzKGNvbG91cj0iWTEiKSkgKyANCiAgc3RhdF9mdW5jdGlvbihmdW49bTAsc2l6ZT0xLGFlcyhjb2xvdXI9IlkwIikpICsgeWxhYigiWSIpICsgeGxhYigiWDEiKQ0KZzMgPSBkYXRhLmZyYW1lKHggPSBjKC1waSwgcGkpKSAlPiUgZ2dwbG90KGFlcyh4KSkgKyBzdGF0X2Z1bmN0aW9uKGZ1bj10YXUsc2l6ZT0xKSArIHlsYWIoZXhwcmVzc2lvbih0YXUpKSArIHhsYWIoIlgxIikNCmcxIC8gZzIgLyBnMw0KYGBgDQogDQo8YnI+DQogDQojIyBXZWlnaHRlZCByZXNpZHVhbC1vbi1yZXNpZHVhbCByZWdyZXNzaW9uIGF0IHNpbmdsZSBwb2ludA0KDQpSZWNhbGwgdGhhdCB0aGUgQ2F1c2FsIEZvcmVzdCBjYW4gYmUgd3JpdHRlbiBhcyBhIHJlc2lkdWFsLW9uLXJlc2lkdWFsIHJlZ3Jlc3Npb24gd2l0aCAkeCQtc3BlY2lmaWMgd2VpZ2h0cyAkXGFscGhhKHgpJDoNCg0KJCRcaGF0e1x0YXV9XntjZn0oeCkgPSBhcmdtaW5fe1xicmV2ZXtcdGF1fX0gXGxlZnRceyBcc3VtX3tpPTF9Xk4gXGFscGhhX2koeCkgXGxlZnRbKFlfaSAtIFxoYXR7bX0oWF9pKSkgIC0gXGJyZXZle1x0YXV9IChXX2kgLSBcaGF0e2V9KFhfaSkpIFxyaWdodF1eMiBccmlnaHRcfSQkDQoNClRoZSB3ZWlnaHRzIGNhbiBiZSBhY2Nlc3NlZCB1c2luZyB0aGUgYGdldF9mb3Jlc3Rfd2VpZ2h0c2AgZnVuY3Rpb24gb24gYSBgY2F1c2FsX2ZvcmVzdGAgb2JqZWN0Lg0KDQpDb25zaWRlciB0aGF0IHdlIGFyZSBpbnRlcmVzdGVkIGluIGVzdGltYXRpbmcgdGhlIENBVEUgZm9yICRYXzE9IC0zJCBhbmQgYWxsIG90aGVyIFhzIGVxdWFsIHRvIHplcm8uDQoNClRoZSBlc3RpbWF0ZWQgdmFsdWUgY2FuIGJlIG9idGFpbmVkIHZpYSB0aGUgYHByZWRpY3RgIGZ1bmN0aW9uDQoNCmBgYHtyfQ0KIyBSdW4gQ0YNCmNmID0gY2F1c2FsX2ZvcmVzdCh4LCB5LCB3LHR1bmUucGFyYW1ldGVycyA9ICJhbGwiKQ0KIyBEZWZpbmUgdGVzdCBwb2ludCANCnRlc3R4ID0gbWF0cml4KGMoLTMscmVwKDAscC0xKSksbnJvdz0xKQ0KIyBDaGVjayB3aGF0IHBhY2thZ2UgcHJlZGljdHMNCnByZWRpY3QoY2YsbmV3ZGF0YSA9IHRlc3R4KSRwcmVkaWN0aW9ucw0KYGBgDQp3aGljaCBpcyByZWFzb25hYmx5IGNsb3NlIHRvIHRoZSB0cnVlIHZhbHVlIG9mIHplcm8gZ2l2ZW4gdGhlIHNtYWxsIG51bWJlciBvZiBvYnNlcnZhdGlvbnMuDQoNCkFsdGVybmF0aXZlbHkgd2UgY2FuIGhhbmRjb2RlIGl0IGFzIGEgd2VpZ2h0ZWQgcmVzaWR1YWwtb24tcmVzaWR1YWwgcmVncmVzc2lvbiBhZnRlciBleHRyYWN0aW5nIHRoZSB1bmRlcmx5aW5nIHdlaWdodHMuDQoNCmBgYHtyfQ0KIyBHZXQgcmVzaWR1YWxzDQpyZXNfeSA9IHkgLSBjZiRZLmhhdA0KcmVzX3cgPSB3IC0gY2YkVy5oYXQNCiMgUmVwbGljYXRlIGhhbmRjb2RlZA0KYWxwaGF4ID0gZ2V0X2ZvcmVzdF93ZWlnaHRzKGNmLG5ld2RhdGEgPSB0ZXN0eClbMSxdDQpjb2VmKGxtKHJlc195IH4gcmVzX3csd2VpZ2h0cyA9IGFscGhheCkpDQpgYGANCg0KVGhlIHNlY29uZCBjb2VmZmljaWVudCAodGhlIHNsb3BlIGNvZWZmaWNpZW50KSBpcyBpZGVudGljYWwgdG8gdGhlIGVzdGltYXRlZCBDQVRFLg0KDQoqUmVtYXJrOiogTm90ZSB0aGF0IHRoZSBpbnRlcmNlcHQgaXMgbm90IG5lY2Vzc2FyeSBhcyB3ZSBrbm93IChhbmQgc2VlKSB0aGF0IGl0IHNob3VsZCBiZSB6ZXJvLiBIb3dldmVyLCB0aGUgYGdyZmAgcGFja2FnZSBpbmNsdWRlcyBpdCBhbmQgdG8gbnVtZXJpY2FsbHkgbWF0Y2ggdGhlIGdyZiBvdXRwdXQsIHdlIGFwcGx5IGl0IGFzIHdlbGwuIEhvd2V2ZXIsIG5vdGUgdGhhdCB0aGUgZGlmZmVyZW5jZXMgdG8gdGhlIGNhc2Ugd2l0aG91dCBjb25zdGFudCBhcmUgbmVnbGlnaWJsZToNCg0KYGBge3J9DQojIFJlcGxpY2F0ZSBoYW5kY29kZWQgdy9vIGNvbnN0YW50DQpjb2VmKGxtKHJlc195IH4gMCArIHJlc193LHdlaWdodHMgPSBhbHBoYXgpKQ0KYGBgDQoNCjxicj4NCg0KIyMgSG93IHRoZSB3ZWlnaHRzIG1vdmUgZm9yIGRpZmZlcmVudCBwcmVkaWN0aW9ucw0KDQpGaW5hbGx5LCB0aGlzIHNldHRpbmcgY2FuIGJlIHVzZWQgdG8gbmljZWx5IGlsbHVzdHJhdGUgd2hhdCBoYXBwZW5zIGF0IGRpZmZlcmVudCB2YWx1ZXMgb2YgJFhfMSQuIFRoZSB0d28gcmVzaWR1YWxzIGNhbiBiZSBwbG90dGVkIGFnYWluc3QgZWFjaCBvdGhlci4gQWRkaXRpb25hbGx5IHRoZSBzaXplIG9mIHRoZSByZXNpZHVhbHMgaXMgcHJvcG9ydGlvbmFsIHRvIHRoZSB3ZWlnaHQgdGhleSByZWNlaXZlIGFuZCB0aGUgY29sb3IgaW5kaWNhdGVzIGxvd2VyIHRvIGhpZ2hlciB2YWx1ZXMgb2YgdGhlIHZhcmlhYmxlOg0KDQoNCmBgYHtyfQ0KIyBSdW4gc2FtZSBvdmVyIGdyaWQgYW4gc2VlIGhvdyB3ZWlnaHRzIG1vdmUNCmdyaWQgPSBzZXEoLTMsMCwxKQ0KZ3JpZHggPSBjYmluZChncmlkLG1hdHJpeCgwLGxlbmd0aChncmlkKSxwLTEpKQ0KZ3JpZF9oYXQgPSBwcmVkaWN0KGNmLG5ld2RhdGEgPSBncmlkeCkkcHJlZGljdGlvbnMNCmFscGhhID0gZ2V0X2ZvcmVzdF93ZWlnaHRzKGNmLG5ld2RhdGEgPSBncmlkeCkNCmZvciAoaSBpbiAxOmxlbmd0aChncmlkKSkgew0KICBnMSA9IGRhdGEuZnJhbWUoeD1ncmlkLHRhdV9oYXQ9Z3JpZF9oYXQpICU+JQ0KICAgIGdncGxvdChhZXMoeD14ICx5PXRhdV9oYXQpKSArIHN0YXRfZnVuY3Rpb24oZnVuPXRhdSxzaXplPTEpICsgDQogICAgZ2VvbV9saW5lKGNvbG9yPSJibHVlIikgKyANCiAgICBnZW9tX3BvaW50KGFlcyh4PWdyaWRbaV0seT1ncmlkX2hhdFtpXSksc2l6ZT00LGNvbG9yPSJibHVlIixzaGFwZT00KSANCiAgDQogIHJvcnIgPSBsbShyZXNfeSB+IHJlc193LHdlaWdodHMgPSBhbHBoYVtpLF0pDQogIA0KICBnMiA9IGRhdGEuZnJhbWUocmVzX3cscmVzX3ksYWxwaGE9YWxwaGFbaSxdLHg9eCkgJT4lDQogICAgZ2dwbG90KGFlcyh4PXJlc193LHk9cmVzX3kpKSArIGdlb21fcG9pbnQoYWVzKHNpemU9YWxwaGEsY29sb3I9eFssMV0pLGFscGhhPTAuNSkgKyANCiAgICBnZW9tX2FibGluZShpbnRlcmNlcHQ9cm9yciRjb2VmZmljaWVudHNbMV0sc2xvcGU9cm9yciRjb2VmZmljaWVudHNbMl0pICsgDQogICAgYW5ub3RhdGUoInRleHQiLCB4ID0gLTAuMjUsIHkgPSAxLCBsYWJlbCA9IHBhc3RlMCgidGF1KCIsdG9TdHJpbmcoZ3JpZFtpXSksIikgPSBzbG9wZSBvZiBsaW5lID0gIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvU3RyaW5nKHJvdW5kKHJvcnIkY29lZmZpY2llbnRzWzJdLDIpKSkpKw0KICAgIHNjYWxlX2NvbG91cl9ncmFkaWVudChsb3cgPSAiYmxhY2siLCBoaWdoID0gInllbGxvdyIpDQogIHByaW50KGcxIC8gZzIpDQp9DQpgYGANCg0KVGhlIGp1bXAgZnJvbSAkWCA9IC0yJCB0byAkWD0tMSQgaXMgbW9zdCBpbnN0cnVjdGl2ZS4gV2Ugc2VlIHRoYXQgYmVmb3JlIHRoZSBqdW1wIG9mIHRoZSB0cnVlIENBVEUgZnVuY3Rpb24gdW5pdHMgd2l0aCBsb3cgdmFsdWVzIG9mICRYXzEkIHJlY2VpdmUgbW9zdCB3ZWlnaHQuIEZ1cnRoZXJtb3JlLCBjb250cm9scyAod2l0aCBuZWdhdGl2ZSB0cmVhdG1lbnQgcmVzaWR1YWxzKSBhbmQgdHJlYXRlZCAod2l0aCBwb3NpdGl2ZSB0cmVhdG1lbnQgcmVzaWR1YWxzKSBvZiB0aG9zZSB3aXRoIGxhcmdlIHdlaWdodHMgYXJlIHF1aXRlIHNpbWlsYXIuICBUaGlzIHJlc3VsdHMgaW4gYSBuZWFybHkgaG9yaXpvbnRhbCBzbG9wZSBhbmQgdGh1cyBhIENBVEUgZXN0aW1hdGUgb2YgY2xvc2UgdG8gemVyby4NCg0KQWZ0ZXIgdGhlIGp1bXAsIHVuaXRzIHdpdGggbGFyZ2VyIHZhbHVlcyBvZiAkWF8xJCByZWNlaXZlIG1vc3Qgb2YgdGhlIHdlaWdodCwgcmVzdWx0aW5nIGluIGEgY2xlYXJseSBwb3NpdGl2ZSBzbG9wZS4gQXMgdGhlIHNsb3BlIG9mIHRoZSBsaW5lIGlzIHRoZSBlc3RpbWF0ZWQgQ0FURSwgd2UgKHJpZ2h0bHkpIGVzdGltYXRlIGEgc3Vic3RhbnRpYWwgcG9zaXRpdmUgZWZmZWN0IGFmdGVyIHRoZSBqdW1wLg0KDQo8YnI+DQoNCg0KIyMgVGFrZS1hd2F5DQogDQogLSBUYXJnZXRpbmcgQ0FURXMgZXhwbGljaXRseSB3b3Jrcy4NCiANCiAtIFRoZSBDYXVzYWwgRm9yZXN0IGNhbiBiZSB0aG91Z2h0IG9mIGFzIHJlc2lkdWFsLW9uLXJlc2lkdWFsIHJlZ3Jlc3Npb25zIHdoZXJlIHRoZSByZXNpZHVhbHMgYXJlIGFsd2F5cyB0aGUgc2FtZSwgYnV0IHRoZSB3ZWlnaHRzIGFyZSBpbmRpdmlkdWFsaXplZC4NCiANCjxicj4NCjxicj4NCiANCiANCiMjIFN1Z2dlc3Rpb25zIHRvIHBsYXkgd2l0aCB0aGUgdG95IG1vZGVsDQoNClNvbWUgc3VnZ2VzdGlvbnM6DQogDQotIEluY3JlYXNlL2RlY3JlYXNlIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zDQoNCi0gQ3JlYXRlIGRpZmZlcmVudCBDQVRFIGZ1bmN0aW9uDQoNCi0gQ2hhbmdlIHRoZSB0cmVhdG1lbnQgc2hhcmVzDQoNCi0gSW5jcmVhc2Ugbm9pc2UgbGV2ZWwgaW4gc2Vjb25kIHBhcnQgYW5kIHNlZSBob3cgd2UgY2FuJ3Qgc2VlIG11Y2ggaWYgb3V0Y29tZSBub2lzZSBpcyBub3QgdmVyeSBzbWFsbC4gSW4gcmVhbCBkYXRhc2V0cyB5b3Ugd29uJ3Qgc2VlIG11Y2gsIGJ1dCBoZXJlIGl0IGhlbHBzIHRvIGJ1aWxkIGludHVpdGlvbi4=