0.1 1. SAT Scores

The working data set is at:

https://raw.githubusercontent.com/pengATwcupa/STA321SP2020/master/sat-scores.txt

The data set has three variables: Verbal score, Math score and gender. 162 observations are included in the data set. This is a simple and almost perfect data.

0.2 2. Fit A Linear Regression Model to the Data

sat = read.table("https://raw.githubusercontent.com/pengATwcupa/STA321SP2020/master/sat-scores.txt", header = TRUE)
# head(sat)
m=lm(Math~Verbal+Sex, data=sat)
summary(m)

Call:
lm(formula = Math ~ Verbal + Sex, data = sat)

Residuals:
     Min       1Q   Median       3Q      Max 
-167.786  -43.444   -2.023   44.512  279.214 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 184.58164   34.06782   5.418 2.19e-07 ***
Verbal        0.68613    0.05513  12.446  < 2e-16 ***
SexM         37.21856   10.93993   3.402 0.000846 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 69.49 on 159 degrees of freedom
Multiple R-squared:  0.5047,    Adjusted R-squared:  0.4985 
F-statistic: 81.02 on 2 and 159 DF,  p-value: < 2.2e-16
par(mfrow=c(2,2))
plot(m)

Residual plots do not indicate any serious violations to the model assumption. We can tell some story about the model based on the summarized statistics.

0.3 3. Bootstrapping Linear Regression - Non-parametric Approaches

Two Types of Bootstrap methods are commonly used in regression modeling.

0.3.1 3.1. Bootstrap Sample and Bootstrap Confidence Intervals

A bootstrap sample is a smaller sample that is “bootstrapped” from a larger sample.

Bootstrapping is a type of re-sampling where large numbers of smaller samples of the same size are repeatedly drawn, with replacement, from a single original sample.

Example 1. Let’s simulate a set of 1000 values from N(10, 8). We want to a bootstrap sample with size 1000 from this data set. I use R do this types of samples in the following

dataset = rnorm(1000, mean =10, sd = 8)
sort(sample(dataset,1000, replace = TRUE))[1:50]
 [1] -11.688487  -7.992661  -7.580345  -7.235443  -7.235443  -7.192148
 [7]  -6.611888  -6.217370  -6.217370  -5.947558  -5.510973  -5.111054
[13]  -5.098377  -5.052878  -5.052878  -4.738285  -4.738285  -4.713458
[19]  -4.651712  -4.530972  -4.256724  -4.180501  -4.071692  -4.012162
[25]  -4.012162  -3.722954  -3.709145  -3.709145  -3.440834  -3.440834
[31]  -3.356528  -3.356528  -3.139717  -2.919020  -2.919020  -2.907259
[37]  -2.907259  -2.894734  -2.894734  -2.616074  -2.616074  -2.615041
[43]  -2.554519  -2.554519  -2.505066  -2.360718  -2.360718  -2.360718
[49]  -2.210809  -2.210809
hist(sample(dataset,1000, replace = TRUE),breaks = 15, main="Freq Dist of Bootstrap Sample")

The histograms indicates that the sampling distribution of the Bootstrap samples is close to a normal distribution.

The bootstrap percentile method is a way to calculate confidence intervals for bootstrapped samples.

With the simple method, a certain percentage (e.g. 5% or 10%) is trimmed from the lower and upper end of the sample statistic (e.g. the mean or standard deviation). Which number you trim depends on the confidence interval you’re looking for. For example, a 90% confidence interval would generate a 100% – 90% = 10% trim (i.e. 5% from both ends). Or, put another (slightly more technical) way, you can get a 90% confidence interval by taking the lower bound 5% and upper bound 95% quantiles of the B replication \(T_1, T_2, \cdots, T_B\). \(B=\) bootstrap replicates.

vec.avg = NULL
B=1000
for(i in 1:B){
   vec.avg[i]  = mean(sample(dataset,1000, replace = TRUE))
}
hist(vec.avg, breaks =15)

0.3.2 3.2. Sampling Cases (Observations)

This is a naive Bootstrap sampling, we take B (usually a large number > 1000) random sets of records (observations) with replacement from the original data set and each subset set has the same size as the original data set. We fit the same regression model to each of the B sets and obtain B regression models. For linear regression model \[ y = \beta_0+\beta_1x_1+ \cdots+\beta_kx_k+\epsilon \] For \(b\)-th bootstrap sample (\(b=1, 2, \cdots, B\)), we have fitted regression line

\[ y = \hat{\beta}_0^{*(b)}+\hat{\beta}_1^{*(b)}x_1+ \cdots+\hat{\beta}_k^{*(b)}x_k \] Then we have a vector of bootstrap estimate for each regression coefficient. That is, \[ \hat{\beta}_i^* = (\hat{\beta}_i^{*(1)}, \hat{\beta}_i^{*(2)}, \cdots, \hat{\beta}_k^{*(B)}) \] for \(i=0, 1, \cdots, k\). The bootstrap sampling distribution of \(\hat{\beta}_i\) can be approximated by the empirical distribution of \(\hat{\beta}_i^*\).

Example We continue to use the above SAT score example. The following R function will the Bootstrap regression by sampling the cases.

sat = read.table("https://raw.githubusercontent.com/pengATwcupa/STA321SP2020/master/sat-scores.txt", header = TRUE)
###
My.lm01=function(dataset, B, histogram=TRUE){
    # dataset = input data set
    # B = number of bootstrap samples
    n0=dim(dataset)[1]
    var.name = c("Intercept",names(dataset)[-1])
    col.num=length(names(dataset))     ## number of variables = number of parameters
    row.num = B                        ## each bootstrap coefficients will be saved in a row
    coef.matrx = matrix(0,ncol=col.num, nrow=B)   # bootstrap coef matrix
    colnames(coef.matrx) = var.name
    for (i in 1:B){
         boot.data = unique(dataset[sample(1:n0, n0, replace=TRUE),])  ## Bootstrap data set
         boot.m = lm(Math~Verbal+Sex, data = boot.data)     ## Bootstrap model
         coef.matrx[i,] = coef(boot.m)            ## save the bootstrap coef to the matrix
    }
    ## original linear regression model
    m0 = lm(Math~Verbal+Sex, data = dataset)
    cof.m0=summary(m0)$coef
    q.025=function(x) quantile(x, 0.025)
    q.975=function(x) quantile(x, 0.975)
    Boot.LCI.025=apply(coef.matrx,2,q.025)
    Boot.UCI.975=apply(coef.matrx,2,q.975)
    new.coef=round(cbind(cof.m0, cbind(BT01.LCI.025=Boot.LCI.025, BT01.UCI.975=Boot.UCI.975)),4)
    if(histogram==TRUE){
       hist(coef.matrx[,1], main=paste("Bootstrap (Case) Distribution \n of Coefficient:",var.name[1]))
       hist(coef.matrx[,2], main=paste("Bootstrap (Case) Distribution \n of Coefficient:",var.name[2]))
       hist(coef.matrx[,3], main=paste("Bootstrap (Case) Distribution \n of Coefficient:",var.name[3]))
    }
    list(boot.coef=coef.matrx, coefficient=new.coef) # two types of outputs vailable from the function
}
My.lm01(dataset=sat, B=1000, histogram=TRUE)$coefficient 

            Estimate Std. Error t value Pr(>|t|) BT01.LCI.025 BT01.UCI.975
(Intercept) 184.5816    34.0678  5.4181    0e+00     122.7337     236.8566
Verbal        0.6861     0.0551 12.4457    0e+00       0.6046       0.7856
SexM         37.2186    10.9399  3.4021    8e-04      20.0164      53.7764

0.3.3 3.3. Re-sampling the Errors (with Fixed Covariates)

The whole idea is to fix all covariates and find the fitted value of \(y\) with corresponding covariates (explanatory variables) and then random assign a bootstrap error take from the residuals.

We This method assumes that the iid residuals but does not assume the normal distribution!

Following are steps of Bootstrap algorithm (assuming taking \(B\) bootstrap samples):

  • Estimate the regression coefficients for the original sample, and calculate the fitted value \(\hat{y}_i\) and residual \(e_i\) for each observation.

  • Select \(b^{th}\) bootstrap samples of the residuals: we will denote these bootstrapped residuals as \(\{e_1^{*(b)},e_2^{*(b)},\cdots, e_n^{*(b)}\}\) . Then, the \(b^{th}\) bootstrapped \(\hat{y}_i^{*(b)}\) is defined to be \(\hat{y}_i+e_i^{*(b)}\), for \(i=1, \cdots, n\) and \(b=1,2, \cdots, B\). The fitted values \(\hat{y}_i=\hat{\beta}_0+\sum_{k=1}^p\hat{\beta}_k x_{ki}\) are obtained from the original regression.

  • The new bootstrap data sets: For illustration purpose, we only write \(b^{th}\) Bootstrap data set in the following matrix

\[ \begin{pmatrix} \hat{y}_1^{*(b)} & x_{11} & x_{21} & \cdots & x_{p1}\\ \hat{y}_2^{*(b)} & x_{12} & x_{22} & \cdots & x_{p2}\\ \vdots & \vdots & \vdots & \vdots & \vdots\\ \hat{y}_n^{*(b)} & x_{1n} & x_{2n} & \cdots & x_{pn}\\ \end{pmatrix} \] where

\[\hat{y}_i^{*(b)}=\hat{\beta}_0+\sum_{k=1}^p\hat{\beta}_k x_{ki} + e_i^{*(b)}\]

  • Fit the the final model to each of the B bootstrap samples and obtain B linear regression models. The fitted Bootstrap linear regression models have the following form

\[ \hat{y}_i^{*(b)}=\hat{\beta}_0^{*(b)}+\sum_{k=1}^p\hat{\beta}_k^{*(b)} x_{ki} \] * For each regression coefficient \(\beta_i\), for \(i=0, 1, \cdots, p\), we obtain \(B\) bootstrap estimates, denoted by \(\hat{\beta}_i^{*(b)}\) for \(b=1, 2, \cdots, B\). The inference about \(\beta_i\) will be based on the corresponding Bootstrap estimates $$

Example (Continued)

My.lm02=function(dataset, B, histogram=TRUE){
    # dataset = input data set
    # B = number of bootstrap samples
    n0=dim(dataset)[1]
    var.name = c("Intercept",names(dataset)[-1])
    col.num=length(names(dataset))     ## number of variables = number of parameters
    row.num = B                        ## each bootstrap coefficients will be saved in a row
    coef.matrx = matrix(0,ncol=col.num, nrow=B)   # bootstrap coef matrix
    colnames(coef.matrx) = var.name
    ##
    m0 = lm(Math~Verbal+Sex, data = dataset)
    original.resid=resid(m0)
    original.fit = fitted(m0)
    Verbal=dataset$Verbal
    Sex=dataset$Sex
    ##
    for (i in 1:B){
         boot.resid = sample(original.resid, n0, replace=TRUE)  ## Bootstrap residuals
         boot.Math = original.fit + boot.resid  ## bootstrap YYY
         boot.m = lm(boot.Math~Verbal+Sex)      ## Bootstrap model
         coef.matrx[i,] = coef(boot.m)            ## save the bootstrap coef to the matrix
    }
    ## original linear regression model
    # m0 = lm(Math~Verbal+Sex, data = dataset)
    cof.m0=summary(m0)$coef
    q.025=function(x) quantile(x, 0.025)
    q.975=function(x) quantile(x, 0.975)
    Boot.LCI.025=apply(coef.matrx,2,q.025)
    Boot.UCI.975=apply(coef.matrx,2,q.975)
    new.coef=round(cbind(cof.m0, cbind(BT02.LCI.025=Boot.LCI.025, BT02.UCI.975=Boot.UCI.975)),4)
    if(histogram==TRUE){
       hist(coef.matrx[,1], main=paste("Bootstrap (Resid) Distribution \n of Coefficient:",var.name[1]))
       hist(coef.matrx[,2], main=paste("Bootstrap (Resid) Distribution \n of Coefficient:",var.name[2]))
       hist(coef.matrx[,3], main=paste("Bootstrap (Resid) Distribution \n of Coefficient:",var.name[3]))
    }
    list(boot.coef=coef.matrx, coefficient=new.coef) # two types of outputs vailable from the function
}
My.lm02(dataset=sat, B=1000, histogram=TRUE)$coefficient 

            Estimate Std. Error t value Pr(>|t|) BT02.LCI.025 BT02.UCI.975
(Intercept) 184.5816    34.0678  5.4181    0e+00     120.0280     254.1572
Verbal        0.6861     0.0551 12.4457    0e+00       0.5715       0.7895
SexM         37.2186    10.9399  3.4021    8e-04      16.5095      58.2278

0.3.4 3.4. Parametric Bootstrap sampling

This bootstrap is similar to Monte Carlo simulation. We assume the residuals are normally distributed with a constant variance. Based on this assumption, we estimated the common variance from the residuals and then generate residuals from the normal distribution and assign the generated values to the fitted values based on the original data set.

The steps of this Bootstrap regression is almost identical to the above method.

We only modify the above code to get a parametric bootstrap regression model.

My.lm03=function(dataset, B, histogram=TRUE){
    # dataset = input data set
    # B = number of bootstrap samples
    n0=dim(dataset)[1]
    var.name = c("Intercept",names(dataset)[-1])
    col.num=length(names(dataset))     ## number of variables = number of parameters
    row.num = B                        ## each bootstrap coefficients will be saved in a row
    coef.matrx = matrix(0,ncol=col.num, nrow=B)   # bootstrap coef matrix
    colnames(coef.matrx) = var.name
    ##
    m0 = lm(Math~Verbal+Sex, data = dataset)
    original.resid=resid(m0)
    sig = sd(original.resid)
    original.fit = fitted(m0)
    Verbal=dataset$Verbal
    Sex=dataset$Sex
    ##
    for (i in 1:B){
         boot.resid = rnorm(n0, mean=0, sd = sig)    ## Parametric Bootstrap residuals
         boot.Math = original.fit + boot.resid       ## bootstrap YYY
         boot.m = lm(boot.Math~Verbal+Sex)           ## Bootstrap model
         coef.matrx[i,] = coef(boot.m)               ## save the bootstrap coef to the matrix
    }
    ## original linear regression model
    # m0 = lm(Math~Verbal+Sex, data = dataset)
    cof.m0=summary(m0)$coef
    q.025=function(x) quantile(x, 0.025)
    q.975=function(x) quantile(x, 0.975)
    Boot.LCI.025=apply(coef.matrx,2,q.025)
    Boot.UCI.975=apply(coef.matrx,2,q.975)
    new.coef=round(cbind(cof.m0, cbind(BT03.LCI.025=Boot.LCI.025, BT03.UCI.975=Boot.UCI.975)),4)
    if(histogram==TRUE){
       hist(coef.matrx[,1], main=paste("Bootstrap (parametric) Distribution \n of Coefficient:",var.name[1]))
       hist(coef.matrx[,2], main=paste("Bootstrap (parametric) Distribution \n of Coefficient:",var.name[2]))
       hist(coef.matrx[,3], main=paste("Bootstrap (parametric) Distribution \n of Coefficient:",var.name[3]))
    }
    list(boot.coef=coef.matrx, coefficient=new.coef) # two types of outputs vailable from the function
}
My.lm03(dataset=sat, B=1000, histogram=TRUE)$coefficient 

            Estimate Std. Error t value Pr(>|t|) BT03.LCI.025 BT03.UCI.975
(Intercept) 184.5816    34.0678  5.4181    0e+00     119.8898     249.3624
Verbal        0.6861     0.0551 12.4457    0e+00       0.5809       0.7912
SexM         37.2186    10.9399  3.4021    8e-04      15.6928      59.3025
LS0tDQp0aXRsZTogIkZyb20gU3RhdGlzdGljcyBNb2RlbHMgdG8gTWFjaGluZSBMZWFybmluZyBBbGdvcml0aG1zIg0KYXV0aG9yOiAiQ2hlbmcgUGVuZyINCmRhdGU6ICIgU1RBNTExIEZvdW5kYXRpb25zIG9mIERhdGEgU2NpZW5jZSINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIGZpZ193aWR0aDogNg0KICAgIGZpZ19oZWlnaHQ6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2NfY29sbGFwc2VkOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgICB0aGVtZTogbHVtZW4NCiAgd29yZF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAga2VlcF9tZDogeWVzDQogIHBkZl9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICBmaWdfd2lkdGg6IDUNCiAgICBmaWdfaGVpZ2h0OiA0DQotLS0NCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoNCmRpdiNUT0MgbGkgew0KICAgIGxpc3Qtc3R5bGU6bm9uZTsNCiAgICBiYWNrZ3JvdW5kLWltYWdlOm5vbmU7DQogICAgYmFja2dyb3VuZC1yZXBlYXQ6bm9uZTsNCiAgICBiYWNrZ3JvdW5kLXBvc2l0aW9uOjA7DQp9DQoNCmgxLnRpdGxlIHsNCiAgZm9udC1zaXplOiAyMHB4Ow0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDQuYXV0aG9yIHsgLyogSGVhZGVyIDQgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgICBmb250LXNpemU6IDE4cHg7DQogIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDQuZGF0ZSB7IC8qIEhlYWRlciA0IC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDEgeyAvKiBIZWFkZXIgMyAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICAgIGZvbnQtc2l6ZTogMjJweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoMiB7IC8qIEhlYWRlciAzIC0gYW5kIHRoZSBhdXRob3IgYW5kIGRhdGEgaGVhZGVycyB1c2UgdGhpcyB0b28gICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmgzIHsgLyogSGVhZGVyIDMgLSBhbmQgdGhlIGF1dGhvciBhbmQgZGF0YSBoZWFkZXJzIHVzZSB0aGlzIHRvbyAgKi8NCiAgICBmb250LXNpemU6IDE1cHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDQgeyAvKiBIZWFkZXIgNCAtIGFuZCB0aGUgYXV0aG9yIGFuZCBkYXRhIGhlYWRlcnMgdXNlIHRoaXMgdG9vICAqLw0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KPC9zdHlsZT4NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQojIGNvZGUgY2h1bmsgc3BlY2lmaWVzIHdoZXRoZXIgdGhlIFIgY29kZSwgd2FybmluZ3MsIGFuZCBvdXRwdXQgDQojIHdpbGwgYmUgaW5jbHVkZWQgaW4gdGhlIG91dHB1dCBmaWxlcy4NCg0KaWYgKCFyZXF1aXJlKCJnZ3Bsb3QyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KICAgbGlicmFyeShnZ3Bsb3QyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJJU0xSIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoIklTTFIiKQ0KICAgbGlicmFyeShJU0xSKQ0KfQ0KaWYgKCFyZXF1aXJlKCJrbml0ciIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJrbml0ciIpDQogICBsaWJyYXJ5KGtuaXRyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJwYW5kZXIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygicGFuZGVyIikNCiAgIGxpYnJhcnkocGFuZGVyKQ0KfQ0KDQojIFRoZSBmb2xsb3dpbmcgUiBzb3VyY2UgY29kZSB3aWxsIGJlIHVzZSB0byBwbG90IHRoZSBwYXRoIHBsb3QNCiMgb2YgbmV1cmFsIG5ldHdvcmsgbW9kZWwgd2l0aCBlc3RpbWF0ZWQgd2VpZ2h0cyBmcm9tIHRoZSBkYXRhLg0KI3NvdXJjZV91cmwoImh0dHBzOi8vcGVuZ2RzY2kuZ2l0aHViLmlvL1NUQTU1MS9wbG90TmV1cmFsbmV0UmZ1bi50eHQiKQ0KIyBrbml0cjo6b3B0c19rbml0JHNldChyb290LmRpciA9ICJDOlxcU1RBNTUxXFx3MDgiKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsICAgDQogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0ID0gVFJVRSwgICANCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgY29tbWVudD0gTkEpDQpgYGANCg0KDQoNCiMjIDEuIFNBVCBTY29yZXMNCiBUaGUgd29ya2luZyBkYXRhIHNldCBpcyBhdDoNCiANCmh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9wZW5nQVR3Y3VwYS9TVEEzMjFTUDIwMjAvbWFzdGVyL3NhdC1zY29yZXMudHh0DQoNClRoZSBkYXRhIHNldCBoYXMgdGhyZWUgdmFyaWFibGVzOiBWZXJiYWwgc2NvcmUsIE1hdGggc2NvcmUgYW5kIGdlbmRlci4gMTYyIG9ic2VydmF0aW9ucyBhcmUgaW5jbHVkZWQgaW4gdGhlIGRhdGEgc2V0LiBUaGlzIGlzIGEgc2ltcGxlIGFuZCBhbG1vc3QgcGVyZmVjdCBkYXRhLiANCg0KIyMgMi4gRml0IEEgTGluZWFyIFJlZ3Jlc3Npb24gTW9kZWwgdG8gdGhlIERhdGENCg0KYGBge3IgbG9hZC1kYXRhfQ0Kc2F0ID0gcmVhZC50YWJsZSgiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3BlbmdBVHdjdXBhL1NUQTMyMVNQMjAyMC9tYXN0ZXIvc2F0LXNjb3Jlcy50eHQiLCBoZWFkZXIgPSBUUlVFKQ0KIyBoZWFkKHNhdCkNCm09bG0oTWF0aH5WZXJiYWwrU2V4LCBkYXRhPXNhdCkNCnN1bW1hcnkobSkNCmBgYA0KDQpgYGB7ciByZXNpZHVhbC1wbG90c30NCnBhcihtZnJvdz1jKDIsMikpDQpwbG90KG0pDQpgYGANCg0KUmVzaWR1YWwgcGxvdHMgZG8gbm90IGluZGljYXRlIGFueSBzZXJpb3VzIHZpb2xhdGlvbnMgdG8gdGhlIG1vZGVsIGFzc3VtcHRpb24uIFdlIGNhbiB0ZWxsIHNvbWUgc3RvcnkgYWJvdXQgdGhlIG1vZGVsIGJhc2VkIG9uIHRoZSBzdW1tYXJpemVkIHN0YXRpc3RpY3MuDQoNCg0KIyMgMy4gQm9vdHN0cmFwcGluZyBMaW5lYXIgUmVncmVzc2lvbiAtIE5vbi1wYXJhbWV0cmljIEFwcHJvYWNoZXMNCg0KVHdvIFR5cGVzIG9mIEJvb3RzdHJhcCBtZXRob2RzIGFyZSBjb21tb25seSB1c2VkIGluIHJlZ3Jlc3Npb24gbW9kZWxpbmcuDQoNCiMjIyAzLjEuIEJvb3RzdHJhcCBTYW1wbGUgYW5kIEJvb3RzdHJhcCBDb25maWRlbmNlIEludGVydmFscw0KDQpBIGJvb3RzdHJhcCBzYW1wbGUgaXMgYSBzbWFsbGVyIHNhbXBsZSB0aGF0IGlzIOKAnGJvb3RzdHJhcHBlZOKAnSBmcm9tIGEgbGFyZ2VyIHNhbXBsZS4NCg0KQm9vdHN0cmFwcGluZyBpcyBhIHR5cGUgb2YgcmUtc2FtcGxpbmcgd2hlcmUgbGFyZ2UgbnVtYmVycyBvZiBzbWFsbGVyIHNhbXBsZXMgb2YgdGhlIHNhbWUgc2l6ZSBhcmUgcmVwZWF0ZWRseSBkcmF3biwgd2l0aCByZXBsYWNlbWVudCwgZnJvbSBhIHNpbmdsZSBvcmlnaW5hbCBzYW1wbGUuDQoNCioqRXhhbXBsZSAxLioqIExldCdzIHNpbXVsYXRlIGEgc2V0IG9mIDEwMDAgdmFsdWVzIGZyb20gTigxMCwgOCkuIFdlIHdhbnQgdG8gYSBib290c3RyYXAgc2FtcGxlIHdpdGggc2l6ZSAxMDAwIGZyb20gdGhpcyBkYXRhIHNldC4gSSB1c2UgUiBkbyB0aGlzIHR5cGVzIG9mIHNhbXBsZXMgaW4gdGhlIGZvbGxvd2luZw0KDQpgYGB7ciByZXNhbXBsaW5nLWJvb3RzdHJhcH0NCmRhdGFzZXQgPSBybm9ybSgxMDAwLCBtZWFuID0xMCwgc2QgPSA4KQ0Kc29ydChzYW1wbGUoZGF0YXNldCwxMDAwLCByZXBsYWNlID0gVFJVRSkpWzE6NTBdDQpgYGANCg0KYGBge3IgaGlzLWJvb3Qtc2FtcGxlfQ0KaGlzdChzYW1wbGUoZGF0YXNldCwxMDAwLCByZXBsYWNlID0gVFJVRSksYnJlYWtzID0gMTUsIG1haW49IkZyZXEgRGlzdCBvZiBCb290c3RyYXAgU2FtcGxlIikNCmBgYA0KDQpUaGUgaGlzdG9ncmFtcyBpbmRpY2F0ZXMgdGhhdCB0aGUgc2FtcGxpbmcgZGlzdHJpYnV0aW9uIG9mIHRoZSBCb290c3RyYXAgc2FtcGxlcyBpcyBjbG9zZSB0byBhIG5vcm1hbCBkaXN0cmlidXRpb24uDQoNClRoZSBib290c3RyYXAgcGVyY2VudGlsZSBtZXRob2QgaXMgYSB3YXkgdG8gY2FsY3VsYXRlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIGZvciBib290c3RyYXBwZWQgc2FtcGxlcy4NCg0KV2l0aCB0aGUgc2ltcGxlIG1ldGhvZCwgYSBjZXJ0YWluIHBlcmNlbnRhZ2UgKGUuZy4gNSUgb3IgMTAlKSBpcyB0cmltbWVkIGZyb20gdGhlIGxvd2VyIGFuZCB1cHBlciBlbmQgb2YgdGhlIHNhbXBsZSBzdGF0aXN0aWMgKGUuZy4gdGhlIG1lYW4gb3Igc3RhbmRhcmQgZGV2aWF0aW9uKS4gV2hpY2ggbnVtYmVyIHlvdSB0cmltIGRlcGVuZHMgb24gdGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgeW914oCZcmUgbG9va2luZyBmb3IuIEZvciBleGFtcGxlLCBhIDkwJSBjb25maWRlbmNlIGludGVydmFsIHdvdWxkIGdlbmVyYXRlIGEgMTAwJSDigJMgOTAlID0gMTAlIHRyaW0gKGkuZS4gNSUgZnJvbSBib3RoIGVuZHMpLiBPciwgcHV0IGFub3RoZXIgKHNsaWdodGx5IG1vcmUgdGVjaG5pY2FsKSB3YXksIHlvdSBjYW4gZ2V0IGEgOTAlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgYnkgdGFraW5nIHRoZSBsb3dlciBib3VuZCA1JSBhbmQgdXBwZXIgYm91bmQgOTUlIHF1YW50aWxlcyBvZiB0aGUgQiByZXBsaWNhdGlvbiAkVF8xLCBUXzIsIFxjZG90cywgVF9CJC4gJEI9JCBib290c3RyYXAgcmVwbGljYXRlcy4NCg0KDQpgYGB7ciBjaX0NCnZlYy5hdmcgPSBOVUxMDQpCPTEwMDANCmZvcihpIGluIDE6Qil7DQogICB2ZWMuYXZnW2ldICA9IG1lYW4oc2FtcGxlKGRhdGFzZXQsMTAwMCwgcmVwbGFjZSA9IFRSVUUpKQ0KfQ0KaGlzdCh2ZWMuYXZnLCBicmVha3MgPTE1KQ0KYGBgDQoNCiMjIyAzLjIuIFNhbXBsaW5nIENhc2VzIChPYnNlcnZhdGlvbnMpDQoNClRoaXMgaXMgYSBuYWl2ZSBCb290c3RyYXAgc2FtcGxpbmcsIHdlIHRha2UgQiAodXN1YWxseSBhIGxhcmdlIG51bWJlciA+IDEwMDApIHJhbmRvbSBzZXRzIG9mIHJlY29yZHMgKG9ic2VydmF0aW9ucykgd2l0aCByZXBsYWNlbWVudCBmcm9tIHRoZSBvcmlnaW5hbCBkYXRhIHNldCBhbmQgZWFjaCBzdWJzZXQgc2V0IGhhcyB0aGUgc2FtZSBzaXplIGFzIHRoZSBvcmlnaW5hbCBkYXRhIHNldC4gV2UgZml0IHRoZSBzYW1lIHJlZ3Jlc3Npb24gbW9kZWwgdG8gZWFjaCBvZiB0aGUgQiBzZXRzIGFuZCBvYnRhaW4gQiByZWdyZXNzaW9uIG1vZGVscy4gRm9yIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIA0KJCQNCnkgPSBcYmV0YV8wK1xiZXRhXzF4XzErIFxjZG90cytcYmV0YV9reF9rK1xlcHNpbG9uDQokJA0KRm9yICRiJC10aCBib290c3RyYXAgc2FtcGxlICgkYj0xLCAyLCBcY2RvdHMsIEIkKSwgd2UgaGF2ZSBmaXR0ZWQgcmVncmVzc2lvbiBsaW5lDQoNCiQkDQp5ID0gXGhhdHtcYmV0YX1fMF57KihiKX0rXGhhdHtcYmV0YX1fMV57KihiKX14XzErIFxjZG90cytcaGF0e1xiZXRhfV9rXnsqKGIpfXhfaw0KJCQNClRoZW4gd2UgaGF2ZSBhIHZlY3RvciBvZiBib290c3RyYXAgZXN0aW1hdGUgZm9yIGVhY2ggcmVncmVzc2lvbiBjb2VmZmljaWVudC4gVGhhdCBpcywNCiQkDQpcaGF0e1xiZXRhfV9pXiogPSAoXGhhdHtcYmV0YX1faV57KigxKX0sIFxoYXR7XGJldGF9X2leeyooMil9LCBcY2RvdHMsIFxoYXR7XGJldGF9X2teeyooQil9KQ0KJCQNCmZvciAkaT0wLCAxLCBcY2RvdHMsIGskLiBUaGUgYm9vdHN0cmFwIHNhbXBsaW5nIGRpc3RyaWJ1dGlvbiBvZiAkXGhhdHtcYmV0YX1faSQgY2FuIGJlIGFwcHJveGltYXRlZCBieSB0aGUgZW1waXJpY2FsIGRpc3RyaWJ1dGlvbiBvZiAkXGhhdHtcYmV0YX1faV4qJC4NCg0KKipFeGFtcGxlICoqIFdlIGNvbnRpbnVlIHRvIHVzZSB0aGUgYWJvdmUgU0FUIHNjb3JlIGV4YW1wbGUuIFRoZSBmb2xsb3dpbmcgUiBmdW5jdGlvbiB3aWxsIHRoZSBCb290c3RyYXAgcmVncmVzc2lvbiBieSBzYW1wbGluZyB0aGUgY2FzZXMuDQoNCmBgYHtyIGJvb3QtY2FzZX0NCnNhdCA9IHJlYWQudGFibGUoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9wZW5nQVR3Y3VwYS9TVEEzMjFTUDIwMjAvbWFzdGVyL3NhdC1zY29yZXMudHh0IiwgaGVhZGVyID0gVFJVRSkNCiMjIw0KTXkubG0wMT1mdW5jdGlvbihkYXRhc2V0LCBCLCBoaXN0b2dyYW09VFJVRSl7DQogICAgIyBkYXRhc2V0ID0gaW5wdXQgZGF0YSBzZXQNCiAgICAjIEIgPSBudW1iZXIgb2YgYm9vdHN0cmFwIHNhbXBsZXMNCiAgICBuMD1kaW0oZGF0YXNldClbMV0NCiAgICB2YXIubmFtZSA9IGMoIkludGVyY2VwdCIsbmFtZXMoZGF0YXNldClbLTFdKQ0KICAgIGNvbC5udW09bGVuZ3RoKG5hbWVzKGRhdGFzZXQpKSAgICAgIyMgbnVtYmVyIG9mIHZhcmlhYmxlcyA9IG51bWJlciBvZiBwYXJhbWV0ZXJzDQogICAgcm93Lm51bSA9IEIgICAgICAgICAgICAgICAgICAgICAgICAjIyBlYWNoIGJvb3RzdHJhcCBjb2VmZmljaWVudHMgd2lsbCBiZSBzYXZlZCBpbiBhIHJvdw0KICAgIGNvZWYubWF0cnggPSBtYXRyaXgoMCxuY29sPWNvbC5udW0sIG5yb3c9QikgICAjIGJvb3RzdHJhcCBjb2VmIG1hdHJpeA0KICAgIGNvbG5hbWVzKGNvZWYubWF0cngpID0gdmFyLm5hbWUNCiAgICBmb3IgKGkgaW4gMTpCKXsNCiAgICAgICAgIGJvb3QuZGF0YSA9IHVuaXF1ZShkYXRhc2V0W3NhbXBsZSgxOm4wLCBuMCwgcmVwbGFjZT1UUlVFKSxdKSAgIyMgQm9vdHN0cmFwIGRhdGEgc2V0DQogICAgICAgICBib290Lm0gPSBsbShNYXRoflZlcmJhbCtTZXgsIGRhdGEgPSBib290LmRhdGEpICAgICAjIyBCb290c3RyYXAgbW9kZWwNCiAgICAgICAgIGNvZWYubWF0cnhbaSxdID0gY29lZihib290Lm0pICAgICAgICAgICAgIyMgc2F2ZSB0aGUgYm9vdHN0cmFwIGNvZWYgdG8gdGhlIG1hdHJpeA0KICAgIH0NCiAgICAjIyBvcmlnaW5hbCBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbA0KICAgIG0wID0gbG0oTWF0aH5WZXJiYWwrU2V4LCBkYXRhID0gZGF0YXNldCkNCiAgICBjb2YubTA9c3VtbWFyeShtMCkkY29lZg0KICAgIHEuMDI1PWZ1bmN0aW9uKHgpIHF1YW50aWxlKHgsIDAuMDI1KQ0KICAgIHEuOTc1PWZ1bmN0aW9uKHgpIHF1YW50aWxlKHgsIDAuOTc1KQ0KICAgIEJvb3QuTENJLjAyNT1hcHBseShjb2VmLm1hdHJ4LDIscS4wMjUpDQogICAgQm9vdC5VQ0kuOTc1PWFwcGx5KGNvZWYubWF0cngsMixxLjk3NSkNCiAgICBuZXcuY29lZj1yb3VuZChjYmluZChjb2YubTAsIGNiaW5kKEJUMDEuTENJLjAyNT1Cb290LkxDSS4wMjUsIEJUMDEuVUNJLjk3NT1Cb290LlVDSS45NzUpKSw0KQ0KICAgIGlmKGhpc3RvZ3JhbT09VFJVRSl7DQogICAgICAgaGlzdChjb2VmLm1hdHJ4WywxXSwgbWFpbj1wYXN0ZSgiQm9vdHN0cmFwIChDYXNlKSBEaXN0cmlidXRpb24gXG4gb2YgQ29lZmZpY2llbnQ6Iix2YXIubmFtZVsxXSkpDQogICAgICAgaGlzdChjb2VmLm1hdHJ4WywyXSwgbWFpbj1wYXN0ZSgiQm9vdHN0cmFwIChDYXNlKSBEaXN0cmlidXRpb24gXG4gb2YgQ29lZmZpY2llbnQ6Iix2YXIubmFtZVsyXSkpDQogICAgICAgaGlzdChjb2VmLm1hdHJ4WywzXSwgbWFpbj1wYXN0ZSgiQm9vdHN0cmFwIChDYXNlKSBEaXN0cmlidXRpb24gXG4gb2YgQ29lZmZpY2llbnQ6Iix2YXIubmFtZVszXSkpDQogICAgfQ0KICAgIGxpc3QoYm9vdC5jb2VmPWNvZWYubWF0cngsIGNvZWZmaWNpZW50PW5ldy5jb2VmKSAjIHR3byB0eXBlcyBvZiBvdXRwdXRzIHZhaWxhYmxlIGZyb20gdGhlIGZ1bmN0aW9uDQp9DQpNeS5sbTAxKGRhdGFzZXQ9c2F0LCBCPTEwMDAsIGhpc3RvZ3JhbT1UUlVFKSRjb2VmZmljaWVudCANCmBgYA0KDQojIyMgMy4zLiBSZS1zYW1wbGluZyB0aGUgRXJyb3JzICh3aXRoIEZpeGVkIENvdmFyaWF0ZXMpDQoNClRoZSB3aG9sZSBpZGVhIGlzIHRvIGZpeCBhbGwgY292YXJpYXRlcyBhbmQgZmluZCB0aGUgZml0dGVkIHZhbHVlIG9mICR5JCB3aXRoIGNvcnJlc3BvbmRpbmcgY292YXJpYXRlcyAoZXhwbGFuYXRvcnkgdmFyaWFibGVzKSBhbmQgdGhlbiByYW5kb20gYXNzaWduIGEgYm9vdHN0cmFwIGVycm9yIHRha2UgZnJvbSB0aGUgcmVzaWR1YWxzLg0KDQoqKldlIFRoaXMgbWV0aG9kIGFzc3VtZXMgdGhhdCB0aGUgaWlkIHJlc2lkdWFscyBidXQgZG9lcyBub3QgYXNzdW1lIHRoZSBub3JtYWwgZGlzdHJpYnV0aW9uISoqDQoNCkZvbGxvd2luZyBhcmUgc3RlcHMgb2YgQm9vdHN0cmFwIGFsZ29yaXRobSAoYXNzdW1pbmcgdGFraW5nICRCJCBib290c3RyYXAgc2FtcGxlcyk6DQoNCiogRXN0aW1hdGUgdGhlIHJlZ3Jlc3Npb24gY29lZmZpY2llbnRzIGZvciB0aGUgb3JpZ2luYWwgc2FtcGxlLCBhbmQgY2FsY3VsYXRlIHRoZSBmaXR0ZWQgdmFsdWUgICRcaGF0e3l9X2kkICBhbmQgcmVzaWR1YWwgICRlX2kkICBmb3IgZWFjaCBvYnNlcnZhdGlvbi4NCg0KKiBTZWxlY3QgJGJee3RofSQgYm9vdHN0cmFwIHNhbXBsZXMgb2YgdGhlIHJlc2lkdWFsczogd2Ugd2lsbCBkZW5vdGUgdGhlc2UgYm9vdHN0cmFwcGVkIHJlc2lkdWFscyBhcyAgJFx7ZV8xXnsqKGIpfSxlXzJeeyooYil9LFxjZG90cywgZV9uXnsqKGIpfVx9JCAuIFRoZW4sIHRoZSAkYl57dGh9JCBib290c3RyYXBwZWQgJFxoYXR7eX1faV57KihiKX0kIGlzIGRlZmluZWQgdG8gYmUgICRcaGF0e3l9X2krZV9pXnsqKGIpfSQsIGZvciAkaT0xLCBcY2RvdHMsIG4kIGFuZCAkYj0xLDIsIFxjZG90cywgQiQuICBUaGUgZml0dGVkIHZhbHVlcyAgJFxoYXR7eX1faT1caGF0e1xiZXRhfV8wK1xzdW1fe2s9MX1ecFxoYXR7XGJldGF9X2sgeF97a2l9JCAgYXJlIG9idGFpbmVkIGZyb20gdGhlIG9yaWdpbmFsIHJlZ3Jlc3Npb24uDQoNCiogVGhlIG5ldyBib290c3RyYXAgZGF0YSBzZXRzOiBGb3IgaWxsdXN0cmF0aW9uIHB1cnBvc2UsIHdlIG9ubHkgd3JpdGUgJGJee3RofSQgQm9vdHN0cmFwIGRhdGEgc2V0IGluIHRoZSBmb2xsb3dpbmcgbWF0cml4DQoNCiQkDQpcYmVnaW57cG1hdHJpeH0NClxoYXR7eX1fMV57KihiKX0gJiB4X3sxMX0gJiB4X3syMX0gJiBcY2RvdHMgJiB4X3twMX1cXA0KXGhhdHt5fV8yXnsqKGIpfSAmIHhfezEyfSAmIHhfezIyfSAmIFxjZG90cyAmIHhfe3AyfVxcDQogICBcdmRvdHMgJiBcdmRvdHMgJiBcdmRvdHMgJiBcdmRvdHMgJiBcdmRvdHNcXA0KXGhhdHt5fV9uXnsqKGIpfSAmIHhfezFufSAmIHhfezJufSAmIFxjZG90cyAmIHhfe3BufVxcDQpcZW5ke3BtYXRyaXh9DQokJA0Kd2hlcmUgDQoNCiQkXGhhdHt5fV9pXnsqKGIpfT1caGF0e1xiZXRhfV8wK1xzdW1fe2s9MX1ecFxoYXR7XGJldGF9X2sgeF97a2l9ICsgZV9pXnsqKGIpfSQkDQoNCiogRml0IHRoZSB0aGUgZmluYWwgbW9kZWwgdG8gZWFjaCBvZiB0aGUgQiBib290c3RyYXAgc2FtcGxlcyBhbmQgb2J0YWluIEIgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWxzLiBUaGUgZml0dGVkIEJvb3RzdHJhcCBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbHMgaGF2ZSB0aGUgZm9sbG93aW5nIGZvcm0NCg0KJCQNClxoYXR7eX1faV57KihiKX09XGhhdHtcYmV0YX1fMF57KihiKX0rXHN1bV97az0xfV5wXGhhdHtcYmV0YX1fa157KihiKX0geF97a2l9DQokJA0KKiBGb3IgZWFjaCByZWdyZXNzaW9uIGNvZWZmaWNpZW50ICRcYmV0YV9pJCwgZm9yICRpPTAsIDEsIFxjZG90cywgcCQsIHdlIG9idGFpbiAkQiQgYm9vdHN0cmFwIGVzdGltYXRlcywgZGVub3RlZCBieSAkXGhhdHtcYmV0YX1faV57KihiKX0kIGZvciAkYj0xLCAyLCBcY2RvdHMsIEIkLiBUaGUgaW5mZXJlbmNlIGFib3V0ICRcYmV0YV9pJCB3aWxsIGJlIGJhc2VkIG9uIHRoZSBjb3JyZXNwb25kaW5nIEJvb3RzdHJhcCBlc3RpbWF0ZXMgJCQNCg0KKipFeGFtcGxlKiogKENvbnRpbnVlZCkNCg0KYGBge3IgYm9vdDAyfQ0KTXkubG0wMj1mdW5jdGlvbihkYXRhc2V0LCBCLCBoaXN0b2dyYW09VFJVRSl7DQogICAgIyBkYXRhc2V0ID0gaW5wdXQgZGF0YSBzZXQNCiAgICAjIEIgPSBudW1iZXIgb2YgYm9vdHN0cmFwIHNhbXBsZXMNCiAgICBuMD1kaW0oZGF0YXNldClbMV0NCiAgICB2YXIubmFtZSA9IGMoIkludGVyY2VwdCIsbmFtZXMoZGF0YXNldClbLTFdKQ0KICAgIGNvbC5udW09bGVuZ3RoKG5hbWVzKGRhdGFzZXQpKSAgICAgIyMgbnVtYmVyIG9mIHZhcmlhYmxlcyA9IG51bWJlciBvZiBwYXJhbWV0ZXJzDQogICAgcm93Lm51bSA9IEIgICAgICAgICAgICAgICAgICAgICAgICAjIyBlYWNoIGJvb3RzdHJhcCBjb2VmZmljaWVudHMgd2lsbCBiZSBzYXZlZCBpbiBhIHJvdw0KICAgIGNvZWYubWF0cnggPSBtYXRyaXgoMCxuY29sPWNvbC5udW0sIG5yb3c9QikgICAjIGJvb3RzdHJhcCBjb2VmIG1hdHJpeA0KICAgIGNvbG5hbWVzKGNvZWYubWF0cngpID0gdmFyLm5hbWUNCiAgICAjIw0KICAgIG0wID0gbG0oTWF0aH5WZXJiYWwrU2V4LCBkYXRhID0gZGF0YXNldCkNCiAgICBvcmlnaW5hbC5yZXNpZD1yZXNpZChtMCkNCiAgICBvcmlnaW5hbC5maXQgPSBmaXR0ZWQobTApDQogICAgVmVyYmFsPWRhdGFzZXQkVmVyYmFsDQogICAgU2V4PWRhdGFzZXQkU2V4DQogICAgIyMNCiAgICBmb3IgKGkgaW4gMTpCKXsNCiAgICAgICAgIGJvb3QucmVzaWQgPSBzYW1wbGUob3JpZ2luYWwucmVzaWQsIG4wLCByZXBsYWNlPVRSVUUpICAjIyBCb290c3RyYXAgcmVzaWR1YWxzDQogICAgICAgICBib290Lk1hdGggPSBvcmlnaW5hbC5maXQgKyBib290LnJlc2lkICAjIyBib290c3RyYXAgWVlZDQogICAgICAgICBib290Lm0gPSBsbShib290Lk1hdGh+VmVyYmFsK1NleCkgICAgICAjIyBCb290c3RyYXAgbW9kZWwNCiAgICAgICAgIGNvZWYubWF0cnhbaSxdID0gY29lZihib290Lm0pICAgICAgICAgICAgIyMgc2F2ZSB0aGUgYm9vdHN0cmFwIGNvZWYgdG8gdGhlIG1hdHJpeA0KICAgIH0NCiAgICAjIyBvcmlnaW5hbCBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbA0KICAgICMgbTAgPSBsbShNYXRoflZlcmJhbCtTZXgsIGRhdGEgPSBkYXRhc2V0KQ0KICAgIGNvZi5tMD1zdW1tYXJ5KG0wKSRjb2VmDQogICAgcS4wMjU9ZnVuY3Rpb24oeCkgcXVhbnRpbGUoeCwgMC4wMjUpDQogICAgcS45NzU9ZnVuY3Rpb24oeCkgcXVhbnRpbGUoeCwgMC45NzUpDQogICAgQm9vdC5MQ0kuMDI1PWFwcGx5KGNvZWYubWF0cngsMixxLjAyNSkNCiAgICBCb290LlVDSS45NzU9YXBwbHkoY29lZi5tYXRyeCwyLHEuOTc1KQ0KICAgIG5ldy5jb2VmPXJvdW5kKGNiaW5kKGNvZi5tMCwgY2JpbmQoQlQwMi5MQ0kuMDI1PUJvb3QuTENJLjAyNSwgQlQwMi5VQ0kuOTc1PUJvb3QuVUNJLjk3NSkpLDQpDQogICAgaWYoaGlzdG9ncmFtPT1UUlVFKXsNCiAgICAgICBoaXN0KGNvZWYubWF0cnhbLDFdLCBtYWluPXBhc3RlKCJCb290c3RyYXAgKFJlc2lkKSBEaXN0cmlidXRpb24gXG4gb2YgQ29lZmZpY2llbnQ6Iix2YXIubmFtZVsxXSkpDQogICAgICAgaGlzdChjb2VmLm1hdHJ4WywyXSwgbWFpbj1wYXN0ZSgiQm9vdHN0cmFwIChSZXNpZCkgRGlzdHJpYnV0aW9uIFxuIG9mIENvZWZmaWNpZW50OiIsdmFyLm5hbWVbMl0pKQ0KICAgICAgIGhpc3QoY29lZi5tYXRyeFssM10sIG1haW49cGFzdGUoIkJvb3RzdHJhcCAoUmVzaWQpIERpc3RyaWJ1dGlvbiBcbiBvZiBDb2VmZmljaWVudDoiLHZhci5uYW1lWzNdKSkNCiAgICB9DQogICAgbGlzdChib290LmNvZWY9Y29lZi5tYXRyeCwgY29lZmZpY2llbnQ9bmV3LmNvZWYpICMgdHdvIHR5cGVzIG9mIG91dHB1dHMgdmFpbGFibGUgZnJvbSB0aGUgZnVuY3Rpb24NCn0NCk15LmxtMDIoZGF0YXNldD1zYXQsIEI9MTAwMCwgaGlzdG9ncmFtPVRSVUUpJGNvZWZmaWNpZW50IA0KYGBgDQoNCiMjIyAzLjQuIFBhcmFtZXRyaWMgQm9vdHN0cmFwIHNhbXBsaW5nDQoNClRoaXMgYm9vdHN0cmFwIGlzIHNpbWlsYXIgdG8gKk1vbnRlIENhcmxvKiBzaW11bGF0aW9uLiBXZSBhc3N1bWUgdGhlIHJlc2lkdWFscyBhcmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgd2l0aCBhIGNvbnN0YW50IHZhcmlhbmNlLiBCYXNlZCBvbiB0aGlzIGFzc3VtcHRpb24sIHdlIGVzdGltYXRlZCB0aGUgY29tbW9uIHZhcmlhbmNlIGZyb20gdGhlIHJlc2lkdWFscyBhbmQgdGhlbiAqKmdlbmVyYXRlIHJlc2lkdWFscyBmcm9tIHRoZSBub3JtYWwgZGlzdHJpYnV0aW9uKiogYW5kIGFzc2lnbiB0aGUgZ2VuZXJhdGVkIHZhbHVlcyB0byB0aGUgZml0dGVkIHZhbHVlcyBiYXNlZCBvbiB0aGUgb3JpZ2luYWwgZGF0YSBzZXQuDQoNClRoZSBzdGVwcyBvZiB0aGlzIEJvb3RzdHJhcCByZWdyZXNzaW9uIGlzIGFsbW9zdCBpZGVudGljYWwgdG8gdGhlIGFib3ZlIG1ldGhvZC4NCg0KV2Ugb25seSBtb2RpZnkgdGhlIGFib3ZlIGNvZGUgdG8gZ2V0IGEgcGFyYW1ldHJpYyBib290c3RyYXAgcmVncmVzc2lvbiBtb2RlbC4NCg0KYGBge3IgYm9vdDAzfQ0KTXkubG0wMz1mdW5jdGlvbihkYXRhc2V0LCBCLCBoaXN0b2dyYW09VFJVRSl7DQogICAgIyBkYXRhc2V0ID0gaW5wdXQgZGF0YSBzZXQNCiAgICAjIEIgPSBudW1iZXIgb2YgYm9vdHN0cmFwIHNhbXBsZXMNCiAgICBuMD1kaW0oZGF0YXNldClbMV0NCiAgICB2YXIubmFtZSA9IGMoIkludGVyY2VwdCIsbmFtZXMoZGF0YXNldClbLTFdKQ0KICAgIGNvbC5udW09bGVuZ3RoKG5hbWVzKGRhdGFzZXQpKSAgICAgIyMgbnVtYmVyIG9mIHZhcmlhYmxlcyA9IG51bWJlciBvZiBwYXJhbWV0ZXJzDQogICAgcm93Lm51bSA9IEIgICAgICAgICAgICAgICAgICAgICAgICAjIyBlYWNoIGJvb3RzdHJhcCBjb2VmZmljaWVudHMgd2lsbCBiZSBzYXZlZCBpbiBhIHJvdw0KICAgIGNvZWYubWF0cnggPSBtYXRyaXgoMCxuY29sPWNvbC5udW0sIG5yb3c9QikgICAjIGJvb3RzdHJhcCBjb2VmIG1hdHJpeA0KICAgIGNvbG5hbWVzKGNvZWYubWF0cngpID0gdmFyLm5hbWUNCiAgICAjIw0KICAgIG0wID0gbG0oTWF0aH5WZXJiYWwrU2V4LCBkYXRhID0gZGF0YXNldCkNCiAgICBvcmlnaW5hbC5yZXNpZD1yZXNpZChtMCkNCiAgICBzaWcgPSBzZChvcmlnaW5hbC5yZXNpZCkNCiAgICBvcmlnaW5hbC5maXQgPSBmaXR0ZWQobTApDQogICAgVmVyYmFsPWRhdGFzZXQkVmVyYmFsDQogICAgU2V4PWRhdGFzZXQkU2V4DQogICAgIyMNCiAgICBmb3IgKGkgaW4gMTpCKXsNCiAgICAgICAgIGJvb3QucmVzaWQgPSBybm9ybShuMCwgbWVhbj0wLCBzZCA9IHNpZykgICAgIyMgUGFyYW1ldHJpYyBCb290c3RyYXAgcmVzaWR1YWxzDQogICAgICAgICBib290Lk1hdGggPSBvcmlnaW5hbC5maXQgKyBib290LnJlc2lkICAgICAgICMjIGJvb3RzdHJhcCBZWVkNCiAgICAgICAgIGJvb3QubSA9IGxtKGJvb3QuTWF0aH5WZXJiYWwrU2V4KSAgICAgICAgICAgIyMgQm9vdHN0cmFwIG1vZGVsDQogICAgICAgICBjb2VmLm1hdHJ4W2ksXSA9IGNvZWYoYm9vdC5tKSAgICAgICAgICAgICAgICMjIHNhdmUgdGhlIGJvb3RzdHJhcCBjb2VmIHRvIHRoZSBtYXRyaXgNCiAgICB9DQogICAgIyMgb3JpZ2luYWwgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwNCiAgICAjIG0wID0gbG0oTWF0aH5WZXJiYWwrU2V4LCBkYXRhID0gZGF0YXNldCkNCiAgICBjb2YubTA9c3VtbWFyeShtMCkkY29lZg0KICAgIHEuMDI1PWZ1bmN0aW9uKHgpIHF1YW50aWxlKHgsIDAuMDI1KQ0KICAgIHEuOTc1PWZ1bmN0aW9uKHgpIHF1YW50aWxlKHgsIDAuOTc1KQ0KICAgIEJvb3QuTENJLjAyNT1hcHBseShjb2VmLm1hdHJ4LDIscS4wMjUpDQogICAgQm9vdC5VQ0kuOTc1PWFwcGx5KGNvZWYubWF0cngsMixxLjk3NSkNCiAgICBuZXcuY29lZj1yb3VuZChjYmluZChjb2YubTAsIGNiaW5kKEJUMDMuTENJLjAyNT1Cb290LkxDSS4wMjUsIEJUMDMuVUNJLjk3NT1Cb290LlVDSS45NzUpKSw0KQ0KICAgIGlmKGhpc3RvZ3JhbT09VFJVRSl7DQogICAgICAgaGlzdChjb2VmLm1hdHJ4WywxXSwgbWFpbj1wYXN0ZSgiQm9vdHN0cmFwIChwYXJhbWV0cmljKSBEaXN0cmlidXRpb24gXG4gb2YgQ29lZmZpY2llbnQ6Iix2YXIubmFtZVsxXSkpDQogICAgICAgaGlzdChjb2VmLm1hdHJ4WywyXSwgbWFpbj1wYXN0ZSgiQm9vdHN0cmFwIChwYXJhbWV0cmljKSBEaXN0cmlidXRpb24gXG4gb2YgQ29lZmZpY2llbnQ6Iix2YXIubmFtZVsyXSkpDQogICAgICAgaGlzdChjb2VmLm1hdHJ4WywzXSwgbWFpbj1wYXN0ZSgiQm9vdHN0cmFwIChwYXJhbWV0cmljKSBEaXN0cmlidXRpb24gXG4gb2YgQ29lZmZpY2llbnQ6Iix2YXIubmFtZVszXSkpDQogICAgfQ0KICAgIGxpc3QoYm9vdC5jb2VmPWNvZWYubWF0cngsIGNvZWZmaWNpZW50PW5ldy5jb2VmKSAjIHR3byB0eXBlcyBvZiBvdXRwdXRzIHZhaWxhYmxlIGZyb20gdGhlIGZ1bmN0aW9uDQp9DQpNeS5sbTAzKGRhdGFzZXQ9c2F0LCBCPTEwMDAsIGhpc3RvZ3JhbT1UUlVFKSRjb2VmZmljaWVudCANCmBgYA0K