3. Bootstrapping
Linear Regression - Non-parametric Approaches
Two Types of Bootstrap methods are commonly used in regression
modeling.
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)

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
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
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