Replication comments:

  • The many required NxN smoother matrices make this notebook relatively memory intensive (32GB RAM should suffice).

  • Running the notebook within the replication docker ensures that results perfectly replicate. Otherwise, results might differ depending on which package versions you use.

This notebook runs the application described in Section 5.1 of Knaus (2024). The first part replicates the results presented in the paper, the second part provides supplementary information

1. Replication of paper results

Getting started

First, load packages and set the seed:

if (!require("OutcomeWeights")) install.packages("OutcomeWeights", dependencies = TRUE); library(OutcomeWeights)
if (!require("hdm")) install.packages("hdm", dependencies = TRUE); library(hdm)
if (!require("grf")) install.packages("grf", dependencies = TRUE); library(grf)
if (!require("cobalt")) install.packages("cobalt", dependencies = TRUE); library(cobalt)
if (!require("tidyverse")) install.packages("tidyverse", dependencies = TRUE); library(tidyverse)
if (!require("viridis")) install.packages("viridis", dependencies = TRUE); library(viridis)
if (!require("gridExtra")) install.packages("gridExtra", dependencies = TRUE); library(gridExtra)

set.seed(1234)

Next, load the data. Here we use the 401(k) data of the hdm package. However, you can adapt the following code chunk to load any suitable data of your choice. Just make sure to call the treatment D, covariates X, and instrument Z. The rest of the notebook should run without further modifications.

data(pension) # Find variable description if you type ?pension in console

# Treatment
D = pension$p401
# Instrument
Z = pension$e401
# Outcome
Y = pension$net_tfa
# Controls
X = model.matrix(~ 0 + age + db + educ + fsize + hown + inc + male + marr + pira + twoearn, data = pension)
var_nm = c("Age","Benefit pension","Education","Family size","Home owner","Income","Male","Married","IRA","Two earners")
colnames(X) = var_nm

Run Double ML

In the following we run double ML with default honest random forest (tuning only increases running time without changing the insights in this application). As standard implementations do currently not allow to extract the outcome smoother matrices, the OutcomeWeights package comes with a tailored internal implementation called dml_with_smoother(), which is used in the following.

2-folds

First, we run all estimators with 2-fold cross-fitting:

# 2 folds
dml_2f = dml_with_smoother(Y,D,X,Z,
                           n_cf_folds = 2)
results_dml_2f = summary(dml_2f)
          Estimate      SE      t         p    
PLR        13903.3  1539.3 9.0323 < 2.2e-16 ***
PLR-IV     12931.8  1914.3 6.7554 1.504e-11 ***
AIPW-ATE   11680.5  1185.0 9.8566 < 2.2e-16 ***
Wald-AIPW  11332.9  1661.0 6.8228 9.448e-12 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
plot(dml_2f)

Now, we use the get_outcome_weights() method to extract the outcome weights as described in the paper. To illustrate that the algebraic results provided in the paper are indeed numerical equivalences and no approximations, we check whether the weights multiplied by the outcome vector reproduces the conventionally generated point estimates.

omega_dml_2f = get_outcome_weights(dml_2f)
cat("ω'Y replicates point etimates?", 
    all.equal(as.numeric(omega_dml_2f$omega %*% Y),
      as.numeric(results_dml_2f[,1])
    ))
ω'Y replicates point etimates? TRUE

5-fold

Run double ML also with 5-fold cross-fitting:

# 5 folds
dml_5f = dml_with_smoother(Y,D,X,Z,
                           n_cf_folds = 5)
results_dml_5f = summary(dml_5f)
          Estimate      SE      t         p    
PLR        13898.6  1525.3 9.1123 < 2.2e-16 ***
PLR-IV     13194.8  1902.7 6.9348 4.322e-12 ***
AIPW-ATE   11574.8  1158.6 9.9903 < 2.2e-16 ***
Wald-AIPW  11386.7  1638.4 6.9499 3.887e-12 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
plot(dml_5f)

extract the weights and confirm numerical equivalence:

omega_dml_5f = get_outcome_weights(dml_5f)
cat("ω'Y replicates point etimates?", 
    all.equal(as.numeric(omega_dml_5f$omega %*% Y),
      as.numeric(results_dml_5f[,1])
    ))
ω'Y replicates point etimates? TRUE

Check covariate balancing

We use the infrastructure of the cobalt package to plot Standardized Mean Differences where we need to flip the sign of the untreated outcome weights to make them compatible with the package framework. This is achieved by multiplying the outcome weights by \(2 \times D-1\):

threshold = 0.1

create_love_plot = function(title, index) {
  love.plot(
    D ~ X,
    weights = list(
      "2-fold" = omega_dml_2f$omega[index, ] * (2*D-1),
      "5-fold" = omega_dml_5f$omega[index, ] * (2*D-1)
    ),
    position = "bottom",
    title = title,
    thresholds = c(m = threshold),
    var.order = "unadjusted",
    binary = "std",
    abs = TRUE,
    line = TRUE,
    colors = viridis(3), # color-blind-friendly
    shapes = c("circle", "triangle", "diamond")
  )
}

# Now you can call this function for each plot:
love_plot_plr = create_love_plot("PLR", 1)
love_plot_plriv = create_love_plot("PLR-IV", 2)
love_plot_aipw = create_love_plot("AIPW", 3)
love_plot_waipw = create_love_plot("Wald-AIPW", 4)
love_plot_plr

love_plot_plriv

love_plot_aipw

love_plot_waipw

Create the combined plot that ends up in the paper as Figure 2:

figure2 = grid.arrange(
  love_plot_plr, love_plot_aipw,
  love_plot_plriv,love_plot_waipw,
  nrow = 2
)

2. Supplementary results

10-folds

Alternatively, we can also apply 10 instead of 5-fold cross-fitting:

dml_10f = dml_with_smoother(Y,D,X,Z,n_cf_folds = 10)
results_dml_10f = summary(dml_10f)
          Estimate      SE      t         p    
PLR        13530.4  1516.6 8.9216 < 2.2e-16 ***
PLR-IV     12609.8  1885.9 6.6864 2.411e-11 ***
AIPW-ATE   11298.4  1158.4 9.7537 < 2.2e-16 ***
Wald-AIPW  11080.6  1647.1 6.7271 1.826e-11 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
omega_dml_10f = get_outcome_weights(dml_10f)
cat("ω'Y replicates point etimates?",
    all.equal(as.numeric(omega_dml_10f$omega %*% Y),
      as.numeric(results_dml_10f[,1])
    ))
ω'Y replicates point etimates? TRUE
rm(dml_10f)

We observe that it does sometimes improve over 5-fold and sometimes is worse. 5-folds seem to provide a good compromise between balancing quality and computational speed.

create_love_plot = function(title, index) {
  love.plot(
    D ~ X,
    weights = list(
      "2-fold" = omega_dml_2f$omega[index, ] * (2*D-1),
      "5-fold" = omega_dml_5f$omega[index, ] * (2*D-1),
      "10-fold" = omega_dml_10f$omega[index, ] * (2*D-1)
    ),
    position = "bottom",
    title = title,
    thresholds = c(m = threshold),
    var.order = "unadjusted",
    binary = "std",
    abs = TRUE,
    line = TRUE,
    colors = viridis(4), # color-blind-friendly
  )
}

# Now you can call this function for each plot:
love_plot_plr = create_love_plot("PLR", 1)
love_plot_plriv = create_love_plot("PLR-IV", 2)
love_plot_aipw = create_love_plot("AIPW", 3)
love_plot_waipw = create_love_plot("Wald-AIPW", 4)
love_plot_plr

love_plot_plriv

love_plot_aipw

love_plot_waipw

Weights descriptives

Finally, we investigate descriptives of the weights. Given the results of the paper it is most interesting to observe that the “Sum of weights” of PLR(-IV) and Wald-AIPW indeed are only scale-normalized. However, they all sum to values very close to one.

cat("2-fold: \n")
2-fold: 
summary(omega_dml_2f)

Weight summary for estimator PLR 
                          Control    Treated    
Minimum weight            -epsilon*  0.0001     
Maximum weight            0.0005     0.0006     
% Negative                0.0194     0.0000     
Sum largest 10%           0.2322     0.1394     
Sum of weights            0.9963     0.9963     
Sum of absolute weights   0.9985     0.9963     
* epsilon <  1e-04 

Weight summary for estimator PLR-IV 
                          Control    Treated    
Minimum weight            -0.0007    epsilon*   
Maximum weight            0.0007     0.0007     
% Negative                0.1527     0.0000     
Sum largest 10%           0.3573     0.1649     
Sum of weights            0.9953     0.9953     
Sum of absolute weights   1.8597     0.9953     
* epsilon <  1e-04 

Weight summary for estimator AIPW-ATE 
                          Control    Treated    
Minimum weight            epsilon*   epsilon*   
Maximum weight            0.0003     0.0030     
% Negative                0.0000     0.0000     
Sum largest 10%           0.1562     0.2878     
Sum of weights            1.0000     1.0000     
Sum of absolute weights   1.0000     1.0000     
* epsilon <  1e-04 

Weight summary for estimator Wald-AIPW 
                          Control    Treated    
Minimum weight            -0.0028    -epsilon*  
Maximum weight            0.0009     0.0030     
% Negative                0.1485     0.0004     
Sum largest 10%           0.3057     0.2920     
Sum of weights            0.9993     0.9993     
Sum of absolute weights   1.8643     0.9994     
* epsilon <  1e-04 
cat("\n\n5-fold: \n")


5-fold: 
summary(omega_dml_5f)

Weight summary for estimator PLR 
                          Control    Treated    
Minimum weight            -epsilon*  0.0001     
Maximum weight            0.0005     0.0006     
% Negative                0.0022     0.0000     
Sum largest 10%           0.2287     0.1388     
Sum of weights            0.9978     0.9978     
Sum of absolute weights   0.9978     0.9978     
* epsilon <  1e-04 

Weight summary for estimator PLR-IV 
                          Control    Treated    
Minimum weight            -0.0007    epsilon*   
Maximum weight            0.0007     0.0007     
% Negative                0.1524     0.0000     
Sum largest 10%           0.3569     0.1640     
Sum of weights            0.9969     0.9969     
Sum of absolute weights   1.8769     0.9969     
* epsilon <  1e-04 

Weight summary for estimator AIPW-ATE 
                          Control    Treated    
Minimum weight            epsilon*   0.0001     
Maximum weight            0.0004     0.0033     
% Negative                0.0000     0.0000     
Sum largest 10%           0.1551     0.2881     
Sum of weights            1.0000     1.0000     
Sum of absolute weights   1.0000     1.0000     
* epsilon <  1e-04 

Weight summary for estimator Wald-AIPW 
                          Control    Treated    
Minimum weight            -0.0040    0.0001     
Maximum weight            0.0011     0.0042     
% Negative                0.1486     0.0000     
Sum largest 10%           0.3058     0.2893     
Sum of weights            0.9999     0.9999     
Sum of absolute weights   1.8772     0.9999     
# cat("\n\n10-fold: \n")
# summary(omega_dml_10f)
LS0tCnRpdGxlOiAiVHJlYXRtZW50IEVmZmVjdCBFc3RpbWF0b3JzIGFzIFdlaWdodGVkIE91dGNvbWVzIgpzdWJ0aXRsZTogIkFwcGxpY2F0aW9uIDQwMShrKSAtIGF2ZXJhZ2UgZWZmZWN0cyIKYXV0aG9yOiAiTWljaGFlbCBDLiBLbmF1cyIKZGF0ZTogIjExLzI0IgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiBzaG93Ci0tLQoKKlJlcGxpY2F0aW9uIGNvbW1lbnRzOiogCgotICpUaGUgbWFueSByZXF1aXJlZCBOeE4gc21vb3RoZXIgbWF0cmljZXMgbWFrZSB0aGlzIG5vdGVib29rIHJlbGF0aXZlbHkgbWVtb3J5IGludGVuc2l2ZSAoMzJHQiBSQU0gc2hvdWxkIHN1ZmZpY2UpLiogCgotICpSdW5uaW5nIHRoZSBub3RlYm9vayB3aXRoaW4gdGhlIHJlcGxpY2F0aW9uIGRvY2tlciBlbnN1cmVzIHRoYXQgcmVzdWx0cyBwZXJmZWN0bHkgcmVwbGljYXRlLiBPdGhlcndpc2UsIHJlc3VsdHMgbWlnaHQgZGlmZmVyIGRlcGVuZGluZyBvbiB3aGljaCBwYWNrYWdlIHZlcnNpb25zIHlvdSB1c2UuKgoKClRoaXMgbm90ZWJvb2sgcnVucyB0aGUgYXBwbGljYXRpb24gZGVzY3JpYmVkIGluIFNlY3Rpb24gNS4xIG9mIFtLbmF1cyAoMjAyNCldKGh0dHBzOi8vYXJ4aXYub3JnL2Ficy8yNDExLjExNTU5KS4gVGhlIGZpcnN0IHBhcnQgcmVwbGljYXRlcyB0aGUgcmVzdWx0cyBwcmVzZW50ZWQgaW4gdGhlIHBhcGVyLCB0aGUgc2Vjb25kIHBhcnQgcHJvdmlkZXMgc3VwcGxlbWVudGFyeSBpbmZvcm1hdGlvbgoKIyAxLiBSZXBsaWNhdGlvbiBvZiBwYXBlciByZXN1bHRzCgojIyBHZXR0aW5nIHN0YXJ0ZWQKCkZpcnN0LCBsb2FkIHBhY2thZ2VzIGFuZCBzZXQgdGhlIHNlZWQ6CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nPUZBTFNFfQppZiAoIXJlcXVpcmUoIk91dGNvbWVXZWlnaHRzIikpIGluc3RhbGwucGFja2FnZXMoIk91dGNvbWVXZWlnaHRzIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoT3V0Y29tZVdlaWdodHMpCmlmICghcmVxdWlyZSgiaGRtIikpIGluc3RhbGwucGFja2FnZXMoImhkbSIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KGhkbSkKaWYgKCFyZXF1aXJlKCJncmYiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ3JmIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoZ3JmKQppZiAoIXJlcXVpcmUoImNvYmFsdCIpKSBpbnN0YWxsLnBhY2thZ2VzKCJjb2JhbHQiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeShjb2JhbHQpCmlmICghcmVxdWlyZSgidGlkeXZlcnNlIikpIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KHRpZHl2ZXJzZSkKaWYgKCFyZXF1aXJlKCJ2aXJpZGlzIikpIGluc3RhbGwucGFja2FnZXMoInZpcmlkaXMiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeSh2aXJpZGlzKQppZiAoIXJlcXVpcmUoImdyaWRFeHRyYSIpKSBpbnN0YWxsLnBhY2thZ2VzKCJncmlkRXh0cmEiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeShncmlkRXh0cmEpCgpzZXQuc2VlZCgxMjM0KQpgYGAKCk5leHQsIGxvYWQgdGhlIGRhdGEuIEhlcmUgd2UgdXNlIHRoZSA0MDEoaykgZGF0YSBvZiB0aGUgYGhkbWAgcGFja2FnZS4gSG93ZXZlciwgeW91IGNhbiBhZGFwdCB0aGUgZm9sbG93aW5nIGNvZGUgY2h1bmsgdG8gbG9hZCBhbnkgc3VpdGFibGUgZGF0YSBvZiB5b3VyIGNob2ljZS4gSnVzdCBtYWtlIHN1cmUgdG8gY2FsbCB0aGUgdHJlYXRtZW50IGBEYCwgY292YXJpYXRlcyBgWGAsIGFuZCBpbnN0cnVtZW50IGBaYC4gVGhlIHJlc3Qgb2YgdGhlIG5vdGVib29rIHNob3VsZCBydW4gd2l0aG91dCBmdXJ0aGVyIG1vZGlmaWNhdGlvbnMuCgpgYGB7cn0KZGF0YShwZW5zaW9uKSAjIEZpbmQgdmFyaWFibGUgZGVzY3JpcHRpb24gaWYgeW91IHR5cGUgP3BlbnNpb24gaW4gY29uc29sZQoKIyBUcmVhdG1lbnQKRCA9IHBlbnNpb24kcDQwMQojIEluc3RydW1lbnQKWiA9IHBlbnNpb24kZTQwMQojIE91dGNvbWUKWSA9IHBlbnNpb24kbmV0X3RmYQojIENvbnRyb2xzClggPSBtb2RlbC5tYXRyaXgofiAwICsgYWdlICsgZGIgKyBlZHVjICsgZnNpemUgKyBob3duICsgaW5jICsgbWFsZSArIG1hcnIgKyBwaXJhICsgdHdvZWFybiwgZGF0YSA9IHBlbnNpb24pCnZhcl9ubSA9IGMoIkFnZSIsIkJlbmVmaXQgcGVuc2lvbiIsIkVkdWNhdGlvbiIsIkZhbWlseSBzaXplIiwiSG9tZSBvd25lciIsIkluY29tZSIsIk1hbGUiLCJNYXJyaWVkIiwiSVJBIiwiVHdvIGVhcm5lcnMiKQpjb2xuYW1lcyhYKSA9IHZhcl9ubQpgYGAKCgojIyBSdW4gRG91YmxlIE1MCgpJbiB0aGUgZm9sbG93aW5nIHdlIHJ1biBkb3VibGUgTUwgd2l0aCBkZWZhdWx0IGhvbmVzdCByYW5kb20gZm9yZXN0ICh0dW5pbmcgb25seSBpbmNyZWFzZXMgcnVubmluZyB0aW1lIHdpdGhvdXQgY2hhbmdpbmcgdGhlIGluc2lnaHRzIGluIHRoaXMgYXBwbGljYXRpb24pLiBBcyBzdGFuZGFyZCBpbXBsZW1lbnRhdGlvbnMgZG8gY3VycmVudGx5IG5vdCBhbGxvdyB0byBleHRyYWN0IHRoZSBvdXRjb21lIHNtb290aGVyIG1hdHJpY2VzLCB0aGUgYE91dGNvbWVXZWlnaHRzYCBwYWNrYWdlIGNvbWVzIHdpdGggYSB0YWlsb3JlZCBpbnRlcm5hbCBpbXBsZW1lbnRhdGlvbiBjYWxsZWQgYGRtbF93aXRoX3Ntb290aGVyKClgLCB3aGljaCBpcyB1c2VkIGluIHRoZSBmb2xsb3dpbmcuCgoKIyMjIDItZm9sZHMKCkZpcnN0LCB3ZSBydW4gYWxsIGVzdGltYXRvcnMgd2l0aCAyLWZvbGQgY3Jvc3MtZml0dGluZzoKCmBgYHtyfQojIDIgZm9sZHMKZG1sXzJmID0gZG1sX3dpdGhfc21vb3RoZXIoWSxELFgsWiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9jZl9mb2xkcyA9IDIpCnJlc3VsdHNfZG1sXzJmID0gc3VtbWFyeShkbWxfMmYpCnBsb3QoZG1sXzJmKQpgYGAKCk5vdywgd2UgdXNlIHRoZSBgZ2V0X291dGNvbWVfd2VpZ2h0cygpYCBtZXRob2QgdG8gZXh0cmFjdCB0aGUgb3V0Y29tZSB3ZWlnaHRzIGFzIGRlc2NyaWJlZCBpbiB0aGUgcGFwZXIuIFRvIGlsbHVzdHJhdGUgdGhhdCB0aGUgYWxnZWJyYWljIHJlc3VsdHMgcHJvdmlkZWQgaW4gdGhlIHBhcGVyIGFyZSBpbmRlZWQgbnVtZXJpY2FsIGVxdWl2YWxlbmNlcyBhbmQgbm8gYXBwcm94aW1hdGlvbnMsIHdlIGNoZWNrIHdoZXRoZXIgdGhlIHdlaWdodHMgbXVsdGlwbGllZCBieSB0aGUgb3V0Y29tZSB2ZWN0b3IgcmVwcm9kdWNlcyB0aGUgY29udmVudGlvbmFsbHkgZ2VuZXJhdGVkIHBvaW50IGVzdGltYXRlcy4KCmBgYHtyfQpvbWVnYV9kbWxfMmYgPSBnZXRfb3V0Y29tZV93ZWlnaHRzKGRtbF8yZikKY2F0KCLPiSdZIHJlcGxpY2F0ZXMgcG9pbnQgZXRpbWF0ZXM/IiwgCiAgICBhbGwuZXF1YWwoYXMubnVtZXJpYyhvbWVnYV9kbWxfMmYkb21lZ2EgJSolIFkpLAogICAgICBhcy5udW1lcmljKHJlc3VsdHNfZG1sXzJmWywxXSkKICAgICkpCmBgYAoKCmBgYHtyLCBlY2hvID0gRiwgcmVzdWx0cz0naGlkZSd9CiMgQXMgdGhlIGBkbWxfd2l0aF9zbW9vdGhlcigpYCBvYmplY3RzIGFyZSBtZW1vcnkgaW50ZW5zaXZlIGJlY2F1c2UgdGhleSBzdG9yZSBzZXZlcmFsICROIFx0aW1lcyBOJCBzbW9vdGhlciBtYXRyaWNlcywgaXQgaXMgY29udmVuaWVudCB0byByZW1vdmUgdGhlIDItZm9sZCBvbmUgYmVmb3JlIHByb2NlZWRpbmcuIENvbW1lbnQgb3V0IGlmIHlvdSBoYXZlIGVub3VnaCBSQU0gYW5kIHdhbnQgdG8gdXNlIHRoZSBvYmplY3RzIGxhdGVyIG9uLgpybShkbWxfMmYpCmdjKCkKYGBgCgoKCiMjIyA1LWZvbGQKClJ1biBkb3VibGUgTUwgYWxzbyB3aXRoIDUtZm9sZCBjcm9zcy1maXR0aW5nOgoKYGBge3J9CiMgNSBmb2xkcwpkbWxfNWYgPSBkbWxfd2l0aF9zbW9vdGhlcihZLEQsWCxaLAogICAgICAgICAgICAgICAgICAgICAgICAgICBuX2NmX2ZvbGRzID0gNSkKcmVzdWx0c19kbWxfNWYgPSBzdW1tYXJ5KGRtbF81ZikKcGxvdChkbWxfNWYpCmBgYAoKZXh0cmFjdCB0aGUgd2VpZ2h0cyBhbmQgY29uZmlybSBudW1lcmljYWwgZXF1aXZhbGVuY2U6CgpgYGB7cn0Kb21lZ2FfZG1sXzVmID0gZ2V0X291dGNvbWVfd2VpZ2h0cyhkbWxfNWYpCmNhdCgiz4knWSByZXBsaWNhdGVzIHBvaW50IGV0aW1hdGVzPyIsIAogICAgYWxsLmVxdWFsKGFzLm51bWVyaWMob21lZ2FfZG1sXzVmJG9tZWdhICUqJSBZKSwKICAgICAgYXMubnVtZXJpYyhyZXN1bHRzX2RtbF81ZlssMV0pCiAgICApKQpgYGAKCgpgYGB7ciwgZWNobyA9IEYsIHJlc3VsdHM9J2hpZGUnfQojIEFzIHRoZSBgZG1sX3dpdGhfc21vb3RoZXIoKWAgb2JqZWN0cyBhcmUgbWVtb3J5IGludGVuc2l2ZSBiZWNhdXNlIHRoZXkgc3RvcmUgc2V2ZXJhbCAkTiBcdGltZXMgTiQgc21vb3RoZXIgbWF0cmljZXMsIGl0IGlzIGNvbnZlbmllbnQgdG8gcmVtb3ZlIHRoZSA1LWZvbGQgb25lIGJlZm9yZSBwcm9jZWVkaW5nLiBDb21tZW50IG91dCBpZiB5b3UgaGF2ZSBlbm91Z2ggUkFNIGFuZCB3YW50IHRvIHVzZSB0aGUgb2JqZWN0cyBsYXRlciBvbi4Kcm0oZG1sXzVmKQpnYygpCmBgYAoKCiMjIENoZWNrIGNvdmFyaWF0ZSBiYWxhbmNpbmcKCldlIHVzZSB0aGUgaW5mcmFzdHJ1Y3R1cmUgb2YgdGhlIGBjb2JhbHRgIHBhY2thZ2UgdG8gcGxvdCBTdGFuZGFyZGl6ZWQgTWVhbiBEaWZmZXJlbmNlcyB3aGVyZSB3ZSBuZWVkIHRvIGZsaXAgdGhlIHNpZ24gb2YgdGhlIHVudHJlYXRlZCBvdXRjb21lIHdlaWdodHMgdG8gbWFrZSB0aGVtIGNvbXBhdGlibGUgd2l0aCB0aGUgcGFja2FnZSBmcmFtZXdvcmsuIFRoaXMgaXMgYWNoaWV2ZWQgYnkgbXVsdGlwbHlpbmcgdGhlIG91dGNvbWUgd2VpZ2h0cyBieSAkMiBcdGltZXMgRC0xJDoKCmBgYHtyLCBtZXNzYWdlID0gRn0KdGhyZXNob2xkID0gMC4xCgpjcmVhdGVfbG92ZV9wbG90ID0gZnVuY3Rpb24odGl0bGUsIGluZGV4KSB7CiAgbG92ZS5wbG90KAogICAgRCB+IFgsCiAgICB3ZWlnaHRzID0gbGlzdCgKICAgICAgIjItZm9sZCIgPSBvbWVnYV9kbWxfMmYkb21lZ2FbaW5kZXgsIF0gKiAoMipELTEpLAogICAgICAiNS1mb2xkIiA9IG9tZWdhX2RtbF81ZiRvbWVnYVtpbmRleCwgXSAqICgyKkQtMSkKICAgICksCiAgICBwb3NpdGlvbiA9ICJib3R0b20iLAogICAgdGl0bGUgPSB0aXRsZSwKICAgIHRocmVzaG9sZHMgPSBjKG0gPSB0aHJlc2hvbGQpLAogICAgdmFyLm9yZGVyID0gInVuYWRqdXN0ZWQiLAogICAgYmluYXJ5ID0gInN0ZCIsCiAgICBhYnMgPSBUUlVFLAogICAgbGluZSA9IFRSVUUsCiAgICBjb2xvcnMgPSB2aXJpZGlzKDMpLCAjIGNvbG9yLWJsaW5kLWZyaWVuZGx5CiAgICBzaGFwZXMgPSBjKCJjaXJjbGUiLCAidHJpYW5nbGUiLCAiZGlhbW9uZCIpCiAgKQp9CgojIE5vdyB5b3UgY2FuIGNhbGwgdGhpcyBmdW5jdGlvbiBmb3IgZWFjaCBwbG90Ogpsb3ZlX3Bsb3RfcGxyID0gY3JlYXRlX2xvdmVfcGxvdCgiUExSIiwgMSkKbG92ZV9wbG90X3Bscml2ID0gY3JlYXRlX2xvdmVfcGxvdCgiUExSLUlWIiwgMikKbG92ZV9wbG90X2FpcHcgPSBjcmVhdGVfbG92ZV9wbG90KCJBSVBXIiwgMykKbG92ZV9wbG90X3dhaXB3ID0gY3JlYXRlX2xvdmVfcGxvdCgiV2FsZC1BSVBXIiwgNCkKbG92ZV9wbG90X3Bscgpsb3ZlX3Bsb3RfcGxyaXYKbG92ZV9wbG90X2FpcHcKbG92ZV9wbG90X3dhaXB3CmBgYAoKCkNyZWF0ZSB0aGUgY29tYmluZWQgcGxvdCB0aGF0IGVuZHMgdXAgaW4gdGhlIHBhcGVyIGFzIEZpZ3VyZSAyOgoKYGBge3IsIHJlc3VsdHM9J2hpZGUnLCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9OH0KZmlndXJlMiA9IGdyaWQuYXJyYW5nZSgKICBsb3ZlX3Bsb3RfcGxyLCBsb3ZlX3Bsb3RfYWlwdywKICBsb3ZlX3Bsb3RfcGxyaXYsbG92ZV9wbG90X3dhaXB3LAogIG5yb3cgPSAyCikKYGBgCgoKIyAyLiBTdXBwbGVtZW50YXJ5IHJlc3VsdHMKCiMjIDEwLWZvbGRzCgpBbHRlcm5hdGl2ZWx5LCB3ZSBjYW4gYWxzbyBhcHBseSAxMCBpbnN0ZWFkIG9mIDUtZm9sZCBjcm9zcy1maXR0aW5nOgoKYGBge3J9CmRtbF8xMGYgPSBkbWxfd2l0aF9zbW9vdGhlcihZLEQsWCxaLG5fY2ZfZm9sZHMgPSAxMCkKcmVzdWx0c19kbWxfMTBmID0gc3VtbWFyeShkbWxfMTBmKQpvbWVnYV9kbWxfMTBmID0gZ2V0X291dGNvbWVfd2VpZ2h0cyhkbWxfMTBmKQpjYXQoIs+JJ1kgcmVwbGljYXRlcyBwb2ludCBldGltYXRlcz8iLAogICAgYWxsLmVxdWFsKGFzLm51bWVyaWMob21lZ2FfZG1sXzEwZiRvbWVnYSAlKiUgWSksCiAgICAgIGFzLm51bWVyaWMocmVzdWx0c19kbWxfMTBmWywxXSkKICAgICkpCnJtKGRtbF8xMGYpCmBgYAoKV2Ugb2JzZXJ2ZSB0aGF0IGl0IGRvZXMgc29tZXRpbWVzIGltcHJvdmUgb3ZlciA1LWZvbGQgYW5kIHNvbWV0aW1lcyBpcyB3b3JzZS4gNS1mb2xkcyBzZWVtIHRvIHByb3ZpZGUgYSBnb29kIGNvbXByb21pc2UgYmV0d2VlbiBiYWxhbmNpbmcgcXVhbGl0eSBhbmQgY29tcHV0YXRpb25hbCBzcGVlZC4KCmBgYHtyLCBtZXNzYWdlID0gRn0KY3JlYXRlX2xvdmVfcGxvdCA9IGZ1bmN0aW9uKHRpdGxlLCBpbmRleCkgewogIGxvdmUucGxvdCgKICAgIEQgfiBYLAogICAgd2VpZ2h0cyA9IGxpc3QoCiAgICAgICIyLWZvbGQiID0gb21lZ2FfZG1sXzJmJG9tZWdhW2luZGV4LCBdICogKDIqRC0xKSwKICAgICAgIjUtZm9sZCIgPSBvbWVnYV9kbWxfNWYkb21lZ2FbaW5kZXgsIF0gKiAoMipELTEpLAogICAgICAiMTAtZm9sZCIgPSBvbWVnYV9kbWxfMTBmJG9tZWdhW2luZGV4LCBdICogKDIqRC0xKQogICAgKSwKICAgIHBvc2l0aW9uID0gImJvdHRvbSIsCiAgICB0aXRsZSA9IHRpdGxlLAogICAgdGhyZXNob2xkcyA9IGMobSA9IHRocmVzaG9sZCksCiAgICB2YXIub3JkZXIgPSAidW5hZGp1c3RlZCIsCiAgICBiaW5hcnkgPSAic3RkIiwKICAgIGFicyA9IFRSVUUsCiAgICBsaW5lID0gVFJVRSwKICAgIGNvbG9ycyA9IHZpcmlkaXMoNCksICMgY29sb3ItYmxpbmQtZnJpZW5kbHkKICApCn0KCiMgTm93IHlvdSBjYW4gY2FsbCB0aGlzIGZ1bmN0aW9uIGZvciBlYWNoIHBsb3Q6CmxvdmVfcGxvdF9wbHIgPSBjcmVhdGVfbG92ZV9wbG90KCJQTFIiLCAxKQpsb3ZlX3Bsb3RfcGxyaXYgPSBjcmVhdGVfbG92ZV9wbG90KCJQTFItSVYiLCAyKQpsb3ZlX3Bsb3RfYWlwdyA9IGNyZWF0ZV9sb3ZlX3Bsb3QoIkFJUFciLCAzKQpsb3ZlX3Bsb3Rfd2FpcHcgPSBjcmVhdGVfbG92ZV9wbG90KCJXYWxkLUFJUFciLCA0KQpsb3ZlX3Bsb3RfcGxyCmxvdmVfcGxvdF9wbHJpdgpsb3ZlX3Bsb3RfYWlwdwpsb3ZlX3Bsb3Rfd2FpcHcKYGBgCgoKCiMjIFdlaWdodHMgZGVzY3JpcHRpdmVzCgpGaW5hbGx5LCB3ZSBpbnZlc3RpZ2F0ZSBkZXNjcmlwdGl2ZXMgb2YgdGhlIHdlaWdodHMuIEdpdmVuIHRoZSByZXN1bHRzIG9mIHRoZSBwYXBlciBpdCBpcyBtb3N0IGludGVyZXN0aW5nIHRvIG9ic2VydmUgdGhhdCB0aGUgIlN1bSBvZiB3ZWlnaHRzIiBvZiBQTFIoLUlWKSBhbmQgV2FsZC1BSVBXIGluZGVlZCBhcmUgb25seSBzY2FsZS1ub3JtYWxpemVkLiBIb3dldmVyLCB0aGV5IGFsbCBzdW0gdG8gdmFsdWVzIHZlcnkgY2xvc2UgdG8gb25lLgoKYGBge3J9CmNhdCgiMi1mb2xkOiBcbiIpCnN1bW1hcnkob21lZ2FfZG1sXzJmKQpjYXQoIlxuXG41LWZvbGQ6IFxuIikKc3VtbWFyeShvbWVnYV9kbWxfNWYpCiMgY2F0KCJcblxuMTAtZm9sZDogXG4iKQojIHN1bW1hcnkob21lZ2FfZG1sXzEwZikKYGBgCgoKCmBgYHtyLCBlY2hvPUZ9CiMgVGhpcyBwYXJ0IGlzIHJlbGV2YW50IGlmIHlvdSBydW4gdGhlIG5vdGVib29rcyBpbnNpZGUgdGhlIGRvY2tlciBhbmQgd2FudCB0byBzYXZlIGdyYXBocyBhbmQgaW1hZ2UgaW4gYSBzaGFyZWQgaG9zdCB2b2x1bWUgY2FsbGVkIHNoYXJlZF9maWxlcyAodW5jb21tZW50IGFuZC9vciBhZGp1c3Qgb24gZGVtYW5kKToKCiMgZ2dzYXZlKCIvaG9tZS9yc3R1ZGlvL3NoYXJlZF9maWxlcy9GaWd1cmUyLnBkZiIsIHBsb3QgPSBmaWd1cmUyLCB3aWR0aCA9IDksIGhlaWdodCA9IDYsZHBpPTMwMCkKIyBzYXZlLmltYWdlKGZpbGUgPSAiL2hvbWUvcnN0dWRpby9zaGFyZWRfZmlsZXMvQXBwbGljYXRpb25fNDAxa19hdmVyYWdlLlJEYXRhIikKYGBgCg==