Setup
Load required libraries for wrangling data, charting, and mapping
library(plyr,quietly = T) # data wrangling
library(dplyr,quietly = T) # data wrangling
library(ggplot2, quietly = T) # charting
library(ggthemes,quietly = T) # so I can add the highcharts theme and palette
library(scales,quietly = T) # to load the percent function when labeling plots
library(caret,quietly = T) # classification and regression training
library(foreach,quietly = T) # parallel processing to speed up the model training
library(doMC,quietly = T) # parallel processing to speed up the model training
library(lubridate,quietly = T) # for timing models
Set our preferred charting theme
theme_set(theme_minimal()+theme_hc()+theme(legend.key.width = unit(1.5, "cm")))
Run script to get hunter data
source('~/_code/colorado-dow/datasets/Colorado Elk Harvest Data.R', echo=F)
Table of the harvest data
head(COElkRifleAll)
Unit Harvest.Antlered Hunters.Antlered Success.Antlered Season HuntCode Harvest.Antlerless
1 1 0 0 NA 1 EM001O1R NA
2 2 0 0 NA 1 EM002O1R NA
3 201 0 0 NA 1 EM201O1R NA
4 3 0 0 NA 1 EM003O1R NA
5 301 0 0 NA 1 EM301O1R NA
6 4 0 0 NA 1 EM004O1R NA
Hunters.Antlerless Success.Antlerless Hunters.Either Success.Either Year
1 NA NA NA NA 2006
2 NA NA NA NA 2006
3 NA NA NA NA 2006
4 NA NA NA NA 2006
5 NA NA NA NA 2006
6 NA NA NA NA 2006
Run script to get draw data
source('~/_code/colorado-dow/datasets/Elk Drawing Summaries.R', echo=F)
Table of the data
head(COElkDrawAll)
source geodata
source('~/_code/colorado-dow/datasets/Colorado GMUnit and Road data.R', echo=F)
Take a peak at the boundary data
head(Unitboundaries2)
Set to predictive analytics directory
setwd("~/_code/colorado-dow/phase III - predictive analytics")
Organize data
Will start by grouping all of the seasons together, and modeling the number of hunters per Year and Unit
Group Draw results data by Year and Unit
COElkDraw <- summarise(group_by(COElkDrawAll,Year,Unit),
Quota = sum(Orig_Quota,na.rm = T),
Drawn = sum(Chcs_Drawn,na.rm = T))
Appropriate field classes for model training
COElkDraw$Year <- as.numeric(COElkDraw$Year)
Group Hunter data by Year and Unit
COElkHunters <- summarise(group_by(COElkRifleAll,Year,Unit),
Hunters = sum(c(Hunters.Antlered,Hunters.Antlerless,Hunters.Either),na.rm = T))
COElkHunters$Year <- as.numeric(COElkHunters$Year)
Join in Hunter and Draw data together
COElkHunters <- left_join(COElkHunters, COElkDraw, by = c("Year","Unit"))
Replace the draw data that don’t have entries with 0
COElkHunters$Drawn[is.na(COElkHunters$Drawn)] <- 0
COElkHunters$Quota[is.na(COElkHunters$Quota)] <- 0
Split into train and test sets. Will use 75% of the data to train on. Be sure to include each unit in the split. … so do the split for each unit, first make sure each Unit has at least three entries
COElkHunters <- mutate(group_by(COElkHunters,Unit),
numentries = n())
COElkHunters <- filter(COElkHunters, numentries >= 3)
COElkHunters$UnitYear <- paste(COElkHunters$Unit, COElkHunters$Year)
traindata <- COElkHunters %>% group_by(Unit) %>% sample_frac(size = .75, replace = F)
testdata <- COElkHunters[!COElkHunters$UnitYear %in% traindata$UnitYear,]
COElkHunters <- select(COElkHunters, -UnitYear, -numentries)
traindata <- select(traindata, -UnitYear, -numentries)
testdata <- select(testdata, -UnitYear, -numentries)
Save off for importing into AzureML
write.csv(COElkHunters,file = "~/_code/colorado-dow/datasets/COElkHunters.csv",row.names = F)
Data Visualization
notice that the number of hunters data is skewed.
ggplot(COElkHunters, aes(Hunters)) +
geom_density() +
xlab("Hunters in Unit") +
ylab("Number of Units") +
theme(axis.text.y = element_blank()) +
labs(title="Distribution of Hunters in each Unit", subtitle="2006-2017", caption="source: cpw.state.co.us")

A general rule of thumb to consider is that skewed data whose ratio of the highest value to the lowest value is greater than 20 have significant skewness. Also, the skewness statistic can be used as a diagnostic. If the predictor distribution is roughly symmetric, the skewness values will be close to zero. As the distribution becomes more right skewed, the skewness statistic becomes larger. Similarly, as the distribution becomes more left skewed, the value becomes negative. Replacing the data with the log, square root, or inverse may help to remove the skew.
Example of how BoxCox can redistribute the data
preProcValues2 <- preProcess(as.data.frame(traindata), method = "BoxCox")
trainBC <- predict(preProcValues2, as.data.frame(traindata))
ggplot(trainBC, aes(Hunters)) +
geom_density() +
xlab("BoxCox Hunters in Unit") +
ylab("Number of Units") +
theme(axis.text.y = element_blank()) +
labs(title="BoxCox Distribution of Hunters in each Unit", subtitle="2006-2017", caption="source: cpw.state.co.us")

caret has a preproccess function for correcting for skewness ‘BoxCox’, we will need to be sure to look at using this function in the training models.
Model Building
This is quite an iterative process. It is important to document and save off data. Run thru differing ‘quick to train’ methods Which one performed the best? Determine other similar methods and run them. Which one performed the best? Determine disimmilar methods. Which one performed the best?
Now lets work on some refined tuning. Assess preprocessing functions. center, scale, pca, boxcox, nzv, etc some units have very little data, should we remove them? ’zero variances 123, 791, 87, 94, 88 TODO, frequency plot of Unit
Run the list of other favorable methods with preprocessing in place Further tune method’s parameters
Is there a method that is best? Are some methods better at part of the data than others? maybe we combine?
Insert into AzureML to further inspections and create into a webservice If the packages or methods are not yet supported in Azure, we will need to create an R Model instead of just running an rscript.
Additonally, AzureML has some additional methods to consider, ensure we attempt to use those as well.
Model Training Methods
Loop through possible methods, utilizing the quicker ‘adaptive_cv’ parameter search from caret. Consider scripting this into AzureML to make it run much faster, though there is more setup and errors to control for
quickmethods <- c("lm",'svmLinear',"svmRadial","knn","cubist","kknn","glm.nb")
step1_all <- NULL
for (imethod in quickmethods) {
step1 <- NULL
start <- now()
if (imethod == "lm") {
controlmethod <- "repeatedcv"
} else {controlmethod <- "adaptive_cv"}
fitControl <- trainControl(
method = controlmethod,
# search = 'random',
number = 4,
repeats = 4,
allowParallel = TRUE,
summaryFunction = defaultSummary)
registerDoSEQ()
registerDoMC(cores = 6)
HuntersModel_1 = train(Hunters ~ ., data = traindata,
method = imethod,
preProc = c("center","scale"),
tuneLength = 15,
trControl = fitControl)
HuntersModel_1
# measure performance
predictdata <- predict(HuntersModel_1, testdata)
step1$method <- imethod
step1$RMSE <- postResample(pred = predictdata, obs = testdata$Hunters)[1]
step1$duration <- now() - start
step1 <- as.data.frame(step1)
step1_all <- rbind(step1_all,step1)
}
View Results
step1_all
method RMSE duration
RMSE lm 165.0025 6.866598 secs
RMSE1 svmLinear 178.0942 4.950974 secs
RMSE2 svmRadial 134.0561 22.081016 secs
RMSE3 knn 733.8210 10.502875 secs
RMSE4 cubist 151.9572 3.057477 secs
RMSE5 kknn 132.0211 9.778734 secs
RMSE6 cubist 160.5464 1.027274 secs
top_two_models <- top_n(step1_all,2,-RMSE)$method
More Model Training Methods
Take the top two and determine some additonal methods to try by maximizing the Jaccard dissimilarity between sets of models
tag <- read.csv("tag_data.csv", row.names = 1)
tag <- as.matrix(tag)
Select only models for regression
regModels <- tag[tag[,"Regression"] == 1,]
all <- 1:nrow(regModels)
dissimilarmethods_all <- NULL
for (itoptwo in 1:2) {
## Seed the analysis with the model of interest
start <- grep(top_two_models[itoptwo], rownames(regModels), fixed = TRUE)
pool <- all[all != start]
## Select 4 model models by maximizing the Jaccard
## dissimilarity between sets of models
nextMods <- maxDissim(regModels[start,,drop = FALSE],
regModels[pool, ],
method = "Jaccard",
n = 4)
rownames(regModels)[c(nextMods)]
dissimilarmethods <- rownames(regModels)[nextMods]
dissimilarmethods <- str_extract(string = dissimilarmethods,pattern = "[:alnum:]+(?=\\))")
dissimilarmethods_all <- c(dissimilarmethods_all,dissimilarmethods)
}
Now we have 8 more methods to try in the same manner
dissimilarmethods_all <- unique(dissimilarmethods_all)
dissimilarmethods_all
[1] "cubist" "brnn" "glm.nb" "foba" "ppr" "rvmLinear"
for (imethod in dissimilarmethods_all) {
step1 <- NULL
start_timer <- now()[1]
if (imethod == "lm") {
controlmethod <- "repeatedcv"
} else {controlmethod <- "adaptive_cv"}
fitControl <- trainControl(
method = controlmethod,
# search = 'random',
number = 4,
repeats = 4,
allowParallel = TRUE,
summaryFunction = defaultSummary)
registerDoSEQ()
registerDoMC(cores = 6)
HuntersModel_1 = train(Hunters ~ ., data = traindata,
method = imethod,
preProc = c("center","scale"),
tuneLength = 15,
trControl = fitControl)
HuntersModel_1
# measure performance
predictdata <- predict(HuntersModel_1, testdata)
step1$method <- imethod
step1$RMSE <- postResample(pred = predictdata, obs = testdata$Hunters)[1]
step1$duration <- now()[1] - start_timer[1]
step1 <- as.data.frame(step1)
step1_all <- rbind(step1_all,step1)
}
step1_all
method RMSE duration
RMSE lm 165.0025 6.866598 secs
RMSE1 svmLinear 178.0942 4.950974 secs
RMSE2 svmRadial 134.0561 22.081016 secs
RMSE3 knn 733.8210 10.502875 secs
RMSE4 cubist 151.9572 3.057477 secs
RMSE5 kknn 132.0211 9.778734 secs
RMSE6 cubist 160.5464 1.027274 secs
Preprocessing on Top Modeling Methods
Now lets work on some refined tuning on the top methods Any valuable preprocessing steps?
preprocessfunctions <- c("BoxCox", "YeoJohnson", "expoTrans", "center", "scale", "range", "knnImpute", "bagImpute", "medianImpute", "pca", "ica", "spatialSign", "corr", "zv", "nzv")
topmethods <- top_n(step1_all,2,-RMSE)$method
fitControl <- trainControl(
method = "adaptive_cv", #repeatedcv,
search = 'random',
number = 10, #4
repeats = 10, #10
# classProbs = TRUE,
# savePred = TRUE,
allowParallel = TRUE,
summaryFunction = defaultSummary)
PPperformance_all <- NULL
PPperformance <- NULL
for (imethod in topmethods) {
for (ipreprocess in preprocessfunctions) {
registerDoSEQ()
registerDoMC(cores = 6)
PreProcessModel = train(Hunters ~ ., data = traindata,
method = imethod,
preProc = ipreprocess,
#tuneLength = 10,
#tuneGrid = kknnTuneGrid,
trControl = fitControl)
print(PreProcessModel)
# check performance
predictdata <- predict(PreProcessModel, testdata)
PPperformance$method <- imethod
PPperformance$preprocess <- ipreprocess
PPperformance$RMSE <- postResample(pred = predictdata, obs = testdata$Hunters)[1]
PPperformance <- as.data.frame(PPperformance)
PPperformance_all <- rbind(PPperformance_all,PPperformance)
}
}
# Some of the models were loaded into AzureML and processed there.
# Output from AzureML
# [ModuleOutput] method preprocess RMSE
# [ModuleOutput] RMSE kknn BoxCox 130.7939
# [ModuleOutput] RMSE1 kknn YeoJohnson 130.9600
# [ModuleOutput] RMSE2 kknn center 130.7331
# [ModuleOutput] RMSE3 kknn scale 130.1818
# [ModuleOutput] RMSE4 kknn pca 130.2071
# [ModuleOutput] RMSE5 svmRadial BoxCox 154.0898
# [ModuleOutput] RMSE6 svmRadial YeoJohnson 169.9816
# [ModuleOutput] RMSE7 svmRadial center 154.1891
# [ModuleOutput] RMSE8 svmRadial scale 154.1000
# [ModuleOutput] RMSE9 svmRadial pca 164.0881
# svmRadial and kknn don't perform better with any of the preprocessing functions in place
PPperformance_all
Error: object 'PPperformance_all' not found
Model Predictors
Now we can review the predictors, there are only a few fields so I will manually test performance while excluding each of them to monitor their importance. Some of our fields are instinctively required (Year, Unit)
Predictors <- c("Quota","Drawn")
Predictorperformance_all <- NULL
Predictorperformance <- NULL
for (imethod in topmethods) {
for (ipredictor in Predictors) {
registerDoSEQ()
registerDoMC(cores = 6)
PredictorModel = train(Hunters ~ ., data = select(traindata,-ipredictor),
method = imethod,
tuneLength = 15,
trControl = fitControl)
print(PredictorModel)
# check performance
predictdata <- predict(PredictorModel, testdata)
Predictorperformance$method <- imethod
Predictorperformance$missing_predictor <- ipredictor
Predictorperformance$RMSE <- postResample(pred = predictdata, obs = testdata$Hunters)[1]
Predictorperformance <- as.data.frame(Predictorperformance)
Predictorperformance_all <- rbind(Predictorperformance_all,Predictorperformance)
}
}
Predictorperformance_all
method missing_predictor RMSE
RMSE svmRadial Quota 152.5027
RMSE1 svmRadial Drawn 153.7358
RMSE2 kknn Quota 131.1298
RMSE3 kknn Drawn 134.1082
RMSE4 kknn Quota and Drawn 130.3965
svMRadial will perform better with all of the predictors, while kknn performs better with only Unit and Year fields
Use above information to test out various combinations of preprocessing and predictor sets
kknn
kknn without Quota and Drawn
fitControl <- trainControl(
method = "adaptive_cv", #repeatedcv,
search = 'random',
number = 10, #4
repeats = 10, #10
# classProbs = TRUE,
# savePred = TRUE,
allowParallel = TRUE,
summaryFunction = defaultSummary)
registerDoSEQ()
registerDoMC(cores = 6)
kknnModel = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
method = "kknn",
tuneLength = 75,
trControl = fitControl)
kknnModel
Best RMSE
# not sure why caret is selecting parameters with higher RMSE, lets select manually
RSMEkknn <- filter(kknnModel$results, RMSE == min(RMSE))
RSMEkknn$kernel <- as.character(RSMEkknn$kernel)
RSMEkknn
kmax distance kernel RMSE Rsquared MAE RMSESD RsquaredSD MAESD .B
1 465 0.1073528 triweight 127.8562 0.9801797 82.07937 14.32044 0.006456134 6.309693 10
Model Tuning
run again with a tune grid
kknnTuneGrid <- data.frame(kmax = c(RSMEkknn$kmax,RSMEkknn$kmax,RSMEkknn$kmax,RSMEkknn$kmax,RSMEkknn$kmax),
distance = c(RSMEkknn$distance*.7,RSMEkknn$distance*.9,RSMEkknn$distance,RSMEkknn$distance*1.1,RSMEkknn$distance*1.3),
kernel = c(RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel))
fitControl <- trainControl(
method = "repeatedcv", #repeatedcv,
number = 10, #4
repeats = 10, #10
allowParallel = TRUE,
summaryFunction = defaultSummary)
registerDoSEQ()
registerDoMC(cores = 6)
kknnGridModel = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
method = "kknn",
tuneGrid = kknnTuneGrid,
trControl = fitControl)
kknnGridModel
Best RMSE
RSMEkknn <- filter(kknnGridModel$results, RMSE == min(RMSE))
RSMEkknn$kernel <- as.character(RSMEkknn$kernel)
run again with a tune grid
kknnTuneGrid2 <- data.frame(kmax = c(RSMEkknn$kmax*.7,RSMEkknn$kmax*.9,RSMEkknn$kmax,RSMEkknn$kmax*1.1,RSMEkknn$kmax*1.3),
distance = c(RSMEkknn$distance,RSMEkknn$distance,RSMEkknn$distance,RSMEkknn$distance,RSMEkknn$distance),
kernel = c(RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel))
registerDoSEQ()
registerDoMC(cores = 6)
kknnGridModel2 = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
method = "kknn",
tuneGrid = kknnTuneGrid2,
trControl = fitControl)
kknnGridModel2
One more time on final parameter (kernel) Best RMSE
RSMEkknn <- filter(kknnGridModel2$results, RMSE == min(RMSE))[1,]
kernels <- levels(kknnModel$results$kernel)
run again with a tune grid
kknnTuneGrid3 <- data.frame(kmax = rep(465.0,8),
distance = rep(0.1395586,8),
kernel = kernels)
registerDoSEQ()
registerDoMC(cores = 6)
kknnGridModel3 = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
method = "kknn",
tuneGrid = kknnTuneGrid3,
trControl = fitControl)
kknnGridModel3
RSMEkknn <- filter(kknnGridModel3$results, RMSE == min(RMSE))
Best RMSE for kknn thus far
RSMEkknn <- filter(kknnModel$results, RMSE == min(RMSE))
Work thru some resampling methods with best kknn params
kknnTuneGrid4 <- data.frame(kmax = RSMEkknn$kmax,
distance = RSMEkknn$distance,
kernel = as.character(RSMEkknn$kernel))
trainmethods <- c("boot", "boot632", "optimism_boot", "boot_all", "cv", "repeatedcv", "LOOCV", "LGOCV", "none")
trainmethodperformance_all <- NULL
for (itrainmethod in trainmethods) {
trainmethodperformance <- NULL
fitControl <- trainControl(
method = itrainmethod,
number = 10, #4
repeats = 10, #10
allowParallel = TRUE,
summaryFunction = defaultSummary)
registerDoSEQ()
registerDoMC(cores = 6)
kknnTrainModel = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
method = "kknn",
tuneGrid = kknnTuneGrid4,
trControl = fitControl)
print(kknnTrainModel)
trainmethodperformance <- filter(kknnTrainModel$results, RMSE == min(RMSE))
trainmethodperformance$trainmethod <- itrainmethod
trainmethodperformance_all <- rbind.fill(trainmethodperformance_all,trainmethodperformance)
}
trainmethodperformance_all
fitControl <- trainControl(
method = "optimism_boot",
number = 10, #4
allowParallel = TRUE,
summaryFunction = defaultSummary)
kknnFinalTrainModel = train(Hunters ~ ., data = COElkHunters,
method = "kknn",
tuneGrid = kknnTuneGrid4,
trControl = fitControl)
save off for future loading
save(kknnFinalTrainModel, file = "~/_code/colorado-dow/datasets/kknnFinalTrainModel.RData")
run again with a tune grid
svmRadTuneGrid <- data.frame(.sigma = c(0.0037653,0.0037653,0.0037653,0.0037653,0.0037653),
.C = c(2048*.7,2048*.9,2048,2048*1.1,2048*1.3))
fitControl <- trainControl(
method = "repeatedcv", #repeatedcv,
number = 10, #4
repeats = 10, #10
allowParallel = TRUE,
summaryFunction = defaultSummary)
registerDoSEQ()
registerDoMC(cores = 6)
svmRadGridModel = train(Hunters ~ ., data = COElkHunters,
method = "svmRadial",
tuneGrid = svmRadTuneGrid,
trControl = fitControl)
svmRadGridModel
Best RMSE, not sure why caret is selecting parameters with higher RMSE, lets select manually
RSMEsvmRad <- filter(svmRadGridModel$results, RMSE == min(RMSE))
run again with a tune grid
svmRadTuneGrid2 <- data.frame(.sigma = c(RSMEsvmRad$sigma*.7,RSMEsvmRad$sigma*.9,RSMEsvmRad$sigma,RSMEsvmRad$sigma*1.1,RSMEsvmRad$sigma*1.3),
.C = c(RSMEsvmRad$C,RSMEsvmRad$C,RSMEsvmRad$C,RSMEsvmRad$C,RSMEsvmRad$C))
registerDoSEQ()
registerDoMC(cores = 6)
svmRadGridModel2 = train(Hunters ~ ., data = COElkHunters,
method = "svmRadial",
tuneGrid = svmRadTuneGrid2,
trControl = fitControl)
svmRadGridModel2
RSMEsvmRad <- filter(svmRadGridModel2$results, RMSE == min(RMSE))
Work thru some resampling methods with best kknn params
svmRadTuneGrid3 <- data.frame(.sigma = RSMEsvmRad$sigma,
.C = RSMEsvmRad$C)
trainmethods <- c("boot", "boot632", "optimism_boot", "cv", "repeatedcv", "LOOCV", "LGOCV", "none")
trainmethodperformance_all <- NULL
for (itrainmethod in trainmethods) {
trainmethodperformance <- NULL
fitControl <- trainControl(
method = itrainmethod,
number = 10, #4
repeats = 10, #10
allowParallel = TRUE,
summaryFunction = defaultSummary)
registerDoSEQ()
registerDoMC(cores = 6)
svmRadTrainModel = train(Hunters ~ ., data = COElkHunters,
method = "svmRadial",
tuneGrid = svmRadTuneGrid3,
trControl = fitControl)
print(svmRadTrainModel)
trainmethodperformance <- filter(svmRadTrainModel$results, RMSE == min(RMSE))
trainmethodperformance$trainmethod <- itrainmethod
trainmethodperformance_all <- rbind.fill(trainmethodperformance_all,trainmethodperformance)
}
trainmethodperformance_all
fitControl <- trainControl(
method = "optimism_boot",
number = 10, #4
allowParallel = TRUE,
summaryFunction = defaultSummary)
svmRadFinalTrainModel = train(Hunters ~ ., data = COElkHunters,
method = "svmRadial",
tuneGrid = svmRadTuneGrid3,
trControl = fitControl)
save off for future loading
save(svmRadFinalTrainModel, file = "~/_code/colorado-dow/datasets/svmRadFinalTrainModel.RData")
back to train vs test data for one more performance measure and chart… even though for future data we will use the final trained model
svmRadTrainModel = train(Hunters ~ ., data = traindata,
method = "svmRadial",
tuneGrid = svmRadTuneGrid3,
trControl = fitControl)
check performance
predictdata <- predict(svmRadTrainModel, testdata)
postResample(pred = predictdata, obs = testdata$Hunters)
Chart performance of predicted
chartperformance <- data.frame(predicted = predictdata, observed = testdata$Hunters)
ggplot(chartperformance, aes(predicted,observed)) +
geom_point() +
labs(title="Performance of Number of Hunters Prediction", caption="source: cpw.state.co.us")
kknn performed better than svmRadial RMSE=130 vs 154
FinalHuntersmodel <- kknnFinalTrainModel
# FinalHuntersmodel <- svmRadFinalTrainModel
save(FinalHuntersmodel, file = "~/_code/colorado-dow/datasets/FinalHuntersmodel.RData")
Use the 2018 Draw data to predict the number of hunters in 2018
COElkDraw2018 <- filter(COElkDraw, Year == 2018)
COElkHunters2018 <- COElkDraw2018[, colnames(COElkDraw2018) %in% c("Unit",FinalHuntersmodel$coefnames)]
COElkHunters2018 <- as.data.frame(unique(COElkHunters$Unit))
colnames(COElkHunters2018) <- "Unit"
COElkHunters2018$Year <- 2018
COElkHunters2018 <- left_join(COElkHunters2018,filter(COElkDraw,Year==2018))
# Replace the draw data that don't have entries with 0
COElkHunters2018$Drawn[is.na(COElkHunters2018$Drawn)] <- 0
COElkHunters2018$Quota[is.na(COElkHunters2018$Quota)] <- 0
COElkHunters2018 <- COElkHunters2018[, colnames(COElkHunters2018) %in% c("Unit",FinalHuntersmodel$coefnames)]
COElkHunters2018$Hunters <- round(predict(FinalHuntersmodel, COElkHunters2018))
COElkHunters2018$Hunters[COElkHunters2018$Hunters<0] <- 0
Save off so we don’t have to recreate the model everytime we want the results
save(COElkHunters2018,file="COElkHunters2018.RData")
Total Elk Harvest
Statewide
Group seasons
COElkHuntersStatewide <- summarise(group_by(COElkRifleAll,Year,Unit),
Hunters = sum(c(Hunters.Antlered,Hunters.Antlerless,Hunters.Either),na.rm = T))
COElkHunters2018b <- COElkHunters2018
# COElkHunters2018b$Year <- as.character(COElkHunters2018b$Year)
# Join 2018 to historic data
COElkHuntersAll <- rbind.fill(COElkHuntersStatewide,COElkHunters2018b)
# Group Units
COElkHuntersStatewide <- summarise(group_by(COElkHuntersAll,Year),
Hunters = sum(Hunters))
ggplot(COElkHuntersStatewide, aes(Year,Hunters)) +
geom_bar(stat="identity",fill=ggthemes_data$hc$palettes$default[2]) +
coord_cartesian(ylim = c(130000,155000)) +
labs(title="Statewide Elk Hunters", caption="source: cpw.state.co.us")
TODO commentary
Hunters by Unit
I’d like to know where the hunters are distributed across the state.
Next year’s data
Year2018 <- filter(COElkHuntersAll, Year == "2018")
HunterstoPlot <- left_join(Unitboundaries2,Year2018, by=c("Unit"))
ggplot(HunterstoPlot, aes(long, lat, group = group)) +
geom_polygon(aes(fill = Hunters),colour = "grey50", size = .2) + #Unit boundaries
geom_path(data = COroads,aes(x = long, y = lat, group = group), color="#3878C7",size=2) + #Roads
geom_text(data=data_centroids,aes(x=longitude,y=latitude,label = Unit),size=3) + #Unit labels
scale_fill_distiller(palette = "Oranges",direction = 1,na.value = 'light grey') +
xlab("") + ylab("") +
labs(title="Predicted 2018 Colorado Elk Hunters", caption="source: cpw.state.co.us")
TODO - commentary
Year to Year Hunter Trends
Create a png of each year
icounter <- 0
for (imap in unique(COElkHuntersAll$Year)){
# Colorado aspect ratio = 1087w x 800h -> 1.35875
# Use trial and error to determine which width and height to define for png files that will retain the correct aspect ratio
png(file=paste("HuntersMap",imap,".png"), width=948, height=700)
yearplot <- filter(COElkHuntersAll, Year == imap)
HunterstoPlot <- left_join(Unitboundaries2,yearplot, by=c("Unit"))
p1 <- ggplot(HunterstoPlot, aes(long, lat, group = group)) +
geom_polygon(aes(fill = Hunters),colour = "grey50", size = .2) + #Unit boundaries
geom_path(data = COroads,aes(x = long, y = lat, group = group), color="#3878C7",size=2) + #Roads
geom_text(data=data_centroids,aes(x=longitude,y=latitude,label = Unit),size=5) + #Unit labels
scale_fill_distiller(palette = "Oranges",
direction = 1,
na.value = 'light grey',
limits = c(0,max(COElkHuntersAll$Hunters))) + #fix so each year chart has same color breaks
xlab("") + ylab("") +
theme(plot.title=element_text(hjust = .5)) +
theme(plot.subtitle=element_text(hjust = icounter/length(unique(COElkHuntersAll$Year)))) +
labs(title="Colorado Elk Hunters", subtitle=imap, caption="source: cpw.state.co.us")
plot(p1)
dev.off()
icounter <- icounter + 1
}
Convert the .png files to one .gif file using ImageMagick.
system("convert -delay 150 *.png HuntersmapPred.gif")
> TODO - commentary
Remove the .png files
file.remove(list.files(pattern=".png"))
Number of Hunters Rank of the Units
Would also be beneficial to rank each unit so I can reference later. In this case average the number of hunters of the last few years
HunterRank2018 <- filter(COElkHuntersAll, as.numeric(Year) == 2018)
HunterRank2018 <- summarise(group_by(HunterRank2018,Unit),
Hunters = mean(Hunters,na.rm = T))
HunterRank2018$HuntersRank = rank(-HunterRank2018$Hunters)
HunterRank2018 <- filter(HunterRank2018, HuntersRank <= 50) # top 50 units
# In order for the chart to retain the order of the rows, the X axis variable (i.e. the categories) has to be converted into a factor.
HunterRank2018 <- HunterRank2018[order(-HunterRank2018$Hunters), ] # sort
HunterRank2018$Unit <- factor(HunterRank2018$Unit, levels = HunterRank2018$Unit) # to retain the order in plot.
Lollipop Chart
ggplot(HunterRank2018, aes(x=Unit, y=Hunters)) +
geom_point(size=3) +
geom_segment(aes(x=Unit,
xend=Unit,
y=0,
yend=Hunters)) +
labs(title="Predicted Elk Hunters 2018\nTop 50 Units", subtitle="Hunters by Unit", caption="source: cpw.state.co.us")
TODO - commentary
LS0tCnRpdGxlOiAiUHJlZGljdCBOdW1iZXIgb2YgRnV0dXJlIEVsayBIdW50ZXJzIgphdXRob3I6ICJQaWVycmUgU2Fybm93IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IGZhbHNlCiAgICB0b2NfZGVwdGg6IDYKICAgIHRoZW1lOiB5ZXRpCiAgICBoaWdodGxpZ2h0OiBkZWZhdWx0CiAgICBjb2RlX2ZvbGRpbmc6IG5vbmUKLS0tCgoKKioqCiMjIERlc2NyaXB0aW9uClVzZSBoaXN0b3JpY2FsIGRyYXcgcmVzdWx0cywgYW5kIG51bWJlciBvZiBodW50ZXJzIHRvIHRyYWluIGEgbW9kZWwgd2UgY2FuIHVzZSB0byAKcHJlZGljdCB0aGUgbnVtYmVyIG9mIGh1bnRlcnMgaW4gZnV0dXJlIHllYXJzLgoKVE9ETyAtIEluY2x1ZGUgb3RoZXIgcG90ZW50aWFsIGlucHV0cyB0aGF0IGNvdWxkIGltcGFjdCBob3cgbWFueSBodW50ZXJzIGdldCBhIGxpY2Vuc2UKYW5kIHNob3cgdXAuIFRob3NlIGNvdWxkIGluY2x1ZGUgZWNvbm9taWMgaW5kaWNhdG9ycywgYW5kIGNvc3RzIGFzc29jaWF0ZWQgd2l0aCBodW50aW5nCih0cmFuc3BvcnRhdGlvbiwgbG9kZ2luZykuCgoqX19OT1RJQ0VfXyB0aGF0IEkgYW0gb25seSBsb29raW5nIGF0IHRoZSBnZW5lcmFsIHJpZmxlIGh1bnRpbmcgc2Vhc29ucyBvbiBwdWJsaWMgbGFuZC4gVGhlcmUgYXJlIGFsc28gCmh1bnRlcnMgZm9yIEFyY2hlcnksIE11enpsZWxvYWRlciwgUHJpdmF0ZSBMYW5kLCBSYW5jaGluZyBmb3IgV2lsZGxpZmUsIGV0Yy4qCgoqKioKIyMgU2V0dXAKTG9hZCByZXF1aXJlZCBsaWJyYXJpZXMgZm9yIHdyYW5nbGluZyBkYXRhLCBjaGFydGluZywgYW5kIG1hcHBpbmcKYGBge3J9CmxpYnJhcnkocGx5cixxdWlldGx5ID0gVCkgIyBkYXRhIHdyYW5nbGluZwpsaWJyYXJ5KGRwbHlyLHF1aWV0bHkgPSBUKSAjIGRhdGEgd3JhbmdsaW5nCmxpYnJhcnkoZ2dwbG90MiwgcXVpZXRseSA9IFQpICMgY2hhcnRpbmcKbGlicmFyeShnZ3RoZW1lcyxxdWlldGx5ID0gVCkgIyBzbyBJIGNhbiBhZGQgdGhlIGhpZ2hjaGFydHMgdGhlbWUgYW5kIHBhbGV0dGUKbGlicmFyeShzY2FsZXMscXVpZXRseSA9IFQpICMgdG8gbG9hZCB0aGUgcGVyY2VudCBmdW5jdGlvbiB3aGVuIGxhYmVsaW5nIHBsb3RzCmxpYnJhcnkoY2FyZXQscXVpZXRseSA9IFQpICMgY2xhc3NpZmljYXRpb24gYW5kIHJlZ3Jlc3Npb24gdHJhaW5pbmcKbGlicmFyeShmb3JlYWNoLHF1aWV0bHkgPSBUKSAjIHBhcmFsbGVsIHByb2Nlc3NpbmcgdG8gc3BlZWQgdXAgdGhlIG1vZGVsIHRyYWluaW5nCmxpYnJhcnkoZG9NQyxxdWlldGx5ID0gVCkgIyBwYXJhbGxlbCBwcm9jZXNzaW5nIHRvIHNwZWVkIHVwIHRoZSBtb2RlbCB0cmFpbmluZwpsaWJyYXJ5KGx1YnJpZGF0ZSxxdWlldGx5ID0gVCkgIyBmb3IgdGltaW5nIG1vZGVscwpgYGAKClNldCBvdXIgcHJlZmVycmVkIGNoYXJ0aW5nIHRoZW1lCmBgYHtyfQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpK3RoZW1lX2hjKCkrdGhlbWUobGVnZW5kLmtleS53aWR0aCA9IHVuaXQoMS41LCAiY20iKSkpCmBgYCAKClJ1biBzY3JpcHQgdG8gZ2V0IGh1bnRlciBkYXRhCmBgYHtyfQpzb3VyY2UoJ34vX2NvZGUvY29sb3JhZG8tZG93L2RhdGFzZXRzL0NvbG9yYWRvIEVsayBIYXJ2ZXN0IERhdGEuUicsIGVjaG89RikKYGBgCgpUYWJsZSBvZiB0aGUgaGFydmVzdCBkYXRhCmBgYHtyfQpoZWFkKENPRWxrUmlmbGVBbGwpCmBgYAoKClJ1biBzY3JpcHQgdG8gZ2V0IGRyYXcgZGF0YQpgYGB7cn0Kc291cmNlKCd+L19jb2RlL2NvbG9yYWRvLWRvdy9kYXRhc2V0cy9FbGsgRHJhd2luZyBTdW1tYXJpZXMuUicsIGVjaG89RikKYGBgCgpUYWJsZSBvZiB0aGUgZGF0YQpgYGB7cn0KaGVhZChDT0Vsa0RyYXdBbGwpCmBgYAoKc291cmNlIGdlb2RhdGEKYGBge3J9CnNvdXJjZSgnfi9fY29kZS9jb2xvcmFkby1kb3cvZGF0YXNldHMvQ29sb3JhZG8gR01Vbml0IGFuZCBSb2FkIGRhdGEuUicsIGVjaG89RikKYGBgCgpUYWtlIGEgcGVhayBhdCB0aGUgYm91bmRhcnkgZGF0YQpgYGB7cn0KaGVhZChVbml0Ym91bmRhcmllczIpCmBgYAoKU2V0IHRvIHByZWRpY3RpdmUgYW5hbHl0aWNzIGRpcmVjdG9yeQpgYGB7cn0Kc2V0d2QoIn4vX2NvZGUvY29sb3JhZG8tZG93L3BoYXNlIElJSSAtIHByZWRpY3RpdmUgYW5hbHl0aWNzIikKYGBgCgojIyMgT3JnYW5pemUgZGF0YQpXaWxsIHN0YXJ0IGJ5IGdyb3VwaW5nIGFsbCBvZiB0aGUgc2Vhc29ucyB0b2dldGhlciwgYW5kIG1vZGVsaW5nIHRoZSBudW1iZXIgb2YgaHVudGVycyBwZXIgWWVhciBhbmQgVW5pdAoKR3JvdXAgRHJhdyByZXN1bHRzIGRhdGEgYnkgWWVhciBhbmQgVW5pdApgYGB7cn0KQ09FbGtEcmF3IDwtIHN1bW1hcmlzZShncm91cF9ieShDT0Vsa0RyYXdBbGwsWWVhcixVbml0KSwKICAgICAgICAgICAgICAgICAgICAgICBRdW90YSA9IHN1bShPcmlnX1F1b3RhLG5hLnJtID0gVCksCiAgICAgICAgICAgICAgICAgICAgICAgRHJhd24gPSBzdW0oQ2hjc19EcmF3bixuYS5ybSA9IFQpKQpgYGAKCkFwcHJvcHJpYXRlIGZpZWxkIGNsYXNzZXMgZm9yIG1vZGVsIHRyYWluaW5nCmBgYHtyfQpDT0Vsa0RyYXckWWVhciA8LSBhcy5udW1lcmljKENPRWxrRHJhdyRZZWFyKQpgYGAKCkdyb3VwIEh1bnRlciBkYXRhIGJ5IFllYXIgYW5kIFVuaXQKYGBge3J9CkNPRWxrSHVudGVycyA8LSBzdW1tYXJpc2UoZ3JvdXBfYnkoQ09FbGtSaWZsZUFsbCxZZWFyLFVuaXQpLAogICAgICAgICAgICAgICAgICAgICAgICAgIEh1bnRlcnMgPSBzdW0oYyhIdW50ZXJzLkFudGxlcmVkLEh1bnRlcnMuQW50bGVybGVzcyxIdW50ZXJzLkVpdGhlciksbmEucm0gPSBUKSkKCkNPRWxrSHVudGVycyRZZWFyIDwtIGFzLm51bWVyaWMoQ09FbGtIdW50ZXJzJFllYXIpCmBgYAoKSm9pbiBpbiBIdW50ZXIgYW5kIERyYXcgZGF0YSB0b2dldGhlcgpgYGB7cn0KQ09FbGtIdW50ZXJzIDwtIGxlZnRfam9pbihDT0Vsa0h1bnRlcnMsIENPRWxrRHJhdywgYnkgPSBjKCJZZWFyIiwiVW5pdCIpKQpgYGAKClJlcGxhY2UgdGhlIGRyYXcgZGF0YSB0aGF0IGRvbid0IGhhdmUgZW50cmllcyB3aXRoIDAKYGBge3J9CkNPRWxrSHVudGVycyREcmF3bltpcy5uYShDT0Vsa0h1bnRlcnMkRHJhd24pXSA8LSAwCkNPRWxrSHVudGVycyRRdW90YVtpcy5uYShDT0Vsa0h1bnRlcnMkUXVvdGEpXSA8LSAwCmBgYAoKU3BsaXQgaW50byB0cmFpbiBhbmQgdGVzdCBzZXRzLiBXaWxsIHVzZSA3NSUgb2YgdGhlIGRhdGEgdG8gdHJhaW4gb24uIEJlIHN1cmUgdG8gaW5jbHVkZQplYWNoIHVuaXQgaW4gdGhlIHNwbGl0LiAuLi4gc28gZG8gdGhlIHNwbGl0IGZvciBlYWNoIHVuaXQsIGZpcnN0IG1ha2Ugc3VyZSBlYWNoIFVuaXQgaGFzCmF0IGxlYXN0IHRocmVlIGVudHJpZXMKCmBgYHtyfQpDT0Vsa0h1bnRlcnMgPC0gbXV0YXRlKGdyb3VwX2J5KENPRWxrSHVudGVycyxVbml0KSwKICAgICAgICAgICAgICAgICAgICAgICBudW1lbnRyaWVzID0gbigpKQpDT0Vsa0h1bnRlcnMgPC0gZmlsdGVyKENPRWxrSHVudGVycywgbnVtZW50cmllcyA+PSAzKQpDT0Vsa0h1bnRlcnMkVW5pdFllYXIgPC0gcGFzdGUoQ09FbGtIdW50ZXJzJFVuaXQsIENPRWxrSHVudGVycyRZZWFyKQoKdHJhaW5kYXRhIDwtIENPRWxrSHVudGVycyAlPiUgZ3JvdXBfYnkoVW5pdCkgJT4lIHNhbXBsZV9mcmFjKHNpemUgPSAuNzUsIHJlcGxhY2UgPSBGKQp0ZXN0ZGF0YSA8LSBDT0Vsa0h1bnRlcnNbIUNPRWxrSHVudGVycyRVbml0WWVhciAlaW4lIHRyYWluZGF0YSRVbml0WWVhcixdCgpDT0Vsa0h1bnRlcnMgPC0gc2VsZWN0KENPRWxrSHVudGVycywgLVVuaXRZZWFyLCAtbnVtZW50cmllcykKCnRyYWluZGF0YSA8LSBzZWxlY3QodHJhaW5kYXRhLCAtVW5pdFllYXIsIC1udW1lbnRyaWVzKQp0ZXN0ZGF0YSA8LSBzZWxlY3QodGVzdGRhdGEsIC1Vbml0WWVhciwgLW51bWVudHJpZXMpCmBgYAoKU2F2ZSBvZmYgZm9yIGltcG9ydGluZyBpbnRvIEF6dXJlTUwKYGBge3J9CndyaXRlLmNzdihDT0Vsa0h1bnRlcnMsZmlsZSA9ICJ+L19jb2RlL2NvbG9yYWRvLWRvdy9kYXRhc2V0cy9DT0Vsa0h1bnRlcnMuY3N2Iixyb3cubmFtZXMgPSBGKQpgYGAKIyMjIERhdGEgVmlzdWFsaXphdGlvbgpub3RpY2UgdGhhdCB0aGUgbnVtYmVyIG9mIGh1bnRlcnMgZGF0YSBpcyBza2V3ZWQuCmBgYHtyIGZpZy53aWR0aD0xMH0KZ2dwbG90KENPRWxrSHVudGVycywgYWVzKEh1bnRlcnMpKSArIAogIGdlb21fZGVuc2l0eSgpICsKICB4bGFiKCJIdW50ZXJzIGluIFVuaXQiKSArCiAgeWxhYigiTnVtYmVyIG9mIFVuaXRzIikgKwogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpKSArCiAgbGFicyh0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIEh1bnRlcnMgaW4gZWFjaCBVbml0Iiwgc3VidGl0bGU9IjIwMDYtMjAxNyIsIGNhcHRpb249InNvdXJjZTogY3B3LnN0YXRlLmNvLnVzIikKCmBgYAoKCkEgZ2VuZXJhbCBydWxlIG9mIHRodW1iIHRvIGNvbnNpZGVyIGlzIHRoYXQgc2tld2VkIGRhdGEgd2hvc2UgcmF0aW8gb2YgdGhlIGhpZ2hlc3QgdmFsdWUgdG8gdGhlIApsb3dlc3QgdmFsdWUgaXMgZ3JlYXRlciB0aGFuIDIwIGhhdmUgc2lnbmlmaWNhbnQgc2tld25lc3MuIEFsc28sIHRoZSBza2V3bmVzcyBzdGF0aXN0aWMgY2FuIGJlIAp1c2VkIGFzIGEgZGlhZ25vc3RpYy4gSWYgdGhlIHByZWRpY3RvciBkaXN0cmlidXRpb24gaXMgcm91Z2hseSBzeW1tZXRyaWMsIHRoZSBza2V3bmVzcyB2YWx1ZXMgCndpbGwgYmUgY2xvc2UgdG8gemVyby4gQXMgdGhlIGRpc3RyaWJ1dGlvbiBiZWNvbWVzIG1vcmUgcmlnaHQgc2tld2VkLCB0aGUgc2tld25lc3Mgc3RhdGlzdGljIApiZWNvbWVzIGxhcmdlci4gU2ltaWxhcmx5LCBhcyB0aGUgZGlzdHJpYnV0aW9uIGJlY29tZXMgbW9yZSBsZWZ0IHNrZXdlZCwgdGhlIHZhbHVlIGJlY29tZXMgbmVnYXRpdmUuClJlcGxhY2luZyB0aGUgZGF0YSB3aXRoIHRoZSBsb2csIHNxdWFyZSByb290LCBvciBpbnZlcnNlIG1heSBoZWxwIHRvIHJlbW92ZSB0aGUgc2tldy4KCkV4YW1wbGUgb2YgaG93IEJveENveCBjYW4gcmVkaXN0cmlidXRlIHRoZSBkYXRhCmBgYHtyfQpwcmVQcm9jVmFsdWVzMiA8LSBwcmVQcm9jZXNzKGFzLmRhdGEuZnJhbWUodHJhaW5kYXRhKSwgbWV0aG9kID0gIkJveENveCIpCnRyYWluQkMgPC0gcHJlZGljdChwcmVQcm9jVmFsdWVzMiwgYXMuZGF0YS5mcmFtZSh0cmFpbmRhdGEpKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0xMH0KZ2dwbG90KHRyYWluQkMsIGFlcyhIdW50ZXJzKSkgKyAKICBnZW9tX2RlbnNpdHkoKSArCiAgeGxhYigiQm94Q294IEh1bnRlcnMgaW4gVW5pdCIpICsKICB5bGFiKCJOdW1iZXIgb2YgVW5pdHMiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICBsYWJzKHRpdGxlPSJCb3hDb3ggRGlzdHJpYnV0aW9uIG9mIEh1bnRlcnMgaW4gZWFjaCBVbml0Iiwgc3VidGl0bGU9IjIwMDYtMjAxNyIsIGNhcHRpb249InNvdXJjZTogY3B3LnN0YXRlLmNvLnVzIikKYGBgCmNhcmV0IGhhcyBhIHByZXByb2NjZXNzIGZ1bmN0aW9uIGZvciBjb3JyZWN0aW5nIGZvciBza2V3bmVzcyAnQm94Q294Jywgd2Ugd2lsbCBuZWVkIHRvIGJlIHN1cmUgdG8KbG9vayBhdCB1c2luZyB0aGlzIGZ1bmN0aW9uIGluIHRoZSB0cmFpbmluZyBtb2RlbHMuCgoqKioKIyMgTW9kZWwgQnVpbGRpbmcKVGhpcyBpcyBxdWl0ZSBhbiBpdGVyYXRpdmUgcHJvY2Vzcy4gSXQgaXMgaW1wb3J0YW50IHRvIGRvY3VtZW50IGFuZCBzYXZlIG9mZiBkYXRhLgpSdW4gdGhydSBkaWZmZXJpbmcgJ3F1aWNrIHRvIHRyYWluJyBtZXRob2RzCldoaWNoIG9uZSBwZXJmb3JtZWQgdGhlIGJlc3Q/CkRldGVybWluZSBvdGhlciBzaW1pbGFyIG1ldGhvZHMgYW5kIHJ1biB0aGVtLgpXaGljaCBvbmUgcGVyZm9ybWVkIHRoZSBiZXN0PwpEZXRlcm1pbmUgZGlzaW1taWxhciBtZXRob2RzLgpXaGljaCBvbmUgcGVyZm9ybWVkIHRoZSBiZXN0PwoKTm93IGxldHMgd29yayBvbiBzb21lIHJlZmluZWQgdHVuaW5nLgpBc3Nlc3MgcHJlcHJvY2Vzc2luZyBmdW5jdGlvbnMuIGNlbnRlciwgc2NhbGUsIHBjYSwgYm94Y294LCBuenYsIGV0Ywpzb21lIHVuaXRzIGhhdmUgdmVyeSBsaXR0bGUgZGF0YSwgc2hvdWxkIHdlIHJlbW92ZSB0aGVtPyAnemVybyB2YXJpYW5jZXMKMTIzLCA3OTEsIDg3LCA5NCwgODgKVE9ETywgZnJlcXVlbmN5IHBsb3Qgb2YgVW5pdAoKUnVuIHRoZSBsaXN0IG9mIG90aGVyIGZhdm9yYWJsZSBtZXRob2RzIHdpdGggcHJlcHJvY2Vzc2luZyBpbiBwbGFjZQpGdXJ0aGVyIHR1bmUgbWV0aG9kJ3MgcGFyYW1ldGVycwoKSXMgdGhlcmUgYSBtZXRob2QgdGhhdCBpcyBiZXN0PyAgQXJlIHNvbWUgbWV0aG9kcyBiZXR0ZXIgYXQgcGFydCBvZiB0aGUgZGF0YQp0aGFuIG90aGVycz8gbWF5YmUgd2UgY29tYmluZT8KCkluc2VydCBpbnRvIEF6dXJlTUwgdG8gZnVydGhlciBpbnNwZWN0aW9ucyBhbmQgY3JlYXRlIGludG8gYSB3ZWJzZXJ2aWNlCklmIHRoZSBwYWNrYWdlcyBvciBtZXRob2RzIGFyZSBub3QgeWV0IHN1cHBvcnRlZCBpbiBBenVyZSwgd2Ugd2lsbCBuZWVkIHRvIGNyZWF0ZSBhbiBSIE1vZGVsCmluc3RlYWQgb2YganVzdCBydW5uaW5nIGFuIHJzY3JpcHQuCgpBZGRpdG9uYWxseSwgQXp1cmVNTCBoYXMgc29tZSBhZGRpdGlvbmFsIG1ldGhvZHMgdG8gY29uc2lkZXIsIGVuc3VyZSB3ZSBhdHRlbXB0IHRvIHVzZSB0aG9zZSBhcyB3ZWxsLgoKIyMjIE1vZGVsIFRyYWluaW5nIE1ldGhvZHMKTG9vcCB0aHJvdWdoIHBvc3NpYmxlIG1ldGhvZHMsIHV0aWxpemluZyB0aGUgcXVpY2tlciAnYWRhcHRpdmVfY3YnIHBhcmFtZXRlciBzZWFyY2ggZnJvbSBjYXJldC4KQ29uc2lkZXIgc2NyaXB0aW5nIHRoaXMgaW50byBBenVyZU1MIHRvIG1ha2UgaXQgcnVuIG11Y2ggZmFzdGVyLCB0aG91Z2ggdGhlcmUgaXMgbW9yZSBzZXR1cCBhbmQgZXJyb3JzIHRvIApjb250cm9sIGZvcgoKYGBge3J9CnF1aWNrbWV0aG9kcyA8LSBjKCJsbSIsJ3N2bUxpbmVhcicsInN2bVJhZGlhbCIsImtubiIsImN1YmlzdCIsImtrbm4iLCJnbG0ubmIiKQoKc3RlcDFfYWxsIDwtIE5VTEwKZm9yIChpbWV0aG9kIGluIHF1aWNrbWV0aG9kcykgewogIHN0ZXAxIDwtIE5VTEwKICBzdGFydCA8LSBub3coKQogIAogIGlmIChpbWV0aG9kID09ICJsbSIpIHsKICAgIGNvbnRyb2xtZXRob2QgPC0gInJlcGVhdGVkY3YiCiAgfSBlbHNlIHtjb250cm9sbWV0aG9kIDwtICJhZGFwdGl2ZV9jdiJ9CiAgCiAgZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2woCiAgICBtZXRob2QgPSBjb250cm9sbWV0aG9kLAogICAgIyBzZWFyY2ggPSAncmFuZG9tJywKICAgIG51bWJlciA9IDQsCiAgICByZXBlYXRzID0gNCwKICAgIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogICAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCiAgCiAgcmVnaXN0ZXJEb1NFUSgpCiAgcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKICAKICBIdW50ZXJzTW9kZWxfMSA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gdHJhaW5kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gaW1ldGhvZCwKICAgICAgICAgICAgICAgICAgICAgICAgIHByZVByb2MgPSBjKCJjZW50ZXIiLCJzY2FsZSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSAxNSwKICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCiAgCiAgSHVudGVyc01vZGVsXzEKICAKICAjIG1lYXN1cmUgcGVyZm9ybWFuY2UKICBwcmVkaWN0ZGF0YSA8LSBwcmVkaWN0KEh1bnRlcnNNb2RlbF8xLCB0ZXN0ZGF0YSkKICAKICBzdGVwMSRtZXRob2QgPC0gaW1ldGhvZAogIHN0ZXAxJFJNU0UgPC0gcG9zdFJlc2FtcGxlKHByZWQgPSBwcmVkaWN0ZGF0YSwgb2JzID0gdGVzdGRhdGEkSHVudGVycylbMV0KICBzdGVwMSRkdXJhdGlvbiA8LSBub3coKSAtIHN0YXJ0CiAgc3RlcDEgPC0gYXMuZGF0YS5mcmFtZShzdGVwMSkKICBzdGVwMV9hbGwgPC0gcmJpbmQoc3RlcDFfYWxsLHN0ZXAxKQp9CmBgYApWaWV3IFJlc3VsdHMKYGBge3J9CnN0ZXAxX2FsbApgYGAKYGBge3J9CnRvcF90d29fbW9kZWxzIDwtIHRvcF9uKHN0ZXAxX2FsbCwyLC1STVNFKSRtZXRob2QKYGBgCiMjIyBNb3JlIE1vZGVsIFRyYWluaW5nIE1ldGhvZHMKClRha2UgdGhlIHRvcCB0d28gYW5kIGRldGVybWluZSBzb21lIGFkZGl0b25hbCBtZXRob2RzIHRvIHRyeSBieSBtYXhpbWl6aW5nIHRoZSBKYWNjYXJkCmRpc3NpbWlsYXJpdHkgYmV0d2VlbiBzZXRzIG9mIG1vZGVscwpgYGB7cn0KdGFnIDwtIHJlYWQuY3N2KCJ0YWdfZGF0YS5jc3YiLCByb3cubmFtZXMgPSAxKQp0YWcgPC0gYXMubWF0cml4KHRhZykKYGBgCgpTZWxlY3Qgb25seSBtb2RlbHMgZm9yIHJlZ3Jlc3Npb24KYGBge3J9CnJlZ01vZGVscyA8LSB0YWdbdGFnWywiUmVncmVzc2lvbiJdID09IDEsXQoKYWxsIDwtIDE6bnJvdyhyZWdNb2RlbHMpCmRpc3NpbWlsYXJtZXRob2RzX2FsbCA8LSBOVUxMCmZvciAoaXRvcHR3byBpbiAxOjIpIHsKICAjIyBTZWVkIHRoZSBhbmFseXNpcyB3aXRoIHRoZSBtb2RlbCBvZiBpbnRlcmVzdAogIHN0YXJ0IDwtIGdyZXAodG9wX3R3b19tb2RlbHNbaXRvcHR3b10sIHJvd25hbWVzKHJlZ01vZGVscyksIGZpeGVkID0gVFJVRSkKICBwb29sIDwtIGFsbFthbGwgIT0gc3RhcnRdCiAgCiAgIyMgU2VsZWN0IDQgbW9kZWwgbW9kZWxzIGJ5IG1heGltaXppbmcgdGhlIEphY2NhcmQKICAjIyBkaXNzaW1pbGFyaXR5IGJldHdlZW4gc2V0cyBvZiBtb2RlbHMKICBuZXh0TW9kcyA8LSBtYXhEaXNzaW0ocmVnTW9kZWxzW3N0YXJ0LCxkcm9wID0gRkFMU0VdLCAKICAgICAgICAgICAgICAgICAgICAgICAgcmVnTW9kZWxzW3Bvb2wsIF0sIAogICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAiSmFjY2FyZCIsCiAgICAgICAgICAgICAgICAgICAgICAgIG4gPSA0KQogIAogIHJvd25hbWVzKHJlZ01vZGVscylbYyhuZXh0TW9kcyldCiAgCiAgZGlzc2ltaWxhcm1ldGhvZHMgPC0gcm93bmFtZXMocmVnTW9kZWxzKVtuZXh0TW9kc10KICBkaXNzaW1pbGFybWV0aG9kcyA8LSBzdHJfZXh0cmFjdChzdHJpbmcgPSBkaXNzaW1pbGFybWV0aG9kcyxwYXR0ZXJuID0gIls6YWxudW06XSsoPz1cXCkpIikKICBkaXNzaW1pbGFybWV0aG9kc19hbGwgPC0gYyhkaXNzaW1pbGFybWV0aG9kc19hbGwsZGlzc2ltaWxhcm1ldGhvZHMpCn0KYGBgCgpOb3cgd2UgaGF2ZSA4IG1vcmUgbWV0aG9kcyB0byB0cnkgaW4gdGhlIHNhbWUgbWFubmVyCmBgYHtyfQpkaXNzaW1pbGFybWV0aG9kc19hbGwgPC0gdW5pcXVlKGRpc3NpbWlsYXJtZXRob2RzX2FsbCkKZGlzc2ltaWxhcm1ldGhvZHNfYWxsCmBgYApgYGB7cn0KZm9yIChpbWV0aG9kIGluIGRpc3NpbWlsYXJtZXRob2RzX2FsbCkgewogIHN0ZXAxIDwtIE5VTEwKICBzdGFydF90aW1lciA8LSBub3coKVsxXQogIAogIGlmIChpbWV0aG9kID09ICJsbSIpIHsKICAgIGNvbnRyb2xtZXRob2QgPC0gInJlcGVhdGVkY3YiCiAgfSBlbHNlIHtjb250cm9sbWV0aG9kIDwtICJhZGFwdGl2ZV9jdiJ9CiAgCiAgZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2woCiAgICBtZXRob2QgPSBjb250cm9sbWV0aG9kLAogICAgIyBzZWFyY2ggPSAncmFuZG9tJywKICAgIG51bWJlciA9IDQsCiAgICByZXBlYXRzID0gNCwKICAgIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogICAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCiAgCiAgcmVnaXN0ZXJEb1NFUSgpCiAgcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKICAKICBIdW50ZXJzTW9kZWxfMSA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gdHJhaW5kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gaW1ldGhvZCwKICAgICAgICAgICAgICAgICAgICAgICAgIHByZVByb2MgPSBjKCJjZW50ZXIiLCJzY2FsZSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSAxNSwKICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCiAgCiAgSHVudGVyc01vZGVsXzEKICAKICAjIG1lYXN1cmUgcGVyZm9ybWFuY2UKICBwcmVkaWN0ZGF0YSA8LSBwcmVkaWN0KEh1bnRlcnNNb2RlbF8xLCB0ZXN0ZGF0YSkKICAKICBzdGVwMSRtZXRob2QgPC0gaW1ldGhvZAogIHN0ZXAxJFJNU0UgPC0gcG9zdFJlc2FtcGxlKHByZWQgPSBwcmVkaWN0ZGF0YSwgb2JzID0gdGVzdGRhdGEkSHVudGVycylbMV0KICBzdGVwMSRkdXJhdGlvbiA8LSBub3coKVsxXSAtIHN0YXJ0X3RpbWVyWzFdCiAgc3RlcDEgPC0gYXMuZGF0YS5mcmFtZShzdGVwMSkKICBzdGVwMV9hbGwgPC0gcmJpbmQoc3RlcDFfYWxsLHN0ZXAxKQp9CmBgYAoKYGBge3J9CnN0ZXAxX2FsbApgYGAKCiMjIyBQcmVwcm9jZXNzaW5nIG9uIFRvcCBNb2RlbGluZyBNZXRob2RzCk5vdyBsZXRzIHdvcmsgb24gc29tZSByZWZpbmVkIHR1bmluZyBvbiB0aGUgdG9wIG1ldGhvZHMKQW55IHZhbHVhYmxlIHByZXByb2Nlc3Npbmcgc3RlcHM/CmBgYHtyfQpwcmVwcm9jZXNzZnVuY3Rpb25zIDwtIGMoIkJveENveCIsICJZZW9Kb2huc29uIiwgImV4cG9UcmFucyIsICJjZW50ZXIiLCAic2NhbGUiLCAicmFuZ2UiLCAia25uSW1wdXRlIiwgImJhZ0ltcHV0ZSIsICJtZWRpYW5JbXB1dGUiLCAicGNhIiwgImljYSIsICJzcGF0aWFsU2lnbiIsICJjb3JyIiwgInp2IiwgIm56diIpCnRvcG1ldGhvZHMgPC0gdG9wX24oc3RlcDFfYWxsLDIsLVJNU0UpJG1ldGhvZAoKZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2woCiAgbWV0aG9kID0gImFkYXB0aXZlX2N2IiwgI3JlcGVhdGVkY3YsIAogIHNlYXJjaCA9ICdyYW5kb20nLAogIG51bWJlciA9IDEwLCAjNAogIHJlcGVhdHMgPSAxMCwgIzEwCiAgIyBjbGFzc1Byb2JzID0gVFJVRSwKICAjIHNhdmVQcmVkID0gVFJVRSwKICBhbGxvd1BhcmFsbGVsID0gVFJVRSwKICBzdW1tYXJ5RnVuY3Rpb24gPSBkZWZhdWx0U3VtbWFyeSkKClBQcGVyZm9ybWFuY2VfYWxsIDwtIE5VTEwKUFBwZXJmb3JtYW5jZSA8LSBOVUxMCmZvciAoaW1ldGhvZCBpbiB0b3BtZXRob2RzKSB7CiAgZm9yIChpcHJlcHJvY2VzcyBpbiBwcmVwcm9jZXNzZnVuY3Rpb25zKSB7CiAgICByZWdpc3RlckRvU0VRKCkKICAgIHJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCiAgICAKICAgIFByZVByb2Nlc3NNb2RlbCA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gdHJhaW5kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gaW1ldGhvZCwKICAgICAgICAgICAgICAgICAgICAgICAgIHByZVByb2MgPSBpcHJlcHJvY2VzcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAjdHVuZUxlbmd0aCA9IDEwLAogICAgICAgICAgICAgICAgICAgICAgICAgI3R1bmVHcmlkID0ga2tublR1bmVHcmlkLAogICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKICAgIAogICAgcHJpbnQoUHJlUHJvY2Vzc01vZGVsKQogICAgCiAgICAjIGNoZWNrIHBlcmZvcm1hbmNlCiAgICBwcmVkaWN0ZGF0YSA8LSBwcmVkaWN0KFByZVByb2Nlc3NNb2RlbCwgdGVzdGRhdGEpCiAgICAKICAgIFBQcGVyZm9ybWFuY2UkbWV0aG9kIDwtIGltZXRob2QKICAgIFBQcGVyZm9ybWFuY2UkcHJlcHJvY2VzcyA8LSBpcHJlcHJvY2VzcwogICAgUFBwZXJmb3JtYW5jZSRSTVNFIDwtIHBvc3RSZXNhbXBsZShwcmVkID0gcHJlZGljdGRhdGEsIG9icyA9IHRlc3RkYXRhJEh1bnRlcnMpWzFdCiAgICBQUHBlcmZvcm1hbmNlIDwtIGFzLmRhdGEuZnJhbWUoUFBwZXJmb3JtYW5jZSkKICAgIFBQcGVyZm9ybWFuY2VfYWxsIDwtIHJiaW5kKFBQcGVyZm9ybWFuY2VfYWxsLFBQcGVyZm9ybWFuY2UpCiAgfQp9CiMgU29tZSBvZiB0aGUgbW9kZWxzIHdlcmUgbG9hZGVkIGludG8gQXp1cmVNTCBhbmQgcHJvY2Vzc2VkIHRoZXJlLgojIE91dHB1dCBmcm9tIEF6dXJlTUwKIyBbTW9kdWxlT3V0cHV0XSAgICAgICAgICBtZXRob2QgcHJlcHJvY2VzcyAgICAgUk1TRQojIFtNb2R1bGVPdXRwdXRdIFJNU0UgICAgICAga2tubiAgICAgQm94Q294IDEzMC43OTM5CiMgW01vZHVsZU91dHB1dF0gUk1TRTEgICAgICBra25uIFllb0pvaG5zb24gMTMwLjk2MDAKIyBbTW9kdWxlT3V0cHV0XSBSTVNFMiAgICAgIGtrbm4gICAgIGNlbnRlciAxMzAuNzMzMQojIFtNb2R1bGVPdXRwdXRdIFJNU0UzICAgICAga2tubiAgICAgIHNjYWxlIDEzMC4xODE4CiMgW01vZHVsZU91dHB1dF0gUk1TRTQgICAgICBra25uICAgICAgICBwY2EgMTMwLjIwNzEKIyBbTW9kdWxlT3V0cHV0XSBSTVNFNSBzdm1SYWRpYWwgICAgIEJveENveCAxNTQuMDg5OAojIFtNb2R1bGVPdXRwdXRdIFJNU0U2IHN2bVJhZGlhbCBZZW9Kb2huc29uIDE2OS45ODE2CiMgW01vZHVsZU91dHB1dF0gUk1TRTcgc3ZtUmFkaWFsICAgICBjZW50ZXIgMTU0LjE4OTEKIyBbTW9kdWxlT3V0cHV0XSBSTVNFOCBzdm1SYWRpYWwgICAgICBzY2FsZSAxNTQuMTAwMAojIFtNb2R1bGVPdXRwdXRdIFJNU0U5IHN2bVJhZGlhbCAgICAgICAgcGNhIDE2NC4wODgxCiMgc3ZtUmFkaWFsIGFuZCBra25uIGRvbid0IHBlcmZvcm0gYmV0dGVyIHdpdGggYW55IG9mIHRoZSBwcmVwcm9jZXNzaW5nIGZ1bmN0aW9ucyBpbiBwbGFjZQpgYGAKYGBge3J9ClBQcGVyZm9ybWFuY2VfYWxsCmBgYAoKIyMjIE1vZGVsIFByZWRpY3RvcnMKTm93IHdlIGNhbiByZXZpZXcgdGhlIHByZWRpY3RvcnMsIHRoZXJlIGFyZSBvbmx5IGEgZmV3IGZpZWxkcyBzbyBJIHdpbGwgbWFudWFsbHkgdGVzdCBwZXJmb3JtYW5jZQp3aGlsZSBleGNsdWRpbmcgZWFjaCBvZiB0aGVtIHRvIG1vbml0b3IgdGhlaXIgaW1wb3J0YW5jZS4KU29tZSBvZiBvdXIgZmllbGRzIGFyZSBpbnN0aW5jdGl2ZWx5IHJlcXVpcmVkIChZZWFyLCBVbml0KQpgYGB7cn0KUHJlZGljdG9ycyA8LSBjKCJRdW90YSIsIkRyYXduIikKUHJlZGljdG9ycGVyZm9ybWFuY2VfYWxsIDwtIE5VTEwKUHJlZGljdG9ycGVyZm9ybWFuY2UgPC0gTlVMTApmb3IgKGltZXRob2QgaW4gdG9wbWV0aG9kcykgewogIGZvciAoaXByZWRpY3RvciBpbiBQcmVkaWN0b3JzKSB7CiAgICByZWdpc3RlckRvU0VRKCkKICAgIHJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCiAgICAKICAgIFByZWRpY3Rvck1vZGVsID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBzZWxlY3QodHJhaW5kYXRhLC1pcHJlZGljdG9yKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9IGltZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQogICAgCiAgICBwcmludChQcmVkaWN0b3JNb2RlbCkKICAgIAogICAgIyBjaGVjayBwZXJmb3JtYW5jZQogICAgcHJlZGljdGRhdGEgPC0gcHJlZGljdChQcmVkaWN0b3JNb2RlbCwgdGVzdGRhdGEpCiAgICAKICAgIFByZWRpY3RvcnBlcmZvcm1hbmNlJG1ldGhvZCA8LSBpbWV0aG9kCiAgICBQcmVkaWN0b3JwZXJmb3JtYW5jZSRtaXNzaW5nX3ByZWRpY3RvciA8LSBpcHJlZGljdG9yCiAgICBQcmVkaWN0b3JwZXJmb3JtYW5jZSRSTVNFIDwtIHBvc3RSZXNhbXBsZShwcmVkID0gcHJlZGljdGRhdGEsIG9icyA9IHRlc3RkYXRhJEh1bnRlcnMpWzFdCiAgICBQcmVkaWN0b3JwZXJmb3JtYW5jZSA8LSBhcy5kYXRhLmZyYW1lKFByZWRpY3RvcnBlcmZvcm1hbmNlKQogICAgUHJlZGljdG9ycGVyZm9ybWFuY2VfYWxsIDwtIHJiaW5kKFByZWRpY3RvcnBlcmZvcm1hbmNlX2FsbCxQcmVkaWN0b3JwZXJmb3JtYW5jZSkKICB9Cn0KYGBgCgpgYGB7cn0KUHJlZGljdG9ycGVyZm9ybWFuY2VfYWxsCmBgYAoKCnN2TVJhZGlhbCB3aWxsIHBlcmZvcm0gYmV0dGVyIHdpdGggYWxsIG9mIHRoZSBwcmVkaWN0b3JzLCB3aGlsZSBra25uIHBlcmZvcm1zCmJldHRlciB3aXRoIG9ubHkgVW5pdCBhbmQgWWVhciBmaWVsZHMKClVzZSBhYm92ZSBpbmZvcm1hdGlvbiB0byB0ZXN0IG91dCB2YXJpb3VzIGNvbWJpbmF0aW9ucyBvZiBwcmVwcm9jZXNzaW5nIGFuZCBwcmVkaWN0b3Igc2V0cwoKIyMjIyMga2tubgpra25uIHdpdGhvdXQgUXVvdGEgYW5kIERyYXduCmBgYHtyfQpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAiYWRhcHRpdmVfY3YiLCAjcmVwZWF0ZWRjdiwgCiAgc2VhcmNoID0gJ3JhbmRvbScsCiAgbnVtYmVyID0gMTAsICM0CiAgcmVwZWF0cyA9IDEwLCAjMTAKICAjIGNsYXNzUHJvYnMgPSBUUlVFLAogICMgc2F2ZVByZWQgPSBUUlVFLAogIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogIHN1bW1hcnlGdW5jdGlvbiA9IGRlZmF1bHRTdW1tYXJ5KQoKcmVnaXN0ZXJEb1NFUSgpCnJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCgpra25uTW9kZWwgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IHNlbGVjdChDT0Vsa0h1bnRlcnMsLVF1b3RhLCAtRHJhd24pLAogICAgICAgICAgICAgICAgICBtZXRob2QgPSAia2tubiIsCiAgICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSA3NSwKICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKCmBgYApgYGB7cn0Ka2tubk1vZGVsCmBgYAoKQmVzdCBSTVNFCmBgYHtyfQojIG5vdCBzdXJlIHdoeSBjYXJldCBpcyBzZWxlY3RpbmcgcGFyYW1ldGVycyB3aXRoIGhpZ2hlciBSTVNFLCBsZXRzIHNlbGVjdCBtYW51YWxseQpSU01Fa2tubiA8LSBmaWx0ZXIoa2tubk1vZGVsJHJlc3VsdHMsIFJNU0UgPT0gbWluKFJNU0UpKQpSU01Fa2tubiRrZXJuZWwgPC0gYXMuY2hhcmFjdGVyKFJTTUVra25uJGtlcm5lbCkKUlNNRWtrbm4KYGBgCgojIyMgTW9kZWwgVHVuaW5nCnJ1biBhZ2FpbiB3aXRoIGEgdHVuZSBncmlkCmBgYHtyfQpra25uVHVuZUdyaWQgPC0gZGF0YS5mcmFtZShrbWF4ID0gYyhSU01Fa2tubiRrbWF4LFJTTUVra25uJGttYXgsUlNNRWtrbm4ka21heCxSU01Fa2tubiRrbWF4LFJTTUVra25uJGttYXgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBkaXN0YW5jZSA9IGMoUlNNRWtrbm4kZGlzdGFuY2UqLjcsUlNNRWtrbm4kZGlzdGFuY2UqLjksUlNNRWtrbm4kZGlzdGFuY2UsUlNNRWtrbm4kZGlzdGFuY2UqMS4xLFJTTUVra25uJGRpc3RhbmNlKjEuMyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbCA9IGMoUlNNRWtrbm4ka2VybmVsLFJTTUVra25uJGtlcm5lbCxSU01Fa2tubiRrZXJuZWwsUlNNRWtrbm4ka2VybmVsLFJTTUVra25uJGtlcm5lbCkpCgpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsICNyZXBlYXRlZGN2LCAKICBudW1iZXIgPSAxMCwgIzQKICByZXBlYXRzID0gMTAsICMxMAogIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogIHN1bW1hcnlGdW5jdGlvbiA9IGRlZmF1bHRTdW1tYXJ5KQoKcmVnaXN0ZXJEb1NFUSgpCnJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCgpra25uR3JpZE1vZGVsID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBzZWxlY3QoQ09FbGtIdW50ZXJzLC1RdW90YSwgLURyYXduKSwKICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImtrbm4iLAogICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGtrbm5UdW5lR3JpZCwKICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKYGBgCmBgYHtyfQpra25uR3JpZE1vZGVsCmBgYAoKCkJlc3QgUk1TRQpgYGB7cn0KUlNNRWtrbm4gPC0gZmlsdGVyKGtrbm5HcmlkTW9kZWwkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpClJTTUVra25uJGtlcm5lbCA8LSBhcy5jaGFyYWN0ZXIoUlNNRWtrbm4ka2VybmVsKQpgYGAKCnJ1biBhZ2FpbiB3aXRoIGEgdHVuZSBncmlkCmBgYHtyfQpra25uVHVuZUdyaWQyIDwtIGRhdGEuZnJhbWUoa21heCA9IGMoUlNNRWtrbm4ka21heCouNyxSU01Fa2tubiRrbWF4Ki45LFJTTUVra25uJGttYXgsUlNNRWtrbm4ka21heCoxLjEsUlNNRWtrbm4ka21heCoxLjMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBkaXN0YW5jZSA9IGMoUlNNRWtrbm4kZGlzdGFuY2UsUlNNRWtrbm4kZGlzdGFuY2UsUlNNRWtrbm4kZGlzdGFuY2UsUlNNRWtrbm4kZGlzdGFuY2UsUlNNRWtrbm4kZGlzdGFuY2UpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWwgPSBjKFJTTUVra25uJGtlcm5lbCxSU01Fa2tubiRrZXJuZWwsUlNNRWtrbm4ka2VybmVsLFJTTUVra25uJGtlcm5lbCxSU01Fa2tubiRrZXJuZWwpKQoKcmVnaXN0ZXJEb1NFUSgpCnJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCgpra25uR3JpZE1vZGVsMiA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gc2VsZWN0KENPRWxrSHVudGVycywtUXVvdGEsIC1EcmF3biksCiAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAia2tubiIsCiAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGtrbm5UdW5lR3JpZDIsCiAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQoKYGBgCgpgYGB7cn0Ka2tubkdyaWRNb2RlbDIKYGBgCgoKT25lIG1vcmUgdGltZSBvbiBmaW5hbCBwYXJhbWV0ZXIgKGtlcm5lbCkKQmVzdCBSTVNFCmBgYHtyfQpSU01Fa2tubiA8LSBmaWx0ZXIoa2tubkdyaWRNb2RlbDIkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpWzEsXQprZXJuZWxzIDwtIGxldmVscyhra25uTW9kZWwkcmVzdWx0cyRrZXJuZWwpCmBgYAoKcnVuIGFnYWluIHdpdGggYSB0dW5lIGdyaWQKYGBge3J9Cmtrbm5UdW5lR3JpZDMgPC0gZGF0YS5mcmFtZShrbWF4ID0gcmVwKDQ2NS4wLDgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzdGFuY2UgPSByZXAoMC4xMzk1NTg2LDgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAga2VybmVsID0ga2VybmVscykKCnJlZ2lzdGVyRG9TRVEoKQpyZWdpc3RlckRvTUMoY29yZXMgPSA2KQoKa2tubkdyaWRNb2RlbDMgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IHNlbGVjdChDT0Vsa0h1bnRlcnMsLVF1b3RhLCAtRHJhd24pLAogICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJra25uIiwKICAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGtrbm5UdW5lR3JpZDMsCiAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKCmBgYAoKYGBge3J9Cmtrbm5HcmlkTW9kZWwzCmBgYAoKYGBge3J9ClJTTUVra25uIDwtIGZpbHRlcihra25uR3JpZE1vZGVsMyRyZXN1bHRzLCBSTVNFID09IG1pbihSTVNFKSkKYGBgCgpCZXN0IFJNU0UgZm9yIGtrbm4gdGh1cyBmYXIKYGBge3J9ClJTTUVra25uIDwtIGZpbHRlcihra25uTW9kZWwkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpCmBgYAoKV29yayB0aHJ1IHNvbWUgcmVzYW1wbGluZyBtZXRob2RzIHdpdGggYmVzdCBra25uIHBhcmFtcwpgYGB7cn0Ka2tublR1bmVHcmlkNCA8LSBkYXRhLmZyYW1lKGttYXggPSBSU01Fa2tubiRrbWF4LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzdGFuY2UgPSBSU01Fa2tubiRkaXN0YW5jZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbCA9IGFzLmNoYXJhY3RlcihSU01Fa2tubiRrZXJuZWwpKQoKdHJhaW5tZXRob2RzIDwtIGMoImJvb3QiLCAiYm9vdDYzMiIsICJvcHRpbWlzbV9ib290IiwgImJvb3RfYWxsIiwgImN2IiwgInJlcGVhdGVkY3YiLCAiTE9PQ1YiLCAiTEdPQ1YiLCAibm9uZSIpCnRyYWlubWV0aG9kcGVyZm9ybWFuY2VfYWxsIDwtIE5VTEwKZm9yIChpdHJhaW5tZXRob2QgaW4gdHJhaW5tZXRob2RzKSB7CiAgdHJhaW5tZXRob2RwZXJmb3JtYW5jZSA8LSBOVUxMCiAgZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2woCiAgICBtZXRob2QgPSBpdHJhaW5tZXRob2QsCiAgICBudW1iZXIgPSAxMCwgIzQKICAgIHJlcGVhdHMgPSAxMCwgIzEwCiAgICBhbGxvd1BhcmFsbGVsID0gVFJVRSwKICAgIHN1bW1hcnlGdW5jdGlvbiA9IGRlZmF1bHRTdW1tYXJ5KQogIAogIHJlZ2lzdGVyRG9TRVEoKQogIHJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCiAgCiAga2tublRyYWluTW9kZWwgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IHNlbGVjdChDT0Vsa0h1bnRlcnMsLVF1b3RhLCAtRHJhd24pLAogICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImtrbm4iLAogICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBra25uVHVuZUdyaWQ0LAogICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKICAKICBwcmludChra25uVHJhaW5Nb2RlbCkKICB0cmFpbm1ldGhvZHBlcmZvcm1hbmNlIDwtIGZpbHRlcihra25uVHJhaW5Nb2RlbCRyZXN1bHRzLCBSTVNFID09IG1pbihSTVNFKSkKICB0cmFpbm1ldGhvZHBlcmZvcm1hbmNlJHRyYWlubWV0aG9kIDwtIGl0cmFpbm1ldGhvZAogIHRyYWlubWV0aG9kcGVyZm9ybWFuY2VfYWxsIDwtIHJiaW5kLmZpbGwodHJhaW5tZXRob2RwZXJmb3JtYW5jZV9hbGwsdHJhaW5tZXRob2RwZXJmb3JtYW5jZSkKfQpgYGAKYGBge3J9CnRyYWlubWV0aG9kcGVyZm9ybWFuY2VfYWxsCmBgYAoKCmBgYHtyfQpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAib3B0aW1pc21fYm9vdCIsCiAgbnVtYmVyID0gMTAsICM0CiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsCiAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCgpra25uRmluYWxUcmFpbk1vZGVsID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBDT0Vsa0h1bnRlcnMsCiAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImtrbm4iLAogICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga2tublR1bmVHcmlkNCwKICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQoKYGBgCgpzYXZlIG9mZiBmb3IgZnV0dXJlIGxvYWRpbmcKYGBge3J9CnNhdmUoa2tubkZpbmFsVHJhaW5Nb2RlbCwgZmlsZSA9ICJ+L19jb2RlL2NvbG9yYWRvLWRvdy9kYXRhc2V0cy9ra25uRmluYWxUcmFpbk1vZGVsLlJEYXRhIikKYGBgCiMjIE1vZGVsIFRlc3RpbmcKYmFjayB0byB0cmFpbiB2cyB0ZXN0IGRhdGEgZm9yIG9uZSBtb3JlIHBlcmZvcm1hbmNlIG1lYXN1cmUgYW5kIGNoYXJ0Li4uIGV2ZW4gdGhvdWdoCmZvciBmdXR1cmUgZGF0YSB3ZSB3aWxsIHVzZSB0aGUgZmluYWwgdHJhaW5lZCBtb2RlbApgYGB7cn0Ka2tublRyYWluTW9kZWwgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IHRyYWluZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJra25uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga2tublR1bmVHcmlkNCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCmBgYAoKY2hlY2sgcGVyZm9ybWFuY2UKYGBge3J9CnByZWRpY3RkYXRhIDwtIHByZWRpY3Qoa2tublRyYWluTW9kZWwsIHRlc3RkYXRhKQoKcG9zdFJlc2FtcGxlKHByZWQgPSBwcmVkaWN0ZGF0YSwgb2JzID0gdGVzdGRhdGEkSHVudGVycykKYGBgCgpDaGFydCBwZXJmb3JtYW5jZSBvZiBwcmVkaWN0ZWQKYGBge3J9CmNoYXJ0cGVyZm9ybWFuY2UgPC0gZGF0YS5mcmFtZShwcmVkaWN0ZWQgPSBwcmVkaWN0ZGF0YSwgb2JzZXJ2ZWQgPSB0ZXN0ZGF0YSRIdW50ZXJzKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0xMH0KZ2dwbG90KGNoYXJ0cGVyZm9ybWFuY2UsIGFlcyhwcmVkaWN0ZWQsb2JzZXJ2ZWQpKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHRpdGxlPSJQZXJmb3JtYW5jZSBvZiBOdW1iZXIgb2YgSHVudGVycyBQcmVkaWN0aW9uIiwgY2FwdGlvbj0ic291cmNlOiBjcHcuc3RhdGUuY28udXMiKQpgYGAKCgoKIyMgU1ZNIApPdXRwdXQgZnJvbSBBenVyZU1MCltNb2R1bGVPdXRwdXRdIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIHdpdGggUmFkaWFsIEJhc2lzIEZ1bmN0aW9uIEtlcm5lbCAKW01vZHVsZU91dHB1dF0gCltNb2R1bGVPdXRwdXRdIDE1NDAgc2FtcGxlcwpbTW9kdWxlT3V0cHV0XSAgICA0IHByZWRpY3RvcnMKW01vZHVsZU91dHB1dF0gCltNb2R1bGVPdXRwdXRdIE5vIHByZS1wcm9jZXNzaW5nCltNb2R1bGVPdXRwdXRdIFJlc2FtcGxpbmc6IENyb3NzLVZhbGlkYXRlZCAoMTAgZm9sZCwgcmVwZWF0ZWQgMTAgdGltZXMpIApbTW9kdWxlT3V0cHV0XSAKW01vZHVsZU91dHB1dF0gU3VtbWFyeSBvZiBzYW1wbGUgc2l6ZXM6IDEzODYsIDEzODYsIDEzODcsIDEzODcsIDEzODUsIDEzODUsIC4uLiAKW01vZHVsZU91dHB1dF0gCltNb2R1bGVPdXRwdXRdIFJlc2FtcGxpbmcgcmVzdWx0cyBhY3Jvc3MgdHVuaW5nIHBhcmFtZXRlcnM6CltNb2R1bGVPdXRwdXRdIApbTW9kdWxlT3V0cHV0XSAgIEMgICAgICAgIFJNU0UgIFJzcXVhcmVkICBSTVNFIFNEICBSc3F1YXJlZCBTRApbTW9kdWxlT3V0cHV0XSAgIDAuMjUgICAgIDI3NiAgIDAuOTQ2ICAgICAzMC42ICAgICAwLjAwODIyICAgIApbTW9kdWxlT3V0cHV0XSAgIDAuNSAgICAgIDIwMyAgIDAuOTYgICAgICAyMS40ICAgICAwLjAwNzU0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDEgICAgICAgIDE4MCAgIDAuOTY1ICAgICAxNy43ICAgICAwLjAwNjcxICAgIApbTW9kdWxlT3V0cHV0XSAgIDIgICAgICAgIDE2OCAgIDAuOTY5ICAgICAxNS43ICAgICAwLjAwNjE0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDQgICAgICAgIDE1OCAgIDAuOTcyICAgICAxNC45ICAgICAwLjAwNTUgICAgIApbTW9kdWxlT3V0cHV0XSAgIDggICAgICAgIDE1MCAgIDAuOTc1ICAgICAxNC43ICAgICAwLjAwNTA2ICAgIApbTW9kdWxlT3V0cHV0XSAgIDE2ICAgICAgIDE0NiAgIDAuOTc2ICAgICAxNC43ICAgICAwLjAwNDgxICAgIApbTW9kdWxlT3V0cHV0XSAgIDMyICAgICAgIDE0NCAgIDAuOTc2ICAgICAxNC43ICAgICAwLjAwNDc3ICAgIApbTW9kdWxlT3V0cHV0XSAgIDY0ICAgICAgIDE0MyAgIDAuOTc3ICAgICAxNC42ICAgICAwLjAwNDc0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDEyOCAgICAgIDE0MCAgIDAuOTc3ICAgICAxNC4zICAgICAwLjAwNDY5ICAgIApbTW9kdWxlT3V0cHV0XSAgIDI1NiAgICAgIDEzOSAgIDAuOTc4ICAgICAxNSAgICAgICAwLjAwNDg1ICAgIApbTW9kdWxlT3V0cHV0XSAgIDUxMiAgICAgIDEzNyAgIDAuOTc4ICAgICAxNC45ICAgICAwLjAwNDg0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDEwMjAgICAgIDEzNiAgIDAuOTc5ICAgICAxNS4zICAgICAwLjAwNDk0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDIwNTAgICAgIDEzNSAgIDAuOTc5ICAgICAxNS42ICAgICAwLjAwNTEzICAgIApbTW9kdWxlT3V0cHV0XSAgIDQxMDAgICAgIDEzNiAgIDAuOTc5ICAgICAxNS41ICAgICAwLjAwNTEgICAgIApbTW9kdWxlT3V0cHV0XSAgIDgxOTAgICAgIDEzNyAgIDAuOTc4ICAgICAxNS43ICAgICAwLjAwNTE4ICAgIApbTW9kdWxlT3V0cHV0XSAgIDE2NDAwICAgIDEzOSAgIDAuOTc4ICAgICAxNi41ICAgICAwLjAwNTUxICAgIApbTW9kdWxlT3V0cHV0XSAgIDMyODAwICAgIDE0MSAgIDAuOTc3ICAgICAxNy42ICAgICAwLjAwNiAgICAgIApbTW9kdWxlT3V0cHV0XSAgIDY1NTAwICAgIDE0NSAgIDAuOTc2ICAgICAxOS40ICAgICAwLjAwNjU5ICAgIApbTW9kdWxlT3V0cHV0XSAgIDEzMTAwMCAgIDE1MSAgIDAuOTc0ICAgICAyMC44ICAgICAwLjAwNzE4ICAgIApbTW9kdWxlT3V0cHV0XSAgIDI2MjAwMCAgIDE2MSAgIDAuOTcgICAgICAyNy4yICAgICAwLjAxMDQgICAgIApbTW9kdWxlT3V0cHV0XSAgIDUyNDAwMCAgIDQ3OCAgIDAuOCAgICAgICAzMjUgICAgICAwLjE3MiAgICAgIApbTW9kdWxlT3V0cHV0XSAgIDEwNTAwMDAgIDExODAgIDAuNSAgICAgICAxMDEwICAgICAwLjIwNCAgICAgIApbTW9kdWxlT3V0cHV0XSAgIDIxMDAwMDAgIDMyNDAgIDAuMTQ4ICAgICAyMjYwICAgICAwLjExNyAgICAgIApbTW9kdWxlT3V0cHV0XSAgIDQxOTAwMDAgIDYwMDAgIDAuMDYwNCAgICA2NDAwICAgICAwLjA1MjcgICAgIApbTW9kdWxlT3V0cHV0XSAKW01vZHVsZU91dHB1dF0gVHVuaW5nIHBhcmFtZXRlciAnc2lnbWEnIHdhcyBoZWxkIGNvbnN0YW50IGF0IGEgdmFsdWUgb2YgMC4wMDM3NjUzCltNb2R1bGVPdXRwdXRdIFJNU0Ugd2FzIHVzZWQgdG8gc2VsZWN0IHRoZSBvcHRpbWFsIG1vZGVsIHVzaW5nICB0aGUgc21hbGxlc3QgdmFsdWUuCltNb2R1bGVPdXRwdXRdIFRoZSBmaW5hbCB2YWx1ZXMgdXNlZCBmb3IgdGhlIG1vZGVsIHdlcmUgc2lnbWEgPSAwLjAwMzc3IGFuZCBDID0gMjA0OC4gCgoKIyBydW4gYWdhaW4gd2l0aCBhIHR1bmUgZ3JpZApgYGB7cn0Kc3ZtUmFkVHVuZUdyaWQgPC0gZGF0YS5mcmFtZSguc2lnbWEgPSBjKDAuMDAzNzY1MywwLjAwMzc2NTMsMC4wMDM3NjUzLDAuMDAzNzY1MywwLjAwMzc2NTMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgLkMgPSBjKDIwNDgqLjcsMjA0OCouOSwyMDQ4LDIwNDgqMS4xLDIwNDgqMS4zKSkKCmZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgI3JlcGVhdGVkY3YsIAogIG51bWJlciA9IDEwLCAjNAogIHJlcGVhdHMgPSAxMCwgIzEwCiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsCiAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCgpyZWdpc3RlckRvU0VRKCkKcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKCnN2bVJhZEdyaWRNb2RlbCA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gQ09FbGtIdW50ZXJzLAogICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bVJhZGlhbCIsCiAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IHN2bVJhZFR1bmVHcmlkLAogICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKYGBgCgpgYGB7cn0Kc3ZtUmFkR3JpZE1vZGVsCmBgYAoKCkJlc3QgUk1TRSwgbm90IHN1cmUgd2h5IGNhcmV0IGlzIHNlbGVjdGluZyBwYXJhbWV0ZXJzIHdpdGggaGlnaGVyIFJNU0UsIGxldHMgc2VsZWN0IG1hbnVhbGx5CmBgYHtyfQpSU01Fc3ZtUmFkIDwtIGZpbHRlcihzdm1SYWRHcmlkTW9kZWwkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpCmBgYAoKcnVuIGFnYWluIHdpdGggYSB0dW5lIGdyaWQKYGBge3J9CnN2bVJhZFR1bmVHcmlkMiA8LSBkYXRhLmZyYW1lKC5zaWdtYSA9IGMoUlNNRXN2bVJhZCRzaWdtYSouNyxSU01Fc3ZtUmFkJHNpZ21hKi45LFJTTUVzdm1SYWQkc2lnbWEsUlNNRXN2bVJhZCRzaWdtYSoxLjEsUlNNRXN2bVJhZCRzaWdtYSoxLjMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5DID0gYyhSU01Fc3ZtUmFkJEMsUlNNRXN2bVJhZCRDLFJTTUVzdm1SYWQkQyxSU01Fc3ZtUmFkJEMsUlNNRXN2bVJhZCRDKSkKCnJlZ2lzdGVyRG9TRVEoKQpyZWdpc3RlckRvTUMoY29yZXMgPSA2KQoKc3ZtUmFkR3JpZE1vZGVsMiA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gQ09FbGtIdW50ZXJzLAogICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUmFkaWFsIiwKICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBzdm1SYWRUdW5lR3JpZDIsCiAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCmBgYAoKYGBge3J9CnN2bVJhZEdyaWRNb2RlbDIKYGBgCgpgYGB7cn0KUlNNRXN2bVJhZCA8LSBmaWx0ZXIoc3ZtUmFkR3JpZE1vZGVsMiRyZXN1bHRzLCBSTVNFID09IG1pbihSTVNFKSkKYGBgCgpXb3JrIHRocnUgc29tZSByZXNhbXBsaW5nIG1ldGhvZHMgd2l0aCBiZXN0IGtrbm4gcGFyYW1zCmBgYHtyfQpzdm1SYWRUdW5lR3JpZDMgPC0gZGF0YS5mcmFtZSguc2lnbWEgPSBSU01Fc3ZtUmFkJHNpZ21hLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgLkMgPSBSU01Fc3ZtUmFkJEMpCgp0cmFpbm1ldGhvZHMgPC0gYygiYm9vdCIsICJib290NjMyIiwgIm9wdGltaXNtX2Jvb3QiLCAiY3YiLCAicmVwZWF0ZWRjdiIsICJMT09DViIsICJMR09DViIsICJub25lIikKdHJhaW5tZXRob2RwZXJmb3JtYW5jZV9hbGwgPC0gTlVMTApmb3IgKGl0cmFpbm1ldGhvZCBpbiB0cmFpbm1ldGhvZHMpIHsKICB0cmFpbm1ldGhvZHBlcmZvcm1hbmNlIDwtIE5VTEwKICBmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICAgIG1ldGhvZCA9IGl0cmFpbm1ldGhvZCwKICAgIG51bWJlciA9IDEwLCAjNAogICAgcmVwZWF0cyA9IDEwLCAjMTAKICAgIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogICAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCiAgCiAgcmVnaXN0ZXJEb1NFUSgpCiAgcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKICAKICBzdm1SYWRUcmFpbk1vZGVsID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBDT0Vsa0h1bnRlcnMsCiAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUmFkaWFsIiwKICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gc3ZtUmFkVHVuZUdyaWQzLAogICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKICAKICBwcmludChzdm1SYWRUcmFpbk1vZGVsKQogIHRyYWlubWV0aG9kcGVyZm9ybWFuY2UgPC0gZmlsdGVyKHN2bVJhZFRyYWluTW9kZWwkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpCiAgdHJhaW5tZXRob2RwZXJmb3JtYW5jZSR0cmFpbm1ldGhvZCA8LSBpdHJhaW5tZXRob2QKICB0cmFpbm1ldGhvZHBlcmZvcm1hbmNlX2FsbCA8LSByYmluZC5maWxsKHRyYWlubWV0aG9kcGVyZm9ybWFuY2VfYWxsLHRyYWlubWV0aG9kcGVyZm9ybWFuY2UpCn0KYGBgCmBgYHtyfQp0cmFpbm1ldGhvZHBlcmZvcm1hbmNlX2FsbApgYGAKCmBgYHtyfQpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAib3B0aW1pc21fYm9vdCIsCiAgbnVtYmVyID0gMTAsICM0CiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsCiAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCgpzdm1SYWRGaW5hbFRyYWluTW9kZWwgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IENPRWxrSHVudGVycywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJzdm1SYWRpYWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBzdm1SYWRUdW5lR3JpZDMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQoKYGBgCgpzYXZlIG9mZiBmb3IgZnV0dXJlIGxvYWRpbmcKYGBge3J9CnNhdmUoc3ZtUmFkRmluYWxUcmFpbk1vZGVsLCBmaWxlID0gIn4vX2NvZGUvY29sb3JhZG8tZG93L2RhdGFzZXRzL3N2bVJhZEZpbmFsVHJhaW5Nb2RlbC5SRGF0YSIpCmBgYAoKYmFjayB0byB0cmFpbiB2cyB0ZXN0IGRhdGEgZm9yIG9uZSBtb3JlIHBlcmZvcm1hbmNlIG1lYXN1cmUgYW5kIGNoYXJ0Li4uIGV2ZW4gdGhvdWdoCmZvciBmdXR1cmUgZGF0YSB3ZSB3aWxsIHVzZSB0aGUgZmluYWwgdHJhaW5lZCBtb2RlbApgYGB7cn0Kc3ZtUmFkVHJhaW5Nb2RlbCA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gdHJhaW5kYXRhLAogICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJzdm1SYWRpYWwiLAogICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gc3ZtUmFkVHVuZUdyaWQzLAogICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCmBgYAoKY2hlY2sgcGVyZm9ybWFuY2UKYGBge3J9CnByZWRpY3RkYXRhIDwtIHByZWRpY3Qoc3ZtUmFkVHJhaW5Nb2RlbCwgdGVzdGRhdGEpCnBvc3RSZXNhbXBsZShwcmVkID0gcHJlZGljdGRhdGEsIG9icyA9IHRlc3RkYXRhJEh1bnRlcnMpCmBgYAoKQ2hhcnQgcGVyZm9ybWFuY2Ugb2YgcHJlZGljdGVkCmBgYHtyIGZpZy53aWR0aD0xMH0KY2hhcnRwZXJmb3JtYW5jZSA8LSBkYXRhLmZyYW1lKHByZWRpY3RlZCA9IHByZWRpY3RkYXRhLCBvYnNlcnZlZCA9IHRlc3RkYXRhJEh1bnRlcnMpCmdncGxvdChjaGFydHBlcmZvcm1hbmNlLCBhZXMocHJlZGljdGVkLG9ic2VydmVkKSkgKwogIGdlb21fcG9pbnQoKSArCiAgbGFicyh0aXRsZT0iUGVyZm9ybWFuY2Ugb2YgTnVtYmVyIG9mIEh1bnRlcnMgUHJlZGljdGlvbiIsIGNhcHRpb249InNvdXJjZTogY3B3LnN0YXRlLmNvLnVzIikKYGBgCgoKa2tubiBwZXJmb3JtZWQgYmV0dGVyIHRoYW4gc3ZtUmFkaWFsIFJNU0U9MTMwIHZzIDE1NApgYGB7cn0KRmluYWxIdW50ZXJzbW9kZWwgPC0ga2tubkZpbmFsVHJhaW5Nb2RlbAojIEZpbmFsSHVudGVyc21vZGVsIDwtIHN2bVJhZEZpbmFsVHJhaW5Nb2RlbApgYGAKYGBge3J9CnNhdmUoRmluYWxIdW50ZXJzbW9kZWwsIGZpbGUgPSAifi9fY29kZS9jb2xvcmFkby1kb3cvZGF0YXNldHMvRmluYWxIdW50ZXJzbW9kZWwuUkRhdGEiKQpgYGAKClVzZSB0aGUgMjAxOCBEcmF3IGRhdGEgdG8gcHJlZGljdCB0aGUgbnVtYmVyIG9mIGh1bnRlcnMgaW4gMjAxOApgYGB7cn0KQ09FbGtEcmF3MjAxOCA8LSBmaWx0ZXIoQ09FbGtEcmF3LCBZZWFyID09IDIwMTgpCkNPRWxrSHVudGVyczIwMTggPC0gQ09FbGtEcmF3MjAxOFssIGNvbG5hbWVzKENPRWxrRHJhdzIwMTgpICVpbiUgYygiVW5pdCIsRmluYWxIdW50ZXJzbW9kZWwkY29lZm5hbWVzKV0KCkNPRWxrSHVudGVyczIwMTggPC0gYXMuZGF0YS5mcmFtZSh1bmlxdWUoQ09FbGtIdW50ZXJzJFVuaXQpKQpjb2xuYW1lcyhDT0Vsa0h1bnRlcnMyMDE4KSA8LSAiVW5pdCIKQ09FbGtIdW50ZXJzMjAxOCRZZWFyIDwtIDIwMTgKQ09FbGtIdW50ZXJzMjAxOCA8LSBsZWZ0X2pvaW4oQ09FbGtIdW50ZXJzMjAxOCxmaWx0ZXIoQ09FbGtEcmF3LFllYXI9PTIwMTgpKQojIFJlcGxhY2UgdGhlIGRyYXcgZGF0YSB0aGF0IGRvbid0IGhhdmUgZW50cmllcyB3aXRoIDAKQ09FbGtIdW50ZXJzMjAxOCREcmF3bltpcy5uYShDT0Vsa0h1bnRlcnMyMDE4JERyYXduKV0gPC0gMApDT0Vsa0h1bnRlcnMyMDE4JFF1b3RhW2lzLm5hKENPRWxrSHVudGVyczIwMTgkUXVvdGEpXSA8LSAwCgpDT0Vsa0h1bnRlcnMyMDE4IDwtIENPRWxrSHVudGVyczIwMThbLCBjb2xuYW1lcyhDT0Vsa0h1bnRlcnMyMDE4KSAlaW4lIGMoIlVuaXQiLEZpbmFsSHVudGVyc21vZGVsJGNvZWZuYW1lcyldCgpDT0Vsa0h1bnRlcnMyMDE4JEh1bnRlcnMgPC0gcm91bmQocHJlZGljdChGaW5hbEh1bnRlcnNtb2RlbCwgQ09FbGtIdW50ZXJzMjAxOCkpCgpDT0Vsa0h1bnRlcnMyMDE4JEh1bnRlcnNbQ09FbGtIdW50ZXJzMjAxOCRIdW50ZXJzPDBdIDwtIDAKYGBgCgpTYXZlIG9mZiBzbyB3ZSBkb24ndCBoYXZlIHRvIHJlY3JlYXRlIHRoZSBtb2RlbCBldmVyeXRpbWUgd2Ugd2FudCB0aGUgcmVzdWx0cwpgYGB7cn0Kc2F2ZShDT0Vsa0h1bnRlcnMyMDE4LGZpbGU9IkNPRWxrSHVudGVyczIwMTguUkRhdGEiKQpgYGAKCioqKgojIyBUb3RhbCBFbGsgSGFydmVzdAojIyMgU3RhdGV3aWRlCkdyb3VwIHNlYXNvbnMKYGBge3J9CkNPRWxrSHVudGVyc1N0YXRld2lkZSA8LSBzdW1tYXJpc2UoZ3JvdXBfYnkoQ09FbGtSaWZsZUFsbCxZZWFyLFVuaXQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSHVudGVycyA9IHN1bShjKEh1bnRlcnMuQW50bGVyZWQsSHVudGVycy5BbnRsZXJsZXNzLEh1bnRlcnMuRWl0aGVyKSxuYS5ybSA9IFQpKQpDT0Vsa0h1bnRlcnMyMDE4YiA8LSBDT0Vsa0h1bnRlcnMyMDE4CiMgQ09FbGtIdW50ZXJzMjAxOGIkWWVhciA8LSBhcy5jaGFyYWN0ZXIoQ09FbGtIdW50ZXJzMjAxOGIkWWVhcikKCiMgSm9pbiAyMDE4IHRvIGhpc3RvcmljIGRhdGEKQ09FbGtIdW50ZXJzQWxsIDwtIHJiaW5kLmZpbGwoQ09FbGtIdW50ZXJzU3RhdGV3aWRlLENPRWxrSHVudGVyczIwMThiKQoKIyBHcm91cCBVbml0cwpDT0Vsa0h1bnRlcnNTdGF0ZXdpZGUgPC0gc3VtbWFyaXNlKGdyb3VwX2J5KENPRWxrSHVudGVyc0FsbCxZZWFyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBIdW50ZXJzID0gc3VtKEh1bnRlcnMpKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0xMH0KZ2dwbG90KENPRWxrSHVudGVyc1N0YXRld2lkZSwgYWVzKFllYXIsSHVudGVycykpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsZmlsbD1nZ3RoZW1lc19kYXRhJGhjJHBhbGV0dGVzJGRlZmF1bHRbMl0pICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMTMwMDAwLDE1NTAwMCkpICsKICBsYWJzKHRpdGxlPSJTdGF0ZXdpZGUgRWxrIEh1bnRlcnMiLCBjYXB0aW9uPSJzb3VyY2U6IGNwdy5zdGF0ZS5jby51cyIpCmBgYAoKCj4gVE9ETyBjb21tZW50YXJ5CgoqKioKCiMjIyBIdW50ZXJzIGJ5IFVuaXQKSSdkIGxpa2UgdG8ga25vdyB3aGVyZSB0aGUgaHVudGVycyBhcmUgZGlzdHJpYnV0ZWQgYWNyb3NzIHRoZSBzdGF0ZS4KCk5leHQgeWVhcidzIGRhdGEKYGBge3J9ClllYXIyMDE4IDwtIGZpbHRlcihDT0Vsa0h1bnRlcnNBbGwsIFllYXIgPT0gIjIwMTgiKQpIdW50ZXJzdG9QbG90IDwtIGxlZnRfam9pbihVbml0Ym91bmRhcmllczIsWWVhcjIwMTgsIGJ5PWMoIlVuaXQiKSkKYGBgCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD04LjQ2fQpnZ3Bsb3QoSHVudGVyc3RvUGxvdCwgYWVzKGxvbmcsIGxhdCwgZ3JvdXAgPSBncm91cCkpICsgCiAgZ2VvbV9wb2x5Z29uKGFlcyhmaWxsID0gSHVudGVycyksY29sb3VyID0gImdyZXk1MCIsIHNpemUgPSAuMikgKyAjVW5pdCBib3VuZGFyaWVzCiAgZ2VvbV9wYXRoKGRhdGEgPSBDT3JvYWRzLGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIGNvbG9yPSIjMzg3OEM3IixzaXplPTIpICsgI1JvYWRzCiAgZ2VvbV90ZXh0KGRhdGE9ZGF0YV9jZW50cm9pZHMsYWVzKHg9bG9uZ2l0dWRlLHk9bGF0aXR1ZGUsbGFiZWwgPSBVbml0KSxzaXplPTMpICsgI1VuaXQgbGFiZWxzCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJPcmFuZ2VzIixkaXJlY3Rpb24gPSAxLG5hLnZhbHVlID0gJ2xpZ2h0IGdyZXknKSArCiAgeGxhYigiIikgKyB5bGFiKCIiKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGVkIDIwMTggQ29sb3JhZG8gRWxrIEh1bnRlcnMiLCBjYXB0aW9uPSJzb3VyY2U6IGNwdy5zdGF0ZS5jby51cyIpCmBgYAoKCj4gVE9ETyAtIGNvbW1lbnRhcnkKCioqKgoKIyMjIFllYXIgdG8gWWVhciBIdW50ZXIgVHJlbmRzCkNyZWF0ZSBhIHBuZyBvZiBlYWNoIHllYXIKYGBge3J9Cmljb3VudGVyIDwtIDAKZm9yIChpbWFwIGluIHVuaXF1ZShDT0Vsa0h1bnRlcnNBbGwkWWVhcikpewogICMgQ29sb3JhZG8gYXNwZWN0IHJhdGlvID0gMTA4N3cgeCA4MDBoIC0+IDEuMzU4NzUKICAjIFVzZSB0cmlhbCBhbmQgZXJyb3IgdG8gZGV0ZXJtaW5lIHdoaWNoIHdpZHRoIGFuZCBoZWlnaHQgdG8gZGVmaW5lIGZvciBwbmcgZmlsZXMgdGhhdCB3aWxsIHJldGFpbiB0aGUgY29ycmVjdCBhc3BlY3QgcmF0aW8KICBwbmcoZmlsZT1wYXN0ZSgiSHVudGVyc01hcCIsaW1hcCwiLnBuZyIpLCB3aWR0aD05NDgsIGhlaWdodD03MDApCiAgeWVhcnBsb3QgPC0gZmlsdGVyKENPRWxrSHVudGVyc0FsbCwgWWVhciA9PSBpbWFwKQogIEh1bnRlcnN0b1Bsb3QgPC0gbGVmdF9qb2luKFVuaXRib3VuZGFyaWVzMix5ZWFycGxvdCwgYnk9YygiVW5pdCIpKQogIHAxIDwtIGdncGxvdChIdW50ZXJzdG9QbG90LCBhZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwKSkgKyAKICAgIGdlb21fcG9seWdvbihhZXMoZmlsbCA9IEh1bnRlcnMpLGNvbG91ciA9ICJncmV5NTAiLCBzaXplID0gLjIpICsgI1VuaXQgYm91bmRhcmllcwogICAgZ2VvbV9wYXRoKGRhdGEgPSBDT3JvYWRzLGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIGNvbG9yPSIjMzg3OEM3IixzaXplPTIpICsgI1JvYWRzCiAgICBnZW9tX3RleHQoZGF0YT1kYXRhX2NlbnRyb2lkcyxhZXMoeD1sb25naXR1ZGUseT1sYXRpdHVkZSxsYWJlbCA9IFVuaXQpLHNpemU9NSkgKyAjVW5pdCBsYWJlbHMKICAgIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGUgPSAiT3JhbmdlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgbmEudmFsdWUgPSAnbGlnaHQgZ3JleScsCiAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsbWF4KENPRWxrSHVudGVyc0FsbCRIdW50ZXJzKSkpICsgI2ZpeCBzbyBlYWNoIHllYXIgY2hhcnQgaGFzIHNhbWUgY29sb3IgYnJlYWtzCiAgICB4bGFiKCIiKSArIHlsYWIoIiIpICsKICAgIHRoZW1lKHBsb3QudGl0bGU9ZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUpKSArCiAgICB0aGVtZShwbG90LnN1YnRpdGxlPWVsZW1lbnRfdGV4dChoanVzdCA9IGljb3VudGVyL2xlbmd0aCh1bmlxdWUoQ09FbGtIdW50ZXJzQWxsJFllYXIpKSkpICsKICAgIGxhYnModGl0bGU9IkNvbG9yYWRvIEVsayBIdW50ZXJzIiwgc3VidGl0bGU9aW1hcCwgY2FwdGlvbj0ic291cmNlOiBjcHcuc3RhdGUuY28udXMiKQogIHBsb3QocDEpCiAgZGV2Lm9mZigpCiAgaWNvdW50ZXIgPC0gaWNvdW50ZXIgKyAxCn0KYGBgCkNvbnZlcnQgdGhlIC5wbmcgZmlsZXMgdG8gb25lIC5naWYgZmlsZSB1c2luZyBJbWFnZU1hZ2ljay4gCmBgYHtyfQpzeXN0ZW0oImNvbnZlcnQgLWRlbGF5IDE1MCAqLnBuZyBIdW50ZXJzbWFwUHJlZC5naWYiKQpgYGAKCiFbXShIdW50ZXJzbWFwUHJlZC5naWYpCj4gVE9ETyAtIGNvbW1lbnRhcnkKClJlbW92ZSB0aGUgLnBuZyBmaWxlcwpgYGB7cn0KZmlsZS5yZW1vdmUobGlzdC5maWxlcyhwYXR0ZXJuPSIucG5nIikpCmBgYAoKKioqCiMjIyBOdW1iZXIgb2YgSHVudGVycyBSYW5rIG9mIHRoZSBVbml0cwpXb3VsZCBhbHNvIGJlIGJlbmVmaWNpYWwgdG8gcmFuayBlYWNoIHVuaXQgc28gSSBjYW4gcmVmZXJlbmNlIGxhdGVyLiBJbiB0aGlzIGNhc2UKYXZlcmFnZSB0aGUgbnVtYmVyIG9mIGh1bnRlcnMgb2YgdGhlIGxhc3QgZmV3IHllYXJzCmBgYHtyfQpIdW50ZXJSYW5rMjAxOCA8LSBmaWx0ZXIoQ09FbGtIdW50ZXJzQWxsLCBhcy5udW1lcmljKFllYXIpID09IDIwMTgpCkh1bnRlclJhbmsyMDE4IDwtIHN1bW1hcmlzZShncm91cF9ieShIdW50ZXJSYW5rMjAxOCxVbml0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBIdW50ZXJzID0gbWVhbihIdW50ZXJzLG5hLnJtID0gVCkpCkh1bnRlclJhbmsyMDE4JEh1bnRlcnNSYW5rID0gcmFuaygtSHVudGVyUmFuazIwMTgkSHVudGVycykKCkh1bnRlclJhbmsyMDE4IDwtIGZpbHRlcihIdW50ZXJSYW5rMjAxOCwgSHVudGVyc1JhbmsgPD0gNTApICMgdG9wIDUwIHVuaXRzCiMgSW4gb3JkZXIgZm9yIHRoZSBjaGFydCB0byByZXRhaW4gdGhlIG9yZGVyIG9mIHRoZSByb3dzLCB0aGUgWCBheGlzIHZhcmlhYmxlIChpLmUuIHRoZSBjYXRlZ29yaWVzKSBoYXMgdG8gYmUgY29udmVydGVkIGludG8gYSBmYWN0b3IuCkh1bnRlclJhbmsyMDE4IDwtIEh1bnRlclJhbmsyMDE4W29yZGVyKC1IdW50ZXJSYW5rMjAxOCRIdW50ZXJzKSwgXSAgIyBzb3J0Ckh1bnRlclJhbmsyMDE4JFVuaXQgPC0gZmFjdG9yKEh1bnRlclJhbmsyMDE4JFVuaXQsIGxldmVscyA9IEh1bnRlclJhbmsyMDE4JFVuaXQpICAjIHRvIHJldGFpbiB0aGUgb3JkZXIgaW4gcGxvdC4KYGBgCgpMb2xsaXBvcCBDaGFydApgYGB7cn0KZ2dwbG90KEh1bnRlclJhbmsyMDE4LCBhZXMoeD1Vbml0LCB5PUh1bnRlcnMpKSArIAogIGdlb21fcG9pbnQoc2l6ZT0zKSArIAogIGdlb21fc2VnbWVudChhZXMoeD1Vbml0LCAKICAgICAgICAgICAgICAgICAgIHhlbmQ9VW5pdCwgCiAgICAgICAgICAgICAgICAgICB5PTAsIAogICAgICAgICAgICAgICAgICAgeWVuZD1IdW50ZXJzKSkgKyAKICBsYWJzKHRpdGxlPSJQcmVkaWN0ZWQgRWxrIEh1bnRlcnMgMjAxOFxuVG9wIDUwIFVuaXRzIiwgc3VidGl0bGU9Ikh1bnRlcnMgYnkgVW5pdCIsIGNhcHRpb249InNvdXJjZTogY3B3LnN0YXRlLmNvLnVzIikKYGBgCgo+IFRPRE8gLSBjb21tZW50YXJ5CgoqKioKIyMgQ29uY2x1c2lvbgo+IFRPRE8KCgo=