Goals:

  • Illustrate how OLS overfits

  • Illustrate why we should evaluate predictions out-of-sample


Data generating process

Definition

Consider the following linear data generating process (DGP):

  • \(p\geq10\) independent and standard normal covariates: \(X \sim N(0,I_p)\), where \(I_p\) is the \(p\)-dimensional identity matrix

  • The outcome model is \(Y = \underbrace{\beta_0 + \beta_1 X_1 + ... + \beta_{10} X_{10}}_{\text{conditional expectation function }m(X)} + e\), where \(X_j\) is the \(j\)-th column of \(X\) and \(e \sim N(0,1)\)

  • We consider the following parameters \(\beta_0 = 0\), \(\beta_1 = 1\), \(\beta_2 = 0.9\), …, \(\beta_9 = 0.2\), \(\beta_{10} = 0.1\) such that the first 10 variables have a decreasing impact on the outcome and any further variables are just irrelevant noise with no true predictive power

We set \(p=99\) and draw a training sample with \(N_{tr}=100\) and a test sample with \(N_{te}=10,000\)

# Load the packages required for later
if (!require("tidyverse")) install.packages("tidyverse", dependencies = TRUE); library(tidyverse)
if (!require("skimr")) install.packages("skimr", dependencies = TRUE); library(skimr)

set.seed(1234) # For replicability

# Define the important parameters
n_tr = 100
n_te = 10000
p = 99
beta = c(0,seq(1,0.1,-0.1),rep(0,p-10))

# Combine constant and randomly drawn covariates
x_tr = cbind(rep(1,n_tr),matrix(rnorm(n_tr*p),ncol=p))
x_te = cbind(rep(1,n_te),matrix(rnorm(n_te*p),ncol=p))

# Create the CEF using matrix multiplication for compactness
cfe_tr = x_tr %*% beta
cfe_te = x_te %*% beta

# Create the "observed" outcomes by adding noise
y_tr = cfe_tr + rnorm(n_tr,0,1)
y_te = cfe_te + rnorm(n_te,0,1)



Some descriptives

Summarize the covariates:

skim(x_tr)
── Data Summary ────────────────────────
                           Values
Name                       x_tr  
Number of rows             100   
Number of columns          100   
_______________________          
Column type frequency:           
  numeric                  100   
________________________         
Group variables            None  

Plot the resulting outcome distribution:

hist(cfe_te)



Perfectly specified OLS

Imagine that we were told that the true model consists only of the first ten variables. Then we could specify the correct model:

perfect_ols = lm(y_tr ~ x_tr[,2:11])
summary(perfect_ols)

Call:
lm(formula = y_tr ~ x_tr[, 2:11])

Residuals:
    Min      1Q  Median      3Q     Max 
-2.4716 -0.6772 -0.1431  0.6921  3.3305 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)    -0.08222    0.11159  -0.737   0.4632    
x_tr[, 2:11]1   0.99921    0.11132   8.976 4.21e-14 ***
x_tr[, 2:11]2   0.88997    0.11319   7.862 8.31e-12 ***
x_tr[, 2:11]3   0.90388    0.11773   7.678 1.98e-11 ***
x_tr[, 2:11]4   0.72184    0.10824   6.669 2.10e-09 ***
x_tr[, 2:11]5   0.66229    0.10169   6.513 4.27e-09 ***
x_tr[, 2:11]6   0.54058    0.12074   4.477 2.23e-05 ***
x_tr[, 2:11]7   0.25639    0.11920   2.151   0.0342 *  
x_tr[, 2:11]8   0.09273    0.11552   0.803   0.4243    
x_tr[, 2:11]9   0.24757    0.12604   1.964   0.0526 .  
x_tr[, 2:11]10  0.03388    0.11159   0.304   0.7621    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.073 on 89 degrees of freedom
Multiple R-squared:  0.8153,    Adjusted R-squared:  0.7945 
F-statistic: 39.28 on 10 and 89 DF,  p-value: < 2.2e-16

We can use it to produce fitted values in the training sample and predicted values in the test sample and plot them against the observed outcomes:

yhat_pols_tr = predict(perfect_ols)
plot(yhat_pols_tr,y_tr)

yhat_pols_te = x_te[,1:11] %*% perfect_ols$coefficients
plot(yhat_pols_te,y_te)

We observe a clear positive correlation between predicted and actual outcomes \(\Rightarrow\) true model does a good job already with 100 observations.

Next, lets check the in-sample and out-of-sample MSE:

paste("In-sample MSE:", round( mean( (y_tr - yhat_pols_tr)^2),3 ) ) 
[1] "In-sample MSE: 1.024"
paste("Out-of-sample MSE:", round( mean( (y_te - yhat_pols_te)^2),3 ) )
[1] "Out-of-sample MSE: 1.124"

This indicates that the in-sample MSE understates the true error \(\Rightarrow\) first signs of overfitting, but could also be by chance



The failure of in-sample measures

Gradually adding covariates

Now consider the case where nobody tells us the correct model and we gradually add the variables to the model. We start with a model including a constant and \(X_1\), then add \(X_2\), then \(X_3\), …, and end up with a model with a constant and 99 regressors.

We calculate and store four quantities: the training and test sample MSE that would be observable in a real application \((Y-\hat{m}(X))^2\) and the respective oracle MSEs \((Y-m(X))^2\), where we exploit that we defined and thus know the true CEF.

# Container of the results
results_ols = matrix(NA,p,4)
colnames(results_ols) = c("Obs MSE train","Obs MSE test","Oracle MSE train","Oracle MSE test")

# Loop that gradually adds variables
for (i in 1:p) {
  temp_ols = lm(y_tr ~ x_tr[,2:(i+1)])
  temp_yhat_tr = predict(temp_ols)
  temp_yhat_te = x_te[,1:(i+1)] %*% temp_ols$coefficients
  
  # Calculate the observable MSEs in training and test sample
  results_ols[i,1] = mean((y_tr - temp_yhat_tr)^2) # in-sample MSE
  results_ols[i,2] = mean((y_te - temp_yhat_te)^2) # out-of-sample MSE
  
  # Calculate the oracle MSEs that are only observables b/c we know the true CEF
  results_ols[i,3] = var(y_tr - cfe_tr) + mean((cfe_tr - temp_yhat_tr)^2)
  results_ols[i,4] = var(y_te - cfe_te) + mean((cfe_te - temp_yhat_te)^2)
}

Let’s first check how the in-sample MSE develops:

# prepare for plotting
df = data.frame("Number of variables" = 1:p,results_ols)

# Plot training MSE
ggplot(df, aes(Number.of.variables)) + 
  geom_line(aes(y = Obs.MSE.train, colour = "Obs.MSE.train"),size=1) + 
  ylab("MSE") + geom_hline(yintercept = 0)

The in-sample MSE drops substantially in the beginning when the relevant variables are added. However, it further decreases gradually as we add more noise variables. Finally, the MSE is exactly zero if as many parameters as observations are estimated.

paste("In-sample MSE with constant and ",p,"predictors:", round(results_ols[p,1]))
[1] "In-sample MSE with constant and  99 predictors: 0"

Remark: This implies also an \(R^2\) of 1 because \(R^2 = 1-MSE/SSE = 1-0/SSE=1\). This also reminds us that optimizing in-sample \(R^2\) when specifying OLS models is a bad guide.



The value of out-of-sample validation

Now let’s plot the training sample MSE from before and the test sample MSE:

# Comparison of observable MSEs
ggplot(df, aes(Number.of.variables)) + 
  geom_line(aes(y = Obs.MSE.train, colour = "Obs.MSE.train"),size=1) + 
  geom_line(aes(y = Obs.MSE.test, colour = "Obs.MSE.test"),size=1) + ylab("MSE") +
  geom_hline(yintercept = 0)

While the training MSE drops to zero, the test sample MSE explodes when the ratio of number of covariates and observations approaches one.

Let’s cut off the extreme values from the right:

ggplot(df[1:80,], aes(Number.of.variables)) + 
  geom_line(aes(y = Obs.MSE.train, colour = "Obs.MSE.train"),size=1) + 
  geom_line(aes(y = Obs.MSE.test, colour = "Obs.MSE.test"),size=1) + ylab("MSE") +
  geom_hline(yintercept = 0)

We observe completely different trajectories between training and test MSE after the relevant variables are added.

Finally, let’s check how well the feasible MSE compares to the infeasible oracle MSE:

ggplot(df[1:80,], aes(Number.of.variables)) + 
  geom_line(aes(y = Obs.MSE.test, colour = "Obs.MSE.test"),size=1) + 
  geom_line(aes(y = Oracle.MSE.test, colour = "Oracle.MSE.test"),size=1) + ylab("MSE") +
  geom_hline(yintercept = 0)

The observable and the oracle MSE are nearly identical. This illustrates why using the observable out-of-sample MSE as proxy for predictive performance is so powerful.



Take-aways

  • It is easy to optimize in-sample fit for OLS by adding noise variables, but this does not resemble the true prediction performance.

  • Test samples are crucial to understand the true predictive performance of models.

  • The observable MSE is nearly identical to the (in applications not observable) oracle MSE.



Suggestions to play with the toy model

Feel free to play around with the code. This is useful to sharpen and challenge your understanding of the methods. Think about the consequences of a modification before you run it and check whether the results are in line with your expectation. Some suggestions:

  • Modify DGP (change betas, change level of noise, introduce correlation between covariates, …)

  • Modify seed

  • Change training and test sample size

LS0tDQp0aXRsZTogIk92ZXJmaXR0aW5nIG9mIE9MUyBhbmQgdmFsdWUgb2YgdHJhaW5pbmcgdnMuIHRlc3Qgc2FtcGxlIg0KYXV0aG9yOiAiTWljaGFlbCBLbmF1cyINCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVtLyV5JylgIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCi0tLQ0KDQo8YnI+DQoNCkdvYWxzOg0KDQotIElsbHVzdHJhdGUgaG93IE9MUyBvdmVyZml0cw0KDQotIElsbHVzdHJhdGUgd2h5IHdlIHNob3VsZCBldmFsdWF0ZSBwcmVkaWN0aW9ucyBvdXQtb2Ytc2FtcGxlDQoNCjxicj4NCg0KIyMgRGF0YSBnZW5lcmF0aW5nIHByb2Nlc3MNCg0KIyMjIERlZmluaXRpb24NCg0KQ29uc2lkZXIgdGhlIGZvbGxvd2luZyBsaW5lYXIgZGF0YSBnZW5lcmF0aW5nIHByb2Nlc3MgKERHUCk6DQoNCi0gJHBcZ2VxMTAkIGluZGVwZW5kZW50IGFuZCBzdGFuZGFyZCBub3JtYWwgY292YXJpYXRlczogJFggXHNpbSBOKDAsSV9wKSQsIHdoZXJlICRJX3AkIGlzIHRoZSAkcCQtZGltZW5zaW9uYWwgaWRlbnRpdHkgbWF0cml4DQoNCi0gVGhlIG91dGNvbWUgbW9kZWwgaXMgJFkgPSBcdW5kZXJicmFjZXtcYmV0YV8wICsgXGJldGFfMSBYXzEgKyAuLi4gKyBcYmV0YV97MTB9IFhfezEwfX1fe1x0ZXh0e2NvbmRpdGlvbmFsIGV4cGVjdGF0aW9uIGZ1bmN0aW9uIH1tKFgpfSArIGUkLCB3aGVyZSAkWF9qJCBpcyB0aGUgJGokLXRoIGNvbHVtbiBvZiAkWCQgYW5kICRlIFxzaW0gTigwLDEpJA0KDQotIFdlIGNvbnNpZGVyIHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVycyAkXGJldGFfMCA9IDAkLCAkXGJldGFfMSA9IDEkLCAkXGJldGFfMiA9IDAuOSQsIC4uLiwgJFxiZXRhXzkgPSAwLjIkLCAkXGJldGFfezEwfSA9IDAuMSQgc3VjaCB0aGF0IHRoZSBmaXJzdCAxMCB2YXJpYWJsZXMgaGF2ZSBhIGRlY3JlYXNpbmcgaW1wYWN0IG9uIHRoZSBvdXRjb21lIGFuZCBhbnkgZnVydGhlciB2YXJpYWJsZXMgYXJlIGp1c3QgaXJyZWxldmFudCBub2lzZSB3aXRoIG5vIHRydWUgcHJlZGljdGl2ZSBwb3dlcg0KDQpXZSBzZXQgJHA9OTkkIGFuZCBkcmF3IGEgdHJhaW5pbmcgc2FtcGxlIHdpdGggJE5fe3RyfT0xMDAkIGFuZCBhIHRlc3Qgc2FtcGxlIHdpdGggJE5fe3RlfT0xMCwwMDAkDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmcgPSBGfQ0KIyBMb2FkIHRoZSBwYWNrYWdlcyByZXF1aXJlZCBmb3IgbGF0ZXINCmlmICghcmVxdWlyZSgidGlkeXZlcnNlIikpIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KHRpZHl2ZXJzZSkNCmlmICghcmVxdWlyZSgic2tpbXIiKSkgaW5zdGFsbC5wYWNrYWdlcygic2tpbXIiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeShza2ltcikNCg0Kc2V0LnNlZWQoMTIzNCkgIyBGb3IgcmVwbGljYWJpbGl0eQ0KDQojIERlZmluZSB0aGUgaW1wb3J0YW50IHBhcmFtZXRlcnMNCm5fdHIgPSAxMDANCm5fdGUgPSAxMDAwMA0KcCA9IDk5DQpiZXRhID0gYygwLHNlcSgxLDAuMSwtMC4xKSxyZXAoMCxwLTEwKSkNCg0KIyBDb21iaW5lIGNvbnN0YW50IGFuZCByYW5kb21seSBkcmF3biBjb3ZhcmlhdGVzDQp4X3RyID0gY2JpbmQocmVwKDEsbl90ciksbWF0cml4KHJub3JtKG5fdHIqcCksbmNvbD1wKSkNCnhfdGUgPSBjYmluZChyZXAoMSxuX3RlKSxtYXRyaXgocm5vcm0obl90ZSpwKSxuY29sPXApKQ0KDQojIENyZWF0ZSB0aGUgQ0VGIHVzaW5nIG1hdHJpeCBtdWx0aXBsaWNhdGlvbiBmb3IgY29tcGFjdG5lc3MNCmNmZV90ciA9IHhfdHIgJSolIGJldGENCmNmZV90ZSA9IHhfdGUgJSolIGJldGENCg0KIyBDcmVhdGUgdGhlICJvYnNlcnZlZCIgb3V0Y29tZXMgYnkgYWRkaW5nIG5vaXNlDQp5X3RyID0gY2ZlX3RyICsgcm5vcm0obl90ciwwLDEpDQp5X3RlID0gY2ZlX3RlICsgcm5vcm0obl90ZSwwLDEpDQpgYGANCjxicj4NCjxicj4NCg0KDQojIyMgU29tZSBkZXNjcmlwdGl2ZXMNCg0KU3VtbWFyaXplIHRoZSBjb3ZhcmlhdGVzOg0KDQpgYGB7cn0NCnNraW0oeF90cikNCmBgYA0KDQpQbG90IHRoZSByZXN1bHRpbmcgb3V0Y29tZSBkaXN0cmlidXRpb246DQoNCmBgYHtyfQ0KaGlzdChjZmVfdGUpDQpgYGANCjxicj4NCjxicj4NCg0KDQojIyMgUGVyZmVjdGx5IHNwZWNpZmllZCBPTFMNCg0KSW1hZ2luZSB0aGF0IHdlIHdlcmUgdG9sZCB0aGF0IHRoZSB0cnVlIG1vZGVsIGNvbnNpc3RzIG9ubHkgb2YgdGhlIGZpcnN0IHRlbiB2YXJpYWJsZXMuIFRoZW4gd2UgY291bGQgc3BlY2lmeSB0aGUgY29ycmVjdCBtb2RlbDoNCg0KYGBge3J9DQpwZXJmZWN0X29scyA9IGxtKHlfdHIgfiB4X3RyWywyOjExXSkNCnN1bW1hcnkocGVyZmVjdF9vbHMpDQpgYGANCg0KV2UgY2FuIHVzZSBpdCB0byBwcm9kdWNlIGZpdHRlZCB2YWx1ZXMgaW4gdGhlIHRyYWluaW5nIHNhbXBsZSBhbmQgcHJlZGljdGVkIHZhbHVlcyBpbiB0aGUgdGVzdCBzYW1wbGUgYW5kIHBsb3QgdGhlbSBhZ2FpbnN0IHRoZSBvYnNlcnZlZCBvdXRjb21lczoNCg0KYGBge3J9DQp5aGF0X3BvbHNfdHIgPSBwcmVkaWN0KHBlcmZlY3Rfb2xzKQ0KcGxvdCh5aGF0X3BvbHNfdHIseV90cikNCnloYXRfcG9sc190ZSA9IHhfdGVbLDE6MTFdICUqJSBwZXJmZWN0X29scyRjb2VmZmljaWVudHMNCnBsb3QoeWhhdF9wb2xzX3RlLHlfdGUpDQpgYGANCg0KV2Ugb2JzZXJ2ZSBhIGNsZWFyIHBvc2l0aXZlIGNvcnJlbGF0aW9uIGJldHdlZW4gcHJlZGljdGVkIGFuZCBhY3R1YWwgb3V0Y29tZXMgJFxSaWdodGFycm93JCB0cnVlIG1vZGVsIGRvZXMgYSBnb29kIGpvYiBhbHJlYWR5IHdpdGggMTAwIG9ic2VydmF0aW9ucy4NCg0KTmV4dCwgbGV0cyBjaGVjayB0aGUgaW4tc2FtcGxlIGFuZCBvdXQtb2Ytc2FtcGxlIE1TRToNCg0KYGBge3J9DQpwYXN0ZSgiSW4tc2FtcGxlIE1TRToiLCByb3VuZCggbWVhbiggKHlfdHIgLSB5aGF0X3BvbHNfdHIpXjIpLDMgKSApIA0KcGFzdGUoIk91dC1vZi1zYW1wbGUgTVNFOiIsIHJvdW5kKCBtZWFuKCAoeV90ZSAtIHloYXRfcG9sc190ZSleMiksMyApICkNCmBgYA0KDQpUaGlzIGluZGljYXRlcyB0aGF0IHRoZSBpbi1zYW1wbGUgTVNFIHVuZGVyc3RhdGVzIHRoZSB0cnVlIGVycm9yICRcUmlnaHRhcnJvdyQgZmlyc3Qgc2lnbnMgb2Ygb3ZlcmZpdHRpbmcsIGJ1dCBjb3VsZCBhbHNvIGJlIGJ5IGNoYW5jZQ0KDQo8YnI+DQo8YnI+DQoNCg0KIyMgVGhlIGZhaWx1cmUgb2YgaW4tc2FtcGxlIG1lYXN1cmVzDQoNCiMjIyBHcmFkdWFsbHkgYWRkaW5nIGNvdmFyaWF0ZXMNCg0KTm93IGNvbnNpZGVyIHRoZSBjYXNlIHdoZXJlIG5vYm9keSB0ZWxscyB1cyB0aGUgY29ycmVjdCBtb2RlbCBhbmQgd2UgZ3JhZHVhbGx5IGFkZCB0aGUgdmFyaWFibGVzIHRvIHRoZSBtb2RlbC4gV2Ugc3RhcnQgd2l0aCBhIG1vZGVsIGluY2x1ZGluZyBhIGNvbnN0YW50IGFuZCAkWF8xJCwgdGhlbiBhZGQgJFhfMiQsIHRoZW4gJFhfMyQsIC4uLiwgYW5kIGVuZCB1cCB3aXRoIGEgbW9kZWwgd2l0aCBhIGNvbnN0YW50IGFuZCA5OSByZWdyZXNzb3JzLg0KDQpXZSBjYWxjdWxhdGUgYW5kIHN0b3JlIGZvdXIgcXVhbnRpdGllczogdGhlIHRyYWluaW5nIGFuZCB0ZXN0IHNhbXBsZSBNU0UgdGhhdCB3b3VsZCBiZSBvYnNlcnZhYmxlIGluIGEgcmVhbCBhcHBsaWNhdGlvbiAkKFktXGhhdHttfShYKSleMiQgYW5kIHRoZSByZXNwZWN0aXZlIG9yYWNsZSBNU0VzICQoWS1tKFgpKV4yJCwgd2hlcmUgd2UgZXhwbG9pdCB0aGF0IHdlIGRlZmluZWQgYW5kIHRodXMga25vdyB0aGUgdHJ1ZSBDRUYuDQoNCmBgYHtyfQ0KIyBDb250YWluZXIgb2YgdGhlIHJlc3VsdHMNCnJlc3VsdHNfb2xzID0gbWF0cml4KE5BLHAsNCkNCmNvbG5hbWVzKHJlc3VsdHNfb2xzKSA9IGMoIk9icyBNU0UgdHJhaW4iLCJPYnMgTVNFIHRlc3QiLCJPcmFjbGUgTVNFIHRyYWluIiwiT3JhY2xlIE1TRSB0ZXN0IikNCg0KIyBMb29wIHRoYXQgZ3JhZHVhbGx5IGFkZHMgdmFyaWFibGVzDQpmb3IgKGkgaW4gMTpwKSB7DQogIHRlbXBfb2xzID0gbG0oeV90ciB+IHhfdHJbLDI6KGkrMSldKQ0KICB0ZW1wX3loYXRfdHIgPSBwcmVkaWN0KHRlbXBfb2xzKQ0KICB0ZW1wX3loYXRfdGUgPSB4X3RlWywxOihpKzEpXSAlKiUgdGVtcF9vbHMkY29lZmZpY2llbnRzDQogIA0KICAjIENhbGN1bGF0ZSB0aGUgb2JzZXJ2YWJsZSBNU0VzIGluIHRyYWluaW5nIGFuZCB0ZXN0IHNhbXBsZQ0KICByZXN1bHRzX29sc1tpLDFdID0gbWVhbigoeV90ciAtIHRlbXBfeWhhdF90cileMikgIyBpbi1zYW1wbGUgTVNFDQogIHJlc3VsdHNfb2xzW2ksMl0gPSBtZWFuKCh5X3RlIC0gdGVtcF95aGF0X3RlKV4yKSAjIG91dC1vZi1zYW1wbGUgTVNFDQogIA0KICAjIENhbGN1bGF0ZSB0aGUgb3JhY2xlIE1TRXMgdGhhdCBhcmUgb25seSBvYnNlcnZhYmxlcyBiL2Mgd2Uga25vdyB0aGUgdHJ1ZSBDRUYNCiAgcmVzdWx0c19vbHNbaSwzXSA9IHZhcih5X3RyIC0gY2ZlX3RyKSArIG1lYW4oKGNmZV90ciAtIHRlbXBfeWhhdF90cileMikNCiAgcmVzdWx0c19vbHNbaSw0XSA9IHZhcih5X3RlIC0gY2ZlX3RlKSArIG1lYW4oKGNmZV90ZSAtIHRlbXBfeWhhdF90ZSleMikNCn0NCmBgYA0KDQpMZXQncyBmaXJzdCBjaGVjayBob3cgdGhlIGluLXNhbXBsZSBNU0UgZGV2ZWxvcHM6DQoNCmBgYHtyfQ0KIyBwcmVwYXJlIGZvciBwbG90dGluZw0KZGYgPSBkYXRhLmZyYW1lKCJOdW1iZXIgb2YgdmFyaWFibGVzIiA9IDE6cCxyZXN1bHRzX29scykNCg0KIyBQbG90IHRyYWluaW5nIE1TRQ0KZ2dwbG90KGRmLCBhZXMoTnVtYmVyLm9mLnZhcmlhYmxlcykpICsgDQogIGdlb21fbGluZShhZXMoeSA9IE9icy5NU0UudHJhaW4sIGNvbG91ciA9ICJPYnMuTVNFLnRyYWluIiksc2l6ZT0xKSArIA0KICB5bGFiKCJNU0UiKSArIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDApDQpgYGANCg0KVGhlIGluLXNhbXBsZSBNU0UgZHJvcHMgc3Vic3RhbnRpYWxseSBpbiB0aGUgYmVnaW5uaW5nIHdoZW4gdGhlIHJlbGV2YW50IHZhcmlhYmxlcyBhcmUgYWRkZWQuIEhvd2V2ZXIsIGl0IGZ1cnRoZXIgZGVjcmVhc2VzIGdyYWR1YWxseSBhcyB3ZSBhZGQgbW9yZSBub2lzZSB2YXJpYWJsZXMuIEZpbmFsbHksIHRoZSBNU0UgaXMgZXhhY3RseSB6ZXJvIGlmIGFzIG1hbnkgcGFyYW1ldGVycyBhcyBvYnNlcnZhdGlvbnMgYXJlIGVzdGltYXRlZC4gDQoNCmBgYHtyfQ0KcGFzdGUoIkluLXNhbXBsZSBNU0Ugd2l0aCBjb25zdGFudCBhbmQgIixwLCJwcmVkaWN0b3JzOiIsIHJvdW5kKHJlc3VsdHNfb2xzW3AsMV0pKQ0KYGBgDQoNCipSZW1hcms6KiBUaGlzIGltcGxpZXMgYWxzbyBhbiAkUl4yJCBvZiAxIGJlY2F1c2UgJFJeMiA9IDEtTVNFL1NTRSA9IDEtMC9TU0U9MSQuIFRoaXMgYWxzbyByZW1pbmRzIHVzIHRoYXQgb3B0aW1pemluZyBpbi1zYW1wbGUgJFJeMiQgd2hlbiBzcGVjaWZ5aW5nIE9MUyBtb2RlbHMgaXMgYSBiYWQgZ3VpZGUuDQoNCjxicj4NCjxicj4NCg0KDQojIyMgVGhlIHZhbHVlIG9mIG91dC1vZi1zYW1wbGUgdmFsaWRhdGlvbg0KDQpOb3cgbGV0J3MgcGxvdCB0aGUgdHJhaW5pbmcgc2FtcGxlIE1TRSBmcm9tIGJlZm9yZSBhbmQgdGhlIHRlc3Qgc2FtcGxlIE1TRToNCg0KYGBge3J9DQojIENvbXBhcmlzb24gb2Ygb2JzZXJ2YWJsZSBNU0VzDQpnZ3Bsb3QoZGYsIGFlcyhOdW1iZXIub2YudmFyaWFibGVzKSkgKyANCiAgZ2VvbV9saW5lKGFlcyh5ID0gT2JzLk1TRS50cmFpbiwgY29sb3VyID0gIk9icy5NU0UudHJhaW4iKSxzaXplPTEpICsgDQogIGdlb21fbGluZShhZXMoeSA9IE9icy5NU0UudGVzdCwgY29sb3VyID0gIk9icy5NU0UudGVzdCIpLHNpemU9MSkgKyB5bGFiKCJNU0UiKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDApDQpgYGANCg0KV2hpbGUgdGhlIHRyYWluaW5nIE1TRSBkcm9wcyB0byB6ZXJvLCB0aGUgdGVzdCBzYW1wbGUgTVNFIGV4cGxvZGVzIHdoZW4gdGhlIHJhdGlvIG9mIG51bWJlciBvZiBjb3ZhcmlhdGVzIGFuZCBvYnNlcnZhdGlvbnMgYXBwcm9hY2hlcyBvbmUuIA0KDQpMZXQncyBjdXQgb2ZmIHRoZSBleHRyZW1lIHZhbHVlcyBmcm9tIHRoZSByaWdodDoNCg0KYGBge3J9DQpnZ3Bsb3QoZGZbMTo4MCxdLCBhZXMoTnVtYmVyLm9mLnZhcmlhYmxlcykpICsgDQogIGdlb21fbGluZShhZXMoeSA9IE9icy5NU0UudHJhaW4sIGNvbG91ciA9ICJPYnMuTVNFLnRyYWluIiksc2l6ZT0xKSArIA0KICBnZW9tX2xpbmUoYWVzKHkgPSBPYnMuTVNFLnRlc3QsIGNvbG91ciA9ICJPYnMuTVNFLnRlc3QiKSxzaXplPTEpICsgeWxhYigiTVNFIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwKQ0KYGBgDQoNCldlIG9ic2VydmUgY29tcGxldGVseSBkaWZmZXJlbnQgdHJhamVjdG9yaWVzIGJldHdlZW4gdHJhaW5pbmcgYW5kIHRlc3QgTVNFIGFmdGVyIHRoZSByZWxldmFudCB2YXJpYWJsZXMgYXJlIGFkZGVkLg0KDQpGaW5hbGx5LCBsZXQncyBjaGVjayBob3cgd2VsbCB0aGUgZmVhc2libGUgTVNFIGNvbXBhcmVzIHRvIHRoZSBpbmZlYXNpYmxlIG9yYWNsZSBNU0U6DQoNCmBgYHtyfQ0KZ2dwbG90KGRmWzE6ODAsXSwgYWVzKE51bWJlci5vZi52YXJpYWJsZXMpKSArIA0KICBnZW9tX2xpbmUoYWVzKHkgPSBPYnMuTVNFLnRlc3QsIGNvbG91ciA9ICJPYnMuTVNFLnRlc3QiKSxzaXplPTEpICsgDQogIGdlb21fbGluZShhZXMoeSA9IE9yYWNsZS5NU0UudGVzdCwgY29sb3VyID0gIk9yYWNsZS5NU0UudGVzdCIpLHNpemU9MSkgKyB5bGFiKCJNU0UiKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDApDQpgYGANCg0KVGhlIG9ic2VydmFibGUgYW5kIHRoZSBvcmFjbGUgTVNFIGFyZSBuZWFybHkgaWRlbnRpY2FsLiBUaGlzIGlsbHVzdHJhdGVzIHdoeSB1c2luZyB0aGUgb2JzZXJ2YWJsZSBvdXQtb2Ytc2FtcGxlIE1TRSBhcyBwcm94eSBmb3IgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBpcyBzbyBwb3dlcmZ1bC4NCg0KPGJyPg0KPGJyPg0KDQoNCiMjIyBUYWtlLWF3YXlzDQogDQogLSBJdCBpcyBlYXN5IHRvIG9wdGltaXplIGluLXNhbXBsZSBmaXQgZm9yIE9MUyBieSBhZGRpbmcgbm9pc2UgdmFyaWFibGVzLCBidXQgdGhpcyBkb2VzIG5vdCByZXNlbWJsZSB0aGUgdHJ1ZSBwcmVkaWN0aW9uIHBlcmZvcm1hbmNlLg0KIA0KIC0gVGVzdCBzYW1wbGVzIGFyZSBjcnVjaWFsIHRvIHVuZGVyc3RhbmQgdGhlIHRydWUgcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBvZiBtb2RlbHMuDQogDQogLSBUaGUgb2JzZXJ2YWJsZSBNU0UgaXMgbmVhcmx5IGlkZW50aWNhbCB0byB0aGUgKGluIGFwcGxpY2F0aW9ucyBub3Qgb2JzZXJ2YWJsZSkgb3JhY2xlIE1TRS4NCiANCjxicj4NCjxicj4NCiANCiANCiMjIyBTdWdnZXN0aW9ucyB0byBwbGF5IHdpdGggdGhlIHRveSBtb2RlbA0KDQpGZWVsIGZyZWUgdG8gcGxheSBhcm91bmQgd2l0aCB0aGUgY29kZS4gVGhpcyBpcyB1c2VmdWwgdG8gc2hhcnBlbiBhbmQgY2hhbGxlbmdlIHlvdXIgdW5kZXJzdGFuZGluZyBvZiB0aGUgbWV0aG9kcy4gVGhpbmsgYWJvdXQgdGhlIGNvbnNlcXVlbmNlcyBvZiBhIG1vZGlmaWNhdGlvbiBiZWZvcmUgeW91IHJ1biBpdCBhbmQgY2hlY2sgd2hldGhlciB0aGUgcmVzdWx0cyBhcmUgaW4gbGluZSB3aXRoIHlvdXIgZXhwZWN0YXRpb24uIFNvbWUgc3VnZ2VzdGlvbnM6DQogDQotIE1vZGlmeSBER1AgKGNoYW5nZSBiZXRhcywgY2hhbmdlIGxldmVsIG9mIG5vaXNlLCBpbnRyb2R1Y2UgY29ycmVsYXRpb24gYmV0d2VlbiBjb3ZhcmlhdGVzLCAuLi4pDQoNCi0gTW9kaWZ5IHNlZWQNCg0KLSBDaGFuZ2UgdHJhaW5pbmcgYW5kIHRlc3Qgc2FtcGxlIHNpemUNCiANCg0KIA==