Goals:
Hand-code partially linear (IV) and AIPW for ATE/ATT/LATE with
the generic Double ML recipe
Test H0:ATT=ATE, H0:LATE=ATE and H0:(ATT−ATE)/ATE×100=0
using the chain rule of influence functions
401(k) dataset again
We again use the data of the hdm
package. The data was
used in Chernozhukov
and Hansen (2004). Their paper investigates the effect of
participation in the employer-sponsored 401(k) retirement savings plan
(p401) on net assets (net_tfa). Since then, the data
was used to showcase many new methods. It is not the most comprehensive
dataset with basically ten covariates/regressors/predictors:
age: age
db: defined benefit pension
educ: education (in years)
fsize: family size
hown: home owner
inc: income (in US $)
male: male
marr: married
pira: participation in individual retirement account
(IRA)
twoearn: two earners
However, it is publicly available and the few controls ensure that
the programs run not as long as with datasets that you hope to have for
your applications.
library(hdm)
library(causalDML)
library(grf)
library(tidyverse)
set.seed(1234)
data(pension)
Y = pension$net_tfa
W = pension$p401
Z = pension$e401
X = model.matrix(~ 0 + age + db + educ + fsize + hown + inc + male + marr + pira + twoearn, data = pension)
We want to estimate the effect of 401(k) participation on net assets.
We leverage two identification strategies:
and use estimators that assume constant effects (partially linear)
and allow for heterogeneous effects (ATE, ATT, LATE).
In total we want to implement five estimators:
partially linear Double ML (PL)
AIPW-ATE Double ML (ATE)
AIPW-ATE Double ML (ATT)
partially linear IV Double ML (PL-IV)
AIPW-LATE Double ML (LATE)
Nuisance parameters
We need a bunch of nuisance parameters to do so. The ones we know
already are
e(X)=E[W|X] coded as
exhat
m(X)=E[Y|X] coded as
mxhat
m(0,X)=E[Y|W=0,X] coded as
mwhat0
m(1,X)=E[Y|W=1,X] coded as
mwhat1
Additionally, we need
h(X)=E[Z|X] to be coded as
hhat
mz(0,X)=E[Y|Z=0,X] to be
coded as mzhat0
mz(1,X)=E[Y|Z=1,X] to be
coded as mzhat1
e(0,X)=E[W|Z=0,X] to be
coded as ezhat0
e(1,X)=E[W|Z=1,X] to be
coded as ezhat1
n = length(Y)
nfolds = 5
fold = sample(1:nfolds,n,replace=T)
exhat = mxhat = mwhat0 = mwhat1 = hhat = mzhat0 = mzhat1 = ezhat0 = ezhat1 = rep(NA,n)
for (i in 1:nfolds){
rfe = regression_forest(X[fold != i,],W[fold != i])
exhat[fold == i] = predict(rfe, X[fold == i,])$predictions
rfm = regression_forest(X[fold != i,],Y[fold != i])
mxhat[fold == i] = predict(rfm, X[fold == i,])$predictions
rfm0 = regression_forest(X[fold != i & W==0,],Y[fold != i & W==0])
mwhat0[fold == i] = predict(rfm0, X[fold == i,])$predictions
rfm1 = regression_forest(X[fold != i & W==1,],Y[fold != i & W==1])
mwhat1[fold == i] = predict(rfm1, X[fold == i,])$predictions
rfh = regression_forest(X[fold != i,],Z[fold != i])
hhat[fold == i] = predict(rfh, X[fold == i,])$predictions
rfmz0 = regression_forest(X[fold != i & Z==0,],Y[fold != i & Z==0])
mzhat0[fold == i] = predict(rfmz0, X[fold == i,])$predictions
rfmz1 = regression_forest(X[fold != i & Z==1,],Y[fold != i & Z==1])
mzhat1[fold == i] = predict(rfmz1, X[fold == i,])$predictions
rfez0 = regression_forest(X[fold != i & Z==0,],W[fold != i & Z==0])
ezhat0[fold == i] = predict(rfez0, X[fold == i,])$predictions
rfez1 = regression_forest(X[fold != i & Z==1,],W[fold != i & Z==1])
ezhat1[fold == i] = predict(rfez1, X[fold == i,])$predictions
}
and finally the easy e=E[W]
coded as ehat
ψa and ψb of each scores
Now we code up the two components of the score, that will help us
later to get the point estimate and the standard error using the same
function.
PL
The empirical version of the score looks like this: ψPL(O;ˆτ,ˆη)=ˆτ(−1)(W−ˆe(X))2⏟ψPLa+(Y−ˆm(X))(W−ˆe(X))⏟ψPLb To follow the
recipe, we need two components:
ψPLa to be coded as
pa_pl
ψPLb to be coded as
pb_pl
pa_pl = -(W - exhat)^2
pb_pl = (Y - mxhat) * (W - exhat)
-sum(pb_pl) / sum(pa_pl)
[1] 13972.06
ATE
The empirical version of the score looks like this: ψATE(O;ˆτATE,ˆη)=ˆτATE(−1)⏟ψATEa+ˆm(1,X)−ˆm(0,X)+W(Y−ˆm(1,X))ˆe(X)−(1−W)(Y−ˆm(0,X))1−ˆe(X)⏟ψATEb To follow the recipe, we need two
components:
ψATEa to be coded as
pa_ate
ψATEb to be coded as
pb_ate
pa_ate = rep(-1,length(Y))
pb_ate = mwhat1 - mwhat0 + W * (Y - mwhat1) / ehat - (1 - W) * (Y - mwhat0) / (1-ehat)
-sum(pb_ate) / sum(pa_ate)
[1] 12356.5
ATT
The empirical version of the score looks like this: ψATT(O;ˆτATT,ˆη)=ˆτATT(−1)Wˆe⏟ψATTa+Wˆe(Y−ˆm(0,X))−(1−W)ˆe(X)ˆe(1−ˆe(X))(Y−ˆm(0,X))⏟ψATTb To follow the recipe, we need two
components:
ψATTa to be coded as
pa_att
ψATTb to be coded as
pb_att
pa_att = -W / ehat
pb_att = W * (Y - mwhat0) / ehat - ( (1 - W) * exhat ) * (Y - mwhat0) / (ehat * (1 - ehat))
-sum(pb_att) / sum(pa_att)
[1] 14796
PL-IV
The empirical version of the score looks like this: ψIV(O;ˆτIV,ˆη)=ˆτIV(−1)(W−ˆe(X))(Z−ˆh(X))⏟ψIVa+(Y−ˆm(X))(Z−ˆh(X))⏟ψIVb To follow the recipe, we need two
components:
ψIVa to be coded as
pa_iv
ψIVb to be coded as
pb_iv
pa_iv = -(W - exhat) * (Z - hhat)
pb_iv = (Y - mxhat) * (Z - hhat)
-sum(pb_iv) / sum(pa_iv)
[1] 12892.28
LATE
The empirical version of the score looks like this: ψLATE(O;ˆτLATE,ˆη)=ˆτLATE(−1)[ˆe(1,X)−ˆe(0,X)+Z(W−ˆe(1,X))ˆh(X)−(1−Z)(W−ˆh(0,X))1−ˆh(X)]⏟ψLATEa+ˆmz(1,X)−ˆmz(0,X)+Z(Y−ˆmz(1,X))ˆh(X)−(1−Z)(Y−ˆmz(0,X))1−ˆh(X)⏟ψLATEb To follow the recipe, we need two components:
ψLATEa to be coded as
pa_late
ψLATEb to be coded as
pb_late
pa_late = -( ezhat1 - ezhat0 + Z * (W - ezhat1) / hhat - (1 - Z) * (W - ezhat0) / (1-hhat) )
pb_late = mzhat1 - mzhat0 + Z * (Y - mzhat1) / hhat - (1 - Z) * (Y - mzhat0) / (1-hhat)
-sum(pb_late) / sum(pa_late)
[1] 11194.11
A generic function for Double ML with linear score
Now we write a function that takes any ψa and ψb as inputs and outputs
point estimate
standard error
t-value
p-value
The function implements the following steps:
Calculate point estimate as $ = - $
ˆθ can then be
used to complete the empirical score as ψ(O;ˆθ,ˆη)=ˆθψa(Oi;ˆηi)+ψb(Oi;ˆηi)
Create influence function Ψ(O;ˆθ,ˆη)=−ψ(O;ˆθ,ˆη)N−1∑iψa(Oi;ˆηi)
Estimate the variance as ˆσ2=Var(Ψ(O;ˆθ,ˆη)) or ˆσ2=N−1∑iψ(Oi;ˆθ,ˆηi)2[N−1∑iψa(Oi;ˆηi)]2
Get the standard error as se(ˆθ)=√ˆσ2N
DML_inference = function(psi_a,psi_b) {
N = length(psi_a)
theta = -sum(psi_b) / sum(psi_a)
psi = theta * psi_a + psi_b
Psi = - psi / mean(psi_a)
sigma2 = var(Psi)
se = sqrt(sigma2 / N)
t = theta / se
p = 2 * pt(abs(t),N,lower = FALSE)
result = c(theta,se,t,p)
return(result)
}
Results
results = matrix(NA,5,4)
rownames(results) = c("PL","ATE","ATT","PL-IV","LATE")
colnames(results) = c("Effect","S.E.","t","p")
results[1,] = DML_inference(pa_pl,pb_pl)
results[2,] = DML_inference(pa_ate,pb_ate)
results[3,] = DML_inference(pa_att,pb_att)
results[4,] = DML_inference(pa_iv,pb_iv)
results[5,] = DML_inference(pa_late,pb_late)
printCoefmat(results,has.Pvalue = TRUE)
Effect S.E. t p
PL 13972.1 1538.8 9.0800 < 2.2e-16 ***
ATE 12356.5 1469.2 8.4102 < 2.2e-16 ***
ATT 14796.0 1647.5 8.9808 < 2.2e-16 ***
PL-IV 12892.3 1923.4 6.7029 2.154e-11 ***
LATE 11194.1 1699.1 6.5881 4.681e-11 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
data.frame(thetas = results[,1],ses = results[,2],
Estimator = rownames(results),
cil = results[,1] - 1.96*results[,2],
ciu = results[,1] + 1.96*results[,2]) %>%
ggplot(aes(x=Estimator,y=thetas,ymin=cil,ymax=ciu)) + geom_point(size=2.5) + geom_errorbar(width=0.15) +
geom_hline(yintercept=0)

ATT vs. ATE
Assume that we want to test whether ATT=ATE.
For this purpose, we create the new parameter ΔATT=τATT−τATE
and want to test H0:Δ(τATT,τATE)=0
The influence function of this new parameter can derived like this:
ΨΔ==1⏞δΔδτATTΨτATT+=−1⏞δΔδτATEΨτATE=ΨτATT−ΨτATE
Define for convenience and later use a Psi_maker()
function that creates the influence function for generic ψa and ψb:
Psi_maker = function(psi_a,psi_b) {
theta = -sum(psi_b) / sum(psi_a)
psi = theta * psi_a + psi_b
Psi = - psi / mean(psi_a)
return(Psi)
}
Now this can be used to test H0:Δ(τATT,τATE)=0:
att = - sum(pb_att) / sum(pa_att)
ate = - sum(pb_ate) / sum(pa_ate)
Delta_att = att - ate
Psi_Delta_att = Psi_maker(pa_att,pb_att) - Psi_maker(pa_ate,pb_ate)
se_Delta_att = sqrt(var(Psi_Delta_att)/length(Psi_Delta_att))
t_Delta_att = Delta_att / se_Delta_att
p_Delta_att = 2 * pt(abs(t_Delta_att),length(Psi_Delta_att),lower = FALSE)
result = matrix(c(Delta_att,se_Delta_att,t_Delta_att,p_Delta_att),nrow = 1)
rownames(result) = c("ATT-ATE")
colnames(result) = c("Delta","S.E.","t","p")
printCoefmat(result,has.Pvalue = TRUE)
Delta S.E. t p
ATT-ATE 2439.50 516.31 4.7249 2.334e-06 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
We observe that the different between ATT and ATE is highly
significant. This may come as a surprise given that the confidence
intervals overlap in the graph above. However, such eyeballing does not
work for correlated estimators and the ATT and ATE estimator are clearly
correlated.
LATE vs. ATE
Similarly, we can investigate H0:ΔLATE=τLATE−τATE=0. (0.5P)
The derivation works like before:
ΨΔLATE=∂ΔLATE∂τLATE⏟=1ΨLATE+∂ΔLATE∂τATE⏟=−1ΨATEΨΔLATE=ΨLATE−ΨATE
and also the inference follows the same recipe:
late = -sum(pb_late)/sum(pa_late)
Delta_late = late - ate
Psi_Delta_late = Psi_maker(pa_late,pb_late) - Psi_maker(pa_ate,pb_ate)
se_Delta_late = sqrt(var(Psi_Delta_late)/length(Psi_Delta_late))
t_Delta_late = Delta_late/se_Delta_late
p_Delta_late = 2 * pt(abs(t_Delta_late),length(Psi_Delta_late),lower = FALSE)
results = matrix(c(Delta_late,se_Delta_late,t_Delta_late,p_Delta_late),nrow = 1)
rownames(results) = c("LATE-ATE")
colnames(results) = c("Delta","S.E.","t","p")
printCoefmat(results,has.Pvalue = TRUE)
Delta S.E. t p
LATE-ATE -1162.4 1247.2 -0.932 0.3514
However, the difference between LATE and ATE is not statistically
significant.
How large is the difference between ATT and ATE in %?
Finally, let’s look at a more complicated new parameter Δ%=τATT−τATEτATE×100.
Following the recipe, we derive the new influence function:
ΨΔ%=∂Δ%∂τATTΨATT+∂Δ%∂τATEΨATEΨΔ%=100⋅1τATEΨATT−100⋅τATTτ2ATEΨATE
ΨATT and ΨATE can be obtained using
Psi_maker
. τATE
and τATT can be replaced by
their estimates:
Delta_pc = (att-ate)/ate*100
Psi_Delta_pc = 100 / ate * Psi_maker(pa_att, pb_att) - 100 * att / (ate^2) * Psi_maker(pa_ate, pb_ate)
se_Delta_pc = sqrt(var(Psi_Delta_pc)/length(Psi_Delta_pc))
t_Delta_pc = Delta_pc/se_Delta_pc
p_Delta_pc = 2 * pt(abs(t_Delta_pc),length(Psi_Delta_pc),lower = FALSE)
results = matrix(c(Delta_pc,se_Delta_pc,t_Delta_pc,p_Delta_pc),nrow = 1)
rownames(results) = c("Delta%")
colnames(results) = c("Delta","S.E.","t","p")
printCoefmat(results,has.Pvalue = TRUE)
Delta S.E. t p
Delta% 19.7426 4.3854 4.5019 6.812e-06 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Not surprisingly the percentage change is significant, like the plain
level difference between ATT and ATE. However, the t-values are not
identical, which illustrates that we explicitly calculate the inference
for such new parameters. And influence functions provide a neat tool to
do so.
LS0tDQp0aXRsZTogIkNhdXNhbCBNTDogRG91YmxlIE1MIGFzIGdlbmVyaWMgcmVjaXBlIg0Kc3VidGl0bGU6ICJBcHBsaWNhdGlvbiBub3RlYm9vayINCmF1dGhvcjogIk1pY2hhZWwgS25hdXMiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclbS8leScpYCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQotLS0NCg0KDQo8YnI+DQoNCkdvYWxzOg0KDQotIEhhbmQtY29kZSBwYXJ0aWFsbHkgbGluZWFyIChJVikgYW5kIEFJUFcgZm9yIEFURS9BVFQvTEFURSB3aXRoIHRoZSBnZW5lcmljIERvdWJsZSBNTCByZWNpcGUNCg0KLSBUZXN0ICRIXzA6IEFUVCA9IEFURSQsICRIXzA6IExBVEUgPSBBVEUkIGFuZCAkSF8wOiAoQVRUIC0gQVRFKSAvIEFURSBcdGltZXMgMTAwID0gMCQgdXNpbmcgdGhlIGNoYWluIHJ1bGUgb2YgaW5mbHVlbmNlIGZ1bmN0aW9ucw0KDQo8YnI+DQoNCiMgNDAxKGspIGRhdGFzZXQgYWdhaW4NCg0KV2UgYWdhaW4gdXNlIHRoZSBkYXRhIG9mIHRoZSBgaGRtYCBwYWNrYWdlLiBUaGUgZGF0YSB3YXMgdXNlZCBpbiBbQ2hlcm5vemh1a292IGFuZCBIYW5zZW4gKDIwMDQpXShodHRwczovL2RpcmVjdC5taXQuZWR1L3Jlc3QvYXJ0aWNsZS84Ni8zLzczNS81NzU4Ni9UaGUtRWZmZWN0cy1vZi00MDEtSy1QYXJ0aWNpcGF0aW9uLW9uLXRoZS1XZWFsdGgpLiBUaGVpciBwYXBlciBpbnZlc3RpZ2F0ZXMgdGhlIGVmZmVjdCBvZiBwYXJ0aWNpcGF0aW9uIGluIHRoZSBlbXBsb3llci1zcG9uc29yZWQgNDAxKGspIHJldGlyZW1lbnQgc2F2aW5ncyBwbGFuICgqcDQwMSopIG9uIG5ldCBhc3NldHMgKCpuZXRfdGZhKikuIFNpbmNlIHRoZW4sIHRoZSBkYXRhIHdhcyB1c2VkIHRvIHNob3djYXNlIG1hbnkgbmV3IG1ldGhvZHMuIEl0IGlzIG5vdCB0aGUgbW9zdCBjb21wcmVoZW5zaXZlIGRhdGFzZXQgd2l0aCBiYXNpY2FsbHkgdGVuIGNvdmFyaWF0ZXMvcmVncmVzc29ycy9wcmVkaWN0b3JzOg0KDQotICphZ2UqOiBhZ2UNCg0KLSAqZGIqOiBkZWZpbmVkIGJlbmVmaXQgcGVuc2lvbg0KDQotICplZHVjKjogZWR1Y2F0aW9uIChpbiB5ZWFycykNCg0KLSAqZnNpemUqOiBmYW1pbHkgc2l6ZQ0KDQotICpob3duKjogaG9tZSBvd25lcg0KDQotICppbmMqOiBpbmNvbWUgKGluIFVTICQpDQoNCi0gKm1hbGUqOiBtYWxlDQoNCi0gKm1hcnIqOiBtYXJyaWVkDQoNCi0gKnBpcmEqOiBwYXJ0aWNpcGF0aW9uIGluIGluZGl2aWR1YWwgcmV0aXJlbWVudCBhY2NvdW50IChJUkEpDQoNCi0gKnR3b2Vhcm4qOiB0d28gZWFybmVycw0KDQpIb3dldmVyLCBpdCBpcyBwdWJsaWNseSBhdmFpbGFibGUgYW5kIHRoZSBmZXcgY29udHJvbHMgZW5zdXJlIHRoYXQgdGhlIHByb2dyYW1zIHJ1biBub3QgYXMgbG9uZyBhcyB3aXRoIGRhdGFzZXRzIHRoYXQgeW91IGhvcGUgdG8gaGF2ZSBmb3IgeW91ciBhcHBsaWNhdGlvbnMuDQoNCmBgYHtyLCB3YXJuaW5nPUYsbWVzc2FnZT1GfQ0KbGlicmFyeShoZG0pDQpsaWJyYXJ5KGNhdXNhbERNTCkNCmxpYnJhcnkoZ3JmKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCnNldC5zZWVkKDEyMzQpICMgZm9yIHJlcGxpY2FiaWxpdHkNCg0KIyBHZXQgZGF0YQ0KZGF0YShwZW5zaW9uKQ0KIyBPdXRjb21lDQpZID0gcGVuc2lvbiRuZXRfdGZhDQojIFRyZWF0bWVudA0KVyA9IHBlbnNpb24kcDQwMQ0KIyBUcmVhdG1lbnQNClogPSBwZW5zaW9uJGU0MDENCiMgQ3JlYXRlIG1haW4gZWZmZWN0cyBtYXRyaXgNClggPSBtb2RlbC5tYXRyaXgofiAwICsgYWdlICsgZGIgKyBlZHVjICsgZnNpemUgKyBob3duICsgaW5jICsgbWFsZSArIG1hcnIgKyBwaXJhICsgdHdvZWFybiwgZGF0YSA9IHBlbnNpb24pDQpgYGANCg0KV2Ugd2FudCB0byBlc3RpbWF0ZSB0aGUgZWZmZWN0IG9mIDQwMShrKSBwYXJ0aWNpcGF0aW9uIG9uIG5ldCBhc3NldHMuIFdlIGxldmVyYWdlIHR3byBpZGVudGlmaWNhdGlvbiBzdHJhdGVnaWVzOg0KDQotIEJhc2VkIG9uIHVuY29uZm91bmRlZG5lc3MgYXMgYmVmb3JlDQoNCi0gdXNpbmcgZWxpZ2liaWxpdHkgZm9yIDQwMShrKSBhcyBpbnN0cnVtZW50IGZvciBhY3R1YWwgcGFydGljaXBhdGlvbg0KDQphbmQgdXNlIGVzdGltYXRvcnMgdGhhdCBhc3N1bWUgY29uc3RhbnQgZWZmZWN0cyAocGFydGlhbGx5IGxpbmVhcikgYW5kIGFsbG93IGZvciBoZXRlcm9nZW5lb3VzIGVmZmVjdHMgKEFURSwgQVRULCBMQVRFKS4gDQoNCkluIHRvdGFsIHdlIHdhbnQgdG8gaW1wbGVtZW50IGZpdmUgZXN0aW1hdG9yczoNCg0KLSBwYXJ0aWFsbHkgbGluZWFyIERvdWJsZSBNTCAoUEwpDQoNCi0gQUlQVy1BVEUgRG91YmxlIE1MIChBVEUpDQoNCi0gQUlQVy1BVEUgRG91YmxlIE1MIChBVFQpDQoNCi0gcGFydGlhbGx5IGxpbmVhciBJViBEb3VibGUgTUwgKFBMLUlWKQ0KDQotIEFJUFctTEFURSBEb3VibGUgTUwgKExBVEUpDQoNCjxicj4NCjxicj4NCg0KIyBOdWlzYW5jZSBwYXJhbWV0ZXJzDQoNCldlIG5lZWQgYSBidW5jaCBvZiBudWlzYW5jZSBwYXJhbWV0ZXJzIHRvIGRvIHNvLiBUaGUgb25lcyB3ZSBrbm93IGFscmVhZHkgYXJlIA0KDQotICRlKFgpPUVbV3xYXSQgY29kZWQgYXMgYGV4aGF0YA0KDQotICRtKFgpPUVbWXxYXSQgY29kZWQgYXMgYG14aGF0YA0KDQotICRtKDAsWCk9RVtZfFc9MCxYXSQgY29kZWQgYXMgYG13aGF0MGANCg0KLSAkbSgxLFgpPUVbWXxXPTEsWF0kIGNvZGVkIGFzIGBtd2hhdDFgDQoNCkFkZGl0aW9uYWxseSwgd2UgbmVlZA0KDQotICRoKFgpPUVbWnxYXSQgdG8gYmUgY29kZWQgYXMgYGhoYXRgDQoNCi0gJG1feigwLFgpPUVbWXxaPTAsWF0kIHRvIGJlIGNvZGVkIGFzIGBtemhhdDBgDQoNCi0gJG1feigxLFgpPUVbWXxaPTEsWF0kIHRvIGJlIGNvZGVkIGFzIGBtemhhdDFgDQoNCi0gJGUoMCxYKT1FW1d8Wj0wLFhdJCB0byBiZSBjb2RlZCBhcyBgZXpoYXQwYA0KDQotICRlKDEsWCk9RVtXfFo9MSxYXSQgdG8gYmUgY29kZWQgYXMgYGV6aGF0MWANCg0KDQpgYGB7cn0NCm4gPSBsZW5ndGgoWSkNCm5mb2xkcyA9IDUNCmZvbGQgPSBzYW1wbGUoMTpuZm9sZHMsbixyZXBsYWNlPVQpDQoNCmV4aGF0ID0gbXhoYXQgPSBtd2hhdDAgPSBtd2hhdDEgPSBoaGF0ID0gbXpoYXQwID0gbXpoYXQxID0gZXpoYXQwID0gZXpoYXQxID0gcmVwKE5BLG4pDQogIA0KZm9yIChpIGluIDE6bmZvbGRzKXsNCiAgcmZlID0gcmVncmVzc2lvbl9mb3Jlc3QoWFtmb2xkICE9IGksXSxXW2ZvbGQgIT0gaV0pDQogIGV4aGF0W2ZvbGQgPT0gaV0gPSBwcmVkaWN0KHJmZSwgWFtmb2xkID09IGksXSkkcHJlZGljdGlvbnMNCiAgDQogIHJmbSA9IHJlZ3Jlc3Npb25fZm9yZXN0KFhbZm9sZCAhPSBpLF0sWVtmb2xkICE9IGldKQ0KICBteGhhdFtmb2xkID09IGldID0gcHJlZGljdChyZm0sIFhbZm9sZCA9PSBpLF0pJHByZWRpY3Rpb25zDQogIA0KICByZm0wID0gcmVncmVzc2lvbl9mb3Jlc3QoWFtmb2xkICE9IGkgJiBXPT0wLF0sWVtmb2xkICE9IGkgJiBXPT0wXSkNCiAgbXdoYXQwW2ZvbGQgPT0gaV0gPSBwcmVkaWN0KHJmbTAsIFhbZm9sZCA9PSBpLF0pJHByZWRpY3Rpb25zDQoNCiAgcmZtMSA9IHJlZ3Jlc3Npb25fZm9yZXN0KFhbZm9sZCAhPSBpICYgVz09MSxdLFlbZm9sZCAhPSBpICYgVz09MV0pDQogIG13aGF0MVtmb2xkID09IGldID0gcHJlZGljdChyZm0xLCBYW2ZvbGQgPT0gaSxdKSRwcmVkaWN0aW9ucw0KDQogIHJmaCA9IHJlZ3Jlc3Npb25fZm9yZXN0KFhbZm9sZCAhPSBpLF0sWltmb2xkICE9IGldKQ0KICBoaGF0W2ZvbGQgPT0gaV0gPSBwcmVkaWN0KHJmaCwgWFtmb2xkID09IGksXSkkcHJlZGljdGlvbnMNCg0KICByZm16MCA9IHJlZ3Jlc3Npb25fZm9yZXN0KFhbZm9sZCAhPSBpICYgWj09MCxdLFlbZm9sZCAhPSBpICYgWj09MF0pDQogIG16aGF0MFtmb2xkID09IGldID0gcHJlZGljdChyZm16MCwgWFtmb2xkID09IGksXSkkcHJlZGljdGlvbnMNCg0KICByZm16MSA9IHJlZ3Jlc3Npb25fZm9yZXN0KFhbZm9sZCAhPSBpICYgWj09MSxdLFlbZm9sZCAhPSBpICYgWj09MV0pDQogIG16aGF0MVtmb2xkID09IGldID0gcHJlZGljdChyZm16MSwgWFtmb2xkID09IGksXSkkcHJlZGljdGlvbnMNCg0KICByZmV6MCA9IHJlZ3Jlc3Npb25fZm9yZXN0KFhbZm9sZCAhPSBpICYgWj09MCxdLFdbZm9sZCAhPSBpICYgWj09MF0pDQogIGV6aGF0MFtmb2xkID09IGldID0gcHJlZGljdChyZmV6MCwgWFtmb2xkID09IGksXSkkcHJlZGljdGlvbnMNCg0KICByZmV6MSA9IHJlZ3Jlc3Npb25fZm9yZXN0KFhbZm9sZCAhPSBpICYgWj09MSxdLFdbZm9sZCAhPSBpICYgWj09MV0pDQogIGV6aGF0MVtmb2xkID09IGldID0gcHJlZGljdChyZmV6MSwgWFtmb2xkID09IGksXSkkcHJlZGljdGlvbnMNCn0NCmBgYA0KDQphbmQgZmluYWxseSB0aGUgZWFzeSAkZT1FW1ddJCBjb2RlZCBhcyBgZWhhdGANCg0KYGBge3J9DQplaGF0ID0gbWVhbihXKQ0KYGBgDQoNCjxicj4NCjxicj4NCg0KIyAkXHBzaV9hJCBhbmQgJFxwc2lfYiQgb2YgZWFjaCBzY29yZXMNCg0KTm93IHdlIGNvZGUgdXAgdGhlIHR3byBjb21wb25lbnRzIG9mIHRoZSBzY29yZSwgdGhhdCB3aWxsIGhlbHAgdXMgbGF0ZXIgdG8gZ2V0IHRoZSBwb2ludCBlc3RpbWF0ZSBhbmQgdGhlIHN0YW5kYXJkIGVycm9yIHVzaW5nIHRoZSBzYW1lIGZ1bmN0aW9uLg0KDQojIyBQTA0KDQpUaGUgZW1waXJpY2FsIHZlcnNpb24gb2YgdGhlIHNjb3JlIGxvb2tzIGxpa2UgdGhpczoNCiQkXHBzaV57UEx9KE87XGhhdHtcdGF1fSxcaGF0e1xldGF9KSA9IFxoYXR7XHRhdX0gXHVuZGVyYnJhY2V7KC0xKShXIC0gXGhhdHtlfShYKSleMn1fe1xwc2lfYV57UEx9fSArICBcdW5kZXJicmFjZXsgKFkgLSBcaGF0e219KFgpKSAoVyAtIFxoYXR7ZX0oWCkpfV97XHBzaV9iXntQTH19JCQNClRvIGZvbGxvdyB0aGUgcmVjaXBlLCB3ZSBuZWVkIHR3byBjb21wb25lbnRzOg0KDQoxLiAkXHBzaV9hXntQTH0kIHRvIGJlIGNvZGVkIGFzIGBwYV9wbGANCg0KMi4gJFxwc2lfYl57UEx9JCB0byBiZSBjb2RlZCBhcyBgcGJfcGxgDQoNCmBgYHtyfQ0KcGFfcGwgPSAtKFcgLSBleGhhdCleMg0KcGJfcGwgPSAoWSAtIG14aGF0KSAqIChXIC0gZXhoYXQpDQotc3VtKHBiX3BsKSAvIHN1bShwYV9wbCkNCmBgYA0KDQo8YnI+DQoNCiMjIEFURQ0KDQpUaGUgZW1waXJpY2FsIHZlcnNpb24gb2YgdGhlIHNjb3JlIGxvb2tzIGxpa2UgdGhpczoNCiQkXHBzaV57QVRFfShPO1xoYXR7XHRhdX1fe0FURX0sXGhhdHtcZXRhfSk9IFxoYXR7XHRhdX1fe0FURX0gXHVuZGVyYnJhY2V7KC0xKX1fe1xwc2lfYV57QVRFfX0gKyBcdW5kZXJicmFjZXtcaGF0e219KDEsWCkgIC1caGF0eyBtfSgwLFgpICArIFxmcmFje1cgKFkgLSBcaGF0e219KDEsWCkgKX17XGhhdHtlfShYKX0gLSBcZnJhY3soMS1XKSAoWSAtIFxoYXR7bX0oMCxYKSApfXsxLVxoYXR7ZX0oWCl9IH1fe1xwc2lfYl57QVRFfX0kJA0KVG8gZm9sbG93IHRoZSByZWNpcGUsIHdlIG5lZWQgdHdvIGNvbXBvbmVudHM6DQoNCjEuICRccHNpX2Fee0FURX0kIHRvIGJlIGNvZGVkIGFzIGBwYV9hdGVgDQoNCjIuICRccHNpX2Jee0FURX0kIHRvIGJlIGNvZGVkIGFzIGBwYl9hdGVgDQoNCg0KYGBge3J9DQpwYV9hdGUgPSByZXAoLTEsbGVuZ3RoKFkpKQ0KcGJfYXRlID0gbXdoYXQxIC0gbXdoYXQwICsgVyAqIChZIC0gbXdoYXQxKSAvIGVoYXQgLSAoMSAtIFcpICogKFkgLSBtd2hhdDApIC8gKDEtZWhhdCkNCi1zdW0ocGJfYXRlKSAvIHN1bShwYV9hdGUpDQpgYGANCg0KPGJyPg0KDQojIyBBVFQNCg0KVGhlIGVtcGlyaWNhbCB2ZXJzaW9uIG9mIHRoZSBzY29yZSBsb29rcyBsaWtlIHRoaXM6DQokJFxwc2lee0FUVH0oTztcaGF0e1x0YXV9X3tBVFR9LFxoYXR7XGV0YX0pPSBcaGF0e1x0YXV9X3tBVFR9IFx1bmRlcmJyYWNleygtMSlcZnJhY3tXfXtcaGF0e2V9fX1fe1xwc2lfYV57QVRUfX0gKyBcdW5kZXJicmFjZXtcZGZyYWN7V317XGhhdHtlfX0gKFkgLSBcaGF0e219KDAsWCkpIC0gIFxkZnJhY3sgKDEtVykgXGhhdHtlfShYKSAgfXtcaGF0e2V9ICgxLVxoYXR7ZX0oWCkpfSAoWSAtIFxoYXR7bX0oMCxYKSkgfV97XHBzaV9iXntBVFR9fSQkDQpUbyBmb2xsb3cgdGhlIHJlY2lwZSwgd2UgbmVlZCB0d28gY29tcG9uZW50czoNCg0KMS4gJFxwc2lfYV57QVRUfSQgdG8gYmUgY29kZWQgYXMgYHBhX2F0dGANCg0KMi4gJFxwc2lfYl57QVRUfSQgdG8gYmUgY29kZWQgYXMgYHBiX2F0dGANCg0KDQpgYGB7cn0NCnBhX2F0dCA9IC1XIC8gZWhhdA0KcGJfYXR0ID0gVyAqIChZIC0gbXdoYXQwKSAvIGVoYXQgLSAoICgxIC0gVykgKiBleGhhdCApICogKFkgLSBtd2hhdDApIC8gKGVoYXQgKiAoMSAtIGVoYXQpKQ0KLXN1bShwYl9hdHQpIC8gc3VtKHBhX2F0dCkNCmBgYA0KDQo8YnI+DQoNCg0KIyMgUEwtSVYNCg0KVGhlIGVtcGlyaWNhbCB2ZXJzaW9uIG9mIHRoZSBzY29yZSBsb29rcyBsaWtlIHRoaXM6DQokJFxwc2lee0lWfShPO1xoYXR7XHRhdX1fe0lWfSxcaGF0e1xldGF9KT0gXGhhdHtcdGF1fV97SVZ9IFx1bmRlcmJyYWNleygtMSkgKFcgLSBcaGF0e2V9KFgpKSAoWiAtIFxoYXR7aH0oWCkpfV97XHBzaV9hXntJVn19ICsgXHVuZGVyYnJhY2V7KFkgLSBcaGF0e219KFgpKSAoWiAtIFxoYXR7aH0oWCkpfV97XHBzaV9iXntJVn19JCQNClRvIGZvbGxvdyB0aGUgcmVjaXBlLCB3ZSBuZWVkIHR3byBjb21wb25lbnRzOg0KDQoxLiAkXHBzaV9hXntJVn0kIHRvIGJlIGNvZGVkIGFzIGBwYV9pdmANCg0KMi4gJFxwc2lfYl57SVZ9JCB0byBiZSBjb2RlZCBhcyBgcGJfaXZgDQoNCg0KYGBge3J9DQpwYV9pdiA9IC0oVyAtIGV4aGF0KSAqIChaIC0gaGhhdCkNCnBiX2l2ID0gKFkgLSBteGhhdCkgKiAoWiAtIGhoYXQpIA0KLXN1bShwYl9pdikgLyBzdW0ocGFfaXYpDQpgYGANCg0KPGJyPg0KDQoNCiMjIExBVEUNCg0KVGhlIGVtcGlyaWNhbCB2ZXJzaW9uIG9mIHRoZSBzY29yZSBsb29rcyBsaWtlIHRoaXM6DQokJA0KXGJlZ2lue2FsaWdufQ0KXHBzaV57TEFURX0oTztcaGF0e1x0YXV9X3tMQVRFfSxcaGF0e1xldGF9KSAmID0gXGhhdHtcdGF1fV97TEFURX0gXHVuZGVyYnJhY2V7ICgtMSkgXEJpZ2dbIFxoYXR7ZX0oMSxYKSAtIFxoYXR7ZX0oMCxYKSArIFxkZnJhY3taIChXIC0gXGhhdHtlfSgxLFgpKX17XGhhdHtofShYKX0gLSBcZGZyYWN7KDEtWikgKFcgLSBcaGF0e2h9KDAsWCkpfXsxLVxoYXR7aH0oWCl9IFxCaWdnXSB9X3tccHNpX2Fee0xBVEV9fSBcXA0KJiBccXVhZCArIFx1bmRlcmJyYWNle1xoYXR7bX1feigxLFgpIC0gXGhhdHttfV96KDAsWCkgKyBcZGZyYWN7WiAoWSAtIFxoYXR7bX1feigxLFgpKX17XGhhdHtofShYKX0gLSBcZGZyYWN7KDEtWikgKFkgLSBcaGF0e219X3ooMCxYKSl9ezEtXGhhdHtofShYKX19X3tccHNpX2Jee0xBVEV9fQ0KXGVuZHthbGlnbn0NCiQkDQpUbyBmb2xsb3cgdGhlIHJlY2lwZSwgd2UgbmVlZCB0d28gY29tcG9uZW50czoNCg0KMS4gJFxwc2lfYV57TEFURX0kIHRvIGJlIGNvZGVkIGFzIGBwYV9sYXRlYA0KDQoyLiAkXHBzaV9iXntMQVRFfSQgdG8gYmUgY29kZWQgYXMgYHBiX2xhdGVgDQoNCg0KYGBge3J9DQpwYV9sYXRlID0gLSggZXpoYXQxIC0gZXpoYXQwICsgWiAqIChXIC0gZXpoYXQxKSAvIGhoYXQgLSAoMSAtIFopICogKFcgLSBlemhhdDApIC8gKDEtaGhhdCkgKQ0KcGJfbGF0ZSA9IG16aGF0MSAtIG16aGF0MCArIFogKiAoWSAtIG16aGF0MSkgLyBoaGF0IC0gKDEgLSBaKSAqIChZIC0gbXpoYXQwKSAvICgxLWhoYXQpDQotc3VtKHBiX2xhdGUpIC8gc3VtKHBhX2xhdGUpDQpgYGANCg0KPGJyPg0KPGJyPg0KDQojIEEgZ2VuZXJpYyBmdW5jdGlvbiBmb3IgRG91YmxlIE1MIHdpdGggbGluZWFyIHNjb3JlDQoNCk5vdyB3ZSB3cml0ZSBhIGZ1bmN0aW9uIHRoYXQgdGFrZXMgYW55ICRccHNpX2EkIGFuZCAkXHBzaV9iJCBhcyBpbnB1dHMgYW5kIG91dHB1dHMNCg0KLSBwb2ludCBlc3RpbWF0ZSANCg0KLSBzdGFuZGFyZCBlcnJvcg0KDQotIHQtdmFsdWUNCg0KLSBwLXZhbHVlDQoNCg0KVGhlIGZ1bmN0aW9uIGltcGxlbWVudHMgdGhlIGZvbGxvd2luZyBzdGVwczoNCg0KMS4gQ2FsY3VsYXRlIHBvaW50IGVzdGltYXRlIGFzICRcaGF0e1x0aGV0YX0gPSAtXGZyYWN7XHN1bV9pXHBzaV9iKE9faTtcaGF0e1xldGF9X2kpfXtcc3VtX2kgXHBzaV9hKE9faTtcaGF0e1xldGF9X2kpfSAkDQoNCjIuICRcaGF0e1x0aGV0YX0kIGNhbiB0aGVuIGJlIHVzZWQgdG8gY29tcGxldGUgdGhlIGVtcGlyaWNhbCBzY29yZSBhcyAkXHBzaShPO1xoYXR7XHRoZXRhfSxcaGF0e1xldGF9KSA9IFxoYXR7XHRoZXRhfSBccHNpX2EoT19pO1xoYXR7XGV0YX1faSkgKyBccHNpX2IoT19pO1xoYXR7XGV0YX1faSkkDQoNCjMuIENyZWF0ZSBpbmZsdWVuY2UgZnVuY3Rpb24gJFxQc2koTztcaGF0e1x0aGV0YX0sXGhhdHtcZXRhfSkgPSAtIFxmcmFje1xwc2koTztcaGF0e1x0aGV0YX0sXGhhdHtcZXRhfSl9e05eey0xfSBcc3VtX2kgXHBzaV9hKE9faTtcaGF0e1xldGF9X2kpfSQNCg0KNC4gRXN0aW1hdGUgdGhlIHZhcmlhbmNlIGFzICRcaGF0e1xzaWdtYX1eMiA9IFZhcihcUHNpKE87XGhhdHtcdGhldGF9LFxoYXR7XGV0YX0pKSQgb3IgJFxoYXR7XHNpZ21hfV4yID0gXGZyYWN7Tl57LTF9IFxzdW1faSAgXHBzaShPX2k7XGhhdHtcdGhldGF9LFxoYXR7XGV0YX1faSleMn17W05eey0xfSBcc3VtX2kgIFxwc2lfYShPX2k7XGhhdHtcZXRhfV9pKV1eMn0kDQoNCjUuIEdldCB0aGUgc3RhbmRhcmQgZXJyb3IgYXMgJHNlKFxoYXR7XHRoZXRhfSkgPSBcc3FydHtcZnJhY3tcaGF0e1xzaWdtYX1eMn17Tn19JA0KDQpgYGB7cn0NCkRNTF9pbmZlcmVuY2UgPSBmdW5jdGlvbihwc2lfYSxwc2lfYikgew0KICBOID0gbGVuZ3RoKHBzaV9hKQ0KICB0aGV0YSA9IC1zdW0ocHNpX2IpIC8gc3VtKHBzaV9hKQ0KICBwc2kgPSB0aGV0YSAqIHBzaV9hICsgcHNpX2INCiAgUHNpID0gLSBwc2kgLyBtZWFuKHBzaV9hKQ0KICBzaWdtYTIgPSB2YXIoUHNpKQ0KICAjIHNpZ21hMiA9IG1lYW4ocHNpXjIpIC8gbWVhbihwc2lfYSleMg0KICBzZSA9IHNxcnQoc2lnbWEyIC8gTikNCiAgdCA9IHRoZXRhIC8gc2UNCiAgcCA9IDIgKiBwdChhYnModCksTixsb3dlciA9IEZBTFNFKQ0KICByZXN1bHQgPSBjKHRoZXRhLHNlLHQscCkNCiAgcmV0dXJuKHJlc3VsdCkNCn0NCmBgYA0KDQo8YnI+DQo8YnI+DQoNCg0KIyBSZXN1bHRzDQoNCmBgYHtyfQ0KcmVzdWx0cyA9IG1hdHJpeChOQSw1LDQpDQpyb3duYW1lcyhyZXN1bHRzKSA9IGMoIlBMIiwiQVRFIiwiQVRUIiwiUEwtSVYiLCJMQVRFIikNCmNvbG5hbWVzKHJlc3VsdHMpID0gYygiRWZmZWN0IiwiUy5FLiIsInQiLCJwIikNCnJlc3VsdHNbMSxdID0gRE1MX2luZmVyZW5jZShwYV9wbCxwYl9wbCkNCnJlc3VsdHNbMixdID0gRE1MX2luZmVyZW5jZShwYV9hdGUscGJfYXRlKQ0KcmVzdWx0c1szLF0gPSBETUxfaW5mZXJlbmNlKHBhX2F0dCxwYl9hdHQpDQpyZXN1bHRzWzQsXSA9IERNTF9pbmZlcmVuY2UocGFfaXYscGJfaXYpDQpyZXN1bHRzWzUsXSA9IERNTF9pbmZlcmVuY2UocGFfbGF0ZSxwYl9sYXRlKQ0KcHJpbnRDb2VmbWF0KHJlc3VsdHMsaGFzLlB2YWx1ZSA9IFRSVUUpDQpgYGANCg0KDQpgYGB7cn0NCmRhdGEuZnJhbWUodGhldGFzID0gcmVzdWx0c1ssMV0sc2VzID0gcmVzdWx0c1ssMl0sDQogICAgICAgICAgICAgICAgRXN0aW1hdG9yID0gcm93bmFtZXMocmVzdWx0cyksDQogICAgICAgICAgICAgICAgY2lsID0gcmVzdWx0c1ssMV0gLSAxLjk2KnJlc3VsdHNbLDJdLA0KICAgICAgICAgICAgICAgIGNpdSA9IHJlc3VsdHNbLDFdICsgMS45NipyZXN1bHRzWywyXSkgICU+JSANCiAgZ2dwbG90KGFlcyh4PUVzdGltYXRvcix5PXRoZXRhcyx5bWluPWNpbCx5bWF4PWNpdSkpICsgZ2VvbV9wb2ludChzaXplPTIuNSkgKyBnZW9tX2Vycm9yYmFyKHdpZHRoPTAuMTUpICArDQogIGdlb21faGxpbmUoeWludGVyY2VwdD0wKQ0KYGBgDQoNCg0KPGJyPg0KPGJyPg0KDQoNCiMgQVRUIHZzLiBBVEUNCg0KQXNzdW1lIHRoYXQgd2Ugd2FudCB0byB0ZXN0IHdoZXRoZXIgJEFUVD1BVEUkLg0KDQpGb3IgdGhpcyBwdXJwb3NlLCB3ZSBjcmVhdGUgdGhlIG5ldyBwYXJhbWV0ZXIgJFxEZWx0YV97QVRUfSA9IFx0YXVfe0FUVH0gLSBcdGF1X3tBVEV9JCBhbmQgd2FudCB0byB0ZXN0ICRIXzA6XERlbHRhKFx0YXVfe0FUVH0sXHRhdV97QVRFfSkgPSAwJA0KDQpUaGUgaW5mbHVlbmNlIGZ1bmN0aW9uIG9mIHRoaXMgbmV3IHBhcmFtZXRlciBjYW4gZGVyaXZlZCBsaWtlIHRoaXM6DQokJA0KXGJlZ2lue2FsaWduKn0NCiAgXFBzaV9cRGVsdGEgJiA9IFxvdmVyYnJhY2V7XGZyYWN7XGRlbHRhIFxEZWx0YX17XGRlbHRhIFx0YXVfe0FUVH19fV57PTF9IFxQc2lfe1x0YXVfe0FUVH19ICsgXG92ZXJicmFjZXtcZnJhY3tcZGVsdGEgXERlbHRhfXtcZGVsdGEgXHRhdV97QVRFfX19Xns9LTF9IFxQc2lfe1x0YXVfe0FURX19IFxcDQogICYgPSBcUHNpX3tcdGF1X3tBVFR9fSAtIFxQc2lfe1x0YXVfe0FURX19DQpcZW5ke2FsaWduKn0NCiQkDQoNCkRlZmluZSBmb3IgY29udmVuaWVuY2UgYW5kIGxhdGVyIHVzZSBhIGBQc2lfbWFrZXIoKWAgZnVuY3Rpb24gdGhhdCBjcmVhdGVzIHRoZSBpbmZsdWVuY2UgZnVuY3Rpb24gZm9yIGdlbmVyaWMgJFxwc2lfYSQgYW5kICRccHNpX2IkOg0KDQpgYGB7cn0NClBzaV9tYWtlciA9IGZ1bmN0aW9uKHBzaV9hLHBzaV9iKSB7DQogIHRoZXRhID0gLXN1bShwc2lfYikgLyBzdW0ocHNpX2EpDQogIHBzaSA9IHRoZXRhICogcHNpX2EgKyBwc2lfYg0KICBQc2kgPSAtIHBzaSAvIG1lYW4ocHNpX2EpDQogIHJldHVybihQc2kpDQp9DQpgYGANCg0KTm93IHRoaXMgY2FuIGJlIHVzZWQgdG8gdGVzdCAkSF8wOlxEZWx0YShcdGF1X3tBVFR9LFx0YXVfe0FURX0pID0gMCQ6DQoNCmBgYHtyfQ0KIyBDYWxjdWxhdGUgcGFyYW1ldGVycw0KYXR0ID0gLSBzdW0ocGJfYXR0KSAvIHN1bShwYV9hdHQpDQphdGUgPSAtIHN1bShwYl9hdGUpIC8gc3VtKHBhX2F0ZSkNCkRlbHRhX2F0dCA9IGF0dCAtIGF0ZQ0KIyBDcmVhdGUgaW5mbHVlbmNlIGZ1bmN0aW9uIGZvciBuZXcgcGFyYW1ldGVyDQpQc2lfRGVsdGFfYXR0ID0gUHNpX21ha2VyKHBhX2F0dCxwYl9hdHQpIC0gUHNpX21ha2VyKHBhX2F0ZSxwYl9hdGUpDQojIENhbGN1bGF0ZSBzdGFuZGFyZCBlcnJvcnMsIHQgYW5kIHB2YWx1ZXMNCnNlX0RlbHRhX2F0dCA9IHNxcnQodmFyKFBzaV9EZWx0YV9hdHQpL2xlbmd0aChQc2lfRGVsdGFfYXR0KSkNCnRfRGVsdGFfYXR0ID0gRGVsdGFfYXR0IC8gc2VfRGVsdGFfYXR0DQpwX0RlbHRhX2F0dCA9IDIgKiBwdChhYnModF9EZWx0YV9hdHQpLGxlbmd0aChQc2lfRGVsdGFfYXR0KSxsb3dlciA9IEZBTFNFKQ0KIyBQcmludCByZXN1bHRzDQpyZXN1bHQgPSBtYXRyaXgoYyhEZWx0YV9hdHQsc2VfRGVsdGFfYXR0LHRfRGVsdGFfYXR0LHBfRGVsdGFfYXR0KSxucm93ID0gMSkNCnJvd25hbWVzKHJlc3VsdCkgPSBjKCJBVFQtQVRFIikNCmNvbG5hbWVzKHJlc3VsdCkgPSBjKCJEZWx0YSIsIlMuRS4iLCJ0IiwicCIpDQpwcmludENvZWZtYXQocmVzdWx0LGhhcy5QdmFsdWUgPSBUUlVFKQ0KYGBgDQoNCldlIG9ic2VydmUgdGhhdCB0aGUgZGlmZmVyZW50IGJldHdlZW4gQVRUIGFuZCBBVEUgaXMgaGlnaGx5IHNpZ25pZmljYW50LiBUaGlzIG1heSBjb21lIGFzIGEgc3VycHJpc2UgZ2l2ZW4gdGhhdCB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbHMgb3ZlcmxhcCBpbiB0aGUgZ3JhcGggYWJvdmUuIEhvd2V2ZXIsIHN1Y2ggZXllYmFsbGluZyBkb2VzIG5vdCB3b3JrIGZvciBjb3JyZWxhdGVkIGVzdGltYXRvcnMgYW5kIHRoZSBBVFQgYW5kIEFURSBlc3RpbWF0b3IgYXJlIGNsZWFybHkgY29ycmVsYXRlZC4NCg0KPGJyPg0KPGJyPg0KDQoNCiMgTEFURSB2cy4gQVRFDQoNCg0KU2ltaWxhcmx5LCB3ZSBjYW4gaW52ZXN0aWdhdGUgJEhfMDpcRGVsdGFfe0xBVEV9ID0gXHRhdV97TEFURX0gLSBcdGF1X3tBVEV9ID0gMCQuICgwLjVQKQ0KDQpUaGUgZGVyaXZhdGlvbiB3b3JrcyBsaWtlIGJlZm9yZToNCg0KJCRcYmVnaW57YWxpZ259DQpcUHNpX3tcRGVsdGFfe0xBVEV9fSAmPSBcdW5kZXJicmFjZXtcZnJhY3tccGFydGlhbCBcRGVsdGFfe0xBVEV9fXtccGFydGlhbCBcdGF1X3tMQVRFfX19X3s9MX0gXFBzaV97TEFURX0gKyBcdW5kZXJicmFjZXtcZnJhY3tccGFydGlhbCBcRGVsdGFfe0xBVEV9fXtccGFydGlhbCBcdGF1X3tBVEV9fX1fez0tMX0gXFBzaV97QVRFfSBcXA0KXFBzaV97XERlbHRhX3tMQVRFfX0gJj1cUHNpX3tMQVRFfSAtIFxQc2lfe0FURX0NClxlbmR7YWxpZ259JCQNCg0KYW5kIGFsc28gdGhlIGluZmVyZW5jZSBmb2xsb3dzIHRoZSBzYW1lIHJlY2lwZToNCg0KYGBge3J9DQojIENhbGN1TEFURQ0KbGF0ZSA9IC1zdW0ocGJfbGF0ZSkvc3VtKHBhX2xhdGUpIA0KIyBOZXcgdGFyZ2V0IHBhcmFtZXRlcg0KRGVsdGFfbGF0ZSA9IGxhdGUgLSBhdGUgDQojIENyZWF0ZSBpbmZsdWVuY2UgZnVuY3Rpb24gZm9yIG5ldyBwYXJhbWV0ZXINClBzaV9EZWx0YV9sYXRlID0gUHNpX21ha2VyKHBhX2xhdGUscGJfbGF0ZSkgLSBQc2lfbWFrZXIocGFfYXRlLHBiX2F0ZSkNCiMgUHJpbnQgcmVzdWx0cw0Kc2VfRGVsdGFfbGF0ZSA9IHNxcnQodmFyKFBzaV9EZWx0YV9sYXRlKS9sZW5ndGgoUHNpX0RlbHRhX2xhdGUpKQ0KdF9EZWx0YV9sYXRlID0gRGVsdGFfbGF0ZS9zZV9EZWx0YV9sYXRlDQpwX0RlbHRhX2xhdGUgPSAyICogcHQoYWJzKHRfRGVsdGFfbGF0ZSksbGVuZ3RoKFBzaV9EZWx0YV9sYXRlKSxsb3dlciA9IEZBTFNFKSAjIGdldCBhIHAtdmFsdWUgKGF0IHdoYXQgbGV2ZWwgd291bGQgYmUgbm90IHJlamVjdD8pDQoNCnJlc3VsdHMgPSBtYXRyaXgoYyhEZWx0YV9sYXRlLHNlX0RlbHRhX2xhdGUsdF9EZWx0YV9sYXRlLHBfRGVsdGFfbGF0ZSksbnJvdyA9IDEpDQpyb3duYW1lcyhyZXN1bHRzKSA9IGMoIkxBVEUtQVRFIikNCmNvbG5hbWVzKHJlc3VsdHMpID0gYygiRGVsdGEiLCJTLkUuIiwidCIsInAiKQ0KcHJpbnRDb2VmbWF0KHJlc3VsdHMsaGFzLlB2YWx1ZSA9IFRSVUUpDQpgYGANCg0KSG93ZXZlciwgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBMQVRFIGFuZCBBVEUgaXMgbm90IHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQuDQoNCjxicj4NCjxicj4NCg0KIyBIb3cgbGFyZ2UgaXMgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBBVFQgYW5kIEFURSBpbiAlPw0KDQpGaW5hbGx5LCBsZXQncyBsb29rIGF0IGEgbW9yZSBjb21wbGljYXRlZCBuZXcgcGFyYW1ldGVyICRcRGVsdGFcJSA9IFxmcmFje1x0YXVfe0FUVH0gLSBcdGF1X3tBVEV9fXtcdGF1X3tBVEV9fVx0aW1lcyAxMDAkLiANCg0KRm9sbG93aW5nIHRoZSByZWNpcGUsIHdlIGRlcml2ZSB0aGUgbmV3IGluZmx1ZW5jZSBmdW5jdGlvbjoNCg0KJCRcYmVnaW57YWxpZ259DQpcUHNpX3tcRGVsdGFcJX0gJj0gXGZyYWN7XHBhcnRpYWwgXERlbHRhXCV9e1xwYXJ0aWFsIFx0YXVfe0FUVH19IFxQc2lfe0FUVH0gKyBcZnJhY3tccGFydGlhbCBcRGVsdGFcJX17XHBhcnRpYWwgXHRhdV97QVRFfX0gXFBzaV97QVRFfSBcXA0KXFBzaV97XERlbHRhXCV9ICY9MTAwIFxjZG90IFxmcmFjezF9e1x0YXVfe0FURX19IFxQc2lfe0FUVH0gLTEwMCBcY2RvdCBcZnJhY3tcdGF1X3tBVFR9fXtcdGF1X3tBVEV9XjJ9IFxQc2lfe0FURX0NClxlbmR7YWxpZ259JCQNCg0KJFxQc2lfe0FUVH0kIGFuZCAkXFBzaV97QVRFfSQgY2FuIGJlIG9idGFpbmVkIHVzaW5nIGBQc2lfbWFrZXJgLiAkXHRhdV97QVRFfSQgYW5kICRcdGF1X3tBVFR9JCBjYW4gYmUgcmVwbGFjZWQgYnkgdGhlaXIgZXN0aW1hdGVzOg0KDQpgYGB7cn0NCiMgTmV3IHBhcmFtZXRlcg0KRGVsdGFfcGMgPSAoYXR0LWF0ZSkvYXRlKjEwMCANCiMgTmV3IElGDQpQc2lfRGVsdGFfcGMgPSAxMDAgLyBhdGUgKiBQc2lfbWFrZXIocGFfYXR0LCBwYl9hdHQpIC0gMTAwICogYXR0IC8gKGF0ZV4yKSAqIFBzaV9tYWtlcihwYV9hdGUsIHBiX2F0ZSkNCiMgUmVzdWx0cw0Kc2VfRGVsdGFfcGMgPSBzcXJ0KHZhcihQc2lfRGVsdGFfcGMpL2xlbmd0aChQc2lfRGVsdGFfcGMpKQ0KdF9EZWx0YV9wYyA9IERlbHRhX3BjL3NlX0RlbHRhX3BjDQpwX0RlbHRhX3BjID0gMiAqIHB0KGFicyh0X0RlbHRhX3BjKSxsZW5ndGgoUHNpX0RlbHRhX3BjKSxsb3dlciA9IEZBTFNFKQ0KcmVzdWx0cyA9IG1hdHJpeChjKERlbHRhX3BjLHNlX0RlbHRhX3BjLHRfRGVsdGFfcGMscF9EZWx0YV9wYyksbnJvdyA9IDEpDQpyb3duYW1lcyhyZXN1bHRzKSA9IGMoIkRlbHRhJSIpDQpjb2xuYW1lcyhyZXN1bHRzKSA9IGMoIkRlbHRhIiwiUy5FLiIsInQiLCJwIikNCnByaW50Q29lZm1hdChyZXN1bHRzLGhhcy5QdmFsdWUgPSBUUlVFKQ0KYGBgDQoNCk5vdCBzdXJwcmlzaW5nbHkgdGhlIHBlcmNlbnRhZ2UgY2hhbmdlIGlzIHNpZ25pZmljYW50LCBsaWtlIHRoZSBwbGFpbiBsZXZlbCBkaWZmZXJlbmNlIGJldHdlZW4gQVRUIGFuZCBBVEUuIEhvd2V2ZXIsIHRoZSB0LXZhbHVlcyBhcmUgbm90IGlkZW50aWNhbCwgd2hpY2ggaWxsdXN0cmF0ZXMgdGhhdCB3ZSBleHBsaWNpdGx5IGNhbGN1bGF0ZSB0aGUgaW5mZXJlbmNlIGZvciBzdWNoIG5ldyBwYXJhbWV0ZXJzLiBBbmQgaW5mbHVlbmNlIGZ1bmN0aW9ucyBwcm92aWRlIGEgbmVhdCB0b29sIHRvIGRvIHNvLg0KDQo8YnI+DQo=