Linear world
We start in a world where the conditional expectation function (CEF)
is actually linear and OLS should converge at \(\sqrt{N}\).
Data generating process
Consider a data generating process (DGP) with:
\(Y = \underbrace{\beta_0 + \beta_1
X}_{CEF} + U\), where \(\beta_0 =
-1\), \(\beta_1 = 2\), \(X \sim uniform(0,1)\) and \(U \sim uniform(-1,1)\).
For illustration, we plot a random draw with \(N=500\) and the true CEF:
# Load the packages required for later
if (!require("tidyverse")) install.packages("tidyverse", dependencies = TRUE); library(tidyverse)
if (!require("ggridges")) install.packages("ggridges", dependencies = TRUE); library(ggridges)
if (!require("latex2exp")) install.packages("latex2exp", dependencies = TRUE); library(latex2exp)
if (!require("np")) install.packages("np", dependencies = TRUE); library(np)
set.seed(1234) # For replicability
n = 500 # Sample size for illustration
x = runif(n) # Draw covariate
# Define population parameters
b0 = -1
b1 = 2
# Define conditional expectation function
cef = function(x){b0 + b1*x}
# Generate outcome variable
y = cef(x) + runif(n,-1,1)
# Plot sample
df = data.frame(x=x,y=y)
ggplot(df) + stat_function(fun=cef,size=1) +
geom_point(aes(x=x,y=y),color="blue",alpha = 0.4)
Applying OLS to the plotted sample shows that it nearly recovers the
true coefficients, but not exactly due to sampling noise (as
expected).
summary(lm(y ~ x))
Call:
lm(formula = y ~ x)
Residuals:
Min 1Q Median 3Q Max
-1.02784 -0.51841 0.00381 0.55908 0.98856
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.99330 0.05481 -18.12 <2e-16 ***
x 2.02376 0.09465 21.38 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 0.599 on 498 degrees of freedom
Multiple R-squared: 0.4786, Adjusted R-squared: 0.4776
F-statistic: 457.2 on 1 and 498 DF, p-value: < 2.2e-16
Convergence rate of OLS - coefficients
We know that \[\sqrt{N}(\hat{\beta}_N -
\beta) \overset{d}{\rightarrow} \mathcal{N}(0,\Sigma)\]
This means that the difference between the estimated coefficients and
the true values is normally distributed with constant
variance if we multiply it by \(\sqrt{N}\).
Let’s unpack this in the following way:
Inspect distributions of \(\hat{\beta}_N\) for increasing \(N\)
Inspect distributions of \(\hat{\beta}_N - \beta\) for increasing
\(N\)
Inspect distributions of \(\sqrt{N}(\hat{\beta}_N - \beta)\) for
increasing \(N\)
The tool we use is a so-called Monte Carlo simulation study.
Concretely, we follow these steps (for I nice intro Ch. 15 of “The
Effect”:
Draw a random sample from the DGP defined above of size \(N\).
Run OLS and save the coefficients.
Repeat 1. and 2. \(R\)
times.
Repeat 1. to 3. for a sequence of sample sizes \(N\) increasing by factor four in each
step.
We start with \(N=10\), look at five
sample sizes, set \(R=5,000\) and focus
on \(\beta_1\).
# Define parameters of the simulation
r = 5000
seq = 5
start = 10
# Initialize empty matrix to fill in estimated coefficient b1
results = matrix(NA,r,seq+1)
colnames(results) = rep("Temp",seq+1) # To be automatically replaced in loop below
for (i in 0:seq) { # Outer loop over sample sizes
n = 4^i * start # sample size for this loop
colnames(results)[i+1] = paste0("n=",toString(n)) # save sample size
for (j in 1:r) { # Inner loop over replications
# Draw sample
x = runif(n)
y = cef(x) + runif(n,-1,1)
# Estimate and directly save b1 coefficient
results[j,i+1] = lm(y ~ x)$coefficients[2]
}
}
\(\hat{\beta}_N\)
Now let’s plot the distributions of the resulting coefficients for
different sample sizes:
# Get the data ready
tib = as_tibble(results) %>% pivot_longer(cols = starts_with("n="), names_to = "N", names_prefix = "n=", values_to = "betas1") %>% mutate(N = as_factor(N))
# Plot
tib %>% ggplot(aes(x = betas1, y = fct_rev(N), fill = N)) +
geom_density_ridges() + ylab("N") + xlab(TeX(r'($\hat{\beta}_1$)')) +
geom_vline(xintercept = b1)
This picture provides as a side effect intuition about two other
important concepts:
Unbiasedness: The estimator distribution is
centered around the true value \(\Rightarrow
E[\hat{\beta}_{1N}] = \beta_1\) for every sample size (each row
in the graph).
Consistency: The distributions center more and
more around the true value as sample size increases.
\(\hat{\beta}_N - \beta\)
Now we center the distributions around zero:
tib %>% mutate(dev = betas1 - b1) %>% ggplot(aes(x = dev, y = fct_rev(N), fill = N)) +
geom_density_ridges() + ylab("N") +
xlab(TeX(r'($\hat{\beta}_1 - \beta_1$)')) +
geom_vline(xintercept = 0)
Of course, this does not change the pattern that the distributions
become more compact with larger sample size.
\(\sqrt{N}(\hat{\beta}_N -
\beta)\)
However, if we multiply each distribution with the square root of the
respective sample size, tadaaa
tib %>% mutate(sqrtn_dev = sqrt(as.numeric(as.character(N))) * (betas1 - b1)) %>%
ggplot(aes(x = sqrtn_dev, y = fct_rev(N), fill = N)) +
geom_density_ridges() + ylab("N") +
xlab(TeX(r'($\sqrt{N}(\hat{\beta}_1 - \beta_1)$)'))
they become very similar looking normal distributions.
This means \(\sqrt{N}\) is exactly
the factor that offsets the speed by which the distributions above
become more compact. We can infer that the distributions converge to
zero at the rate that is required to offset convergence.
To illustrate further, vary delta
in the following code
snippet and observe how the distributions still narrow if it is smaller
than 0.5 like, e.g. with 0.3 …
delta = 0.3
tib %>% mutate(dev = (as.numeric(as.character(N)))^delta * (betas1 - b1)) %>%
ggplot(aes(x = dev, y = fct_rev(N), fill = N)) +
geom_density_ridges() + ylab("N") +
xlab(TeX(r'($N^{3/10}(\hat{\beta}_1 - \beta_1)$)'))
… and that the distributions explode if we set it larger than 0.5,
e.g. 0.7:
delta = 0.7
tib %>% mutate(dev = (as.numeric(as.character(N)))^delta * (betas1 - b1)) %>%
ggplot(aes(x = dev, y = fct_rev(N), fill = N)) +
geom_density_ridges() + ylab("N") +
xlab(TeX(r'($N^{7/10}(\hat{\beta}_1 - \beta_1)$)'))
Another illustration, how the distribution of the deviations is
stabilized by multiplying with \(\sqrt{N}\), shows the standard deviation of
\(N^\delta(\hat{\beta_1} - \beta_1)\)
which is \(\sqrt{Var(N^\delta(\hat{\beta_1} -
\beta_1))} = \sqrt{N^{2\delta} \cdot Var(\hat{\beta_1} - \beta_1)} =
N^\delta \cdot sd(\hat{\beta_1} - \beta_1)\). The following graph
shows how \(N^\delta \cdot sd(\hat{\beta_1} -
\beta_1)\) evolves for different \(\delta\) with increasing \(N\):
new_tib = tib %>% mutate(dev = betas1 - b1, N = as.numeric(as.character(N))) %>% group_by(N) %>% summarise(sd_dev = sqrt(var(dev)))
delta_seq = c(0,0.3,0.4,0.5,0.55,0.6)
new_tib = cbind(as.matrix(new_tib), matrix(NA,nrow = dim(as.matrix(new_tib))[1],ncol = length(delta_seq)))
colnames(new_tib) = c("N", "sd", rep("Temp",length(delta_seq)))
for (i in 1:length(delta_seq)) {
delta = delta_seq[i]
colnames(new_tib)[i+2] = paste0("d=",toString(delta))
new_tib[,i+2] = new_tib[,1]^(delta) * new_tib[,2]
}
new_tib = as_tibble(new_tib) %>% pivot_longer(cols = starts_with("d="), names_to = "delta", names_prefix = "d=", values_to = "value") %>% mutate(delta = as_factor(delta))
new_tib %>% ggplot(aes(x = N, y = value, color = delta))+
geom_line(aes(size = delta)) + scale_size_manual(values = c(0.5,0.5, 0.5, 2,0.5,0.5)) +
ylab(TeX(r'($N^\delta \cdot sd(\hat{\beta_1} - \beta_1)$)'))
With \(\delta < 1/2\), the
standard deviation of \(\hat{\beta_1} -
\beta_1\) goes to zero. Only when when multiplying by \(N^{1/2}\) (thick), the standard deviation
remains constant across different sample sizes \(\Rightarrow\) The distribution is case
stabilized. Multiplication by \(N^\delta,
\delta>1/2\) makes the standard deviation of the product
increase with increasing sample size \(\Rightarrow\) the distributions explode
asymptotically.
\(N(\hat{\beta}_1 -
\beta_1)^2\)
Note similarly that \(N\) stabilizes
the distribution of the squared difference
tib %>% mutate(sqrtn_dev = as.numeric(as.character(N)) * (betas1 - b1)^2) %>%
ggplot(aes(x = sqrtn_dev, y = fct_rev(N), fill = N)) +
geom_density_ridges() + ylab("N") +
xlab(TeX(r'($N(\hat{\beta}_1 - \beta_1)^2$)'))
\(\sqrt{N(\hat{\beta}_1 -
\beta_1)^2}\)
… and \(\sqrt N\) stabilizes the
distribution of the square root of the squared difference:
tib %>% mutate(sqrtn_dev = sqrt( as.numeric(as.character(N)) * (betas1 - b1)^2) ) %>%
ggplot(aes(x = sqrtn_dev, y = fct_rev(N), fill = N)) +
geom_density_ridges() + ylab("N") +
xlab(TeX(r'($\sqrt{N(\hat{\beta}_1 - \beta_1)^2}$)'))
Convergence rate of OLS - RMSE of fitted/predicted values
In this course, it will be important how fast fitted/predicted values
of a method converge to the CEF, not how fast a single coefficient
converges.
Therefore note that \(\sqrt{N}\)
convergence in the parameters implies \(\sqrt{N}\) of the root mean squared error
(RMSE) \[RMSE =
\sqrt{\frac{1}{N}\sum_i(X_i'\hat{\beta}_N -
X_i'\beta)^2}\]
To illustrate this, we repeat the simulation above, but instead of
saving the coefficient, we save the RMSE of each run.
# Initialize empty matrix to fill in RMSE
results = matrix(NA,r,seq+1)
colnames(results) = rep("Temp",seq+1) # To be automatically replaced in loop below
for (i in 0:seq) { # Outer loop over sample sizes
n = 4^i * start # sample size for this loop
colnames(results)[i+1] = paste0("n=",toString(n)) # save sample size
for (j in 1:r) { # Inner loop over replications
# Draw sample
x = runif(n)
y = cef(x) + runif(n,-1,1)
# Save RMSE of this replication
results[j,i+1] = sqrt( mean( (lm(y ~ x)$fitted.values - cef(x))^2 ) )
}
}
Plot the RMSE for different sample sizes:
tib = as_tibble(results) %>% pivot_longer(cols = starts_with("n="), names_to = "N", names_prefix = "n=", values_to = "rmse") %>% mutate(N = as_factor(N))
tib %>% ggplot(aes(x = rmse, y = fct_rev(N), fill = N)) +
geom_density_ridges() + ylab("N") + xlab("RMSE")
The distributions of RMSE come closer to zero, the larger the sample
size.
To see that also the RMSE distributions converge at rate \(\sqrt{N}\), we blow up the raw
distributions once more and observe that they stabilize when doing
so:
tib %>% ggplot(aes(x = sqrt( as.numeric(as.character(N)) ) * rmse , y = fct_rev(N), fill = N)) +
geom_density_ridges() + ylab("N") + xlab(TeX(r'($\sqrt{N}\times RMSE$)'))
This illustrates the (for me) most intuitive implication of \(\sqrt{N}\) convergence that the RMSE halves
when we quadruple the sample size.
Check the means of the RMSE distributions
mrmse = round(colMeans(results),3)
mrmse
n=10 n=40 n=160 n=640 n=2560 n=10240
0.229 0.114 0.057 0.029 0.014 0.007
and realize that they indeed nearly perfectly halve:
round(mrmse/lag(mrmse),3)
n=10 n=40 n=160 n=640 n=2560 n=10240
NA 0.498 0.500 0.509 0.483 0.500
Ideas for DIY extensions:
Use different sequences of \(N\)
Check also \(\beta_0\)
Illustrate that the mean squared error (MSE) converges at rate
\(N\)
Adapt the DGP, e.g. different parameter values, more than one
covariate, different error term distribution, …
Kernel regression convergence rates
Recall that we specified a linear CEF and therefore OLS is supposed
to work very well. However, we could also use nonparametric methods that
do not assume linearity. They differ from OLS in several ways:
They do not estimate global parameters. Convergence in terms of
\(\beta\)s can therefore not be
investigated.
They converge slower than \(\sqrt{N}\) because they do not work under
the assumption that the world is linear and need to figure this out on
their own.
They are computationally more expensive. We run not so many
replications and sample sizes as with OLS
We run the same simulation study as above, but using kernel
regression with cross-validated bandwidth, which should converge at
\(N^{2/5}\). We print the plot of the
first replication of a sample size to illustrate the fit.
# Set simulation parameters
r = 100 # Decrease for faster running time, increase for more precise results
seq = 4
# Initialize empty matrix to fill in RMSE
results = matrix(NA,r,seq+1)
colnames(results) = rep("Temp",seq+1) # To be automatically replaced in loop below
for (i in 0:seq) { # Outer loop over sample sizes
n = 4^i * start # sample size for this loop
colnames(results)[i+1] = paste0("n=",toString(n)) # save sample size
for (j in 1:r) { # Inner loop over replications
# Draw sample
x = runif(n)
y = cef(x) + runif(n,-1,1)
# Cross-validate bandwidth, but only once for computational reasons
if(j==1) bwobj = npregbw(ydat = y, xdat = x, regtype = 'lc', bwmethod = 'cv.ls')
# Run Kernel regression
model = npreg(tydat = y, txdat = x, bws=bwobj$bw, regtype = 'lc')
if(j==1) plot(model, main = paste0("N=",toString(n)))
results[j,i+1] = sqrt( mean( (fitted(model) - cef(x))^2 ) )
}
}
The RMSE of kernel regression decreases also with higher \(N\)
mrmse = round(colMeans(results),3)
mrmse
n=10 n=40 n=160 n=640 n=2560
0.469 0.155 0.102 0.057 0.033
BUT at a slower rate (as expected)
round(mrmse/lag(mrmse),3)
n=10 n=40 n=160 n=640 n=2560
NA 0.330 0.658 0.559 0.579
Note that the theoretical decrease for quadrupling sample size is
n^(2/5) / (4*n)^(2/5)
[1] 0.5743492
which is roughly what we observe (the more replications you can
computationally afford, the closer it should get).
Non-linear world
So far, we created a world where OLS shines and shows (the best
possible) \(\sqrt{N}\) convergence of
coefficients and RMSE.
BUT why should the world be linear? To make the point, let’s push it
and assume the following definitely non linear DGP: \[Y = \underbrace{sin(X)}_{CEF} + U\] where
\[X \sim uniform(-1.43 \pi,1.43 \pi);~U \sim
\mathcal{N}(0,1)\]
n = 500 # Sample size for illustration
# Define conditional expectation function
cef = function(x){sin(x)}
# Draw sample
x = runif(n,-pi*1.43,pi*1.43)
y = cef(x) + rnorm(n,0,1)
# Plot sample
df = data.frame(x=x,y=y)
ggplot(df) + stat_function(fun=cef,size=1) +
geom_point(aes(x=x,y=y),color="blue",alpha = 0.4)
The DGP is engineered to trick OLS to estimate \(\beta_0 = \beta_1 = 0\) exploiting
basically no information in the dataset:
summary(lm(y~ x))
Call:
lm(formula = y ~ x)
Residuals:
Min 1Q Median 3Q Max
-3.2184 -0.8817 -0.0519 0.8344 3.3488
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) -0.036329 0.053115 -0.684 0.494
x -0.003947 0.019593 -0.201 0.840
Residual standard error: 1.187 on 498 degrees of freedom
Multiple R-squared: 8.149e-05, Adjusted R-squared: -0.001926
F-statistic: 0.04059 on 1 and 498 DF, p-value: 0.8404
Now we run a simulation study with this DGP and compare the evolution
of the RMSE between OLS and kernel regression:
r = 100 # Decrease for faster running time, increase for more precise results
seq = 4
# Initialize empty matrices to fill in RMSE
results_ols = results_kr = matrix(NA,r,seq+1)
Ns = rep(NA,seq+1)
for (i in 0:seq) { # Outer loop over sample sizes
n = 4^i * start # sample size for this loop
Ns[i+1] = n
for (j in 1:r) { # Inner loop over replications
# Draw sample
x = runif(n,-pi*1.43,pi*1.43)
y = cef(x) + rnorm(n,0,1)
# OLS
results_ols[j,i+1] = sqrt( mean( (lm(y ~ x)$fitted.values - cef(x))^2 ) )
# Kernel
bwobj = npregbw(ydat = y, xdat = x, regtype = 'lc', bwmethod = 'cv.ls')
model = npreg(tydat = y, txdat = x, bws=bwobj$bw, regtype = 'lc')
if(j==1) plot(model, main = paste0("N=",toString(n)))
results_kr[j,i+1] = sqrt( mean( (fitted(model) - cef(x))^2 ) )
}
}
OLS with only the main effect has no chance and converges to a
completely wrong CEF such that the RMSE does not decrease with
increasing sample size. Kernel regression on the other hand steadily but
slowly improves as we increase the sample size:
tibble(N = Ns, ols = colMeans(results_ols), kernel = colMeans(results_kr)) %>%
pivot_longer(names_to = "Estimator",values_to = "rmse", cols = -N) %>%
ggplot(aes(x=N,y=rmse,color = Estimator)) + geom_line() + geom_point() +
geom_hline(yintercept = 0)
Indeed, the convergence rate of Kernel regression in this simulation
is close to the theoretical 0.574 (and probably would come closer if we
increased r
):
round(colMeans(results_kr)/lag(colMeans(results_kr)),3)
[1] NA 0.571 0.573 0.545 0.584
Ideas for DIY extensions:
Illustrate that the coefficients of OLS converge at \(\sqrt{N}\) to zero?
How does OLS perform if we add squared, cubic,… terms?
LS0tDQp0aXRsZTogIkJhc2ljczogQ29udmVyZ2VuY2UgcmF0ZXMiDQpzdWJ0aXRsZTogIlNpbXVsYXRpb24gbm90ZWJvb2siDQphdXRob3I6ICJNaWNoYWVsIEtuYXVzIg0KZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJW0vJXknKWAiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KLS0tDQoNCjxicj4NCg0KR29hbHM6DQoNCi0gSWxsdXN0cmF0ZSBjb252ZXJnZW5jZSByYXRlcw0KDQotIElsbHVzdHJhdGUgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBjb252ZXJnZW5jZSBvZiBjb2VmZmljaWVudHMgYW5kIGNvbnZlcmdlbmNlIG9mIHJvb3QgbWVhbiBzcXVhcmVkIGVycm9yDQoNCi0gSWxsdXN0cmF0ZSB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBPTFMgYW5kIGtlcm5lbCByZWdyZXNzaW9uDQoNCg0KPGJyPg0KDQojIyBMaW5lYXIgd29ybGQNCg0KV2Ugc3RhcnQgaW4gYSB3b3JsZCB3aGVyZSB0aGUgY29uZGl0aW9uYWwgZXhwZWN0YXRpb24gZnVuY3Rpb24gKENFRikgaXMgYWN0dWFsbHkgbGluZWFyIGFuZCBPTFMgc2hvdWxkIGNvbnZlcmdlIGF0ICRcc3FydHtOfSQuDQoNCiMjIyBEYXRhIGdlbmVyYXRpbmcgcHJvY2Vzcw0KDQpDb25zaWRlciBhIGRhdGEgZ2VuZXJhdGluZyBwcm9jZXNzIChER1ApIHdpdGg6IA0KDQokWSA9IFx1bmRlcmJyYWNle1xiZXRhXzAgKyBcYmV0YV8xIFh9X3tDRUZ9ICsgVSQsIHdoZXJlICRcYmV0YV8wID0gLTEkLCAkXGJldGFfMSA9IDIkLCAkWCBcc2ltIHVuaWZvcm0oMCwxKSQgYW5kICRVIFxzaW0gdW5pZm9ybSgtMSwxKSQuDQoNCkZvciBpbGx1c3RyYXRpb24sIHdlIHBsb3QgYSByYW5kb20gZHJhdyB3aXRoICROPTUwMCQgYW5kIHRoZSB0cnVlIENFRjoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZyA9IEZ9DQojIExvYWQgdGhlIHBhY2thZ2VzIHJlcXVpcmVkIGZvciBsYXRlcg0KaWYgKCFyZXF1aXJlKCJ0aWR5dmVyc2UiKSkgaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkodGlkeXZlcnNlKQ0KaWYgKCFyZXF1aXJlKCJnZ3JpZGdlcyIpKSBpbnN0YWxsLnBhY2thZ2VzKCJnZ3JpZGdlcyIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KGdncmlkZ2VzKQ0KaWYgKCFyZXF1aXJlKCJsYXRleDJleHAiKSkgaW5zdGFsbC5wYWNrYWdlcygibGF0ZXgyZXhwIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkobGF0ZXgyZXhwKQ0KaWYgKCFyZXF1aXJlKCJucCIpKSBpbnN0YWxsLnBhY2thZ2VzKCJucCIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KG5wKQ0KDQpzZXQuc2VlZCgxMjM0KSAgIyBGb3IgcmVwbGljYWJpbGl0eQ0KbiA9IDUwMCAgICAgICAgICMgU2FtcGxlIHNpemUgZm9yIGlsbHVzdHJhdGlvbg0KDQp4ID0gcnVuaWYobikgICAgIyBEcmF3IGNvdmFyaWF0ZQ0KDQojIERlZmluZSBwb3B1bGF0aW9uIHBhcmFtZXRlcnMNCmIwID0gLTENCmIxID0gMg0KDQojIERlZmluZSBjb25kaXRpb25hbCBleHBlY3RhdGlvbiBmdW5jdGlvbg0KY2VmID0gZnVuY3Rpb24oeCl7YjAgKyBiMSp4fQ0KDQojIEdlbmVyYXRlIG91dGNvbWUgdmFyaWFibGUNCnkgPSBjZWYoeCkgKyBydW5pZihuLC0xLDEpDQoNCiMgUGxvdCBzYW1wbGUNCmRmID0gZGF0YS5mcmFtZSh4PXgseT15KQ0KZ2dwbG90KGRmKSArIHN0YXRfZnVuY3Rpb24oZnVuPWNlZixzaXplPTEpICsgDQogICAgICAgICAgICBnZW9tX3BvaW50KGFlcyh4PXgseT15KSxjb2xvcj0iYmx1ZSIsYWxwaGEgPSAwLjQpDQpgYGANCg0KQXBwbHlpbmcgT0xTIHRvIHRoZSBwbG90dGVkIHNhbXBsZSBzaG93cyB0aGF0IGl0IG5lYXJseSByZWNvdmVycyB0aGUgdHJ1ZSBjb2VmZmljaWVudHMsIGJ1dCBub3QgZXhhY3RseSBkdWUgdG8gc2FtcGxpbmcgbm9pc2UgKGFzIGV4cGVjdGVkKS4NCg0KYGBge3J9DQpzdW1tYXJ5KGxtKHkgfiB4KSkNCmBgYA0KDQo8YnI+DQo8YnI+DQoNCiMjIyBDb252ZXJnZW5jZSByYXRlIG9mIE9MUyAtIGNvZWZmaWNpZW50cw0KDQpXZSBrbm93IHRoYXQgJCRcc3FydHtOfShcaGF0e1xiZXRhfV9OIC0gXGJldGEpIFxvdmVyc2V0e2R9e1xyaWdodGFycm93fSBcbWF0aGNhbHtOfSgwLFxTaWdtYSkkJCANCg0KVGhpcyBtZWFucyB0aGF0IHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGVzdGltYXRlZCBjb2VmZmljaWVudHMgYW5kIHRoZSB0cnVlIHZhbHVlcyBpcyBub3JtYWxseSBkaXN0cmlidXRlZCAqKndpdGggY29uc3RhbnQgdmFyaWFuY2UqKiBpZiB3ZSBtdWx0aXBseSBpdCBieSAkXHNxcnR7Tn0kLg0KDQo8YnI+DQoNCkxldCdzIHVucGFjayB0aGlzIGluIHRoZSBmb2xsb3dpbmcgd2F5Og0KDQoxLiBJbnNwZWN0IGRpc3RyaWJ1dGlvbnMgb2YgJFxoYXR7XGJldGF9X04kIGZvciBpbmNyZWFzaW5nICROJA0KDQoyLiBJbnNwZWN0IGRpc3RyaWJ1dGlvbnMgb2YgJFxoYXR7XGJldGF9X04gLSBcYmV0YSQgZm9yIGluY3JlYXNpbmcgJE4kDQoNCjMuIEluc3BlY3QgZGlzdHJpYnV0aW9ucyBvZiAkXHNxcnR7Tn0oXGhhdHtcYmV0YX1fTiAtIFxiZXRhKSQgZm9yIGluY3JlYXNpbmcgJE4kDQoNCjxicj4NCg0KDQpUaGUgdG9vbCB3ZSB1c2UgaXMgYSBzby1jYWxsZWQgTW9udGUgQ2FybG8gc2ltdWxhdGlvbiBzdHVkeS4gQ29uY3JldGVseSwgd2UgZm9sbG93IHRoZXNlIHN0ZXBzIChmb3IgSSBuaWNlIGludHJvIFtDaC4gMTUgb2YgIlRoZSBFZmZlY3QiXShodHRwczovL3RoZWVmZmVjdGJvb2submV0L2NoLVNpbXVsYXRpb24uaHRtbCk6DQoNCjEuIERyYXcgYSByYW5kb20gc2FtcGxlIGZyb20gdGhlIERHUCBkZWZpbmVkIGFib3ZlIG9mIHNpemUgJE4kLg0KDQoyLiBSdW4gT0xTIGFuZCBzYXZlIHRoZSBjb2VmZmljaWVudHMuDQoNCjMuIFJlcGVhdCAxLiBhbmQgMi4gJFIkIHRpbWVzLg0KDQo0LiBSZXBlYXQgMS4gdG8gMy4gZm9yIGEgc2VxdWVuY2Ugb2Ygc2FtcGxlIHNpemVzICROJCBpbmNyZWFzaW5nIGJ5IGZhY3RvciBmb3VyIGluIGVhY2ggc3RlcC4NCg0KPGJyPg0KDQpXZSBzdGFydCB3aXRoICROPTEwJCwgbG9vayBhdCBmaXZlIHNhbXBsZSBzaXplcywgc2V0ICRSPTUsMDAwJCBhbmQgZm9jdXMgb24gJFxiZXRhXzEkLg0KDQpgYGB7cn0NCiMgRGVmaW5lIHBhcmFtZXRlcnMgb2YgdGhlIHNpbXVsYXRpb24NCnIgPSA1MDAwDQpzZXEgPSA1DQpzdGFydCA9IDEwDQoNCiMgSW5pdGlhbGl6ZSBlbXB0eSBtYXRyaXggdG8gZmlsbCBpbiBlc3RpbWF0ZWQgY29lZmZpY2llbnQgYjENCnJlc3VsdHMgPSBtYXRyaXgoTkEscixzZXErMSkNCmNvbG5hbWVzKHJlc3VsdHMpID0gcmVwKCJUZW1wIixzZXErMSkgIyBUbyBiZSBhdXRvbWF0aWNhbGx5IHJlcGxhY2VkIGluIGxvb3AgYmVsb3cNCg0KDQpmb3IgKGkgaW4gMDpzZXEpIHsgIyBPdXRlciBsb29wIG92ZXIgc2FtcGxlIHNpemVzIA0KICBuID0gNF5pICogc3RhcnQgIyBzYW1wbGUgc2l6ZSBmb3IgdGhpcyBsb29wDQogIGNvbG5hbWVzKHJlc3VsdHMpW2krMV0gPSBwYXN0ZTAoIm49Iix0b1N0cmluZyhuKSkgIyBzYXZlIHNhbXBsZSBzaXplDQogIGZvciAoaiBpbiAxOnIpIHsgIyBJbm5lciBsb29wIG92ZXIgcmVwbGljYXRpb25zDQogICAgIyBEcmF3IHNhbXBsZQ0KICAgIHggPSBydW5pZihuKQ0KICAgIHkgPSBjZWYoeCkgKyBydW5pZihuLC0xLDEpDQogICAgIyBFc3RpbWF0ZSBhbmQgZGlyZWN0bHkgc2F2ZSBiMSBjb2VmZmljaWVudA0KICAgIHJlc3VsdHNbaixpKzFdID0gbG0oeSB+IHgpJGNvZWZmaWNpZW50c1syXQ0KICB9DQp9DQpgYGANCg0KPGJyPg0KDQojIyMjICRcaGF0e1xiZXRhfV9OJA0KDQpOb3cgbGV0J3MgcGxvdCB0aGUgZGlzdHJpYnV0aW9ucyBvZiB0aGUgcmVzdWx0aW5nIGNvZWZmaWNpZW50cyBmb3IgZGlmZmVyZW50IHNhbXBsZSBzaXplczoNCg0KDQpgYGB7cn0NCiMgR2V0IHRoZSBkYXRhIHJlYWR5DQp0aWIgPSBhc190aWJibGUocmVzdWx0cykgJT4lIHBpdm90X2xvbmdlcihjb2xzID0gc3RhcnRzX3dpdGgoIm49IiksIG5hbWVzX3RvID0gIk4iLCBuYW1lc19wcmVmaXggPSAibj0iLCB2YWx1ZXNfdG8gPSAiYmV0YXMxIikgJT4lIG11dGF0ZShOID0gYXNfZmFjdG9yKE4pKQ0KIyBQbG90DQp0aWIgJT4lIGdncGxvdChhZXMoeCA9IGJldGFzMSwgeSA9IGZjdF9yZXYoTiksIGZpbGwgPSBOKSkgKw0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzKCkgKyB5bGFiKCJOIikgKyB4bGFiKFRlWChyJygkXGhhdHtcYmV0YX1fMSQpJykpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYjEpDQoNCmBgYA0KDQpUaGlzIHBpY3R1cmUgcHJvdmlkZXMgYXMgYSBzaWRlIGVmZmVjdCBpbnR1aXRpb24gYWJvdXQgdHdvIG90aGVyIGltcG9ydGFudCBjb25jZXB0czoNCg0KMS4gKipVbmJpYXNlZG5lc3M6KiogVGhlIGVzdGltYXRvciBkaXN0cmlidXRpb24gaXMgY2VudGVyZWQgYXJvdW5kIHRoZSB0cnVlIHZhbHVlICRcUmlnaHRhcnJvdyBFW1xoYXR7XGJldGF9X3sxTn1dID0gXGJldGFfMSQgZm9yIGV2ZXJ5IHNhbXBsZSBzaXplIChlYWNoIHJvdyBpbiB0aGUgZ3JhcGgpLg0KDQoyLiAqKkNvbnNpc3RlbmN5OioqIFRoZSBkaXN0cmlidXRpb25zIGNlbnRlciBtb3JlIGFuZCBtb3JlIGFyb3VuZCB0aGUgdHJ1ZSB2YWx1ZSBhcyBzYW1wbGUgc2l6ZSBpbmNyZWFzZXMuDQoNCjxicj4NCg0KIyMjIyAkXGhhdHtcYmV0YX1fTiAtIFxiZXRhJA0KDQpOb3cgd2UgY2VudGVyIHRoZSBkaXN0cmlidXRpb25zIGFyb3VuZCB6ZXJvOg0KDQpgYGB7cn0NCnRpYiAlPiUgbXV0YXRlKGRldiA9IGJldGFzMSAtIGIxKSAlPiUgZ2dwbG90KGFlcyh4ID0gZGV2LCB5ID0gZmN0X3JldihOKSwgZmlsbCA9IE4pKSArDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoKSArIHlsYWIoIk4iKSArIA0KICAgIHhsYWIoVGVYKHInKCRcaGF0e1xiZXRhfV8xIC0gXGJldGFfMSQpJykpICsNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCkNCmBgYA0KDQpPZiBjb3Vyc2UsIHRoaXMgZG9lcyBub3QgY2hhbmdlIHRoZSBwYXR0ZXJuIHRoYXQgdGhlIGRpc3RyaWJ1dGlvbnMgYmVjb21lIG1vcmUgY29tcGFjdCB3aXRoIGxhcmdlciBzYW1wbGUgc2l6ZS4NCg0KPGJyPg0KDQojIyMjICRcc3FydHtOfShcaGF0e1xiZXRhfV9OIC0gXGJldGEpJA0KDQpIb3dldmVyLCBpZiB3ZSBtdWx0aXBseSBlYWNoIGRpc3RyaWJ1dGlvbiB3aXRoIHRoZSBzcXVhcmUgcm9vdCBvZiB0aGUgcmVzcGVjdGl2ZSBzYW1wbGUgc2l6ZSwgdGFkYWFhDQoNCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9DQp0aWIgJT4lIG11dGF0ZShzcXJ0bl9kZXYgPSBzcXJ0KGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKE4pKSkgKiAoYmV0YXMxIC0gYjEpKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHNxcnRuX2RldiwgeSA9IGZjdF9yZXYoTiksIGZpbGwgPSBOKSkgKw0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzKCkgKyB5bGFiKCJOIikgKw0KICAgeGxhYihUZVgocicoJFxzcXJ0e059KFxoYXR7XGJldGF9XzEgLSBcYmV0YV8xKSQpJykpDQpgYGANCg0KdGhleSBiZWNvbWUgdmVyeSBzaW1pbGFyIGxvb2tpbmcgbm9ybWFsIGRpc3RyaWJ1dGlvbnMuDQoNClRoaXMgbWVhbnMgJFxzcXJ0e059JCBpcyBleGFjdGx5IHRoZSBmYWN0b3IgdGhhdCBvZmZzZXRzIHRoZSBzcGVlZCBieSB3aGljaCB0aGUgZGlzdHJpYnV0aW9ucyBhYm92ZSBiZWNvbWUgbW9yZSBjb21wYWN0LiBXZSBjYW4gaW5mZXIgdGhhdCB0aGUgZGlzdHJpYnV0aW9ucyBjb252ZXJnZSB0byB6ZXJvIGF0IHRoZSByYXRlIHRoYXQgaXMgcmVxdWlyZWQgdG8gb2Zmc2V0IGNvbnZlcmdlbmNlLg0KDQpUbyBpbGx1c3RyYXRlIGZ1cnRoZXIsIHZhcnkgYGRlbHRhYCBpbiB0aGUgZm9sbG93aW5nIGNvZGUgc25pcHBldCBhbmQgb2JzZXJ2ZSBob3cgdGhlIGRpc3RyaWJ1dGlvbnMgc3RpbGwgbmFycm93IGlmIGl0IGlzIHNtYWxsZXIgdGhhbiAwLjUgbGlrZSwgZS5nLiB3aXRoIDAuMyAuLi4NCg0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0NCmRlbHRhID0gMC4zDQogIHRpYiAlPiUgbXV0YXRlKGRldiA9IChhcy5udW1lcmljKGFzLmNoYXJhY3RlcihOKSkpXmRlbHRhICogKGJldGFzMSAtIGIxKSkgJT4lIA0KICAgIGdncGxvdChhZXMoeCA9IGRldiwgeSA9IGZjdF9yZXYoTiksIGZpbGwgPSBOKSkgKw0KICAgIGdlb21fZGVuc2l0eV9yaWRnZXMoKSArIHlsYWIoIk4iKSArIA0KICAgIHhsYWIoVGVYKHInKCROXnszLzEwfShcaGF0e1xiZXRhfV8xIC0gXGJldGFfMSkkKScpKQ0KYGBgDQoNCi4uLiBhbmQgdGhhdCB0aGUgZGlzdHJpYnV0aW9ucyBleHBsb2RlIGlmIHdlIHNldCBpdCBsYXJnZXIgdGhhbiAwLjUsIGUuZy4gMC43Og0KDQpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQ0KZGVsdGEgPSAwLjcNCiAgdGliICU+JSBtdXRhdGUoZGV2ID0gKGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKE4pKSleZGVsdGEgKiAoYmV0YXMxIC0gYjEpKSAlPiUgDQogICAgZ2dwbG90KGFlcyh4ID0gZGV2LCB5ID0gZmN0X3JldihOKSwgZmlsbCA9IE4pKSArDQogICAgZ2VvbV9kZW5zaXR5X3JpZGdlcygpICsgeWxhYigiTiIpICsNCiAgICB4bGFiKFRlWChyJygkTl57Ny8xMH0oXGhhdHtcYmV0YX1fMSAtIFxiZXRhXzEpJCknKSkNCmBgYA0KDQo8YnI+DQoNCkFub3RoZXIgaWxsdXN0cmF0aW9uLCBob3cgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgZGV2aWF0aW9ucyBpcyBzdGFiaWxpemVkIGJ5IG11bHRpcGx5aW5nIHdpdGggJFxzcXJ0e059JCwgc2hvd3MgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiAkTl5cZGVsdGEoXGhhdHtcYmV0YV8xfSAtIFxiZXRhXzEpJCB3aGljaCBpcyAkXHNxcnR7VmFyKE5eXGRlbHRhKFxoYXR7XGJldGFfMX0gLSBcYmV0YV8xKSl9ID0gXHNxcnR7Tl57MlxkZWx0YX0gXGNkb3QgVmFyKFxoYXR7XGJldGFfMX0gLSBcYmV0YV8xKX0gPSBOXlxkZWx0YSBcY2RvdCBzZChcaGF0e1xiZXRhXzF9IC0gXGJldGFfMSkkLiBUaGUgZm9sbG93aW5nIGdyYXBoIHNob3dzIGhvdyAkTl5cZGVsdGEgXGNkb3Qgc2QoXGhhdHtcYmV0YV8xfSAtIFxiZXRhXzEpJCBldm9sdmVzIGZvciBkaWZmZXJlbnQgJFxkZWx0YSQgd2l0aCBpbmNyZWFzaW5nICROJDoNCg0KYGBge3J9DQpuZXdfdGliID0gdGliICU+JSBtdXRhdGUoZGV2ID0gYmV0YXMxIC0gYjEsIE4gPSBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihOKSkpICAlPiUgZ3JvdXBfYnkoTikgJT4lIHN1bW1hcmlzZShzZF9kZXYgPSBzcXJ0KHZhcihkZXYpKSkgDQoNCmRlbHRhX3NlcSA9IGMoMCwwLjMsMC40LDAuNSwwLjU1LDAuNikNCm5ld190aWIgPSBjYmluZChhcy5tYXRyaXgobmV3X3RpYiksIG1hdHJpeChOQSxucm93ID0gZGltKGFzLm1hdHJpeChuZXdfdGliKSlbMV0sbmNvbCA9ICBsZW5ndGgoZGVsdGFfc2VxKSkpDQpjb2xuYW1lcyhuZXdfdGliKSA9IGMoIk4iLCAic2QiLCByZXAoIlRlbXAiLGxlbmd0aChkZWx0YV9zZXEpKSkNCg0KZm9yIChpIGluIDE6bGVuZ3RoKGRlbHRhX3NlcSkpIHsNCiAgZGVsdGEgPSBkZWx0YV9zZXFbaV0NCiAgY29sbmFtZXMobmV3X3RpYilbaSsyXSA9IHBhc3RlMCgiZD0iLHRvU3RyaW5nKGRlbHRhKSkNCiAgbmV3X3RpYlssaSsyXSA9IG5ld190aWJbLDFdXihkZWx0YSkgKiBuZXdfdGliWywyXQ0KfQ0KDQpuZXdfdGliID0gYXNfdGliYmxlKG5ld190aWIpICU+JSBwaXZvdF9sb25nZXIoY29scyA9IHN0YXJ0c193aXRoKCJkPSIpLCBuYW1lc190byA9ICJkZWx0YSIsIG5hbWVzX3ByZWZpeCA9ICJkPSIsIHZhbHVlc190byA9ICJ2YWx1ZSIpICU+JSBtdXRhdGUoZGVsdGEgPSBhc19mYWN0b3IoZGVsdGEpKQ0KDQpuZXdfdGliICU+JSBnZ3Bsb3QoYWVzKHggPSBOLCB5ID0gdmFsdWUsIGNvbG9yID0gZGVsdGEpKSsNCiAgZ2VvbV9saW5lKGFlcyhzaXplID0gZGVsdGEpKSArIHNjYWxlX3NpemVfbWFudWFsKHZhbHVlcyA9IGMoMC41LDAuNSwgMC41LCAyLDAuNSwwLjUpKSArIA0KICAgIHlsYWIoVGVYKHInKCROXlxkZWx0YSBcY2RvdCBzZChcaGF0e1xiZXRhXzF9IC0gXGJldGFfMSkkKScpKQ0KDQpgYGANCldpdGggJFxkZWx0YSA8IDEvMiQsIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgJFxoYXR7XGJldGFfMX0gLSBcYmV0YV8xJCBnb2VzIHRvIHplcm8uIE9ubHkgd2hlbiB3aGVuIG11bHRpcGx5aW5nIGJ5ICROXnsxLzJ9JCAodGhpY2spLCB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIHJlbWFpbnMgY29uc3RhbnQgYWNyb3NzIGRpZmZlcmVudCBzYW1wbGUgc2l6ZXMgJFxSaWdodGFycm93JCBUaGUgZGlzdHJpYnV0aW9uIGlzIGNhc2Ugc3RhYmlsaXplZC4gTXVsdGlwbGljYXRpb24gYnkgJE5eXGRlbHRhLCBcZGVsdGE+MS8yJCBtYWtlcyB0aGUgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIHRoZSBwcm9kdWN0IGluY3JlYXNlIHdpdGggaW5jcmVhc2luZyBzYW1wbGUgc2l6ZSAkXFJpZ2h0YXJyb3ckIHRoZSBkaXN0cmlidXRpb25zIGV4cGxvZGUgYXN5bXB0b3RpY2FsbHkuDQo8YnI+DQoNCiMjIyMgJE4oXGhhdHtcYmV0YX1fMSAtIFxiZXRhXzEpXjIkDQoNCk5vdGUgc2ltaWxhcmx5IHRoYXQgJE4kIHN0YWJpbGl6ZXMgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgc3F1YXJlZCBkaWZmZXJlbmNlDQoNCmBgYHtyfQ0KdGliICU+JSBtdXRhdGUoc3FydG5fZGV2ID0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoTikpICogKGJldGFzMSAtIGIxKV4yKSAlPiUgDQogIGdncGxvdChhZXMoeCA9IHNxcnRuX2RldiwgeSA9IGZjdF9yZXYoTiksIGZpbGwgPSBOKSkgKw0KICBnZW9tX2RlbnNpdHlfcmlkZ2VzKCkgKyB5bGFiKCJOIikgKw0KICAgeGxhYihUZVgocicoJE4oXGhhdHtcYmV0YX1fMSAtIFxiZXRhXzEpXjIkKScpKQ0KYGBgDQoNCjxicj4NCg0KIyMjIyAkXHNxcnR7TihcaGF0e1xiZXRhfV8xIC0gXGJldGFfMSleMn0kDQoNCi4uLiBhbmQgJFxzcXJ0IE4kIHN0YWJpbGl6ZXMgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgc3F1YXJlIHJvb3Qgb2YgdGhlIHNxdWFyZWQgZGlmZmVyZW5jZToNCg0KYGBge3J9DQp0aWIgJT4lIG11dGF0ZShzcXJ0bl9kZXYgPSBzcXJ0KCBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihOKSkgKiAoYmV0YXMxIC0gYjEpXjIpICkgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSBzcXJ0bl9kZXYsIHkgPSBmY3RfcmV2KE4pLCBmaWxsID0gTikpICsNCiAgZ2VvbV9kZW5zaXR5X3JpZGdlcygpICsgeWxhYigiTiIpICsNCiAgIHhsYWIoVGVYKHInKCRcc3FydHtOKFxoYXR7XGJldGF9XzEgLSBcYmV0YV8xKV4yfSQpJykpDQoNCmBgYA0KDQo8YnI+DQoNCg0KIyMjIENvbnZlcmdlbmNlIHJhdGUgb2YgT0xTIC0gUk1TRSBvZiBmaXR0ZWQvcHJlZGljdGVkIHZhbHVlcw0KDQpJbiB0aGlzIGNvdXJzZSwgaXQgd2lsbCBiZSBpbXBvcnRhbnQgaG93IGZhc3QgZml0dGVkL3ByZWRpY3RlZCB2YWx1ZXMgb2YgYSBtZXRob2QgY29udmVyZ2UgdG8gdGhlIENFRiwgbm90IGhvdyBmYXN0IGEgc2luZ2xlIGNvZWZmaWNpZW50IGNvbnZlcmdlcy4NCg0KVGhlcmVmb3JlIG5vdGUgdGhhdCAkXHNxcnR7Tn0kIGNvbnZlcmdlbmNlIGluIHRoZSBwYXJhbWV0ZXJzIGltcGxpZXMgJFxzcXJ0e059JCBvZiB0aGUgcm9vdCBtZWFuIHNxdWFyZWQgZXJyb3IgKFJNU0UpICQkUk1TRSA9IFxzcXJ0e1xmcmFjezF9e059XHN1bV9pKFhfaSdcaGF0e1xiZXRhfV9OIC0gWF9pJ1xiZXRhKV4yfSQkDQoNClRvIGlsbHVzdHJhdGUgdGhpcywgd2UgcmVwZWF0IHRoZSBzaW11bGF0aW9uIGFib3ZlLCBidXQgaW5zdGVhZCBvZiBzYXZpbmcgdGhlIGNvZWZmaWNpZW50LCB3ZSBzYXZlIHRoZSBSTVNFIG9mIGVhY2ggcnVuLg0KDQpgYGB7cn0NCiMgSW5pdGlhbGl6ZSBlbXB0eSBtYXRyaXggdG8gZmlsbCBpbiBSTVNFDQpyZXN1bHRzID0gbWF0cml4KE5BLHIsc2VxKzEpDQpjb2xuYW1lcyhyZXN1bHRzKSA9IHJlcCgiVGVtcCIsc2VxKzEpICMgVG8gYmUgYXV0b21hdGljYWxseSByZXBsYWNlZCBpbiBsb29wIGJlbG93DQoNCmZvciAoaSBpbiAwOnNlcSkgeyAjIE91dGVyIGxvb3Agb3ZlciBzYW1wbGUgc2l6ZXMgDQogIG4gPSA0XmkgKiBzdGFydCAjIHNhbXBsZSBzaXplIGZvciB0aGlzIGxvb3ANCiAgY29sbmFtZXMocmVzdWx0cylbaSsxXSA9IHBhc3RlMCgibj0iLHRvU3RyaW5nKG4pKSAjIHNhdmUgc2FtcGxlIHNpemUNCiAgZm9yIChqIGluIDE6cikgeyAjIElubmVyIGxvb3Agb3ZlciByZXBsaWNhdGlvbnMNCiAgICAjIERyYXcgc2FtcGxlDQogICAgeCA9IHJ1bmlmKG4pDQogICAgeSA9IGNlZih4KSArIHJ1bmlmKG4sLTEsMSkNCiAgICAjIFNhdmUgUk1TRSBvZiB0aGlzIHJlcGxpY2F0aW9uDQogICAgcmVzdWx0c1tqLGkrMV0gPSBzcXJ0KCBtZWFuKCAobG0oeSB+IHgpJGZpdHRlZC52YWx1ZXMgLSBjZWYoeCkpXjIgKSApDQogIH0NCn0NCmBgYA0KDQpQbG90IHRoZSBSTVNFIGZvciBkaWZmZXJlbnQgc2FtcGxlIHNpemVzOg0KDQpgYGB7cn0NCnRpYiA9IGFzX3RpYmJsZShyZXN1bHRzKSAlPiUgcGl2b3RfbG9uZ2VyKGNvbHMgPSBzdGFydHNfd2l0aCgibj0iKSwgbmFtZXNfdG8gPSAiTiIsIG5hbWVzX3ByZWZpeCA9ICJuPSIsIHZhbHVlc190byA9ICJybXNlIikgJT4lIG11dGF0ZShOID0gYXNfZmFjdG9yKE4pKQ0KdGliICU+JSBnZ3Bsb3QoYWVzKHggPSBybXNlLCB5ID0gZmN0X3JldihOKSwgZmlsbCA9IE4pKSArDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoKSArIHlsYWIoIk4iKSArIHhsYWIoIlJNU0UiKQ0KYGBgDQoNClRoZSBkaXN0cmlidXRpb25zIG9mIFJNU0UgY29tZSBjbG9zZXIgdG8gemVybywgdGhlIGxhcmdlciB0aGUgc2FtcGxlIHNpemUuDQoNClRvIHNlZSB0aGF0IGFsc28gdGhlIFJNU0UgZGlzdHJpYnV0aW9ucyBjb252ZXJnZSBhdCByYXRlICRcc3FydHtOfSQsIHdlIGJsb3cgdXAgdGhlIHJhdyBkaXN0cmlidXRpb25zIG9uY2UgbW9yZSBhbmQgb2JzZXJ2ZSB0aGF0IHRoZXkgc3RhYmlsaXplIHdoZW4gZG9pbmcgc286DQoNCmBgYHtyfQ0KdGliICU+JSBnZ3Bsb3QoYWVzKHggPSBzcXJ0KCBhcy5udW1lcmljKGFzLmNoYXJhY3RlcihOKSkgKSAqIHJtc2UgLCB5ID0gZmN0X3JldihOKSwgZmlsbCA9IE4pKSArDQogIGdlb21fZGVuc2l0eV9yaWRnZXMoKSArIHlsYWIoIk4iKSArIHhsYWIoVGVYKHInKCRcc3FydHtOfVx0aW1lcyBSTVNFJCknKSkNCmBgYA0KDQo8YnI+DQoNClRoaXMgaWxsdXN0cmF0ZXMgdGhlIChmb3IgbWUpIG1vc3QgaW50dWl0aXZlIGltcGxpY2F0aW9uIG9mICRcc3FydHtOfSQgY29udmVyZ2VuY2UgdGhhdCB0aGUgUk1TRSBoYWx2ZXMgd2hlbiB3ZSBxdWFkcnVwbGUgdGhlIHNhbXBsZSBzaXplLg0KDQpDaGVjayB0aGUgbWVhbnMgb2YgdGhlIFJNU0UgZGlzdHJpYnV0aW9ucw0KDQpgYGB7cn0NCm1ybXNlID0gcm91bmQoY29sTWVhbnMocmVzdWx0cyksMykNCm1ybXNlDQpgYGANCg0KYW5kIHJlYWxpemUgdGhhdCB0aGV5IGluZGVlZCBuZWFybHkgcGVyZmVjdGx5IGhhbHZlOg0KDQpgYGB7cn0NCnJvdW5kKG1ybXNlL2xhZyhtcm1zZSksMykNCmBgYA0KIA0KPGJyPg0KPGJyPg0KDQoqSWRlYXMgZm9yIERJWSBleHRlbnNpb25zOioNCg0KLSBVc2UgZGlmZmVyZW50IHNlcXVlbmNlcyBvZiAkTiQNCg0KLSBDaGVjayBhbHNvICRcYmV0YV8wJA0KDQotIElsbHVzdHJhdGUgdGhhdCB0aGUgbWVhbiBzcXVhcmVkIGVycm9yIChNU0UpIGNvbnZlcmdlcyBhdCByYXRlICROJA0KDQotIEFkYXB0IHRoZSBER1AsIGUuZy4gZGlmZmVyZW50IHBhcmFtZXRlciB2YWx1ZXMsIG1vcmUgdGhhbiBvbmUgY292YXJpYXRlLCBkaWZmZXJlbnQgZXJyb3IgdGVybSBkaXN0cmlidXRpb24sIC4uLg0KDQo8YnI+DQo8YnI+DQoNCiMjIyBLZXJuZWwgcmVncmVzc2lvbiBjb252ZXJnZW5jZSByYXRlcw0KDQpSZWNhbGwgdGhhdCB3ZSBzcGVjaWZpZWQgYSBsaW5lYXIgQ0VGIGFuZCB0aGVyZWZvcmUgT0xTIGlzIHN1cHBvc2VkIHRvIHdvcmsgdmVyeSB3ZWxsLiBIb3dldmVyLCB3ZSBjb3VsZCBhbHNvIHVzZSBub25wYXJhbWV0cmljIG1ldGhvZHMgdGhhdCBkbyBub3QgYXNzdW1lIGxpbmVhcml0eS4gVGhleSBkaWZmZXIgZnJvbSBPTFMgaW4gc2V2ZXJhbCB3YXlzOg0KDQotIFRoZXkgZG8gbm90IGVzdGltYXRlIGdsb2JhbCBwYXJhbWV0ZXJzLiBDb252ZXJnZW5jZSBpbiB0ZXJtcyBvZiAkXGJldGEkcyBjYW4gdGhlcmVmb3JlIG5vdCBiZSBpbnZlc3RpZ2F0ZWQuDQoNCi0gVGhleSBjb252ZXJnZSBzbG93ZXIgdGhhbiAkXHNxcnR7Tn0kIGJlY2F1c2UgdGhleSBkbyBub3Qgd29yayB1bmRlciB0aGUgYXNzdW1wdGlvbiB0aGF0IHRoZSB3b3JsZCBpcyBsaW5lYXIgYW5kIG5lZWQgdG8gZmlndXJlIHRoaXMgb3V0IG9uIHRoZWlyIG93bi4NCg0KLSBUaGV5IGFyZSBjb21wdXRhdGlvbmFsbHkgbW9yZSBleHBlbnNpdmUuIFdlIHJ1biBub3Qgc28gbWFueSByZXBsaWNhdGlvbnMgYW5kIHNhbXBsZSBzaXplcyBhcyB3aXRoIE9MUw0KDQpXZSBydW4gdGhlIHNhbWUgc2ltdWxhdGlvbiBzdHVkeSBhcyBhYm92ZSwgYnV0IHVzaW5nIGtlcm5lbCByZWdyZXNzaW9uIHdpdGggY3Jvc3MtdmFsaWRhdGVkIGJhbmR3aWR0aCwgd2hpY2ggc2hvdWxkIGNvbnZlcmdlIGF0ICROXnsyLzV9JC4gV2UgcHJpbnQgdGhlIHBsb3Qgb2YgdGhlIGZpcnN0IHJlcGxpY2F0aW9uIG9mIGEgc2FtcGxlIHNpemUgdG8gaWxsdXN0cmF0ZSB0aGUgZml0Lg0KDQoNCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30NCiMgU2V0IHNpbXVsYXRpb24gcGFyYW1ldGVycw0KciA9IDEwMCAjIERlY3JlYXNlIGZvciBmYXN0ZXIgcnVubmluZyB0aW1lLCBpbmNyZWFzZSBmb3IgbW9yZSBwcmVjaXNlIHJlc3VsdHMNCnNlcSA9IDQNCg0KIyBJbml0aWFsaXplIGVtcHR5IG1hdHJpeCB0byBmaWxsIGluIFJNU0UNCnJlc3VsdHMgPSBtYXRyaXgoTkEscixzZXErMSkNCmNvbG5hbWVzKHJlc3VsdHMpID0gcmVwKCJUZW1wIixzZXErMSkgIyBUbyBiZSBhdXRvbWF0aWNhbGx5IHJlcGxhY2VkIGluIGxvb3AgYmVsb3cNCg0KZm9yIChpIGluIDA6c2VxKSB7ICMgT3V0ZXIgbG9vcCBvdmVyIHNhbXBsZSBzaXplcyANCiAgbiA9IDReaSAqIHN0YXJ0ICMgc2FtcGxlIHNpemUgZm9yIHRoaXMgbG9vcA0KICBjb2xuYW1lcyhyZXN1bHRzKVtpKzFdID0gcGFzdGUwKCJuPSIsdG9TdHJpbmcobikpICMgc2F2ZSBzYW1wbGUgc2l6ZQ0KICBmb3IgKGogaW4gMTpyKSB7ICMgSW5uZXIgbG9vcCBvdmVyIHJlcGxpY2F0aW9ucw0KICAgICMgRHJhdyBzYW1wbGUgICAgDQogICAgeCA9IHJ1bmlmKG4pDQogICAgeSA9IGNlZih4KSArIHJ1bmlmKG4sLTEsMSkNCiAgICANCiAgICAjIENyb3NzLXZhbGlkYXRlIGJhbmR3aWR0aCwgYnV0IG9ubHkgb25jZSBmb3IgY29tcHV0YXRpb25hbCByZWFzb25zDQogICAgaWYoaj09MSkgYndvYmogPSBucHJlZ2J3KHlkYXQgPSB5LCB4ZGF0ID0geCwgIHJlZ3R5cGUgPSAnbGMnLCBid21ldGhvZCA9ICdjdi5scycpDQogICAgIyBSdW4gS2VybmVsIHJlZ3Jlc3Npb24NCiAgICBtb2RlbCA9IG5wcmVnKHR5ZGF0ID0geSwgdHhkYXQgPSB4LCBid3M9YndvYmokYncsIHJlZ3R5cGUgPSAnbGMnKQ0KICAgIGlmKGo9PTEpIHBsb3QobW9kZWwsIG1haW4gPSBwYXN0ZTAoIk49Iix0b1N0cmluZyhuKSkpDQogICAgcmVzdWx0c1tqLGkrMV0gPSBzcXJ0KCBtZWFuKCAoZml0dGVkKG1vZGVsKSAtIGNlZih4KSleMiApICkNCiAgfQ0KfQ0KYGBgDQoNClRoZSBSTVNFIG9mIGtlcm5lbCByZWdyZXNzaW9uIGRlY3JlYXNlcyBhbHNvIHdpdGggaGlnaGVyICROJA0KDQpgYGB7cn0NCm1ybXNlID0gcm91bmQoY29sTWVhbnMocmVzdWx0cyksMykNCm1ybXNlDQpgYGANCg0KQlVUIGF0IGEgc2xvd2VyIHJhdGUgKGFzIGV4cGVjdGVkKQ0KIA0KYGBge3J9DQpyb3VuZChtcm1zZS9sYWcobXJtc2UpLDMpDQpgYGANCg0KDQpOb3RlIHRoYXQgdGhlIHRoZW9yZXRpY2FsIGRlY3JlYXNlIGZvciBxdWFkcnVwbGluZyBzYW1wbGUgc2l6ZSBpcw0KYGBge3J9DQpuXigyLzUpIC8gKDQqbileKDIvNSkNCmBgYA0KDQp3aGljaCBpcyByb3VnaGx5IHdoYXQgd2Ugb2JzZXJ2ZSAodGhlIG1vcmUgcmVwbGljYXRpb25zIHlvdSBjYW4gY29tcHV0YXRpb25hbGx5IGFmZm9yZCwgdGhlIGNsb3NlciBpdCBzaG91bGQgZ2V0KS4NCg0KPGJyPg0KPGJyPg0KDQojIyBOb24tbGluZWFyIHdvcmxkDQoNClNvIGZhciwgd2UgY3JlYXRlZCBhIHdvcmxkIHdoZXJlIE9MUyBzaGluZXMgYW5kIHNob3dzICh0aGUgYmVzdCBwb3NzaWJsZSkgJFxzcXJ0e059JCBjb252ZXJnZW5jZSBvZiBjb2VmZmljaWVudHMgYW5kIFJNU0UuDQoNCkJVVCB3aHkgc2hvdWxkIHRoZSB3b3JsZCBiZSBsaW5lYXI/IFRvIG1ha2UgdGhlIHBvaW50LCBsZXQncyBwdXNoIGl0IGFuZCBhc3N1bWUgdGhlIGZvbGxvd2luZyBkZWZpbml0ZWx5IG5vbiBsaW5lYXIgREdQOiAkJFkgPSBcdW5kZXJicmFjZXtzaW4oWCl9X3tDRUZ9ICsgVSQkDQp3aGVyZSANCiQkWCBcc2ltIHVuaWZvcm0oLTEuNDMgXHBpLDEuNDMgXHBpKTt+VSBcc2ltIFxtYXRoY2Fse059KDAsMSkkJA0KDQoNCmBgYHtyfQ0KbiA9IDUwMCAgICAgICAgIyBTYW1wbGUgc2l6ZSBmb3IgaWxsdXN0cmF0aW9uDQoNCiMgRGVmaW5lIGNvbmRpdGlvbmFsIGV4cGVjdGF0aW9uIGZ1bmN0aW9uDQpjZWYgPSBmdW5jdGlvbih4KXtzaW4oeCl9DQoNCiMgRHJhdyBzYW1wbGUNCnggPSBydW5pZihuLC1waSoxLjQzLHBpKjEuNDMpDQp5ID0gY2VmKHgpICsgcm5vcm0obiwwLDEpDQoNCiMgUGxvdCBzYW1wbGUNCmRmID0gZGF0YS5mcmFtZSh4PXgseT15KQ0KZ2dwbG90KGRmKSArIHN0YXRfZnVuY3Rpb24oZnVuPWNlZixzaXplPTEpICsgDQogICAgICAgICAgIGdlb21fcG9pbnQoYWVzKHg9eCx5PXkpLGNvbG9yPSJibHVlIixhbHBoYSA9IDAuNCkNCmBgYA0KDQoNClRoZSBER1AgaXMgZW5naW5lZXJlZCB0byB0cmljayBPTFMgdG8gZXN0aW1hdGUgJFxiZXRhXzAgPSBcYmV0YV8xID0gMCQgZXhwbG9pdGluZyBiYXNpY2FsbHkgbm8gaW5mb3JtYXRpb24gaW4gdGhlIGRhdGFzZXQ6DQoNCmBgYHtyfQ0Kc3VtbWFyeShsbSh5fiB4KSkNCmBgYA0KDQo8YnI+DQoNCk5vdyB3ZSBydW4gYSBzaW11bGF0aW9uIHN0dWR5IHdpdGggdGhpcyBER1AgYW5kIGNvbXBhcmUgdGhlIGV2b2x1dGlvbiBvZiB0aGUgUk1TRSBiZXR3ZWVuIE9MUyBhbmQga2VybmVsIHJlZ3Jlc3Npb246DQoNCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30NCnIgPSAxMDAgIyBEZWNyZWFzZSBmb3IgZmFzdGVyIHJ1bm5pbmcgdGltZSwgaW5jcmVhc2UgZm9yIG1vcmUgcHJlY2lzZSByZXN1bHRzDQpzZXEgPSA0DQoNCiMgSW5pdGlhbGl6ZSBlbXB0eSBtYXRyaWNlcyB0byBmaWxsIGluIFJNU0UNCnJlc3VsdHNfb2xzID0gcmVzdWx0c19rciA9IG1hdHJpeChOQSxyLHNlcSsxKQ0KTnMgPSByZXAoTkEsc2VxKzEpDQoNCmZvciAoaSBpbiAwOnNlcSkgeyAjIE91dGVyIGxvb3Agb3ZlciBzYW1wbGUgc2l6ZXMgDQogIG4gPSA0XmkgKiBzdGFydCAjIHNhbXBsZSBzaXplIGZvciB0aGlzIGxvb3ANCiAgTnNbaSsxXSA9IG4NCiAgZm9yIChqIGluIDE6cikgeyAjIElubmVyIGxvb3Agb3ZlciByZXBsaWNhdGlvbnMNCiAgICAjIERyYXcgc2FtcGxlDQogICAgeCA9IHJ1bmlmKG4sLXBpKjEuNDMscGkqMS40MykNCiAgICB5ID0gY2VmKHgpICsgcm5vcm0obiwwLDEpDQogICAgDQogICAgIyBPTFMNCiAgICByZXN1bHRzX29sc1tqLGkrMV0gPSBzcXJ0KCBtZWFuKCAobG0oeSB+IHgpJGZpdHRlZC52YWx1ZXMgLSBjZWYoeCkpXjIgKSApDQogICAgDQogICAgIyBLZXJuZWwNCiAgICBid29iaiA9IG5wcmVnYncoeWRhdCA9IHksIHhkYXQgPSB4LCByZWd0eXBlID0gJ2xjJywgYndtZXRob2QgPSAnY3YubHMnKQ0KICAgIG1vZGVsID0gbnByZWcodHlkYXQgPSB5LCB0eGRhdCA9IHgsIGJ3cz1id29iaiRidywgcmVndHlwZSA9ICdsYycpDQogICAgaWYoaj09MSkgcGxvdChtb2RlbCwgbWFpbiA9IHBhc3RlMCgiTj0iLHRvU3RyaW5nKG4pKSkNCiAgICByZXN1bHRzX2tyW2osaSsxXSA9IHNxcnQoIG1lYW4oIChmaXR0ZWQobW9kZWwpIC0gY2VmKHgpKV4yICkgKQ0KICB9DQp9DQpgYGANCg0KDQpPTFMgd2l0aCBvbmx5IHRoZSBtYWluIGVmZmVjdCBoYXMgbm8gY2hhbmNlIGFuZCBjb252ZXJnZXMgdG8gYSBjb21wbGV0ZWx5IHdyb25nIENFRiBzdWNoIHRoYXQgdGhlIFJNU0UgZG9lcyBub3QgZGVjcmVhc2Ugd2l0aCBpbmNyZWFzaW5nIHNhbXBsZSBzaXplLiBLZXJuZWwgcmVncmVzc2lvbiBvbiB0aGUgb3RoZXIgaGFuZCBzdGVhZGlseSBidXQgc2xvd2x5IGltcHJvdmVzIGFzIHdlIGluY3JlYXNlIHRoZSBzYW1wbGUgc2l6ZToNCg0KYGBge3J9DQp0aWJibGUoTiA9IE5zLCBvbHMgPSBjb2xNZWFucyhyZXN1bHRzX29scyksIGtlcm5lbCA9IGNvbE1lYW5zKHJlc3VsdHNfa3IpKSAlPiUNCiAgICAgIHBpdm90X2xvbmdlcihuYW1lc190byA9ICJFc3RpbWF0b3IiLHZhbHVlc190byA9ICJybXNlIiwgY29scyA9IC1OKSAlPiUNCiAgICAgIGdncGxvdChhZXMoeD1OLHk9cm1zZSxjb2xvciA9IEVzdGltYXRvcikpICsgZ2VvbV9saW5lKCkgICsgZ2VvbV9wb2ludCgpICsNCiAgICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDApDQpgYGANCg0KSW5kZWVkLCB0aGUgY29udmVyZ2VuY2UgcmF0ZSBvZiBLZXJuZWwgcmVncmVzc2lvbiBpbiB0aGlzIHNpbXVsYXRpb24gaXMgY2xvc2UgdG8gdGhlIHRoZW9yZXRpY2FsIGByIHJvdW5kKG5eKDIvNSkgLyAoNCpuKV4oMi81KSwzKWAgKGFuZCBwcm9iYWJseSB3b3VsZCBjb21lIGNsb3NlciBpZiB3ZSBpbmNyZWFzZWQgYHJgKToNCiANCmBgYHtyfQ0Kcm91bmQoY29sTWVhbnMocmVzdWx0c19rcikvbGFnKGNvbE1lYW5zKHJlc3VsdHNfa3IpKSwzKQ0KYGBgDQoNCjxicj4NCjxicj4NCg0KDQoqKklkZWFzIGZvciBESVkgZXh0ZW5zaW9ucyoqOg0KDQotIElsbHVzdHJhdGUgdGhhdCB0aGUgY29lZmZpY2llbnRzIG9mIE9MUyBjb252ZXJnZSBhdCAkXHNxcnR7Tn0kIHRvIHplcm8/DQoNCi0gSG93IGRvZXMgT0xTIHBlcmZvcm0gaWYgd2UgYWRkIHNxdWFyZWQsIGN1YmljLC4uLiB0ZXJtcz8=