Goals:

  • Illustrate convergence rates

  • Illustrate the difference between convergence of coefficients and convergence of root mean squared error

  • Illustrate the differences between OLS and kernel regression


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:

  1. Inspect distributions of \(\hat{\beta}_N\) for increasing \(N\)

  2. Inspect distributions of \(\hat{\beta}_N - \beta\) for increasing \(N\)

  3. 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”:

  1. Draw a random sample from the DGP defined above of size \(N\).

  2. Run OLS and save the coefficients.

  3. Repeat 1. and 2. \(R\) times.

  4. 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:

  1. 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).

  2. 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=