“Double Machine Learning based Program Evaluation under Unconfoundedness”

Michael C. Knaus

Version 2

This Notebook replicates the main results of the paper using a different seed (results in the paper are obtained with 1234, below 12345 is specified). The notebook should help to …

  • show the underlying code for researchers interested in replicating the analysis using the same data or adapting it to new datasets

  • show that most of the results reported in the paper are not sensitive to the seed used to initialize the random number generators that are called for defining the cross-fitting folds and within random forests

The explanations throughout the notebook are kept short. Please see the paper for more explanations regarding the parameters of interest and their Double Machine Learning based estimation.

Click on the “Code” button on the top right of this notebook to “Hide All Code” and see only the results or “Download Rmd” to extract the underlying code.

Running the analyses in this notebook took roughly two days on a SWITCHengine with eight cores and 32 GB RAM.



Preparation

The analysis is mostly based on the causalDML R-package that was developed in parallel to the paper. The preparation of the “Database_SALMP_MCK2020.Rdata” that is loaded can be replicated by requesting the data via FORSbase and running the data preparation file.

## Define and set seed
seed = 12345
set.seed(seed)

## Load relevant packages, in case you did not yet install causalDML uncomment the next two lines
# library(devtools)
# install_github(repo="MCKnaus/causalDML")
library(causalDML)
library(policytree)
library(tidyverse)
library(lmtest)
library(sandwich)
library(matrixStats)

## Set to path where data are stored for replication
# setwd("C:/Users/Administrator/switchdrive/Papers/Multi Treat Labor/Paper/Data")
load("Database_SALMP_MCK2020.RData")



Average effects

The causalDML command estimates first the cross-fitted nuisance parameters and uses them to calculate the double robust scores for APO and ATE, which are then used to estimate the APO and ATE(T).

# Create the ML method to be used for nuisance parameter prediction
forest = create_method("forest_grf",args=list(tune.parameters = "all",seed=seed))

# Run the main function that outputs nuisance parameters, APO and ATE
cDML = causalDML(y,w,x,ml_w=list(forest),ml_y=list(forest))



Average potential outcome (APO)

The following table and plot shows the DML estimated APOs.

summary(cDML$APO)
                APO         SE
no program 14.79627 0.05607573
job search 13.75765 0.11484011
vocational 18.01463 0.42204324
computer   18.03404 0.42602710
language   17.28424 0.37482108
plot(cDML$APO,label_w)



Average treatment effects (ATE and ATET)

ATE

The following table shows the results of all possible ATEs given the five different treatments:

summary(cDML$ATE)
                              ATE        SE       t         p    
job search - no program -1.038615  0.125267 -8.2912 < 2.2e-16 ***
vocational - no program  3.218358  0.425095  7.5709 3.757e-14 ***
computer - no program    3.237774  0.429024  7.5468 4.520e-14 ***
language - no program    2.487976  0.377771  6.5859 4.556e-11 ***
vocational - job search  4.256973  0.436936  9.7428 < 2.2e-16 ***
computer - job search    4.276389  0.440728  9.7030 < 2.2e-16 ***
language - job search    3.526591  0.391096  9.0172 < 2.2e-16 ***
computer - vocational    0.019416  0.599454  0.0324    0.9742    
language - vocational   -0.730382  0.563997 -1.2950    0.1953    
language - computer     -0.749798  0.566864 -1.3227    0.1859    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1


ATET

The following tables show the results for all possible ATETs given the five different treatments. The first line shows for example an effect of -1.045 for being in a job search program vs. no program for those who were actually observed in no program, or formally adapting the notation of the paper \(E[Y(jo b~search) - Y(no~program) \mid W = no~program]\). As we can condition on all five programs, we get five tables:

# Estimate ATETs for the programs
atets = matrix(NA,4,2)
for (i in 1:5) {
  print(label_w[i])
  APO_atet = APO_dml_atet(y,cDML$APO$m_mat,cDML$APO$w_mat,cDML$APO$e_mat,cDML$APO$cf_mat,treated=i)
  ATET = ATE_dml(APO_atet)
  summary(ATET)
  atets[i-1,] = ATET$results[i-1,1:2]
}
[1] "No program"
                             ATET        SE       t         p    
job search - no program -1.061727  0.130743 -8.1207 4.717e-16 ***
vocational - no program  3.250641  0.425757  7.6350 2.291e-14 ***
computer - no program    3.195185  0.432797  7.3826 1.571e-13 ***
language - no program    2.449338  0.383402  6.3884 1.688e-10 ***
vocational - job search  4.312368  0.438981  9.8236 < 2.2e-16 ***
computer - job search    4.256912  0.445830  9.5483 < 2.2e-16 ***
language - job search    3.511065  0.398179  8.8178 < 2.2e-16 ***
computer - vocational   -0.055456  0.602127 -0.0921    0.9266    
language - vocational   -0.801303  0.568047 -1.4106    0.1584    
language - computer     -0.745847  0.573260 -1.3011    0.1932    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
[1] "Job search"
                            ATET       SE       t         p    
job search - no program -0.98007  0.12116 -8.0891 6.116e-16 ***
vocational - no program  3.02888  0.47650  6.3565 2.078e-10 ***
computer - no program    3.43079  0.46849  7.3231 2.452e-13 ***
language - no program    2.75514  0.42315  6.5110 7.520e-11 ***
vocational - job search  4.00895  0.48375  8.2873 < 2.2e-16 ***
computer - job search    4.41086  0.47582  9.2699 < 2.2e-16 ***
language - job search    3.73521  0.43112  8.6639 < 2.2e-16 ***
computer - vocational    0.40191  0.66017  0.6088    0.5427    
language - vocational   -0.27374  0.62994 -0.4345    0.6639    
language - computer     -0.67565  0.62361 -1.0834    0.2786    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
[1] "Vocational"
                             ATET        SE       t         p    
job search - no program -1.382461  0.155460 -8.8927 < 2.2e-16 ***
vocational - no program  2.787897  0.392552  7.1020 1.243e-12 ***
computer - no program    2.575450  0.467496  5.5090 3.622e-08 ***
language - no program    2.867676  0.402898  7.1176 1.110e-12 ***
vocational - job search  4.170358  0.423084  9.8570 < 2.2e-16 ***
computer - job search    3.957911  0.485932  8.1450 3.863e-16 ***
language - job search    4.250137  0.426540  9.9642 < 2.2e-16 ***
computer - vocational   -0.212447  0.587166 -0.3618    0.7175    
language - vocational    0.079779  0.545000  0.1464    0.8836    
language - computer      0.292226  0.591884  0.4937    0.6215    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
[1] "Computer"
                            ATET       SE       t         p    
job search - no program -1.26680  0.15684 -8.0769 6.761e-16 ***
vocational - no program  3.10856  0.48660  6.3884 1.688e-10 ***
computer - no program    3.56250  0.39968  8.9134 < 2.2e-16 ***
language - no program    3.37081  0.47934  7.0322 2.053e-12 ***
vocational - job search  4.37536  0.50556  8.6545 < 2.2e-16 ***
computer - job search    4.82930  0.42993 11.2329 < 2.2e-16 ***
language - job search    4.63761  0.50019  9.2717 < 2.2e-16 ***
computer - vocational    0.45394  0.60194  0.7541    0.4508    
language - vocational    0.26225  0.66088  0.3968    0.6915    
language - computer     -0.19169  0.59775 -0.3207    0.7485    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
[1] "Language"
                            ATET       SE       t         p    
job search - no program -0.60883  0.19432 -3.1332 0.0017301 ** 
vocational - no program  4.02450  0.79810  5.0426 4.605e-07 ***
computer - no program    3.36646  0.73306  4.5924 4.391e-06 ***
language - no program    1.04701  0.29307  3.5725 0.0003538 ***
vocational - job search  4.63333  0.80990  5.7208 1.065e-08 ***
computer - job search    3.97529  0.74590  5.3296 9.880e-08 ***
language - job search    1.65585  0.33816  4.8966 9.776e-07 ***
computer - vocational   -0.65804  1.05962 -0.6210 0.5345920    
language - vocational   -2.97748  0.84299 -3.5320 0.0004127 ***
language - computer     -2.31944  0.78089 -2.9703 0.0029766 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1



Like in the paper, we focus here and in the following on the comparison of programs vs. no program and ignore the comparisons between programs. The average effects are very similar to those reported in the paper, which shows that the average effects are robust to the alternative seed.

# Plot ATE and ATET together
ates = cDML$ATE$results[1:4,1:2]
df = data.frame(Estimand = c(rep("ATE",4),rep("ATET",4)),
                program = factor(rep(label_w[-1],2),label_w[-1]),
                effect = c(ates[,1],atets[,1]))
df$low = df$effect - 1.96 * c(ates[,2],atets[,2])
df$up = df$effect + 1.96 * c(ates[,2],atets[,2])

ggplot(data = df,mapping = aes(x = program, y = effect,ymin = low,
                                   ymax = up,group = Estimand, linetype = Estimand)) +
          geom_point(size=2,position = position_dodge(width = 0.4)) +  
          geom_errorbar(width=0.2,position = position_dodge(width = 0.4))  + 
          theme_bw() + theme(axis.text.x = element_text(angle = 45, hjust = 1),axis.title.x=element_blank()) +
          ylab("Average treatment effects") + geom_hline(yintercept = 0) +
          theme(text=element_text(family="serif",size = 14, colour="black")) 




Heterogeneous effects

The output of the causalDML command contains an APO_dml object and an ATE_dml object that carry a gamma and delta matrix storing the doubly robust APO and ATE scores, respectively. The delta matrix of the ATE_dml object can be used to conveniently estimate heterogeneous effects.

GATEs

Group average treatment effects (GATEs) represent the average effects in different subgroups. Such subgroup analyses are an integral part of most program evaluations and usually require to rerun the preferred method in subsamples. Using the doubly robust ATE score as pseudo outcome in a standard OLS regression and a group indicator as explanatory variable estimates GATEs.

The results shown in the following are very similar to those presented in Table 4 of the paper, suggesting that also the GATEs are not sensitive to the choice of the seed.

Gender

## GATEs gender
for (i in 1:4) {
  print(label_w[i+1])
  temp_ols = lm(cDML$ATE$delta[,i] ~ z_blp$female)
  print(coeftest(temp_ols, vcov = vcovHC(temp_ols)))
}
[1] "Job search"

t test of coefficients:

             Estimate Std. Error t value  Pr(>|t|)    
(Intercept)  -1.30196    0.16520 -7.8811 3.296e-15 ***
z_blp$female  0.59799    0.25324  2.3613   0.01821 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

[1] "Vocational"

t test of coefficients:

             Estimate Std. Error t value  Pr(>|t|)    
(Intercept)   3.76423    0.54987  6.8457 7.679e-12 ***
z_blp$female -1.23955    0.86366 -1.4352    0.1512    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

[1] "Computer"

t test of coefficients:

             Estimate Std. Error t value Pr(>|t|)    
(Intercept)   2.15071    0.59528  3.6129 0.000303 ***
z_blp$female  2.46847    0.85507  2.8869 0.003892 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

[1] "Language"

t test of coefficients:

             Estimate Std. Error t value  Pr(>|t|)    
(Intercept)   3.24428    0.46511  6.9753 3.082e-12 ***
z_blp$female -1.71740    0.77643 -2.2119   0.02698 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1


Foreigner

## GATEs foreigner
for (i in 1:4) {
  print(label_w[i+1])
  temp_ols = lm(cDML$ATE$delta[,i] ~ z_blp$foreigner)
  print(coeftest(temp_ols, vcov = vcovHC(temp_ols)))
}
[1] "Job search"

t test of coefficients:

                Estimate Std. Error t value  Pr(>|t|)    
(Intercept)     -1.29340    0.15933 -8.1175 4.843e-16 ***
z_blp$foreigner  0.70197    0.25746  2.7265  0.006403 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

[1] "Vocational"

t test of coefficients:

                Estimate Std. Error t value Pr(>|t|)    
(Intercept)      2.41846    0.53053  4.5586 5.16e-06 ***
z_blp$foreigner  2.20381    0.88661  2.4857  0.01293 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

[1] "Computer"

t test of coefficients:

                Estimate Std. Error t value  Pr(>|t|)    
(Intercept)      3.66128    0.49838  7.3464 2.061e-13 ***
z_blp$foreigner -1.16680    0.93833 -1.2435    0.2137    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

[1] "Language"

t test of coefficients:

                Estimate Std. Error t value  Pr(>|t|)    
(Intercept)      3.47048    0.52174  6.6518 2.919e-11 ***
z_blp$foreigner -2.70691    0.71890 -3.7654 0.0001665 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1


Employability

## GATEs employability
for (i in 1:4) {
  print(label_w[i+1])
  temp_ols = lm(cDML$ATE$delta[,i] ~ z_blp$employability)
  print(coeftest(temp_ols, vcov = vcovHC(temp_ols)))
}
[1] "Job search"

t test of coefficients:

                     Estimate Std. Error t value Pr(>|t|)   
(Intercept)          -0.20813    0.32970 -0.6313  0.52786   
z_blp$employability2 -0.90582    0.36020 -2.5148  0.01191 * 
z_blp$employability3 -1.62687    0.49524 -3.2850  0.00102 **
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

[1] "Vocational"

t test of coefficients:

                     Estimate Std. Error t value  Pr(>|t|)    
(Intercept)            5.6555     1.0312  5.4845 4.164e-08 ***
z_blp$employability2  -2.6602     1.1489 -2.3156  0.020585 *  
z_blp$employability3  -4.7576     1.5087 -3.1534  0.001614 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

[1] "Computer"

t test of coefficients:

                     Estimate Std. Error t value  Pr(>|t|)    
(Intercept)            5.3963     1.0863  4.9676 6.796e-07 ***
z_blp$employability2  -2.4153     1.1966 -2.0186   0.04353 *  
z_blp$employability3  -3.7462     1.6566 -2.2613   0.02374 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

[1] "Language"

t test of coefficients:

                     Estimate Std. Error t value  Pr(>|t|)    
(Intercept)           2.86716    0.84162  3.4067 0.0006579 ***
z_blp$employability2 -0.49344    0.95351 -0.5175 0.6048096    
z_blp$employability3 -0.11146    1.46490 -0.0761 0.9393486    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1



Best linear prediction of GATE

We can also model effect heterogeneity in a multivariate OLS. Again the ATE score is reused as a pseudo-outcome in a plain vanilla OLS regression with heteroscedasticity robust standard errors. The estimated coefficients are again mostly in line with the ones reported in Table 5 of the paper.

## BLP CATEs
for (i in 1:4) {
  print(label_w[i+1])
  temp_df = data.frame(y = cDML$ATE$delta[,i])
  temp_df = cbind(temp_df,z_blp)
  temp_ols = lm("y ~ .",data = temp_df)
  print(coeftest(temp_ols, vcov = vcovHC(temp_ols)))
}
[1] "Job search"

t test of coefficients:

                    Estimate Std. Error t value  Pr(>|t|)    
(Intercept)        -0.427987   0.703382 -0.6085   0.54288    
female              0.236418   0.266986  0.8855   0.37589    
age                 0.022365   0.014863  1.5048   0.13238    
foreigner           0.489428   0.264692  1.8491   0.06446 .  
employability2     -0.617320   0.369749 -1.6696   0.09501 .  
employability3     -1.148397   0.510107 -2.2513   0.02437 *  
past_incomein10000 -0.264551   0.064185 -4.1217 3.766e-05 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

[1] "Vocational"

t test of coefficients:

                    Estimate Std. Error t value Pr(>|t|)   
(Intercept)         4.573663   2.386915  1.9161 0.055352 . 
female             -2.169673   0.921052 -2.3556 0.018494 * 
age                 0.100046   0.049381  2.0260 0.042770 * 
foreigner           1.583702   0.899843  1.7600 0.078417 . 
employability2     -1.821302   1.169122 -1.5578 0.119277   
employability3     -3.365911   1.538792 -2.1874 0.028719 * 
past_incomein10000 -0.687101   0.230396 -2.9823 0.002862 **
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

[1] "Computer"

t test of coefficients:

                    Estimate Std. Error t value Pr(>|t|)  
(Intercept)         4.958203   2.447231  2.0260  0.04276 *
female              1.837452   0.911824  2.0151  0.04389 *
age                 0.045730   0.050252  0.9100  0.36281  
foreigner          -1.507276   0.962790 -1.5655  0.11746  
employability2     -2.129763   1.219728 -1.7461  0.08080 .
employability3     -3.350109   1.684136 -1.9892  0.04668 *
past_incomein10000 -0.405734   0.187346 -2.1657  0.03034 *
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

[1] "Language"

t test of coefficients:

                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)         6.026019   2.091576  2.8811 0.003964 ** 
female             -1.344921   0.809590 -1.6612 0.096671 .  
age                -0.067032   0.044410 -1.5094 0.131202    
foreigner          -2.649330   0.741761 -3.5717 0.000355 ***
employability2     -1.092292   0.978464 -1.1163 0.264284    
employability3     -1.160974   1.502992 -0.7724 0.439856    
past_incomein10000  0.324794   0.179486  1.8096 0.070366 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
rm(temp_df,temp_ols)



Kernel regression GATEs

Next, we reuse the ATE score as pseudo outcome in a one-dimensional kernel regression. The causalDML package implements this based on the np package.

The figures show the results in the following order: job search, vocational, computer and language. They are all very similar to the ones provided in the paper.

## Kernel regression CATEs
list_kr_cates_inc = vector("list",4)
for (i in 1:4) {
  list_kr_cates_inc[[i]] = kr_cate(cDML$ATE$delta[,i],x[,"past_income"])
}
# Plot Kernel regression CATEs
for (i in 1:4) {
  print(plot(list_kr_cates_inc[[i]],z_label="Past income",yrange=c(-5,10)))
}



Series regression GATEs

Next, we reuse the ATE score as pseudo outcome in a one-dimensional series regression. The causalDML package implements this based on the crs package.

The figures show the results in the following order: job search, vocational, computer and language. The only notable difference occurs for language programs. The estmated function in the paper is S-shaped, while here it is linear. However, the observation that job seekers with lower previous income benefit less from language training is by and large confirmed.

## Series regression GATEs
list_sr_cates_inc = vector("list",4)
for (i in 1:4) {
  list_sr_cates_inc[[i]] = spline_cate(cDML$ATE$delta[,i],x[,"past_income"])
}
# Plot Series regression GATEs
for (i in 1:4) {
  print(plot(list_sr_cates_inc[[i]],z_label="Past income",yrange=c(-5,10)))
}



Individualized average treatment effects

We further predict the individualized average treatment effects (IATEs) using the DR- and the NDR-learner. Like in the paper, we focus on the out-of-sample variant described in Algorithms 1 and 2 of the paper that are implemented using the ndr_learner function of the causalDML package.

The results confirm the potentially erratic behavior of the DR-learner. Like in the paper, predicted IATEs can take values outside of the possible bounds. However, the outliers here occur also for vocational training. Most importantly, the NDR-learner stabilizes the estimates substantially also in this replication.

## (N)DR-learner
ndr = ndr_learner(y,w,x,ml_w = list(forest),ml_y = list(forest),ml_tau = list(forest),compare_all = FALSE)
# Plot the results
df_box = NULL
for (i in 1:4) {
  df = data.frame("DRL" = ndr$cates[i,,1], "NDRL" = ndr$cates[i,,2])
  df = gather(df)
  df = cbind(label_w[i+1],df)
  colnames(df)[1] = "label"
  df_box = rbind(df_box,df)
}
ggplot(data=df_box) + geom_boxplot( aes(x=factor(label,label_w[-1]),y=value,fill=key)) +
  theme_bw() + theme(axis.title.x=element_blank(),legend.title = element_blank()) +
  ylab("Individualized average treatment effect") + geom_hline(yintercept = 0) + geom_hline(yintercept = -31,linetype="dashed") +
  geom_hline(yintercept = 31,linetype="dashed") +
  theme(text=element_text(family="serif",size = 16, colour="black"),axis.text.x = element_text(angle = 45, hjust = 1)) + 
  scale_fill_grey(start = 0.9,end=0.4)


Also the classification analysis shows that the same eight variables as in Table 6 of the paper are most predictive of being in the highest or lowest quintile of the IATE distribution:

# Classification analysis
diff = matrix(NA,ncol(x),4)
rownames(diff) = label_x
colnames(diff) = label_w[-1]
for (i in 1:4) {
  diff[,i] = clan(ndr$cates[i,,2],scale(x))[,5][-1] - clan(ndr$cates[i,,2],scale(x))[,1][-1]
}
round(diff[order(rowMaxs(abs(diff)),decreasing=T),][1:8,],digits=2)
                                                 Job search Vocational Computer Language
Previous job: unskilled worker                         1.03       0.58     0.34    -1.32
Mother tongue other than German, French, Italian       0.74       0.57     0.00    -1.30
Past income                                           -1.29      -0.92    -1.14     1.17
Swiss citizen                                         -0.73      -0.49     0.07     1.25
Qualification: some degree                            -0.96      -0.54    -0.40     1.24
Qualification: unskilled                               0.84       0.34     0.31    -1.14
Previous job: skilled worker                          -0.81      -0.31    -0.12     0.96
Fraction of months employed last 2 years              -0.92      -0.34    -0.45     0.31




Optimal assigment rules

Finally, the gamma matrix of the APO_dml object can be used as input for the policytree R package. Figure 5 in the paper displayed the chosen program in the final leaf, which required messing with the code of the package. Here, we apply the standard package plot function that numerates the programs: (1) no program, (2) job search, (3) vocational, (4) computer and (5) language.

We observe that the depth one trees with five covariates (first graph below) is identical to Figure 5a. However, the other trees chose different splits indicating that the choice of the seeds might matter for the estimated assignment rules.

## Estimate optimal assignment rule estimation
# Depth one
pt_low1 = policy_tree(x_pt_low,cDML$APO$gamma,depth = 1)
pt_high1 = policy_tree(x_pt_high,cDML$APO$gamma,depth = 1)

# Depth two
pt_low2 = policy_tree(x_pt_low,cDML$APO$gamma,depth = 2)
pt_high2 = policy_tree(x_pt_high,cDML$APO$gamma,depth = 2)
## Plot optimal assignment rule estimation
plot(pt_low1)

plot(pt_high1)

plot(pt_low2)

plot(pt_high2)


# save.image("Notebook_image.RData")
LS0tDQp0aXRsZTogIlJlcGxpY2F0aW9uIE5vdGVib29rIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQotLS0NCg0KIyAiRG91YmxlIE1hY2hpbmUgTGVhcm5pbmcgYmFzZWQgUHJvZ3JhbSBFdmFsdWF0aW9uIHVuZGVyIFVuY29uZm91bmRlZG5lc3MiDQojIyBNaWNoYWVsIEMuIEtuYXVzDQojIyMgVmVyc2lvbiAyDQoNClRoaXMgTm90ZWJvb2sgcmVwbGljYXRlcyB0aGUgbWFpbiByZXN1bHRzIG9mIHRoZSBbcGFwZXJdKGh0dHBzOi8vYXJ4aXYub3JnL2Ficy8yMDAzLjAzMTkxKSB1c2luZyBhIGRpZmZlcmVudCBzZWVkIChyZXN1bHRzIGluIHRoZSBwYXBlciBhcmUgb2J0YWluZWQgd2l0aCAxMjM0LCBiZWxvdyAxMjM0NSBpcyBzcGVjaWZpZWQpLiBUaGUgbm90ZWJvb2sgc2hvdWxkIGhlbHAgdG8gLi4uDQoNCi0gc2hvdyB0aGUgdW5kZXJseWluZyBjb2RlIGZvciByZXNlYXJjaGVycyBpbnRlcmVzdGVkIGluIHJlcGxpY2F0aW5nIHRoZSBhbmFseXNpcyB1c2luZyB0aGUgW3NhbWUgZGF0YV0oaHR0cHM6Ly9mb3JzYmFzZS51bmlsLmNoL3Byb2plY3Qvc3R1ZHktcHVibGljLW92ZXJ2aWV3LzE3MDM1LzEvKSBvciBhZGFwdGluZyBpdCB0byBuZXcgZGF0YXNldHMNCg0KLSBzaG93IHRoYXQgbW9zdCBvZiB0aGUgcmVzdWx0cyByZXBvcnRlZCBpbiB0aGUgcGFwZXIgYXJlIG5vdCBzZW5zaXRpdmUgdG8gdGhlIHNlZWQgdXNlZCB0byBpbml0aWFsaXplIHRoZSByYW5kb20gbnVtYmVyIGdlbmVyYXRvcnMgdGhhdCBhcmUgY2FsbGVkIGZvciBkZWZpbmluZyB0aGUgY3Jvc3MtZml0dGluZyBmb2xkcyBhbmQgd2l0aGluIHJhbmRvbSBmb3Jlc3RzDQoNClRoZSBleHBsYW5hdGlvbnMgdGhyb3VnaG91dCB0aGUgbm90ZWJvb2sgYXJlIGtlcHQgc2hvcnQuIFBsZWFzZSBzZWUgdGhlIFtwYXBlcl0oaHR0cHM6Ly9hcnhpdi5vcmcvYWJzLzIwMDMuMDMxOTEpIGZvciBtb3JlIGV4cGxhbmF0aW9ucyByZWdhcmRpbmcgdGhlIHBhcmFtZXRlcnMgb2YgaW50ZXJlc3QgYW5kIHRoZWlyIERvdWJsZSBNYWNoaW5lIExlYXJuaW5nIGJhc2VkIGVzdGltYXRpb24uDQoNCkNsaWNrIG9uIHRoZSAiQ29kZSIgYnV0dG9uIG9uIHRoZSB0b3AgcmlnaHQgb2YgdGhpcyBub3RlYm9vayB0byAiSGlkZSBBbGwgQ29kZSIgYW5kIHNlZSBvbmx5IHRoZSByZXN1bHRzIG9yICJEb3dubG9hZCBSbWQiIHRvIGV4dHJhY3QgdGhlIHVuZGVybHlpbmcgY29kZS4NCg0KUnVubmluZyB0aGUgYW5hbHlzZXMgaW4gdGhpcyBub3RlYm9vayB0b29rIHJvdWdobHkgdHdvIGRheXMgb24gYSBbU1dJVENIZW5naW5lXShodHRwczovL3d3dy5zd2l0Y2guY2gvZW5naW5lcy8pIHdpdGggZWlnaHQgY29yZXMgYW5kIDMyIEdCIFJBTS4NCg0KDQo8YnI+DQo8YnI+DQoNCiMjIFByZXBhcmF0aW9uDQoNClRoZSBhbmFseXNpcyBpcyBtb3N0bHkgYmFzZWQgb24gdGhlICpbY2F1c2FsRE1MXShodHRwczovL2dpdGh1Yi5jb20vTUNLbmF1cy9jYXVzYWxETUwpKiBSLXBhY2thZ2UgdGhhdCB3YXMgZGV2ZWxvcGVkIGluIHBhcmFsbGVsIHRvIHRoZSBwYXBlci4gVGhlIHByZXBhcmF0aW9uIG9mIHRoZSAqIkRhdGFiYXNlX1NBTE1QX01DSzIwMjAuUmRhdGEiKiB0aGF0IGlzIGxvYWRlZCBjYW4gYmUgcmVwbGljYXRlZCBieSByZXF1ZXN0aW5nIHRoZSBbZGF0YSB2aWEgRk9SU2Jhc2VdKGh0dHBzOi8vZm9yc2Jhc2UudW5pbC5jaC9wcm9qZWN0L3N0dWR5LXB1YmxpYy1vdmVydmlldy8xNzAzNS8xLykgYW5kIHJ1bm5pbmcgdGhlIFtkYXRhIHByZXBhcmF0aW9uIGZpbGVdKGh0dHBzOi8vZ2l0aHViLmNvbS9NQ0tuYXVzL21ja25hdXMuZ2l0aHViLmlvL2Jsb2IvbWFzdGVyL2Fzc2V0cy9jb2RlL0RhdGFfcHJlcGFyYXRpb25fTUNLMjAyMC5SKS4NCg0KDQpgYGB7cn0NCiMjIERlZmluZSBhbmQgc2V0IHNlZWQNCnNlZWQgPSAxMjM0NQ0Kc2V0LnNlZWQoc2VlZCkNCg0KIyMgTG9hZCByZWxldmFudCBwYWNrYWdlcywgaW4gY2FzZSB5b3UgZGlkIG5vdCB5ZXQgaW5zdGFsbCBjYXVzYWxETUwgdW5jb21tZW50IHRoZSBuZXh0IHR3byBsaW5lcw0KIyBsaWJyYXJ5KGRldnRvb2xzKQ0KIyBpbnN0YWxsX2dpdGh1YihyZXBvPSJNQ0tuYXVzL2NhdXNhbERNTCIpDQpsaWJyYXJ5KGNhdXNhbERNTCkNCmxpYnJhcnkocG9saWN5dHJlZSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShsbXRlc3QpDQpsaWJyYXJ5KHNhbmR3aWNoKQ0KbGlicmFyeShtYXRyaXhTdGF0cykNCg0KIyMgU2V0IHRvIHBhdGggd2hlcmUgZGF0YSBhcmUgc3RvcmVkIGZvciByZXBsaWNhdGlvbg0KIyBzZXR3ZCgiQzovVXNlcnMvQWRtaW5pc3RyYXRvci9zd2l0Y2hkcml2ZS9QYXBlcnMvTXVsdGkgVHJlYXQgTGFib3IvUGFwZXIvRGF0YSIpDQpsb2FkKCJEYXRhYmFzZV9TQUxNUF9NQ0syMDIwLlJEYXRhIikNCmBgYA0KDQo8YnI+DQo8YnI+DQoNCiMgQXZlcmFnZSBlZmZlY3RzDQoNClRoZSAqY2F1c2FsRE1MKiBjb21tYW5kIGVzdGltYXRlcyBmaXJzdCB0aGUgY3Jvc3MtZml0dGVkIG51aXNhbmNlIHBhcmFtZXRlcnMgYW5kIHVzZXMgdGhlbSB0byBjYWxjdWxhdGUgdGhlIGRvdWJsZSByb2J1c3Qgc2NvcmVzIGZvciBBUE8gYW5kIEFURSwgd2hpY2ggYXJlIHRoZW4gdXNlZCB0byBlc3RpbWF0ZSB0aGUgQVBPIGFuZCBBVEUoVCkuDQoNCmBgYHtyfQ0KIyBDcmVhdGUgdGhlIE1MIG1ldGhvZCB0byBiZSB1c2VkIGZvciBudWlzYW5jZSBwYXJhbWV0ZXIgcHJlZGljdGlvbg0KZm9yZXN0ID0gY3JlYXRlX21ldGhvZCgiZm9yZXN0X2dyZiIsYXJncz1saXN0KHR1bmUucGFyYW1ldGVycyA9ICJhbGwiLHNlZWQ9c2VlZCkpDQoNCiMgUnVuIHRoZSBtYWluIGZ1bmN0aW9uIHRoYXQgb3V0cHV0cyBudWlzYW5jZSBwYXJhbWV0ZXJzLCBBUE8gYW5kIEFURQ0KY0RNTCA9IGNhdXNhbERNTCh5LHcseCxtbF93PWxpc3QoZm9yZXN0KSxtbF95PWxpc3QoZm9yZXN0KSkNCmBgYA0KDQo8YnI+DQo8YnI+DQoNCiMjIEF2ZXJhZ2UgcG90ZW50aWFsIG91dGNvbWUgKEFQTykNCg0KVGhlIGZvbGxvd2luZyB0YWJsZSBhbmQgcGxvdCBzaG93cyB0aGUgRE1MIGVzdGltYXRlZCBBUE9zLg0KDQpgYGB7cn0NCnN1bW1hcnkoY0RNTCRBUE8pDQpwbG90KGNETUwkQVBPLGxhYmVsX3cpDQpgYGANCg0KPGJyPg0KPGJyPg0KDQoNCiMjIEF2ZXJhZ2UgdHJlYXRtZW50IGVmZmVjdHMgKEFURSBhbmQgQVRFVCkNCg0KIyMjIEFURQ0KDQpUaGUgZm9sbG93aW5nIHRhYmxlIHNob3dzIHRoZSByZXN1bHRzIG9mIGFsbCBwb3NzaWJsZSBBVEVzIGdpdmVuIHRoZSBmaXZlIGRpZmZlcmVudCB0cmVhdG1lbnRzOg0KDQpgYGB7cn0NCnN1bW1hcnkoY0RNTCRBVEUpDQpgYGANCg0KPGJyPg0KDQojIyMgQVRFVA0KDQpUaGUgZm9sbG93aW5nIHRhYmxlcyBzaG93IHRoZSByZXN1bHRzIGZvciBhbGwgcG9zc2libGUgQVRFVHMgZ2l2ZW4gdGhlIGZpdmUgZGlmZmVyZW50IHRyZWF0bWVudHMuIFRoZSBmaXJzdCBsaW5lIHNob3dzIGZvciBleGFtcGxlIGFuIGVmZmVjdCBvZiAtMS4wNDUgZm9yIGJlaW5nIGluIGEgam9iIHNlYXJjaCBwcm9ncmFtIHZzLiBubyBwcm9ncmFtIGZvciAqdGhvc2Ugd2hvIHdlcmUgYWN0dWFsbHkgb2JzZXJ2ZWQgaW4gbm8gcHJvZ3JhbSosIG9yIGZvcm1hbGx5IGFkYXB0aW5nIHRoZSBub3RhdGlvbiBvZiB0aGUgcGFwZXIgJEVbWShqbyBifnNlYXJjaCkgLSBZKG5vfnByb2dyYW0pIFxtaWQgVyA9IG5vfnByb2dyYW1dJC4gQXMgd2UgY2FuIGNvbmRpdGlvbiBvbiBhbGwgZml2ZSBwcm9ncmFtcywgd2UgZ2V0IGZpdmUgdGFibGVzOg0KDQpgYGB7cn0NCiMgRXN0aW1hdGUgQVRFVHMgZm9yIHRoZSBwcm9ncmFtcw0KYXRldHMgPSBtYXRyaXgoTkEsNCwyKQ0KZm9yIChpIGluIDE6NSkgew0KICBwcmludChsYWJlbF93W2ldKQ0KICBBUE9fYXRldCA9IEFQT19kbWxfYXRldCh5LGNETUwkQVBPJG1fbWF0LGNETUwkQVBPJHdfbWF0LGNETUwkQVBPJGVfbWF0LGNETUwkQVBPJGNmX21hdCx0cmVhdGVkPWkpDQogIEFURVQgPSBBVEVfZG1sKEFQT19hdGV0KQ0KICBzdW1tYXJ5KEFURVQpDQogIGF0ZXRzW2ktMSxdID0gQVRFVCRyZXN1bHRzW2ktMSwxOjJdDQp9DQpgYGANCg0KPGJyPg0KPGJyPg0KDQoNCkxpa2UgaW4gdGhlIHBhcGVyLCB3ZSBmb2N1cyBoZXJlIGFuZCBpbiB0aGUgZm9sbG93aW5nIG9uIHRoZSBjb21wYXJpc29uIG9mIHByb2dyYW1zIHZzLiBubyBwcm9ncmFtIGFuZCBpZ25vcmUgdGhlIGNvbXBhcmlzb25zIGJldHdlZW4gcHJvZ3JhbXMuIFRoZSBhdmVyYWdlIGVmZmVjdHMgYXJlIHZlcnkgc2ltaWxhciB0byB0aG9zZSByZXBvcnRlZCBpbiB0aGUgcGFwZXIsIHdoaWNoIHNob3dzIHRoYXQgdGhlIGF2ZXJhZ2UgZWZmZWN0cyBhcmUgcm9idXN0IHRvIHRoZSBhbHRlcm5hdGl2ZSBzZWVkLg0KDQpgYGB7cn0NCiMgUGxvdCBBVEUgYW5kIEFURVQgdG9nZXRoZXINCmF0ZXMgPSBjRE1MJEFURSRyZXN1bHRzWzE6NCwxOjJdDQpkZiA9IGRhdGEuZnJhbWUoRXN0aW1hbmQgPSBjKHJlcCgiQVRFIiw0KSxyZXAoIkFURVQiLDQpKSwNCiAgICAgICAgICAgICAgICBwcm9ncmFtID0gZmFjdG9yKHJlcChsYWJlbF93Wy0xXSwyKSxsYWJlbF93Wy0xXSksDQogICAgICAgICAgICAgICAgZWZmZWN0ID0gYyhhdGVzWywxXSxhdGV0c1ssMV0pKQ0KZGYkbG93ID0gZGYkZWZmZWN0IC0gMS45NiAqIGMoYXRlc1ssMl0sYXRldHNbLDJdKQ0KZGYkdXAgPSBkZiRlZmZlY3QgKyAxLjk2ICogYyhhdGVzWywyXSxhdGV0c1ssMl0pDQoNCmdncGxvdChkYXRhID0gZGYsbWFwcGluZyA9IGFlcyh4ID0gcHJvZ3JhbSwgeSA9IGVmZmVjdCx5bWluID0gbG93LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5bWF4ID0gdXAsZ3JvdXAgPSBFc3RpbWFuZCwgbGluZXR5cGUgPSBFc3RpbWFuZCkpICsNCiAgICAgICAgICBnZW9tX3BvaW50KHNpemU9Mixwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMC40KSkgKyAgDQogICAgICAgICAgZ2VvbV9lcnJvcmJhcih3aWR0aD0wLjIscG9zaXRpb24gPSBwb3NpdGlvbl9kb2RnZSh3aWR0aCA9IDAuNCkpICArIA0KICAgICAgICAgIHRoZW1lX2J3KCkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCkpICsNCiAgICAgICAgICB5bGFiKCJBdmVyYWdlIHRyZWF0bWVudCBlZmZlY3RzIikgKyBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwKSArDQogICAgICAgICAgdGhlbWUodGV4dD1lbGVtZW50X3RleHQoZmFtaWx5PSJzZXJpZiIsc2l6ZSA9IDE0LCBjb2xvdXI9ImJsYWNrIikpIA0KYGBgDQoNCjxicj4NCjxicj4NCjxicj4NCg0KIyBIZXRlcm9nZW5lb3VzIGVmZmVjdHMNCg0KVGhlIG91dHB1dCBvZiB0aGUgKmNhdXNhbERNTCogY29tbWFuZCBjb250YWlucyBhbiAqQVBPX2RtbCogb2JqZWN0IGFuZCBhbiAqQVRFX2RtbCogb2JqZWN0IHRoYXQgY2FycnkgYSAqZ2FtbWEqIGFuZCAqZGVsdGEqIG1hdHJpeCBzdG9yaW5nIHRoZSBkb3VibHkgcm9idXN0IEFQTyBhbmQgQVRFIHNjb3JlcywgcmVzcGVjdGl2ZWx5LiBUaGUgKmRlbHRhKiBtYXRyaXggb2YgdGhlICpBVEVfZG1sKiBvYmplY3QgY2FuIGJlIHVzZWQgdG8gY29udmVuaWVudGx5IGVzdGltYXRlIGhldGVyb2dlbmVvdXMgZWZmZWN0cy4NCg0KIyMgR0FURXMNCg0KR3JvdXAgYXZlcmFnZSB0cmVhdG1lbnQgZWZmZWN0cyAoR0FURXMpIHJlcHJlc2VudCB0aGUgYXZlcmFnZSBlZmZlY3RzIGluIGRpZmZlcmVudCBzdWJncm91cHMuIFN1Y2ggc3ViZ3JvdXAgYW5hbHlzZXMgYXJlIGFuIGludGVncmFsIHBhcnQgb2YgbW9zdCBwcm9ncmFtIGV2YWx1YXRpb25zIGFuZCB1c3VhbGx5IHJlcXVpcmUgdG8gcmVydW4gdGhlIHByZWZlcnJlZCBtZXRob2QgaW4gc3Vic2FtcGxlcy4gVXNpbmcgdGhlIGRvdWJseSByb2J1c3QgQVRFIHNjb3JlIGFzIHBzZXVkbyBvdXRjb21lIGluIGEgc3RhbmRhcmQgT0xTIHJlZ3Jlc3Npb24gYW5kIGEgZ3JvdXAgaW5kaWNhdG9yIGFzIGV4cGxhbmF0b3J5IHZhcmlhYmxlIGVzdGltYXRlcyBHQVRFcy4NCg0KVGhlIHJlc3VsdHMgc2hvd24gaW4gdGhlIGZvbGxvd2luZyBhcmUgdmVyeSBzaW1pbGFyIHRvIHRob3NlIHByZXNlbnRlZCBpbiBUYWJsZSA0IG9mIHRoZSBwYXBlciwgc3VnZ2VzdGluZyB0aGF0IGFsc28gdGhlIEdBVEVzIGFyZSBub3Qgc2Vuc2l0aXZlIHRvIHRoZSBjaG9pY2Ugb2YgdGhlIHNlZWQuDQoNCiMjIyBHZW5kZXINCg0KYGBge3J9DQojIyBHQVRFcyBnZW5kZXINCmZvciAoaSBpbiAxOjQpIHsNCiAgcHJpbnQobGFiZWxfd1tpKzFdKQ0KICB0ZW1wX29scyA9IGxtKGNETUwkQVRFJGRlbHRhWyxpXSB+IHpfYmxwJGZlbWFsZSkNCiAgcHJpbnQoY29lZnRlc3QodGVtcF9vbHMsIHZjb3YgPSB2Y292SEModGVtcF9vbHMpKSkNCn0NCmBgYA0KDQo8YnI+DQoNCiMjIyBGb3JlaWduZXINCg0KYGBge3J9DQojIyBHQVRFcyBmb3JlaWduZXINCmZvciAoaSBpbiAxOjQpIHsNCiAgcHJpbnQobGFiZWxfd1tpKzFdKQ0KICB0ZW1wX29scyA9IGxtKGNETUwkQVRFJGRlbHRhWyxpXSB+IHpfYmxwJGZvcmVpZ25lcikNCiAgcHJpbnQoY29lZnRlc3QodGVtcF9vbHMsIHZjb3YgPSB2Y292SEModGVtcF9vbHMpKSkNCn0NCmBgYA0KPGJyPg0KDQoNCiMjIyBFbXBsb3lhYmlsaXR5DQoNCmBgYHtyfQ0KIyMgR0FURXMgZW1wbG95YWJpbGl0eQ0KZm9yIChpIGluIDE6NCkgew0KICBwcmludChsYWJlbF93W2krMV0pDQogIHRlbXBfb2xzID0gbG0oY0RNTCRBVEUkZGVsdGFbLGldIH4gel9ibHAkZW1wbG95YWJpbGl0eSkNCiAgcHJpbnQoY29lZnRlc3QodGVtcF9vbHMsIHZjb3YgPSB2Y292SEModGVtcF9vbHMpKSkNCn0NCmBgYA0KDQo8YnI+DQo8YnI+DQoNCiMjIEJlc3QgbGluZWFyIHByZWRpY3Rpb24gb2YgR0FURQ0KDQpXZSBjYW4gYWxzbyBtb2RlbCBlZmZlY3QgaGV0ZXJvZ2VuZWl0eSBpbiBhIG11bHRpdmFyaWF0ZSBPTFMuIEFnYWluIHRoZSBBVEUgc2NvcmUgaXMgcmV1c2VkIGFzIGEgcHNldWRvLW91dGNvbWUgaW4gYSBwbGFpbiB2YW5pbGxhIE9MUyByZWdyZXNzaW9uIHdpdGggaGV0ZXJvc2NlZGFzdGljaXR5IHJvYnVzdCBzdGFuZGFyZCBlcnJvcnMuIFRoZSBlc3RpbWF0ZWQgY29lZmZpY2llbnRzIGFyZSBhZ2FpbiBtb3N0bHkgaW4gbGluZSB3aXRoIHRoZSBvbmVzIHJlcG9ydGVkIGluIFRhYmxlIDUgb2YgdGhlIHBhcGVyLg0KDQoNCmBgYHtyfQ0KIyMgQkxQIENBVEVzDQpmb3IgKGkgaW4gMTo0KSB7DQogIHByaW50KGxhYmVsX3dbaSsxXSkNCiAgdGVtcF9kZiA9IGRhdGEuZnJhbWUoeSA9IGNETUwkQVRFJGRlbHRhWyxpXSkNCiAgdGVtcF9kZiA9IGNiaW5kKHRlbXBfZGYsel9ibHApDQogIHRlbXBfb2xzID0gbG0oInkgfiAuIixkYXRhID0gdGVtcF9kZikNCiAgcHJpbnQoY29lZnRlc3QodGVtcF9vbHMsIHZjb3YgPSB2Y292SEModGVtcF9vbHMpKSkNCn0NCnJtKHRlbXBfZGYsdGVtcF9vbHMpDQpgYGANCg0KPGJyPg0KPGJyPg0KDQojIyBLZXJuZWwgcmVncmVzc2lvbiBHQVRFcw0KDQpOZXh0LCB3ZSByZXVzZSB0aGUgQVRFIHNjb3JlIGFzIHBzZXVkbyBvdXRjb21lIGluIGEgb25lLWRpbWVuc2lvbmFsIGtlcm5lbCByZWdyZXNzaW9uLiBUaGUgICpbY2F1c2FsRE1MXShodHRwczovL2dpdGh1Yi5jb20vTUNLbmF1cy9jYXVzYWxETUwpKiBwYWNrYWdlIGltcGxlbWVudHMgdGhpcyBiYXNlZCBvbiB0aGUgKltucF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL25wL2luZGV4Lmh0bWwpKiBwYWNrYWdlLg0KDQpUaGUgZmlndXJlcyBzaG93IHRoZSByZXN1bHRzIGluIHRoZSBmb2xsb3dpbmcgb3JkZXI6IGpvYiBzZWFyY2gsIHZvY2F0aW9uYWwsIGNvbXB1dGVyIGFuZCBsYW5ndWFnZS4gVGhleSBhcmUgYWxsIHZlcnkgc2ltaWxhciB0byB0aGUgb25lcyBwcm92aWRlZCBpbiB0aGUgcGFwZXIuDQoNCg0KYGBge3J9DQojIyBLZXJuZWwgcmVncmVzc2lvbiBDQVRFcw0KbGlzdF9rcl9jYXRlc19pbmMgPSB2ZWN0b3IoImxpc3QiLDQpDQpmb3IgKGkgaW4gMTo0KSB7DQogIGxpc3Rfa3JfY2F0ZXNfaW5jW1tpXV0gPSBrcl9jYXRlKGNETUwkQVRFJGRlbHRhWyxpXSx4WywicGFzdF9pbmNvbWUiXSkNCn0NCmBgYA0KDQpgYGB7cn0NCiMgUGxvdCBLZXJuZWwgcmVncmVzc2lvbiBDQVRFcw0KZm9yIChpIGluIDE6NCkgew0KICBwcmludChwbG90KGxpc3Rfa3JfY2F0ZXNfaW5jW1tpXV0sel9sYWJlbD0iUGFzdCBpbmNvbWUiLHlyYW5nZT1jKC01LDEwKSkpDQp9DQpgYGANCg0KPGJyPg0KPGJyPg0KDQojIyBTZXJpZXMgcmVncmVzc2lvbiBHQVRFcw0KDQpOZXh0LCB3ZSByZXVzZSB0aGUgQVRFIHNjb3JlIGFzIHBzZXVkbyBvdXRjb21lIGluIGEgb25lLWRpbWVuc2lvbmFsIHNlcmllcyByZWdyZXNzaW9uLiBUaGUgICpbY2F1c2FsRE1MXShodHRwczovL2dpdGh1Yi5jb20vTUNLbmF1cy9jYXVzYWxETUwpKiBwYWNrYWdlIGltcGxlbWVudHMgdGhpcyBiYXNlZCBvbiB0aGUgKltjcnNdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9jcnMvaW5kZXguaHRtbCkqIHBhY2thZ2UuDQoNClRoZSBmaWd1cmVzIHNob3cgdGhlIHJlc3VsdHMgaW4gdGhlIGZvbGxvd2luZyBvcmRlcjogam9iIHNlYXJjaCwgdm9jYXRpb25hbCwgY29tcHV0ZXIgYW5kIGxhbmd1YWdlLiBUaGUgb25seSBub3RhYmxlIGRpZmZlcmVuY2Ugb2NjdXJzIGZvciBsYW5ndWFnZSBwcm9ncmFtcy4gVGhlIGVzdG1hdGVkIGZ1bmN0aW9uIGluIHRoZSBwYXBlciBpcyBTLXNoYXBlZCwgd2hpbGUgaGVyZSBpdCBpcyBsaW5lYXIuIEhvd2V2ZXIsIHRoZSBvYnNlcnZhdGlvbiB0aGF0IGpvYiBzZWVrZXJzIHdpdGggbG93ZXIgcHJldmlvdXMgaW5jb21lIGJlbmVmaXQgbGVzcyBmcm9tIGxhbmd1YWdlIHRyYWluaW5nIGlzIGJ5IGFuZCBsYXJnZSBjb25maXJtZWQuDQoNCg0KYGBge3J9DQojIyBTZXJpZXMgcmVncmVzc2lvbiBHQVRFcw0KbGlzdF9zcl9jYXRlc19pbmMgPSB2ZWN0b3IoImxpc3QiLDQpDQpmb3IgKGkgaW4gMTo0KSB7DQogIGxpc3Rfc3JfY2F0ZXNfaW5jW1tpXV0gPSBzcGxpbmVfY2F0ZShjRE1MJEFURSRkZWx0YVssaV0seFssInBhc3RfaW5jb21lIl0pDQp9DQpgYGANCg0KYGBge3J9DQojIFBsb3QgU2VyaWVzIHJlZ3Jlc3Npb24gR0FURXMNCmZvciAoaSBpbiAxOjQpIHsNCiAgcHJpbnQocGxvdChsaXN0X3NyX2NhdGVzX2luY1tbaV1dLHpfbGFiZWw9IlBhc3QgaW5jb21lIix5cmFuZ2U9YygtNSwxMCkpKQ0KfQ0KYGBgDQoNCjxicj4NCjxicj4NCg0KIyMgSW5kaXZpZHVhbGl6ZWQgYXZlcmFnZSB0cmVhdG1lbnQgZWZmZWN0cw0KDQpXZSBmdXJ0aGVyIHByZWRpY3QgdGhlIGluZGl2aWR1YWxpemVkIGF2ZXJhZ2UgdHJlYXRtZW50IGVmZmVjdHMgKElBVEVzKSB1c2luZyB0aGUgRFItIGFuZCB0aGUgTkRSLWxlYXJuZXIuIExpa2UgaW4gdGhlIHBhcGVyLCB3ZSBmb2N1cyBvbiB0aGUgb3V0LW9mLXNhbXBsZSB2YXJpYW50IGRlc2NyaWJlZCBpbiBBbGdvcml0aG1zIDEgYW5kIDIgb2YgdGhlIHBhcGVyIHRoYXQgYXJlIGltcGxlbWVudGVkIHVzaW5nIHRoZSAqbmRyX2xlYXJuZXIqIGZ1bmN0aW9uIG9mIHRoZSAqW2NhdXNhbERNTF0oaHR0cHM6Ly9naXRodWIuY29tL01DS25hdXMvY2F1c2FsRE1MKSogcGFja2FnZS4NCg0KVGhlIHJlc3VsdHMgY29uZmlybSB0aGUgcG90ZW50aWFsbHkgZXJyYXRpYyBiZWhhdmlvciBvZiB0aGUgRFItbGVhcm5lci4gTGlrZSBpbiB0aGUgcGFwZXIsIHByZWRpY3RlZCBJQVRFcyBjYW4gdGFrZSB2YWx1ZXMgb3V0c2lkZSBvZiB0aGUgcG9zc2libGUgYm91bmRzLiBIb3dldmVyLCB0aGUgb3V0bGllcnMgaGVyZSBvY2N1ciBhbHNvIGZvciB2b2NhdGlvbmFsIHRyYWluaW5nLiBNb3N0IGltcG9ydGFudGx5LCB0aGUgTkRSLWxlYXJuZXIgc3RhYmlsaXplcyB0aGUgZXN0aW1hdGVzIHN1YnN0YW50aWFsbHkgYWxzbyBpbiB0aGlzIHJlcGxpY2F0aW9uLg0KDQoNCmBgYHtyfQ0KIyMgKE4pRFItbGVhcm5lcg0KbmRyID0gbmRyX2xlYXJuZXIoeSx3LHgsbWxfdyA9IGxpc3QoZm9yZXN0KSxtbF95ID0gbGlzdChmb3Jlc3QpLG1sX3RhdSA9IGxpc3QoZm9yZXN0KSxjb21wYXJlX2FsbCA9IEZBTFNFKQ0KYGBgDQoNCg0KDQpgYGB7cn0NCiMgUGxvdCB0aGUgcmVzdWx0cw0KZGZfYm94ID0gTlVMTA0KZm9yIChpIGluIDE6NCkgew0KICBkZiA9IGRhdGEuZnJhbWUoIkRSTCIgPSBuZHIkY2F0ZXNbaSwsMV0sICJORFJMIiA9IG5kciRjYXRlc1tpLCwyXSkNCiAgZGYgPSBnYXRoZXIoZGYpDQogIGRmID0gY2JpbmQobGFiZWxfd1tpKzFdLGRmKQ0KICBjb2xuYW1lcyhkZilbMV0gPSAibGFiZWwiDQogIGRmX2JveCA9IHJiaW5kKGRmX2JveCxkZikNCn0NCmdncGxvdChkYXRhPWRmX2JveCkgKyBnZW9tX2JveHBsb3QoIGFlcyh4PWZhY3RvcihsYWJlbCxsYWJlbF93Wy0xXSkseT12YWx1ZSxmaWxsPWtleSkpICsNCiAgdGhlbWVfYncoKSArIHRoZW1lKGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCksbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKSArDQogIHlsYWIoIkluZGl2aWR1YWxpemVkIGF2ZXJhZ2UgdHJlYXRtZW50IGVmZmVjdCIpICsgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCkgKyBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAtMzEsbGluZXR5cGU9ImRhc2hlZCIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMzEsbGluZXR5cGU9ImRhc2hlZCIpICsNCiAgdGhlbWUodGV4dD1lbGVtZW50X3RleHQoZmFtaWx5PSJzZXJpZiIsc2l6ZSA9IDE2LCBjb2xvdXI9ImJsYWNrIiksYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgKyANCiAgc2NhbGVfZmlsbF9ncmV5KHN0YXJ0ID0gMC45LGVuZD0wLjQpDQpgYGANCg0KPGJyPg0KDQpBbHNvIHRoZSBjbGFzc2lmaWNhdGlvbiBhbmFseXNpcyBzaG93cyB0aGF0IHRoZSBzYW1lIGVpZ2h0IHZhcmlhYmxlcyBhcyBpbiBUYWJsZSA2IG9mIHRoZSBwYXBlciBhcmUgbW9zdCBwcmVkaWN0aXZlIG9mIGJlaW5nIGluIHRoZSBoaWdoZXN0IG9yIGxvd2VzdCBxdWludGlsZSBvZiB0aGUgSUFURSBkaXN0cmlidXRpb246DQoNCmBgYHtyfQ0KIyBDbGFzc2lmaWNhdGlvbiBhbmFseXNpcw0KZGlmZiA9IG1hdHJpeChOQSxuY29sKHgpLDQpDQpyb3duYW1lcyhkaWZmKSA9IGxhYmVsX3gNCmNvbG5hbWVzKGRpZmYpID0gbGFiZWxfd1stMV0NCmZvciAoaSBpbiAxOjQpIHsNCiAgZGlmZlssaV0gPSBjbGFuKG5kciRjYXRlc1tpLCwyXSxzY2FsZSh4KSlbLDVdWy0xXSAtIGNsYW4obmRyJGNhdGVzW2ksLDJdLHNjYWxlKHgpKVssMV1bLTFdDQp9DQpyb3VuZChkaWZmW29yZGVyKHJvd01heHMoYWJzKGRpZmYpKSxkZWNyZWFzaW5nPVQpLF1bMTo4LF0sZGlnaXRzPTIpDQpgYGANCg0KPGJyPg0KPGJyPg0KPGJyPg0KDQojIyBPcHRpbWFsIGFzc2lnbWVudCBydWxlcw0KDQpGaW5hbGx5LCB0aGUgKmdhbW1hKiBtYXRyaXggb2YgdGhlICpBUE9fZG1sKiBvYmplY3QgY2FuIGJlIHVzZWQgYXMgaW5wdXQgZm9yIHRoZSAqW3BvbGljeXRyZWVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9wb2xpY3l0cmVlL2luZGV4Lmh0bWwpKiBSIHBhY2thZ2UuIEZpZ3VyZSA1IGluIHRoZSBwYXBlciBkaXNwbGF5ZWQgdGhlIGNob3NlbiBwcm9ncmFtIGluIHRoZSBmaW5hbCBsZWFmLCB3aGljaCByZXF1aXJlZCBtZXNzaW5nIHdpdGggdGhlIGNvZGUgb2YgdGhlIHBhY2thZ2UuIEhlcmUsIHdlIGFwcGx5IHRoZSBzdGFuZGFyZCBwYWNrYWdlICpwbG90KiBmdW5jdGlvbiB0aGF0IG51bWVyYXRlcyB0aGUgcHJvZ3JhbXM6ICgxKSBubyBwcm9ncmFtLCAoMikgam9iIHNlYXJjaCwgKDMpIHZvY2F0aW9uYWwsICg0KSBjb21wdXRlciBhbmQgKDUpIGxhbmd1YWdlLg0KDQpXZSBvYnNlcnZlIHRoYXQgdGhlIGRlcHRoIG9uZSB0cmVlcyB3aXRoIGZpdmUgY292YXJpYXRlcyAoZmlyc3QgZ3JhcGggYmVsb3cpIGlzIGlkZW50aWNhbCB0byBGaWd1cmUgNWEuIEhvd2V2ZXIsIHRoZSBvdGhlciB0cmVlcyBjaG9zZSBkaWZmZXJlbnQgc3BsaXRzIGluZGljYXRpbmcgdGhhdCB0aGUgY2hvaWNlIG9mIHRoZSBzZWVkcyBtaWdodCBtYXR0ZXIgZm9yIHRoZSBlc3RpbWF0ZWQgYXNzaWdubWVudCBydWxlcy4NCg0KYGBge3J9DQojIyBFc3RpbWF0ZSBvcHRpbWFsIGFzc2lnbm1lbnQgcnVsZSBlc3RpbWF0aW9uDQojIERlcHRoIG9uZQ0KcHRfbG93MSA9IHBvbGljeV90cmVlKHhfcHRfbG93LGNETUwkQVBPJGdhbW1hLGRlcHRoID0gMSkNCnB0X2hpZ2gxID0gcG9saWN5X3RyZWUoeF9wdF9oaWdoLGNETUwkQVBPJGdhbW1hLGRlcHRoID0gMSkNCg0KIyBEZXB0aCB0d28NCnB0X2xvdzIgPSBwb2xpY3lfdHJlZSh4X3B0X2xvdyxjRE1MJEFQTyRnYW1tYSxkZXB0aCA9IDIpDQpwdF9oaWdoMiA9IHBvbGljeV90cmVlKHhfcHRfaGlnaCxjRE1MJEFQTyRnYW1tYSxkZXB0aCA9IDIpDQpgYGANCg0KDQpgYGB7cn0NCiMjIFBsb3Qgb3B0aW1hbCBhc3NpZ25tZW50IHJ1bGUgZXN0aW1hdGlvbg0KcGxvdChwdF9sb3cxKQ0KcGxvdChwdF9oaWdoMSkNCnBsb3QocHRfbG93MikNCnBsb3QocHRfaGlnaDIpDQoNCiMgc2F2ZS5pbWFnZSgiTm90ZWJvb2tfaW1hZ2UuUkRhdGEiKQ0KYGBgDQoNCg0KDQo=