Replication comment: Running the notebooks 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.2 of the paper. 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:

library(OutcomeWeights)
library(hdm)
library(grf)
library(tidyverse)
library(viridis) # for color-blind friendly plotting
library(reshape2)
library(ggridges)

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

Get outcome model and smoother matrix

The grf package does not save the nuisance parameter models. Thus, we could not retrieve the required smoother matrices after running causal_forest and instrumental_forest below. Therefore, we estimate the outcome nuisance model externally to pass the nuisance parameters later to the functions. As described in Section 5.2 of the paper, we run a default and a tuned version:

### Externally calculate outcome nuisance
rf_Y.hat_default = regression_forest(X,Y)
rf_Y.hat_tuned = regression_forest(X,Y,tune.parameters = "all")
Y.hat_default = predict(rf_Y.hat_default)$predictions
Y.hat_tuned = predict(rf_Y.hat_tuned)$predictions

Then, we extract the smoother matrix using get_forest_weights():

# And get smoother matrices
S_default = get_forest_weights(rf_Y.hat_default)
S_tuned = get_forest_weights(rf_Y.hat_tuned)

For illustration, check that random forest is an affine smoother by summing all smoother vectors and checking whether they sum to one:

cat("RF affine smoother?", 
    all.equal(rowSums(as.matrix(S_default)),
      rep(1,length(Y))
    ))
RF affine smoother? TRUE

Causal forest

Run causal forest with the externally estimated outcome nuisance:

# Run CF with the pre-specified outcome nuisance 
cf_default = causal_forest(X,Y,D,Y.hat=Y.hat_default)
cf_tuned = causal_forest(X,Y,D,Y.hat=Y.hat_tuned,tune.parameters = "all")

Get the out-of-bag CATEs:

cates_default = predict(cf_default)$predictions
cates_tuned = predict(cf_tuned)$predictions

Use the new get_outcome_weights() method, which requires to pass the externally estimated outcome smoother matrix:

omega_cf_default = get_outcome_weights(cf_default, S = S_default)
omega_cf_tuned = get_outcome_weights(cf_tuned, S = S_tuned)

Observe that the outcome weights recover the grf package output:

cat("ω'Y replicates CATE point estimates (default)?", 
    all.equal(as.numeric(omega_cf_default$omega %*% Y),
      as.numeric(cates_default)
    ))
ω'Y replicates CATE point estimates (default)? TRUE
cat("\nω'Y replicates CATE point estimates (tuned)?", 
    all.equal(as.numeric(omega_cf_tuned$omega %*% Y),
      as.numeric(cates_tuned)
    ))

ω'Y replicates CATE point estimates (tuned)? TRUE

Now calculate the absolute standardized mean differences and plot them for each CATE and variables (Figure 3a in paper):

cb_cate_default = standardized_mean_differences(X,D,omega_cf_default$omega,X)
cb_cate_tuned = standardized_mean_differences(X,D,omega_cf_tuned$omega,X)

smd_default = t(abs(cb_cate_default[,3,]))
smd_tuned = t(abs(cb_cate_tuned[,3,]))

# Melt the smd_default matrix to long format
df_default_long = melt(smd_default)
df_default_long$Group = "smd_default"  # Add a group identifier

# Melt the smd_tuned matrix to long format
df_tuned_long = melt(smd_tuned)
df_tuned_long$Group = "smd_tuned"  # Add a group identifier

# Combine the two data frames
df_long = rbind(df_default_long, df_tuned_long)

# Rename the columns for clarity
colnames(df_long) = c("Row", "Variable", "Value", "Group")

# Create the ggplot
figure3a = ggplot(df_long, aes(x = factor(Variable, levels = rev(unique(Variable))), y = Value, fill = Group)) +
  geom_boxplot(position = position_dodge(width = 0.8)) +
  labs(x = element_blank(), y = "Absolute Standardized Mean Differences") +
  scale_fill_manual(values = viridis(2),
                    name = element_blank(),
                    labels = c("default", "tuned")) +
  theme_minimal() +
  geom_hline(yintercept = 0, linetype = "solid", color = "black") + 
  coord_flip() +
  theme(legend.position = "bottom")
figure3a

Instrumental forest

Run instrumental forest with the externally estimated outcome nuisance:

# Run IF with the pre-specified outcome nuisance
ivf_default = instrumental_forest(X,Y,D,Z,Y.hat=Y.hat_default)
ivf_tuned = instrumental_forest(X,Y,D,Z,Y.hat=Y.hat_tuned,tune.parameters = "all")

Get the out-of-bag CLATEs:

clates_default = predict(ivf_default)$predictions
clates_tuned = predict(ivf_tuned)$predictions

Use the new get_outcome_weights() method, which requires to pass the externally estimated outcome smoother matrix:

omega_if_default = get_outcome_weights(ivf_default, S = S_default)
omega_if_tuned = get_outcome_weights(ivf_tuned, S = S_tuned)

Observe that the outcome weights recover the grf package output:

cat("ω'Y replicates CLATE point estimates (default)?", 
    all.equal(as.numeric(omega_if_default$omega %*% Y),
      as.numeric(clates_default)
    ))
ω'Y replicates CLATE point estimates (default)? TRUE
cat("\nω'Y replicates CLATE point estimates (tuned)?", 
    all.equal(as.numeric(omega_if_tuned$omega %*% Y),
      as.numeric(clates_tuned)
    ))

ω'Y replicates CLATE point estimates (tuned)? TRUE

Now calculate the absolute standardized mean differences and plot them for each CLATE and variables (Figure 3b in paper):

cb_clate_default = standardized_mean_differences(X,D,omega_if_default$omega,X)
cb_clate_tuned = standardized_mean_differences(X,D,omega_if_tuned$omega,X)

smd_default = t(abs(cb_clate_default[,3,]))
smd_tuned = t(abs(cb_clate_tuned[,3,]))

# Melt the smd_default matrix to long format
df_default_long = melt(smd_default)
df_default_long$Group = "smd_default"  # Add a group identifier

# Melt the smd_tuned matrix to long format
df_tuned_long = melt(smd_tuned)
df_tuned_long$Group = "smd_tuned"  # Add a group identifier

# Combine the two data frames
df_long = rbind(df_default_long, df_tuned_long)

# Rename the columns for clarity
colnames(df_long) = c("Row", "Variable", "Value", "Group")

# Create the ggplot
figure3b = ggplot(df_long, aes(x = factor(Variable, levels = rev(unique(Variable))), y = Value, fill = Group)) +
  geom_boxplot(position = position_dodge(width = 0.8)) +
  labs(x = element_blank(), y = "Absolute Standardized Mean Differences") +
  scale_fill_manual(values = viridis(2),
                    name = element_blank(),
                    labels = c("default", "tuned")) +
  theme_minimal() +
  geom_hline(yintercept = 0, linetype = "solid", color = "black") + 
  coord_flip() +
  theme(legend.position = "bottom")
figure3b

2. Supplementary results

Plot results

Here we observe that not only the default effects show much worse balancing, but they also lead to implausibly high variance in their estimates.

data = data.frame(
  value = c(cates_default, cates_tuned,clates_default, clates_tuned),
  category = rep(c("grf CATEs default", "grf CATEs tuned","grf CLATEs default", "grf CLATEs tuned"), each = length(cates_default))
)

ggplot(data, aes(y = category, x = value, fill = category)) +
  geom_boxplot(alpha = 0.7) +
  geom_vline(xintercept = 0, color = "black", linetype = "solid") +
  labs(
    x = "Estimate",
    y = "Estimator/Implementation"
  ) +
  theme_minimal() +
  theme(legend.position = "none")

# Create the ridge plot
ggplot(data, aes(x = value, y = category, fill = category)) +
  geom_density_ridges(alpha = 0.7, scale = 1) +
  labs(
    x = "Estimate",
    y = "Estimator/Implementation"
  ) +
  theme_minimal() +
  theme(legend.position = "none")

Summarize weights

Finally, we summarize the weights vectors for each C(L)ATE plotting different descriptives:

  • Minimum weight

  • Maximum weight

  • % Negative

  • Sum largest 10%

  • Sum of weights

  • Sum of absolute weights

Especially the sum of weights is interesting in light of the paper. It shows that tuning also produces weight sums closer to one in this data set. However, also negative weights are fewer and less pronounced for the tuned version:

for (i in 1:dim(summary_weights_if_tuned)[3]) {
  # Extract untreated and treated weights for each group
  sum_weights_cf_default_untreated = summary_weights_cf_default[1,,i]
  sum_weights_cf_default_treated = summary_weights_cf_default[2,,i]
  
  sum_weights_cf_tuned_untreated = summary_weights_cf_tuned[1,,i]
  sum_weights_cf_tuned_treated = summary_weights_cf_tuned[2,,i]
  
  sum_weights_if_default_untreated = summary_weights_if_default[1,,i]
  sum_weights_if_default_treated = summary_weights_if_default[2,,i]
  
  sum_weights_if_tuned_untreated = summary_weights_if_tuned[1,,i]
  sum_weights_if_tuned_treated = summary_weights_if_tuned[2,,i]
  
  # Combine all vectors into a single data frame, with a new 'Treatment' column
  df_weights <- data.frame(
    Value = c(
      sum_weights_cf_default_untreated, sum_weights_cf_default_treated,
      sum_weights_cf_tuned_untreated, sum_weights_cf_tuned_treated,
      sum_weights_if_default_untreated, sum_weights_if_default_treated,
      sum_weights_if_tuned_untreated, sum_weights_if_tuned_treated
    ),
    Group = factor(c(
      rep("CF default", length(sum_weights_cf_default_untreated) + length(sum_weights_cf_default_treated)),
      rep("CF tuned", length(sum_weights_cf_tuned_untreated) + length(sum_weights_cf_tuned_treated)),
      rep("IF default", length(sum_weights_if_default_untreated) + length(sum_weights_if_default_treated)),
      rep("IF tuned", length(sum_weights_if_tuned_untreated) + length(sum_weights_if_tuned_treated))
    )),
    Treatment = factor(c(
      rep("Untreated", length(sum_weights_cf_default_untreated)),
      rep("Treated", length(sum_weights_cf_default_treated)),
      rep("Untreated", length(sum_weights_cf_tuned_untreated)),
      rep("Treated", length(sum_weights_cf_tuned_treated)),
      rep("Untreated", length(sum_weights_if_default_untreated)),
      rep("Treated", length(sum_weights_if_default_treated)),
      rep("Untreated", length(sum_weights_if_tuned_untreated)),
      rep("Treated", length(sum_weights_if_tuned_treated))
    ))
  )
  
  # Plot with ggplot and ggridges
  g <- ggplot(df_weights, aes(x = Value, y = Group, fill = Treatment)) +
    geom_boxplot(position = position_dodge(width = 0.75)) +
    labs(x = dimnames(summary_weights_cf_tuned)[[3]][i], y = NULL) +
    theme_minimal() +
    theme(legend.position = "bottom")  # Place legend at the bottom
  print(g)
}

LS0tCnRpdGxlOiAiVHJlYXRtZW50IEVmZmVjdCBFc3RpbWF0b3JzIGFzIFdlaWdodGVkIE91dGNvbWVzIgpzdWJ0aXRsZTogIkFwcGxpY2F0aW9uIDQwMShrKSAtIGhldGVyb2dlbmVvdXMgZWZmZWN0cyIKYXV0aG9yOiAiTWljaGFlbCBDLiBLbmF1cyIKZGF0ZTogIjExLzI0IgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9mb2xkaW5nOiBzaG93Ci0tLQoKKlJlcGxpY2F0aW9uIGNvbW1lbnQ6IFJ1bm5pbmcgdGhlIG5vdGVib29rcyB3aXRoaW4gdGhlIHJlcGxpY2F0aW9uIGRvY2tlciBlbnN1cmVzIHRoYXQgcmVzdWx0cyBwZXJmZWN0bHkgcmVwbGljYXRlLiBPdGhlcndpc2UsIHJlc3VsdHMgbWlnaHQgZGlmZmVyIGRlcGVuZGluZyBvbiB3aGljaCBwYWNrYWdlIHZlcnNpb25zIHlvdSB1c2UuKgoKClRoaXMgbm90ZWJvb2sgcnVucyB0aGUgYXBwbGljYXRpb24gZGVzY3JpYmVkIGluIFNlY3Rpb24gNS4yIG9mIHRoZSBwYXBlci4gVGhlIGZpcnN0IHBhcnQgcmVwbGljYXRlcyB0aGUgcmVzdWx0cyBwcmVzZW50ZWQgaW4gdGhlIHBhcGVyLCB0aGUgc2Vjb25kIHBhcnQgcHJvdmlkZXMgc3VwcGxlbWVudGFyeSBpbmZvcm1hdGlvbgoKIyAxLiBSZXBsaWNhdGlvbiBvZiBwYXBlciByZXN1bHRzCgojIyBHZXR0aW5nIHN0YXJ0ZWQKCkZpcnN0LCBsb2FkIHBhY2thZ2VzIGFuZCBzZXQgdGhlIHNlZWQ6CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KE91dGNvbWVXZWlnaHRzKQpsaWJyYXJ5KGhkbSkKbGlicmFyeShncmYpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHZpcmlkaXMpICMgZm9yIGNvbG9yLWJsaW5kIGZyaWVuZGx5IHBsb3R0aW5nCmxpYnJhcnkocmVzaGFwZTIpCmxpYnJhcnkoZ2dyaWRnZXMpCgpzZXQuc2VlZCgxMjM0KQpgYGAKCk5leHQsIGxvYWQgdGhlIGRhdGEuIEhlcmUgd2UgdXNlIHRoZSA0MDEoaykgZGF0YSBvZiB0aGUgYGhkbWAgcGFja2FnZS4gSG93ZXZlciwgeW91IGNhbiBhZGFwdCB0aGUgZm9sbG93aW5nIGNvZGUgY2h1bmsgdG8gbG9hZCBhbnkgc3VpdGFibGUgZGF0YSBvZiB5b3VyIGNob2ljZS4gSnVzdCBtYWtlIHN1cmUgdG8gY2FsbCB0aGUgdHJlYXRtZW50IGBEYCwgY292YXJpYXRlcyBgWGAsIGFuZCBpbnN0cnVtZW50IGBaYC4gVGhlIHJlc3Qgb2YgdGhlIG5vdGVib29rIHNob3VsZCBydW4gd2l0aG91dCBmdXJ0aGVyIG1vZGlmaWNhdGlvbnMuCgpgYGB7cn0KZGF0YShwZW5zaW9uKSAjIEZpbmQgdmFyaWFibGUgZGVzY3JpcHRpb24gaWYgeW91IHR5cGUgP3BlbnNpb24gaW4gY29uc29sZQoKIyBUcmVhdG1lbnQKRCA9IHBlbnNpb24kcDQwMQojIEluc3RydW1lbnQKWiA9IHBlbnNpb24kZTQwMQojIE91dGNvbWUKWSA9IHBlbnNpb24kbmV0X3RmYQojIENvbnRyb2xzClggPSBtb2RlbC5tYXRyaXgofiAwICsgYWdlICsgZGIgKyBlZHVjICsgZnNpemUgKyBob3duICsgaW5jICsgbWFsZSArIG1hcnIgKyBwaXJhICsgdHdvZWFybiwgZGF0YSA9IHBlbnNpb24pCnZhcl9ubSA9IGMoIkFnZSIsIkJlbmVmaXQgcGVuc2lvbiIsIkVkdWNhdGlvbiIsIkZhbWlseSBzaXplIiwiSG9tZSBvd25lciIsIkluY29tZSIsIk1hbGUiLCJNYXJyaWVkIiwiSVJBIiwiVHdvIGVhcm5lcnMiKQpjb2xuYW1lcyhYKSA9IHZhcl9ubQpgYGAKCgojIyBHZXQgb3V0Y29tZSBtb2RlbCBhbmQgc21vb3RoZXIgbWF0cml4CgpUaGUgYGdyZmAgcGFja2FnZSBkb2VzIG5vdCBzYXZlIHRoZSBudWlzYW5jZSBwYXJhbWV0ZXIgbW9kZWxzLiBUaHVzLCB3ZSBjb3VsZCBub3QgcmV0cmlldmUgdGhlIHJlcXVpcmVkIHNtb290aGVyIG1hdHJpY2VzIGFmdGVyIHJ1bm5pbmcgYGNhdXNhbF9mb3Jlc3RgIGFuZCBgaW5zdHJ1bWVudGFsX2ZvcmVzdGAgYmVsb3cuIFRoZXJlZm9yZSwgd2UgZXN0aW1hdGUgdGhlIG91dGNvbWUgbnVpc2FuY2UgbW9kZWwgZXh0ZXJuYWxseSB0byBwYXNzIHRoZSBudWlzYW5jZSBwYXJhbWV0ZXJzIGxhdGVyIHRvIHRoZSBmdW5jdGlvbnMuIEFzIGRlc2NyaWJlZCBpbiBTZWN0aW9uIDUuMiBvZiB0aGUgcGFwZXIsIHdlIHJ1biBhIGRlZmF1bHQgYW5kIGEgdHVuZWQgdmVyc2lvbjoKCmBgYHtyfQojIyMgRXh0ZXJuYWxseSBjYWxjdWxhdGUgb3V0Y29tZSBudWlzYW5jZQpyZl9ZLmhhdF9kZWZhdWx0ID0gcmVncmVzc2lvbl9mb3Jlc3QoWCxZKQpyZl9ZLmhhdF90dW5lZCA9IHJlZ3Jlc3Npb25fZm9yZXN0KFgsWSx0dW5lLnBhcmFtZXRlcnMgPSAiYWxsIikKWS5oYXRfZGVmYXVsdCA9IHByZWRpY3QocmZfWS5oYXRfZGVmYXVsdCkkcHJlZGljdGlvbnMKWS5oYXRfdHVuZWQgPSBwcmVkaWN0KHJmX1kuaGF0X3R1bmVkKSRwcmVkaWN0aW9ucwpgYGAKClRoZW4sIHdlIGV4dHJhY3QgdGhlIHNtb290aGVyIG1hdHJpeCB1c2luZyBgZ2V0X2ZvcmVzdF93ZWlnaHRzKClgOgoKYGBge3J9CiMgQW5kIGdldCBzbW9vdGhlciBtYXRyaWNlcwpTX2RlZmF1bHQgPSBnZXRfZm9yZXN0X3dlaWdodHMocmZfWS5oYXRfZGVmYXVsdCkKU190dW5lZCA9IGdldF9mb3Jlc3Rfd2VpZ2h0cyhyZl9ZLmhhdF90dW5lZCkKYGBgCgpGb3IgaWxsdXN0cmF0aW9uLCBjaGVjayB0aGF0IHJhbmRvbSBmb3Jlc3QgaXMgYW4gYWZmaW5lIHNtb290aGVyIGJ5IHN1bW1pbmcgYWxsIHNtb290aGVyIHZlY3RvcnMgYW5kIGNoZWNraW5nIHdoZXRoZXIgdGhleSBzdW0gdG8gb25lOgoKYGBge3J9CmNhdCgiUkYgYWZmaW5lIHNtb290aGVyPyIsIAogICAgYWxsLmVxdWFsKHJvd1N1bXMoYXMubWF0cml4KFNfZGVmYXVsdCkpLAogICAgICByZXAoMSxsZW5ndGgoWSkpCiAgICApKQpgYGAKCgojIyBDYXVzYWwgZm9yZXN0CgpSdW4gY2F1c2FsIGZvcmVzdCB3aXRoIHRoZSBleHRlcm5hbGx5IGVzdGltYXRlZCBvdXRjb21lIG51aXNhbmNlOgoKYGBge3J9CiMgUnVuIENGIHdpdGggdGhlIHByZS1zcGVjaWZpZWQgb3V0Y29tZSBudWlzYW5jZSAKY2ZfZGVmYXVsdCA9IGNhdXNhbF9mb3Jlc3QoWCxZLEQsWS5oYXQ9WS5oYXRfZGVmYXVsdCkKY2ZfdHVuZWQgPSBjYXVzYWxfZm9yZXN0KFgsWSxELFkuaGF0PVkuaGF0X3R1bmVkLHR1bmUucGFyYW1ldGVycyA9ICJhbGwiKQpgYGAKCkdldCB0aGUgb3V0LW9mLWJhZyBDQVRFczoKCmBgYHtyfQpjYXRlc19kZWZhdWx0ID0gcHJlZGljdChjZl9kZWZhdWx0KSRwcmVkaWN0aW9ucwpjYXRlc190dW5lZCA9IHByZWRpY3QoY2ZfdHVuZWQpJHByZWRpY3Rpb25zCmBgYAoKVXNlIHRoZSBuZXcgYGdldF9vdXRjb21lX3dlaWdodHMoKWAgbWV0aG9kLCB3aGljaCByZXF1aXJlcyB0byBwYXNzIHRoZSBleHRlcm5hbGx5IGVzdGltYXRlZCBvdXRjb21lIHNtb290aGVyIG1hdHJpeDoKCmBgYHtyfQpvbWVnYV9jZl9kZWZhdWx0ID0gZ2V0X291dGNvbWVfd2VpZ2h0cyhjZl9kZWZhdWx0LCBTID0gU19kZWZhdWx0KQpvbWVnYV9jZl90dW5lZCA9IGdldF9vdXRjb21lX3dlaWdodHMoY2ZfdHVuZWQsIFMgPSBTX3R1bmVkKQpgYGAKCk9ic2VydmUgdGhhdCB0aGUgb3V0Y29tZSB3ZWlnaHRzIHJlY292ZXIgdGhlIGBncmZgIHBhY2thZ2Ugb3V0cHV0OgoKYGBge3J9CmNhdCgiz4knWSByZXBsaWNhdGVzIENBVEUgcG9pbnQgZXN0aW1hdGVzIChkZWZhdWx0KT8iLCAKICAgIGFsbC5lcXVhbChhcy5udW1lcmljKG9tZWdhX2NmX2RlZmF1bHQkb21lZ2EgJSolIFkpLAogICAgICBhcy5udW1lcmljKGNhdGVzX2RlZmF1bHQpCiAgICApKQpjYXQoIlxuz4knWSByZXBsaWNhdGVzIENBVEUgcG9pbnQgZXN0aW1hdGVzICh0dW5lZCk/IiwgCiAgICBhbGwuZXF1YWwoYXMubnVtZXJpYyhvbWVnYV9jZl90dW5lZCRvbWVnYSAlKiUgWSksCiAgICAgIGFzLm51bWVyaWMoY2F0ZXNfdHVuZWQpCiAgICApKQpgYGAKCk5vdyBjYWxjdWxhdGUgdGhlIGFic29sdXRlIHN0YW5kYXJkaXplZCBtZWFuIGRpZmZlcmVuY2VzIGFuZCBwbG90IHRoZW0gZm9yIGVhY2ggQ0FURSBhbmQgdmFyaWFibGVzIChGaWd1cmUgM2EgaW4gcGFwZXIpOgoKYGBge3J9CmNiX2NhdGVfZGVmYXVsdCA9IHN0YW5kYXJkaXplZF9tZWFuX2RpZmZlcmVuY2VzKFgsRCxvbWVnYV9jZl9kZWZhdWx0JG9tZWdhLFgpCmNiX2NhdGVfdHVuZWQgPSBzdGFuZGFyZGl6ZWRfbWVhbl9kaWZmZXJlbmNlcyhYLEQsb21lZ2FfY2ZfdHVuZWQkb21lZ2EsWCkKCnNtZF9kZWZhdWx0ID0gdChhYnMoY2JfY2F0ZV9kZWZhdWx0WywzLF0pKQpzbWRfdHVuZWQgPSB0KGFicyhjYl9jYXRlX3R1bmVkWywzLF0pKQoKIyBNZWx0IHRoZSBzbWRfZGVmYXVsdCBtYXRyaXggdG8gbG9uZyBmb3JtYXQKZGZfZGVmYXVsdF9sb25nID0gbWVsdChzbWRfZGVmYXVsdCkKZGZfZGVmYXVsdF9sb25nJEdyb3VwID0gInNtZF9kZWZhdWx0IiAgIyBBZGQgYSBncm91cCBpZGVudGlmaWVyCgojIE1lbHQgdGhlIHNtZF90dW5lZCBtYXRyaXggdG8gbG9uZyBmb3JtYXQKZGZfdHVuZWRfbG9uZyA9IG1lbHQoc21kX3R1bmVkKQpkZl90dW5lZF9sb25nJEdyb3VwID0gInNtZF90dW5lZCIgICMgQWRkIGEgZ3JvdXAgaWRlbnRpZmllcgoKIyBDb21iaW5lIHRoZSB0d28gZGF0YSBmcmFtZXMKZGZfbG9uZyA9IHJiaW5kKGRmX2RlZmF1bHRfbG9uZywgZGZfdHVuZWRfbG9uZykKCiMgUmVuYW1lIHRoZSBjb2x1bW5zIGZvciBjbGFyaXR5CmNvbG5hbWVzKGRmX2xvbmcpID0gYygiUm93IiwgIlZhcmlhYmxlIiwgIlZhbHVlIiwgIkdyb3VwIikKCiMgQ3JlYXRlIHRoZSBnZ3Bsb3QKZmlndXJlM2EgPSBnZ3Bsb3QoZGZfbG9uZywgYWVzKHggPSBmYWN0b3IoVmFyaWFibGUsIGxldmVscyA9IHJldih1bmlxdWUoVmFyaWFibGUpKSksIHkgPSBWYWx1ZSwgZmlsbCA9IEdyb3VwKSkgKwogIGdlb21fYm94cGxvdChwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMC44KSkgKwogIGxhYnMoeCA9IGVsZW1lbnRfYmxhbmsoKSwgeSA9ICJBYnNvbHV0ZSBTdGFuZGFyZGl6ZWQgTWVhbiBEaWZmZXJlbmNlcyIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSB2aXJpZGlzKDIpLAogICAgICAgICAgICAgICAgICAgIG5hbWUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiZGVmYXVsdCIsICJ0dW5lZCIpKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJzb2xpZCIsIGNvbG9yID0gImJsYWNrIikgKyAKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQpmaWd1cmUzYQpgYGAKCgojIyBJbnN0cnVtZW50YWwgZm9yZXN0CgpSdW4gaW5zdHJ1bWVudGFsIGZvcmVzdCB3aXRoIHRoZSBleHRlcm5hbGx5IGVzdGltYXRlZCBvdXRjb21lIG51aXNhbmNlOgoKYGBge3J9CiMgUnVuIElGIHdpdGggdGhlIHByZS1zcGVjaWZpZWQgb3V0Y29tZSBudWlzYW5jZQppdmZfZGVmYXVsdCA9IGluc3RydW1lbnRhbF9mb3Jlc3QoWCxZLEQsWixZLmhhdD1ZLmhhdF9kZWZhdWx0KQppdmZfdHVuZWQgPSBpbnN0cnVtZW50YWxfZm9yZXN0KFgsWSxELFosWS5oYXQ9WS5oYXRfdHVuZWQsdHVuZS5wYXJhbWV0ZXJzID0gImFsbCIpCmBgYAoKR2V0IHRoZSBvdXQtb2YtYmFnIENMQVRFczoKCmBgYHtyfQpjbGF0ZXNfZGVmYXVsdCA9IHByZWRpY3QoaXZmX2RlZmF1bHQpJHByZWRpY3Rpb25zCmNsYXRlc190dW5lZCA9IHByZWRpY3QoaXZmX3R1bmVkKSRwcmVkaWN0aW9ucwpgYGAKClVzZSB0aGUgbmV3IGBnZXRfb3V0Y29tZV93ZWlnaHRzKClgIG1ldGhvZCwgd2hpY2ggcmVxdWlyZXMgdG8gcGFzcyB0aGUgZXh0ZXJuYWxseSBlc3RpbWF0ZWQgb3V0Y29tZSBzbW9vdGhlciBtYXRyaXg6CgpgYGB7cn0Kb21lZ2FfaWZfZGVmYXVsdCA9IGdldF9vdXRjb21lX3dlaWdodHMoaXZmX2RlZmF1bHQsIFMgPSBTX2RlZmF1bHQpCm9tZWdhX2lmX3R1bmVkID0gZ2V0X291dGNvbWVfd2VpZ2h0cyhpdmZfdHVuZWQsIFMgPSBTX3R1bmVkKQpgYGAKCk9ic2VydmUgdGhhdCB0aGUgb3V0Y29tZSB3ZWlnaHRzIHJlY292ZXIgdGhlIGBncmZgIHBhY2thZ2Ugb3V0cHV0OgoKYGBge3J9CmNhdCgiz4knWSByZXBsaWNhdGVzIENMQVRFIHBvaW50IGVzdGltYXRlcyAoZGVmYXVsdCk/IiwgCiAgICBhbGwuZXF1YWwoYXMubnVtZXJpYyhvbWVnYV9pZl9kZWZhdWx0JG9tZWdhICUqJSBZKSwKICAgICAgYXMubnVtZXJpYyhjbGF0ZXNfZGVmYXVsdCkKICAgICkpCmNhdCgiXG7PiSdZIHJlcGxpY2F0ZXMgQ0xBVEUgcG9pbnQgZXN0aW1hdGVzICh0dW5lZCk/IiwgCiAgICBhbGwuZXF1YWwoYXMubnVtZXJpYyhvbWVnYV9pZl90dW5lZCRvbWVnYSAlKiUgWSksCiAgICAgIGFzLm51bWVyaWMoY2xhdGVzX3R1bmVkKQogICAgKSkKYGBgCgpOb3cgY2FsY3VsYXRlIHRoZSBhYnNvbHV0ZSBzdGFuZGFyZGl6ZWQgbWVhbiBkaWZmZXJlbmNlcyBhbmQgcGxvdCB0aGVtIGZvciBlYWNoIENMQVRFIGFuZCB2YXJpYWJsZXMgKEZpZ3VyZSAzYiBpbiBwYXBlcik6CgpgYGB7cn0KY2JfY2xhdGVfZGVmYXVsdCA9IHN0YW5kYXJkaXplZF9tZWFuX2RpZmZlcmVuY2VzKFgsRCxvbWVnYV9pZl9kZWZhdWx0JG9tZWdhLFgpCmNiX2NsYXRlX3R1bmVkID0gc3RhbmRhcmRpemVkX21lYW5fZGlmZmVyZW5jZXMoWCxELG9tZWdhX2lmX3R1bmVkJG9tZWdhLFgpCgpzbWRfZGVmYXVsdCA9IHQoYWJzKGNiX2NsYXRlX2RlZmF1bHRbLDMsXSkpCnNtZF90dW5lZCA9IHQoYWJzKGNiX2NsYXRlX3R1bmVkWywzLF0pKQoKIyBNZWx0IHRoZSBzbWRfZGVmYXVsdCBtYXRyaXggdG8gbG9uZyBmb3JtYXQKZGZfZGVmYXVsdF9sb25nID0gbWVsdChzbWRfZGVmYXVsdCkKZGZfZGVmYXVsdF9sb25nJEdyb3VwID0gInNtZF9kZWZhdWx0IiAgIyBBZGQgYSBncm91cCBpZGVudGlmaWVyCgojIE1lbHQgdGhlIHNtZF90dW5lZCBtYXRyaXggdG8gbG9uZyBmb3JtYXQKZGZfdHVuZWRfbG9uZyA9IG1lbHQoc21kX3R1bmVkKQpkZl90dW5lZF9sb25nJEdyb3VwID0gInNtZF90dW5lZCIgICMgQWRkIGEgZ3JvdXAgaWRlbnRpZmllcgoKIyBDb21iaW5lIHRoZSB0d28gZGF0YSBmcmFtZXMKZGZfbG9uZyA9IHJiaW5kKGRmX2RlZmF1bHRfbG9uZywgZGZfdHVuZWRfbG9uZykKCiMgUmVuYW1lIHRoZSBjb2x1bW5zIGZvciBjbGFyaXR5CmNvbG5hbWVzKGRmX2xvbmcpID0gYygiUm93IiwgIlZhcmlhYmxlIiwgIlZhbHVlIiwgIkdyb3VwIikKCiMgQ3JlYXRlIHRoZSBnZ3Bsb3QKZmlndXJlM2IgPSBnZ3Bsb3QoZGZfbG9uZywgYWVzKHggPSBmYWN0b3IoVmFyaWFibGUsIGxldmVscyA9IHJldih1bmlxdWUoVmFyaWFibGUpKSksIHkgPSBWYWx1ZSwgZmlsbCA9IEdyb3VwKSkgKwogIGdlb21fYm94cGxvdChwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gMC44KSkgKwogIGxhYnMoeCA9IGVsZW1lbnRfYmxhbmsoKSwgeSA9ICJBYnNvbHV0ZSBTdGFuZGFyZGl6ZWQgTWVhbiBEaWZmZXJlbmNlcyIpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSB2aXJpZGlzKDIpLAogICAgICAgICAgICAgICAgICAgIG5hbWUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiZGVmYXVsdCIsICJ0dW5lZCIpKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJzb2xpZCIsIGNvbG9yID0gImJsYWNrIikgKyAKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQpmaWd1cmUzYgpgYGAKCgpgYGB7ciwgZWNobz1GfQojIFRoaXMgcGFydCBpcyByZWxldmFudCBpZiB5b3UgcnVuIHRoZSBub3RlYm9va3MgaW5zaWRlIHRoZSBkb2NrZXIgYW5kIHdhbnQgdG8gc2F2ZSBncmFwaHMgYW5kIGltYWdlIGluIGEgc2hhcmVkIGhvc3Qgdm9sdW1lIGNhbGxlZCBzaGFyZWRfZmlsZXMgKHVuY29tbWVudCBhbmQvb3IgYWRqdXN0IG9uIGRlbWFuZCk6CgojIGdnc2F2ZSgiL2hvbWUvcnN0dWRpby9zaGFyZWRfZmlsZXMvRmlndXJlM2EucGRmIiwgcGxvdCA9IGZpZ3VyZTNhLCB3aWR0aCA9IDYsIGhlaWdodCA9IDQsZHBpPTMwMCkKIyBnZ3NhdmUoIi9ob21lL3JzdHVkaW8vc2hhcmVkX2ZpbGVzL0ZpZ3VyZTNhLnBuZyIsIHBsb3QgPSBmaWd1cmUzYSwgd2lkdGggPSA2LCBoZWlnaHQgPSA0LGRwaT04MDApCiMgZ2dzYXZlKCIvaG9tZS9yc3R1ZGlvL3NoYXJlZF9maWxlcy9GaWd1cmUzYi5wZGYiLCBwbG90ID0gZmlndXJlM2IsIHdpZHRoID0gNiwgaGVpZ2h0ID0gNCxkcGk9MzAwKQojIGdnc2F2ZSgiL2hvbWUvcnN0dWRpby9zaGFyZWRfZmlsZXMvRmlndXJlM2IucG5nIiwgcGxvdCA9IGZpZ3VyZTNiLCB3aWR0aCA9IDYsIGhlaWdodCA9IDQsZHBpPTgwMCkKIyBzYXZlLmltYWdlKGZpbGUgPSAiL2hvbWUvcnN0dWRpby9zaGFyZWRfZmlsZXMvQXBwbGljYXRpb25fNDAxa19oZXRlcm9nZW5lb3VzLlJEYXRhIikKYGBgCgoKIyAyLiBTdXBwbGVtZW50YXJ5IHJlc3VsdHMKCiMjIFBsb3QgcmVzdWx0cwoKSGVyZSB3ZSBvYnNlcnZlIHRoYXQgbm90IG9ubHkgdGhlIGRlZmF1bHQgZWZmZWN0cyBzaG93IG11Y2ggd29yc2UgYmFsYW5jaW5nLCBidXQgdGhleSBhbHNvIGxlYWQgdG8gaW1wbGF1c2libHkgaGlnaCB2YXJpYW5jZSBpbiB0aGVpciBlc3RpbWF0ZXMuCgpgYGB7cn0KZGF0YSA9IGRhdGEuZnJhbWUoCiAgdmFsdWUgPSBjKGNhdGVzX2RlZmF1bHQsIGNhdGVzX3R1bmVkLGNsYXRlc19kZWZhdWx0LCBjbGF0ZXNfdHVuZWQpLAogIGNhdGVnb3J5ID0gcmVwKGMoImdyZiBDQVRFcyBkZWZhdWx0IiwgImdyZiBDQVRFcyB0dW5lZCIsImdyZiBDTEFURXMgZGVmYXVsdCIsICJncmYgQ0xBVEVzIHR1bmVkIiksIGVhY2ggPSBsZW5ndGgoY2F0ZXNfZGVmYXVsdCkpCikKCmdncGxvdChkYXRhLCBhZXMoeSA9IGNhdGVnb3J5LCB4ID0gdmFsdWUsIGZpbGwgPSBjYXRlZ29yeSkpICsKICBnZW9tX2JveHBsb3QoYWxwaGEgPSAwLjcpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJibGFjayIsIGxpbmV0eXBlID0gInNvbGlkIikgKwogIGxhYnMoCiAgICB4ID0gIkVzdGltYXRlIiwKICAgIHkgPSAiRXN0aW1hdG9yL0ltcGxlbWVudGF0aW9uIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgpgYGB7cn0KIyBDcmVhdGUgdGhlIHJpZGdlIHBsb3QKZ2dwbG90KGRhdGEsIGFlcyh4ID0gdmFsdWUsIHkgPSBjYXRlZ29yeSwgZmlsbCA9IGNhdGVnb3J5KSkgKwogIGdlb21fZGVuc2l0eV9yaWRnZXMoYWxwaGEgPSAwLjcsIHNjYWxlID0gMSkgKwogIGxhYnMoCiAgICB4ID0gIkVzdGltYXRlIiwKICAgIHkgPSAiRXN0aW1hdG9yL0ltcGxlbWVudGF0aW9uIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgojIyBTdW1tYXJpemUgd2VpZ2h0cwoKRmluYWxseSwgd2Ugc3VtbWFyaXplIHRoZSB3ZWlnaHRzIHZlY3RvcnMgZm9yIGVhY2ggQyhMKUFURSBwbG90dGluZyBkaWZmZXJlbnQgZGVzY3JpcHRpdmVzOgoKLSBNaW5pbXVtIHdlaWdodAoKLSBNYXhpbXVtIHdlaWdodAoKLSAlIE5lZ2F0aXZlCgotIFN1bSBsYXJnZXN0IDEwJQoKLSBTdW0gb2Ygd2VpZ2h0cwoKLSBTdW0gb2YgYWJzb2x1dGUgd2VpZ2h0cwoKRXNwZWNpYWxseSB0aGUgc3VtIG9mIHdlaWdodHMgaXMgaW50ZXJlc3RpbmcgaW4gbGlnaHQgb2YgdGhlIHBhcGVyLiBJdCBzaG93cyB0aGF0IHR1bmluZyBhbHNvIHByb2R1Y2VzIHdlaWdodCBzdW1zIGNsb3NlciB0byBvbmUgaW4gdGhpcyBkYXRhIHNldC4gSG93ZXZlciwgYWxzbyBuZWdhdGl2ZSB3ZWlnaHRzIGFyZSBmZXdlciBhbmQgbGVzcyBwcm9ub3VuY2VkIGZvciB0aGUgdHVuZWQgdmVyc2lvbjoKCmBgYHtyfQpzdW1tYXJ5X3dlaWdodHNfY2ZfZGVmYXVsdCA9IHN1bW1hcnkob21lZ2FfY2ZfZGVmYXVsdCwgcXVpZXQgPSBUUlVFKQpzdW1tYXJ5X3dlaWdodHNfY2ZfdHVuZWQgPSBzdW1tYXJ5KG9tZWdhX2NmX3R1bmVkLCBxdWlldCA9IFRSVUUpCnN1bW1hcnlfd2VpZ2h0c19pZl9kZWZhdWx0ID0gc3VtbWFyeShvbWVnYV9pZl9kZWZhdWx0LCBxdWlldCA9IFRSVUUpCnN1bW1hcnlfd2VpZ2h0c19pZl90dW5lZCA9IHN1bW1hcnkob21lZ2FfaWZfdHVuZWQsIHF1aWV0ID0gVFJVRSkKCmZvciAoaSBpbiAxOmRpbShzdW1tYXJ5X3dlaWdodHNfaWZfdHVuZWQpWzNdKSB7CiAgIyBFeHRyYWN0IHVudHJlYXRlZCBhbmQgdHJlYXRlZCB3ZWlnaHRzIGZvciBlYWNoIGdyb3VwCiAgc3VtX3dlaWdodHNfY2ZfZGVmYXVsdF91bnRyZWF0ZWQgPSBzdW1tYXJ5X3dlaWdodHNfY2ZfZGVmYXVsdFsxLCxpXQogIHN1bV93ZWlnaHRzX2NmX2RlZmF1bHRfdHJlYXRlZCA9IHN1bW1hcnlfd2VpZ2h0c19jZl9kZWZhdWx0WzIsLGldCiAgCiAgc3VtX3dlaWdodHNfY2ZfdHVuZWRfdW50cmVhdGVkID0gc3VtbWFyeV93ZWlnaHRzX2NmX3R1bmVkWzEsLGldCiAgc3VtX3dlaWdodHNfY2ZfdHVuZWRfdHJlYXRlZCA9IHN1bW1hcnlfd2VpZ2h0c19jZl90dW5lZFsyLCxpXQogIAogIHN1bV93ZWlnaHRzX2lmX2RlZmF1bHRfdW50cmVhdGVkID0gc3VtbWFyeV93ZWlnaHRzX2lmX2RlZmF1bHRbMSwsaV0KICBzdW1fd2VpZ2h0c19pZl9kZWZhdWx0X3RyZWF0ZWQgPSBzdW1tYXJ5X3dlaWdodHNfaWZfZGVmYXVsdFsyLCxpXQogIAogIHN1bV93ZWlnaHRzX2lmX3R1bmVkX3VudHJlYXRlZCA9IHN1bW1hcnlfd2VpZ2h0c19pZl90dW5lZFsxLCxpXQogIHN1bV93ZWlnaHRzX2lmX3R1bmVkX3RyZWF0ZWQgPSBzdW1tYXJ5X3dlaWdodHNfaWZfdHVuZWRbMiwsaV0KICAKICAjIENvbWJpbmUgYWxsIHZlY3RvcnMgaW50byBhIHNpbmdsZSBkYXRhIGZyYW1lLCB3aXRoIGEgbmV3ICdUcmVhdG1lbnQnIGNvbHVtbgogIGRmX3dlaWdodHMgPC0gZGF0YS5mcmFtZSgKICAgIFZhbHVlID0gYygKICAgICAgc3VtX3dlaWdodHNfY2ZfZGVmYXVsdF91bnRyZWF0ZWQsIHN1bV93ZWlnaHRzX2NmX2RlZmF1bHRfdHJlYXRlZCwKICAgICAgc3VtX3dlaWdodHNfY2ZfdHVuZWRfdW50cmVhdGVkLCBzdW1fd2VpZ2h0c19jZl90dW5lZF90cmVhdGVkLAogICAgICBzdW1fd2VpZ2h0c19pZl9kZWZhdWx0X3VudHJlYXRlZCwgc3VtX3dlaWdodHNfaWZfZGVmYXVsdF90cmVhdGVkLAogICAgICBzdW1fd2VpZ2h0c19pZl90dW5lZF91bnRyZWF0ZWQsIHN1bV93ZWlnaHRzX2lmX3R1bmVkX3RyZWF0ZWQKICAgICksCiAgICBHcm91cCA9IGZhY3RvcihjKAogICAgICByZXAoIkNGIGRlZmF1bHQiLCBsZW5ndGgoc3VtX3dlaWdodHNfY2ZfZGVmYXVsdF91bnRyZWF0ZWQpICsgbGVuZ3RoKHN1bV93ZWlnaHRzX2NmX2RlZmF1bHRfdHJlYXRlZCkpLAogICAgICByZXAoIkNGIHR1bmVkIiwgbGVuZ3RoKHN1bV93ZWlnaHRzX2NmX3R1bmVkX3VudHJlYXRlZCkgKyBsZW5ndGgoc3VtX3dlaWdodHNfY2ZfdHVuZWRfdHJlYXRlZCkpLAogICAgICByZXAoIklGIGRlZmF1bHQiLCBsZW5ndGgoc3VtX3dlaWdodHNfaWZfZGVmYXVsdF91bnRyZWF0ZWQpICsgbGVuZ3RoKHN1bV93ZWlnaHRzX2lmX2RlZmF1bHRfdHJlYXRlZCkpLAogICAgICByZXAoIklGIHR1bmVkIiwgbGVuZ3RoKHN1bV93ZWlnaHRzX2lmX3R1bmVkX3VudHJlYXRlZCkgKyBsZW5ndGgoc3VtX3dlaWdodHNfaWZfdHVuZWRfdHJlYXRlZCkpCiAgICApKSwKICAgIFRyZWF0bWVudCA9IGZhY3RvcihjKAogICAgICByZXAoIlVudHJlYXRlZCIsIGxlbmd0aChzdW1fd2VpZ2h0c19jZl9kZWZhdWx0X3VudHJlYXRlZCkpLAogICAgICByZXAoIlRyZWF0ZWQiLCBsZW5ndGgoc3VtX3dlaWdodHNfY2ZfZGVmYXVsdF90cmVhdGVkKSksCiAgICAgIHJlcCgiVW50cmVhdGVkIiwgbGVuZ3RoKHN1bV93ZWlnaHRzX2NmX3R1bmVkX3VudHJlYXRlZCkpLAogICAgICByZXAoIlRyZWF0ZWQiLCBsZW5ndGgoc3VtX3dlaWdodHNfY2ZfdHVuZWRfdHJlYXRlZCkpLAogICAgICByZXAoIlVudHJlYXRlZCIsIGxlbmd0aChzdW1fd2VpZ2h0c19pZl9kZWZhdWx0X3VudHJlYXRlZCkpLAogICAgICByZXAoIlRyZWF0ZWQiLCBsZW5ndGgoc3VtX3dlaWdodHNfaWZfZGVmYXVsdF90cmVhdGVkKSksCiAgICAgIHJlcCgiVW50cmVhdGVkIiwgbGVuZ3RoKHN1bV93ZWlnaHRzX2lmX3R1bmVkX3VudHJlYXRlZCkpLAogICAgICByZXAoIlRyZWF0ZWQiLCBsZW5ndGgoc3VtX3dlaWdodHNfaWZfdHVuZWRfdHJlYXRlZCkpCiAgICApKQogICkKICAKICAjIFBsb3Qgd2l0aCBnZ3Bsb3QgYW5kIGdncmlkZ2VzCiAgZyA8LSBnZ3Bsb3QoZGZfd2VpZ2h0cywgYWVzKHggPSBWYWx1ZSwgeSA9IEdyb3VwLCBmaWxsID0gVHJlYXRtZW50KSkgKwogICAgZ2VvbV9ib3hwbG90KHBvc2l0aW9uID0gcG9zaXRpb25fZG9kZ2Uod2lkdGggPSAwLjc1KSkgKwogICAgbGFicyh4ID0gZGltbmFtZXMoc3VtbWFyeV93ZWlnaHRzX2NmX3R1bmVkKVtbM11dW2ldLCB5ID0gTlVMTCkgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKSAgIyBQbGFjZSBsZWdlbmQgYXQgdGhlIGJvdHRvbQogIHByaW50KGcpCn0KCmBgYAoK