0.1 Introduction

1 Description

The PGA 2004 dataset contains performance statistics and winnings data for 196 participants in the PGA (Proffesional Golfers Association) during the 2004 season. The dataset provides information on several key aspects of player performance and earnings such as:

Name : The name of each golfer. Age : The age of the player. Average Drive : The average driving distance of the player in yards Driving Accuracy: The percentage of drives that land on the fairway, Greens in Regulation : The percentage of greens reached in regulation, meaning the number of strokes used by the player to land the ball on the green is two or more less than par. Average Number of Putts : The average number of putts per round Save Percentage : The percentage of times a player saves par or better from around the green Money Rank : The rank of the player based on total earnings for the season. Number of Events : The total number of events the player participated in during the season. Total Winnings : The total amount of money earned by the player over the course of the season. Average Winnings : The average amount of money earned per event by the player.

2 Research Question

We want to investigate what variables affect the players winnings of this given season. We will look at mainly the average drive and how it correlates to winnings.

3 Data Preperation

url <- "https://users.stat.ufl.edu/~winner/data/pga2004.dat"
pga_data <- read.table(url, header = FALSE, fill = TRUE)
pga_data$V1 <- NULL
colnames(pga_data) <- c("Player_name", "Player_Age", "Average_Drive", "Driving_Accuracy", "Greens_on_reg", 
                        "Average_number_putts", "Save_Percent", "Money_Rank",
                        "Number_events", "Total_Winnings", "Average_Winnings")
head(pga_data)
  Player_name Player_Age Average_Drive Driving_Accuracy Greens_on_reg
1    Baddeley         23         288.0             53.1          58.2
2       Scott         24         295.4             57.7          65.6
3       Cejka         34         285.8             64.2          63.8
4       Stolz         34         297.9             59.0          63.0
5       Atwal         31         289.4             60.5          62.5
6  Oberholser         29         284.6             68.8          67.0
  Average_number_putts Save_Percent Money_Rank Number_events Total_Winnings
1                1.767         50.9        123            27         632878
2                1.757         59.3          7            16        3724984
3                1.795         50.7         54            24        1313484
4                1.787         47.7        101            20         808373
5                1.766         43.5        146            30         486053
6                1.780         50.9         52            23        1355433
  Average_Winnings
1            23440
2           232812
3            54729
4            40419
5            16202
6            58932

We take a broad look at the data and see there was two categorical variables one of which having the first name of a player and then a second one with the last name. We void the first name and just look at the last names.

pander(head(pga_data))
Table continues below
Player_name Player_Age Average_Drive Driving_Accuracy Greens_on_reg
Baddeley 23 288 53.1 58.2
Scott 24 295.4 57.7 65.6
Cejka 34 285.8 64.2 63.8
Stolz 34 297.9 59 63
Atwal 31 289.4 60.5 62.5
Oberholser 29 284.6 68.8 67
Table continues below
Average_number_putts Save_Percent Money_Rank Number_events
1.767 50.9 123 27
1.757 59.3 7 16
1.795 50.7 54 24
1.787 47.7 101 20
1.766 43.5 146 30
1.78 50.9 52 23
Total_Winnings Average_Winnings
632878 23440
3724984 232812
1313484 54729
808373 40419
486053 16202
1355433 58932
cor(pga_data[, c("Average_Drive" , "Driving_Accuracy" , "Greens_on_reg" , "Average_number_putts" , "Save_Percent" , "Money_Rank" , "Number_events" , "Total_Winnings" , "Average_Winnings")], use = "pairwise.complete.obs")
                     Average_Drive Driving_Accuracy Greens_on_reg
Average_Drive          1.000000000      -0.99252269   0.002134259
Driving_Accuracy      -0.992522688       1.00000000   0.046962093
Greens_on_reg          0.002134259       0.04696209   1.000000000
Average_number_putts  -0.988803391       0.99389638   0.022156599
Save_Percent           0.867471569      -0.87937510  -0.056544353
Money_Rank             0.186561899      -0.20345865  -0.498016597
Number_events         -0.699109599       0.68370009   0.062783323
Total_Winnings         0.226265605      -0.21129425   0.405497292
Average_Winnings      -0.773878692       0.79872244   0.013823260
                     Average_number_putts Save_Percent  Money_Rank
Average_Drive                  -0.9888034   0.86747157  0.18656190
Driving_Accuracy                0.9938964  -0.87937510 -0.20345865
Greens_on_reg                   0.0221566  -0.05654435 -0.49801660
Average_number_putts            1.0000000  -0.88762172 -0.19710155
Save_Percent                   -0.8876217   1.00000000  0.06385765
Money_Rank                     -0.1971016   0.06385765  1.00000000
Number_events                   0.6924957  -0.63927742 -0.11418017
Total_Winnings                 -0.2044137   0.27127779 -0.66596035
Average_Winnings                0.7948355  -0.68320935 -0.27537388
                     Number_events Total_Winnings Average_Winnings
Average_Drive          -0.69910960    0.226265605     -0.773878692
Driving_Accuracy        0.68370009   -0.211294247      0.798722439
Greens_on_reg           0.06278332    0.405497292      0.013823260
Average_number_putts    0.69249567   -0.204413695      0.794835458
Save_Percent           -0.63927742    0.271277789     -0.683209349
Money_Rank             -0.11418017   -0.665960350     -0.275373883
Number_events           1.00000000   -0.180433459      0.190800029
Total_Winnings         -0.18043346    1.000000000      0.002463485
Average_Winnings        0.19080003    0.002463485      1.000000000

We see that a lot of our variables have high correlation which can cause multicollinearity. We must deal with this issue before doing our analysis.

colSums(is.na(pga_data))
         Player_name           Player_Age        Average_Drive 
                   0                    0                   10 
    Driving_Accuracy        Greens_on_reg Average_number_putts 
                  10                   10                   10 
        Save_Percent           Money_Rank        Number_events 
                  10                   10                   10 
      Total_Winnings     Average_Winnings 
                  10                   10 
pga_data_clean <- na.omit(pga_data)

For the sake of missing observations we will be removing it for our data analysis because we have a plentiful amount of observations.

ggplot(pga_data_clean, aes(x = Average_Drive, y = Average_Winnings)) +
  geom_point() +
  geom_smooth(method = "lm", col = "green") +
  labs(title = "Scatter Plot of Average Drive vs. Average Winnings", x = "Average Drive", y = "Average Winnings")
`geom_smooth()` using formula = 'y ~ x'

str(pga_data_clean)
'data.frame':   196 obs. of  11 variables:
 $ Player_name         : chr  "Baddeley" "Scott" "Cejka" "Stolz" ...
 $ Player_Age          : chr  "23" "24" "34" "34" ...
 $ Average_Drive       : num  288 295 286 298 289 ...
 $ Driving_Accuracy    : num  53.1 57.7 64.2 59 60.5 68.8 74.2 64.4 64.3 62.6 ...
 $ Greens_on_reg       : num  58.2 65.6 63.8 63 62.5 67 68.9 64.2 63.4 65.3 ...
 $ Average_number_putts: num  1.77 1.76 1.79 1.79 1.77 ...
 $ Save_Percent        : num  50.9 59.3 50.7 47.7 43.5 50.9 40.4 53.8 42.2 47.7 ...
 $ Money_Rank          : num  123 7 54 101 146 52 80 75 141 83 ...
 $ Number_events       : int  27 16 24 20 30 23 23 27 20 15 ...
 $ Total_Winnings      : int  632878 3724984 1313484 808373 486053 1355433 962167 1036958 500818 943589 ...
 $ Average_Winnings    : int  23440 232812 54729 40419 16202 58932 41833 38406 25041 62906 ...
 - attr(*, "na.action")= 'omit' Named int [1:10] 15 32 54 58 68 110 142 155 198 200
  ..- attr(*, "names")= chr [1:10] "15" "32" "54" "58" ...
remove_low_drives <- function(pga_data_clean) {
  filtered_data <- pga_data_clean %>%
    filter(Average_Drive >= 150)
  
  return(filtered_data)
}

# Example usage
pga_data_filtered <- remove_low_drives(pga_data_clean)
ggplot(pga_data_filtered, aes(x = Average_Drive, y = Average_Winnings)) +
  geom_point() +
  geom_smooth(method = "lm", col = "green") +
  labs(
    title = "Scatter Plot of Average Drive vs. Average Winnings (Filtered)",
    x = "Average Drive",
    y = "Average Winnings"
  ) +
  theme_minimal() 
`geom_smooth()` using formula = 'y ~ x'

We look at an initial graph of average winnigs vs average drive. It looks like we have a good cluster of data but we have a couple of outliers in our data that is scewing our line.

pga_data_filtered$Age_Above_30 <- pga_data_filtered$Player_Age > 30

head(pga_data_filtered$Age_Above_30)
[1] FALSE FALSE  TRUE  TRUE  TRUE FALSE
pga_data_filtered <- pga_data_filtered %>% select(-Player_name, -Driving_Accuracy, -Total_Winnings, -Average_number_putts, -Money_Rank, -Player_Age)
kable(head(pga_data_filtered))
Average_Drive Greens_on_reg Save_Percent Number_events Average_Winnings Age_Above_30
288.0 58.2 50.9 27 23440 FALSE
295.4 65.6 59.3 16 232812 FALSE
285.8 63.8 50.7 24 54729 TRUE
297.9 63.0 47.7 20 40419 TRUE
289.4 62.5 43.5 30 16202 TRUE
284.6 67.0 50.9 23 58932 FALSE

We remove player name and money rank because we are “ranking” them or sorting them by +/- 30 age. We remove driving accuracy because we have a variable with average drive because that is what we are making assumptions on and also it could cause multicollinearity because driving accuracy and average drive are highly correlated. The same can be said for total winnings versus average winnings and also save percent and number of putts. They are both correlated and can cause multicollinearity.

3.1 Model Bulding

4 Linear Model

colnames(pga_data_filtered)
[1] "Average_Drive"    "Greens_on_reg"    "Save_Percent"     "Number_events"   
[5] "Average_Winnings" "Age_Above_30"    
full.model = lm(Average_Winnings ~  Average_Drive + Greens_on_reg +  Save_Percent + Number_events + Age_Above_30, data = pga_data_filtered)
kable(summary(full.model)$coef, caption ="Statistics of Regression Coefficients")
Statistics of Regression Coefficients
Estimate Std. Error t value Pr(>|t|)
(Intercept) -802913.292 147739.1904 -5.434667 0.0000002
Average_Drive 1198.229 435.7615 2.749735 0.0065731
Greens_on_reg 7278.954 1205.2877 6.039183 0.0000000
Save_Percent 2486.926 627.6347 3.962378 0.0001069
Number_events -3508.708 723.2608 -4.851234 0.0000026
Age_Above_30TRUE 3537.995 8630.5405 0.409939 0.6823382

We transform the age category into a true or false of payers with above or below age 30. All of our values are significant besides Age.

par(mfrow=c(2,2))
plot(full.model)

It looks like we have faults in our assumptions. Based on the QQ plot there seems to be a non-normal distribution with several outliers. There also seems to be non-constant variance.

vif(full.model)
Average_Drive Greens_on_reg  Save_Percent Number_events  Age_Above_30 
     1.136250      1.044126      1.047080      1.018950      1.072829 
barplot(vif(full.model), main = "VIF Values", horiz = FALSE, col = "steelblue")

We see in the vif values above that all of them are below 5 which shows little to no multicollineairty so we can continue.

5 Box Cox Transformation

We perform this transformation because our data has non constant variance

par(pty = "s", mfrow = c(2, 2), oma=c(.1,.1,.1,.1), mar=c(4, 0, 2, 0))
##
boxcox(Average_Winnings ~ log(Average_Drive) + Greens_on_reg +  Save_Percent + Number_events + Age_Above_30, data = pga_data_filtered, lambda = seq(-1, 1, length = 10), 
       xlab=expression(paste(lambda, ": log Average Drive")))
##
boxcox(Average_Winnings ~ Average_Drive + Greens_on_reg +  Save_Percent + Number_events + Age_Above_30, data = pga_data_filtered, lambda = seq(-1, 1, length = 10), 
       xlab=expression(paste(lambda, ": Average Drive")))
##
boxcox(Average_Winnings ~ Average_Drive + Greens_on_reg +  Save_Percent + Number_events + log(1+Age_Above_30), data = pga_data_filtered, lambda = seq(-1, 1, length = 10), 
       xlab=expression(paste(lambda, ": log-age")))
##
boxcox(Average_Winnings ~ log(Average_Drive) + Greens_on_reg +  Save_Percent + Number_events + log(1+Age_Above_30), data = pga_data_filtered, lambda = seq(-1, 1, length = 10), 
      xlab=expression(paste(lambda, ": log-age, log Average Drive")))

Values of lamda are around 0 so we shoudl use a log transformation ## Square Root Model

sqrt.winnings.log.drive = lm((Average_Winnings)^0.5 ~ Greens_on_reg +  Save_Percent + Number_events + Age_Above_30 + log(Average_Drive), data = pga_data_filtered)
kable(summary(sqrt.winnings.log.drive)$coef, caption = "log-transformed model")
log-transformed model
Estimate Std. Error t value Pr(>|t|)
(Intercept) -3187.392815 1176.262371 -2.7097635 0.0073840
Greens_on_reg 16.184221 1.999182 8.0954209 0.0000000
Save_Percent 5.154888 1.041392 4.9499977 0.0000017
Number_events -6.056634 1.199193 -5.0505915 0.0000011
Age_Above_30TRUE 2.354375 14.312534 0.1644974 0.8695242
log(Average_Drive) 394.504246 208.497492 1.8921295 0.0600792
par(mfrow = c(2,2))
plot(sqrt.winnings.log.drive)

This model worsened our QQ-plot normality and our non constant variance remained the same, so we can try another model to see if it helps.

6 Log Transformation

log.winnings = lm(log(Average_Winnings) ~ Average_Drive + Greens_on_reg +  Save_Percent + Number_events + Age_Above_30, data = pga_data_filtered)
kable(summary(log.winnings)$coef, caption = "log-transformed model")
log-transformed model
Estimate Std. Error t value Pr(>|t|)
(Intercept) -5.4512847 2.3833429 -2.2872432 0.0233453
Average_Drive 0.0057567 0.0070297 0.8189126 0.4139191
Greens_on_reg 0.1920075 0.0194438 9.8749896 0.0000000
Save_Percent 0.0563106 0.0101251 5.5615033 0.0000001
Number_events -0.0450036 0.0116677 -3.8571026 0.0001596
Age_Above_30TRUE 0.0333772 0.1392287 0.2397290 0.8108131
par(mfrow = c(2,2))
plot(log.winnings)

This model looks drastically better then the other two models. While normality still cannot be assumed the QQ Plot looks better. The same thing has happened with our Residual plot while constant variance can still not be assumed it is drastically better.

7 Comparison of models

par(pty = "s", mfrow = c(1, 3))

qqnorm(full.model$residuals, main = "Full-Model")
qqline(full.model$residuals)

qqnorm(log.winnings$residuals, main = "Log Winnings")
qqline(log.winnings$residuals)

qqnorm(sqrt.winnings.log.drive$residuals, main = "sqrt winnings log drive")
qqline(sqrt.winnings.log.drive$residuals)

We can see that Log Winnings is the best model for our data.

8 Goodness of Fit Comparison

select=function(m){ 
 e = m$resid                           
 n0 = length(e)                        
 SSE=(m$df)*(summary(m)$sigma)^2       
 R.sq=summary(m)$r.squared             
 R.adj=summary(m)$adj.r               
 MSE=(summary(m)$sigma)^2              
 Cp=(SSE/MSE)-(n0-2*(n0-m$df))         
 AIC=n0*log(SSE)-n0*log(n0)+2*(n0-m$df)          
 SBC=n0*log(SSE)-n0*log(n0)+(log(n0))*(n0-m$df)  
 X=model.matrix(m)                     
 H=X%*%solve(t(X)%*%X)%*%t(X)          
 d=e/(1-diag(H))                       
 PRESS=t(d)%*%d   
 tbl = as.data.frame(cbind(SSE=SSE, R.sq=R.sq, R.adj = R.adj, Cp = Cp, AIC = AIC, SBC = SBC, PRD = PRESS))
 names(tbl)=c("SSE", "R.sq", "R.adj", "Cp", "AIC", "SBC", "PRESS")
 tbl
 }
output.sum = rbind(select(full.model), select(sqrt.winnings.log.drive), select(log.winnings))
row.names(output.sum) = c("full.model", "sqrt.winnings.log.drive", "log.winnings")
kable(output.sum, caption = "Goodness-of-fit Measures of Candidate Models")
Goodness-of-fit Measures of Candidate Models
SSE R.sq R.adj Cp AIC SBC PRESS
full.model 3.852006e+11 0.3349285 0.3164543 6 4001.9387 4021.2932 4.229954e+11
sqrt.winnings.log.drive 1.058809e+06 0.4162839 0.4000695 6 1620.3250 1639.6795 1.143272e+06
log.winnings 1.002463e+02 0.4547915 0.4396468 6 -102.9696 -83.6151 1.075339e+02

Even though the R, R squared, and Cp look better in the other two models our value are still relatively good for log.winnings. Log.winnings has better residuals, QQ plot, but less goodness of fit measures but they are not significantly bad so we will stick with the log model.

9 Final Model

kable(summary(log.winnings)$coef, caption = "Inferential Statistics of Final Model")
Inferential Statistics of Final Model
Estimate Std. Error t value Pr(>|t|)
(Intercept) -5.4512847 2.3833429 -2.2872432 0.0233453
Average_Drive 0.0057567 0.0070297 0.8189126 0.4139191
Greens_on_reg 0.1920075 0.0194438 9.8749896 0.0000000
Save_Percent 0.0563106 0.0101251 5.5615033 0.0000001
Number_events -0.0450036 0.0116677 -3.8571026 0.0001596
Age_Above_30TRUE 0.0333772 0.1392287 0.2397290 0.8108131

While we do have a large sample of 196 and all of our pvalues are close to 0 we do have one variable “Age” which is not significant.

9.1 Summary of Model

The final model can be represented as log(price)=3.9245 − 0.0246×Average_Drive + 0.1866×Greens_on_reg + 0.0387×Save_percent − 0.0181×Number_events + 0.1826×Age_Above_30TRUE

From the above data we can also conclude that as average winnings go up our average drive goes down by -2.5% while other variables such as greens on regulation and save percent go up.

log.winnings = lm(log(Average_Winnings) ~ Average_Drive + Greens_on_reg + Save_Percent + Number_events + Age_Above_30, data = pga_data_filtered)

B = 1000  #

num.p = length(coef(log.winnings))  # number of parameters in the model (includes intercept)
smpl.n = nrow(pga_data_filtered)       # sample size

# Matrix to store bootstrap coefficients
coef.mtrx = matrix(0, nrow = B, ncol = num.p)

# Bootstrap loop
for (i in 1:B) {
  bootc.id = sample(1:smpl.n, smpl.n, replace = TRUE)  # Resample with replacement
  boot_data = pga_data_filtered[bootc.id, ]  # Create the bootstrap sample
  log.winnings.btc = lm(log(Average_Winnings) ~ Average_Drive + Greens_on_reg + Save_Percent + Number_events + Age_Above_30, data = boot_data)
  coef.mtrx[i, ] = coef(log.winnings.btc)  # Store the coefficients
}

9.2 Bootstrapping Final Model

boot.hist = function(cmtrx, bt.coef.mtrx, var.id, var.nm){

  x1.1 <- seq(min(bt.coef.mtrx[,var.id]), max(bt.coef.mtrx[,var.id]), length=300 )
  y1.1 <- dnorm(x1.1, mean(bt.coef.mtrx[,var.id]), sd(bt.coef.mtrx[,var.id]))

  highestbar = max(hist(bt.coef.mtrx[,var.id], plot = FALSE)$density) 
  ylimit <- max(c(y1.1,highestbar))
  hist(bt.coef.mtrx[,var.id], probability = TRUE, main = var.nm, xlab="", 
       col = "azure1",ylim=c(0,ylimit), border="lightseagreen")
  lines(x = x1.1, y = y1.1, col = "red3")
  lines(density(bt.coef.mtrx[,var.id], adjust=2), col="blue") 
  
}
par(mfrow=c(2,3))  
boot.hist(bt.coef.mtrx=coef.mtrx, var.id=1, var.nm ="Intercept" )
boot.hist(bt.coef.mtrx=coef.mtrx, var.id=2, var.nm ="Average Drive" )
boot.hist(bt.coef.mtrx=coef.mtrx, var.id=3, var.nm ="Greens On Regulation" )
boot.hist(bt.coef.mtrx=coef.mtrx, var.id=4, var.nm ="Save Percentage" )
boot.hist(bt.coef.mtrx=coef.mtrx, var.id=5, var.nm ="Number of Events" )
boot.hist(bt.coef.mtrx=coef.mtrx, var.id=6, var.nm ="Age Above 30" )

We see from our histograms that they look to have a equal distribution. The blue curve represents the p-values reported. While the blue curve represents the bootstrap intervals. They are very similar.

10 95% CI

num.p = dim(coef.mtrx)[2]  

btc.ci = NULL
btc.wd = NULL

for (i in 1:num.p) {
  lci.025 = round(quantile(coef.mtrx[, i], 0.025, type = 2), 8)
  uci.975 = round(quantile(coef.mtrx[, i], 0.975, type = 2), 8)
  btc.wd[i] = uci.975 - lci.025
  btc.ci[i] = paste("[", round(lci.025, 4), ", ", round(uci.975, 4), "]")
}

mean.coefs = apply(coef.mtrx, 2, mean)

results = as.data.frame(cbind(Mean_Coef = formatC(mean.coefs, 4, format = "f"), btc.ci.95 = btc.ci))

kable(results, caption = "Regression Coefficient Matrix with Bootstrap Confidence Intervals")
Regression Coefficient Matrix with Bootstrap Confidence Intervals
Mean_Coef btc.ci.95
-5.4436 [ -10.4839 , -0.2628 ]
0.0059 [ -0.0065 , 0.0181 ]
0.1917 [ 0.1442 , 0.236 ]
0.0559 [ 0.038 , 0.0752 ]
-0.0452 [ -0.0696 , -0.0216 ]
0.0298 [ -0.2617 , 0.3228 ]

We can see that our values are consistent with the p-values that we got. Our 95% CI spans over 0 in the Age variable which matches up with our p-value not being significant

hist(sort(log.winnings$residuals),n=40,
     xlab="Residuals",
     col = "lightblue",
     border="navy",
     main = "Histogram of Residuals")

We appear to have a normal distribution but we do appear to have some outliers on either side of the curve.

11 Residual Bootstrap Regression

log.winnings <- lm(log(Average_Winnings) ~ Average_Drive + Greens_on_reg + Save_Percent + Number_events + Age_Above_30, data = pga_data_filtered)
model.resid = log.winnings$residuals
B=1000
num.p = dim(model.matrix(log.winnings))[2]   
samp.n = dim(model.matrix(log.winnings))[1]  
btr.mtrx = matrix(rep(0,6*B), ncol=num.p)
for (i in 1:B){

  bt.lg.winnings = log.winnings$fitted.values + 
        sample(log.winnings$residuals, samp.n, replace = TRUE)  
  
  pga_data_filtered$bt.lg.winnings =  bt.lg.winnings   #  send the boot response to the data
  btr.model = lm(bt.lg.winnings ~ Average_Drive + Greens_on_reg + Save_Percent + Number_events + Age_Above_30, data = pga_data_filtered)  
  btr.mtrx[i,]=btr.model$coefficients
}
boot.hist = function(bt.coef.mtrx, var.id, var.nm){

  x1.1 <- seq(min(bt.coef.mtrx[,var.id]), max(bt.coef.mtrx[,var.id]), length=300 )
  y1.1 <- dnorm(x1.1, mean(bt.coef.mtrx[,var.id]), sd(bt.coef.mtrx[,var.id]))

  highestbar = max(hist(bt.coef.mtrx[,var.id], plot = FALSE)$density) 
  ylimit <- max(c(y1.1,highestbar))
  hist(bt.coef.mtrx[,var.id], probability = TRUE, main = var.nm, xlab="", 
       col = "azure1",ylim=c(0,ylimit), border="lightseagreen")
  lines(x = x1.1, y = y1.1, col = "red3")             
  lines(density(bt.coef.mtrx[,var.id], adjust=2), col="blue")    
} 
par(mfrow=c(2,3))  
boot.hist(bt.coef.mtrx=btr.mtrx, var.id=1, var.nm ="Intercept" )
boot.hist(bt.coef.mtrx=btr.mtrx, var.id=2, var.nm ="Average Drive" )
boot.hist(bt.coef.mtrx=btr.mtrx, var.id=3, var.nm ="Greens on Regulation" )
boot.hist(bt.coef.mtrx=btr.mtrx, var.id=4, var.nm ="Save Percentage" )
boot.hist(bt.coef.mtrx=btr.mtrx, var.id=5, var.nm ="Number of Events" )
boot.hist(bt.coef.mtrx=btr.mtrx, var.id=6, var.nm ="Age" )

We again see that our histograms have an equal distribution. Again we see that the p-values and residual boostrap have very similar values.

um.p = dim(btr.mtrx)[2]  
btr.ci = NULL
btr.wd = NULL

for (i in 1:num.p) {
  lci.025 = round(quantile(btr.mtrx[, i], 0.025, type = 2), 8)
  uci.975 = round(quantile(btr.mtrx[, i], 0.975, type = 2), 8)
  btr.wd[i] = uci.975 - lci.025
  btr.ci[i] = paste("[", round(lci.025, 4), ", ", round(uci.975, 4), "]")
}

kable(as.data.frame(cbind(formatC(btr.mtrx[1, ], 4, format = "f"), btr.ci.95 = btr.ci)), 
      caption = "Regression Coefficient Matrix with 95% Residual Bootstrap CI")
Regression Coefficient Matrix with 95% Residual Bootstrap CI
V1 btr.ci.95
-3.9016 [ -10.2057 , -1.1977 ]
-0.0006 [ -0.0066 , 0.0192 ]
0.2044 [ 0.155 , 0.2292 ]
0.0500 [ 0.0369 , 0.0758 ]
-0.0583 [ -0.0669 , -0.0231 ]
0.1272 [ -0.2253 , 0.291 ]

Once again we get the same output as expected. The values above show the same results as our p-values, all being significant except for age which includes 0.

11.1 Combining results

p_values <- summary(log.winnings)$coefficients[-3, 4]  

combined_matrix <- cbind(
  Coefficients = formatC(coef(log.winnings)[-3], 4, format = "f"),  
  `95% CI (Bootstrap t)` = btc.ci,                                
  `95% CI (Bootstrap r)` = btr.ci,                                 
  `p-values` = formatC(p_values, 4, format = "f")                  
)
Warning in cbind(Coefficients = formatC(coef(log.winnings)[-3], 4, format =
"f"), : number of rows of result is not a multiple of vector length (arg 1)
library(knitr)
kable(as.data.frame(combined_matrix), 
      caption = "Final Combined Inferential Statistics: Coefficients, p-values, and Bootstrap CIs")
Final Combined Inferential Statistics: Coefficients, p-values, and Bootstrap CIs
Coefficients 95% CI (Bootstrap t) 95% CI (Bootstrap r) p-values
-5.4513 [ -10.4839 , -0.2628 ] [ -10.2057 , -1.1977 ] 0.0233
0.0058 [ -0.0065 , 0.0181 ] [ -0.0066 , 0.0192 ] 0.4139
0.0563 [ 0.1442 , 0.236 ] [ 0.155 , 0.2292 ] 0.0000
-0.0450 [ 0.038 , 0.0752 ] [ 0.0369 , 0.0758 ] 0.0002
0.0334 [ -0.0696 , -0.0216 ] [ -0.0669 , -0.0231 ] 0.8108
-5.4513 [ -0.2617 , 0.3228 ] [ -0.2253 , 0.291 ] 0.0233

All of the models yield the same result, showing there is a violation in the age variable but all other variables are significant.

kable(round(cbind(btc.wd, btr.wd),4), caption="width of the two bootstrap confidence intervals")
width of the two bootstrap confidence intervals
btc.wd btr.wd
10.2211 9.0080
0.0245 0.0258
0.0918 0.0741
0.0372 0.0389
0.0480 0.0438
0.5845 0.5163

We see from the above analysis that the two values are very similar to each other.

11.2 Summary

We see from our findings above that all three of our models come to the same conclusion. We will be using our initial final model where we used a log transformation because they seem to be a little more significant.

Again we see that the final model can be represented as log(price)=3.9245 − 0.0246×Average_Drive + 0.1866×Greens_on_reg + 0.0387×Save_percent − 0.0181×Number_events + 0.1826×Age_Above_30TRUE

From the above data we can also conclude that as average winnings go up our average drive goes down by -2.5% while other variables such as greens on regulation and save percent go up.

12 Conclusion

The main conclusion we can draw is that while driving distance in golf is perceived to be a big indication of whether a person is winning or not we do not see this in our analysis. While the Average Winnings goes up the average drive goes down by -2.5% while Save Percentage goes up by 3.9% and Greens on Regulation goes up by 1.9% which shows that the “short” game in golf (putting and chipping) is more important than driving in regards to average money won.

We see one flaw in our analysis and it was that age was not significant which could mess up our analysis. in the future we could push our age up or down for the true or false statement to see if that changes the significance. We could also look at some values of age to see if there are any outliers, for example golfers are not younger than 20 but they can be 45+ which could mess with our significance.

We can use this model for seasons to come to see if things may have changed. For example since golf has evolved recently to players having longer drives we could run this model again in 2024 to see if the “short” game is less likely to win more money than the “long” game would.

LS0tDQp0aXRsZTogIkZhY3RvcnMgSW5mbHVlbmNlIEdvbGYgRWFybmluZ3MiDQphdXRob3I6ICdUeWxlciBCYXR0YWdsaW5pJw0KZGF0ZTogIjIwMjQtMDktMTkiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBmaWdfd2lkdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICB0b2NfY29sbGFwc2VkOiB5ZXMNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICBzbW9vdGhfc2Nyb2xsOiB5ZXMNCiAgICB0aGVtZTogbHVtZW4NCiAgd29yZF9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAga2VlcF9tZDogeWVzDQogIHBkZl9kb2N1bWVudDogDQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDQNCiAgICBmaWdfY2FwdGlvbjogeWVzDQogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMNCiAgICBmaWdfd2lkdGg6IDMNCiAgICBmaWdfaGVpZ2h0OiAzDQplZGl0b3Jfb3B0aW9uczogDQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCmFsd2F5c19hbGxvd19odG1sOiB0cnVlDQotLS0NCg0KYGBgez1odG1sfQ0KDQo8c3R5bGUgdHlwZT0idGV4dC9jc3MiPg0KDQovKiBDYXNjYWRpbmcgU3R5bGUgU2hlZXRzIChDU1MpIGlzIGEgc3R5bGVzaGVldCBsYW5ndWFnZSB1c2VkIHRvIGRlc2NyaWJlIHRoZSBwcmVzZW50YXRpb24gb2YgYSBkb2N1bWVudCB3cml0dGVuIGluIEhUTUwgb3IgWE1MLiBpdCBpcyBhIHNpbXBsZSBtZWNoYW5pc20gZm9yIGFkZGluZyBzdHlsZSAoZS5nLiwgZm9udHMsIGNvbG9ycywgc3BhY2luZykgdG8gV2ViIGRvY3VtZW50cy4gKi8NCg0KaDEudGl0bGUgeyAgLyogVGl0bGUgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIHRoZSByZXBvcnQgdGl0bGUgKi8NCiAgZm9udC1zaXplOiAyNHB4Ow0KICBmb250LXdlaWdodDogYm9sZDsNCiAgY29sb3I6IERhcmtSZWQ7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgZm9udC1mYW1pbHk6ICJHaWxsIFNhbnMiLCBzYW5zLXNlcmlmOw0KfQ0KaDQuYXV0aG9yIHsgLyogSGVhZGVyIDQgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBhdXRob3JzICAqLw0KICBmb250LXNpemU6IDIwcHg7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDQuZGF0ZSB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgdGhlIGRhdGUgICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGZvbnQtZmFtaWx5OiBzeXN0ZW0tdWk7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KfQ0KaDEgeyAvKiBIZWFkZXIgMSAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIGxldmVsIDEgc2VjdGlvbiB0aXRsZSAgKi8NCiAgICBmb250LXNpemU6IDIycHg7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogICAgZm9udC1mYW1pbHk6IHN5c3RlbS11aTsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KaDIgeyAvKiBIZWFkZXIgMiAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIGxldmVsIDIgc2VjdGlvbiB0aXRsZSAqLw0KICAgIGZvbnQtc2l6ZTogMjBweDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoMyB7IC8qIEhlYWRlciAzIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiBsZXZlbCAzIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtd2VpZ2h0OiBib2xkOw0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmg0IHsgLyogSGVhZGVyIDQgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIGxldmVsIDQgc2VjdGlvbiB0aXRsZSAgKi8NCiAgICBmb250LXNpemU6IDE4cHg7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IGRhcmtyZWQ7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KYm9keSB7IGJhY2tncm91bmQtY29sb3I6d2hpdGU7IH0NCg0KLmhpZ2hsaWdodG1lIHsgYmFja2dyb3VuZC1jb2xvcjp5ZWxsb3c7IH0NCg0KcCB7IGJhY2tncm91bmQtY29sb3I6d2hpdGU7IH0NCg0KPC9zdHlsZT4NCmBgYA0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiMgRGV0ZWN0LCBpbnN0YWxsIGFuZCBsb2FkIHBhY2thZ2VzIGlmIG5lZWRlZC4NCmlmICghcmVxdWlyZSgia25pdHIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygia25pdHIiKQ0KICAgbGlicmFyeShrbml0cikNCn0NCmlmICghcmVxdWlyZSgiTUFTUyIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJNQVNTIikNCiAgIGxpYnJhcnkoTUFTUykNCn0NCmlmICghcmVxdWlyZSgibmxlcXNsdiIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJubGVxc2x2IikNCiAgIGxpYnJhcnkobmxlcXNsdikNCn0NCiMNCmlmICghcmVxdWlyZSgicGFuZGVyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInBhbmRlciIpDQogICBsaWJyYXJ5KHBhbmRlcikNCn0NCg0KaWYgKCFyZXF1aXJlKCJwc3ljaCIpKSB7ICAgDQogIGluc3RhbGwucGFja2FnZXMoInBzeWNoIikNCiAgIGxpYnJhcnkocHN5Y2gpDQp9DQppZiAoIXJlcXVpcmUoIk1BU1MiKSkgeyAgIA0KICBpbnN0YWxsLnBhY2thZ2VzKCJNQVNTIikNCiAgIGxpYnJhcnkoTUFTUykNCn0NCmlmICghcmVxdWlyZSgiZ2dwbG90MiIpKSB7ICAgDQogIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKQ0KICAgbGlicmFyeShnZ3Bsb3QyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJHR2FsbHkiKSkgeyAgIA0KICBpbnN0YWxsLnBhY2thZ2VzKCJHR2FsbHkiKQ0KICAgbGlicmFyeShHR2FsbHkpDQp9DQppZiAoIXJlcXVpcmUoImNhciIpKSB7ICAgDQogIGluc3RhbGwucGFja2FnZXMoImNhciIpDQogICBsaWJyYXJ5KGNhcikNCn0NCmlmICghcmVxdWlyZSgiZHBseXIiKSkgeyAgIA0KICBpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIpDQogICBsaWJyYXJ5KGRwbHlyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJjYXJldCIpKSB7ICAgDQogIGluc3RhbGwucGFja2FnZXMoImNhcmV0IikNCiAgIGxpYnJhcnkoY2FyZXQpDQp9DQoNCiMgc3BlY2lmaWNhdGlvbnMgb2Ygb3V0cHV0cyBvZiBjb2RlIGluIGNvZGUgY2h1bmtzDQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsICAgICAgIyBpbmNsdWRlIGNvZGUgY2h1bmsgaW4gdGhlIG91dHB1dCBmaWxlDQogICAgICAgICAgICAgICAgICAgICAgd2FybmluZ3MgPSBGQUxTRSwgICMgc29tZXRpbWVzLCB5b3UgY29kZSBtYXkgcHJvZHVjZSB3YXJuaW5nIG1lc3NhZ2VzLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHlvdSBjYW4gY2hvb3NlIHRvIGluY2x1ZGUgdGhlIHdhcm5pbmcgbWVzc2FnZXMgaW4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyB0aGUgb3V0cHV0IGZpbGUuIA0KICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2VzID0gRkFMU0UsICAjDQogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0cyA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgY29tbWVudCA9IE5BICAgICAgICMgeW91IGNhbiBhbHNvIGRlY2lkZSB3aGV0aGVyIHRvIGluY2x1ZGUgdGhlIG91dHB1dA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGluIHRoZSBvdXRwdXQgZmlsZS4NCiAgICAgICAgICAgICAgICAgICAgICApICAgDQpgYGANCg0KDQojIyMgSW50cm9kdWN0aW9uDQoNCiMjIERlc2NyaXB0aW9uDQpUaGUgUEdBIDIwMDQgZGF0YXNldCBjb250YWlucyBwZXJmb3JtYW5jZSBzdGF0aXN0aWNzIGFuZCB3aW5uaW5ncyBkYXRhIGZvciAxOTYgcGFydGljaXBhbnRzIGluIHRoZSBQR0EgKFByb2ZmZXNpb25hbCBHb2xmZXJzIEFzc29jaWF0aW9uKSBkdXJpbmcgdGhlIDIwMDQgc2Vhc29uLiBUaGUgZGF0YXNldCBwcm92aWRlcyBpbmZvcm1hdGlvbiBvbiBzZXZlcmFsIGtleSBhc3BlY3RzIG9mIHBsYXllciBwZXJmb3JtYW5jZSBhbmQgZWFybmluZ3Mgc3VjaCBhczoNCg0KTmFtZSA6IFRoZSBuYW1lIG9mIGVhY2ggZ29sZmVyLg0KQWdlIDogVGhlIGFnZSBvZiB0aGUgcGxheWVyLg0KQXZlcmFnZSBEcml2ZSA6IFRoZSBhdmVyYWdlIGRyaXZpbmcgZGlzdGFuY2Ugb2YgdGhlIHBsYXllciBpbiB5YXJkcw0KRHJpdmluZyBBY2N1cmFjeTogVGhlIHBlcmNlbnRhZ2Ugb2YgZHJpdmVzIHRoYXQgbGFuZCBvbiB0aGUgZmFpcndheSwgDQpHcmVlbnMgaW4gUmVndWxhdGlvbiA6IFRoZSBwZXJjZW50YWdlIG9mIGdyZWVucyByZWFjaGVkIGluIHJlZ3VsYXRpb24sIG1lYW5pbmcgdGhlIG51bWJlciBvZiBzdHJva2VzIHVzZWQgYnkgdGhlIHBsYXllciB0byBsYW5kIHRoZSBiYWxsIG9uIHRoZSBncmVlbiBpcyB0d28gb3IgbW9yZSBsZXNzIHRoYW4gcGFyLg0KQXZlcmFnZSBOdW1iZXIgb2YgUHV0dHMgOiBUaGUgYXZlcmFnZSBudW1iZXIgb2YgcHV0dHMgcGVyIHJvdW5kDQpTYXZlIFBlcmNlbnRhZ2UgOiBUaGUgcGVyY2VudGFnZSBvZiB0aW1lcyBhIHBsYXllciBzYXZlcyBwYXIgb3IgYmV0dGVyIGZyb20gYXJvdW5kIHRoZSBncmVlbiANCk1vbmV5IFJhbmsgOiBUaGUgcmFuayBvZiB0aGUgcGxheWVyIGJhc2VkIG9uIHRvdGFsIGVhcm5pbmdzIGZvciB0aGUgc2Vhc29uLg0KTnVtYmVyIG9mIEV2ZW50cyA6IFRoZSB0b3RhbCBudW1iZXIgb2YgZXZlbnRzIHRoZSBwbGF5ZXIgcGFydGljaXBhdGVkIGluIGR1cmluZyB0aGUgc2Vhc29uLg0KVG90YWwgV2lubmluZ3MgOiBUaGUgdG90YWwgYW1vdW50IG9mIG1vbmV5IGVhcm5lZCBieSB0aGUgcGxheWVyIG92ZXIgdGhlIGNvdXJzZSBvZiB0aGUgc2Vhc29uLg0KQXZlcmFnZSBXaW5uaW5ncyA6IFRoZSBhdmVyYWdlIGFtb3VudCBvZiBtb25leSBlYXJuZWQgcGVyIGV2ZW50IGJ5IHRoZSBwbGF5ZXIuDQoNCiMjIFJlc2VhcmNoIFF1ZXN0aW9uDQpXZSB3YW50IHRvIGludmVzdGlnYXRlIHdoYXQgdmFyaWFibGVzIGFmZmVjdCB0aGUgcGxheWVycyB3aW5uaW5ncyBvZiB0aGlzIGdpdmVuIHNlYXNvbi4gV2Ugd2lsbCBsb29rIGF0IG1haW5seSB0aGUgYXZlcmFnZSBkcml2ZSBhbmQgaG93IGl0IGNvcnJlbGF0ZXMgdG8gd2lubmluZ3MuDQoNCiMjIERhdGEgUHJlcGVyYXRpb24NCg0KYGBge3J9DQoNCnVybCA8LSAiaHR0cHM6Ly91c2Vycy5zdGF0LnVmbC5lZHUvfndpbm5lci9kYXRhL3BnYTIwMDQuZGF0Ig0KcGdhX2RhdGEgPC0gcmVhZC50YWJsZSh1cmwsIGhlYWRlciA9IEZBTFNFLCBmaWxsID0gVFJVRSkNCnBnYV9kYXRhJFYxIDwtIE5VTEwNCmNvbG5hbWVzKHBnYV9kYXRhKSA8LSBjKCJQbGF5ZXJfbmFtZSIsICJQbGF5ZXJfQWdlIiwgIkF2ZXJhZ2VfRHJpdmUiLCAiRHJpdmluZ19BY2N1cmFjeSIsICJHcmVlbnNfb25fcmVnIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAiQXZlcmFnZV9udW1iZXJfcHV0dHMiLCAiU2F2ZV9QZXJjZW50IiwgIk1vbmV5X1JhbmsiLA0KICAgICAgICAgICAgICAgICAgICAgICAgIk51bWJlcl9ldmVudHMiLCAiVG90YWxfV2lubmluZ3MiLCAiQXZlcmFnZV9XaW5uaW5ncyIpDQpoZWFkKHBnYV9kYXRhKQ0KYGBgDQoNCldlIHRha2UgYSBicm9hZCBsb29rIGF0IHRoZSBkYXRhIGFuZCBzZWUgdGhlcmUgd2FzIHR3byBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgb25lIG9mIHdoaWNoIGhhdmluZyB0aGUgZmlyc3QgbmFtZSBvZiBhIHBsYXllciBhbmQgdGhlbiBhIHNlY29uZCBvbmUgd2l0aCB0aGUgbGFzdCBuYW1lLiBXZSB2b2lkIHRoZSBmaXJzdCBuYW1lIGFuZCBqdXN0IGxvb2sgYXQgdGhlIGxhc3QgbmFtZXMuDQoNCmBgYHtyfQ0KcGFuZGVyKGhlYWQocGdhX2RhdGEpKQ0KDQpgYGANCmBgYHtyfQ0KY29yKHBnYV9kYXRhWywgYygiQXZlcmFnZV9Ecml2ZSIgLCAiRHJpdmluZ19BY2N1cmFjeSIgLCAiR3JlZW5zX29uX3JlZyIgLCAiQXZlcmFnZV9udW1iZXJfcHV0dHMiICwgIlNhdmVfUGVyY2VudCIgLCAiTW9uZXlfUmFuayIgLCAiTnVtYmVyX2V2ZW50cyIgLCAiVG90YWxfV2lubmluZ3MiICwgIkF2ZXJhZ2VfV2lubmluZ3MiKV0sIHVzZSA9ICJwYWlyd2lzZS5jb21wbGV0ZS5vYnMiKQ0KDQoNCmBgYA0KV2Ugc2VlIHRoYXQgYSBsb3Qgb2Ygb3VyIHZhcmlhYmxlcyBoYXZlIGhpZ2ggY29ycmVsYXRpb24gd2hpY2ggY2FuIGNhdXNlIG11bHRpY29sbGluZWFyaXR5LiBXZSBtdXN0IGRlYWwgd2l0aCB0aGlzIGlzc3VlIGJlZm9yZSBkb2luZyBvdXIgYW5hbHlzaXMuIA0KDQpgYGB7cn0NCmNvbFN1bXMoaXMubmEocGdhX2RhdGEpKQ0KcGdhX2RhdGFfY2xlYW4gPC0gbmEub21pdChwZ2FfZGF0YSkNCmBgYA0KDQpGb3IgdGhlIHNha2Ugb2YgbWlzc2luZyBvYnNlcnZhdGlvbnMgd2Ugd2lsbCBiZSByZW1vdmluZyBpdCBmb3Igb3VyIGRhdGEgYW5hbHlzaXMgYmVjYXVzZSB3ZSBoYXZlIGEgcGxlbnRpZnVsIGFtb3VudCBvZiBvYnNlcnZhdGlvbnMuDQoNCmBgYHtyfQ0KZ2dwbG90KHBnYV9kYXRhX2NsZWFuLCBhZXMoeCA9IEF2ZXJhZ2VfRHJpdmUsIHkgPSBBdmVyYWdlX1dpbm5pbmdzKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBjb2wgPSAiZ3JlZW4iKSArDQogIGxhYnModGl0bGUgPSAiU2NhdHRlciBQbG90IG9mIEF2ZXJhZ2UgRHJpdmUgdnMuIEF2ZXJhZ2UgV2lubmluZ3MiLCB4ID0gIkF2ZXJhZ2UgRHJpdmUiLCB5ID0gIkF2ZXJhZ2UgV2lubmluZ3MiKQ0Kc3RyKHBnYV9kYXRhX2NsZWFuKQ0KcmVtb3ZlX2xvd19kcml2ZXMgPC0gZnVuY3Rpb24ocGdhX2RhdGFfY2xlYW4pIHsNCiAgZmlsdGVyZWRfZGF0YSA8LSBwZ2FfZGF0YV9jbGVhbiAlPiUNCiAgICBmaWx0ZXIoQXZlcmFnZV9Ecml2ZSA+PSAxNTApDQogIA0KICByZXR1cm4oZmlsdGVyZWRfZGF0YSkNCn0NCg0KIyBFeGFtcGxlIHVzYWdlDQpwZ2FfZGF0YV9maWx0ZXJlZCA8LSByZW1vdmVfbG93X2RyaXZlcyhwZ2FfZGF0YV9jbGVhbikNCmdncGxvdChwZ2FfZGF0YV9maWx0ZXJlZCwgYWVzKHggPSBBdmVyYWdlX0RyaXZlLCB5ID0gQXZlcmFnZV9XaW5uaW5ncykpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgY29sID0gImdyZWVuIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlNjYXR0ZXIgUGxvdCBvZiBBdmVyYWdlIERyaXZlIHZzLiBBdmVyYWdlIFdpbm5pbmdzIChGaWx0ZXJlZCkiLA0KICAgIHggPSAiQXZlcmFnZSBEcml2ZSIsDQogICAgeSA9ICJBdmVyYWdlIFdpbm5pbmdzIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpIA0KYGBgDQoNCg0KV2UgbG9vayBhdCBhbiBpbml0aWFsIGdyYXBoIG9mIGF2ZXJhZ2Ugd2lubmlncyB2cyBhdmVyYWdlIGRyaXZlLiBJdCBsb29rcyBsaWtlIHdlIGhhdmUgYSBnb29kIGNsdXN0ZXIgb2YgZGF0YSBidXQgd2UgaGF2ZSBhIGNvdXBsZSBvZiBvdXRsaWVycyBpbiBvdXIgZGF0YSB0aGF0IGlzIHNjZXdpbmcgb3VyIGxpbmUuDQoNCmBgYHtyfQ0KcGdhX2RhdGFfZmlsdGVyZWQkQWdlX0Fib3ZlXzMwIDwtIHBnYV9kYXRhX2ZpbHRlcmVkJFBsYXllcl9BZ2UgPiAzMA0KDQpoZWFkKHBnYV9kYXRhX2ZpbHRlcmVkJEFnZV9BYm92ZV8zMCkNCnBnYV9kYXRhX2ZpbHRlcmVkIDwtIHBnYV9kYXRhX2ZpbHRlcmVkICU+JSBzZWxlY3QoLVBsYXllcl9uYW1lLCAtRHJpdmluZ19BY2N1cmFjeSwgLVRvdGFsX1dpbm5pbmdzLCAtQXZlcmFnZV9udW1iZXJfcHV0dHMsIC1Nb25leV9SYW5rLCAtUGxheWVyX0FnZSkNCmthYmxlKGhlYWQocGdhX2RhdGFfZmlsdGVyZWQpKQ0KDQoNCmBgYA0KDQpXZSByZW1vdmUgcGxheWVyIG5hbWUgYW5kIG1vbmV5IHJhbmsgYmVjYXVzZSB3ZSBhcmUgInJhbmtpbmciIHRoZW0gb3Igc29ydGluZyB0aGVtIGJ5ICsvLSAzMCBhZ2UuIFdlIHJlbW92ZSBkcml2aW5nIGFjY3VyYWN5IGJlY2F1c2Ugd2UgaGF2ZSBhIHZhcmlhYmxlIHdpdGggYXZlcmFnZSBkcml2ZSBiZWNhdXNlIHRoYXQgaXMgd2hhdCB3ZSBhcmUgbWFraW5nIGFzc3VtcHRpb25zIG9uIGFuZCBhbHNvIGl0IGNvdWxkIGNhdXNlIG11bHRpY29sbGluZWFyaXR5IGJlY2F1c2UgZHJpdmluZyBhY2N1cmFjeSBhbmQgYXZlcmFnZSBkcml2ZSBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQuIFRoZSBzYW1lIGNhbiBiZSBzYWlkIGZvciB0b3RhbCB3aW5uaW5ncyB2ZXJzdXMgYXZlcmFnZSB3aW5uaW5ncyBhbmQgYWxzbyBzYXZlIHBlcmNlbnQgYW5kIG51bWJlciBvZiBwdXR0cy4gVGhleSBhcmUgYm90aCBjb3JyZWxhdGVkIGFuZCBjYW4gY2F1c2UgbXVsdGljb2xsaW5lYXJpdHkuDQoNCiMjIyBNb2RlbCBCdWxkaW5nDQoNCiMjIExpbmVhciBNb2RlbA0KDQpgYGB7cn0NCmNvbG5hbWVzKHBnYV9kYXRhX2ZpbHRlcmVkKQ0KZnVsbC5tb2RlbCA9IGxtKEF2ZXJhZ2VfV2lubmluZ3MgfiAgQXZlcmFnZV9Ecml2ZSArIEdyZWVuc19vbl9yZWcgKyAgU2F2ZV9QZXJjZW50ICsgTnVtYmVyX2V2ZW50cyArIEFnZV9BYm92ZV8zMCwgZGF0YSA9IHBnYV9kYXRhX2ZpbHRlcmVkKQ0Ka2FibGUoc3VtbWFyeShmdWxsLm1vZGVsKSRjb2VmLCBjYXB0aW9uID0iU3RhdGlzdGljcyBvZiBSZWdyZXNzaW9uIENvZWZmaWNpZW50cyIpDQoNCg0KYGBgDQpXZSB0cmFuc2Zvcm0gdGhlIGFnZSBjYXRlZ29yeSBpbnRvIGEgdHJ1ZSBvciBmYWxzZSBvZiBwYXllcnMgd2l0aCBhYm92ZSBvciBiZWxvdyBhZ2UgMzAuIEFsbCBvZiBvdXIgdmFsdWVzIGFyZSBzaWduaWZpY2FudCBiZXNpZGVzIEFnZS4NCmBgYHtyfQ0KcGFyKG1mcm93PWMoMiwyKSkNCnBsb3QoZnVsbC5tb2RlbCkNCg0KDQpgYGANCg0KSXQgbG9va3MgbGlrZSB3ZSBoYXZlIGZhdWx0cyBpbiBvdXIgYXNzdW1wdGlvbnMuIEJhc2VkIG9uIHRoZSBRUSBwbG90IHRoZXJlIHNlZW1zIHRvIGJlIGEgbm9uLW5vcm1hbCBkaXN0cmlidXRpb24gd2l0aCBzZXZlcmFsIG91dGxpZXJzLiBUaGVyZSBhbHNvIHNlZW1zIHRvIGJlIG5vbi1jb25zdGFudCB2YXJpYW5jZS4NCg0KYGBge3J9DQp2aWYoZnVsbC5tb2RlbCkNCmJhcnBsb3QodmlmKGZ1bGwubW9kZWwpLCBtYWluID0gIlZJRiBWYWx1ZXMiLCBob3JpeiA9IEZBTFNFLCBjb2wgPSAic3RlZWxibHVlIikNCmBgYA0KDQpXZSBzZWUgaW4gdGhlIHZpZiB2YWx1ZXMgYWJvdmUgdGhhdCBhbGwgb2YgdGhlbSBhcmUgYmVsb3cgNSB3aGljaCBzaG93cyBsaXR0bGUgdG8gbm8gbXVsdGljb2xsaW5lYWlydHkgc28gd2UgY2FuIGNvbnRpbnVlLg0KDQojIyBCb3ggQ294IFRyYW5zZm9ybWF0aW9uDQoNCldlIHBlcmZvcm0gdGhpcyB0cmFuc2Zvcm1hdGlvbiBiZWNhdXNlIG91ciBkYXRhIGhhcyBub24gY29uc3RhbnQgdmFyaWFuY2UNCg0KYGBge3J9DQpwYXIocHR5ID0gInMiLCBtZnJvdyA9IGMoMiwgMiksIG9tYT1jKC4xLC4xLC4xLC4xKSwgbWFyPWMoNCwgMCwgMiwgMCkpDQojIw0KYm94Y294KEF2ZXJhZ2VfV2lubmluZ3MgfiBsb2coQXZlcmFnZV9Ecml2ZSkgKyBHcmVlbnNfb25fcmVnICsgIFNhdmVfUGVyY2VudCArIE51bWJlcl9ldmVudHMgKyBBZ2VfQWJvdmVfMzAsIGRhdGEgPSBwZ2FfZGF0YV9maWx0ZXJlZCwgbGFtYmRhID0gc2VxKC0xLCAxLCBsZW5ndGggPSAxMCksIA0KICAgICAgIHhsYWI9ZXhwcmVzc2lvbihwYXN0ZShsYW1iZGEsICI6IGxvZyBBdmVyYWdlIERyaXZlIikpKQ0KIyMNCmJveGNveChBdmVyYWdlX1dpbm5pbmdzIH4gQXZlcmFnZV9Ecml2ZSArIEdyZWVuc19vbl9yZWcgKyAgU2F2ZV9QZXJjZW50ICsgTnVtYmVyX2V2ZW50cyArIEFnZV9BYm92ZV8zMCwgZGF0YSA9IHBnYV9kYXRhX2ZpbHRlcmVkLCBsYW1iZGEgPSBzZXEoLTEsIDEsIGxlbmd0aCA9IDEwKSwgDQogICAgICAgeGxhYj1leHByZXNzaW9uKHBhc3RlKGxhbWJkYSwgIjogQXZlcmFnZSBEcml2ZSIpKSkNCiMjDQpib3hjb3goQXZlcmFnZV9XaW5uaW5ncyB+IEF2ZXJhZ2VfRHJpdmUgKyBHcmVlbnNfb25fcmVnICsgIFNhdmVfUGVyY2VudCArIE51bWJlcl9ldmVudHMgKyBsb2coMStBZ2VfQWJvdmVfMzApLCBkYXRhID0gcGdhX2RhdGFfZmlsdGVyZWQsIGxhbWJkYSA9IHNlcSgtMSwgMSwgbGVuZ3RoID0gMTApLCANCiAgICAgICB4bGFiPWV4cHJlc3Npb24ocGFzdGUobGFtYmRhLCAiOiBsb2ctYWdlIikpKQ0KIyMNCmJveGNveChBdmVyYWdlX1dpbm5pbmdzIH4gbG9nKEF2ZXJhZ2VfRHJpdmUpICsgR3JlZW5zX29uX3JlZyArICBTYXZlX1BlcmNlbnQgKyBOdW1iZXJfZXZlbnRzICsgbG9nKDErQWdlX0Fib3ZlXzMwKSwgZGF0YSA9IHBnYV9kYXRhX2ZpbHRlcmVkLCBsYW1iZGEgPSBzZXEoLTEsIDEsIGxlbmd0aCA9IDEwKSwgDQogICAgICB4bGFiPWV4cHJlc3Npb24ocGFzdGUobGFtYmRhLCAiOiBsb2ctYWdlLCBsb2cgQXZlcmFnZSBEcml2ZSIpKSkNCg0KYGBgDQoNClZhbHVlcyBvZiBsYW1kYSBhcmUgYXJvdW5kIDAgc28gd2Ugc2hvdWRsIHVzZSBhIGxvZyB0cmFuc2Zvcm1hdGlvbg0KIyMgU3F1YXJlIFJvb3QgTW9kZWwNCg0KYGBge3J9DQpzcXJ0Lndpbm5pbmdzLmxvZy5kcml2ZSA9IGxtKChBdmVyYWdlX1dpbm5pbmdzKV4wLjUgfiBHcmVlbnNfb25fcmVnICsgIFNhdmVfUGVyY2VudCArIE51bWJlcl9ldmVudHMgKyBBZ2VfQWJvdmVfMzAgKyBsb2coQXZlcmFnZV9Ecml2ZSksIGRhdGEgPSBwZ2FfZGF0YV9maWx0ZXJlZCkNCmthYmxlKHN1bW1hcnkoc3FydC53aW5uaW5ncy5sb2cuZHJpdmUpJGNvZWYsIGNhcHRpb24gPSAibG9nLXRyYW5zZm9ybWVkIG1vZGVsIikNCg0KDQpgYGANCg0KYGBge3J9DQpwYXIobWZyb3cgPSBjKDIsMikpDQpwbG90KHNxcnQud2lubmluZ3MubG9nLmRyaXZlKQ0KDQpgYGANCg0KVGhpcyBtb2RlbCB3b3JzZW5lZCBvdXIgUVEtcGxvdCBub3JtYWxpdHkgYW5kIG91ciBub24gY29uc3RhbnQgdmFyaWFuY2UgcmVtYWluZWQgdGhlIHNhbWUsIHNvIHdlIGNhbiB0cnkgYW5vdGhlciBtb2RlbCB0byBzZWUgaWYgaXQgaGVscHMuDQoNCiMjIExvZyBUcmFuc2Zvcm1hdGlvbg0KDQpgYGB7cn0NCmxvZy53aW5uaW5ncyA9IGxtKGxvZyhBdmVyYWdlX1dpbm5pbmdzKSB+IEF2ZXJhZ2VfRHJpdmUgKyBHcmVlbnNfb25fcmVnICsgIFNhdmVfUGVyY2VudCArIE51bWJlcl9ldmVudHMgKyBBZ2VfQWJvdmVfMzAsIGRhdGEgPSBwZ2FfZGF0YV9maWx0ZXJlZCkNCmthYmxlKHN1bW1hcnkobG9nLndpbm5pbmdzKSRjb2VmLCBjYXB0aW9uID0gImxvZy10cmFuc2Zvcm1lZCBtb2RlbCIpDQoNCg0KYGBgDQoNCmBgYHtyfQ0KcGFyKG1mcm93ID0gYygyLDIpKQ0KcGxvdChsb2cud2lubmluZ3MpDQoNCmBgYA0KDQpUaGlzIG1vZGVsIGxvb2tzIGRyYXN0aWNhbGx5IGJldHRlciB0aGVuIHRoZSBvdGhlciB0d28gbW9kZWxzLiBXaGlsZSBub3JtYWxpdHkgc3RpbGwgY2Fubm90IGJlIGFzc3VtZWQgdGhlIFFRIFBsb3QgbG9va3MgYmV0dGVyLiBUaGUgc2FtZSB0aGluZyBoYXMgaGFwcGVuZWQgd2l0aCBvdXIgUmVzaWR1YWwgcGxvdCB3aGlsZSBjb25zdGFudCB2YXJpYW5jZSBjYW4gc3RpbGwgbm90IGJlIGFzc3VtZWQgaXQgaXMgZHJhc3RpY2FsbHkgYmV0dGVyLg0KDQojIyBDb21wYXJpc29uIG9mIG1vZGVscw0KDQpgYGB7cn0NCg0KcGFyKHB0eSA9ICJzIiwgbWZyb3cgPSBjKDEsIDMpKQ0KDQpxcW5vcm0oZnVsbC5tb2RlbCRyZXNpZHVhbHMsIG1haW4gPSAiRnVsbC1Nb2RlbCIpDQpxcWxpbmUoZnVsbC5tb2RlbCRyZXNpZHVhbHMpDQoNCnFxbm9ybShsb2cud2lubmluZ3MkcmVzaWR1YWxzLCBtYWluID0gIkxvZyBXaW5uaW5ncyIpDQpxcWxpbmUobG9nLndpbm5pbmdzJHJlc2lkdWFscykNCg0KcXFub3JtKHNxcnQud2lubmluZ3MubG9nLmRyaXZlJHJlc2lkdWFscywgbWFpbiA9ICJzcXJ0IHdpbm5pbmdzIGxvZyBkcml2ZSIpDQpxcWxpbmUoc3FydC53aW5uaW5ncy5sb2cuZHJpdmUkcmVzaWR1YWxzKQ0KDQoNCmBgYA0KDQpXZSBjYW4gc2VlIHRoYXQgTG9nIFdpbm5pbmdzIGlzIHRoZSBiZXN0IG1vZGVsIGZvciBvdXIgZGF0YS4NCg0KIyMgR29vZG5lc3Mgb2YgRml0IENvbXBhcmlzb24NCg0KYGBge3J9DQpzZWxlY3Q9ZnVuY3Rpb24obSl7IA0KIGUgPSBtJHJlc2lkICAgICAgICAgICAgICAgICAgICAgICAgICAgDQogbjAgPSBsZW5ndGgoZSkgICAgICAgICAgICAgICAgICAgICAgICANCiBTU0U9KG0kZGYpKihzdW1tYXJ5KG0pJHNpZ21hKV4yICAgICAgIA0KIFIuc3E9c3VtbWFyeShtKSRyLnNxdWFyZWQgICAgICAgICAgICAgDQogUi5hZGo9c3VtbWFyeShtKSRhZGouciAgICAgICAgICAgICAgIA0KIE1TRT0oc3VtbWFyeShtKSRzaWdtYSleMiAgICAgICAgICAgICAgDQogQ3A9KFNTRS9NU0UpLShuMC0yKihuMC1tJGRmKSkgICAgICAgICANCiBBSUM9bjAqbG9nKFNTRSktbjAqbG9nKG4wKSsyKihuMC1tJGRmKSAgICAgICAgICANCiBTQkM9bjAqbG9nKFNTRSktbjAqbG9nKG4wKSsobG9nKG4wKSkqKG4wLW0kZGYpICANCiBYPW1vZGVsLm1hdHJpeChtKSAgICAgICAgICAgICAgICAgICAgIA0KIEg9WCUqJXNvbHZlKHQoWCklKiVYKSUqJXQoWCkgICAgICAgICAgDQogZD1lLygxLWRpYWcoSCkpICAgICAgICAgICAgICAgICAgICAgICANCiBQUkVTUz10KGQpJSolZCAgIA0KIHRibCA9IGFzLmRhdGEuZnJhbWUoY2JpbmQoU1NFPVNTRSwgUi5zcT1SLnNxLCBSLmFkaiA9IFIuYWRqLCBDcCA9IENwLCBBSUMgPSBBSUMsIFNCQyA9IFNCQywgUFJEID0gUFJFU1MpKQ0KIG5hbWVzKHRibCk9YygiU1NFIiwgIlIuc3EiLCAiUi5hZGoiLCAiQ3AiLCAiQUlDIiwgIlNCQyIsICJQUkVTUyIpDQogdGJsDQogfQ0KDQpgYGANCg0KYGBge3J9DQoNCm91dHB1dC5zdW0gPSByYmluZChzZWxlY3QoZnVsbC5tb2RlbCksIHNlbGVjdChzcXJ0Lndpbm5pbmdzLmxvZy5kcml2ZSksIHNlbGVjdChsb2cud2lubmluZ3MpKQ0Kcm93Lm5hbWVzKG91dHB1dC5zdW0pID0gYygiZnVsbC5tb2RlbCIsICJzcXJ0Lndpbm5pbmdzLmxvZy5kcml2ZSIsICJsb2cud2lubmluZ3MiKQ0Ka2FibGUob3V0cHV0LnN1bSwgY2FwdGlvbiA9ICJHb29kbmVzcy1vZi1maXQgTWVhc3VyZXMgb2YgQ2FuZGlkYXRlIE1vZGVscyIpDQoNCmBgYA0KDQpFdmVuIHRob3VnaCB0aGUgUiwgUiBzcXVhcmVkLCBhbmQgQ3AgbG9vayBiZXR0ZXIgaW4gdGhlIG90aGVyIHR3byBtb2RlbHMgb3VyIHZhbHVlIGFyZSBzdGlsbCByZWxhdGl2ZWx5IGdvb2QgZm9yIGxvZy53aW5uaW5ncy4gTG9nLndpbm5pbmdzIGhhcyBiZXR0ZXIgcmVzaWR1YWxzLCBRUSBwbG90LCBidXQgbGVzcyBnb29kbmVzcyBvZiBmaXQgbWVhc3VyZXMgYnV0IHRoZXkgYXJlIG5vdCBzaWduaWZpY2FudGx5IGJhZCBzbyB3ZSB3aWxsIHN0aWNrIHdpdGggdGhlIGxvZyBtb2RlbC4NCg0KIyMgRmluYWwgTW9kZWwNCg0KYGBge3J9DQprYWJsZShzdW1tYXJ5KGxvZy53aW5uaW5ncykkY29lZiwgY2FwdGlvbiA9ICJJbmZlcmVudGlhbCBTdGF0aXN0aWNzIG9mIEZpbmFsIE1vZGVsIikNCmBgYA0KDQpXaGlsZSB3ZSBkbyBoYXZlIGEgbGFyZ2Ugc2FtcGxlIG9mIDE5NiBhbmQgYWxsIG9mIG91ciBwdmFsdWVzIGFyZSBjbG9zZSB0byAwIHdlIGRvIGhhdmUgb25lIHZhcmlhYmxlICJBZ2UiIHdoaWNoIGlzIG5vdCBzaWduaWZpY2FudC4NCg0KIyMjIFN1bW1hcnkgb2YgTW9kZWwNClRoZSBmaW5hbCBtb2RlbCBjYW4gYmUgcmVwcmVzZW50ZWQgYXMgbG9nKHByaWNlKT0zLjkyNDUg4oiSIDAuMDI0NsOXQXZlcmFnZV9Ecml2ZSArIDAuMTg2NsOXR3JlZW5zX29uX3JlZyArIA0KMC4wMzg3w5dTYXZlX3BlcmNlbnQg4oiSIDAuMDE4McOXTnVtYmVyX2V2ZW50cyArIDAuMTgyNsOXQWdlX0Fib3ZlXzMwVFJVRQ0KDQpGcm9tIHRoZSBhYm92ZSBkYXRhIHdlIGNhbiBhbHNvIGNvbmNsdWRlIHRoYXQgYXMgYXZlcmFnZSB3aW5uaW5ncyBnbyB1cCBvdXIgYXZlcmFnZSBkcml2ZSBnb2VzIGRvd24gYnkgLTIuNSUgd2hpbGUgb3RoZXIgdmFyaWFibGVzIHN1Y2ggYXMgZ3JlZW5zIG9uIHJlZ3VsYXRpb24gYW5kIHNhdmUgcGVyY2VudCBnbyB1cC4NCg0KYGBge3J9DQpsb2cud2lubmluZ3MgPSBsbShsb2coQXZlcmFnZV9XaW5uaW5ncykgfiBBdmVyYWdlX0RyaXZlICsgR3JlZW5zX29uX3JlZyArIFNhdmVfUGVyY2VudCArIE51bWJlcl9ldmVudHMgKyBBZ2VfQWJvdmVfMzAsIGRhdGEgPSBwZ2FfZGF0YV9maWx0ZXJlZCkNCg0KQiA9IDEwMDAgICMNCg0KbnVtLnAgPSBsZW5ndGgoY29lZihsb2cud2lubmluZ3MpKSAgIyBudW1iZXIgb2YgcGFyYW1ldGVycyBpbiB0aGUgbW9kZWwgKGluY2x1ZGVzIGludGVyY2VwdCkNCnNtcGwubiA9IG5yb3cocGdhX2RhdGFfZmlsdGVyZWQpICAgICAgICMgc2FtcGxlIHNpemUNCg0KIyBNYXRyaXggdG8gc3RvcmUgYm9vdHN0cmFwIGNvZWZmaWNpZW50cw0KY29lZi5tdHJ4ID0gbWF0cml4KDAsIG5yb3cgPSBCLCBuY29sID0gbnVtLnApDQoNCiMgQm9vdHN0cmFwIGxvb3ANCmZvciAoaSBpbiAxOkIpIHsNCiAgYm9vdGMuaWQgPSBzYW1wbGUoMTpzbXBsLm4sIHNtcGwubiwgcmVwbGFjZSA9IFRSVUUpICAjIFJlc2FtcGxlIHdpdGggcmVwbGFjZW1lbnQNCiAgYm9vdF9kYXRhID0gcGdhX2RhdGFfZmlsdGVyZWRbYm9vdGMuaWQsIF0gICMgQ3JlYXRlIHRoZSBib290c3RyYXAgc2FtcGxlDQogIGxvZy53aW5uaW5ncy5idGMgPSBsbShsb2coQXZlcmFnZV9XaW5uaW5ncykgfiBBdmVyYWdlX0RyaXZlICsgR3JlZW5zX29uX3JlZyArIFNhdmVfUGVyY2VudCArIE51bWJlcl9ldmVudHMgKyBBZ2VfQWJvdmVfMzAsIGRhdGEgPSBib290X2RhdGEpDQogIGNvZWYubXRyeFtpLCBdID0gY29lZihsb2cud2lubmluZ3MuYnRjKSAgIyBTdG9yZSB0aGUgY29lZmZpY2llbnRzDQp9DQoNCmBgYA0KDQojIyMgQm9vdHN0cmFwcGluZyBGaW5hbCBNb2RlbA0KDQpgYGB7cn0NCg0KYm9vdC5oaXN0ID0gZnVuY3Rpb24oY210cngsIGJ0LmNvZWYubXRyeCwgdmFyLmlkLCB2YXIubm0pew0KDQogIHgxLjEgPC0gc2VxKG1pbihidC5jb2VmLm10cnhbLHZhci5pZF0pLCBtYXgoYnQuY29lZi5tdHJ4Wyx2YXIuaWRdKSwgbGVuZ3RoPTMwMCApDQogIHkxLjEgPC0gZG5vcm0oeDEuMSwgbWVhbihidC5jb2VmLm10cnhbLHZhci5pZF0pLCBzZChidC5jb2VmLm10cnhbLHZhci5pZF0pKQ0KDQogIGhpZ2hlc3RiYXIgPSBtYXgoaGlzdChidC5jb2VmLm10cnhbLHZhci5pZF0sIHBsb3QgPSBGQUxTRSkkZGVuc2l0eSkgDQogIHlsaW1pdCA8LSBtYXgoYyh5MS4xLGhpZ2hlc3RiYXIpKQ0KICBoaXN0KGJ0LmNvZWYubXRyeFssdmFyLmlkXSwgcHJvYmFiaWxpdHkgPSBUUlVFLCBtYWluID0gdmFyLm5tLCB4bGFiPSIiLCANCiAgICAgICBjb2wgPSAiYXp1cmUxIix5bGltPWMoMCx5bGltaXQpLCBib3JkZXI9ImxpZ2h0c2VhZ3JlZW4iKQ0KICBsaW5lcyh4ID0geDEuMSwgeSA9IHkxLjEsIGNvbCA9ICJyZWQzIikNCiAgbGluZXMoZGVuc2l0eShidC5jb2VmLm10cnhbLHZhci5pZF0sIGFkanVzdD0yKSwgY29sPSJibHVlIikgDQogIA0KfQ0KDQpgYGANCg0KYGBge3J9DQpwYXIobWZyb3c9YygyLDMpKSAgDQpib290Lmhpc3QoYnQuY29lZi5tdHJ4PWNvZWYubXRyeCwgdmFyLmlkPTEsIHZhci5ubSA9IkludGVyY2VwdCIgKQ0KYm9vdC5oaXN0KGJ0LmNvZWYubXRyeD1jb2VmLm10cngsIHZhci5pZD0yLCB2YXIubm0gPSJBdmVyYWdlIERyaXZlIiApDQpib290Lmhpc3QoYnQuY29lZi5tdHJ4PWNvZWYubXRyeCwgdmFyLmlkPTMsIHZhci5ubSA9IkdyZWVucyBPbiBSZWd1bGF0aW9uIiApDQpib290Lmhpc3QoYnQuY29lZi5tdHJ4PWNvZWYubXRyeCwgdmFyLmlkPTQsIHZhci5ubSA9IlNhdmUgUGVyY2VudGFnZSIgKQ0KYm9vdC5oaXN0KGJ0LmNvZWYubXRyeD1jb2VmLm10cngsIHZhci5pZD01LCB2YXIubm0gPSJOdW1iZXIgb2YgRXZlbnRzIiApDQpib290Lmhpc3QoYnQuY29lZi5tdHJ4PWNvZWYubXRyeCwgdmFyLmlkPTYsIHZhci5ubSA9IkFnZSBBYm92ZSAzMCIgKQ0KYGBgDQoNCldlIHNlZSBmcm9tIG91ciBoaXN0b2dyYW1zIHRoYXQgdGhleSBsb29rIHRvIGhhdmUgYSBlcXVhbCBkaXN0cmlidXRpb24uIFRoZSBibHVlIGN1cnZlIHJlcHJlc2VudHMgdGhlIHAtdmFsdWVzIHJlcG9ydGVkLiBXaGlsZSB0aGUgYmx1ZSBjdXJ2ZSByZXByZXNlbnRzIHRoZSBib290c3RyYXAgaW50ZXJ2YWxzLiBUaGV5IGFyZSB2ZXJ5IHNpbWlsYXIuDQoNCiMjIDk1JSBDSQ0KDQpgYGB7cn0NCg0KbnVtLnAgPSBkaW0oY29lZi5tdHJ4KVsyXSAgDQoNCmJ0Yy5jaSA9IE5VTEwNCmJ0Yy53ZCA9IE5VTEwNCg0KZm9yIChpIGluIDE6bnVtLnApIHsNCiAgbGNpLjAyNSA9IHJvdW5kKHF1YW50aWxlKGNvZWYubXRyeFssIGldLCAwLjAyNSwgdHlwZSA9IDIpLCA4KQ0KICB1Y2kuOTc1ID0gcm91bmQocXVhbnRpbGUoY29lZi5tdHJ4WywgaV0sIDAuOTc1LCB0eXBlID0gMiksIDgpDQogIGJ0Yy53ZFtpXSA9IHVjaS45NzUgLSBsY2kuMDI1DQogIGJ0Yy5jaVtpXSA9IHBhc3RlKCJbIiwgcm91bmQobGNpLjAyNSwgNCksICIsICIsIHJvdW5kKHVjaS45NzUsIDQpLCAiXSIpDQp9DQoNCm1lYW4uY29lZnMgPSBhcHBseShjb2VmLm10cngsIDIsIG1lYW4pDQoNCnJlc3VsdHMgPSBhcy5kYXRhLmZyYW1lKGNiaW5kKE1lYW5fQ29lZiA9IGZvcm1hdEMobWVhbi5jb2VmcywgNCwgZm9ybWF0ID0gImYiKSwgYnRjLmNpLjk1ID0gYnRjLmNpKSkNCg0Ka2FibGUocmVzdWx0cywgY2FwdGlvbiA9ICJSZWdyZXNzaW9uIENvZWZmaWNpZW50IE1hdHJpeCB3aXRoIEJvb3RzdHJhcCBDb25maWRlbmNlIEludGVydmFscyIpDQoNCmBgYA0KDQpXZSBjYW4gc2VlIHRoYXQgb3VyIHZhbHVlcyBhcmUgY29uc2lzdGVudCB3aXRoIHRoZSBwLXZhbHVlcyB0aGF0IHdlIGdvdC4gT3VyIDk1JSBDSSBzcGFucyBvdmVyIDAgaW4gdGhlIEFnZSB2YXJpYWJsZSB3aGljaCBtYXRjaGVzIHVwIHdpdGggb3VyIHAtdmFsdWUgbm90IGJlaW5nIHNpZ25pZmljYW50DQoNCmBgYHtyfQ0KaGlzdChzb3J0KGxvZy53aW5uaW5ncyRyZXNpZHVhbHMpLG49NDAsDQogICAgIHhsYWI9IlJlc2lkdWFscyIsDQogICAgIGNvbCA9ICJsaWdodGJsdWUiLA0KICAgICBib3JkZXI9Im5hdnkiLA0KICAgICBtYWluID0gIkhpc3RvZ3JhbSBvZiBSZXNpZHVhbHMiKQ0KDQoNCmBgYA0KDQpXZSBhcHBlYXIgdG8gaGF2ZSBhIG5vcm1hbCBkaXN0cmlidXRpb24gYnV0IHdlIGRvIGFwcGVhciB0byBoYXZlIHNvbWUgb3V0bGllcnMgb24gZWl0aGVyIHNpZGUgb2YgdGhlIGN1cnZlLg0KDQojIyBSZXNpZHVhbCBCb290c3RyYXAgUmVncmVzc2lvbg0KDQpgYGB7cn0NCmxvZy53aW5uaW5ncyA8LSBsbShsb2coQXZlcmFnZV9XaW5uaW5ncykgfiBBdmVyYWdlX0RyaXZlICsgR3JlZW5zX29uX3JlZyArIFNhdmVfUGVyY2VudCArIE51bWJlcl9ldmVudHMgKyBBZ2VfQWJvdmVfMzAsIGRhdGEgPSBwZ2FfZGF0YV9maWx0ZXJlZCkNCm1vZGVsLnJlc2lkID0gbG9nLndpbm5pbmdzJHJlc2lkdWFscw0KQj0xMDAwDQpudW0ucCA9IGRpbShtb2RlbC5tYXRyaXgobG9nLndpbm5pbmdzKSlbMl0gICANCnNhbXAubiA9IGRpbShtb2RlbC5tYXRyaXgobG9nLndpbm5pbmdzKSlbMV0gIA0KYnRyLm10cnggPSBtYXRyaXgocmVwKDAsNipCKSwgbmNvbD1udW0ucCkNCmZvciAoaSBpbiAxOkIpew0KDQogIGJ0LmxnLndpbm5pbmdzID0gbG9nLndpbm5pbmdzJGZpdHRlZC52YWx1ZXMgKyANCiAgICAgICAgc2FtcGxlKGxvZy53aW5uaW5ncyRyZXNpZHVhbHMsIHNhbXAubiwgcmVwbGFjZSA9IFRSVUUpICANCiAgDQogIHBnYV9kYXRhX2ZpbHRlcmVkJGJ0LmxnLndpbm5pbmdzID0gIGJ0LmxnLndpbm5pbmdzICAgIyAgc2VuZCB0aGUgYm9vdCByZXNwb25zZSB0byB0aGUgZGF0YQ0KICBidHIubW9kZWwgPSBsbShidC5sZy53aW5uaW5ncyB+IEF2ZXJhZ2VfRHJpdmUgKyBHcmVlbnNfb25fcmVnICsgU2F2ZV9QZXJjZW50ICsgTnVtYmVyX2V2ZW50cyArIEFnZV9BYm92ZV8zMCwgZGF0YSA9IHBnYV9kYXRhX2ZpbHRlcmVkKSAgDQogIGJ0ci5tdHJ4W2ksXT1idHIubW9kZWwkY29lZmZpY2llbnRzDQp9DQpgYGANCg0KYGBge3J9DQpib290Lmhpc3QgPSBmdW5jdGlvbihidC5jb2VmLm10cngsIHZhci5pZCwgdmFyLm5tKXsNCg0KICB4MS4xIDwtIHNlcShtaW4oYnQuY29lZi5tdHJ4Wyx2YXIuaWRdKSwgbWF4KGJ0LmNvZWYubXRyeFssdmFyLmlkXSksIGxlbmd0aD0zMDAgKQ0KICB5MS4xIDwtIGRub3JtKHgxLjEsIG1lYW4oYnQuY29lZi5tdHJ4Wyx2YXIuaWRdKSwgc2QoYnQuY29lZi5tdHJ4Wyx2YXIuaWRdKSkNCg0KICBoaWdoZXN0YmFyID0gbWF4KGhpc3QoYnQuY29lZi5tdHJ4Wyx2YXIuaWRdLCBwbG90ID0gRkFMU0UpJGRlbnNpdHkpIA0KICB5bGltaXQgPC0gbWF4KGMoeTEuMSxoaWdoZXN0YmFyKSkNCiAgaGlzdChidC5jb2VmLm10cnhbLHZhci5pZF0sIHByb2JhYmlsaXR5ID0gVFJVRSwgbWFpbiA9IHZhci5ubSwgeGxhYj0iIiwgDQogICAgICAgY29sID0gImF6dXJlMSIseWxpbT1jKDAseWxpbWl0KSwgYm9yZGVyPSJsaWdodHNlYWdyZWVuIikNCiAgbGluZXMoeCA9IHgxLjEsIHkgPSB5MS4xLCBjb2wgPSAicmVkMyIpICAgICAgICAgICAgIA0KICBsaW5lcyhkZW5zaXR5KGJ0LmNvZWYubXRyeFssdmFyLmlkXSwgYWRqdXN0PTIpLCBjb2w9ImJsdWUiKSAgICANCn0gDQoNCg0KDQpgYGANCg0KYGBge3J9DQpwYXIobWZyb3c9YygyLDMpKSAgDQpib290Lmhpc3QoYnQuY29lZi5tdHJ4PWJ0ci5tdHJ4LCB2YXIuaWQ9MSwgdmFyLm5tID0iSW50ZXJjZXB0IiApDQpib290Lmhpc3QoYnQuY29lZi5tdHJ4PWJ0ci5tdHJ4LCB2YXIuaWQ9MiwgdmFyLm5tID0iQXZlcmFnZSBEcml2ZSIgKQ0KYm9vdC5oaXN0KGJ0LmNvZWYubXRyeD1idHIubXRyeCwgdmFyLmlkPTMsIHZhci5ubSA9IkdyZWVucyBvbiBSZWd1bGF0aW9uIiApDQpib290Lmhpc3QoYnQuY29lZi5tdHJ4PWJ0ci5tdHJ4LCB2YXIuaWQ9NCwgdmFyLm5tID0iU2F2ZSBQZXJjZW50YWdlIiApDQpib290Lmhpc3QoYnQuY29lZi5tdHJ4PWJ0ci5tdHJ4LCB2YXIuaWQ9NSwgdmFyLm5tID0iTnVtYmVyIG9mIEV2ZW50cyIgKQ0KYm9vdC5oaXN0KGJ0LmNvZWYubXRyeD1idHIubXRyeCwgdmFyLmlkPTYsIHZhci5ubSA9IkFnZSIgKQ0KDQoNCmBgYA0KDQpXZSBhZ2FpbiBzZWUgdGhhdCBvdXIgaGlzdG9ncmFtcyBoYXZlIGFuIGVxdWFsIGRpc3RyaWJ1dGlvbi4gQWdhaW4gd2Ugc2VlIHRoYXQgdGhlIHAtdmFsdWVzIGFuZCByZXNpZHVhbCBib29zdHJhcCBoYXZlIHZlcnkgc2ltaWxhciB2YWx1ZXMuDQpgYGB7cn0NCnVtLnAgPSBkaW0oYnRyLm10cngpWzJdICANCmJ0ci5jaSA9IE5VTEwNCmJ0ci53ZCA9IE5VTEwNCg0KZm9yIChpIGluIDE6bnVtLnApIHsNCiAgbGNpLjAyNSA9IHJvdW5kKHF1YW50aWxlKGJ0ci5tdHJ4WywgaV0sIDAuMDI1LCB0eXBlID0gMiksIDgpDQogIHVjaS45NzUgPSByb3VuZChxdWFudGlsZShidHIubXRyeFssIGldLCAwLjk3NSwgdHlwZSA9IDIpLCA4KQ0KICBidHIud2RbaV0gPSB1Y2kuOTc1IC0gbGNpLjAyNQ0KICBidHIuY2lbaV0gPSBwYXN0ZSgiWyIsIHJvdW5kKGxjaS4wMjUsIDQpLCAiLCAiLCByb3VuZCh1Y2kuOTc1LCA0KSwgIl0iKQ0KfQ0KDQprYWJsZShhcy5kYXRhLmZyYW1lKGNiaW5kKGZvcm1hdEMoYnRyLm10cnhbMSwgXSwgNCwgZm9ybWF0ID0gImYiKSwgYnRyLmNpLjk1ID0gYnRyLmNpKSksIA0KICAgICAgY2FwdGlvbiA9ICJSZWdyZXNzaW9uIENvZWZmaWNpZW50IE1hdHJpeCB3aXRoIDk1JSBSZXNpZHVhbCBCb290c3RyYXAgQ0kiKQ0KDQpgYGANCg0KT25jZSBhZ2FpbiB3ZSBnZXQgdGhlIHNhbWUgb3V0cHV0IGFzIGV4cGVjdGVkLiBUaGUgdmFsdWVzIGFib3ZlIHNob3cgdGhlIHNhbWUgcmVzdWx0cyBhcyBvdXIgcC12YWx1ZXMsIGFsbCBiZWluZyBzaWduaWZpY2FudCBleGNlcHQgZm9yIGFnZSB3aGljaCBpbmNsdWRlcyAwLg0KDQojIyMgQ29tYmluaW5nIHJlc3VsdHMNCg0KYGBge3J9DQpwX3ZhbHVlcyA8LSBzdW1tYXJ5KGxvZy53aW5uaW5ncykkY29lZmZpY2llbnRzWy0zLCA0XSAgDQoNCmNvbWJpbmVkX21hdHJpeCA8LSBjYmluZCgNCiAgQ29lZmZpY2llbnRzID0gZm9ybWF0Qyhjb2VmKGxvZy53aW5uaW5ncylbLTNdLCA0LCBmb3JtYXQgPSAiZiIpLCAgDQogIGA5NSUgQ0kgKEJvb3RzdHJhcCB0KWAgPSBidGMuY2ksICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgYDk1JSBDSSAoQm9vdHN0cmFwIHIpYCA9IGJ0ci5jaSwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgYHAtdmFsdWVzYCA9IGZvcm1hdEMocF92YWx1ZXMsIDQsIGZvcm1hdCA9ICJmIikgICAgICAgICAgICAgICAgICANCikNCg0KDQpsaWJyYXJ5KGtuaXRyKQ0Ka2FibGUoYXMuZGF0YS5mcmFtZShjb21iaW5lZF9tYXRyaXgpLCANCiAgICAgIGNhcHRpb24gPSAiRmluYWwgQ29tYmluZWQgSW5mZXJlbnRpYWwgU3RhdGlzdGljczogQ29lZmZpY2llbnRzLCBwLXZhbHVlcywgYW5kIEJvb3RzdHJhcCBDSXMiKQ0KDQoNCg0KYGBgDQoNCkFsbCBvZiB0aGUgbW9kZWxzIHlpZWxkIHRoZSBzYW1lIHJlc3VsdCwgc2hvd2luZyB0aGVyZSBpcyBhIHZpb2xhdGlvbiBpbiB0aGUgYWdlIHZhcmlhYmxlIGJ1dCBhbGwgb3RoZXIgdmFyaWFibGVzIGFyZSBzaWduaWZpY2FudC4NCg0KYGBge3J9DQoNCmthYmxlKHJvdW5kKGNiaW5kKGJ0Yy53ZCwgYnRyLndkKSw0KSwgY2FwdGlvbj0id2lkdGggb2YgdGhlIHR3byBib290c3RyYXAgY29uZmlkZW5jZSBpbnRlcnZhbHMiKQ0KDQoNCmBgYA0KDQpXZSBzZWUgZnJvbSB0aGUgYWJvdmUgYW5hbHlzaXMgdGhhdCB0aGUgdHdvIHZhbHVlcyBhcmUgdmVyeSBzaW1pbGFyIHRvIGVhY2ggb3RoZXIuDQoNCg0KDQojIyMgU3VtbWFyeQ0KDQpXZSBzZWUgZnJvbSBvdXIgZmluZGluZ3MgYWJvdmUgdGhhdCBhbGwgdGhyZWUgb2Ygb3VyIG1vZGVscyBjb21lIHRvIHRoZSBzYW1lIGNvbmNsdXNpb24uIFdlIHdpbGwgYmUgdXNpbmcgb3VyIGluaXRpYWwgZmluYWwgbW9kZWwgd2hlcmUgd2UgdXNlZCBhIGxvZyB0cmFuc2Zvcm1hdGlvbiBiZWNhdXNlIHRoZXkgc2VlbSB0byBiZSBhIGxpdHRsZSBtb3JlIHNpZ25pZmljYW50LiANCg0KQWdhaW4gd2Ugc2VlIHRoYXQgdGhlIGZpbmFsIG1vZGVsIGNhbiBiZSByZXByZXNlbnRlZCBhcyBsb2cocHJpY2UpPTMuOTI0NSDiiJIgMC4wMjQ2w5dBdmVyYWdlX0RyaXZlICsgMC4xODY2w5dHcmVlbnNfb25fcmVnICsgDQowLjAzODfDl1NhdmVfcGVyY2VudCDiiJIgMC4wMTgxw5dOdW1iZXJfZXZlbnRzICsgMC4xODI2w5dBZ2VfQWJvdmVfMzBUUlVFDQoNCkZyb20gdGhlIGFib3ZlIGRhdGEgd2UgY2FuIGFsc28gY29uY2x1ZGUgdGhhdCBhcyBhdmVyYWdlIHdpbm5pbmdzIGdvIHVwIG91ciBhdmVyYWdlIGRyaXZlIGdvZXMgZG93biBieSAtMi41JSB3aGlsZSBvdGhlciB2YXJpYWJsZXMgc3VjaCBhcyBncmVlbnMgb24gcmVndWxhdGlvbiBhbmQgc2F2ZSBwZXJjZW50IGdvIHVwLg0KDQojIyBDb25jbHVzaW9uIA0KVGhlIG1haW4gY29uY2x1c2lvbiB3ZSBjYW4gZHJhdyBpcyB0aGF0IHdoaWxlIGRyaXZpbmcgZGlzdGFuY2UgaW4gZ29sZiBpcyBwZXJjZWl2ZWQgdG8gYmUgYSBiaWcgaW5kaWNhdGlvbiBvZiB3aGV0aGVyIGEgcGVyc29uIGlzIHdpbm5pbmcgb3Igbm90IHdlIGRvIG5vdCBzZWUgdGhpcyBpbiBvdXIgYW5hbHlzaXMuIFdoaWxlIHRoZSBBdmVyYWdlIFdpbm5pbmdzIGdvZXMgdXAgdGhlIGF2ZXJhZ2UgZHJpdmUgZ29lcyBkb3duIGJ5IC0yLjUlIHdoaWxlIFNhdmUgUGVyY2VudGFnZSBnb2VzIHVwIGJ5IDMuOSUgYW5kIEdyZWVucyBvbiBSZWd1bGF0aW9uIGdvZXMgdXAgYnkgMS45JSB3aGljaCBzaG93cyB0aGF0IHRoZSAic2hvcnQiIGdhbWUgaW4gZ29sZiAocHV0dGluZyBhbmQgY2hpcHBpbmcpIGlzIG1vcmUgaW1wb3J0YW50IHRoYW4gZHJpdmluZyBpbiByZWdhcmRzIHRvIGF2ZXJhZ2UgbW9uZXkgd29uLg0KDQpXZSBzZWUgb25lIGZsYXcgaW4gb3VyIGFuYWx5c2lzIGFuZCBpdCB3YXMgdGhhdCBhZ2Ugd2FzIG5vdCBzaWduaWZpY2FudCB3aGljaCBjb3VsZCBtZXNzIHVwIG91ciBhbmFseXNpcy4gaW4gdGhlIGZ1dHVyZSB3ZSBjb3VsZCBwdXNoIG91ciBhZ2UgdXAgb3IgZG93biBmb3IgdGhlIHRydWUgb3IgZmFsc2Ugc3RhdGVtZW50IHRvIHNlZSBpZiB0aGF0IGNoYW5nZXMgdGhlIHNpZ25pZmljYW5jZS4gV2UgY291bGQgYWxzbyBsb29rIGF0IHNvbWUgdmFsdWVzIG9mIGFnZSB0byBzZWUgaWYgdGhlcmUgYXJlIGFueSBvdXRsaWVycywgZm9yIGV4YW1wbGUgZ29sZmVycyBhcmUgbm90IHlvdW5nZXIgdGhhbiAyMCBidXQgdGhleSBjYW4gYmUgNDUrIHdoaWNoIGNvdWxkIG1lc3Mgd2l0aCBvdXIgc2lnbmlmaWNhbmNlLg0KDQpXZSBjYW4gdXNlIHRoaXMgbW9kZWwgZm9yIHNlYXNvbnMgdG8gY29tZSB0byBzZWUgaWYgdGhpbmdzIG1heSBoYXZlIGNoYW5nZWQuIEZvciBleGFtcGxlIHNpbmNlIGdvbGYgaGFzIGV2b2x2ZWQgcmVjZW50bHkgdG8gcGxheWVycyBoYXZpbmcgbG9uZ2VyIGRyaXZlcyB3ZSBjb3VsZCBydW4gdGhpcyBtb2RlbCBhZ2FpbiBpbiAyMDI0IHRvIHNlZSBpZiB0aGUgInNob3J0IiBnYW1lIGlzIGxlc3MgbGlrZWx5IHRvIHdpbiBtb3JlIG1vbmV5IHRoYW4gdGhlICJsb25nIiBnYW1lIHdvdWxkLg0KDQo=