packages = c(
  "dplyr","ggplot2","d3heatmap","googleVis","devtools","plotly", "xgboost",
  "magrittr","caTools","ROCR","corrplot", "rpart", "rpart.plot",
  "doParallel", "caret", "glmnet", "Matrix", "e1071", "randomForest"
  )
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)
rm(list=ls(all=T))
options(digits=5, scipen=12)
library(dplyr)
library(caTools)
library(rpart)
library(rpart.plot)
library(randomForest)

1. Suprime Court Decision

1.1 Prepare Data

D = read.csv("data/stevens.csv")

library(caTools)
set.seed(3000)
spl = sample.split(D$Reverse, SplitRatio = 0.7)
TR = subset(D, spl)
TS = subset(D, !spl)

1.2 CART (Classification & Regression Tree) - rpart::rpart()

library(rpart)
rpart1 = rpart(Reverse ~ ., TR[,3:9],          # simplify the formula
               method="class", minbucket=25)

1.3 Plot Decision Tree

library(rpart.plot)
prp(rpart1)
rpart.plot(rpart1,cex=0.6)

1.4 Make Prediction

pred = predict(rpart1, TS, type = "class")  # predict classes
x = table(actual = TS$Reverse, pred); x
sum(diag(x))/sum(x)                         # .65882

1.5 ROC & AUC

library(ROCR)
PredictROC = predict(rpart1, TS)              # predict prob.
head(PredictROC)
perf = prediction(PredictROC[,2], TS$Reverse)
perf = performance(perf, "tpr", "fpr")
plot(perf)
pred = predict(rpart1, TS)[,2]     # prob. of Reverse = 1         
colAUC(pred, TS$Reverse, T)        # AUC = 0.6927

1.6 DPP of Decision Tree

rpart.plot(rpart1,cex=0.75,varlen=3,faclen=2,box.palette="GnRd")
source("DPP.R")
auc = DPP(predict(rpart1)[,2], TR$Reverse, 1)     # AUC = 0.7884

【QUIZ-1】比較以上prp()rpart.plot()DPP()這幾張圖形

  • 一棵決策樹的形狀, 和由它所產生的預測機率分布之間,有什麼關係呢 ? 以上DPP 圖中的分布點數、位置和高度,分別會對應到決策樹的什麼特徵呢?

  • 決策樹的分類是利用現有的變數,將資料做最好的區隔變數來適配,於是藉由設定參數讓決策樹演算出最好的模型,每個分類下的資料點會計算出其機率,再對應其類別。將所有資料點依演算法做出的模型做切割,每個資料點會到不同的分類(不同分類有不同的機率與類別),而這分類的過程也就是決策樹的分支。/DPP的橫軸為機率;縱軸為數量,將決策樹分類後的每個bucket有其機率則其會分布在DPP所對應的機率上,而因此基本上bucket數與DPP上的長條數會一樣(本題因兩個機率相同則合併計算),另外決策樹的bucket會顯示%數為所含總資料數的比例,反應在DPP的高度。


1.7 The effect of minbucket

t5 = rpart(Reverse ~ ., TR[,3:9], method="class", minbucket=5)
prp(t5)
t100 = rpart(Reverse ~ ., TR[,3:9], method="class", minbucket=100)
prp(t100)

【QUIZ-2】Based on the 2 plots above, what is the effect of minbucket?

  • Minbucket 代表在決策樹做分類時,每個bucket所需包含的最少數量,這個設定可以避免過度適配原有資料而導致決策樹過於複雜流失更多的ACC。


1.8 Random Forest Method - randomForest::randomForest()

library(randomForest)
TR$Reverse = as.factor(TR$Reverse)  # Convert outcome to factor
TS$Reverse = as.factor(TS$Reverse)

# Build model
rf1 = randomForest(Reverse ~ ., TR[,3:9], ntree=200, nodesize=25)

# Make predictions
pred = predict(rf1, TS)
x = table(TS$Reverse, pred)
sum(diag(x)) / sum(x)          # 0.6824
pred = predict(rf1, TS, type='prob')[,2]
table(TS$Reverse, pred > 0.5) %>% {sum(diag(.))/sum(.)}

1.9 Cross Validation & Parameter Tuning - caret package

library(caret)
cv1 = train(
  Reverse ~ ., TR[,3:9], method = "rpart", 
  trControl = trainControl(method="cv", number=10), # 10 fold CV
  tuneGrid=expand.grid(cp = seq(0.01,0.5,0.01))     # parameter combination
  )
cv1; plot(cv1)

【QUIZ-3】Which cp which we should we choose? Why?

  • 0.19, cp = cost of complexity 多設一個節點,損失下降如果比設定的cp低,就不再往下分支了;cp越高,代表model越不複雜。 從交叉驗證選擇最佳的cp的過程中發現本題cp=0.06~0.19的ACC皆屬一致,在ACC一致的情況下會傾向選擇複雜度較低的cp值,故選擇0.19。


1.10 The Final Model

rpart2 = rpart(Reverse ~ ., TR[,3:9], method="class", cp=0.19)
pred = predict(rpart2, TS, type='prob')[,2]
table(TS$Reverse, pred > 0.5) %>% {sum(diag(.)) / sum(.)}  # 0.7235
rpart.plot(rpart2)


【學習重點】

  • Classification & Regression Tree rpart
    • method=“classs” : 用來分類
    • predict時,type可為“class”或“prob”
  • A Bag of Tree - Random Forest randomForest
    • 如果要分類的話要把y變數轉換為factor型態
    • 可以調整mtry, ntree, nodesize, cp
  • Overfitting : Curse of Complexity
    • 樹太過複雜導致
    • 透過參數調校解決問題
  • Method Parameter : Cost of Compexity
    • 每多設一個節點會增加複雜度的成本,所以透過設定cp參數來限制樹的複雜度
    • 可以利用CV選擇最佳cp
  • Parameter Tuning by CV (caret package)
    • 利用Cross-Validation來選擇最佳的cp
    • 把原始training data分成k份,每次輪流選擇一份當作testing data其他k-1份當作training data,建立多個模型來找最好的參數



2 D2HawkEye

2.1 Prepare & Examine Data

rm(list=ls(all=TRUE))
D = read.csv("data/ClaimsData.csv")

# Percentage of patients in each cost bucket
table(D$bucket2009)/nrow(D)
# Split the data
library(caTools)
set.seed(88)
spl = sample.split(D$bucket2009, SplitRatio = 0.6)
TR = subset(D, spl)
TS = subset(D, !spl)
# sapply(): Apply Function & Aggregate Output
sapply(list(D, TR, TS), function(x) 
  table(x$bucket2009) %>% prop.table) %>% t
# Data Exploration 
mean(TR$age)                 # 72.64
mean(TR$diabetes)            # .3809 

2.2 Smart Baseline

# Baseline Accuracy
acc.base = table(TS$bucket2009, TS$bucket2008) %>% {sum(diag(.)) / sum(.)}
acc.base   # acc.base = .6838
# Penalty Matrix
Penalty = matrix(c(
  0,1,2,3,4,
  2,0,1,2,3,
  4,2,0,1,2,
  6,4,2,0,1,
  8,6,4,2,0), byrow=TRUE, nrow=5)
Penalty
# Penalty Error of Baseline Method
cm = as.matrix(table(TS$bucket2009, TS$bucket2008))
pen.base = sum(cm*Penalty) / nrow(TS)
pen.base  # pen.base: .7386
# Dumb baseline - Using the most frequent class
acc.dumb = (table(TS$bucket2009)/nrow(TS))[1]                 
pen.dumb = sum(table(TS$bucket2009) * c(0,2,4,6,8))/nrow(TS)  
c(acc.dumb, pen.dumb)   # 0.6713 1.0443

2.3 CART Model

library(rpart)
library(rpart.plot)
rpart1 = rpart(bucket2009 ~ ., data=TR[c(1:14, 16)], 
               method="class", cp=0.00005)
prp(rpart1)
# Make predictions
pred = predict(rpart1, newdata = TS, type = "class")
acc.rpart1 = table(TS$bucket2009, pred) %>% { sum(diag(.)) /nrow(TS)}
acc.rpart1    # acc.rpart1 = .71267 <- .68381
cm = as.matrix(table(TS$bucket2009, pred))
pen.rpart1 = sum(cm*Penalty)/nrow(TS)
pen.rpart1    # pen.rpart1: .75789 <- .7386

2.4 CART Model with Customized Loss Matrix

rpart2 = rpart(bucket2009 ~ ., data=TR[c(1:14, 16)], 
               method="class", cp=0.00005, 
               parms=list(loss=Penalty) )    # customized loss function # # 原本決策樹選擇ACC最高的結果,但將penalty矩陣加入參數,將會選擇penalty最低的模型。
               

pred = predict(rpart2, TS, type = "class")
acc.rpart2 = table(TS$bucket2009, pred) %>% {sum(diag(.))/sum(.)}
acc.rpart2    # acc.rpart2 = .64727   
cm = as.matrix(table(TS$bucket2009, pred))
pen.rpart2 = sum(cm*Penalty)/nrow(TS)    
pen.rpart2    # pen.rpart2: .64182 
# Summary
data.frame(
  accuracy = c(acc.dumb, acc.base, acc.rpart1, acc.rpart2),
  penalty = c(pen.dumb, pen.base, pen.rpart1, pen.rpart2),
  row.names = c("dumb","base","CART","CART w/LossMx"))


【學習重點】

  • 5 classes: 5X5 confussion/penalty(payoff) matrices
  • dumb vs. smart baseline
  • model1: accuracy up, penalty up
  • model2: accuracy down, penalty down
  • accuracy != profitability
  • “customized” model optimize criteria in classification methods (not always available)
  • This CV/PT process is over simplified
  • we will cover a better process next time



3 Boston House Price

rm(list=ls(all=TRUE))
D = read.csv("data/boston.csv")

3.1 Examine Data

# Plot observations
plot(D$LON, D$LAT)

# Tracts alongside the Charles River
with(D, points(LON[CHAS==1], LAT[CHAS==1], col="blue", pch=19))
points(D$LON[D$CHAS==1], D$LAT[D$CHAS==1], col="blue", pch=19)

# Plot MIT
with(D, points(LON[TRACT==3531], LAT[TRACT==3531], col="red", pch=19) )

# Plot polution
summary(D$NOX)
points(D$LON[D$NOX>=0.55], D$LAT[D$NOX>=0.55], col="green", pch=20)

# Plot prices
plot(D$LON, D$LAT)
summary(D$MEDV)
points(D$LON[D$MEDV>=21.2], D$LAT[D$MEDV>=21.2], col="red", pch=20)

3.2 Linear Regression using LAT and LON

latlonlm = lm(MEDV ~ LAT + LON, data=D)
summary(latlonlm)

3.3 Visualize regression output

plot(D$LON, D$LAT)
points(D$LON[D$MEDV>=21.2], D$LAT[D$MEDV>=21.2], col="red", pch=20)

# latlonlm$fitted.values
points(D$LON[latlonlm$fitted.values >= 21.2], 
       D$LAT[latlonlm$fitted.values >= 21.2], 
       col="blue", pch="x")
library(rpart)
library(rpart.plot)
# CART model
latlontree = rpart(MEDV ~ LAT + LON, data=D)
prp(latlontree)

# Visualize output
plot(D$LON, D$LAT)
points(D$LON[D$MEDV>=21.2], D$LAT[D$MEDV>=21.2], 
       col="red", pch=20)

fittedvalues = predict(latlontree)
points(D$LON[fittedvalues>21.2], 
       D$LAT[fittedvalues>=21.2], col="blue", pch="x")

3.4 Simplify tree by increasing minbucket

latlontree = rpart(MEDV ~ LAT + LON, data=D, minbucket=50)
rpart.plot(latlontree)
# Visualize Output
plot(D$LON,D$LAT)
abline(v=-71.07)
abline(h=42.21)
abline(h=42.17)
points(D$LON[D$MEDV>=21.2], 
       D$LAT[D$MEDV>=21.2], col="red", pch=20)

3.5 Use all the variables

# Split the data
library(caTools)
set.seed(123)
split = sample.split(D$MEDV, SplitRatio = 0.7)
train = subset(D, split==TRUE)
test = subset(D, split==FALSE)

# Create linear regression
linreg = lm(MEDV ~ LAT + LON + CRIM + ZN + INDUS + CHAS + NOX + RM + 
              AGE + DIS + RAD + TAX + PTRATIO, data=train)
summary(linreg)

# Make predictions
linreg.pred = predict(linreg, newdata=test)
linreg.sse = sum((linreg.pred - test$MEDV)^2)
linreg.sse

3.6 Create a CART model

tree = rpart(MEDV ~ LAT + LON + CRIM + ZN + INDUS + CHAS + NOX + RM + 
               AGE + DIS + RAD + TAX + PTRATIO, data=train)
prp(tree)

# Make predictions
tree.pred = predict(tree, newdata=test)
tree.sse = sum((tree.pred - test$MEDV)^2)
tree.sse

3.7 Load libraries for cross-validation

library(caret)

# Number of folds
tr.control = trainControl(method = "cv", number = 10)

# cp values
cp.grid = expand.grid( .cp = (0:10)*0.001)

# Cross-validation
tr = train(MEDV ~ LAT + LON + CRIM + ZN + INDUS + CHAS + NOX + RM + 
             AGE + DIS + RAD + TAX + PTRATIO, 
           data = train, method = "rpart", 
           trControl = tr.control, tuneGrid = cp.grid)
tr

# Extract tree
best.tree = tr$finalModel
prp(best.tree)

# Make predictions
best.tree.pred = predict(best.tree, newdata=test)
best.tree.sse = sum((best.tree.pred - test$MEDV)^2)
best.tree.sse

3.8 Ensembling

en.pred = (best.tree.pred + linreg.pred)/2
sum((en.pred - test$MEDV)^2)
#       METHOD   SSE
#           lm  3037
#         rpart 4329
#      rpart.cv 3660
#   lm+rpart.cv 2599  


【學習重點】

  • drawing map with spatial data
  • tree in spatial data
  • tree vs. linear regression
  • the concept of ensembling








LS0tDQp0aXRsZTogIkFTNC0wIEdydW9wNCDoqrLloILnrYboqJgiDQphdXRob3I6ICJHcnVvcDQiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7cn0NCnBhY2thZ2VzID0gYygNCiAgImRwbHlyIiwiZ2dwbG90MiIsImQzaGVhdG1hcCIsImdvb2dsZVZpcyIsImRldnRvb2xzIiwicGxvdGx5IiwgInhnYm9vc3QiLA0KICAibWFncml0dHIiLCJjYVRvb2xzIiwiUk9DUiIsImNvcnJwbG90IiwgInJwYXJ0IiwgInJwYXJ0LnBsb3QiLA0KICAiZG9QYXJhbGxlbCIsICJjYXJldCIsICJnbG1uZXQiLCAiTWF0cml4IiwgImUxMDcxIiwgInJhbmRvbUZvcmVzdCINCiAgKQ0KZXhpc3RpbmcgPSBhcy5jaGFyYWN0ZXIoaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdKQ0KZm9yKHBrZyBpbiBwYWNrYWdlc1shKHBhY2thZ2VzICVpbiUgZXhpc3RpbmcpXSkgaW5zdGFsbC5wYWNrYWdlcyhwa2cpDQpgYGANCg0KYGBge3IgZWNobz1ULCBtZXNzYWdlPUYsIGNhY2hlPUYsIHdhcm5pbmc9Rn0NCnJtKGxpc3Q9bHMoYWxsPVQpKQ0Kb3B0aW9ucyhkaWdpdHM9NSwgc2NpcGVuPTEyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoY2FUb29scykNCmxpYnJhcnkocnBhcnQpDQpsaWJyYXJ5KHJwYXJ0LnBsb3QpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmBgYA0KDQotIC0gLQ0KDQojIyMgMS4gU3VwcmltZSBDb3VydCBEZWNpc2lvbg0KDQojIyMjIDEuMSBQcmVwYXJlIERhdGENCmBgYHtyfQ0KRCA9IHJlYWQuY3N2KCJkYXRhL3N0ZXZlbnMuY3N2IikNCg0KbGlicmFyeShjYVRvb2xzKQ0Kc2V0LnNlZWQoMzAwMCkNCnNwbCA9IHNhbXBsZS5zcGxpdChEJFJldmVyc2UsIFNwbGl0UmF0aW8gPSAwLjcpDQpUUiA9IHN1YnNldChELCBzcGwpDQpUUyA9IHN1YnNldChELCAhc3BsKQ0KYGBgDQoNCiMjIyMgMS4yIENBUlQgKENsYXNzaWZpY2F0aW9uICYgUmVncmVzc2lvbiBUcmVlKSAtIGBycGFydDo6cnBhcnQoKWANCmBgYHtyfQ0KbGlicmFyeShycGFydCkNCnJwYXJ0MSA9IHJwYXJ0KFJldmVyc2UgfiAuLCBUUlssMzo5XSwgICAgICAgICAgIyBzaW1wbGlmeSB0aGUgZm9ybXVsYQ0KICAgICAgICAgICAgICAgbWV0aG9kPSJjbGFzcyIsIG1pbmJ1Y2tldD0yNSkNCmBgYA0KDQojIyMjIDEuMyBQbG90IERlY2lzaW9uIFRyZWUNCmBgYHtyfQ0KbGlicmFyeShycGFydC5wbG90KQ0KcHJwKHJwYXJ0MSkNCnJwYXJ0LnBsb3QocnBhcnQxLGNleD0wLjYpDQpgYGANCg0KIyMjIyAxLjQgTWFrZSBQcmVkaWN0aW9uDQpgYGB7cn0NCnByZWQgPSBwcmVkaWN0KHJwYXJ0MSwgVFMsIHR5cGUgPSAiY2xhc3MiKSAgIyBwcmVkaWN0IGNsYXNzZXMNCnggPSB0YWJsZShhY3R1YWwgPSBUUyRSZXZlcnNlLCBwcmVkKTsgeA0Kc3VtKGRpYWcoeCkpL3N1bSh4KSAgICAgICAgICAgICAgICAgICAgICAgICAjIC42NTg4Mg0KYGBgDQoNCiMjIyMgMS41IFJPQyAmIEFVQw0KYGBge3J9DQpsaWJyYXJ5KFJPQ1IpDQpQcmVkaWN0Uk9DID0gcHJlZGljdChycGFydDEsIFRTKSAgICAgICAgICAgICAgIyBwcmVkaWN0IHByb2IuDQpoZWFkKFByZWRpY3RST0MpDQpwZXJmID0gcHJlZGljdGlvbihQcmVkaWN0Uk9DWywyXSwgVFMkUmV2ZXJzZSkNCnBlcmYgPSBwZXJmb3JtYW5jZShwZXJmLCAidHByIiwgImZwciIpDQpwbG90KHBlcmYpDQpgYGANCg0KYGBge3J9DQpwcmVkID0gcHJlZGljdChycGFydDEsIFRTKVssMl0gICAgICMgcHJvYi4gb2YgUmV2ZXJzZSA9IDEgICAgICAgICANCmNvbEFVQyhwcmVkLCBUUyRSZXZlcnNlLCBUKSAgICAgICAgIyBBVUMgPSAwLjY5MjcNCmBgYA0KDQoNCiMjIyMgMS42IERQUCBvZiBEZWNpc2lvbiBUcmVlDQoNCmBgYHtyfQ0KcnBhcnQucGxvdChycGFydDEsY2V4PTAuNzUsdmFybGVuPTMsZmFjbGVuPTIsYm94LnBhbGV0dGU9IkduUmQiKQ0KYGBgDQoNCg0KYGBge3J9DQpzb3VyY2UoIkRQUC5SIikNCmF1YyA9IERQUChwcmVkaWN0KHJwYXJ0MSlbLDJdLCBUUiRSZXZlcnNlLCAxKSAgICAgIyBBVUMgPSAwLjc4ODQNCmBgYA0KDQojIyMjIOOAkFFVSVotMeOAkeavlOi8g+S7peS4imBwcnAoKWDjgIFgcnBhcnQucGxvdCgpYOWSjGBEUFAoKWDpgJnlub7lvLXlnJblvaINCg0KKyDkuIDmo7XmsbrnrZbmqLnnmoTlvaLni4DvvIwg5ZKM55Sx5a6D5omA55Si55Sf55qE6aCQ5ris5qmf546H5YiG5biD5LmL6ZaT77yM5pyJ5LuA6bq86Zec5L+C5ZGiID8gIOS7peS4ikRQUCDlnJbkuK3nmoTliIbluIPpu57mlbjjgIHkvY3nva7lkozpq5jluqbvvIzliIbliKXmnIPlsI3mh4nliLDmsbrnrZbmqLnnmoTku4DpurznibnlvrXlkaI/DQoNCisg5rG6562W5qi555qE5YiG6aGe5piv5Yip55So54++5pyJ55qE6K6K5pW477yM5bCH6LOH5paZ5YGa5pyA5aW955qE5Y2A6ZqU6K6K5pW45L6G6YGp6YWN77yM5pa85piv6JeJ55Sx6Kit5a6a5Y+D5pW46K6T5rG6562W5qi55ryU566X5Ye65pyA5aW955qE5qih5Z6L77yM5q+P5YCL5YiG6aGe5LiL55qE6LOH5paZ6bue5pyD6KiI566X5Ye65YW25qmf546H77yM5YaN5bCN5oeJ5YW26aGe5Yil44CC5bCH5omA5pyJ6LOH5paZ6bue5L6d5ryU566X5rOV5YGa5Ye655qE5qih5Z6L5YGa5YiH5Ymy77yM5q+P5YCL6LOH5paZ6bue5pyD5Yiw5LiN5ZCM55qE5YiG6aGeKOS4jeWQjOWIhumhnuacieS4jeWQjOeahOapn+eOh+iIh+mhnuWIpSnvvIzogIzpgJnliIbpoZ7nmoTpgY7nqIvkuZ/lsLHmmK/msbrnrZbmqLnnmoTliIbmlK/jgIIvRFBQ55qE5qmr6Lu454K65qmf546H77yb57ix6Lu454K65pW46YeP77yM5bCH5rG6562W5qi55YiG6aGe5b6M55qE5q+P5YCLYnVja2V05pyJ5YW25qmf546H5YmH5YW25pyD5YiG5biD5ZyoRFBQ5omA5bCN5oeJ55qE5qmf546H5LiK77yM6ICM5Zug5q2k5Z+65pys5LiKYnVja2V05pW46IiHRFBQ5LiK55qE6ZW35qKd5pW45pyD5LiA5qijKOacrOmhjOWboOWFqeWAi+apn+eOh+ebuOWQjOWJh+WQiOS9teioiOeulynvvIzlj6blpJbmsbrnrZbmqLnnmoRidWNrZXTmnIPpoa/npLol5pW454K65omA5ZCr57i96LOH5paZ5pW455qE5q+U5L6L77yM5Y+N5oeJ5ZyoRFBQ55qE6auY5bqm44CCDQoNCjxicj4NCg0KIyMjIyAxLjcgVGhlIGVmZmVjdCBvZiBgbWluYnVja2V0YA0KYGBge3J9DQp0NSA9IHJwYXJ0KFJldmVyc2UgfiAuLCBUUlssMzo5XSwgbWV0aG9kPSJjbGFzcyIsIG1pbmJ1Y2tldD01KQ0KcHJwKHQ1KQ0KYGBgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9Mn0NCnQxMDAgPSBycGFydChSZXZlcnNlIH4gLiwgVFJbLDM6OV0sIG1ldGhvZD0iY2xhc3MiLCBtaW5idWNrZXQ9MTAwKQ0KcHJwKHQxMDApDQpgYGANCg0KDQojIyMj44CQUVVJWi0y44CRQmFzZWQgb24gdGhlIDIgcGxvdHMgYWJvdmUsIHdoYXQgaXMgdGhlIGVmZmVjdCBvZiBgbWluYnVja2V0YD8gDQoNCisgTWluYnVja2V0IOS7o+ihqOWcqOaxuuetluaoueWBmuWIhumhnuaZgu+8jOavj+WAi2J1Y2tldOaJgOmcgOWMheWQq+eahOacgOWwkeaVuOmHj++8jOmAmeWAi+ioreWumuWPr+S7pemBv+WFjemBjuW6pumBqemFjeWOn+acieizh+aWmeiAjOWwjuiHtOaxuuetluaouemBjuaWvOikh+mbnOa1geWkseabtOWkmueahEFDQ+OAgg0KDQo8YnI+DQoNCiMjIyMgMS44IFJhbmRvbSBGb3Jlc3QgTWV0aG9kIC0gYHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KClgDQpgYGB7cn0NCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KVFIkUmV2ZXJzZSA9IGFzLmZhY3RvcihUUiRSZXZlcnNlKSAgIyBDb252ZXJ0IG91dGNvbWUgdG8gZmFjdG9yDQpUUyRSZXZlcnNlID0gYXMuZmFjdG9yKFRTJFJldmVyc2UpDQoNCiMgQnVpbGQgbW9kZWwNCnJmMSA9IHJhbmRvbUZvcmVzdChSZXZlcnNlIH4gLiwgVFJbLDM6OV0sIG50cmVlPTIwMCwgbm9kZXNpemU9MjUpDQoNCiMgTWFrZSBwcmVkaWN0aW9ucw0KcHJlZCA9IHByZWRpY3QocmYxLCBUUykNCnggPSB0YWJsZShUUyRSZXZlcnNlLCBwcmVkKQ0Kc3VtKGRpYWcoeCkpIC8gc3VtKHgpICAgICAgICAgICMgMC42ODI0DQpgYGANCg0KYGBge3J9DQpwcmVkID0gcHJlZGljdChyZjEsIFRTLCB0eXBlPSdwcm9iJylbLDJdDQp0YWJsZShUUyRSZXZlcnNlLCBwcmVkID4gMC41KSAlPiUge3N1bShkaWFnKC4pKS9zdW0oLil9DQpgYGANCg0KDQojIyMjIDEuOSBDcm9zcyBWYWxpZGF0aW9uICYgUGFyYW1ldGVyIFR1bmluZyAtIGBjYXJldGAgcGFja2FnZQ0KYGBge3J9DQpsaWJyYXJ5KGNhcmV0KQ0KY3YxID0gdHJhaW4oDQogIFJldmVyc2UgfiAuLCBUUlssMzo5XSwgbWV0aG9kID0gInJwYXJ0IiwgDQogIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2Q9ImN2IiwgbnVtYmVyPTEwKSwgIyAxMCBmb2xkIENWDQogIHR1bmVHcmlkPWV4cGFuZC5ncmlkKGNwID0gc2VxKDAuMDEsMC41LDAuMDEpKSAgICAgIyBwYXJhbWV0ZXIgY29tYmluYXRpb24NCiAgKQ0KY3YxOyBwbG90KGN2MSkNCmBgYA0KDQojIyMj44CQUVVJWi0z44CRV2hpY2ggYGNwYCB3aGljaCB3ZSBzaG91bGQgd2UgY2hvb3NlPyBXaHk/IA0KDQorIDAuMTnvvIwNCmNwID0gY29zdCBvZiBjb21wbGV4aXR5DQrlpJroqK3kuIDlgIvnr4Dpu57vvIzmkI3lpLHkuIvpmY3lpoLmnpzmr5ToqK3lrprnmoRjcOS9ju+8jOWwseS4jeWGjeW+gOS4i+WIhuaUr+S6hu+8m2Nw6LaK6auY77yM5Luj6KGobW9kZWzotorkuI3opIfpm5zjgIINCuW+nuS6pOWPiempl+itiemBuOaTh+acgOS9s+eahGNw55qE6YGO56iL5Lit55m854++5pys6aGMY3A9MC4wNn4wLjE555qEQUND55qG5bGs5LiA6Ie077yM5ZyoQUND5LiA6Ie055qE5oOF5rOB5LiL5pyD5YK+5ZCR6YG45pOH6KSH6Zuc5bqm6LyD5L2O55qEY3DlgLzvvIzmlYXpgbjmk4cwLjE544CCDQoNCjxicj4NCg0KIyMjIyAxLjEwIFRoZSBGaW5hbCBNb2RlbA0KYGBge3J9DQpycGFydDIgPSBycGFydChSZXZlcnNlIH4gLiwgVFJbLDM6OV0sIG1ldGhvZD0iY2xhc3MiLCBjcD0wLjE5KQ0KcHJlZCA9IHByZWRpY3QocnBhcnQyLCBUUywgdHlwZT0ncHJvYicpWywyXQ0KdGFibGUoVFMkUmV2ZXJzZSwgcHJlZCA+IDAuNSkgJT4lIHtzdW0oZGlhZyguKSkgLyBzdW0oLil9ICAjIDAuNzIzNQ0KYGBgDQoNCmBgYHtyIGZpZy53aWR0aD0zfQ0KcnBhcnQucGxvdChycGFydDIpDQpgYGANCjxicj4NCg0KIyMjI+OAkOWtuOe/kumHjem7nuOAkQ0KDQorIENsYXNzaWZpY2F0aW9uICYgUmVncmVzc2lvbiBUcmVlICBycGFydA0KICAgICsgbWV0aG9kPSJjbGFzc3MiIDog55So5L6G5YiG6aGeDQogICAgKyBwcmVkaWN05pmC77yMdHlwZeWPr+eCuiJjbGFzcyLmiJYicHJvYiINCisgQSBCYWcgb2YgVHJlZSAtIFJhbmRvbSBGb3Jlc3QgcmFuZG9tRm9yZXN0ICANCiAgICArIOWmguaenOimgeWIhumhnueahOipseimgeaKinnorormlbjovYnmj5vngrpmYWN0b3LlnovmhYsNCiAgICArIOWPr+S7peiqv+aVtG10cnksIG50cmVlLCBub2Rlc2l6ZSwgY3ANCisgT3ZlcmZpdHRpbmcgOiBDdXJzZSBvZiBDb21wbGV4aXR5DQogICAgKyDmqLnlpKrpgY7opIfpm5zlsI7oh7QNCiAgICArIOmAj+mBjuWPg+aVuOiqv+agoeino+axuuWVj+mhjA0KKyBNZXRob2QgUGFyYW1ldGVyIDogQ29zdCBvZiBDb21wZXhpdHkgDQogICAgKyDmr4/lpJroqK3kuIDlgIvnr4Dpu57mnIPlop7liqDopIfpm5zluqbnmoTmiJDmnKzvvIzmiYDku6XpgI/pgY7oqK3lrppjcOWPg+aVuOS+humZkOWItuaoueeahOikh+mbnOW6pg0KICAgICsg5Y+v5Lul5Yip55SoQ1bpgbjmk4fmnIDkvbNjcA0KKyBQYXJhbWV0ZXIgVHVuaW5nIGJ5IENWIChjYXJldCBwYWNrYWdlKQ0KICAgICsg5Yip55SoQ3Jvc3MtVmFsaWRhdGlvbuS+humBuOaTh+acgOS9s+eahGNwDQogICAgKyDmiorljp/lp4t0cmFpbmluZyBkYXRh5YiG5oiQa+S7ve+8jOavj+asoei8qua1gemBuOaTh+S4gOS7veeVtuS9nHRlc3RpbmcgZGF0YeWFtuS7lmstMeS7veeVtuS9nHRyYWluaW5nIGRhdGHvvIzlu7rnq4vlpJrlgIvmqKHlnovkvobmib7mnIDlpb3nmoTlj4PmlbgNCg0KPGJyPg0KDQotIC0gLQ0KDQojIyMgMiBEMkhhd2tFeWUNCg0KIyMjIyAyLjEgUHJlcGFyZSAmIEV4YW1pbmUgRGF0YSANCmBgYHtyIHdhcm5pbmc9RiwgbWVzc2FnZT1GLCBlcnJvcj1GfQ0Kcm0obGlzdD1scyhhbGw9VFJVRSkpDQpEID0gcmVhZC5jc3YoImRhdGEvQ2xhaW1zRGF0YS5jc3YiKQ0KDQojIFBlcmNlbnRhZ2Ugb2YgcGF0aWVudHMgaW4gZWFjaCBjb3N0IGJ1Y2tldA0KdGFibGUoRCRidWNrZXQyMDA5KS9ucm93KEQpDQpgYGANCg0KYGBge3J9DQojIFNwbGl0IHRoZSBkYXRhDQpsaWJyYXJ5KGNhVG9vbHMpDQpzZXQuc2VlZCg4OCkNCnNwbCA9IHNhbXBsZS5zcGxpdChEJGJ1Y2tldDIwMDksIFNwbGl0UmF0aW8gPSAwLjYpDQpUUiA9IHN1YnNldChELCBzcGwpDQpUUyA9IHN1YnNldChELCAhc3BsKQ0KYGBgDQoNCmBgYHtyfQ0KIyBzYXBwbHkoKTogQXBwbHkgRnVuY3Rpb24gJiBBZ2dyZWdhdGUgT3V0cHV0DQpzYXBwbHkobGlzdChELCBUUiwgVFMpLCBmdW5jdGlvbih4KSANCiAgdGFibGUoeCRidWNrZXQyMDA5KSAlPiUgcHJvcC50YWJsZSkgJT4lIHQNCmBgYA0KDQpgYGB7cn0NCiMgRGF0YSBFeHBsb3JhdGlvbiANCm1lYW4oVFIkYWdlKSAgICAgICAgICAgICAgICAgIyA3Mi42NA0KbWVhbihUUiRkaWFiZXRlcykgICAgICAgICAgICAjIC4zODA5IA0KYGBgDQoNCiMjIyMgMi4yIFNtYXJ0IEJhc2VsaW5lDQpgYGB7cn0NCiMgQmFzZWxpbmUgQWNjdXJhY3kNCmFjYy5iYXNlID0gdGFibGUoVFMkYnVja2V0MjAwOSwgVFMkYnVja2V0MjAwOCkgJT4lIHtzdW0oZGlhZyguKSkgLyBzdW0oLil9DQphY2MuYmFzZSAgICMgYWNjLmJhc2UgPSAuNjgzOA0KYGBgDQoNCmBgYHtyfQ0KIyBQZW5hbHR5IE1hdHJpeA0KUGVuYWx0eSA9IG1hdHJpeChjKA0KICAwLDEsMiwzLDQsDQogIDIsMCwxLDIsMywNCiAgNCwyLDAsMSwyLA0KICA2LDQsMiwwLDEsDQogIDgsNiw0LDIsMCksIGJ5cm93PVRSVUUsIG5yb3c9NSkNClBlbmFsdHkNCmBgYA0KDQpgYGB7cn0NCiMgUGVuYWx0eSBFcnJvciBvZiBCYXNlbGluZSBNZXRob2QNCmNtID0gYXMubWF0cml4KHRhYmxlKFRTJGJ1Y2tldDIwMDksIFRTJGJ1Y2tldDIwMDgpKQ0KcGVuLmJhc2UgPSBzdW0oY20qUGVuYWx0eSkgLyBucm93KFRTKQ0KcGVuLmJhc2UgICMgcGVuLmJhc2U6IC43Mzg2DQpgYGANCg0KYGBge3J9DQojIER1bWIgYmFzZWxpbmUgLSBVc2luZyB0aGUgbW9zdCBmcmVxdWVudCBjbGFzcw0KYWNjLmR1bWIgPSAodGFibGUoVFMkYnVja2V0MjAwOSkvbnJvdyhUUykpWzFdICAgICAgICAgICAgICAgICANCnBlbi5kdW1iID0gc3VtKHRhYmxlKFRTJGJ1Y2tldDIwMDkpICogYygwLDIsNCw2LDgpKS9ucm93KFRTKSAgDQpjKGFjYy5kdW1iLCBwZW4uZHVtYikgICAjIDAuNjcxMyAxLjA0NDMNCmBgYA0KDQojIyMjIDIuMyBDQVJUIE1vZGVsDQpgYGB7cn0NCmxpYnJhcnkocnBhcnQpDQpsaWJyYXJ5KHJwYXJ0LnBsb3QpDQpycGFydDEgPSBycGFydChidWNrZXQyMDA5IH4gLiwgZGF0YT1UUltjKDE6MTQsIDE2KV0sIA0KICAgICAgICAgICAgICAgbWV0aG9kPSJjbGFzcyIsIGNwPTAuMDAwMDUpDQpwcnAocnBhcnQxKQ0KYGBgDQoNCmBgYHtyfQ0KIyBNYWtlIHByZWRpY3Rpb25zDQpwcmVkID0gcHJlZGljdChycGFydDEsIG5ld2RhdGEgPSBUUywgdHlwZSA9ICJjbGFzcyIpDQphY2MucnBhcnQxID0gdGFibGUoVFMkYnVja2V0MjAwOSwgcHJlZCkgJT4lIHsgc3VtKGRpYWcoLikpIC9ucm93KFRTKX0NCmFjYy5ycGFydDEgICAgIyBhY2MucnBhcnQxID0gLjcxMjY3IDwtIC42ODM4MQ0KY20gPSBhcy5tYXRyaXgodGFibGUoVFMkYnVja2V0MjAwOSwgcHJlZCkpDQpwZW4ucnBhcnQxID0gc3VtKGNtKlBlbmFsdHkpL25yb3coVFMpDQpwZW4ucnBhcnQxICAgICMgcGVuLnJwYXJ0MTogLjc1Nzg5IDwtIC43Mzg2DQpgYGANCg0KIyMjIyAyLjQgQ0FSVCBNb2RlbCB3aXRoIEN1c3RvbWl6ZWQgTG9zcyBNYXRyaXgNCmBgYHtyfQ0KcnBhcnQyID0gcnBhcnQoYnVja2V0MjAwOSB+IC4sIGRhdGE9VFJbYygxOjE0LCAxNildLCANCiAgICAgICAgICAgICAgIG1ldGhvZD0iY2xhc3MiLCBjcD0wLjAwMDA1LCANCiAgICAgICAgICAgICAgIHBhcm1zPWxpc3QobG9zcz1QZW5hbHR5KSApICAgICMgY3VzdG9taXplZCBsb3NzIGZ1bmN0aW9uICMgIyDljp/mnKzmsbrnrZbmqLnpgbjmk4dBQ0PmnIDpq5jnmoTntZDmnpzvvIzkvYblsIdwZW5hbHR555+p6Zmj5Yqg5YWl5Y+D5pW477yM5bCH5pyD6YG45pOHcGVuYWx0eeacgOS9jueahOaooeWei+OAgg0KICAgICAgICAgICAgICAgDQoNCnByZWQgPSBwcmVkaWN0KHJwYXJ0MiwgVFMsIHR5cGUgPSAiY2xhc3MiKQ0KYWNjLnJwYXJ0MiA9IHRhYmxlKFRTJGJ1Y2tldDIwMDksIHByZWQpICU+JSB7c3VtKGRpYWcoLikpL3N1bSguKX0NCmFjYy5ycGFydDIgICAgIyBhY2MucnBhcnQyID0gLjY0NzI3ICAgDQpjbSA9IGFzLm1hdHJpeCh0YWJsZShUUyRidWNrZXQyMDA5LCBwcmVkKSkNCnBlbi5ycGFydDIgPSBzdW0oY20qUGVuYWx0eSkvbnJvdyhUUykgICAgDQpwZW4ucnBhcnQyICAgICMgcGVuLnJwYXJ0MjogLjY0MTgyIA0KYGBgDQoNCmBgYHtyfQ0KIyBTdW1tYXJ5DQpkYXRhLmZyYW1lKA0KICBhY2N1cmFjeSA9IGMoYWNjLmR1bWIsIGFjYy5iYXNlLCBhY2MucnBhcnQxLCBhY2MucnBhcnQyKSwNCiAgcGVuYWx0eSA9IGMocGVuLmR1bWIsIHBlbi5iYXNlLCBwZW4ucnBhcnQxLCBwZW4ucnBhcnQyKSwNCiAgcm93Lm5hbWVzID0gYygiZHVtYiIsImJhc2UiLCJDQVJUIiwiQ0FSVCB3L0xvc3NNeCIpKQ0KYGBgDQo8YnI+DQoNCiMjIyMg44CQ5a2457+S6YeN6bue44CRDQoNCisgNSBjbGFzc2VzOiA1WDUgY29uZnVzc2lvbi9wZW5hbHR5KHBheW9mZikgbWF0cmljZXMgDQogICAgKyANCiAgICArDQorIGR1bWIgdnMuIHNtYXJ0IGJhc2VsaW5lDQogICAgKyANCiAgICArDQorIG1vZGVsMTogYWNjdXJhY3kgdXAsIHBlbmFsdHkgdXANCiAgICArIA0KICAgICsNCisgbW9kZWwyOiBhY2N1cmFjeSBkb3duLCBwZW5hbHR5IGRvd24NCiAgICArIA0KICAgICsNCisgYWNjdXJhY3kgIT0gcHJvZml0YWJpbGl0eQ0KICAgICsgDQogICAgKw0KKyAiY3VzdG9taXplZCIgbW9kZWwgb3B0aW1pemUgY3JpdGVyaWEgaW4gY2xhc3NpZmljYXRpb24gbWV0aG9kcyAobm90IGFsd2F5cyBhdmFpbGFibGUpDQogICAgKyANCiAgICArDQorIFRoaXMgQ1YvUFQgcHJvY2VzcyBpcyBvdmVyIHNpbXBsaWZpZWQNCiAgICArIA0KICAgICsNCisgd2Ugd2lsbCBjb3ZlciBhIGJldHRlciBwcm9jZXNzIG5leHQgdGltZQ0KICAgICsgDQogICAgKw0KDQoNCjxicj4NCg0KLSAtIC0NCg0KIyMjIDMgQm9zdG9uIEhvdXNlIFByaWNlDQoNCmBgYHtyfQ0Kcm0obGlzdD1scyhhbGw9VFJVRSkpDQpEID0gcmVhZC5jc3YoImRhdGEvYm9zdG9uLmNzdiIpDQpgYGANCg0KIyMjIyAzLjEgRXhhbWluZSBEYXRhDQpgYGB7cn0NCiMgUGxvdCBvYnNlcnZhdGlvbnMNCnBsb3QoRCRMT04sIEQkTEFUKQ0KDQojIFRyYWN0cyBhbG9uZ3NpZGUgdGhlIENoYXJsZXMgUml2ZXINCndpdGgoRCwgcG9pbnRzKExPTltDSEFTPT0xXSwgTEFUW0NIQVM9PTFdLCBjb2w9ImJsdWUiLCBwY2g9MTkpKQ0KcG9pbnRzKEQkTE9OW0QkQ0hBUz09MV0sIEQkTEFUW0QkQ0hBUz09MV0sIGNvbD0iYmx1ZSIsIHBjaD0xOSkNCg0KIyBQbG90IE1JVA0Kd2l0aChELCBwb2ludHMoTE9OW1RSQUNUPT0zNTMxXSwgTEFUW1RSQUNUPT0zNTMxXSwgY29sPSJyZWQiLCBwY2g9MTkpICkNCg0KIyBQbG90IHBvbHV0aW9uDQpzdW1tYXJ5KEQkTk9YKQ0KcG9pbnRzKEQkTE9OW0QkTk9YPj0wLjU1XSwgRCRMQVRbRCROT1g+PTAuNTVdLCBjb2w9ImdyZWVuIiwgcGNoPTIwKQ0KDQojIFBsb3QgcHJpY2VzDQpwbG90KEQkTE9OLCBEJExBVCkNCnN1bW1hcnkoRCRNRURWKQ0KcG9pbnRzKEQkTE9OW0QkTUVEVj49MjEuMl0sIEQkTEFUW0QkTUVEVj49MjEuMl0sIGNvbD0icmVkIiwgcGNoPTIwKQ0KYGBgDQoNCiMjIyMgMy4yIExpbmVhciBSZWdyZXNzaW9uIHVzaW5nIExBVCBhbmQgTE9ODQpgYGB7cn0NCmxhdGxvbmxtID0gbG0oTUVEViB+IExBVCArIExPTiwgZGF0YT1EKQ0Kc3VtbWFyeShsYXRsb25sbSkNCmBgYA0KDQojIyMjIDMuMyBWaXN1YWxpemUgcmVncmVzc2lvbiBvdXRwdXQNCmBgYHtyfQ0KcGxvdChEJExPTiwgRCRMQVQpDQpwb2ludHMoRCRMT05bRCRNRURWPj0yMS4yXSwgRCRMQVRbRCRNRURWPj0yMS4yXSwgY29sPSJyZWQiLCBwY2g9MjApDQoNCiMgbGF0bG9ubG0kZml0dGVkLnZhbHVlcw0KcG9pbnRzKEQkTE9OW2xhdGxvbmxtJGZpdHRlZC52YWx1ZXMgPj0gMjEuMl0sIA0KICAgICAgIEQkTEFUW2xhdGxvbmxtJGZpdHRlZC52YWx1ZXMgPj0gMjEuMl0sIA0KICAgICAgIGNvbD0iYmx1ZSIsIHBjaD0ieCIpDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KIyBDQVJUIG1vZGVsDQpsYXRsb250cmVlID0gcnBhcnQoTUVEViB+IExBVCArIExPTiwgZGF0YT1EKQ0KcHJwKGxhdGxvbnRyZWUpDQoNCiMgVmlzdWFsaXplIG91dHB1dA0KcGxvdChEJExPTiwgRCRMQVQpDQpwb2ludHMoRCRMT05bRCRNRURWPj0yMS4yXSwgRCRMQVRbRCRNRURWPj0yMS4yXSwgDQogICAgICAgY29sPSJyZWQiLCBwY2g9MjApDQoNCmZpdHRlZHZhbHVlcyA9IHByZWRpY3QobGF0bG9udHJlZSkNCnBvaW50cyhEJExPTltmaXR0ZWR2YWx1ZXM+MjEuMl0sIA0KICAgICAgIEQkTEFUW2ZpdHRlZHZhbHVlcz49MjEuMl0sIGNvbD0iYmx1ZSIsIHBjaD0ieCIpDQpgYGANCg0KIyMjIyAzLjQgU2ltcGxpZnkgdHJlZSBieSBpbmNyZWFzaW5nIG1pbmJ1Y2tldA0KYGBge3J9DQpsYXRsb250cmVlID0gcnBhcnQoTUVEViB+IExBVCArIExPTiwgZGF0YT1ELCBtaW5idWNrZXQ9NTApDQpycGFydC5wbG90KGxhdGxvbnRyZWUpDQpgYGANCg0KYGBge3J9DQojIFZpc3VhbGl6ZSBPdXRwdXQNCnBsb3QoRCRMT04sRCRMQVQpDQphYmxpbmUodj0tNzEuMDcpDQphYmxpbmUoaD00Mi4yMSkNCmFibGluZShoPTQyLjE3KQ0KcG9pbnRzKEQkTE9OW0QkTUVEVj49MjEuMl0sIA0KICAgICAgIEQkTEFUW0QkTUVEVj49MjEuMl0sIGNvbD0icmVkIiwgcGNoPTIwKQ0KYGBgDQoNCiMjIyMgMy41IFVzZSBhbGwgdGhlIHZhcmlhYmxlcw0KYGBge3J9DQojIFNwbGl0IHRoZSBkYXRhDQpsaWJyYXJ5KGNhVG9vbHMpDQpzZXQuc2VlZCgxMjMpDQpzcGxpdCA9IHNhbXBsZS5zcGxpdChEJE1FRFYsIFNwbGl0UmF0aW8gPSAwLjcpDQp0cmFpbiA9IHN1YnNldChELCBzcGxpdD09VFJVRSkNCnRlc3QgPSBzdWJzZXQoRCwgc3BsaXQ9PUZBTFNFKQ0KDQojIENyZWF0ZSBsaW5lYXIgcmVncmVzc2lvbg0KbGlucmVnID0gbG0oTUVEViB+IExBVCArIExPTiArIENSSU0gKyBaTiArIElORFVTICsgQ0hBUyArIE5PWCArIFJNICsgDQogICAgICAgICAgICAgIEFHRSArIERJUyArIFJBRCArIFRBWCArIFBUUkFUSU8sIGRhdGE9dHJhaW4pDQpzdW1tYXJ5KGxpbnJlZykNCg0KIyBNYWtlIHByZWRpY3Rpb25zDQpsaW5yZWcucHJlZCA9IHByZWRpY3QobGlucmVnLCBuZXdkYXRhPXRlc3QpDQpsaW5yZWcuc3NlID0gc3VtKChsaW5yZWcucHJlZCAtIHRlc3QkTUVEVileMikNCmxpbnJlZy5zc2UNCmBgYA0KDQojIyMjIDMuNiBDcmVhdGUgYSBDQVJUIG1vZGVsDQpgYGB7cn0NCnRyZWUgPSBycGFydChNRURWIH4gTEFUICsgTE9OICsgQ1JJTSArIFpOICsgSU5EVVMgKyBDSEFTICsgTk9YICsgUk0gKyANCiAgICAgICAgICAgICAgIEFHRSArIERJUyArIFJBRCArIFRBWCArIFBUUkFUSU8sIGRhdGE9dHJhaW4pDQpwcnAodHJlZSkNCg0KIyBNYWtlIHByZWRpY3Rpb25zDQp0cmVlLnByZWQgPSBwcmVkaWN0KHRyZWUsIG5ld2RhdGE9dGVzdCkNCnRyZWUuc3NlID0gc3VtKCh0cmVlLnByZWQgLSB0ZXN0JE1FRFYpXjIpDQp0cmVlLnNzZQ0KYGBgDQoNCiMjIyMgMy43IExvYWQgbGlicmFyaWVzIGZvciBjcm9zcy12YWxpZGF0aW9uDQpgYGB7cn0NCmxpYnJhcnkoY2FyZXQpDQoNCiMgTnVtYmVyIG9mIGZvbGRzDQp0ci5jb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQ0KDQojIGNwIHZhbHVlcw0KY3AuZ3JpZCA9IGV4cGFuZC5ncmlkKCAuY3AgPSAoMDoxMCkqMC4wMDEpDQoNCiMgQ3Jvc3MtdmFsaWRhdGlvbg0KdHIgPSB0cmFpbihNRURWIH4gTEFUICsgTE9OICsgQ1JJTSArIFpOICsgSU5EVVMgKyBDSEFTICsgTk9YICsgUk0gKyANCiAgICAgICAgICAgICBBR0UgKyBESVMgKyBSQUQgKyBUQVggKyBQVFJBVElPLCANCiAgICAgICAgICAgZGF0YSA9IHRyYWluLCBtZXRob2QgPSAicnBhcnQiLCANCiAgICAgICAgICAgdHJDb250cm9sID0gdHIuY29udHJvbCwgdHVuZUdyaWQgPSBjcC5ncmlkKQ0KdHINCg0KIyBFeHRyYWN0IHRyZWUNCmJlc3QudHJlZSA9IHRyJGZpbmFsTW9kZWwNCnBycChiZXN0LnRyZWUpDQoNCiMgTWFrZSBwcmVkaWN0aW9ucw0KYmVzdC50cmVlLnByZWQgPSBwcmVkaWN0KGJlc3QudHJlZSwgbmV3ZGF0YT10ZXN0KQ0KYmVzdC50cmVlLnNzZSA9IHN1bSgoYmVzdC50cmVlLnByZWQgLSB0ZXN0JE1FRFYpXjIpDQpiZXN0LnRyZWUuc3NlDQpgYGANCg0KIyMjIyAzLjggRW5zZW1ibGluZw0KYGBge3J9DQplbi5wcmVkID0gKGJlc3QudHJlZS5wcmVkICsgbGlucmVnLnByZWQpLzINCnN1bSgoZW4ucHJlZCAtIHRlc3QkTUVEVileMikNCmBgYA0KDQoNCmBgYHtyfQ0KIyAgICAgICBNRVRIT0QJIFNTRQ0KIyAgICAgICAgICAgbG0JMzAzNw0KIyAgICAgICAgIHJwYXJ0CTQzMjkNCiMgICAgICBycGFydC5jdgkzNjYwDQojICAgbG0rcnBhcnQuY3YJMjU5OSAgDQpgYGANCjxicj4NCg0KIyMjIyDjgJDlrbjnv5Lph43pu57jgJENCg0KKyBkcmF3aW5nIG1hcCB3aXRoIHNwYXRpYWwgZGF0YSANCiAgICArDQogICAgKw0KKyB0cmVlIGluIHNwYXRpYWwgZGF0YQ0KICAgICsNCiAgICArDQorIHRyZWUgdnMuIGxpbmVhciByZWdyZXNzaW9uDQogICAgKw0KICAgICsNCisgdGhlIGNvbmNlcHQgb2YgZW5zZW1ibGluZw0KICAgICsNCiAgICArDQoNCjxicj4NCg0KLSAtIC0NCg0KPGJyPjxicj48YnI+PGJyPjxicj4NCg0KPHN0eWxlPg0KLmNhcHRpb24gew0KICBjb2xvcjogIzc3NzsNCiAgbWFyZ2luLXRvcDogMTBweDsNCn0NCnAgY29kZSB7DQogIHdoaXRlLXNwYWNlOiBpbmhlcml0Ow0KfQ0KcHJlIHsNCiAgd29yZC1icmVhazogbm9ybWFsOw0KICB3b3JkLXdyYXA6IG5vcm1hbDsNCiAgbGluZS1oZWlnaHQ6IDE7DQp9DQpwcmUgY29kZSB7DQogIHdoaXRlLXNwYWNlOiBpbmhlcml0Ow0KfQ0KcCxsaSB7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQoucnsNCiAgbGluZS1oZWlnaHQ6IDEuMjsNCn0NCg0KdGl0bGV7DQogIGNvbG9yOiAjY2MwMDAwOw0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KYm9keXsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCmgxLGgyLGgzLGg0LGg1ew0KICBjb2xvcjogIzAwNjZmZjsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCmg0LGg1ew0KICBiYWNrZ3JvdW5kOiAjY2NmZmZmOw0KfQ0KDQo8L3N0eWxlPg0KDQoNCg0KDQoNCg0K