Goals:
Estimate subgroup effects
Estimate best linear predictor of heterogeneity
Estimate nonparametric heterogeneity with kernel and spline
regression
Introducing the data
The Application Notebook builds on the dataset that is kindly
provided in 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 relatively few covariates
ensure that the programs do not run too long.
# To install the causalDML package uncomment the following two lines
# library(devtools)
# install_github(repo="MCKnaus/causalDML")
# Load the packages required for later
library(hdm)
library(tidyverse)
library(causalDML)
library(grf)
library(estimatr)
set.seed(1234) # for replicability
options(scipen = 10) # Switch off scientific notation
data(pension)
# Outcome
Y = pension$net_tfa
# Treatment
W = pension$p401
# Create main effects matrix
X = model.matrix(~ 0 + age + db + educ + fsize + hown + inc + male + marr + pira + twoearn, data = pension)
Double ML for AIPW with causalDML
package
Like previous notebooks, we create the pseudo-outcome \[\tilde{Y}_{ATE} = \underbrace{\hat{m}(1,X) -
\hat{m}(0,X)}_{\text{outcome predictions}} + \underbrace{\frac{W (Y -
\hat{m}(1,X))}{\hat{e}(X)} - \frac{(1-W) (Y -
\hat{m}(0,X))}{1-\hat{e}(X)}}_{\text{weighted residuals}}\]
by running the DML_aipw
function:
# 5-fold cross-fitting with causalDML package
aipw = DML_aipw(Y,W,X)
summary(aipw$ATE)
ATE SE t p
1 - 0 11288.4 1166.9 9.6736 < 2.2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
# If you have more time, tune the forest
# forest = create_method("forest_grf",args=list(tune.parameters = "all"))
# aipw = DML_aipw(Y,W,X,ml_w=list(forest),ml_y=list(forest),cf=5)
GATE estimation
The pseudo-outcome can now be used to estimate different
heterogeneous effects. We use standard regression models but by using
the pseudo-outcome instead of a real outcome we model effect size and
not outcome level.
Subgroup effect
First, let’s check a classic. Gender differences. This would usually
be implemented by splitting the sample by gender and rerunning the whole
analysis in the subsamples separately.
With the pseudo-outcome stored in aipw$ATE$delta
this
boils down to running an OLS regression with the male
indicator as single regressor.
male = X[,7]
blp_male = lm_robust(aipw$ATE$delta ~ male)
summary(blp_male)
Call:
lm_robust(formula = aipw$ATE$delta ~ male)
Standard error type: HC2
Coefficients:
Estimate Std. Error t value Pr(>|t|) CI Lower CI Upper DF
(Intercept) 10691 1303 8.2076 2.537e-16 8138 13245 9913
male 2899 2929 0.9895 3.224e-01 -2843 8641 9913
Multiple R-squared: 0.0001018 , Adjusted R-squared: 8.978e-07
F-statistic: 0.9791 on 1 and 9913 DF, p-value: 0.3224
We can interpret this outcome as we are used to, only that we model
now effect size. This means the intercept gives us the average of the
reference group (women) and the coefficient tells us how much higher the
effect is for men. In this case, we find no significant gender
differences in the effect of 401(k) participation on net wealth.
If you are interested in the gender specific effect instead of
differences between groups, just run an OLS regression without constant
and all group indicators:
female = 1-male
blp_male1 = lm_robust(aipw$ATE$delta ~ 0 + female + male)
summary(blp_male1)
Call:
lm_robust(formula = aipw$ATE$delta ~ 0 + female + male)
Standard error type: HC2
Coefficients:
Estimate Std. Error t value Pr(>|t|) CI Lower CI Upper DF
female 10691 1303 8.208 2.537e-16 8138 13245 9913
male 13590 2624 5.180 2.267e-07 8447 18733 9913
Multiple R-squared: 0.009451 , Adjusted R-squared: 0.009251
F-statistic: 47.1 on 2 and 9913 DF, p-value: < 2.2e-16
You see that we can transfer all the strategies that we know about
modeling outcomes with OLS for modelling causal effects.
Best linear prediction
Maybe we do not want to focus on subgroup analyses but to model the
effect using all main effects at our disposal. In standard OLS this
would mean to include a lot of interaction effects while completely
relying on correct specification of the outcome model.
Using the pseudo-outcome allows us to be completely agnostic about
the outcome model and to receive a nice summary of the underlying effect
heterogeneity in a familiar format, an OLS output:
blp = lm_robust(aipw$ATE$delta ~ X)
summary(blp)
Call:
lm_robust(formula = aipw$ATE$delta ~ X)
Standard error type: HC2
Coefficients:
Estimate Std. Error t value Pr(>|t|) CI Lower CI Upper DF
(Intercept) -16173.07643 8508.1126 -1.9009 0.057344 -32850.7088 504.5559 9904
Xage 302.55404 105.7100 2.8621 0.004217 95.3409 509.7672 9904
Xdb 5655.01827 2873.3302 1.9681 0.049084 22.7063 11287.3302 9904
Xeduc 438.08990 614.0359 0.7135 0.475578 -765.5455 1641.7253 9904
Xfsize 121.76803 803.8349 0.1515 0.879597 -1453.9119 1697.4480 9904
Xhown 5737.88595 2162.7697 2.6530 0.007990 1498.4171 9977.3548 9904
Xinc 0.04715 0.1977 0.2385 0.811532 -0.3404 0.4347 9904
Xmale 5347.95842 3097.7852 1.7264 0.084310 -724.3310 11420.2479 9904
Xmarr 1277.05730 3572.4932 0.3575 0.720748 -5725.7565 8279.8711 9904
Xpira -1316.68641 3773.1158 -0.3490 0.727123 -8712.7613 6079.3885 9904
Xtwoearn 1097.38011 4751.4033 0.2310 0.817351 -8216.3374 10411.0976 9904
Multiple R-squared: 0.003032 , Adjusted R-squared: 0.002025
F-statistic: 5.272 on 10 and 9904 DF, p-value: 0.00000008826
For example, we see that, all other regressors held constant, being
one year older increases the effect of 401(k) participation of wealth on
average by $ 303.
Again we realize that everything we learned about OLS for modelling
outcomes directly translates to modeling effect sizes.
Non-parametric heterogeneity
The imho coolest thing about having the pseudo-outcome is that we can
also estimate heterogeneous effects with nonparametric regressions. This
means we are not only agnostic about the outcome and propensity score
models but also about the functional of effect heterogeneity.
This is especially useful if we have some continuous variable like
age for which we want to understand effect heterogeneity.
Spline regression
The spline_cate
function implements spline regression
reusing the pseudo-outcome:
age = X[,1]
sr_age = spline_cate(aipw$ATE$delta,age)
plot(sr_age,z_label = "Age")
Kernel regression
The kr_cate
function implements kernel regression
reusing the pseudo-outcome:
kr_age = kr_cate(aipw$ATE$delta,age)
plot(kr_age,z_label = "Age")
Both nonparametric approaches document the same pattern. The effect
of 401(k) participation on net wealth increases more or less linearly
until the age of 50 and then slightly drops.
For me this is an incredible new option for our policy evaluation
toolbox.
LS0tDQp0aXRsZTogIkNhdXNhbCBNTDogRG91YmxlIE1MIGZvciBncm91cCBhdmVyYWdlIHRyZWF0bWVudCBlZmZlY3RzIg0Kc3VidGl0bGU6ICJBcHBsaWNhdGlvbiBub3RlYm9vayINCmF1dGhvcjogIk1pY2hhZWwgS25hdXMiDQpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclbS8leScpYCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQotLS0NCg0KDQpHb2FsczoNCg0KLSBFc3RpbWF0ZSBzdWJncm91cCBlZmZlY3RzDQoNCi0gRXN0aW1hdGUgYmVzdCBsaW5lYXIgcHJlZGljdG9yIG9mIGhldGVyb2dlbmVpdHkgDQoNCi0gRXN0aW1hdGUgbm9ucGFyYW1ldHJpYyBoZXRlcm9nZW5laXR5IHdpdGgga2VybmVsIGFuZCBzcGxpbmUgcmVncmVzc2lvbg0KDQo8YnI+DQoNCiMgSW50cm9kdWNpbmcgdGhlIGRhdGENCg0KVGhlIEFwcGxpY2F0aW9uIE5vdGVib29rIGJ1aWxkcyBvbiB0aGUgZGF0YXNldCB0aGF0IGlzIGtpbmRseSBwcm92aWRlZCBpbiB0aGUgYGhkbWAgcGFja2FnZS4gVGhlIGRhdGEgd2FzIHVzZWQgaW4gW0NoZXJub3podWtvdiBhbmQgSGFuc2VuICgyMDA0KV0oaHR0cHM6Ly9kaXJlY3QubWl0LmVkdS9yZXN0L2FydGljbGUvODYvMy83MzUvNTc1ODYvVGhlLUVmZmVjdHMtb2YtNDAxLUstUGFydGljaXBhdGlvbi1vbi10aGUtV2VhbHRoKS4gVGhlaXIgcGFwZXIgaW52ZXN0aWdhdGVzIHRoZSBlZmZlY3Qgb2YgcGFydGljaXBhdGlvbiBpbiB0aGUgZW1wbG95ZXItc3BvbnNvcmVkIDQwMShrKSByZXRpcmVtZW50IHNhdmluZ3MgcGxhbiAoYHA0MDFgKSBvbiBuZXQgYXNzZXRzIChgbmV0X3RmYWApLiBTaW5jZSB0aGVuIHRoZSBkYXRhIHdhcyB1c2VkIHRvIHNob3djYXNlIG1hbnkgbmV3IG1ldGhvZHMuIEl0IGlzIG5vdCB0aGUgbW9zdCBjb21wcmVoZW5zaXZlIGRhdGFzZXQgd2l0aCBiYXNpY2FsbHkgdGVuIGNvdmFyaWF0ZXMvcmVncmVzc29ycy9wcmVkaWN0b3JzOg0KDQotICphZ2UqOiBhZ2UNCg0KLSAqZGIqOiBkZWZpbmVkIGJlbmVmaXQgcGVuc2lvbg0KDQotICplZHVjKjogZWR1Y2F0aW9uIChpbiB5ZWFycykNCg0KLSAqZnNpemUqOiBmYW1pbHkgc2l6ZQ0KDQotICpob3duKjogaG9tZSBvd25lcg0KDQotICppbmMqOiBpbmNvbWUgKGluIFVTICQpDQoNCi0gKm1hbGUqOiBtYWxlDQoNCi0gKm1hcnIqOiBtYXJyaWVkDQoNCi0gKnBpcmEqOiBwYXJ0aWNpcGF0aW9uIGluIGluZGl2aWR1YWwgcmV0aXJlbWVudCBhY2NvdW50IChJUkEpDQoNCi0gKnR3b2Vhcm4qOiB0d28gZWFybmVycw0KDQpIb3dldmVyLCBpdCBpcyBwdWJsaWNseSBhdmFpbGFibGUgYW5kIHRoZSByZWxhdGl2ZWx5IGZldyBjb3ZhcmlhdGVzIGVuc3VyZSB0aGF0IHRoZSBwcm9ncmFtcyBkbyBub3QgcnVuIHRvbyBsb25nLg0KDQpgYGB7ciwgd2FybmluZyA9IEYsIG1lc3NhZ2UgPSBGfQ0KIyBUbyBpbnN0YWxsIHRoZSBjYXVzYWxETUwgcGFja2FnZSB1bmNvbW1lbnQgdGhlIGZvbGxvd2luZyB0d28gbGluZXMNCiMgbGlicmFyeShkZXZ0b29scykNCiMgaW5zdGFsbF9naXRodWIocmVwbz0iTUNLbmF1cy9jYXVzYWxETUwiKQ0KDQojIExvYWQgdGhlIHBhY2thZ2VzIHJlcXVpcmVkIGZvciBsYXRlcg0KbGlicmFyeShoZG0pDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoY2F1c2FsRE1MKQ0KbGlicmFyeShncmYpDQpsaWJyYXJ5KGVzdGltYXRyKQ0KDQoNCnNldC5zZWVkKDEyMzQpICMgZm9yIHJlcGxpY2FiaWxpdHkNCm9wdGlvbnMoc2NpcGVuID0gMTApICMgU3dpdGNoIG9mZiBzY2llbnRpZmljIG5vdGF0aW9uDQoNCmRhdGEocGVuc2lvbikNCiMgT3V0Y29tZQ0KWSA9IHBlbnNpb24kbmV0X3RmYQ0KIyBUcmVhdG1lbnQNClcgPSBwZW5zaW9uJHA0MDENCiMgQ3JlYXRlIG1haW4gZWZmZWN0cyBtYXRyaXgNClggPSBtb2RlbC5tYXRyaXgofiAwICsgYWdlICsgZGIgKyBlZHVjICsgZnNpemUgKyBob3duICsgaW5jICsgbWFsZSArIG1hcnIgKyBwaXJhICsgdHdvZWFybiwgZGF0YSA9IHBlbnNpb24pDQpgYGANCg0KPGJyPg0KPGJyPg0KDQojIERvdWJsZSBNTCBmb3IgQUlQVyB3aXRoIGBjYXVzYWxETUxgIHBhY2thZ2UNCg0KTGlrZSBwcmV2aW91cyBub3RlYm9va3MsIHdlIGNyZWF0ZSB0aGUgcHNldWRvLW91dGNvbWUNCiQkXHRpbGRle1l9X3tBVEV9ID0gXHVuZGVyYnJhY2V7XGhhdHttfSgxLFgpIC0gXGhhdHttfSgwLFgpfV97XHRleHR7b3V0Y29tZSBwcmVkaWN0aW9uc319ICsgXHVuZGVyYnJhY2V7XGZyYWN7VyAoWSAtIFxoYXR7bX0oMSxYKSl9e1xoYXR7ZX0oWCl9IC0gXGZyYWN7KDEtVykgKFkgLSBcaGF0e219KDAsWCkpfXsxLVxoYXR7ZX0oWCl9fV97XHRleHR7d2VpZ2h0ZWQgcmVzaWR1YWxzfX0kJA0KDQpieSBydW5uaW5nIHRoZSBgRE1MX2FpcHdgIGZ1bmN0aW9uOg0KDQpgYGB7cn0NCiMgNS1mb2xkIGNyb3NzLWZpdHRpbmcgd2l0aCBjYXVzYWxETUwgcGFja2FnZQ0KYWlwdyA9IERNTF9haXB3KFksVyxYKQ0Kc3VtbWFyeShhaXB3JEFURSkNCg0KIyBJZiB5b3UgaGF2ZSBtb3JlIHRpbWUsIHR1bmUgdGhlIGZvcmVzdA0KIyBmb3Jlc3QgPSBjcmVhdGVfbWV0aG9kKCJmb3Jlc3RfZ3JmIixhcmdzPWxpc3QodHVuZS5wYXJhbWV0ZXJzID0gImFsbCIpKQ0KIyBhaXB3ID0gRE1MX2FpcHcoWSxXLFgsbWxfdz1saXN0KGZvcmVzdCksbWxfeT1saXN0KGZvcmVzdCksY2Y9NSkNCmBgYA0KDQo8YnI+DQo8YnI+DQoNCg0KIyBHQVRFIGVzdGltYXRpb24NCg0KVGhlIHBzZXVkby1vdXRjb21lIGNhbiBub3cgYmUgdXNlZCB0byBlc3RpbWF0ZSBkaWZmZXJlbnQgaGV0ZXJvZ2VuZW91cyBlZmZlY3RzLiBXZSB1c2Ugc3RhbmRhcmQgcmVncmVzc2lvbiBtb2RlbHMgYnV0IGJ5IHVzaW5nIHRoZSBwc2V1ZG8tb3V0Y29tZSBpbnN0ZWFkIG9mIGEgcmVhbCBvdXRjb21lIHdlIG1vZGVsIGVmZmVjdCBzaXplIGFuZCBub3Qgb3V0Y29tZSBsZXZlbC4NCg0KIyMgU3ViZ3JvdXAgZWZmZWN0DQoNCkZpcnN0LCBsZXQncyBjaGVjayBhIGNsYXNzaWMuIEdlbmRlciBkaWZmZXJlbmNlcy4gVGhpcyB3b3VsZCB1c3VhbGx5IGJlIGltcGxlbWVudGVkIGJ5IHNwbGl0dGluZyB0aGUgc2FtcGxlIGJ5IGdlbmRlciBhbmQgcmVydW5uaW5nIHRoZSB3aG9sZSBhbmFseXNpcyBpbiB0aGUgc3Vic2FtcGxlcyBzZXBhcmF0ZWx5Lg0KDQpXaXRoIHRoZSBwc2V1ZG8tb3V0Y29tZSBzdG9yZWQgaW4gYGFpcHckQVRFJGRlbHRhYCB0aGlzIGJvaWxzIGRvd24gdG8gcnVubmluZyBhbiBPTFMgcmVncmVzc2lvbiB3aXRoIHRoZSBgbWFsZWAgaW5kaWNhdG9yIGFzIHNpbmdsZSByZWdyZXNzb3IuDQoNCmBgYHtyfQ0KbWFsZSA9IFhbLDddDQpibHBfbWFsZSA9IGxtX3JvYnVzdChhaXB3JEFURSRkZWx0YSB+IG1hbGUpDQpzdW1tYXJ5KGJscF9tYWxlKQ0KYGBgDQoNCldlIGNhbiBpbnRlcnByZXQgdGhpcyBvdXRjb21lIGFzIHdlIGFyZSB1c2VkIHRvLCBvbmx5IHRoYXQgd2UgbW9kZWwgbm93IGVmZmVjdCBzaXplLiBUaGlzIG1lYW5zIHRoZSBpbnRlcmNlcHQgZ2l2ZXMgdXMgdGhlIGF2ZXJhZ2Ugb2YgdGhlIHJlZmVyZW5jZSBncm91cCAod29tZW4pIGFuZCB0aGUgY29lZmZpY2llbnQgdGVsbHMgdXMgaG93IG11Y2ggaGlnaGVyIHRoZSBlZmZlY3QgaXMgZm9yIG1lbi4gSW4gdGhpcyBjYXNlLCB3ZSBmaW5kIG5vIHNpZ25pZmljYW50IGdlbmRlciBkaWZmZXJlbmNlcyBpbiB0aGUgZWZmZWN0IG9mIDQwMShrKSBwYXJ0aWNpcGF0aW9uIG9uIG5ldCB3ZWFsdGguDQoNCklmIHlvdSBhcmUgaW50ZXJlc3RlZCBpbiB0aGUgZ2VuZGVyIHNwZWNpZmljIGVmZmVjdCBpbnN0ZWFkIG9mIGRpZmZlcmVuY2VzIGJldHdlZW4gZ3JvdXBzLCBqdXN0IHJ1biBhbiBPTFMgcmVncmVzc2lvbiB3aXRob3V0IGNvbnN0YW50IGFuZCBhbGwgZ3JvdXAgaW5kaWNhdG9yczoNCg0KYGBge3J9DQpmZW1hbGUgPSAxLW1hbGUNCmJscF9tYWxlMSA9IGxtX3JvYnVzdChhaXB3JEFURSRkZWx0YSB+IDAgKyBmZW1hbGUgKyBtYWxlKQ0Kc3VtbWFyeShibHBfbWFsZTEpDQpgYGANCg0KWW91IHNlZSB0aGF0IHdlIGNhbiB0cmFuc2ZlciBhbGwgdGhlIHN0cmF0ZWdpZXMgdGhhdCB3ZSBrbm93IGFib3V0IG1vZGVsaW5nIG91dGNvbWVzIHdpdGggT0xTIGZvciBtb2RlbGxpbmcgY2F1c2FsIGVmZmVjdHMuDQoNCjxicj4NCg0KIyMgQmVzdCBsaW5lYXIgcHJlZGljdGlvbg0KDQpNYXliZSB3ZSBkbyBub3Qgd2FudCB0byBmb2N1cyBvbiBzdWJncm91cCBhbmFseXNlcyBidXQgdG8gbW9kZWwgdGhlIGVmZmVjdCB1c2luZyBhbGwgbWFpbiBlZmZlY3RzIGF0IG91ciBkaXNwb3NhbC4gSW4gc3RhbmRhcmQgT0xTIHRoaXMgd291bGQgbWVhbiB0byBpbmNsdWRlIGEgbG90IG9mIGludGVyYWN0aW9uIGVmZmVjdHMgd2hpbGUgY29tcGxldGVseSByZWx5aW5nIG9uIGNvcnJlY3Qgc3BlY2lmaWNhdGlvbiBvZiB0aGUgb3V0Y29tZSBtb2RlbC4NCg0KVXNpbmcgdGhlIHBzZXVkby1vdXRjb21lIGFsbG93cyB1cyB0byBiZSBjb21wbGV0ZWx5IGFnbm9zdGljIGFib3V0IHRoZSBvdXRjb21lIG1vZGVsIGFuZCB0byByZWNlaXZlIGEgbmljZSBzdW1tYXJ5IG9mIHRoZSB1bmRlcmx5aW5nIGVmZmVjdCBoZXRlcm9nZW5laXR5IGluIGEgZmFtaWxpYXIgZm9ybWF0LCBhbiBPTFMgb3V0cHV0Og0KDQpgYGB7cn0NCmJscCA9IGxtX3JvYnVzdChhaXB3JEFURSRkZWx0YSB+IFgpDQpzdW1tYXJ5KGJscCkNCmBgYA0KDQpGb3IgZXhhbXBsZSwgd2Ugc2VlIHRoYXQsIGFsbCBvdGhlciByZWdyZXNzb3JzIGhlbGQgY29uc3RhbnQsIGJlaW5nIG9uZSB5ZWFyIG9sZGVyIGluY3JlYXNlcyB0aGUgZWZmZWN0IG9mIDQwMShrKSBwYXJ0aWNpcGF0aW9uIG9mIHdlYWx0aCBvbiBhdmVyYWdlIGJ5ICQgYHIgcm91bmQoYmxwJGNvZWZmaWNpZW50c1syXSlgLg0KDQpBZ2FpbiB3ZSByZWFsaXplIHRoYXQgZXZlcnl0aGluZyB3ZSBsZWFybmVkIGFib3V0IE9MUyBmb3IgbW9kZWxsaW5nIG91dGNvbWVzIGRpcmVjdGx5IHRyYW5zbGF0ZXMgdG8gbW9kZWxpbmcgZWZmZWN0IHNpemVzLg0KDQoNCjxicj4NCg0KDQojIyBOb24tcGFyYW1ldHJpYyBoZXRlcm9nZW5laXR5DQoNClRoZSBpbWhvIGNvb2xlc3QgdGhpbmcgYWJvdXQgaGF2aW5nIHRoZSBwc2V1ZG8tb3V0Y29tZSBpcyB0aGF0IHdlIGNhbiBhbHNvIGVzdGltYXRlIGhldGVyb2dlbmVvdXMgZWZmZWN0cyB3aXRoIG5vbnBhcmFtZXRyaWMgcmVncmVzc2lvbnMuIFRoaXMgbWVhbnMgd2UgYXJlIG5vdCBvbmx5IGFnbm9zdGljIGFib3V0IHRoZSBvdXRjb21lIGFuZCBwcm9wZW5zaXR5IHNjb3JlIG1vZGVscyBidXQgYWxzbyBhYm91dCB0aGUgZnVuY3Rpb25hbCBvZiBlZmZlY3QgaGV0ZXJvZ2VuZWl0eS4gDQoNClRoaXMgaXMgZXNwZWNpYWxseSB1c2VmdWwgaWYgd2UgaGF2ZSBzb21lIGNvbnRpbnVvdXMgdmFyaWFibGUgbGlrZSBhZ2UgZm9yIHdoaWNoIHdlIHdhbnQgdG8gdW5kZXJzdGFuZCBlZmZlY3QgaGV0ZXJvZ2VuZWl0eS4NCg0KPGJyPg0KDQojIyMgU3BsaW5lIHJlZ3Jlc3Npb24NCg0KVGhlIGBzcGxpbmVfY2F0ZWAgZnVuY3Rpb24gaW1wbGVtZW50cyBzcGxpbmUgcmVncmVzc2lvbiByZXVzaW5nIHRoZSBwc2V1ZG8tb3V0Y29tZToNCg0KYGBge3IsIHJlc3VsdHM9J2hpZGUnfQ0KYWdlID0gWFssMV0NCnNyX2FnZSA9IHNwbGluZV9jYXRlKGFpcHckQVRFJGRlbHRhLGFnZSkNCmBgYA0KDQpgYGB7cn0NCnBsb3Qoc3JfYWdlLHpfbGFiZWwgPSAiQWdlIikNCmBgYA0KDQo8YnI+DQoNCiMjIyBLZXJuZWwgcmVncmVzc2lvbg0KDQpUaGUgYGtyX2NhdGVgIGZ1bmN0aW9uIGltcGxlbWVudHMga2VybmVsIHJlZ3Jlc3Npb24gcmV1c2luZyB0aGUgcHNldWRvLW91dGNvbWU6DQoNCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30NCmtyX2FnZSA9IGtyX2NhdGUoYWlwdyRBVEUkZGVsdGEsYWdlKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChrcl9hZ2Usel9sYWJlbCA9ICJBZ2UiKQ0KYGBgDQoNCkJvdGggbm9ucGFyYW1ldHJpYyBhcHByb2FjaGVzIGRvY3VtZW50IHRoZSBzYW1lIHBhdHRlcm4uIFRoZSBlZmZlY3Qgb2YgNDAxKGspIHBhcnRpY2lwYXRpb24gb24gbmV0IHdlYWx0aCBpbmNyZWFzZXMgbW9yZSBvciBsZXNzIGxpbmVhcmx5IHVudGlsIHRoZSBhZ2Ugb2YgNTAgYW5kIHRoZW4gc2xpZ2h0bHkgZHJvcHMuDQoNCkZvciBtZSB0aGlzIGlzIGFuIGluY3JlZGlibGUgbmV3IG9wdGlvbiBmb3Igb3VyIHBvbGljeSBldmFsdWF0aW9uIHRvb2xib3guDQoNCjxicj4NCjxicj4=