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).
- 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)
- Use regression tree to fit model in treated subsample
tree1 = rpart(y~x,data = df,subset= (w==1))
rpart.plot(tree1)
- 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
Calculate the effect in the left leaf as mean difference between
treated and control units \(\hat{\tau}_{L(j,s)}\)
Calculate the effect in the right leaf as mean difference between
treated and control units \(\hat{\tau}_{R(j,s)}\)
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=