Goals:

  • Influence functions play a crucial role for Double ML

  • This notebook illustrates what an influence function is and does within the familiar OLS setting

  • In particular it showcases how influence functions obeying the chain rule can be useful

Acknowledgements: I thank Henri Pfleiderer for his assistance in preparing this notebook.


OLS influence functions

Manually implement OLS

Consider a data generating process (DGP) with:

\(Y = \underbrace{\beta_0 + \beta_1 X}_{CEF} + U\), where \(\beta_0 = 1\), \(\beta_1 = 1/2\), \(X \sim \mathcal{N}(0,1)\) and \(U \sim uniform(-1,1)\).

For illustration, we plot a random draw with \(N=30\) and the true CEF:

if (!require("tidyverse")) install.packages("tidyverse", dependencies = TRUE); library(tidyverse)
if (!require("estimatr")) install.packages("estimatr", dependencies = TRUE); library(estimatr)
if (!require("MASS")) install.packages("MASS", dependencies = TRUE); library(MASS)

n = 30
set.seed(1234)

# Draw independent variable
x = rnorm(n)
hist(x)


# Define population parameters
b0 = 1
b1 = 1/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,linewidth=1) + 
            geom_point(aes(x=x,y=y),color="blue",alpha = 0.4)

Now, manually implement OLS. Recall that OLS has the closed form solution: \(\hat{\beta} = (X'X)^{-1}X'Y\).

# Add constant to covariates
X = cbind(rep(1,n),x)
colnames(X) = c("Constant","X")
Q = solve(crossprod(X))
betas = Q %*% t(X) %*% y
betas
              [,1]
Constant 0.8364823
X        0.5292423

Let’s verify this result with a pre-implemented OLS command

# Check that it is identical to lm
summary(lm_robust(y ~ x))

Call:
lm_robust(formula = y ~ x)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value  Pr(>|t|) CI Lower CI Upper DF
(Intercept)   0.8365     0.1092   7.658 2.423e-08   0.6127   1.0602 28
x             0.5292     0.1363   3.883 5.748e-04   0.2500   0.8084 28

Multiple R-squared:  0.3834 ,   Adjusted R-squared:  0.3614 
F-statistic: 15.08 on 1 and 28 DF,  p-value: 0.0005748


Recap: General Recipe for Influence Functions

In the lecture we focus on linear score functions, which can be written as:

\[ \psi(O;\tilde{\theta}, \tilde{\eta}) = \tilde{\theta} \psi_a(O;\tilde{\eta}) + \psi _b(O;\tilde{\eta})\]

with the true parameter values \(\theta\) and \(\eta\) satisfying the moment condition:

\[E[\psi(O;\theta, \eta)] = \theta E[ \psi_a (O;\eta)] + E[\psi_b (O;\eta)] = 0 \] The solution for \(\theta\) can then be found by rearranging as:

\[ \theta = -\frac{E[\psi_b (O;\eta)]}{E[ \psi_a (O;\eta)]}\] The influence function for such estimators is defined as \[ \Psi(O;\theta,\eta) = -E \left[\frac{\partial \psi}{\partial \theta}\right]^{-1} \psi(O;\theta, \eta) = -E[\psi_a(O;\eta)] ^{-1} \psi(O;\theta, \eta) \] being a scaled version of the score, evaluated at the true parameter values. As discussed in the lecture, one can show the following result:

\[ \frac{1}{\sqrt N} \left(\hat{\theta}- \theta \right) \xrightarrow[]{d} N(0, Var[\Psi(O;\theta,\eta)]) \] This means that influence functions can be used to calculate standard errors for the estimators: \(se(\hat{\theta}) = \sqrt{\frac{Var[\Psi(O;\theta,\eta)]}{N}}\)


Influence Functions for OLS

Calculating OLS Influence Functions

Influence functions are very useful. To get to know them better, let’s focus on the canonical OLS case. This should make you more comfortable to use them also in the Double ML settings where they play a crucial role. Recall that OLS has the hopefully familiar looking moment condition:

\[ E[\underbrace{X'U}_{\psi}] = E[\underbrace{X'(Y-X\beta)}_{\psi}] = E[X' Y - X'X \beta] = E[X'Y] - E[X'X] \beta = E[\underbrace{-X'X}_{\psi_a}] \beta + E[\underbrace{X'Y}_{\psi_b}] = 0 \] \(\beta\) is then:

\[ \beta = E[X'X]^{-1} E[X'Y] \] This formulation can be used to obtain the influence function for individual \(i\) as follows:

\[ \Psi_i(X,Y;\beta) = E[\underbrace{X'X}_{=-\psi_a}]^{-1} \underbrace{X_i(Y_i-X_i'\beta)}_{=\psi}\] Note, that this is a vector with an influence function for each parameter. To see this consider \(k\) explanatory variables (including a constant):

\[ \underset{k \times 1}{\Psi_i(X,Y;\beta)} = \underset{k \times k}{E[X'X]^{-1}} \underset{k \times 1}{X_i}\underset{1 \times 1}{(Y_i-X_i'\beta)}\] This expression can be calculated using the results from the simulation and (manual) estimation. Let’s do this for the first individual. The expectation \(E[X'_iX_i]^{-1}\) can be estimated by: \(\left(\frac{1}{N} \sum_i X_i 'X_i\right)^{-1} = \left(\frac{1}{N} X'X\right)^{-1}\). Additionally, we need to replace the true \(\beta\) by the estimate \(\hat{\beta}\):

# Calculate IFs for individual 1
solve(crossprod(X) / n) %*% X[1,] %*% (y[1] - X[1,] %*% betas)
               [,1]
Constant  0.6105027
X        -1.0727165

One compact way to calculate the IFs for all individuals at once is as follows:

IF = (X * as.numeric(y - X %*% betas)) %*% solve(crossprod(X) / n)
dim(IF)
[1] 30  2

We have created an \(N \times k\) matrix with all influence functions and can check the theoretical properties numerically.

Theoretically, the IFs should satisfy: \(E[\Psi(X,Y;\beta)] = 0\). As a sanity check for our computation of the IFs, let’s see whether the column means are actually zero:

all.equal(as.numeric(colMeans(IF)),rep(0,ncol(X))) # check whether the means of the influence functions are equal to 0
[1] TRUE


Influence Functions for standard errors

The influence functions can be used to estimate the standard errors. The general expression \(se\left(\hat{\theta}\right) = \sqrt{\frac{Var[\Psi(O;\theta,\eta)]}{N}}\) from the lecture slides changes slightly. As we are looking at a case where \(Var[\Psi(X,Y;\beta,)]\) is a \(k \times k\) covariance matrix. The standard errors for the elements of \(\beta\) are the square root of the diagonal elements of \(\frac{1}{N} Var\left[\Psi\left(X,Y;\hat{\beta}\right)\right]\) (Note: we have again replaced \(\beta\) by the estimates \(\hat{\beta}\), as - in practice - we don’t know the true parameter values):

sqrt(diag(var(IF)) / n)
 Constant         X 
0.1061760 0.1266398 

We can compare these standard errors to the standard errors, we obtain by a pre-implemented command using the different available options. Although they are not exactly identical, they should are reasonably close:

lm_robust(y ~ x,se_type = "HC0")$std.error
(Intercept)           x 
  0.1043914   0.1245113 
lm_robust(y ~ x,se_type = "HC1")$std.error
(Intercept)           x 
  0.1080553   0.1288814 
lm_robust(y ~ x,se_type = "HC2")$std.error
(Intercept)           x 
  0.1092306   0.1363034 
lm_robust(y ~ x,se_type = "HC3")$std.error
(Intercept)           x 
  0.1150604   0.1505970 


Checking coverage rates

Let’s run a quick simulation to check the coverage rates of the different types of standard errors. The coverage rate calculate how often the true value is included in the confidence intervals (see ring toss analogy as an intuitive refresher).

The coverage rate can be used to evaluate the quality of the different standard errors. We would like to have nominal coverage, i.e. for a 95% confidence level it should happen in 95% of the replications, for a 90% confidence level in 90% of the replications, …

We do this for the estimate of the coefficient on \(x\) in the regression above.

rep = 1000 # number of replications for the simulation
se_matrix = matrix(NA, nrow = rep, ncol = 5) # empty matrix to store the standard errors
cover_matrix = matrix(NA, nrow = rep, ncol = 5) # matrix to store whether true value was covered
colnames(se_matrix) = c("HC0", "HC1", "HC2", "HC3", "IF") # give meaningful names to the columns of the matrices
colnames(cover_matrix) = c("HC0", "HC1", "HC2", "HC3", "IF")

n = 100 # number of observations

# True parameter values
b0 = 1
b1 = 1/2

critical_value = qt(0.975, df = n-2)

for (i in 1: rep){

  # Draw independent variable
  x = rnorm(n)
  
  # Generate outcome
  y = b0 + b1 * x + runif(n,-1,1)
  
  # use the preimplemented commands, save the standard errors:
  se_matrix[i,1] = lm_robust(y ~ x,se_type = "HC0")$std.error[2]
  se_matrix[i,2] = lm_robust(y ~ x,se_type = "HC1")$std.error[2]
  se_matrix[i,3] = lm_robust(y ~ x,se_type = "HC2")$std.error[2]
  se_matrix[i,4] = lm_robust(y ~ x,se_type = "HC3")$std.error[2]
  
  # influence function, apply the formulas from above:
  X = cbind(rep(1,n),x)
  Q = solve(crossprod(X))
  betas = Q %*% t(X) %*% y
  IF = (X * as.numeric(y - X %*% betas)) %*% solve(crossprod(X) / n)
  se_matrix[i,5] = sqrt(diag(var(IF)) / n)[2]
  
  # check the coverage; i.e. does the true value lie within the bounds of the 95% confidence interval?:
  cover_matrix[i,1] = 1*((betas[2] - critical_value* se_matrix[i,1] < b1) & (betas[2] + critical_value*  se_matrix[i,1] > b1))
  cover_matrix[i,2] = 1*((betas[2] - critical_value* se_matrix[i,2] < b1) & (betas[2] + critical_value*  se_matrix[i,2] > b1))
  cover_matrix[i,3] = 1*((betas[2] - critical_value* se_matrix[i,3] < b1) & (betas[2] + critical_value*  se_matrix[i,3] > b1))
  cover_matrix[i,4] = 1*((betas[2] - critical_value* se_matrix[i,4] < b1) & (betas[2] + critical_value*  se_matrix[i,4] > b1))
  cover_matrix[i,5] = 1*((betas[2] - critical_value* se_matrix[i,5] < b1) & (betas[2] + critical_value*  se_matrix[i,5] > b1))
}

Look at the coverage rates. As we used 95% confidence intervals, they should all be close to 95%

colMeans(cover_matrix)
  HC0   HC1   HC2   HC3    IF 
0.934 0.935 0.935 0.941 0.934 

and they are (increasing \(N\) gets them even closer).

The different standard errors should also be highly correlated in the different replications

cor(se_matrix)
          HC0       HC1       HC2       HC3        IF
HC0 1.0000000 1.0000000 0.9995008 0.9979216 1.0000000
HC1 1.0000000 1.0000000 0.9995008 0.9979216 1.0000000
HC2 0.9995008 0.9995008 1.0000000 0.9994590 0.9995008
HC3 0.9979216 0.9979216 0.9994590 1.0000000 0.9979216
IF  1.0000000 1.0000000 0.9995008 0.9979216 1.0000000

and they are.

(Bonus remark: Note that using the influence functions, one can exactly recover the classic (Stata) Huber-White SEs using a correction term.)

IF = (X * as.numeric(y - X %*% betas)) %*% solve(crossprod(X) / n) * sqrt((n-1)/(n-2))
sqrt(diag(var(IF)) / n)
                    x 
0.05461077 0.06310453 
lm_robust(y ~ x,se_type = "HC1")$std.error
(Intercept)           x 
 0.05461077  0.06310453 


Intuition behind Influence Functions

Intuitively, influence functions allow us to evaluate the influence of a single observation on an estimate (e.g., the coefficients in an OLS regression). To be more precise, the influence of observation \(i\) on the estimation of the target parameter (here: \(\beta\)) is approximated by \(\frac{\Psi_i(O; \theta, \eta)}{N}\) or here: \(\frac{\Psi_i(X,Y; \hat{\beta})}{N}\).

To illustrate this, the following code snippet calculates two different measures and compares them:

  1. For each observation \(i\), we compute the OLS estimates with the sample excluding observation \(i\). We then take the difference between the full sample estimates and this new estimate without individual \(i\). This captures the influence of observation \(i\) on the estimates.
  2. The measure \(\frac{\Psi_i(X,Y; \hat{\beta)}}{N}\)

If the influence functions work as they are supposed to, these individual-specific differences should be highly correlated.

# Draw independent variable
x = rnorm(n)

# Generate outcome
y = b0 + b1 * x + runif(n,-1,1)

# influence function, apply the formulas from above:
X = cbind(rep(1,n),x)
Q = solve(crossprod(X))
betas = Q %*% t(X) %*% y
IF = solve(crossprod(X) / n) %*% t(X * as.numeric(y - X %*% betas))

loools = matrix(NA,n,2)

#Calculate coefficients w/o individual i
for (i in 1:n) {
  loools[i,] = solve(crossprod(X[-i,])) %*% t(X[-i,]) %*% y[-i]
}

Plot the 2 measures against each other:

# plot diff between full sample and leave-on-out against the IF
plot(betas[1] - loools[,1], IF[1,] / n)


plot(betas[2] - loools[,2], IF[2,] / n)

The difference between the coefficients with and without each observation are very similar, though not identical) to the influence function (divided by \(N\)). This illustrates that the influence function approximates the influence of each individual observation on the estimates.


Chain rule

We learned in the lecture that influence functions obey a chain rule that allows to derive new influence function of composite parameters once we know the influence function of its components.

Use case 1: Testing for omitted variable bias

Consider the following stylized DGP. There is an outcome \(Y\), a continuous treatment \(W\), and a confounding variable \(X\): \[ Y= 0.5 W + X + U\]

with \(U_1 \sim \mathcal{N}(0,1)\) and: \[ \begin{align} [W, X]'&\sim \mathcal{N}\left([0~~0]',\left[ \begin{array} {rrr} 1 & \rho \\ \rho & 1 \end{array}\right]\right) \end{align} \]

i.e. the constant treatment effect is 0.5.


No confounding

First, simulate the data set as described above with \(\rho = 0\), i.e. no confounding:

n = 1000
mu = c(0,0)
rho = 0
sigma = matrix(c(1,rho,rho,1), nrow = 2)
draw = mvrnorm(n, mu, sigma)
W = draw[,1]
X =  draw[,2]
Y = 0.5 * W + X + rnorm(n)

Let’s run the regression of \(Y\) on \(W\) and calculate the influence functions. This is done exactly the same way as above.

# run a regression using the pre-implemented command:
summary(lm_robust(Y~ W))

Call:
lm_robust(formula = Y ~ W)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value  Pr(>|t|) CI Lower CI Upper  DF
(Intercept)   0.1102    0.04494   2.452 1.438e-02   0.0220   0.1984 998
W             0.4575    0.04675   9.785 1.189e-21   0.3657   0.5492 998

Multiple R-squared:  0.09553 ,  Adjusted R-squared:  0.09462 
F-statistic: 95.74 on 1 and 998 DF,  p-value: < 2.2e-16
# Calculate the influence functions. In contrast to the code above, here we directly construct the matrix X using model.matrix(lm_robust(Y~ W)) 
# and the OLS coefficients as a vector with matrix(lm_robust(Y~ W)$coefficients) in the same line of code
IF_OVB = (model.matrix(lm_robust(Y~ W)) * as.numeric(Y - model.matrix(lm_robust(Y~ W)) %*% matrix(lm_robust(Y~ W)$coefficients))) %*% solve(crossprod(model.matrix(lm_robust(Y~ W))) / n)
dim(IF_OVB)
[1] 1000    2

We can also run a regression with \(X\) included as a control variable and calculate the Influence Functions:

summary(lm_robust(Y~ W+X))

Call:
lm_robust(formula = Y ~ W + X)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value   Pr(>|t|) CI Lower CI Upper  DF
(Intercept)  0.02966    0.03142  0.9439  3.455e-01  -0.0320  0.09131 997
W            0.46146    0.03035 15.2056  4.075e-47   0.4019  0.52102 997
X            0.99754    0.02989 33.3694 1.524e-164   0.9389  1.05621 997

Multiple R-squared:   0.56 ,    Adjusted R-squared:  0.5591 
F-statistic: 651.9 on 2 and 997 DF,  p-value: < 2.2e-16
IF_X = (model.matrix(lm_robust(Y~ W+X)) * as.numeric(Y - model.matrix(lm_robust(Y~ W+X)) %*% matrix(lm_robust(Y~ W+X)$coefficients))) %*% solve(crossprod(model.matrix(lm_robust(Y~ W+X))) / n)

The two estimated coefficients of the treatment (call them \(\hat{\tau}_{OVB}\) and \(\hat{\tau}_X\)) look quite similar in the two regressions, but we would like to be able to calculate standard errors for their difference \(\Delta = \tau_{OVB} - \tau_{X}\) and test for statistical significance. As influence functions obey the chain rule, the influence function for \(\Delta\) can be obtained as:

\[ \Psi_{\Delta} = \frac{\partial \Delta}{\partial \tau_{OVB}} \Psi_{\tau_{OVB}} + \frac{\partial \Delta}{\partial \tau_{X}} \Psi_{\tau_{X}} = \Psi_{\tau_{OVB}} - \Psi_{\tau_{X}}\]:

This is very easily done, since we already calculated \(\Psi_{\tau_{OVB}}\) and \(\Psi_{\tau_{X}}\) when running the two separate regressions:

# display the difference:
Delta = lm_robust(Y ~ W)$coefficients[2] - lm_robust(Y ~ W + X)$coefficients[2]
print(paste0("Delta: ", Delta))
[1] "Delta: -0.003987390770746"
IF_Delta = IF_OVB[,2] - IF_X[,2] # Influence Function for Delta

se_Delta = sqrt(var(IF_Delta) / length(IF_Delta))
t_stat = Delta/se_Delta # t-statistic
print(paste0("t-statistic: ", t_stat))
[1] "t-statistic: -0.11409469745405"
p_val = 2*(1-pnorm(abs(Delta/se_Delta))) # p value
print(paste0("p-value: ", p_val))
[1] "p-value: 0.909162725595152"

The p-value shows that the difference is not significantly different from zero on any conventional level of significance. There is no significant omitted variable bias. As \(W\) and \(X\) are independent, this is what we expected.


Confounding

Now, let’s do the same again, but using \(\rho = 0.7\):

rho = 0.7
sigma = matrix(c(1,rho,rho,1), nrow = 2)
draw = mvrnorm(n, mu, sigma)
W = draw[,1]
X =  draw[,2]
Y = 0.5 * W + X + rnorm(n)

This introduces omitted variable bias as we can observe by running OLS once without controlling for \(X\)

summary(lm_robust(Y ~ W))

Call:
lm_robust(formula = Y ~ W)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value   Pr(>|t|) CI Lower CI Upper  DF
(Intercept) -0.05799    0.03858  -1.503  1.332e-01  -0.1337  0.01772 998
W            1.16744    0.03796  30.751 1.284e-146   1.0929  1.24194 998

Multiple R-squared:  0.4828 ,   Adjusted R-squared:  0.4823 
F-statistic: 945.6 on 1 and 998 DF,  p-value: < 2.2e-16
IF_OVB = (model.matrix(lm_robust(Y~ W)) * as.numeric(Y - model.matrix(lm_robust(Y~ W)) %*% matrix(lm_robust(Y~ W)$coefficients))) %*% solve(crossprod(model.matrix(lm_robust(Y~ W))) / n)

and once with controlling for \(X\):

summary(lm_robust(Y ~ W + X))

Call:
lm_robust(formula = Y ~ W + X)

Standard error type:  HC2 

Coefficients:
            Estimate Std. Error t value   Pr(>|t|) CI Lower CI Upper  DF
(Intercept) -0.05404    0.03106   -1.74  8.221e-02  -0.1150 0.006915 997
W            0.45356    0.04376   10.37  5.574e-24   0.3677 0.539426 997
X            1.02014    0.04238   24.07 2.679e-101   0.9370 1.103307 997

Multiple R-squared:  0.6663 ,   Adjusted R-squared:  0.6656 
F-statistic:  1006 on 2 and 997 DF,  p-value: < 2.2e-16
IF_X = (model.matrix(lm_robust(Y~ W+X)) * as.numeric(Y - model.matrix(lm_robust(Y~ W+X)) %*% matrix(lm_robust(Y~ W+X)$coefficients))) %*% solve(crossprod(model.matrix(lm_robust(Y~ W+X))) / n)

To check whether the difference of the estimates from the two regressions are statistically significantly different from zero, we will again use the composite influence function \(\Psi_{\Delta}\):

# display the difference:
Delta = lm_robust(Y ~ W)$coefficients[2] - lm_robust(Y ~ W + X)$coefficients[2]
print(paste0("Delta: ", Delta))
[1] "Delta: 0.713878565262364"
IF_Delta = IF_OVB[,2] - IF_X[,2] # Influence Function for Delta

se_Delta = sqrt(var(IF_Delta) / length(IF_Delta))
t_stat = Delta/se_Delta # t-statistic
print(paste0("t-statistic: ", t_stat))
[1] "t-statistic: 19.3672020483771"
p_val = 2*(1-pnorm(abs(Delta/se_Delta))) # p value
print(paste0("p-value: ", p_val))
[1] "p-value: 0"

The difference is significantly different from zero. In this case, as expected, there is a significant omitted variable bias.


Use case 2: Standard errors for fitted values using influence functions

Assume that after running OLS we want to obtain standard errors for the fitted values for individual \(i\) \[\hat{Y}_i = X_i \hat{\beta} = X_{i,1} \hat{\beta_1} + ... + X_{i,k} \hat{\beta_k}\]

Again, the chain rule comes in handy. We can write the influence function for the prediction \(\hat{Y_i}\), i.e. \(\Psi_{\hat{Y_i}}\) as:

\[ \begin{equation} \Psi_{\hat{Y_i}} = \frac{\partial \hat{Y_i}}{\partial \hat{\beta_1}} \Psi_{\hat{\beta_1}} + ... + \frac{\partial \hat{Y_i}}{\partial \hat{\beta_k}} \Psi_{\hat{\beta_k}} = X_{i,1} \Psi_{\hat{\beta_1}} + ... + X_{i,k} \Psi_{\hat{\beta_k}} = X_i' \Psi^{'}_{\hat{\beta}} \end{equation} \]

The matrix \(\underset{N \times k}{\Psi_{\hat{\beta}}}\) is what we already calculated above. It contains the values of the influence function for all individuals and all elements of \(\beta\). To demonstrate how we can calculate standard errors for \(\hat{Y_i}\), we run the very simple regression from the beginning and in a first step calculate the influence functions for the estimated coefficients and all individuals. The procedure is exactly the same as above:

x_seq = seq(-3,3,0.01)
n = length(x_seq)

# Draw independent variable
x = rnorm(n)

# True parameter
b0 = -1
b1 = 1/2

# Generate outcome
y = b0 + b1 * x + runif(n,-1,1)

# Add constant
X = cbind(rep(1,n),x)

# Manually obtain the OLS coefficients:
Q = solve(crossprod(X))
betas = Q %*% t(X) %*% y

# And get the influence functions for all individuals
IF = (X * as.numeric(y - X %*% betas)) %*% solve(crossprod(X) / n)

We now want to obtain the influence functions not for the coefficients, but for fitted values with different values of \(X_i\). In particular, we calculate standard errors for several fitted values, and use a sequence from -3 to 3 as values for \(X_{i,2}\). Let’s denote this sequence, which consists of \(m\) values by \(\tilde{x}\).

The vector of fitted values will thus be: \[\underset{m \times 1}{\hat{Y}} = \underset{m \times 1}{\hat{\beta_1}} + \hat{\beta_2} \cdot \underset{m \times 1}{\tilde{x}} = \underset{m \times k}{\tilde{X}} \cdot \underset{k \times 1}{\hat{\beta}} \]

\(\tilde{X}\) is a matrix with the first column consisting of ones and the sequence \(\tilde{x}\) in the second column. For each of the \(m\) fitted values we can now calculate the values of the influence functions (again, remember: one for each individual) as we have seen above. We can do this for all the \(m\) values of the sequence \(\tilde{x}\) and write it very concisely as:

\[ \underset{m \times N}{\Psi_{\hat{y}}} = \underset{m \times k} {\tilde{X}} \underset{k\times N}{\Psi^{'}_{\hat{\beta}}}\].

The following code snippet calculates the influence function for these fitted values:

X_seq  = cbind(rep(1, length(x_seq)), x_seq) # construct the matrix, which in the text is called big X tilde

# Get the m x 1 vector of predictions y_hat using the sequence from -3 to 3 as values for x:
y_hat = X_seq %*% betas

# Influence function for the predictions, using the influence functions for the coefficients:
IF_yhat = X_seq %*% t(IF)

Let’s get the standard errors and calculate the 95%-confidence intervals at the different \(\tilde{x}\) and illustrate this graphically:

# Standard errors:
ses = sqrt(diag(var(t(IF_yhat))) / n) # just like we did it above

# manually compute the bounds of the confidence interval:
CI_lower = y_hat - critical_value*ses
CI_upper = y_hat + critical_value*ses

# plot
tibble(x_seq, y_hat, CI_lower, CI_upper, x , y) %>% ggplot(aes(x = x_seq, y = y_hat))+
  geom_line(color = "red", linetype = "solid")+
  geom_line(aes(x = x_seq, y = CI_lower), color = "red", linetype = "dashed")+
  geom_line(aes(x = x_seq, y = CI_upper), color = "red", linetype = "dashed")+
  geom_point(aes(x = x, y  =y))+
  xlab("x")+
  ylab("")

In a last step, let’s check whether we did this correctly using the coverage rate, for the grid of \(x\)-values:

R = 1000 # number of replications
n = 100 # number of observations

# True parameters (unchanged)
b0 = -1
b1 = 1/2

critical_value = qt(0.975, df = n-2)

# "True" (expected) y:
y_exp = b0 +b1*x_seq # this is the "true" quantity we are estimating

coverage_matrix = matrix(NA, ncol = length(x_seq), nrow =  R)

for (i in 1:R){
  x = rnorm(n)
  
  # Generate outcome
  y = b0 + b1 * x + runif(n,-1,1)
  
  # Add constant
  X = cbind(rep(1,n),x)
  
  # Manually obtain the OLS coefficients:
  Q = solve(crossprod(X))
  betas = Q %*% t(X) %*% y
  
  # And get the influence functions for all individuals
  IF = (X * as.numeric(y - X %*% betas)) %*% solve(crossprod(X) / n)
  
  y_hat = X_seq %*% betas # the predictions
  
  # Influence function for the predictions:
  IF_yhat = X_seq %*% t(IF)
  
  # standard errors:
  ses = sqrt(diag(var(t(IF_yhat))) / n)
  
  coverage_matrix[i,] = 1*(((y_hat - critical_value* ses) <= y_exp) & ((y_hat + critical_value* ses) > y_exp)) # for all observations: check whether the confidence interval
  # includes the true value, save as 0 or 1
}

coverage_rates = colMeans(coverage_matrix) # get the coverage rates at all different values of x

# plot the coverage rates over the grid of x
tibble(x_seq, coverage_rates) %>% ggplot(aes(x = x_seq, y = coverage_rates))+
  geom_hline(yintercept=(0.95), linetype="dashed")+ 
  geom_line(colour = "red", linewidth = 1) +
  ylim(c(0,1)) +
  geom_hline(yintercept=c(0,1)) +
  labs(
    x="x",
    y="Coverage",
    title="Model coverage",
    caption="Based on simulated data"
  ) +
  theme_bw()

This looks as expected. For each value of \(\tilde{x}\), the 95% confidence interval on the fitted value includes the true value in roughly 95% of the cases.


Conclusion

Influence Functions are very useful. We can use them to get standard errors for the coefficients, the fitted values, or even to perform statistical tests on compositional parameters like differences between coefficients from different regressions. Additionally, they are informative about the influence of each observations on the estimates.

LS0tDQp0aXRsZTogIkJhc2ljczogSW5mbHVlbmNlIGZ1bmN0aW9ucyBleHBsYWluZWQgdXNpbmcgT0xTIg0Kc3VidGl0bGU6ICJTaW11bGF0aW9uIG5vdGVib29rIg0KYXV0aG9yOiAiTWljaGFlbCBLbmF1cyINCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVtLyV5JylgIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCi0tLQ0KDQo8YnI+DQoNCkdvYWxzOg0KDQotIEluZmx1ZW5jZSBmdW5jdGlvbnMgcGxheSBhIGNydWNpYWwgcm9sZSBmb3IgRG91YmxlIE1MDQoNCi0gVGhpcyBub3RlYm9vayBpbGx1c3RyYXRlcyB3aGF0IGFuIGluZmx1ZW5jZSBmdW5jdGlvbiBpcyBhbmQgZG9lcyB3aXRoaW4gdGhlIGZhbWlsaWFyIE9MUyBzZXR0aW5nDQoNCi0gSW4gcGFydGljdWxhciBpdCBzaG93Y2FzZXMgaG93IGluZmx1ZW5jZSBmdW5jdGlvbnMgb2JleWluZyB0aGUgY2hhaW4gcnVsZSBjYW4gYmUgdXNlZnVsDQoNCipBY2tub3dsZWRnZW1lbnRzOiBJIHRoYW5rIEhlbnJpIFBmbGVpZGVyZXIgZm9yIGhpcyBhc3Npc3RhbmNlIGluIHByZXBhcmluZyB0aGlzIG5vdGVib29rLioNCg0KPGJyPg0KDQojIyBPTFMgaW5mbHVlbmNlIGZ1bmN0aW9ucw0KDQojIyMgTWFudWFsbHkgaW1wbGVtZW50IE9MUw0KDQpDb25zaWRlciBhIGRhdGEgZ2VuZXJhdGluZyBwcm9jZXNzIChER1ApIHdpdGg6IA0KDQokWSA9IFx1bmRlcmJyYWNle1xiZXRhXzAgKyBcYmV0YV8xIFh9X3tDRUZ9ICsgVSQsIHdoZXJlICRcYmV0YV8wID0gMSQsICRcYmV0YV8xID0gMS8yJCwgJFggXHNpbSBcbWF0aGNhbHtOfSgwLDEpJCBhbmQgJFUgXHNpbSB1bmlmb3JtKC0xLDEpJC4NCg0KRm9yIGlsbHVzdHJhdGlvbiwgd2UgcGxvdCBhIHJhbmRvbSBkcmF3IHdpdGggJE49MzAkIGFuZCB0aGUgdHJ1ZSBDRUY6DQoNCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9DQppZiAoIXJlcXVpcmUoInRpZHl2ZXJzZSIpKSBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeSh0aWR5dmVyc2UpDQppZiAoIXJlcXVpcmUoImVzdGltYXRyIikpIGluc3RhbGwucGFja2FnZXMoImVzdGltYXRyIiwgZGVwZW5kZW5jaWVzID0gVFJVRSk7IGxpYnJhcnkoZXN0aW1hdHIpDQppZiAoIXJlcXVpcmUoIk1BU1MiKSkgaW5zdGFsbC5wYWNrYWdlcygiTUFTUyIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KE1BU1MpDQoNCm4gPSAzMA0Kc2V0LnNlZWQoMTIzNCkNCg0KIyBEcmF3IGluZGVwZW5kZW50IHZhcmlhYmxlDQp4ID0gcm5vcm0obikNCmhpc3QoeCkNCg0KIyBEZWZpbmUgcG9wdWxhdGlvbiBwYXJhbWV0ZXJzDQpiMCA9IDENCmIxID0gMS8yDQoNCiMgRGVmaW5lIGNvbmRpdGlvbmFsIGV4cGVjdGF0aW9uIGZ1bmN0aW9uDQpjZWYgPSBmdW5jdGlvbih4KXtiMCArIGIxKnh9DQoNCiMgR2VuZXJhdGUgb3V0Y29tZSB2YXJpYWJsZQ0KeSA9IGNlZih4KSArIHJ1bmlmKG4sLTEsMSkNCg0KIyBQbG90IHNhbXBsZQ0KZGYgPSBkYXRhLmZyYW1lKHg9eCx5PXkpDQpnZ3Bsb3QoZGYpICsgc3RhdF9mdW5jdGlvbihmdW49Y2VmLGxpbmV3aWR0aD0xKSArIA0KICAgICAgICAgICAgZ2VvbV9wb2ludChhZXMoeD14LHk9eSksY29sb3I9ImJsdWUiLGFscGhhID0gMC40KQ0KYGBgDQoNCk5vdywgbWFudWFsbHkgaW1wbGVtZW50IE9MUy4gUmVjYWxsIHRoYXQgT0xTIGhhcyB0aGUgY2xvc2VkIGZvcm0gc29sdXRpb246ICRcaGF0e1xiZXRhfSA9IChYJ1gpXnstMX1YJ1kkLg0KYGBge3J9DQojIEFkZCBjb25zdGFudCB0byBjb3ZhcmlhdGVzDQpYID0gY2JpbmQocmVwKDEsbikseCkNCmNvbG5hbWVzKFgpID0gYygiQ29uc3RhbnQiLCJYIikNClEgPSBzb2x2ZShjcm9zc3Byb2QoWCkpDQpiZXRhcyA9IFEgJSolIHQoWCkgJSolIHkNCmJldGFzDQpgYGANCg0KTGV0J3MgdmVyaWZ5IHRoaXMgcmVzdWx0IHdpdGggYSBwcmUtaW1wbGVtZW50ZWQgT0xTIGNvbW1hbmQNCmBgYHtyfQ0KIyBDaGVjayB0aGF0IGl0IGlzIGlkZW50aWNhbCB0byBsbQ0Kc3VtbWFyeShsbV9yb2J1c3QoeSB+IHgpKQ0KYGBgDQoNCjxicj4NCg0KIyMjIFJlY2FwOiBHZW5lcmFsIFJlY2lwZSBmb3IgSW5mbHVlbmNlIEZ1bmN0aW9ucw0KDQpJbiB0aGUgbGVjdHVyZSB3ZSBmb2N1cyBvbiBsaW5lYXIgc2NvcmUgZnVuY3Rpb25zLCB3aGljaCBjYW4gYmUgd3JpdHRlbiBhczogDQoNCiQkIFxwc2koTztcdGlsZGV7XHRoZXRhfSwgXHRpbGRle1xldGF9KSA9IFx0aWxkZXtcdGhldGF9IFxwc2lfYShPO1x0aWxkZXtcZXRhfSkgKyBccHNpIF9iKE87XHRpbGRle1xldGF9KSQkDQoNCndpdGggdGhlIHRydWUgcGFyYW1ldGVyIHZhbHVlcyAkXHRoZXRhJCBhbmQgJFxldGEkIHNhdGlzZnlpbmcgdGhlIG1vbWVudCBjb25kaXRpb246DQoNCiQkRVtccHNpKE87XHRoZXRhLCBcZXRhKV0gPSBcdGhldGEgRVsgXHBzaV9hIChPO1xldGEpXSArIEVbXHBzaV9iIChPO1xldGEpXSA9IDAgJCQNClRoZSBzb2x1dGlvbiBmb3IgJFx0aGV0YSQgY2FuIHRoZW4gYmUgZm91bmQgYnkgcmVhcnJhbmdpbmcgYXM6DQoNCiQkIFx0aGV0YSA9IC1cZnJhY3tFW1xwc2lfYiAoTztcZXRhKV19e0VbIFxwc2lfYSAoTztcZXRhKV19JCQNClRoZSBpbmZsdWVuY2UgZnVuY3Rpb24gZm9yIHN1Y2ggZXN0aW1hdG9ycyBpcyBkZWZpbmVkIGFzDQokJCBcUHNpKE87XHRoZXRhLFxldGEpID0gLUUgXGxlZnRbXGZyYWN7XHBhcnRpYWwgXHBzaX17XHBhcnRpYWwgXHRoZXRhfVxyaWdodF1eey0xfSBccHNpKE87XHRoZXRhLCBcZXRhKSA9IC1FW1xwc2lfYShPO1xldGEpXSBeey0xfSBccHNpKE87XHRoZXRhLCBcZXRhKSAkJA0KYmVpbmcgYSBzY2FsZWQgdmVyc2lvbiBvZiB0aGUgc2NvcmUsIGV2YWx1YXRlZCBhdCB0aGUgdHJ1ZSBwYXJhbWV0ZXIgdmFsdWVzLiBBcyBkaXNjdXNzZWQgaW4gdGhlIGxlY3R1cmUsIG9uZSBjYW4gc2hvdyB0aGUgZm9sbG93aW5nIHJlc3VsdDoNCg0KJCQgXGZyYWN7MX17XHNxcnQgTn0gXGxlZnQoXGhhdHtcdGhldGF9LSBcdGhldGEgXHJpZ2h0KSBceHJpZ2h0YXJyb3dbXXtkfSBOKDAsIFZhcltcUHNpKE87XHRoZXRhLFxldGEpXSkgJCQNClRoaXMgbWVhbnMgdGhhdCBpbmZsdWVuY2UgZnVuY3Rpb25zIGNhbiBiZSB1c2VkIHRvIGNhbGN1bGF0ZSBzdGFuZGFyZCBlcnJvcnMgZm9yIHRoZSBlc3RpbWF0b3JzOiAkc2UoXGhhdHtcdGhldGF9KSA9IFxzcXJ0e1xmcmFje1ZhcltcUHNpKE87XHRoZXRhLFxldGEpXX17Tn19JA0KDQo8YnI+DQoNCiMjIyBJbmZsdWVuY2UgRnVuY3Rpb25zIGZvciBPTFMNCg0KIyMjIyBDYWxjdWxhdGluZyBPTFMgSW5mbHVlbmNlIEZ1bmN0aW9ucw0KDQpJbmZsdWVuY2UgZnVuY3Rpb25zIGFyZSB2ZXJ5IHVzZWZ1bC4gVG8gZ2V0IHRvIGtub3cgdGhlbSBiZXR0ZXIsIGxldCdzIGZvY3VzIG9uIHRoZSBjYW5vbmljYWwgT0xTIGNhc2UuIFRoaXMgc2hvdWxkIG1ha2UgeW91IG1vcmUgY29tZm9ydGFibGUgdG8gdXNlIHRoZW0gYWxzbyBpbiB0aGUgRG91YmxlIE1MIHNldHRpbmdzIHdoZXJlIHRoZXkgcGxheSBhIGNydWNpYWwgcm9sZS4gUmVjYWxsIHRoYXQgT0xTIGhhcyB0aGUgaG9wZWZ1bGx5IGZhbWlsaWFyIGxvb2tpbmcgbW9tZW50IGNvbmRpdGlvbjoNCg0KJCQgIEVbXHVuZGVyYnJhY2V7WCdVfV97XHBzaX1dID0gRVtcdW5kZXJicmFjZXtYJyhZLVhcYmV0YSl9X3tccHNpfV0gPSBFW1gnIFkgLSBYJ1ggXGJldGFdID0gRVtYJ1ldIC0gRVtYJ1hdIFxiZXRhID0gIEVbXHVuZGVyYnJhY2V7LVgnWH1fe1xwc2lfYX1dIFxiZXRhICsgRVtcdW5kZXJicmFjZXtYJ1l9X3tccHNpX2J9XSA9IDAgJCQNCiRcYmV0YSQgaXMgdGhlbjogDQoNCiQkIFxiZXRhID0gRVtYJ1hdXnstMX0gRVtYJ1ldICQkDQpUaGlzIGZvcm11bGF0aW9uIGNhbiBiZSB1c2VkIHRvIG9idGFpbiB0aGUgaW5mbHVlbmNlIGZ1bmN0aW9uIGZvciBpbmRpdmlkdWFsICRpJCBhcyBmb2xsb3dzOg0KDQokJCBcUHNpX2koWCxZO1xiZXRhKSA9IEVbXHVuZGVyYnJhY2V7WCdYfV97PS1ccHNpX2F9XV57LTF9IFx1bmRlcmJyYWNle1hfaShZX2ktWF9pJ1xiZXRhKX1fez1ccHNpfSQkDQpOb3RlLCB0aGF0IHRoaXMgaXMgYSB2ZWN0b3Igd2l0aCBhbiBpbmZsdWVuY2UgZnVuY3Rpb24gZm9yIGVhY2ggcGFyYW1ldGVyLiBUbyBzZWUgdGhpcyBjb25zaWRlciAkayQgZXhwbGFuYXRvcnkgdmFyaWFibGVzIChpbmNsdWRpbmcgYSBjb25zdGFudCk6IA0KDQokJCBcdW5kZXJzZXR7ayBcdGltZXMgMX17XFBzaV9pKFgsWTtcYmV0YSl9ID0gXHVuZGVyc2V0e2sgXHRpbWVzIGt9e0VbWCdYXV57LTF9fSBcdW5kZXJzZXR7ayBcdGltZXMgMX17WF9pfVx1bmRlcnNldHsxIFx0aW1lcyAxfXsoWV9pLVhfaSdcYmV0YSl9JCQNClRoaXMgZXhwcmVzc2lvbiBjYW4gYmUgY2FsY3VsYXRlZCB1c2luZyB0aGUgcmVzdWx0cyBmcm9tIHRoZSBzaW11bGF0aW9uIGFuZCAobWFudWFsKSBlc3RpbWF0aW9uLiBMZXQncyBkbyB0aGlzIGZvciB0aGUgZmlyc3QgaW5kaXZpZHVhbC4gVGhlIGV4cGVjdGF0aW9uICRFW1gnX2lYX2ldXnstMX0kIGNhbiBiZSBlc3RpbWF0ZWQgYnk6ICRcbGVmdChcZnJhY3sxfXtOfSBcc3VtX2kgWF9pICdYX2lccmlnaHQpXnstMX0gPSBcbGVmdChcZnJhY3sxfXtOfSBYJ1hccmlnaHQpXnstMX0kLiBBZGRpdGlvbmFsbHksIHdlIG5lZWQgdG8gcmVwbGFjZSB0aGUgdHJ1ZSAkXGJldGEkIGJ5IHRoZSBlc3RpbWF0ZSAkXGhhdHtcYmV0YX0kOg0KDQpgYGB7cn0NCiMgQ2FsY3VsYXRlIElGcyBmb3IgaW5kaXZpZHVhbCAxDQpzb2x2ZShjcm9zc3Byb2QoWCkgLyBuKSAlKiUgWFsxLF0gJSolICh5WzFdIC0gWFsxLF0gJSolIGJldGFzKQ0KYGBgDQoNCk9uZSBjb21wYWN0IHdheSB0byBjYWxjdWxhdGUgdGhlIElGcyBmb3IgYWxsIGluZGl2aWR1YWxzIGF0IG9uY2UgaXMgYXMgZm9sbG93czoNCmBgYHtyfQ0KSUYgPSAoWCAqIGFzLm51bWVyaWMoeSAtIFggJSolIGJldGFzKSkgJSolIHNvbHZlKGNyb3NzcHJvZChYKSAvIG4pDQpkaW0oSUYpDQpgYGANCg0KV2UgaGF2ZSBjcmVhdGVkIGFuICROIFx0aW1lcyBrJCBtYXRyaXggd2l0aCBhbGwgaW5mbHVlbmNlIGZ1bmN0aW9ucyBhbmQgY2FuIGNoZWNrIHRoZSB0aGVvcmV0aWNhbCBwcm9wZXJ0aWVzIG51bWVyaWNhbGx5Lg0KDQpUaGVvcmV0aWNhbGx5LCB0aGUgSUZzIHNob3VsZCBzYXRpc2Z5OiAkRVtcUHNpKFgsWTtcYmV0YSldID0gMCQuIEFzIGEgc2FuaXR5IGNoZWNrIGZvciBvdXIgY29tcHV0YXRpb24gb2YgdGhlIElGcywgbGV0J3Mgc2VlIHdoZXRoZXIgdGhlIGNvbHVtbiBtZWFucyBhcmUgYWN0dWFsbHkgemVybzoNCg0KYGBge3J9DQphbGwuZXF1YWwoYXMubnVtZXJpYyhjb2xNZWFucyhJRikpLHJlcCgwLG5jb2woWCkpKSAjIGNoZWNrIHdoZXRoZXIgdGhlIG1lYW5zIG9mIHRoZSBpbmZsdWVuY2UgZnVuY3Rpb25zIGFyZSBlcXVhbCB0byAwDQpgYGANCjxicj4NCg0KIyMjIyBJbmZsdWVuY2UgRnVuY3Rpb25zIGZvciBzdGFuZGFyZCBlcnJvcnMNCg0KVGhlIGluZmx1ZW5jZSBmdW5jdGlvbnMgY2FuIGJlIHVzZWQgdG8gZXN0aW1hdGUgdGhlIHN0YW5kYXJkIGVycm9ycy4gVGhlIGdlbmVyYWwgZXhwcmVzc2lvbiAkc2VcbGVmdChcaGF0e1x0aGV0YX1ccmlnaHQpID0gXHNxcnR7XGZyYWN7VmFyW1xQc2koTztcdGhldGEsXGV0YSldfXtOfX0kIGZyb20gdGhlIGxlY3R1cmUgc2xpZGVzIGNoYW5nZXMgc2xpZ2h0bHkuIEFzIHdlIGFyZSBsb29raW5nIGF0IGEgY2FzZSB3aGVyZSAkVmFyW1xQc2koWCxZO1xiZXRhLCldJCBpcyBhICRrIFx0aW1lcyBrJCAgY292YXJpYW5jZSBtYXRyaXguIFRoZSBzdGFuZGFyZCBlcnJvcnMgZm9yIHRoZSBlbGVtZW50cyBvZiAkXGJldGEkIGFyZSB0aGUgc3F1YXJlIHJvb3Qgb2YgdGhlIGRpYWdvbmFsIGVsZW1lbnRzIG9mICRcZnJhY3sxfXtOfSBWYXJcbGVmdFtcUHNpXGxlZnQoWCxZO1xoYXR7XGJldGF9XHJpZ2h0KVxyaWdodF0kIChOb3RlOiB3ZSBoYXZlIGFnYWluIHJlcGxhY2VkICRcYmV0YSQgYnkgdGhlIGVzdGltYXRlcyAkXGhhdHtcYmV0YX0kLCBhcyAtIGluIHByYWN0aWNlIC0gd2UgZG9uJ3Qga25vdyB0aGUgdHJ1ZSBwYXJhbWV0ZXIgdmFsdWVzKToNCg0KYGBge3J9DQpzcXJ0KGRpYWcodmFyKElGKSkgLyBuKQ0KYGBgDQoNCg0KV2UgY2FuIGNvbXBhcmUgdGhlc2Ugc3RhbmRhcmQgZXJyb3JzIHRvIHRoZSBzdGFuZGFyZCBlcnJvcnMsIHdlIG9idGFpbiBieSBhIHByZS1pbXBsZW1lbnRlZCBjb21tYW5kIHVzaW5nIHRoZSBkaWZmZXJlbnQgYXZhaWxhYmxlIG9wdGlvbnMuIEFsdGhvdWdoIHRoZXkgYXJlIG5vdCBleGFjdGx5IGlkZW50aWNhbCwgdGhleSBzaG91bGQgYXJlIHJlYXNvbmFibHkgY2xvc2U6IA0KYGBge3J9DQpsbV9yb2J1c3QoeSB+IHgsc2VfdHlwZSA9ICJIQzAiKSRzdGQuZXJyb3INCmxtX3JvYnVzdCh5IH4geCxzZV90eXBlID0gIkhDMSIpJHN0ZC5lcnJvcg0KbG1fcm9idXN0KHkgfiB4LHNlX3R5cGUgPSAiSEMyIikkc3RkLmVycm9yDQpsbV9yb2J1c3QoeSB+IHgsc2VfdHlwZSA9ICJIQzMiKSRzdGQuZXJyb3INCmBgYA0KDQo8YnI+DQoNCiMjIyMgQ2hlY2tpbmcgY292ZXJhZ2UgcmF0ZXMNCg0KTGV0J3MgcnVuIGEgcXVpY2sgc2ltdWxhdGlvbiB0byBjaGVjayB0aGUgY292ZXJhZ2UgcmF0ZXMgb2YgdGhlIGRpZmZlcmVudCB0eXBlcyBvZiBzdGFuZGFyZCBlcnJvcnMuIFRoZSBjb3ZlcmFnZSByYXRlIGNhbGN1bGF0ZSBob3cgb2Z0ZW4gdGhlIHRydWUgdmFsdWUgaXMgaW5jbHVkZWQgaW4gdGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIChzZWUgW3JpbmcgdG9zcyBhbmFsb2d5XShodHRwczovL21lZGl1bS5jb20vQEVwaUVsbGllL2hhdmluZy1jb25maWRlbmNlLWluLWNvbmZpZGVuY2UtaW50ZXJ2YWxzLThmODgxNzEyZDgzNykgYXMgYW4gaW50dWl0aXZlIHJlZnJlc2hlcikuIA0KDQpUaGUgY292ZXJhZ2UgcmF0ZSBjYW4gYmUgdXNlZCB0byBldmFsdWF0ZSB0aGUgcXVhbGl0eSBvZiB0aGUgZGlmZmVyZW50IHN0YW5kYXJkIGVycm9ycy4gV2Ugd291bGQgbGlrZSB0byBoYXZlIG5vbWluYWwgY292ZXJhZ2UsIGkuZS4gZm9yIGEgOTUlIGNvbmZpZGVuY2UgbGV2ZWwgaXQgc2hvdWxkIGhhcHBlbiBpbiA5NSUgb2YgdGhlIHJlcGxpY2F0aW9ucywgZm9yIGEgOTAlIGNvbmZpZGVuY2UgbGV2ZWwgaW4gOTAlIG9mIHRoZSByZXBsaWNhdGlvbnMsIC4uLg0KDQpXZSBkbyB0aGlzIGZvciB0aGUgZXN0aW1hdGUgb2YgdGhlIGNvZWZmaWNpZW50IG9uICR4JCBpbiB0aGUgcmVncmVzc2lvbiBhYm92ZS4NCmBgYHtyfQ0KcmVwID0gMTAwMCAjIG51bWJlciBvZiByZXBsaWNhdGlvbnMgZm9yIHRoZSBzaW11bGF0aW9uDQpzZV9tYXRyaXggPSBtYXRyaXgoTkEsIG5yb3cgPSByZXAsIG5jb2wgPSA1KSAjIGVtcHR5IG1hdHJpeCB0byBzdG9yZSB0aGUgc3RhbmRhcmQgZXJyb3JzDQpjb3Zlcl9tYXRyaXggPSBtYXRyaXgoTkEsIG5yb3cgPSByZXAsIG5jb2wgPSA1KSAjIG1hdHJpeCB0byBzdG9yZSB3aGV0aGVyIHRydWUgdmFsdWUgd2FzIGNvdmVyZWQNCmNvbG5hbWVzKHNlX21hdHJpeCkgPSBjKCJIQzAiLCAiSEMxIiwgIkhDMiIsICJIQzMiLCAiSUYiKSAjIGdpdmUgbWVhbmluZ2Z1bCBuYW1lcyB0byB0aGUgY29sdW1ucyBvZiB0aGUgbWF0cmljZXMNCmNvbG5hbWVzKGNvdmVyX21hdHJpeCkgPSBjKCJIQzAiLCAiSEMxIiwgIkhDMiIsICJIQzMiLCAiSUYiKQ0KDQpuID0gMTAwICMgbnVtYmVyIG9mIG9ic2VydmF0aW9ucw0KDQojIFRydWUgcGFyYW1ldGVyIHZhbHVlcw0KYjAgPSAxDQpiMSA9IDEvMg0KDQpjcml0aWNhbF92YWx1ZSA9IHF0KDAuOTc1LCBkZiA9IG4tMikNCg0KZm9yIChpIGluIDE6IHJlcCl7DQoNCiAgIyBEcmF3IGluZGVwZW5kZW50IHZhcmlhYmxlDQogIHggPSBybm9ybShuKQ0KICANCiAgIyBHZW5lcmF0ZSBvdXRjb21lDQogIHkgPSBiMCArIGIxICogeCArIHJ1bmlmKG4sLTEsMSkNCiAgDQogICMgdXNlIHRoZSBwcmVpbXBsZW1lbnRlZCBjb21tYW5kcywgc2F2ZSB0aGUgc3RhbmRhcmQgZXJyb3JzOg0KICBzZV9tYXRyaXhbaSwxXSA9IGxtX3JvYnVzdCh5IH4geCxzZV90eXBlID0gIkhDMCIpJHN0ZC5lcnJvclsyXQ0KICBzZV9tYXRyaXhbaSwyXSA9IGxtX3JvYnVzdCh5IH4geCxzZV90eXBlID0gIkhDMSIpJHN0ZC5lcnJvclsyXQ0KICBzZV9tYXRyaXhbaSwzXSA9IGxtX3JvYnVzdCh5IH4geCxzZV90eXBlID0gIkhDMiIpJHN0ZC5lcnJvclsyXQ0KICBzZV9tYXRyaXhbaSw0XSA9IGxtX3JvYnVzdCh5IH4geCxzZV90eXBlID0gIkhDMyIpJHN0ZC5lcnJvclsyXQ0KICANCiAgIyBpbmZsdWVuY2UgZnVuY3Rpb24sIGFwcGx5IHRoZSBmb3JtdWxhcyBmcm9tIGFib3ZlOg0KICBYID0gY2JpbmQocmVwKDEsbikseCkNCiAgUSA9IHNvbHZlKGNyb3NzcHJvZChYKSkNCiAgYmV0YXMgPSBRICUqJSB0KFgpICUqJSB5DQogIElGID0gKFggKiBhcy5udW1lcmljKHkgLSBYICUqJSBiZXRhcykpICUqJSBzb2x2ZShjcm9zc3Byb2QoWCkgLyBuKQ0KICBzZV9tYXRyaXhbaSw1XSA9IHNxcnQoZGlhZyh2YXIoSUYpKSAvIG4pWzJdDQogIA0KICAjIGNoZWNrIHRoZSBjb3ZlcmFnZTsgaS5lLiBkb2VzIHRoZSB0cnVlIHZhbHVlIGxpZSB3aXRoaW4gdGhlIGJvdW5kcyBvZiB0aGUgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWw/Og0KICBjb3Zlcl9tYXRyaXhbaSwxXSA9IDEqKChiZXRhc1syXSAtIGNyaXRpY2FsX3ZhbHVlKiBzZV9tYXRyaXhbaSwxXSA8IGIxKSAmIChiZXRhc1syXSArIGNyaXRpY2FsX3ZhbHVlKiAgc2VfbWF0cml4W2ksMV0gPiBiMSkpDQogIGNvdmVyX21hdHJpeFtpLDJdID0gMSooKGJldGFzWzJdIC0gY3JpdGljYWxfdmFsdWUqIHNlX21hdHJpeFtpLDJdIDwgYjEpICYgKGJldGFzWzJdICsgY3JpdGljYWxfdmFsdWUqICBzZV9tYXRyaXhbaSwyXSA+IGIxKSkNCiAgY292ZXJfbWF0cml4W2ksM10gPSAxKigoYmV0YXNbMl0gLSBjcml0aWNhbF92YWx1ZSogc2VfbWF0cml4W2ksM10gPCBiMSkgJiAoYmV0YXNbMl0gKyBjcml0aWNhbF92YWx1ZSogIHNlX21hdHJpeFtpLDNdID4gYjEpKQ0KICBjb3Zlcl9tYXRyaXhbaSw0XSA9IDEqKChiZXRhc1syXSAtIGNyaXRpY2FsX3ZhbHVlKiBzZV9tYXRyaXhbaSw0XSA8IGIxKSAmIChiZXRhc1syXSArIGNyaXRpY2FsX3ZhbHVlKiAgc2VfbWF0cml4W2ksNF0gPiBiMSkpDQogIGNvdmVyX21hdHJpeFtpLDVdID0gMSooKGJldGFzWzJdIC0gY3JpdGljYWxfdmFsdWUqIHNlX21hdHJpeFtpLDVdIDwgYjEpICYgKGJldGFzWzJdICsgY3JpdGljYWxfdmFsdWUqICBzZV9tYXRyaXhbaSw1XSA+IGIxKSkNCn0NCmBgYA0KDQpMb29rIGF0IHRoZSBjb3ZlcmFnZSByYXRlcy4gQXMgd2UgdXNlZCA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbHMsIHRoZXkgc2hvdWxkIGFsbCBiZSBjbG9zZSB0byA5NSUNCmBgYHtyfQ0KY29sTWVhbnMoY292ZXJfbWF0cml4KQ0KYGBgDQoNCmFuZCB0aGV5IGFyZSAoaW5jcmVhc2luZyAkTiQgZ2V0cyB0aGVtIGV2ZW4gY2xvc2VyKS4NCg0KVGhlIGRpZmZlcmVudCBzdGFuZGFyZCBlcnJvcnMgc2hvdWxkIGFsc28gYmUgaGlnaGx5IGNvcnJlbGF0ZWQgaW4gdGhlIGRpZmZlcmVudCByZXBsaWNhdGlvbnMNCmBgYHtyfQ0KY29yKHNlX21hdHJpeCkNCmBgYA0KYW5kIHRoZXkgYXJlLg0KDQooQm9udXMgcmVtYXJrOiBOb3RlIHRoYXQgdXNpbmcgdGhlIGluZmx1ZW5jZSBmdW5jdGlvbnMsIG9uZSBjYW4gZXhhY3RseSByZWNvdmVyIHRoZSBjbGFzc2ljIChTdGF0YSkgSHViZXItV2hpdGUgU0VzIHVzaW5nIGEgY29ycmVjdGlvbiB0ZXJtLikNCmBgYHtyfQ0KSUYgPSAoWCAqIGFzLm51bWVyaWMoeSAtIFggJSolIGJldGFzKSkgJSolIHNvbHZlKGNyb3NzcHJvZChYKSAvIG4pICogc3FydCgobi0xKS8obi0yKSkNCnNxcnQoZGlhZyh2YXIoSUYpKSAvIG4pDQpsbV9yb2J1c3QoeSB+IHgsc2VfdHlwZSA9ICJIQzEiKSRzdGQuZXJyb3INCmBgYA0KDQo8YnI+DQoNCiMjIyBJbnR1aXRpb24gYmVoaW5kIEluZmx1ZW5jZSBGdW5jdGlvbnMNCg0KSW50dWl0aXZlbHksIGluZmx1ZW5jZSBmdW5jdGlvbnMgYWxsb3cgdXMgdG8gZXZhbHVhdGUgdGhlIGluZmx1ZW5jZSBvZiBhIHNpbmdsZSBvYnNlcnZhdGlvbiBvbiBhbiBlc3RpbWF0ZSAoZS5nLiwgdGhlIGNvZWZmaWNpZW50cyBpbiBhbiBPTFMgcmVncmVzc2lvbikuIFRvIGJlIG1vcmUgcHJlY2lzZSwgdGhlIGluZmx1ZW5jZSBvZiBvYnNlcnZhdGlvbiAkaSQgb24gdGhlIGVzdGltYXRpb24gb2YgdGhlIHRhcmdldCBwYXJhbWV0ZXIgKGhlcmU6ICRcYmV0YSQpIGlzIGFwcHJveGltYXRlZCBieSAkXGZyYWN7XFBzaV9pKE87IFx0aGV0YSwgXGV0YSl9e059JCBvciBoZXJlOiAkXGZyYWN7XFBzaV9pKFgsWTsgXGhhdHtcYmV0YX0pfXtOfSQuIA0KDQpUbyBpbGx1c3RyYXRlIHRoaXMsIHRoZSBmb2xsb3dpbmcgY29kZSBzbmlwcGV0IGNhbGN1bGF0ZXMgdHdvIGRpZmZlcmVudCBtZWFzdXJlcyBhbmQgY29tcGFyZXMgdGhlbTogDQoNCjEuIEZvciBlYWNoIG9ic2VydmF0aW9uICRpJCwgd2UgY29tcHV0ZSB0aGUgT0xTIGVzdGltYXRlcyB3aXRoIHRoZSBzYW1wbGUgKmV4Y2x1ZGluZyogb2JzZXJ2YXRpb24gJGkkLiBXZSB0aGVuIHRha2UgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgZnVsbCBzYW1wbGUgZXN0aW1hdGVzIGFuZCB0aGlzIG5ldyBlc3RpbWF0ZSB3aXRob3V0IGluZGl2aWR1YWwgJGkkLiBUaGlzIGNhcHR1cmVzIHRoZSBpbmZsdWVuY2Ugb2Ygb2JzZXJ2YXRpb24gJGkkIG9uIHRoZSBlc3RpbWF0ZXMuICANCjIuIFRoZSBtZWFzdXJlICRcZnJhY3tcUHNpX2koWCxZOyBcaGF0e1xiZXRhKX19e059JA0KDQpJZiB0aGUgaW5mbHVlbmNlIGZ1bmN0aW9ucyB3b3JrIGFzIHRoZXkgYXJlIHN1cHBvc2VkIHRvLCB0aGVzZSBpbmRpdmlkdWFsLXNwZWNpZmljIGRpZmZlcmVuY2VzIHNob3VsZCBiZSBoaWdobHkgY29ycmVsYXRlZC4NCg0KYGBge3J9DQojIERyYXcgaW5kZXBlbmRlbnQgdmFyaWFibGUNCnggPSBybm9ybShuKQ0KDQojIEdlbmVyYXRlIG91dGNvbWUNCnkgPSBiMCArIGIxICogeCArIHJ1bmlmKG4sLTEsMSkNCg0KIyBpbmZsdWVuY2UgZnVuY3Rpb24sIGFwcGx5IHRoZSBmb3JtdWxhcyBmcm9tIGFib3ZlOg0KWCA9IGNiaW5kKHJlcCgxLG4pLHgpDQpRID0gc29sdmUoY3Jvc3Nwcm9kKFgpKQ0KYmV0YXMgPSBRICUqJSB0KFgpICUqJSB5DQpJRiA9IHNvbHZlKGNyb3NzcHJvZChYKSAvIG4pICUqJSB0KFggKiBhcy5udW1lcmljKHkgLSBYICUqJSBiZXRhcykpDQoNCmxvb29scyA9IG1hdHJpeChOQSxuLDIpDQoNCiNDYWxjdWxhdGUgY29lZmZpY2llbnRzIHcvbyBpbmRpdmlkdWFsIGkNCmZvciAoaSBpbiAxOm4pIHsNCiAgbG9vb2xzW2ksXSA9IHNvbHZlKGNyb3NzcHJvZChYWy1pLF0pKSAlKiUgdChYWy1pLF0pICUqJSB5Wy1pXQ0KfQ0KYGBgDQoNClBsb3QgdGhlIDIgbWVhc3VyZXMgYWdhaW5zdCBlYWNoIG90aGVyOg0KYGBge3J9DQojIHBsb3QgZGlmZiBiZXR3ZWVuIGZ1bGwgc2FtcGxlIGFuZCBsZWF2ZS1vbi1vdXQgYWdhaW5zdCB0aGUgSUYNCnBsb3QoYmV0YXNbMV0gLSBsb29vbHNbLDFdLCBJRlsxLF0gLyBuKQ0KDQpwbG90KGJldGFzWzJdIC0gbG9vb2xzWywyXSwgSUZbMixdIC8gbikNCmBgYA0KVGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgY29lZmZpY2llbnRzIHdpdGggYW5kIHdpdGhvdXQgZWFjaCBvYnNlcnZhdGlvbiBhcmUgdmVyeSBzaW1pbGFyLCB0aG91Z2ggbm90IGlkZW50aWNhbCkgdG8gdGhlIGluZmx1ZW5jZSBmdW5jdGlvbiAoZGl2aWRlZCBieSAkTiQpLiBUaGlzIGlsbHVzdHJhdGVzIHRoYXQgdGhlIGluZmx1ZW5jZSBmdW5jdGlvbiBhcHByb3hpbWF0ZXMgdGhlIGluZmx1ZW5jZSBvZiBlYWNoIGluZGl2aWR1YWwgb2JzZXJ2YXRpb24gb24gdGhlIGVzdGltYXRlcy4NCg0KPGJyPg0KDQojIyBDaGFpbiBydWxlDQoNCldlIGxlYXJuZWQgaW4gdGhlIGxlY3R1cmUgdGhhdCBpbmZsdWVuY2UgZnVuY3Rpb25zIG9iZXkgYSBjaGFpbiBydWxlIHRoYXQgYWxsb3dzIHRvIGRlcml2ZSBuZXcgaW5mbHVlbmNlIGZ1bmN0aW9uIG9mIGNvbXBvc2l0ZSBwYXJhbWV0ZXJzIG9uY2Ugd2Uga25vdyB0aGUgaW5mbHVlbmNlIGZ1bmN0aW9uIG9mIGl0cyBjb21wb25lbnRzLg0KDQojIyMgVXNlIGNhc2UgMTogVGVzdGluZyBmb3Igb21pdHRlZCB2YXJpYWJsZSBiaWFzDQoNCkNvbnNpZGVyIHRoZSBmb2xsb3dpbmcgc3R5bGl6ZWQgREdQLiBUaGVyZSBpcyBhbiBvdXRjb21lICRZJCwgYSBjb250aW51b3VzIHRyZWF0bWVudCAkVyQsIGFuZCBhIGNvbmZvdW5kaW5nIHZhcmlhYmxlICRYJDoNCiQkICAgWT0gIDAuNSBXICsgWCArIFUkJA0KDQoNCndpdGggJFVfMSBcc2ltIFxtYXRoY2Fse059KDAsMSkkIA0KYW5kOg0KJCQgXGJlZ2lue2FsaWdufQ0KIFtXLCBYXScmXHNpbSBcbWF0aGNhbHtOfVxsZWZ0KFswfn4wXScsXGxlZnRbIFxiZWdpbnthcnJheX0NCntycnJ9DQoxICYgXHJobyAgXFwNClxyaG8gJiAxIA0KXGVuZHthcnJheX1ccmlnaHRdXHJpZ2h0KQ0KXGVuZHthbGlnbn0gJCQNCg0KDQppLmUuIHRoZSBjb25zdGFudCB0cmVhdG1lbnQgZWZmZWN0IGlzIDAuNS4NCg0KPGJyPg0KDQojIyMjIE5vIGNvbmZvdW5kaW5nDQoNCkZpcnN0LCBzaW11bGF0ZSB0aGUgZGF0YSBzZXQgYXMgZGVzY3JpYmVkIGFib3ZlIHdpdGggJFxyaG8gPSAwJCwgaS5lLiBubyBjb25mb3VuZGluZzoNCg0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KbiA9IDEwMDANCm11ID0gYygwLDApDQpyaG8gPSAwDQpzaWdtYSA9IG1hdHJpeChjKDEscmhvLHJobywxKSwgbnJvdyA9IDIpDQpkcmF3ID0gbXZybm9ybShuLCBtdSwgc2lnbWEpDQpXID0gZHJhd1ssMV0NClggPSAgZHJhd1ssMl0NClkgPSAwLjUgKiBXICsgWCArIHJub3JtKG4pDQpgYGANCg0KDQpMZXQncyBydW4gdGhlIHJlZ3Jlc3Npb24gb2YgJFkkIG9uICRXJCBhbmQgY2FsY3VsYXRlIHRoZSBpbmZsdWVuY2UgZnVuY3Rpb25zLiBUaGlzIGlzIGRvbmUgZXhhY3RseSB0aGUgc2FtZSB3YXkgYXMgYWJvdmUuDQoNCmBgYHtyfQ0KIyBydW4gYSByZWdyZXNzaW9uIHVzaW5nIHRoZSBwcmUtaW1wbGVtZW50ZWQgY29tbWFuZDoNCnN1bW1hcnkobG1fcm9idXN0KFl+IFcpKQ0KDQojIENhbGN1bGF0ZSB0aGUgaW5mbHVlbmNlIGZ1bmN0aW9ucy4gSW4gY29udHJhc3QgdG8gdGhlIGNvZGUgYWJvdmUsIGhlcmUgd2UgZGlyZWN0bHkgY29uc3RydWN0IHRoZSBtYXRyaXggWCB1c2luZyBtb2RlbC5tYXRyaXgobG1fcm9idXN0KFl+IFcpKSANCiMgYW5kIHRoZSBPTFMgY29lZmZpY2llbnRzIGFzIGEgdmVjdG9yIHdpdGggbWF0cml4KGxtX3JvYnVzdChZfiBXKSRjb2VmZmljaWVudHMpIGluIHRoZSBzYW1lIGxpbmUgb2YgY29kZQ0KSUZfT1ZCID0gKG1vZGVsLm1hdHJpeChsbV9yb2J1c3QoWX4gVykpICogYXMubnVtZXJpYyhZIC0gbW9kZWwubWF0cml4KGxtX3JvYnVzdChZfiBXKSkgJSolIG1hdHJpeChsbV9yb2J1c3QoWX4gVykkY29lZmZpY2llbnRzKSkpICUqJSBzb2x2ZShjcm9zc3Byb2QobW9kZWwubWF0cml4KGxtX3JvYnVzdChZfiBXKSkpIC8gbikNCmRpbShJRl9PVkIpDQpgYGANCg0KV2UgY2FuIGFsc28gcnVuIGEgcmVncmVzc2lvbiB3aXRoICRYJCBpbmNsdWRlZCBhcyBhIGNvbnRyb2wgdmFyaWFibGUgYW5kIGNhbGN1bGF0ZSB0aGUgSW5mbHVlbmNlIEZ1bmN0aW9uczoNCg0KYGBge3J9DQpzdW1tYXJ5KGxtX3JvYnVzdChZfiBXK1gpKQ0KDQpJRl9YID0gKG1vZGVsLm1hdHJpeChsbV9yb2J1c3QoWX4gVytYKSkgKiBhcy5udW1lcmljKFkgLSBtb2RlbC5tYXRyaXgobG1fcm9idXN0KFl+IFcrWCkpICUqJSBtYXRyaXgobG1fcm9idXN0KFl+IFcrWCkkY29lZmZpY2llbnRzKSkpICUqJSBzb2x2ZShjcm9zc3Byb2QobW9kZWwubWF0cml4KGxtX3JvYnVzdChZfiBXK1gpKSkgLyBuKQ0KYGBgDQoNClRoZSB0d28gZXN0aW1hdGVkIGNvZWZmaWNpZW50cyBvZiB0aGUgdHJlYXRtZW50IChjYWxsIHRoZW0gJFxoYXR7XHRhdX1fe09WQn0kIGFuZCAkXGhhdHtcdGF1fV9YJCkgbG9vayBxdWl0ZSBzaW1pbGFyIGluIHRoZSB0d28gcmVncmVzc2lvbnMsIGJ1dCB3ZSB3b3VsZCBsaWtlIHRvIGJlIGFibGUgdG8gY2FsY3VsYXRlIHN0YW5kYXJkIGVycm9ycyBmb3IgdGhlaXIgZGlmZmVyZW5jZSAkXERlbHRhID0gXHRhdV97T1ZCfSAtIFx0YXVfe1h9JCBhbmQgdGVzdCBmb3Igc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlLiBBcyBpbmZsdWVuY2UgZnVuY3Rpb25zIG9iZXkgdGhlIGNoYWluIHJ1bGUsIHRoZSBpbmZsdWVuY2UgZnVuY3Rpb24gZm9yICRcRGVsdGEkIGNhbiBiZSBvYnRhaW5lZCBhczoNCg0KJCQgXFBzaV97XERlbHRhfSA9IFxmcmFje1xwYXJ0aWFsIFxEZWx0YX17XHBhcnRpYWwgXHRhdV97T1ZCfX0gXFBzaV97XHRhdV97T1ZCfX0gKyBcZnJhY3tccGFydGlhbCBcRGVsdGF9e1xwYXJ0aWFsIFx0YXVfe1h9fSBcUHNpX3tcdGF1X3tYfX0gPSBcUHNpX3tcdGF1X3tPVkJ9fSAgLSBcUHNpX3tcdGF1X3tYfX0kJDoNCg0KVGhpcyBpcyB2ZXJ5IGVhc2lseSBkb25lLCBzaW5jZSB3ZSBhbHJlYWR5IGNhbGN1bGF0ZWQgJFxQc2lfe1x0YXVfe09WQn19JCBhbmQgJFxQc2lfe1x0YXVfe1h9fSQgd2hlbiBydW5uaW5nIHRoZSB0d28gc2VwYXJhdGUgcmVncmVzc2lvbnM6DQoNCmBgYHtyfQ0KIyBkaXNwbGF5IHRoZSBkaWZmZXJlbmNlOg0KRGVsdGEgPSBsbV9yb2J1c3QoWSB+IFcpJGNvZWZmaWNpZW50c1syXSAtIGxtX3JvYnVzdChZIH4gVyArIFgpJGNvZWZmaWNpZW50c1syXQ0KcHJpbnQocGFzdGUwKCJEZWx0YTogIiwgRGVsdGEpKQ0KDQpJRl9EZWx0YSA9IElGX09WQlssMl0gLSBJRl9YWywyXSAjIEluZmx1ZW5jZSBGdW5jdGlvbiBmb3IgRGVsdGENCg0Kc2VfRGVsdGEgPSBzcXJ0KHZhcihJRl9EZWx0YSkgLyBsZW5ndGgoSUZfRGVsdGEpKQ0KdF9zdGF0ID0gRGVsdGEvc2VfRGVsdGEgIyB0LXN0YXRpc3RpYw0KcHJpbnQocGFzdGUwKCJ0LXN0YXRpc3RpYzogIiwgdF9zdGF0KSkNCnBfdmFsID0gMiooMS1wbm9ybShhYnMoRGVsdGEvc2VfRGVsdGEpKSkgIyBwIHZhbHVlDQpwcmludChwYXN0ZTAoInAtdmFsdWU6ICIsIHBfdmFsKSkNCmBgYA0KVGhlIHAtdmFsdWUgc2hvd3MgdGhhdCB0aGUgZGlmZmVyZW5jZSBpcyBub3Qgc2lnbmlmaWNhbnRseSBkaWZmZXJlbnQgZnJvbSB6ZXJvIG9uIGFueSBjb252ZW50aW9uYWwgbGV2ZWwgb2Ygc2lnbmlmaWNhbmNlLiBUaGVyZSBpcyBubyBzaWduaWZpY2FudCBvbWl0dGVkIHZhcmlhYmxlIGJpYXMuIEFzICRXJCBhbmQgJFgkIGFyZSBpbmRlcGVuZGVudCwgdGhpcyBpcyB3aGF0IHdlIGV4cGVjdGVkLg0KDQo8YnI+DQoNCiMjIyMgQ29uZm91bmRpbmcNCg0KTm93LCBsZXQncyBkbyB0aGUgc2FtZSBhZ2FpbiwgYnV0IHVzaW5nICRccmhvID0gMC43JDoNCg0KYGBge3IsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQ0KcmhvID0gMC43DQpzaWdtYSA9IG1hdHJpeChjKDEscmhvLHJobywxKSwgbnJvdyA9IDIpDQpkcmF3ID0gbXZybm9ybShuLCBtdSwgc2lnbWEpDQpXID0gZHJhd1ssMV0NClggPSAgZHJhd1ssMl0NClkgPSAwLjUgKiBXICsgWCArIHJub3JtKG4pDQpgYGANCg0KDQpUaGlzIGludHJvZHVjZXMgb21pdHRlZCB2YXJpYWJsZSBiaWFzIGFzIHdlIGNhbiBvYnNlcnZlIGJ5IHJ1bm5pbmcgT0xTIG9uY2Ugd2l0aG91dCBjb250cm9sbGluZyBmb3IgJFgkIA0KDQpgYGB7cn0NCnN1bW1hcnkobG1fcm9idXN0KFkgfiBXKSkNCg0KSUZfT1ZCID0gKG1vZGVsLm1hdHJpeChsbV9yb2J1c3QoWX4gVykpICogYXMubnVtZXJpYyhZIC0gbW9kZWwubWF0cml4KGxtX3JvYnVzdChZfiBXKSkgJSolIG1hdHJpeChsbV9yb2J1c3QoWX4gVykkY29lZmZpY2llbnRzKSkpICUqJSBzb2x2ZShjcm9zc3Byb2QobW9kZWwubWF0cml4KGxtX3JvYnVzdChZfiBXKSkpIC8gbikNCmBgYA0KDQphbmQgb25jZSB3aXRoIGNvbnRyb2xsaW5nIGZvciAkWCQ6DQoNCmBgYHtyfQ0Kc3VtbWFyeShsbV9yb2J1c3QoWSB+IFcgKyBYKSkNCg0KSUZfWCA9IChtb2RlbC5tYXRyaXgobG1fcm9idXN0KFl+IFcrWCkpICogYXMubnVtZXJpYyhZIC0gbW9kZWwubWF0cml4KGxtX3JvYnVzdChZfiBXK1gpKSAlKiUgbWF0cml4KGxtX3JvYnVzdChZfiBXK1gpJGNvZWZmaWNpZW50cykpKSAlKiUgc29sdmUoY3Jvc3Nwcm9kKG1vZGVsLm1hdHJpeChsbV9yb2J1c3QoWX4gVytYKSkpIC8gbikNCmBgYA0KDQpUbyBjaGVjayB3aGV0aGVyIHRoZSBkaWZmZXJlbmNlIG9mIHRoZSBlc3RpbWF0ZXMgZnJvbSB0aGUgdHdvIHJlZ3Jlc3Npb25zIGFyZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50bHkgZGlmZmVyZW50IGZyb20gemVybywgd2Ugd2lsbCBhZ2FpbiB1c2UgdGhlIGNvbXBvc2l0ZSBpbmZsdWVuY2UgZnVuY3Rpb24gJFxQc2lfe1xEZWx0YX0kOg0KDQpgYGB7cn0NCiMgZGlzcGxheSB0aGUgZGlmZmVyZW5jZToNCkRlbHRhID0gbG1fcm9idXN0KFkgfiBXKSRjb2VmZmljaWVudHNbMl0gLSBsbV9yb2J1c3QoWSB+IFcgKyBYKSRjb2VmZmljaWVudHNbMl0NCnByaW50KHBhc3RlMCgiRGVsdGE6ICIsIERlbHRhKSkNCg0KSUZfRGVsdGEgPSBJRl9PVkJbLDJdIC0gSUZfWFssMl0gIyBJbmZsdWVuY2UgRnVuY3Rpb24gZm9yIERlbHRhDQoNCnNlX0RlbHRhID0gc3FydCh2YXIoSUZfRGVsdGEpIC8gbGVuZ3RoKElGX0RlbHRhKSkNCnRfc3RhdCA9IERlbHRhL3NlX0RlbHRhICMgdC1zdGF0aXN0aWMNCnByaW50KHBhc3RlMCgidC1zdGF0aXN0aWM6ICIsIHRfc3RhdCkpDQpwX3ZhbCA9IDIqKDEtcG5vcm0oYWJzKERlbHRhL3NlX0RlbHRhKSkpICMgcCB2YWx1ZQ0KcHJpbnQocGFzdGUwKCJwLXZhbHVlOiAiLCBwX3ZhbCkpDQpgYGANClRoZSBkaWZmZXJlbmNlIGlzIHNpZ25pZmljYW50bHkgZGlmZmVyZW50IGZyb20gemVyby4gSW4gdGhpcyBjYXNlLCBhcyBleHBlY3RlZCwgdGhlcmUgaXMgYSBzaWduaWZpY2FudCBvbWl0dGVkIHZhcmlhYmxlIGJpYXMuDQoNCjxicj4NCg0KIyMjIFVzZSBjYXNlIDI6IFN0YW5kYXJkIGVycm9ycyBmb3IgZml0dGVkIHZhbHVlcyB1c2luZyBpbmZsdWVuY2UgZnVuY3Rpb25zDQoNCkFzc3VtZSB0aGF0IGFmdGVyIHJ1bm5pbmcgT0xTIHdlIHdhbnQgdG8gb2J0YWluIHN0YW5kYXJkIGVycm9ycyBmb3IgdGhlIGZpdHRlZCB2YWx1ZXMgZm9yIGluZGl2aWR1YWwgJGkkIA0KJCRcaGF0e1l9X2kgPSBYX2kgXGhhdHtcYmV0YX0gPSBYX3tpLDF9IFxoYXR7XGJldGFfMX0gKyAuLi4gKyBYX3tpLGt9IFxoYXR7XGJldGFfa30kJCANCg0KQWdhaW4sIHRoZSBjaGFpbiBydWxlIGNvbWVzIGluIGhhbmR5LiBXZSBjYW4gd3JpdGUgdGhlIGluZmx1ZW5jZSBmdW5jdGlvbiBmb3IgdGhlIHByZWRpY3Rpb24gJFxoYXR7WV9pfSQsIGkuZS4gJFxQc2lfe1xoYXR7WV9pfX0kIGFzOg0KDQokJCBcYmVnaW57ZXF1YXRpb259IFxQc2lfe1xoYXR7WV9pfX0gPSBcZnJhY3tccGFydGlhbCBcaGF0e1lfaX19e1xwYXJ0aWFsIFxoYXR7XGJldGFfMX19IFxQc2lfe1xoYXR7XGJldGFfMX19ICsgLi4uICsgXGZyYWN7XHBhcnRpYWwgXGhhdHtZX2l9fXtccGFydGlhbCBcaGF0e1xiZXRhX2t9fSBcUHNpX3tcaGF0e1xiZXRhX2t9fSA9IFhfe2ksMX0gXFBzaV97XGhhdHtcYmV0YV8xfX0gKyAuLi4gKyBYX3tpLGt9IFxQc2lfe1xoYXR7XGJldGFfa319ID0gWF9pJyBcUHNpXnsnfV97XGhhdHtcYmV0YX19IFxlbmR7ZXF1YXRpb259ICQkDQoNClRoZSBtYXRyaXggJFx1bmRlcnNldHtOIFx0aW1lcyBrfXtcUHNpX3tcaGF0e1xiZXRhfX19JCBpcyB3aGF0IHdlIGFscmVhZHkgY2FsY3VsYXRlZCBhYm92ZS4gSXQgY29udGFpbnMgdGhlIHZhbHVlcyBvZiB0aGUgaW5mbHVlbmNlIGZ1bmN0aW9uIGZvciBhbGwgaW5kaXZpZHVhbHMgYW5kIGFsbCBlbGVtZW50cyBvZiAkXGJldGEkLiBUbyBkZW1vbnN0cmF0ZSBob3cgd2UgY2FuIGNhbGN1bGF0ZSBzdGFuZGFyZCBlcnJvcnMgZm9yICRcaGF0e1lfaX0kLCB3ZSBydW4gdGhlIHZlcnkgc2ltcGxlIHJlZ3Jlc3Npb24gZnJvbSB0aGUgYmVnaW5uaW5nIGFuZCBpbiBhIGZpcnN0IHN0ZXAgY2FsY3VsYXRlIHRoZSBpbmZsdWVuY2UgZnVuY3Rpb25zIGZvciB0aGUgZXN0aW1hdGVkIGNvZWZmaWNpZW50cyBhbmQgYWxsIGluZGl2aWR1YWxzLiBUaGUgcHJvY2VkdXJlIGlzIGV4YWN0bHkgdGhlIHNhbWUgYXMgYWJvdmU6DQoNCmBgYHtyLCBtZXNzYWdlID0gRkFMU0V9DQp4X3NlcSA9IHNlcSgtMywzLDAuMDEpDQpuID0gbGVuZ3RoKHhfc2VxKQ0KDQojIERyYXcgaW5kZXBlbmRlbnQgdmFyaWFibGUNCnggPSBybm9ybShuKQ0KDQojIFRydWUgcGFyYW1ldGVyDQpiMCA9IC0xDQpiMSA9IDEvMg0KDQojIEdlbmVyYXRlIG91dGNvbWUNCnkgPSBiMCArIGIxICogeCArIHJ1bmlmKG4sLTEsMSkNCg0KIyBBZGQgY29uc3RhbnQNClggPSBjYmluZChyZXAoMSxuKSx4KQ0KDQojIE1hbnVhbGx5IG9idGFpbiB0aGUgT0xTIGNvZWZmaWNpZW50czoNClEgPSBzb2x2ZShjcm9zc3Byb2QoWCkpDQpiZXRhcyA9IFEgJSolIHQoWCkgJSolIHkNCg0KIyBBbmQgZ2V0IHRoZSBpbmZsdWVuY2UgZnVuY3Rpb25zIGZvciBhbGwgaW5kaXZpZHVhbHMNCklGID0gKFggKiBhcy5udW1lcmljKHkgLSBYICUqJSBiZXRhcykpICUqJSBzb2x2ZShjcm9zc3Byb2QoWCkgLyBuKQ0KYGBgDQoNCldlIG5vdyB3YW50IHRvIG9idGFpbiB0aGUgaW5mbHVlbmNlIGZ1bmN0aW9ucyBub3QgZm9yIHRoZSBjb2VmZmljaWVudHMsIGJ1dCBmb3IgZml0dGVkIHZhbHVlcyB3aXRoIGRpZmZlcmVudCB2YWx1ZXMgb2YgJFhfaSQuIEluIHBhcnRpY3VsYXIsIHdlIGNhbGN1bGF0ZSBzdGFuZGFyZCBlcnJvcnMgZm9yIHNldmVyYWwgZml0dGVkIHZhbHVlcywgYW5kIHVzZSBhIHNlcXVlbmNlIGZyb20gLTMgdG8gMyBhcyB2YWx1ZXMgZm9yICRYX3tpLDJ9JC4gTGV0J3MgZGVub3RlIHRoaXMgc2VxdWVuY2UsIHdoaWNoIGNvbnNpc3RzIG9mICRtJCB2YWx1ZXMgYnkgJFx0aWxkZXt4fSQuIA0KDQpUaGUgdmVjdG9yIG9mIGZpdHRlZCB2YWx1ZXMgd2lsbCB0aHVzIGJlOiANCiQkXHVuZGVyc2V0e20gXHRpbWVzIDF9e1xoYXR7WX19ID0gXHVuZGVyc2V0e20gXHRpbWVzIDF9e1xoYXR7XGJldGFfMX19ICsgXGhhdHtcYmV0YV8yfSBcY2RvdCBcdW5kZXJzZXR7bSBcdGltZXMgMX17XHRpbGRle3h9fSA9IFx1bmRlcnNldHttIFx0aW1lcyBrfXtcdGlsZGV7WH19IFxjZG90IFx1bmRlcnNldHtrIFx0aW1lcyAxfXtcaGF0e1xiZXRhfX0gJCQgDQoNCg0KJFx0aWxkZXtYfSQgaXMgYSBtYXRyaXggd2l0aCB0aGUgZmlyc3QgY29sdW1uIGNvbnNpc3Rpbmcgb2Ygb25lcyBhbmQgdGhlIHNlcXVlbmNlICRcdGlsZGV7eH0kIGluIHRoZSBzZWNvbmQgY29sdW1uLiBGb3IgZWFjaCBvZiB0aGUgJG0kIGZpdHRlZCB2YWx1ZXMgd2UgY2FuIG5vdyBjYWxjdWxhdGUgdGhlIHZhbHVlcyBvZiB0aGUgaW5mbHVlbmNlIGZ1bmN0aW9ucyAoYWdhaW4sIHJlbWVtYmVyOiBvbmUgZm9yIGVhY2ggaW5kaXZpZHVhbCkgYXMgd2UgaGF2ZSBzZWVuIGFib3ZlLiBXZSBjYW4gZG8gdGhpcyBmb3IgYWxsIHRoZSAkbSQgdmFsdWVzIG9mIHRoZSBzZXF1ZW5jZSAkXHRpbGRle3h9JCBhbmQgd3JpdGUgaXQgdmVyeSBjb25jaXNlbHkgYXM6DQoNCiQkIFx1bmRlcnNldHttIFx0aW1lcyBOfXtcUHNpX3tcaGF0e3l9fX0gPSBcdW5kZXJzZXR7bSBcdGltZXMga30ge1x0aWxkZXtYfX0gXHVuZGVyc2V0e2tcdGltZXMgTn17XFBzaV57J31fe1xoYXR7XGJldGF9fX0kJC4gDQoNClRoZSBmb2xsb3dpbmcgY29kZSBzbmlwcGV0IGNhbGN1bGF0ZXMgdGhlIGluZmx1ZW5jZSBmdW5jdGlvbiBmb3IgdGhlc2UgZml0dGVkIHZhbHVlczoNCg0KYGBge3J9DQpYX3NlcSAgPSBjYmluZChyZXAoMSwgbGVuZ3RoKHhfc2VxKSksIHhfc2VxKSAjIGNvbnN0cnVjdCB0aGUgbWF0cml4LCB3aGljaCBpbiB0aGUgdGV4dCBpcyBjYWxsZWQgYmlnIFggdGlsZGUNCg0KIyBHZXQgdGhlIG0geCAxIHZlY3RvciBvZiBwcmVkaWN0aW9ucyB5X2hhdCB1c2luZyB0aGUgc2VxdWVuY2UgZnJvbSAtMyB0byAzIGFzIHZhbHVlcyBmb3IgeDoNCnlfaGF0ID0gWF9zZXEgJSolIGJldGFzDQoNCiMgSW5mbHVlbmNlIGZ1bmN0aW9uIGZvciB0aGUgcHJlZGljdGlvbnMsIHVzaW5nIHRoZSBpbmZsdWVuY2UgZnVuY3Rpb25zIGZvciB0aGUgY29lZmZpY2llbnRzOg0KSUZfeWhhdCA9IFhfc2VxICUqJSB0KElGKQ0KYGBgDQoNCkxldCdzIGdldCB0aGUgc3RhbmRhcmQgZXJyb3JzIGFuZCBjYWxjdWxhdGUgdGhlIDk1JS1jb25maWRlbmNlIGludGVydmFscyBhdCB0aGUgZGlmZmVyZW50ICRcdGlsZGV7eH0kIGFuZCBpbGx1c3RyYXRlIHRoaXMgZ3JhcGhpY2FsbHk6DQoNCmBgYHtyfQ0KIyBTdGFuZGFyZCBlcnJvcnM6DQpzZXMgPSBzcXJ0KGRpYWcodmFyKHQoSUZfeWhhdCkpKSAvIG4pICMganVzdCBsaWtlIHdlIGRpZCBpdCBhYm92ZQ0KDQojIG1hbnVhbGx5IGNvbXB1dGUgdGhlIGJvdW5kcyBvZiB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbDoNCkNJX2xvd2VyID0geV9oYXQgLSBjcml0aWNhbF92YWx1ZSpzZXMNCkNJX3VwcGVyID0geV9oYXQgKyBjcml0aWNhbF92YWx1ZSpzZXMNCg0KIyBwbG90DQp0aWJibGUoeF9zZXEsIHlfaGF0LCBDSV9sb3dlciwgQ0lfdXBwZXIsIHggLCB5KSAlPiUgZ2dwbG90KGFlcyh4ID0geF9zZXEsIHkgPSB5X2hhdCkpKw0KICBnZW9tX2xpbmUoY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAic29saWQiKSsNCiAgZ2VvbV9saW5lKGFlcyh4ID0geF9zZXEsIHkgPSBDSV9sb3dlciksIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpKw0KICBnZW9tX2xpbmUoYWVzKHggPSB4X3NlcSwgeSA9IENJX3VwcGVyKSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikrDQogIGdlb21fcG9pbnQoYWVzKHggPSB4LCB5ICA9eSkpKw0KICB4bGFiKCJ4IikrDQogIHlsYWIoIiIpDQpgYGANCg0KDQpJbiBhIGxhc3Qgc3RlcCwgbGV0J3MgY2hlY2sgd2hldGhlciB3ZSBkaWQgdGhpcyBjb3JyZWN0bHkgdXNpbmcgdGhlIGNvdmVyYWdlIHJhdGUsIGZvciB0aGUgZ3JpZCBvZiAkeCQtdmFsdWVzOg0KDQpgYGB7cn0NClIgPSAxMDAwICMgbnVtYmVyIG9mIHJlcGxpY2F0aW9ucw0KbiA9IDEwMCAjIG51bWJlciBvZiBvYnNlcnZhdGlvbnMNCg0KIyBUcnVlIHBhcmFtZXRlcnMgKHVuY2hhbmdlZCkNCmIwID0gLTENCmIxID0gMS8yDQoNCmNyaXRpY2FsX3ZhbHVlID0gcXQoMC45NzUsIGRmID0gbi0yKQ0KDQojICJUcnVlIiAoZXhwZWN0ZWQpIHk6DQp5X2V4cCA9IGIwICtiMSp4X3NlcSAjIHRoaXMgaXMgdGhlICJ0cnVlIiBxdWFudGl0eSB3ZSBhcmUgZXN0aW1hdGluZw0KDQpjb3ZlcmFnZV9tYXRyaXggPSBtYXRyaXgoTkEsIG5jb2wgPSBsZW5ndGgoeF9zZXEpLCBucm93ID0gIFIpDQoNCmZvciAoaSBpbiAxOlIpew0KICB4ID0gcm5vcm0obikNCiAgDQogICMgR2VuZXJhdGUgb3V0Y29tZQ0KICB5ID0gYjAgKyBiMSAqIHggKyBydW5pZihuLC0xLDEpDQogIA0KICAjIEFkZCBjb25zdGFudA0KICBYID0gY2JpbmQocmVwKDEsbikseCkNCiAgDQogICMgTWFudWFsbHkgb2J0YWluIHRoZSBPTFMgY29lZmZpY2llbnRzOg0KICBRID0gc29sdmUoY3Jvc3Nwcm9kKFgpKQ0KICBiZXRhcyA9IFEgJSolIHQoWCkgJSolIHkNCiAgDQogICMgQW5kIGdldCB0aGUgaW5mbHVlbmNlIGZ1bmN0aW9ucyBmb3IgYWxsIGluZGl2aWR1YWxzDQogIElGID0gKFggKiBhcy5udW1lcmljKHkgLSBYICUqJSBiZXRhcykpICUqJSBzb2x2ZShjcm9zc3Byb2QoWCkgLyBuKQ0KICANCiAgeV9oYXQgPSBYX3NlcSAlKiUgYmV0YXMgIyB0aGUgcHJlZGljdGlvbnMNCiAgDQogICMgSW5mbHVlbmNlIGZ1bmN0aW9uIGZvciB0aGUgcHJlZGljdGlvbnM6DQogIElGX3loYXQgPSBYX3NlcSAlKiUgdChJRikNCiAgDQogICMgc3RhbmRhcmQgZXJyb3JzOg0KICBzZXMgPSBzcXJ0KGRpYWcodmFyKHQoSUZfeWhhdCkpKSAvIG4pDQogIA0KICBjb3ZlcmFnZV9tYXRyaXhbaSxdID0gMSooKCh5X2hhdCAtIGNyaXRpY2FsX3ZhbHVlKiBzZXMpIDw9IHlfZXhwKSAmICgoeV9oYXQgKyBjcml0aWNhbF92YWx1ZSogc2VzKSA+IHlfZXhwKSkgIyBmb3IgYWxsIG9ic2VydmF0aW9uczogY2hlY2sgd2hldGhlciB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbA0KICAjIGluY2x1ZGVzIHRoZSB0cnVlIHZhbHVlLCBzYXZlIGFzIDAgb3IgMQ0KfQ0KDQpjb3ZlcmFnZV9yYXRlcyA9IGNvbE1lYW5zKGNvdmVyYWdlX21hdHJpeCkgIyBnZXQgdGhlIGNvdmVyYWdlIHJhdGVzIGF0IGFsbCBkaWZmZXJlbnQgdmFsdWVzIG9mIHgNCg0KIyBwbG90IHRoZSBjb3ZlcmFnZSByYXRlcyBvdmVyIHRoZSBncmlkIG9mIHgNCnRpYmJsZSh4X3NlcSwgY292ZXJhZ2VfcmF0ZXMpICU+JSBnZ3Bsb3QoYWVzKHggPSB4X3NlcSwgeSA9IGNvdmVyYWdlX3JhdGVzKSkrDQogIGdlb21faGxpbmUoeWludGVyY2VwdD0oMC45NSksIGxpbmV0eXBlPSJkYXNoZWQiKSsgDQogIGdlb21fbGluZShjb2xvdXIgPSAicmVkIiwgbGluZXdpZHRoID0gMSkgKw0KICB5bGltKGMoMCwxKSkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9YygwLDEpKSArDQogIGxhYnMoDQogICAgeD0ieCIsDQogICAgeT0iQ292ZXJhZ2UiLA0KICAgIHRpdGxlPSJNb2RlbCBjb3ZlcmFnZSIsDQogICAgY2FwdGlvbj0iQmFzZWQgb24gc2ltdWxhdGVkIGRhdGEiDQogICkgKw0KICB0aGVtZV9idygpDQpgYGANCg0KVGhpcyBsb29rcyBhcyBleHBlY3RlZC4gRm9yIGVhY2ggdmFsdWUgb2YgJFx0aWxkZXt4fSQsIHRoZSA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbCBvbiB0aGUgZml0dGVkIHZhbHVlIGluY2x1ZGVzIHRoZSB0cnVlIHZhbHVlIGluIHJvdWdobHkgOTUlIG9mIHRoZSBjYXNlcy4NCg0KPGJyPg0KDQojIyMgQ29uY2x1c2lvbg0KDQpJbmZsdWVuY2UgRnVuY3Rpb25zIGFyZSB2ZXJ5IHVzZWZ1bC4gV2UgY2FuIHVzZSB0aGVtIHRvIGdldCBzdGFuZGFyZCBlcnJvcnMgZm9yIHRoZSBjb2VmZmljaWVudHMsIHRoZSBmaXR0ZWQgdmFsdWVzLCBvciBldmVuIHRvIHBlcmZvcm0gc3RhdGlzdGljYWwgdGVzdHMgb24gY29tcG9zaXRpb25hbCBwYXJhbWV0ZXJzIGxpa2UgZGlmZmVyZW5jZXMgYmV0d2VlbiBjb2VmZmljaWVudHMgZnJvbSBkaWZmZXJlbnQgcmVncmVzc2lvbnMuIEFkZGl0aW9uYWxseSwgdGhleSBhcmUgaW5mb3JtYXRpdmUgYWJvdXQgdGhlIGluZmx1ZW5jZSBvZiBlYWNoIG9ic2VydmF0aW9ucyBvbiB0aGUgZXN0aW1hdGVzLg0KDQo=