Description

Use historical harvests, number of hunters, and weather data to predict the harvest for the upcoming hunting seasons.

NOTICE that I am only looking at the general rifle hunting seasons on public land. There are also hunters for Archery, Muzzleloader, Private Land, Ranching for Wildlife, etc.


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)
toOrdinal 1.0-0.0  (7-18-2017). For help: >help("toOrdinal") or visit https://centerforassessment.github.io/toOrdinal
The working directory was changed to /Users/psarnow/_code/colorado-dow/datasets inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.

Table of the harvest data

head(COElkRifleAll)
  Unit Harvest.Antlered Hunters.Antlered Success.Antlered Season HuntCode
1    1                0                0               NA      1 EM001O1R
2    2                0                0               NA      1 EM002O1R
3  201                0                0               NA      1 EM201O1R
4    3                0                0               NA      1 EM003O1R
5  301                0                0               NA      1 EM301O1R
6    4                0                0               NA      1 EM004O1R
  Harvest.Antlerless Hunters.Antlerless Success.Antlerless Hunters.Either
1                 NA                 NA                 NA             NA
2                 NA                 NA                 NA             NA
3                 NA                 NA                 NA             NA
4                 NA                 NA                 NA             NA
5                 NA                 NA                 NA             NA
6                 NA                 NA                 NA             NA
  Success.Either Year
1             NA 2006
2             NA 2006
3             NA 2006
4             NA 2006
5             NA 2006
6             NA 2006

Load the weather data

load("weatherdata5.RData")
head(weatherdata5)

source geodata

source('~/_code/colorado-dow/datasets/Colorado GMUnit and Road data.R', echo=F)
rgdal: version: 1.2-20, (SVN revision 725)
 Geospatial Data Abstraction Library extensions to R successfully loaded
 Loaded GDAL runtime: GDAL 2.1.3, released 2017/20/01
 Path to GDAL shared files: /Library/Frameworks/R.framework/Versions/3.5/Resources/library/rgdal/gdal
 GDAL binary built with GEOS: FALSE 
 Loaded PROJ.4 runtime: Rel. 4.9.3, 15 August 2016, [PJ_VERSION: 493]
 Path to PROJ.4 shared files: /Library/Frameworks/R.framework/Versions/3.5/Resources/library/rgdal/proj
 Linking to sp version: 1.2-7 
rgeos version: 0.3-26, (SVN revision 560)
 GEOS runtime version: 3.6.1-CAPI-1.10.1 r0 
 Linking to sp version: 1.2-7 
 Polygon checking: TRUE 
OGR data source with driver: ESRI Shapefile 
Source: "/Users/psarnow/_code/colorado-dow/datasets/CPW_GMUBoundaries/BigGameGMUBoundaries03172015.shp", layer: "BigGameGMUBoundaries03172015"
with 185 features
It has 12 fields
Integer64 fields read as strings:  GMUID 
OGR data source with driver: ESRI Shapefile 
Source: "/Users/psarnow/_code/colorado-dow/datasets/ne_10m_roads/ne_10m_roads.shp", layer: "ne_10m_roads"
with 56601 features
It has 29 fields
Integer64 fields read as strings:  scalerank question 

Take a peak at the boundary data

head(Unitboundaries2)
  id      long      lat order  hole piece group COUNTY DEERDAU ELKDAU ANTDAU MOOSEDAU
1  1 -109.0486 40.83476     1 FALSE     1   1.1 MOFFAT     D-1   E-47   A-11     M-99
2  1 -109.0472 40.83357     2 FALSE     1   1.1 MOFFAT     D-1   E-47   A-11     M-99
3  1 -109.0460 40.83295     3 FALSE     1   1.1 MOFFAT     D-1   E-47   A-11     M-99
4  1 -109.0449 40.83228     4 FALSE     1   1.1 MOFFAT     D-1   E-47   A-11     M-99
5  1 -109.0438 40.83204     5 FALSE     1   1.1 MOFFAT     D-1   E-47   A-11     M-99
6  1 -109.0423 40.83181     6 FALSE     1   1.1 MOFFAT     D-1   E-47   A-11     M-99
  BEARDAU LIONDAU SQ_MILES    ACRES SHAPE_area SHAPE_len Unit
1    B-15     L-1 127.2227 81422.53  329506619  100751.7    1
2    B-15     L-1 127.2227 81422.53  329506619  100751.7    1
3    B-15     L-1 127.2227 81422.53  329506619  100751.7    1
4    B-15     L-1 127.2227 81422.53  329506619  100751.7    1
5    B-15     L-1 127.2227 81422.53  329506619  100751.7    1
6    B-15     L-1 127.2227 81422.53  329506619  100751.7    1

Set to predictive analytics directory

setwd("~/_code/colorado-dow/phase III - predictive analytics")

Organize data

Group weather data by year, and season

UnitWeather_Season <- summarise(group_by(weatherdata5,Year,Unit,Season),
                         daily.temperatureHigh = max(daily.temperatureHigh,na.rm = T),
                         daily.temperatureLow = min(daily.temperatureLow,na.rm = T),
                         daily.temperatureMean = mean(daily.temperatureMean,na.rm = T),
                         daily.precipAccumulation = mean(daily.precipAccumulation,na.rm = T),
                         daily.precipType = mean(daily.precipType,na.rm = T),
                         daily.windSpeed = mean(daily.windSpeed,na.rm = T),
                         daily.FullmoonPhase = mean(daily.FullmoonPhase,na.rm = T))

Appropriate field classes for model training

UnitWeather_Season$Year <- as.numeric(UnitWeather_Season$Year)

Group Hunter data by Year, Season, and Unit

COElkHarvest_Season <- summarise(group_by(COElkRifleAll,Year,Unit,Season),
                          Harvest = sum(c(Harvest.Antlered,Harvest.Antlerless),na.rm = T),
                          Hunters = sum(c(Hunters.Antlered,Hunters.Antlerless,Hunters.Either),na.rm = T))
COElkHarvest_Season$Year <- as.numeric(COElkHarvest_Season$Year)

Join Harvest, Hunter, and Weather data

COElkHarvest_Season <- full_join(COElkHarvest_Season, UnitWeather_Season, by = c("Year","Unit","Season"))

Remove rows with missing data

COElkHarvest_Season <- filter(COElkHarvest_Season, !is.na(Harvest) & !is.na(daily.temperatureMean) & Year != 2018)

Split into train and test sets. Will use 75% of the data to train on.

COElkHarvest_Season <- mutate(group_by(COElkHarvest_Season,Unit),
                       numentries = n())
COElkHarvest_Season <- filter(COElkHarvest_Season, numentries >= 3)
COElkHarvest_Season$UnitYearSeason <- paste(COElkHarvest_Season$Unit, COElkHarvest_Season$Year,COElkHarvest_Season$Season)
traindata2 <- COElkHarvest_Season %>% group_by(Unit) %>% sample_frac(size = .75, replace = F)
testdata2 <- COElkHarvest_Season[!COElkHarvest_Season$UnitYearSeason %in% traindata2$UnitYearSeason,]
COElkHarvest_Season <- select(COElkHarvest_Season, -UnitYearSeason, -numentries)
traindata2 <- select(traindata2, -UnitYearSeason, -numentries)
testdata2 <- select(testdata2, -UnitYearSeason, -numentries)

Save off for importing into AzureML

write.csv(COElkHarvest_Season,file = "~/_code/colorado-dow/datasets/COElkHarvest_Season.csv",row.names = F)

Model Building

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")
step1_Season_all <- NULL
for (imethod in quickmethods) {
  step1_Season <- NULL
  start <- now()
  
  if (imethod == "lm" | imethod == "svmLinear") {
    controlmethod <- "repeatedcv"
  } else {controlmethod <- "adaptive_cv"}
  
  fitControl <- trainControl(
    method = controlmethod,
    # search = 'random',
    number = 4,
    repeats = 4,
    allowParallel = TRUE,
    summaryFunction = defaultSummary)
  
  registerDoSEQ()
  registerDoMC(cores = 6)
  
  HarvestModel_1_Season = train(Harvest ~ ., data = traindata2,
                         method = imethod,
                         #preProc = c("center","scale"), 
                         tuneLength = 15,
                         trControl = fitControl)
  
  HarvestModel_1_Season
  
  # measure performance
  predictdata <- predict(HarvestModel_1_Season, testdata2)
  
  step1_Season$method <- imethod
  step1_Season$RMSE <- postResample(pred = predictdata, obs = testdata2$Harvest)[1]
  step1_Season$duration <- now() - start
  step1_Season <- as.data.frame(step1_Season)
  step1_Season_all <- rbind(step1_Season_all,step1_Season)
}
prediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleadingprediction from a rank-deficient fit may be misleading
Attaching package: ‘kernlab’

The following object is masked from ‘package:scales’:

    alpha

The following object is masked from ‘package:ggplot2’:

    alpha

row names were found from a short variable and have been discardedrow names were found from a short variable and have been discardedrow names were found from a short variable and have been discardedrow names were found from a short variable and have been discardedrow names were found from a short variable and have been discardedrow names were found from a short variable and have been discardedrow names were found from a short variable and have been discardedrow names were found from a short variable and have been discarded
Attaching package: ‘kknn’

The following object is masked from ‘package:caret’:

    contr.dummy

View Results, and compare to previous first models that did not expand to Seasons

step1_all
         method      RMSE       duration
RMSE         lm  52.23203  1.742155 secs
RMSE1 svmLinear  56.67347  1.994666 secs
RMSE2 svmRadial  55.10650  7.456390 secs
RMSE3       knn 105.23611  4.174521 secs
RMSE4    cubist  60.09588  9.766908 secs
RMSE5      kknn  59.56523 15.774304 secs
step1_Season_all
         method     RMSE       duration
RMSE         lm 26.60899  4.378424 secs
RMSE1 svmLinear 27.43676  8.249770 secs
RMSE2 svmRadial 25.41266 35.865861 secs
RMSE3       knn 35.31770 10.047640 secs
RMSE4    cubist 22.66765 30.146231 secs
RMSE5      kknn 26.01812 28.380214 secs

The performance RMSE metric is certainly improved when including the Seasonal grouping. Lets take the best method, and see if it is visually reasonable while also charting without Seasons.

Predictions using best Season model

Run the best model with all of the data

fitControl <- trainControl(
    method = "adaptive_cv",
    # search = 'random',
    number = 4,
    repeats = 4,
    allowParallel = TRUE,
    summaryFunction = defaultSummary)
  
registerDoSEQ()
registerDoMC(cores = 6)
  
HarvestModel_1_Season = train(Harvest ~ ., data = COElkHarvest_Season,
                         method = 'cubist',
                         #preProc = c("center","scale"), 
                         tuneLength = 15,
                         trControl = fitControl)
row names were found from a short variable and have been discardedrow names were found from a short variable and have been discardedrow names were found from a short variable and have been discarded
  
HarvestModel_1_Season
Cubist 

2775 samples
  11 predictor

No pre-processing
Resampling: Adaptively Cross-Validated (4 fold, repeated 4 times) 
Summary of sample sizes: 2081, 2080, 2082, 2082, 2080, 2082, ... 
Resampling results across tuning parameters:

  committees  neighbors  RMSE      Rsquared   MAE       Resamples
   1          0          30.01973  0.7616367  18.36972   5       
   1          5          26.40694  0.8138750  16.06972   5       
   1          9          26.49602  0.8129865  16.00035   5       
  10          0          27.61127  0.8028791  16.89908   5       
  10          5          25.05156  0.8361794  15.33611   7       
  10          9          24.70932  0.8396775  15.18101  13       
  20          0          27.27628  0.8084462  16.74196   5       
  20          5          24.97369  0.8369100  15.27278   8       
  20          9          24.87747  0.8386215  15.26527  22       

RMSE was used to select the optimal model using the smallest value.
The final values used for the model were committees = 20 and neighbors = 9.

Predict Hunter for next year, 2018

COElkHunters_Season2018Predicted <- summarise(group_by(COElkHunters2018_Season, Year, Unit,Season),
                                       Hunters = sum(Hunters,na.rm = T))
# Get list of Units and Seasons that will have data
COElkHarvest2018_Season <- COElkHarvest_Season
COElkHarvest2018_Season$Unit_Season <- paste(COElkHarvest2018_Season$Unit,COElkHarvest2018_Season$Season)
COElkHarvest2018_Season <- as.data.frame(unique(COElkHarvest2018_Season$Unit_Season))
colnames(COElkHarvest2018_Season) <- "Unit_Season"
# Fill in missing Units and Seasons per unique Unit_Seasons
COElkHarvest2018_Season$Unit <- str_extract(COElkHarvest2018_Season$Unit_Season,"[:alnum:]+(?=[:blank:])")
COElkHarvest2018_Season$Season <- str_extract(COElkHarvest2018_Season$Unit_Season,"(?<=[:blank:])[:alnum:]+")
COElkHarvest2018_Season <- select(COElkHarvest2018_Season, -Unit_Season)
COElkHarvest2018_Season$Year <- 2018
# Weather data for 2018
UnitWeather_Season2018 <- filter(UnitWeather_Season,Year==2018)
# A left join will autofill missing draw data with NAs, but will retain the full list of Unit Seasons
COElkHarvest2018_Season <- left_join(COElkHarvest2018_Season,UnitWeather_Season2018)
Joining, by = c("Unit", "Season", "Year")
# Join in forecasted Hunters
COElkHarvest2018_Season <- left_join(COElkHarvest2018_Season,COElkHunters_Season2018Predicted)
Joining, by = c("Unit", "Season", "Year")
# Only use the fields that were included in the model
COElkHarvest2018_Season <- COElkHarvest2018_Season[, colnames(COElkHarvest2018_Season) %in% c("Unit","Season",HarvestModel_1_Season$coefnames)]
# Use trained model to predict Harvest
COElkHarvest2018_Season$Harvest <- round(predict(HarvestModel_1_Season, COElkHarvest2018_Season))
COElkHarvest2018_Season$Harvest[COElkHarvest2018_Season$Harvest<0] <- 0

Save off so we don’t have to recreate the model everytime we want the results

Label and Join models together for comparisons

# Group Units
HarvestcompareStatewide <- summarise(group_by(Harvestcompare,Year,modeldata),
                                   Harvest = sum(Harvest))
ggplot(HarvestcompareStatewide, aes(Year,Harvest,group=modeldata,fill=modeldata)) +
  geom_bar(position="dodge",stat="identity") +
  # coord_cartesian(ylim = c(130000,155000)) +
  scale_fill_hc() +
  labs(title="Statewide Elk Harvest", caption="source: cpw.state.co.us")

Harvest Statewide by Season and Year

Harvest_Season <- rbind.fill(COElkHarvest_Season,COElkHarvest2018_Season)
ggplot(Harvest_Season, aes(Year,Harvest,group=Season,fill=Season)) +
  geom_bar(position="dodge",stat="identity") +
  # coord_cartesian(ylim = c(130000,155000)) +
  scale_fill_hc() +
  labs(title="Statewide Elk Harvest", caption="source: cpw.state.co.us")

Harvest in Unit 77

Harvest_Season_77 <- filter(Harvest_Season, Unit == "77")
ggplot(Harvest_Season_77, aes(Year,Harvest,group=Season,fill=Season)) +
  geom_bar(position="dodge",stat="identity") +
  # coord_cartesian(ylim = c(130000,155000)) +
  scale_fill_hc() +
  labs(title="Unit 77 Elk Harvest", caption="source: cpw.state.co.us")

LS0tCnRpdGxlOiAiUHJlZGljdCBGdXR1cmUgRWxrIEhhcnZlc3QgYnkgU2Vhc29uIgphdXRob3I6ICJQaWVycmUgU2Fybm93IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IGZhbHNlCiAgICB0b2NfZGVwdGg6IDYKICAgIHRoZW1lOiB5ZXRpCiAgICBoaWdodGxpZ2h0OiBkZWZhdWx0CiAgICBjb2RlX2ZvbGRpbmc6IG5vbmUKLS0tCioqKgojIyBEZXNjcmlwdGlvbgpVc2UgaGlzdG9yaWNhbCBoYXJ2ZXN0cywgbnVtYmVyIG9mIGh1bnRlcnMsIGFuZCB3ZWF0aGVyIGRhdGEgdG8gcHJlZGljdCB0aGUgaGFydmVzdCBmb3IgdGhlIHVwY29taW5nIGh1bnRpbmcgc2Vhc29ucy4KCipfX05PVElDRV9fIHRoYXQgSSBhbSBvbmx5IGxvb2tpbmcgYXQgdGhlIGdlbmVyYWwgcmlmbGUgaHVudGluZyBzZWFzb25zIG9uIHB1YmxpYyBsYW5kLiBUaGVyZSBhcmUgYWxzbyAKaHVudGVycyBmb3IgQXJjaGVyeSwgTXV6emxlbG9hZGVyLCBQcml2YXRlIExhbmQsIFJhbmNoaW5nIGZvciBXaWxkbGlmZSwgZXRjLioKCioqKgojIyBTZXR1cApMb2FkIHJlcXVpcmVkIGxpYnJhcmllcyBmb3Igd3JhbmdsaW5nIGRhdGEsIGNoYXJ0aW5nLCBhbmQgbWFwcGluZwpgYGB7cn0KbGlicmFyeShwbHlyLHF1aWV0bHkgPSBUKSAjIGRhdGEgd3JhbmdsaW5nCmxpYnJhcnkoZHBseXIscXVpZXRseSA9IFQpICMgZGF0YSB3cmFuZ2xpbmcKbGlicmFyeShnZ3Bsb3QyLCBxdWlldGx5ID0gVCkgIyBjaGFydGluZwpsaWJyYXJ5KGdndGhlbWVzLHF1aWV0bHkgPSBUKSAjIHNvIEkgY2FuIGFkZCB0aGUgaGlnaGNoYXJ0cyB0aGVtZSBhbmQgcGFsZXR0ZQpsaWJyYXJ5KHNjYWxlcyxxdWlldGx5ID0gVCkgIyB0byBsb2FkIHRoZSBwZXJjZW50IGZ1bmN0aW9uIHdoZW4gbGFiZWxpbmcgcGxvdHMKbGlicmFyeShjYXJldCxxdWlldGx5ID0gVCkgIyBjbGFzc2lmaWNhdGlvbiBhbmQgcmVncmVzc2lvbiB0cmFpbmluZwpsaWJyYXJ5KGZvcmVhY2gscXVpZXRseSA9IFQpICMgcGFyYWxsZWwgcHJvY2Vzc2luZyB0byBzcGVlZCB1cCB0aGUgbW9kZWwgdHJhaW5pbmcKbGlicmFyeShkb01DLHF1aWV0bHkgPSBUKSAjIHBhcmFsbGVsIHByb2Nlc3NpbmcgdG8gc3BlZWQgdXAgdGhlIG1vZGVsIHRyYWluaW5nCmxpYnJhcnkobHVicmlkYXRlLHF1aWV0bHkgPSBUKSAjIGZvciB0aW1pbmcgbW9kZWxzCmBgYAoKU2V0IG91ciBwcmVmZXJyZWQgY2hhcnRpbmcgdGhlbWUKYGBge3J9CnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkrdGhlbWVfaGMoKSt0aGVtZShsZWdlbmQua2V5LndpZHRoID0gdW5pdCgxLjUsICJjbSIpKSkKYGBgIAoKUnVuIHNjcmlwdCB0byBnZXQgaHVudGVyIGRhdGEKYGBge3J9CnNvdXJjZSgnfi9fY29kZS9jb2xvcmFkby1kb3cvZGF0YXNldHMvQ29sb3JhZG8gRWxrIEhhcnZlc3QgRGF0YS5SJywgZWNobz1GKQpgYGAKClRhYmxlIG9mIHRoZSBoYXJ2ZXN0IGRhdGEKYGBge3J9CmhlYWQoQ09FbGtSaWZsZUFsbCkKYGBgCgoKTG9hZCB0aGUgd2VhdGhlciBkYXRhCmBgYHtyfQpsb2FkKCJ3ZWF0aGVyZGF0YTUuUkRhdGEiKQpoZWFkKHdlYXRoZXJkYXRhNSkKYGBgCgpzb3VyY2UgZ2VvZGF0YQpgYGB7cn0Kc291cmNlKCd+L19jb2RlL2NvbG9yYWRvLWRvdy9kYXRhc2V0cy9Db2xvcmFkbyBHTVVuaXQgYW5kIFJvYWQgZGF0YS5SJywgZWNobz1GKQpgYGAKClRha2UgYSBwZWFrIGF0IHRoZSBib3VuZGFyeSBkYXRhCmBgYHtyfQpoZWFkKFVuaXRib3VuZGFyaWVzMikKYGBgCgpTZXQgdG8gcHJlZGljdGl2ZSBhbmFseXRpY3MgZGlyZWN0b3J5CmBgYHtyfQpzZXR3ZCgifi9fY29kZS9jb2xvcmFkby1kb3cvcGhhc2UgSUlJIC0gcHJlZGljdGl2ZSBhbmFseXRpY3MiKQpgYGAKKioqCiMjIyBPcmdhbml6ZSBkYXRhCgpHcm91cCB3ZWF0aGVyIGRhdGEgYnkgeWVhciwgYW5kIHNlYXNvbgpgYGB7cn0KVW5pdFdlYXRoZXJfU2Vhc29uIDwtIHN1bW1hcmlzZShncm91cF9ieSh3ZWF0aGVyZGF0YTUsWWVhcixVbml0LFNlYXNvbiksCiAgICAgICAgICAgICAgICAgICAgICAgICBkYWlseS50ZW1wZXJhdHVyZUhpZ2ggPSBtYXgoZGFpbHkudGVtcGVyYXR1cmVIaWdoLG5hLnJtID0gVCksCiAgICAgICAgICAgICAgICAgICAgICAgICBkYWlseS50ZW1wZXJhdHVyZUxvdyA9IG1pbihkYWlseS50ZW1wZXJhdHVyZUxvdyxuYS5ybSA9IFQpLAogICAgICAgICAgICAgICAgICAgICAgICAgZGFpbHkudGVtcGVyYXR1cmVNZWFuID0gbWVhbihkYWlseS50ZW1wZXJhdHVyZU1lYW4sbmEucm0gPSBUKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGRhaWx5LnByZWNpcEFjY3VtdWxhdGlvbiA9IG1lYW4oZGFpbHkucHJlY2lwQWNjdW11bGF0aW9uLG5hLnJtID0gVCksCiAgICAgICAgICAgICAgICAgICAgICAgICBkYWlseS5wcmVjaXBUeXBlID0gbWVhbihkYWlseS5wcmVjaXBUeXBlLG5hLnJtID0gVCksCiAgICAgICAgICAgICAgICAgICAgICAgICBkYWlseS53aW5kU3BlZWQgPSBtZWFuKGRhaWx5LndpbmRTcGVlZCxuYS5ybSA9IFQpLAogICAgICAgICAgICAgICAgICAgICAgICAgZGFpbHkuRnVsbG1vb25QaGFzZSA9IG1lYW4oZGFpbHkuRnVsbG1vb25QaGFzZSxuYS5ybSA9IFQpKQpgYGAKCkFwcHJvcHJpYXRlIGZpZWxkIGNsYXNzZXMgZm9yIG1vZGVsIHRyYWluaW5nCmBgYHtyfQpVbml0V2VhdGhlcl9TZWFzb24kWWVhciA8LSBhcy5udW1lcmljKFVuaXRXZWF0aGVyX1NlYXNvbiRZZWFyKQpgYGAKCkdyb3VwIEh1bnRlciBkYXRhIGJ5IFllYXIsIFNlYXNvbiwgYW5kIFVuaXQKYGBge3J9CkNPRWxrSGFydmVzdF9TZWFzb24gPC0gc3VtbWFyaXNlKGdyb3VwX2J5KENPRWxrUmlmbGVBbGwsWWVhcixVbml0LFNlYXNvbiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgSGFydmVzdCA9IHN1bShjKEhhcnZlc3QuQW50bGVyZWQsSGFydmVzdC5BbnRsZXJsZXNzKSxuYS5ybSA9IFQpLAogICAgICAgICAgICAgICAgICAgICAgICAgIEh1bnRlcnMgPSBzdW0oYyhIdW50ZXJzLkFudGxlcmVkLEh1bnRlcnMuQW50bGVybGVzcyxIdW50ZXJzLkVpdGhlciksbmEucm0gPSBUKSkKCkNPRWxrSGFydmVzdF9TZWFzb24kWWVhciA8LSBhcy5udW1lcmljKENPRWxrSGFydmVzdF9TZWFzb24kWWVhcikKYGBgCgpKb2luIEhhcnZlc3QsIEh1bnRlciwgYW5kIFdlYXRoZXIgZGF0YQpgYGB7cn0KQ09FbGtIYXJ2ZXN0X1NlYXNvbiA8LSBmdWxsX2pvaW4oQ09FbGtIYXJ2ZXN0X1NlYXNvbiwgVW5pdFdlYXRoZXJfU2Vhc29uLCBieSA9IGMoIlllYXIiLCJVbml0IiwiU2Vhc29uIikpCmBgYAoKUmVtb3ZlIHJvd3Mgd2l0aCBtaXNzaW5nIGRhdGEKYGBge3J9CkNPRWxrSGFydmVzdF9TZWFzb24gPC0gZmlsdGVyKENPRWxrSGFydmVzdF9TZWFzb24sICFpcy5uYShIYXJ2ZXN0KSAmICFpcy5uYShkYWlseS50ZW1wZXJhdHVyZU1lYW4pICYgWWVhciAhPSAyMDE4KQpgYGAKClNwbGl0IGludG8gdHJhaW4gYW5kIHRlc3Qgc2V0cy4gV2lsbCB1c2UgNzUlIG9mIHRoZSBkYXRhIHRvIHRyYWluIG9uLiAKCmBgYHtyfQpDT0Vsa0hhcnZlc3RfU2Vhc29uIDwtIG11dGF0ZShncm91cF9ieShDT0Vsa0hhcnZlc3RfU2Vhc29uLFVuaXQpLAogICAgICAgICAgICAgICAgICAgICAgIG51bWVudHJpZXMgPSBuKCkpCkNPRWxrSGFydmVzdF9TZWFzb24gPC0gZmlsdGVyKENPRWxrSGFydmVzdF9TZWFzb24sIG51bWVudHJpZXMgPj0gMykKQ09FbGtIYXJ2ZXN0X1NlYXNvbiRVbml0WWVhclNlYXNvbiA8LSBwYXN0ZShDT0Vsa0hhcnZlc3RfU2Vhc29uJFVuaXQsIENPRWxrSGFydmVzdF9TZWFzb24kWWVhcixDT0Vsa0hhcnZlc3RfU2Vhc29uJFNlYXNvbikKCnRyYWluZGF0YTIgPC0gQ09FbGtIYXJ2ZXN0X1NlYXNvbiAlPiUgZ3JvdXBfYnkoVW5pdCkgJT4lIHNhbXBsZV9mcmFjKHNpemUgPSAuNzUsIHJlcGxhY2UgPSBGKQp0ZXN0ZGF0YTIgPC0gQ09FbGtIYXJ2ZXN0X1NlYXNvblshQ09FbGtIYXJ2ZXN0X1NlYXNvbiRVbml0WWVhclNlYXNvbiAlaW4lIHRyYWluZGF0YTIkVW5pdFllYXJTZWFzb24sXQoKQ09FbGtIYXJ2ZXN0X1NlYXNvbiA8LSBzZWxlY3QoQ09FbGtIYXJ2ZXN0X1NlYXNvbiwgLVVuaXRZZWFyU2Vhc29uLCAtbnVtZW50cmllcykKCnRyYWluZGF0YTIgPC0gc2VsZWN0KHRyYWluZGF0YTIsIC1Vbml0WWVhclNlYXNvbiwgLW51bWVudHJpZXMpCnRlc3RkYXRhMiA8LSBzZWxlY3QodGVzdGRhdGEyLCAtVW5pdFllYXJTZWFzb24sIC1udW1lbnRyaWVzKQpgYGAKClNhdmUgb2ZmIGZvciBpbXBvcnRpbmcgaW50byBBenVyZU1MCmBgYHtyfQp3cml0ZS5jc3YoQ09FbGtIYXJ2ZXN0X1NlYXNvbixmaWxlID0gIn4vX2NvZGUvY29sb3JhZG8tZG93L2RhdGFzZXRzL0NPRWxrSGFydmVzdF9TZWFzb24uY3N2Iixyb3cubmFtZXMgPSBGKQpgYGAKCioqKgojIyBNb2RlbCBCdWlsZGluZwoKIyMjIE1vZGVsIFRyYWluaW5nIE1ldGhvZHMKTG9vcCB0aHJvdWdoIHBvc3NpYmxlIG1ldGhvZHMsIHV0aWxpemluZyB0aGUgcXVpY2tlciAnYWRhcHRpdmVfY3YnIHBhcmFtZXRlciBzZWFyY2ggZnJvbSBjYXJldC4KQ29uc2lkZXIgc2NyaXB0aW5nIHRoaXMgaW50byBBenVyZU1MIHRvIG1ha2UgaXQgcnVuIG11Y2ggZmFzdGVyLCB0aG91Z2ggdGhlcmUgaXMgbW9yZSBzZXR1cCBhbmQgZXJyb3JzIHRvIApjb250cm9sIGZvcgoKYGBge3J9CnF1aWNrbWV0aG9kcyA8LSBjKCJsbSIsJ3N2bUxpbmVhcicsInN2bVJhZGlhbCIsImtubiIsImN1YmlzdCIsImtrbm4iKQoKc3RlcDFfU2Vhc29uX2FsbCA8LSBOVUxMCmZvciAoaW1ldGhvZCBpbiBxdWlja21ldGhvZHMpIHsKICBzdGVwMV9TZWFzb24gPC0gTlVMTAogIHN0YXJ0IDwtIG5vdygpCiAgCiAgaWYgKGltZXRob2QgPT0gImxtIiB8IGltZXRob2QgPT0gInN2bUxpbmVhciIpIHsKICAgIGNvbnRyb2xtZXRob2QgPC0gInJlcGVhdGVkY3YiCiAgfSBlbHNlIHtjb250cm9sbWV0aG9kIDwtICJhZGFwdGl2ZV9jdiJ9CiAgCiAgZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2woCiAgICBtZXRob2QgPSBjb250cm9sbWV0aG9kLAogICAgIyBzZWFyY2ggPSAncmFuZG9tJywKICAgIG51bWJlciA9IDQsCiAgICByZXBlYXRzID0gNCwKICAgIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogICAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCiAgCiAgcmVnaXN0ZXJEb1NFUSgpCiAgcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKICAKICBIYXJ2ZXN0TW9kZWxfMV9TZWFzb24gPSB0cmFpbihIYXJ2ZXN0IH4gLiwgZGF0YSA9IHRyYWluZGF0YTIsCiAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSBpbWV0aG9kLAogICAgICAgICAgICAgICAgICAgICAgICAgI3ByZVByb2MgPSBjKCJjZW50ZXIiLCJzY2FsZSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSAxNSwKICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCiAgCiAgSGFydmVzdE1vZGVsXzFfU2Vhc29uCiAgCiAgIyBtZWFzdXJlIHBlcmZvcm1hbmNlCiAgcHJlZGljdGRhdGEgPC0gcHJlZGljdChIYXJ2ZXN0TW9kZWxfMV9TZWFzb24sIHRlc3RkYXRhMikKICAKICBzdGVwMV9TZWFzb24kbWV0aG9kIDwtIGltZXRob2QKICBzdGVwMV9TZWFzb24kUk1TRSA8LSBwb3N0UmVzYW1wbGUocHJlZCA9IHByZWRpY3RkYXRhLCBvYnMgPSB0ZXN0ZGF0YTIkSGFydmVzdClbMV0KICBzdGVwMV9TZWFzb24kZHVyYXRpb24gPC0gbm93KCkgLSBzdGFydAogIHN0ZXAxX1NlYXNvbiA8LSBhcy5kYXRhLmZyYW1lKHN0ZXAxX1NlYXNvbikKICBzdGVwMV9TZWFzb25fYWxsIDwtIHJiaW5kKHN0ZXAxX1NlYXNvbl9hbGwsc3RlcDFfU2Vhc29uKQp9CmBgYApWaWV3IFJlc3VsdHMsIGFuZCBjb21wYXJlIHRvIHByZXZpb3VzIGZpcnN0IG1vZGVscyB0aGF0IGRpZCBub3QgZXhwYW5kIHRvIFNlYXNvbnMKYGBge3J9CnN0ZXAxX2FsbApzdGVwMV9TZWFzb25fYWxsCmBgYApUaGUgcGVyZm9ybWFuY2UgUk1TRSBtZXRyaWMgaXMgY2VydGFpbmx5IGltcHJvdmVkIHdoZW4gaW5jbHVkaW5nIHRoZSBTZWFzb25hbCBncm91cGluZy4KTGV0cyB0YWtlIHRoZSBiZXN0IG1ldGhvZCwgYW5kIHNlZSBpZiBpdCBpcyB2aXN1YWxseSByZWFzb25hYmxlIHdoaWxlIGFsc28gY2hhcnRpbmcgd2l0aG91dCBTZWFzb25zLgoKIyMjIFByZWRpY3Rpb25zIHVzaW5nIGJlc3QgU2Vhc29uIG1vZGVsClJ1biB0aGUgYmVzdCBtb2RlbCB3aXRoIGFsbCBvZiB0aGUgZGF0YQpgYGB7cn0KZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2woCiAgICBtZXRob2QgPSAiYWRhcHRpdmVfY3YiLAogICAgIyBzZWFyY2ggPSAncmFuZG9tJywKICAgIG51bWJlciA9IDQsCiAgICByZXBlYXRzID0gNCwKICAgIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogICAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCiAgCnJlZ2lzdGVyRG9TRVEoKQpyZWdpc3RlckRvTUMoY29yZXMgPSA2KQogIApIYXJ2ZXN0TW9kZWxfMV9TZWFzb24gPSB0cmFpbihIYXJ2ZXN0IH4gLiwgZGF0YSA9IENPRWxrSGFydmVzdF9TZWFzb24sCiAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAnY3ViaXN0JywKICAgICAgICAgICAgICAgICAgICAgICAgICNwcmVQcm9jID0gYygiY2VudGVyIiwic2NhbGUiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTUsCiAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQogIApIYXJ2ZXN0TW9kZWxfMV9TZWFzb24KYGBgCgojIyBQcmVkaWN0IEh1bnRlciBmb3IgbmV4dCB5ZWFyLCAyMDE4CmBgYHtyfQojIEdldCAyMDE4IEh1bnRlcnMgZnJvbSB0aGUgSHVudGVycyBQcmVkaWN0ZWQgbW9kZWwKbG9hZCgifi9fY29kZS9jb2xvcmFkby1kb3cvZGF0YXNldHMvQ09FbGtIdW50ZXJzMjAxOF9TZWFzb24uUkRhdGEiKQoKQ09FbGtIdW50ZXJzX1NlYXNvbjIwMThQcmVkaWN0ZWQgPC0gc3VtbWFyaXNlKGdyb3VwX2J5KENPRWxrSHVudGVyczIwMThfU2Vhc29uLCBZZWFyLCBVbml0LFNlYXNvbiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEh1bnRlcnMgPSBzdW0oSHVudGVycyxuYS5ybSA9IFQpKQoKIyBHZXQgbGlzdCBvZiBVbml0cyBhbmQgU2Vhc29ucyB0aGF0IHdpbGwgaGF2ZSBkYXRhCkNPRWxrSGFydmVzdDIwMThfU2Vhc29uIDwtIENPRWxrSGFydmVzdF9TZWFzb24KQ09FbGtIYXJ2ZXN0MjAxOF9TZWFzb24kVW5pdF9TZWFzb24gPC0gcGFzdGUoQ09FbGtIYXJ2ZXN0MjAxOF9TZWFzb24kVW5pdCxDT0Vsa0hhcnZlc3QyMDE4X1NlYXNvbiRTZWFzb24pCkNPRWxrSGFydmVzdDIwMThfU2Vhc29uIDwtIGFzLmRhdGEuZnJhbWUodW5pcXVlKENPRWxrSGFydmVzdDIwMThfU2Vhc29uJFVuaXRfU2Vhc29uKSkKY29sbmFtZXMoQ09FbGtIYXJ2ZXN0MjAxOF9TZWFzb24pIDwtICJVbml0X1NlYXNvbiIKIyBGaWxsIGluIG1pc3NpbmcgVW5pdHMgYW5kIFNlYXNvbnMgcGVyIHVuaXF1ZSBVbml0X1NlYXNvbnMKQ09FbGtIYXJ2ZXN0MjAxOF9TZWFzb24kVW5pdCA8LSBzdHJfZXh0cmFjdChDT0Vsa0hhcnZlc3QyMDE4X1NlYXNvbiRVbml0X1NlYXNvbiwiWzphbG51bTpdKyg/PVs6Ymxhbms6XSkiKQpDT0Vsa0hhcnZlc3QyMDE4X1NlYXNvbiRTZWFzb24gPC0gc3RyX2V4dHJhY3QoQ09FbGtIYXJ2ZXN0MjAxOF9TZWFzb24kVW5pdF9TZWFzb24sIig/PD1bOmJsYW5rOl0pWzphbG51bTpdKyIpCkNPRWxrSGFydmVzdDIwMThfU2Vhc29uIDwtIHNlbGVjdChDT0Vsa0hhcnZlc3QyMDE4X1NlYXNvbiwgLVVuaXRfU2Vhc29uKQpDT0Vsa0hhcnZlc3QyMDE4X1NlYXNvbiRZZWFyIDwtIDIwMTgKCiMgV2VhdGhlciBkYXRhIGZvciAyMDE4ClVuaXRXZWF0aGVyX1NlYXNvbjIwMTggPC0gZmlsdGVyKFVuaXRXZWF0aGVyX1NlYXNvbixZZWFyPT0yMDE4KQoKIyBBIGxlZnQgam9pbiB3aWxsIGF1dG9maWxsIG1pc3NpbmcgZHJhdyBkYXRhIHdpdGggTkFzLCBidXQgd2lsbCByZXRhaW4gdGhlIGZ1bGwgbGlzdCBvZiBVbml0IFNlYXNvbnMKQ09FbGtIYXJ2ZXN0MjAxOF9TZWFzb24gPC0gbGVmdF9qb2luKENPRWxrSGFydmVzdDIwMThfU2Vhc29uLFVuaXRXZWF0aGVyX1NlYXNvbjIwMTgpCgojIEpvaW4gaW4gZm9yZWNhc3RlZCBIdW50ZXJzCkNPRWxrSGFydmVzdDIwMThfU2Vhc29uIDwtIGxlZnRfam9pbihDT0Vsa0hhcnZlc3QyMDE4X1NlYXNvbixDT0Vsa0h1bnRlcnNfU2Vhc29uMjAxOFByZWRpY3RlZCkKCiMgT25seSB1c2UgdGhlIGZpZWxkcyB0aGF0IHdlcmUgaW5jbHVkZWQgaW4gdGhlIG1vZGVsCkNPRWxrSGFydmVzdDIwMThfU2Vhc29uIDwtIENPRWxrSGFydmVzdDIwMThfU2Vhc29uWywgY29sbmFtZXMoQ09FbGtIYXJ2ZXN0MjAxOF9TZWFzb24pICVpbiUgYygiVW5pdCIsIlNlYXNvbiIsSGFydmVzdE1vZGVsXzFfU2Vhc29uJGNvZWZuYW1lcyldCgojIFVzZSB0cmFpbmVkIG1vZGVsIHRvIHByZWRpY3QgSGFydmVzdApDT0Vsa0hhcnZlc3QyMDE4X1NlYXNvbiRIYXJ2ZXN0IDwtIHJvdW5kKHByZWRpY3QoSGFydmVzdE1vZGVsXzFfU2Vhc29uLCBDT0Vsa0hhcnZlc3QyMDE4X1NlYXNvbikpCgpDT0Vsa0hhcnZlc3QyMDE4X1NlYXNvbiRIYXJ2ZXN0W0NPRWxrSGFydmVzdDIwMThfU2Vhc29uJEhhcnZlc3Q8MF0gPC0gMAoKIyBDb21iaW5lIHdpdGggaGlzdG9yaWMgZGF0YQpDT0Vsa0hhcnZlc3RBbGxfU2Vhc29uIDwtIHJiaW5kLmZpbGwoQ09FbGtIYXJ2ZXN0X1NlYXNvbixDT0Vsa0hhcnZlc3QyMDE4X1NlYXNvbikKYGBgClNhdmUgb2ZmIHNvIHdlIGRvbid0IGhhdmUgdG8gcmVjcmVhdGUgdGhlIG1vZGVsIGV2ZXJ5dGltZSB3ZSB3YW50IHRoZSByZXN1bHRzCmBgYHtyfQpzYXZlKENPRWxrSGFydmVzdDIwMThfU2Vhc29uLGZpbGU9In4vX2NvZGUvY29sb3JhZG8tZG93L2RhdGFzZXRzL0NPRWxrSGFydmVzdDIwMThfU2Vhc29uLlJEYXRhIikKYGBgCgpMYWJlbCBhbmQgSm9pbiBtb2RlbHMgdG9nZXRoZXIgZm9yIGNvbXBhcmlzb25zCmBgYHtyfQojIExvYWQgZmlyc3QgbW9kZWwgd2l0aG91dCBTZWFzb25zCmxvYWQoIn4vX2NvZGUvY29sb3JhZG8tZG93L2RhdGFzZXRzL0NPRWxrSGFydmVzdDIwMTguUkRhdGEiKQpIYXJ2ZXN0Y29tcGFyZSA8LSByYmluZC5maWxsKENPRWxrSGFydmVzdCxDT0Vsa0hhcnZlc3QyMDE4KQpIYXJ2ZXN0Y29tcGFyZSRtb2RlbGRhdGEgPC0gIldpdGhvdXQgU2Vhc29ucyIKCkhhcnZlc3Rjb21wYXJlX1NlYXNvbiA8LSByYmluZC5maWxsKENPRWxrSGFydmVzdF9TZWFzb24sQ09FbGtIYXJ2ZXN0MjAxOF9TZWFzb24pCkhhcnZlc3Rjb21wYXJlX1NlYXNvbiA8LSBzdW1tYXJpc2UoZ3JvdXBfYnkoSGFydmVzdGNvbXBhcmVfU2Vhc29uLFllYXIsVW5pdCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSGFydmVzdCA9IHN1bShIYXJ2ZXN0KSkKSGFydmVzdGNvbXBhcmVfU2Vhc29uJG1vZGVsZGF0YSA8LSAiU2Vhc29ucyIKCkhhcnZlc3Rjb21wYXJlIDwtIHJiaW5kLmZpbGwoSGFydmVzdGNvbXBhcmUsSGFydmVzdGNvbXBhcmVfU2Vhc29uKQoKYGBgCmBgYHtyfQojIEdyb3VwIFVuaXRzCkhhcnZlc3Rjb21wYXJlU3RhdGV3aWRlIDwtIHN1bW1hcmlzZShncm91cF9ieShIYXJ2ZXN0Y29tcGFyZSxZZWFyLG1vZGVsZGF0YSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSGFydmVzdCA9IHN1bShIYXJ2ZXN0KSkKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTB9CmdncGxvdChIYXJ2ZXN0Y29tcGFyZVN0YXRld2lkZSwgYWVzKFllYXIsSGFydmVzdCxncm91cD1tb2RlbGRhdGEsZmlsbD1tb2RlbGRhdGEpKSArCiAgZ2VvbV9iYXIocG9zaXRpb249ImRvZGdlIixzdGF0PSJpZGVudGl0eSIpICsKICAjIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygxMzAwMDAsMTU1MDAwKSkgKwogIHNjYWxlX2ZpbGxfaGMoKSArCiAgbGFicyh0aXRsZT0iU3RhdGV3aWRlIEVsayBIYXJ2ZXN0IiwgY2FwdGlvbj0ic291cmNlOiBjcHcuc3RhdGUuY28udXMiKQpgYGAKCiMjIyMgSGFydmVzdCBTdGF0ZXdpZGUgYnkgU2Vhc29uIGFuZCBZZWFyCmBgYHtyIGZpZy53aWR0aD0xMH0KSGFydmVzdF9TZWFzb24gPC0gcmJpbmQuZmlsbChDT0Vsa0hhcnZlc3RfU2Vhc29uLENPRWxrSGFydmVzdDIwMThfU2Vhc29uKQpnZ3Bsb3QoSGFydmVzdF9TZWFzb24sIGFlcyhZZWFyLEhhcnZlc3QsZ3JvdXA9U2Vhc29uLGZpbGw9U2Vhc29uKSkgKwogIGdlb21fYmFyKHBvc2l0aW9uPSJkb2RnZSIsc3RhdD0iaWRlbnRpdHkiKSArCiAgIyBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMTMwMDAwLDE1NTAwMCkpICsKICBzY2FsZV9maWxsX2hjKCkgKwogIGxhYnModGl0bGU9IlN0YXRld2lkZSBFbGsgSGFydmVzdCIsIGNhcHRpb249InNvdXJjZTogY3B3LnN0YXRlLmNvLnVzIikKYGBgCgojIyMjIEhhcnZlc3QgaW4gVW5pdCA3NwpgYGB7ciBmaWcud2lkdGg9MTB9CkhhcnZlc3RfU2Vhc29uXzc3IDwtIGZpbHRlcihIYXJ2ZXN0X1NlYXNvbiwgVW5pdCA9PSAiNzciKQpnZ3Bsb3QoSGFydmVzdF9TZWFzb25fNzcsIGFlcyhZZWFyLEhhcnZlc3QsZ3JvdXA9U2Vhc29uLGZpbGw9U2Vhc29uKSkgKwogIGdlb21fYmFyKHBvc2l0aW9uPSJkb2RnZSIsc3RhdD0iaWRlbnRpdHkiKSArCiAgIyBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMTMwMDAwLDE1NTAwMCkpICsKICBzY2FsZV9maWxsX2hjKCkgKwogIGxhYnModGl0bGU9IlVuaXQgNzcgRWxrIEhhcnZlc3QiLCBjYXB0aW9uPSJzb3VyY2U6IGNwdy5zdGF0ZS5jby51cyIpCmBgYAoKCgoKCg==