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