We still use the lalonde data from the MatchIt package and use the propensity score model that we found out in R example 7.

library("MatchIt")
data("lalonde")

model <- glm(treat ~ . , data = lalonde[, -9], family = "binomial")
eps <- predict(model, type = "response")

lalonde

1. Esimate the inverse probability weights

The figures are not very informative as there are units with extremely large weights. We can perfrom log transformation to make the histogram more informative.

## Calculate the raw weights (before normalization)
## Weights to estimate ATE
n.treated <- sum(lalonde$treat == 1)
n.control <- sum(lalonde$treat == 0)
weights <- ifelse(lalonde$treat == 1, 1/eps, 1/(1 - eps))

## Check the weights histogram
library(ggplot2)
temp.data <- data.frame(weights = weights, treated = as.factor(lalonde$treat))
ggplot(temp.data, aes(x = weights, fill = treated, color = treated)) + 
  geom_histogram(alpha = 0.5, position = "identity") + 
  xlab("Weights") 


## We can perform log transformation to make the histogram more informative
ggplot(temp.data, aes(x = weights, fill = treated, color = treated)) + 
  geom_histogram(alpha = 0.5, position = "identity") +  scale_x_log10() + 
  xlab("Weights") 

temp.data.eps <- data.frame(eps = eps, treated = as.factor(lalonde$treat))
ggplot(temp.data.eps, aes(x = eps, fill = treated, color = treated)) + 
  geom_histogram(alpha = 0.5, position = "identity") + xlim(c(0, 1)) +
  ggtitle("Histogram of eps before trimming")

We likely will do some trimming to make sure overlapping assumption is satisfied and probably will also improve covariates balancing.

We can check covariate balancing from the love plot after inverse probability weighting. Here, we need to write our own function to draw the love plot.

## Need to change race (categorical) into indicators (numerical)
lalonde$black <- lalonde$race == "black"
lalonde$hispan <- lalonde$race == "hispan"
lalonde$white <- lalonde$race == "white"

## Draw love plot
love.plot = function(cov, treat,  ## cov is the matrix of covariates and treat is a vector of treatment assignment
                     weights = rep(1, length(treat)),
                     plot = F) 
{
    
    ## mean with normalized weights \sum w_i x_i / (\sum w_i)
  treat.means <- colSums(cov[treat == 1,] * weights[treat == 1])/sum(weights[treat == 1])
  treat.var <- colSums(t(t(cov[treat == 1,]) - treat.means)^2 *
                          weights[treat == 1])/sum(weights[treat == 1])
  
  control.means <- colSums(cov[treat == 0,] * weights[treat == 0])/sum(weights[treat == 0])
  control.var <- colSums(t(t(cov[treat == 0,]) - control.means)^2 *
                          weights[treat == 0])/sum(weights[treat == 0])
  
  ## the standardized mean differences for every covariate
  smd <- (treat.means - control.means)/sqrt((treat.var + control.var)/2)
  names(smd) <- colnames(cov)
  
  if (plot == T) {
    plot.data <- data.frame(smd = smd, covariates = names(smd))
    range <- max(abs(smd))
    ggplot(plot.data) + geom_point(aes(x = as.numeric(smd), y = covariates)) +
      geom_vline(xintercept = 0) + xlim(-range, range) +
      labs(x = 'Standardized Difference in Means')
  }
  return(smd)
}

raw.smd <- love.plot(lalonde[, c(2:3, 5:8, 10:12)], lalonde$treat)
weighted.smd <- love.plot(lalonde[, c(2:3, 5:8, 10:12)], lalonde$treat, weights = weights)


plot.data <- data.frame(smd = c(raw.smd, weighted.smd), 
                        covariates = c(names(raw.smd), names(weighted.smd)),
                        category = c(rep("Original", length(raw.smd)), rep("IPW", length(weighted.smd))))
range <- max(abs(plot.data$smd))

ggplot(plot.data) + geom_point(aes(x = as.numeric(smd), y = covariates, color = category)) +
      geom_vline(xintercept = c(-0.1, -0.05, 0, 0.05, 0.1),
                 linetype = c("solid", "dashed", "solid", "dashed", "solid")) + 
      xlim(-range, range) +
      labs(x = 'Standardized Difference in Means')

Covariate balancing is greatly improved after IPW. There is still room for further improvement.

2. Trimming

2.1 Check weights and covariates balancing after trimming

  • Perform trimming and check the histogram of weights
rm.idx <- which(eps < 0.1 | eps > 0.9)

temp.data.trimmed <- temp.data[-rm.idx, ]
lalonde.trimmed <- lalonde[-rm.idx, ]

## Check the histogram of weights again

ggplot(temp.data.trimmed, aes(x = weights, fill = treated, color = treated)) + 
  geom_histogram(alpha = 0.5, position = "identity") +  scale_x_log10() + 
  xlab("Weights") + ggtitle("Histogram of weights after trimming")

  • Check covariate balancing after trimming.
raw.smd <- love.plot(lalonde.trimmed[, c(2:3, 5:8, 10:12)], lalonde.trimmed$treat)
weighted.smd <- love.plot(lalonde.trimmed[, c(2:3, 5:8, 10:12)], lalonde.trimmed$treat, 
                          weights = weights[-rm.idx])
plot.data <- data.frame(smd = c(raw.smd, weighted.smd), 
                        covariates = c(names(raw.smd), names(weighted.smd)),
                        category = c(rep("Original", length(raw.smd)), rep("IPW", length(weighted.smd))))
range <- max(abs(plot.data$smd))
ggplot(plot.data) + geom_point(aes(x = as.numeric(smd), y = covariates, color = category)) +
      geom_vline(xintercept = c(-0.1, -0.05, 0, 0.05, 0.1),
                 linetype = c("solid", "dashed", "solid", "dashed", "solid")) + 
      xlim(-range, range) +
      labs(x = 'Standardized Difference in Means')

Though covariate balanicng of the raw data gets better, covariates balancing with IPW does not improve.

2.2 Refit the propensity score model after trimming

We do this because the covariate balancing the previous step is not satisfying.

## Refit the propensity score model
model.trimmed <- glm(treat ~ . , data = lalonde.trimmed[, -(9:12)], family = "binomial")
eps.trimmed <- predict(model.trimmed, type = "response")
weights.trimmed <- ifelse(lalonde.trimmed$treat == 1, 1/eps.trimmed, 1/(1 - eps.trimmed))
temp.data.trimmed$weights <- weights.trimmed

## Check the histogram of weights again

ggplot(temp.data.trimmed, aes(x = weights, fill = treated, color = treated)) + 
  geom_histogram(alpha = 0.5, position = "identity") +  scale_x_log10() + 
  xlab("Weights") + ggtitle("Histogram of weights after trimming")

  • Check covariate balancing again.
raw.smd <- love.plot(lalonde.trimmed[, c(2:3, 5:8, 10:12)], lalonde.trimmed$treat)
weighted.smd <- love.plot(lalonde.trimmed[, c(2:3, 5:8, 10:12)], lalonde.trimmed$treat, 
                          weights = weights.trimmed)
plot.data <- data.frame(smd = c(raw.smd, weighted.smd), 
                        covariates = c(names(raw.smd), names(weighted.smd)),
                        category = c(rep("Original", length(raw.smd)), rep("IPW", length(weighted.smd))))
range <- max(abs(plot.data$smd))
ggplot(plot.data) + geom_point(aes(x = as.numeric(smd), y = covariates, color = category)) +
      geom_vline(xintercept = c(-0.1, -0.05, 0, 0.05, 0.1),
                 linetype = c("solid", "dashed", "solid", "dashed", "solid")) + 
      xlim(-range, range) +
      labs(x = 'Standardized Difference in Means')

3. Estimate the ATE using IPW

lm.result <- lm(re78 ~ treat, weights = weights.trimmed, data = lalonde.trimmed)
summary(lm.result)

Call:
lm(formula = re78 ~ treat, data = lalonde.trimmed, weights = weights.trimmed)

Weighted Residuals:
   Min     1Q Median     3Q    Max 
-16478  -7345  -2741   3852  61182 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   5402.7      537.4  10.053   <2e-16 ***
treat         1155.1      756.9   1.526    0.128    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 9888 on 339 degrees of freedom
Multiple R-squared:  0.006823,  Adjusted R-squared:  0.003894 
F-statistic: 2.329 on 1 and 339 DF,  p-value: 0.1279

We use the Sandwich estimator allowing heteroscedastic noise levels between the treated and control grousp

library(sandwich)
tau_hat <- lm.result$coefficients[2]
SE <- sqrt(diag(vcovHC(lm.result, type = "HC2")))[2]

## get the 95% CI
result <- c(tau_hat, SE, c(tau_hat- 1.96 * SE, tau_hat + 1.96 * SE))
names(result) <- c("est", "sd", "CI_lower", "CI_upper")
result
      est        sd  CI_lower  CI_upper 
1155.0913  827.0484 -465.9235 2776.1062 

IPW_estimator <- function(W, Y, X) {
  ## Estimate propensity score
  model <- glm(W ~ X , family = "binomial")
  eps <- predict(model, type = "response")
  
  ## Calculate the weights
  weights <- ifelse(W == 1, 1/eps, 1/(1 - eps))
  
  ## Calculate weighted mean difference between treated and control group
  est <- lm(Y ~ W, weights = weights)$coef[2]
  return(est)
}

X <- model.matrix(model.trimmed)
Y <- lalonde.trimmed$re78
W <- lalonde.trimmed$treat

IPW_bootstrap <- function(W, Y, X, n.boot = 200){
  est <- IPW_estimator(W, Y, X)
  IPWboot <- sapply(1:n.boot, function(i) {
    id.boot <- sample(1:length(W), replace = T)
    IPW_estimator(W[id.boot], Y[id.boot], X[id.boot, ])
  })
  return(c(est, sd(IPWboot)))
}

SE_boostrap <- IPW_bootstrap(W, Y, X, 5000)[2]
result_bootstrap <- c(tau_hat, SE_boostrap, 
                      c(tau_hat- 1.96 * SE_boostrap, tau_hat + 1.96 * SE_boostrap))
names(result_bootstrap) <- c("est", "sd (bootstrap)", "CI_lower (bootstrap)", "CI_upper (bootstrap)")
result_bootstrap
                 est       sd (bootstrap) CI_lower (bootstrap) CI_upper (bootstrap) 
           1155.0913             869.8960            -549.9049            2860.0875 
LS0tCnRpdGxlOiAiUiBFeGFtcGxlIDk6IEludmVyc2UgcHJvcGVuc2l0eSBzY29yZSB3ZWlnaHRpbmciCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCldlIHN0aWxsIHVzZSB0aGUgbGFsb25kZSBkYXRhIGZyb20gdGhlIE1hdGNoSXQgcGFja2FnZSBhbmQgdXNlIHRoZSBwcm9wZW5zaXR5IHNjb3JlIG1vZGVsIHRoYXQgd2UgZm91bmQgb3V0IGluIFIgZXhhbXBsZSA3LgoKYGBge3J9CmxpYnJhcnkoIk1hdGNoSXQiKQpkYXRhKCJsYWxvbmRlIikKCm1vZGVsIDwtIGdsbSh0cmVhdCB+IC4gLCBkYXRhID0gbGFsb25kZVssIC05XSwgZmFtaWx5ID0gImJpbm9taWFsIikKZXBzIDwtIHByZWRpY3QobW9kZWwsIHR5cGUgPSAicmVzcG9uc2UiKQoKbGFsb25kZQpgYGAKCiMgMS4gRXNpbWF0ZSB0aGUgaW52ZXJzZSBwcm9iYWJpbGl0eSB3ZWlnaHRzIAoKLSBWaXN1YWxpemUgdGhlIHdlaWdodHMKClRoZSBmaWd1cmVzIGFyZSBub3QgdmVyeSBpbmZvcm1hdGl2ZSBhcyB0aGVyZSBhcmUgdW5pdHMgd2l0aCBleHRyZW1lbHkgbGFyZ2Ugd2VpZ2h0cy4gV2UgY2FuIHBlcmZyb20gbG9nIHRyYW5zZm9ybWF0aW9uIHRvIG1ha2UgdGhlIGhpc3RvZ3JhbSBtb3JlIGluZm9ybWF0aXZlLgpgYGB7cn0KIyMgQ2FsY3VsYXRlIHRoZSByYXcgd2VpZ2h0cyAoYmVmb3JlIG5vcm1hbGl6YXRpb24pCiMjIFdlaWdodHMgdG8gZXN0aW1hdGUgQVRFCm4udHJlYXRlZCA8LSBzdW0obGFsb25kZSR0cmVhdCA9PSAxKQpuLmNvbnRyb2wgPC0gc3VtKGxhbG9uZGUkdHJlYXQgPT0gMCkKd2VpZ2h0cyA8LSBpZmVsc2UobGFsb25kZSR0cmVhdCA9PSAxLCAxL2VwcywgMS8oMSAtIGVwcykpCgojIyBDaGVjayB0aGUgd2VpZ2h0cyBoaXN0b2dyYW0KbGlicmFyeShnZ3Bsb3QyKQp0ZW1wLmRhdGEgPC0gZGF0YS5mcmFtZSh3ZWlnaHRzID0gd2VpZ2h0cywgdHJlYXRlZCA9IGFzLmZhY3RvcihsYWxvbmRlJHRyZWF0KSkKZ2dwbG90KHRlbXAuZGF0YSwgYWVzKHggPSB3ZWlnaHRzLCBmaWxsID0gdHJlYXRlZCwgY29sb3IgPSB0cmVhdGVkKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShhbHBoYSA9IDAuNSwgcG9zaXRpb24gPSAiaWRlbnRpdHkiKSArIAogIHhsYWIoIldlaWdodHMiKSAKCiMjIFdlIGNhbiBwZXJmb3JtIGxvZyB0cmFuc2Zvcm1hdGlvbiB0byBtYWtlIHRoZSBoaXN0b2dyYW0gbW9yZSBpbmZvcm1hdGl2ZQpnZ3Bsb3QodGVtcC5kYXRhLCBhZXMoeCA9IHdlaWdodHMsIGZpbGwgPSB0cmVhdGVkLCBjb2xvciA9IHRyZWF0ZWQpKSArIAogIGdlb21faGlzdG9ncmFtKGFscGhhID0gMC41LCBwb3NpdGlvbiA9ICJpZGVudGl0eSIpICsgIHNjYWxlX3hfbG9nMTAoKSArIAogIHhsYWIoIldlaWdodHMiKSAKYGBgCgotIFZpc3VhbGl6ZSB0aGUgZXN0aW1hdGVkIHByb3BlbnNpdHkgc2NvcmVzCmBgYHtyfQp0ZW1wLmRhdGEuZXBzIDwtIGRhdGEuZnJhbWUoZXBzID0gZXBzLCB0cmVhdGVkID0gYXMuZmFjdG9yKGxhbG9uZGUkdHJlYXQpKQpnZ3Bsb3QodGVtcC5kYXRhLmVwcywgYWVzKHggPSBlcHMsIGZpbGwgPSB0cmVhdGVkLCBjb2xvciA9IHRyZWF0ZWQpKSArIAogIGdlb21faGlzdG9ncmFtKGFscGhhID0gMC41LCBwb3NpdGlvbiA9ICJpZGVudGl0eSIpICsgeGxpbShjKDAsIDEpKSArCiAgZ2d0aXRsZSgiSGlzdG9ncmFtIG9mIGVwcyBiZWZvcmUgdHJpbW1pbmciKQpgYGAKCldlIGxpa2VseSB3aWxsIGRvIHNvbWUgdHJpbW1pbmcgdG8gbWFrZSBzdXJlIG92ZXJsYXBwaW5nIGFzc3VtcHRpb24gaXMgc2F0aXNmaWVkIGFuZCBwcm9iYWJseSB3aWxsIGFsc28gaW1wcm92ZSBjb3ZhcmlhdGVzIGJhbGFuY2luZy4KCi0gQ2hlY2sgY292YXJpYXRlIGJhbGFuY2luZy4KCldlIGNhbiBjaGVjayBjb3ZhcmlhdGUgYmFsYW5jaW5nIGZyb20gdGhlIGxvdmUgcGxvdCBhZnRlciBpbnZlcnNlIHByb2JhYmlsaXR5IHdlaWdodGluZy4gSGVyZSwgd2UgbmVlZCB0byB3cml0ZSBvdXIgb3duIGZ1bmN0aW9uIHRvIGRyYXcgdGhlIGxvdmUgcGxvdC4gCmBgYHtyfQojIyBOZWVkIHRvIGNoYW5nZSByYWNlIChjYXRlZ29yaWNhbCkgaW50byBpbmRpY2F0b3JzIChudW1lcmljYWwpCmxhbG9uZGUkYmxhY2sgPC0gbGFsb25kZSRyYWNlID09ICJibGFjayIKbGFsb25kZSRoaXNwYW4gPC0gbGFsb25kZSRyYWNlID09ICJoaXNwYW4iCmxhbG9uZGUkd2hpdGUgPC0gbGFsb25kZSRyYWNlID09ICJ3aGl0ZSIKCiMjIERyYXcgbG92ZSBwbG90CmxvdmUucGxvdCA9IGZ1bmN0aW9uKGNvdiwgdHJlYXQsICAjIyBjb3YgaXMgdGhlIG1hdHJpeCBvZiBjb3ZhcmlhdGVzIGFuZCB0cmVhdCBpcyBhIHZlY3RvciBvZiB0cmVhdG1lbnQgYXNzaWdubWVudAogICAgICAgICAgICAgICAgICAgICB3ZWlnaHRzID0gcmVwKDEsIGxlbmd0aCh0cmVhdCkpLAogICAgICAgICAgICAgICAgICAgICBwbG90ID0gRikgCnsKICAgIAogICAgIyMgbWVhbiB3aXRoIG5vcm1hbGl6ZWQgd2VpZ2h0cyBcc3VtIHdfaSB4X2kgLyAoXHN1bSB3X2kpCiAgdHJlYXQubWVhbnMgPC0gY29sU3Vtcyhjb3ZbdHJlYXQgPT0gMSxdICogd2VpZ2h0c1t0cmVhdCA9PSAxXSkvc3VtKHdlaWdodHNbdHJlYXQgPT0gMV0pCiAgdHJlYXQudmFyIDwtIGNvbFN1bXModCh0KGNvdlt0cmVhdCA9PSAxLF0pIC0gdHJlYXQubWVhbnMpXjIgKgogICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodHNbdHJlYXQgPT0gMV0pL3N1bSh3ZWlnaHRzW3RyZWF0ID09IDFdKQogIAogIGNvbnRyb2wubWVhbnMgPC0gY29sU3Vtcyhjb3ZbdHJlYXQgPT0gMCxdICogd2VpZ2h0c1t0cmVhdCA9PSAwXSkvc3VtKHdlaWdodHNbdHJlYXQgPT0gMF0pCiAgY29udHJvbC52YXIgPC0gY29sU3Vtcyh0KHQoY292W3RyZWF0ID09IDAsXSkgLSBjb250cm9sLm1lYW5zKV4yICoKICAgICAgICAgICAgICAgICAgICAgICAgICB3ZWlnaHRzW3RyZWF0ID09IDBdKS9zdW0od2VpZ2h0c1t0cmVhdCA9PSAwXSkKICAKICAjIyB0aGUgc3RhbmRhcmRpemVkIG1lYW4gZGlmZmVyZW5jZXMgZm9yIGV2ZXJ5IGNvdmFyaWF0ZQogIHNtZCA8LSAodHJlYXQubWVhbnMgLSBjb250cm9sLm1lYW5zKS9zcXJ0KCh0cmVhdC52YXIgKyBjb250cm9sLnZhcikvMikKICBuYW1lcyhzbWQpIDwtIGNvbG5hbWVzKGNvdikKICAKICBpZiAocGxvdCA9PSBUKSB7CiAgICBwbG90LmRhdGEgPC0gZGF0YS5mcmFtZShzbWQgPSBzbWQsIGNvdmFyaWF0ZXMgPSBuYW1lcyhzbWQpKQogICAgcmFuZ2UgPC0gbWF4KGFicyhzbWQpKQogICAgZ2dwbG90KHBsb3QuZGF0YSkgKyBnZW9tX3BvaW50KGFlcyh4ID0gYXMubnVtZXJpYyhzbWQpLCB5ID0gY292YXJpYXRlcykpICsKICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCkgKyB4bGltKC1yYW5nZSwgcmFuZ2UpICsKICAgICAgbGFicyh4ID0gJ1N0YW5kYXJkaXplZCBEaWZmZXJlbmNlIGluIE1lYW5zJykKICB9CiAgcmV0dXJuKHNtZCkKfQoKcmF3LnNtZCA8LSBsb3ZlLnBsb3QobGFsb25kZVssIGMoMjozLCA1OjgsIDEwOjEyKV0sIGxhbG9uZGUkdHJlYXQpCndlaWdodGVkLnNtZCA8LSBsb3ZlLnBsb3QobGFsb25kZVssIGMoMjozLCA1OjgsIDEwOjEyKV0sIGxhbG9uZGUkdHJlYXQsIHdlaWdodHMgPSB3ZWlnaHRzKQoKCnBsb3QuZGF0YSA8LSBkYXRhLmZyYW1lKHNtZCA9IGMocmF3LnNtZCwgd2VpZ2h0ZWQuc21kKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIGNvdmFyaWF0ZXMgPSBjKG5hbWVzKHJhdy5zbWQpLCBuYW1lcyh3ZWlnaHRlZC5zbWQpKSwKICAgICAgICAgICAgICAgICAgICAgICAgY2F0ZWdvcnkgPSBjKHJlcCgiT3JpZ2luYWwiLCBsZW5ndGgocmF3LnNtZCkpLCByZXAoIklQVyIsIGxlbmd0aCh3ZWlnaHRlZC5zbWQpKSkpCnJhbmdlIDwtIG1heChhYnMocGxvdC5kYXRhJHNtZCkpCgpnZ3Bsb3QocGxvdC5kYXRhKSArIGdlb21fcG9pbnQoYWVzKHggPSBhcy5udW1lcmljKHNtZCksIHkgPSBjb3ZhcmlhdGVzLCBjb2xvciA9IGNhdGVnb3J5KSkgKwogICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjKC0wLjEsIC0wLjA1LCAwLCAwLjA1LCAwLjEpLAogICAgICAgICAgICAgICAgIGxpbmV0eXBlID0gYygic29saWQiLCAiZGFzaGVkIiwgInNvbGlkIiwgImRhc2hlZCIsICJzb2xpZCIpKSArIAogICAgICB4bGltKC1yYW5nZSwgcmFuZ2UpICsKICAgICAgbGFicyh4ID0gJ1N0YW5kYXJkaXplZCBEaWZmZXJlbmNlIGluIE1lYW5zJykKYGBgCgpDb3ZhcmlhdGUgYmFsYW5jaW5nIGlzIGdyZWF0bHkgaW1wcm92ZWQgYWZ0ZXIgSVBXLiBUaGVyZSBpcyBzdGlsbCByb29tIGZvciBmdXJ0aGVyIGltcHJvdmVtZW50LgoKIyAyLiBUcmltbWluZwoKIyMgMi4xIENoZWNrIHdlaWdodHMgYW5kIGNvdmFyaWF0ZXMgYmFsYW5jaW5nIGFmdGVyIHRyaW1taW5nCgotIFBlcmZvcm0gdHJpbW1pbmcgYW5kIGNoZWNrIHRoZSBoaXN0b2dyYW0gb2Ygd2VpZ2h0cwoKYGBge3J9CnJtLmlkeCA8LSB3aGljaChlcHMgPCAwLjEgfCBlcHMgPiAwLjkpCgp0ZW1wLmRhdGEudHJpbW1lZCA8LSB0ZW1wLmRhdGFbLXJtLmlkeCwgXQpsYWxvbmRlLnRyaW1tZWQgPC0gbGFsb25kZVstcm0uaWR4LCBdCgojIyBDaGVjayB0aGUgaGlzdG9ncmFtIG9mIHdlaWdodHMgYWdhaW4KCmdncGxvdCh0ZW1wLmRhdGEudHJpbW1lZCwgYWVzKHggPSB3ZWlnaHRzLCBmaWxsID0gdHJlYXRlZCwgY29sb3IgPSB0cmVhdGVkKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShhbHBoYSA9IDAuNSwgcG9zaXRpb24gPSAiaWRlbnRpdHkiKSArICBzY2FsZV94X2xvZzEwKCkgKyAKICB4bGFiKCJXZWlnaHRzIikgKyBnZ3RpdGxlKCJIaXN0b2dyYW0gb2Ygd2VpZ2h0cyBhZnRlciB0cmltbWluZyIpCmBgYAoKLSBDaGVjayBjb3ZhcmlhdGUgYmFsYW5jaW5nIGFmdGVyIHRyaW1taW5nLgoKYGBge3J9CnJhdy5zbWQgPC0gbG92ZS5wbG90KGxhbG9uZGUudHJpbW1lZFssIGMoMjozLCA1OjgsIDEwOjEyKV0sIGxhbG9uZGUudHJpbW1lZCR0cmVhdCkKd2VpZ2h0ZWQuc21kIDwtIGxvdmUucGxvdChsYWxvbmRlLnRyaW1tZWRbLCBjKDI6MywgNTo4LCAxMDoxMildLCBsYWxvbmRlLnRyaW1tZWQkdHJlYXQsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodHMgPSB3ZWlnaHRzWy1ybS5pZHhdKQpwbG90LmRhdGEgPC0gZGF0YS5mcmFtZShzbWQgPSBjKHJhdy5zbWQsIHdlaWdodGVkLnNtZCksIAogICAgICAgICAgICAgICAgICAgICAgICBjb3ZhcmlhdGVzID0gYyhuYW1lcyhyYXcuc21kKSwgbmFtZXMod2VpZ2h0ZWQuc21kKSksCiAgICAgICAgICAgICAgICAgICAgICAgIGNhdGVnb3J5ID0gYyhyZXAoIk9yaWdpbmFsIiwgbGVuZ3RoKHJhdy5zbWQpKSwgcmVwKCJJUFciLCBsZW5ndGgod2VpZ2h0ZWQuc21kKSkpKQpyYW5nZSA8LSBtYXgoYWJzKHBsb3QuZGF0YSRzbWQpKQpnZ3Bsb3QocGxvdC5kYXRhKSArIGdlb21fcG9pbnQoYWVzKHggPSBhcy5udW1lcmljKHNtZCksIHkgPSBjb3ZhcmlhdGVzLCBjb2xvciA9IGNhdGVnb3J5KSkgKwogICAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBjKC0wLjEsIC0wLjA1LCAwLCAwLjA1LCAwLjEpLAogICAgICAgICAgICAgICAgIGxpbmV0eXBlID0gYygic29saWQiLCAiZGFzaGVkIiwgInNvbGlkIiwgImRhc2hlZCIsICJzb2xpZCIpKSArIAogICAgICB4bGltKC1yYW5nZSwgcmFuZ2UpICsKICAgICAgbGFicyh4ID0gJ1N0YW5kYXJkaXplZCBEaWZmZXJlbmNlIGluIE1lYW5zJykKYGBgCgpUaG91Z2ggY292YXJpYXRlIGJhbGFuaWNuZyBvZiB0aGUgcmF3IGRhdGEgZ2V0cyBiZXR0ZXIsIGNvdmFyaWF0ZXMgYmFsYW5jaW5nIHdpdGggSVBXIGRvZXMgbm90IGltcHJvdmUuCgojIyAyLjIgUmVmaXQgdGhlIHByb3BlbnNpdHkgc2NvcmUgbW9kZWwgYWZ0ZXIgdHJpbW1pbmcKCldlIGRvIHRoaXMgYmVjYXVzZSB0aGUgY292YXJpYXRlIGJhbGFuY2luZyB0aGUgcHJldmlvdXMgc3RlcCBpcyBub3Qgc2F0aXNmeWluZy4KCmBgYHtyfQojIyBSZWZpdCB0aGUgcHJvcGVuc2l0eSBzY29yZSBtb2RlbAptb2RlbC50cmltbWVkIDwtIGdsbSh0cmVhdCB+IC4gLCBkYXRhID0gbGFsb25kZS50cmltbWVkWywgLSg5OjEyKV0sIGZhbWlseSA9ICJiaW5vbWlhbCIpCmVwcy50cmltbWVkIDwtIHByZWRpY3QobW9kZWwudHJpbW1lZCwgdHlwZSA9ICJyZXNwb25zZSIpCndlaWdodHMudHJpbW1lZCA8LSBpZmVsc2UobGFsb25kZS50cmltbWVkJHRyZWF0ID09IDEsIDEvZXBzLnRyaW1tZWQsIDEvKDEgLSBlcHMudHJpbW1lZCkpCnRlbXAuZGF0YS50cmltbWVkJHdlaWdodHMgPC0gd2VpZ2h0cy50cmltbWVkCgojIyBDaGVjayB0aGUgaGlzdG9ncmFtIG9mIHdlaWdodHMgYWdhaW4KCmdncGxvdCh0ZW1wLmRhdGEudHJpbW1lZCwgYWVzKHggPSB3ZWlnaHRzLCBmaWxsID0gdHJlYXRlZCwgY29sb3IgPSB0cmVhdGVkKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShhbHBoYSA9IDAuNSwgcG9zaXRpb24gPSAiaWRlbnRpdHkiKSArICBzY2FsZV94X2xvZzEwKCkgKyAKICB4bGFiKCJXZWlnaHRzIikgKyBnZ3RpdGxlKCJIaXN0b2dyYW0gb2Ygd2VpZ2h0cyBhZnRlciB0cmltbWluZyIpCgpgYGAKCgotIENoZWNrIGNvdmFyaWF0ZSBiYWxhbmNpbmcgYWdhaW4uCgpgYGB7cn0KcmF3LnNtZCA8LSBsb3ZlLnBsb3QobGFsb25kZS50cmltbWVkWywgYygyOjMsIDU6OCwgMTA6MTIpXSwgbGFsb25kZS50cmltbWVkJHRyZWF0KQp3ZWlnaHRlZC5zbWQgPC0gbG92ZS5wbG90KGxhbG9uZGUudHJpbW1lZFssIGMoMjozLCA1OjgsIDEwOjEyKV0sIGxhbG9uZGUudHJpbW1lZCR0cmVhdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0cyA9IHdlaWdodHMudHJpbW1lZCkKcGxvdC5kYXRhIDwtIGRhdGEuZnJhbWUoc21kID0gYyhyYXcuc21kLCB3ZWlnaHRlZC5zbWQpLCAKICAgICAgICAgICAgICAgICAgICAgICAgY292YXJpYXRlcyA9IGMobmFtZXMocmF3LnNtZCksIG5hbWVzKHdlaWdodGVkLnNtZCkpLAogICAgICAgICAgICAgICAgICAgICAgICBjYXRlZ29yeSA9IGMocmVwKCJPcmlnaW5hbCIsIGxlbmd0aChyYXcuc21kKSksIHJlcCgiSVBXIiwgbGVuZ3RoKHdlaWdodGVkLnNtZCkpKSkKcmFuZ2UgPC0gbWF4KGFicyhwbG90LmRhdGEkc21kKSkKZ2dwbG90KHBsb3QuZGF0YSkgKyBnZW9tX3BvaW50KGFlcyh4ID0gYXMubnVtZXJpYyhzbWQpLCB5ID0gY292YXJpYXRlcywgY29sb3IgPSBjYXRlZ29yeSkpICsKICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYygtMC4xLCAtMC4wNSwgMCwgMC4wNSwgMC4xKSwKICAgICAgICAgICAgICAgICBsaW5ldHlwZSA9IGMoInNvbGlkIiwgImRhc2hlZCIsICJzb2xpZCIsICJkYXNoZWQiLCAic29saWQiKSkgKyAKICAgICAgeGxpbSgtcmFuZ2UsIHJhbmdlKSArCiAgICAgIGxhYnMoeCA9ICdTdGFuZGFyZGl6ZWQgRGlmZmVyZW5jZSBpbiBNZWFucycpCmBgYAoKIyAzLiBFc3RpbWF0ZSB0aGUgQVRFIHVzaW5nIElQVwoKLSBFc3RpbWF0ZSBBVEUgYnkgd2VpZ2h0ZWQgbGVhc3Qgc3F1YXJlIHJlZ3Jlc3Npb24gCgpgYGB7cn0KbG0ucmVzdWx0IDwtIGxtKHJlNzggfiB0cmVhdCwgd2VpZ2h0cyA9IHdlaWdodHMudHJpbW1lZCwgZGF0YSA9IGxhbG9uZGUudHJpbW1lZCkKc3VtbWFyeShsbS5yZXN1bHQpCgpgYGAKCi0gU3RhdGlzdGljYWwgaW5mZXJlbmNlIHRyZWF0aW5nIHRoZSB3ZWlnaHRzIGFzIGZpeGVkLgoKV2UgdXNlIHRoZSBTYW5kd2ljaCBlc3RpbWF0b3IgYWxsb3dpbmcgaGV0ZXJvc2NlZGFzdGljIG5vaXNlIGxldmVscyBiZXR3ZWVuIHRoZSB0cmVhdGVkIGFuZCBjb250cm9sIGdyb3VzcAoKYGBge3J9CmxpYnJhcnkoc2FuZHdpY2gpCnRhdV9oYXQgPC0gbG0ucmVzdWx0JGNvZWZmaWNpZW50c1syXQpTRSA8LSBzcXJ0KGRpYWcodmNvdkhDKGxtLnJlc3VsdCwgdHlwZSA9ICJIQzIiKSkpWzJdCgojIyBnZXQgdGhlIDk1JSBDSQpyZXN1bHQgPC0gYyh0YXVfaGF0LCBTRSwgYyh0YXVfaGF0LSAxLjk2ICogU0UsIHRhdV9oYXQgKyAxLjk2ICogU0UpKQpuYW1lcyhyZXN1bHQpIDwtIGMoImVzdCIsICJzZCIsICJDSV9sb3dlciIsICJDSV91cHBlciIpCnJlc3VsdApgYGAKCi0gU3RhdGlzdGljYWwgaW5mZXJlbmNlIHVzaW5nIGJvb3RzdHJhcAoKYGBge3J9CgpJUFdfZXN0aW1hdG9yIDwtIGZ1bmN0aW9uKFcsIFksIFgpIHsKICAjIyBFc3RpbWF0ZSBwcm9wZW5zaXR5IHNjb3JlCiAgbW9kZWwgPC0gZ2xtKFcgfiBYICwgZmFtaWx5ID0gImJpbm9taWFsIikKICBlcHMgPC0gcHJlZGljdChtb2RlbCwgdHlwZSA9ICJyZXNwb25zZSIpCiAgCiAgIyMgQ2FsY3VsYXRlIHRoZSB3ZWlnaHRzCiAgd2VpZ2h0cyA8LSBpZmVsc2UoVyA9PSAxLCAxL2VwcywgMS8oMSAtIGVwcykpCiAgCiAgIyMgQ2FsY3VsYXRlIHdlaWdodGVkIG1lYW4gZGlmZmVyZW5jZSBiZXR3ZWVuIHRyZWF0ZWQgYW5kIGNvbnRyb2wgZ3JvdXAKICBlc3QgPC0gbG0oWSB+IFcsIHdlaWdodHMgPSB3ZWlnaHRzKSRjb2VmWzJdCiAgcmV0dXJuKGVzdCkKfQoKWCA8LSBtb2RlbC5tYXRyaXgobW9kZWwudHJpbW1lZCkKWSA8LSBsYWxvbmRlLnRyaW1tZWQkcmU3OApXIDwtIGxhbG9uZGUudHJpbW1lZCR0cmVhdAoKSVBXX2Jvb3RzdHJhcCA8LSBmdW5jdGlvbihXLCBZLCBYLCBuLmJvb3QgPSAyMDApewogIGVzdCA8LSBJUFdfZXN0aW1hdG9yKFcsIFksIFgpCiAgSVBXYm9vdCA8LSBzYXBwbHkoMTpuLmJvb3QsIGZ1bmN0aW9uKGkpIHsKICAgIGlkLmJvb3QgPC0gc2FtcGxlKDE6bGVuZ3RoKFcpLCByZXBsYWNlID0gVCkKICAgIElQV19lc3RpbWF0b3IoV1tpZC5ib290XSwgWVtpZC5ib290XSwgWFtpZC5ib290LCBdKQogIH0pCiAgcmV0dXJuKGMoZXN0LCBzZChJUFdib290KSkpCn0KClNFX2Jvb3N0cmFwIDwtIElQV19ib290c3RyYXAoVywgWSwgWCwgNTAwMClbMl0KcmVzdWx0X2Jvb3RzdHJhcCA8LSBjKHRhdV9oYXQsIFNFX2Jvb3N0cmFwLCAKICAgICAgICAgICAgICAgICAgICAgIGModGF1X2hhdC0gMS45NiAqIFNFX2Jvb3N0cmFwLCB0YXVfaGF0ICsgMS45NiAqIFNFX2Jvb3N0cmFwKSkKbmFtZXMocmVzdWx0X2Jvb3RzdHJhcCkgPC0gYygiZXN0IiwgInNkIChib290c3RyYXApIiwgIkNJX2xvd2VyIChib290c3RyYXApIiwgIkNJX3VwcGVyIChib290c3RyYXApIikKcmVzdWx0X2Jvb3RzdHJhcApgYGAKCgo=