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("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)
Bad 'data' field in model 'call' field.
         To make this warning go away:
             Call prp with roundint=FALSE,
             or rebuild the rpart model with model=TRUE.

rpart.plot(rpart1,cex=0.6)
Bad 'data' field in model 'call' field.
         To make this warning go away:
             Call rpart.plot with roundint=FALSE,
             or rebuild the rpart model with model=TRUE.

1.4 Make Prediction

pred = predict(rpart1, TS, type = "class")  # predict classes
x = table(actual = TS$Reverse, pred); x
      pred
actual  0  1
     0 41 36
     1 22 71
sum(diag(x))/sum(x)                         # .65882
[1] 0.65882

1.5 ROC & AUC

library(ROCR)
PredictROC = predict(rpart1, TS)              # predict prob.
head(PredictROC)
         0       1
1  0.30357 0.69643
3  0.30357 0.69643
4  0.40000 0.60000
6  0.40000 0.60000
8  0.40000 0.60000
21 0.30357 0.69643
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]
0 vs. 1 0.69271

1.6 DPP of Decision Tree

rpart.plot(rpart1,cex=0.75,varlen=3,faclen=2,box.palette="GnRd")
Bad 'data' field in model 'call' field.
         To make this warning go away:
             Call rpart.plot with roundint=FALSE,
             or rebuild the rpart model with model=TRUE.

source("~/big data/Unit4/CD4/DPP.R")
auc = DPP(predict(rpart1)[,2], TR$Reverse, 1)     # AUC = 0.7884

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

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

  • 1.最終分類的情況
  • 2.位置:越往右邊,預測紅結果為紅的機率越高;反之,越往左邊,預測紅為紅的機率越低。高度:代表分類的情況。


1.7 The effect of minbucket

t5 = rpart(Reverse ~ ., TR[,3:9], method="class", minbucket=5)
prp(t5)
Bad 'data' field in model 'call' field.
         To make this warning go away:
             Call prp with roundint=FALSE,
             or rebuild the rpart model with model=TRUE.

t100 = rpart(Reverse ~ ., TR[,3:9], method="class", minbucket=100)
prp(t100)
Bad 'data' field in model 'call' field.
         To make this warning go away:
             Call prp with roundint=FALSE,
             or rebuild the rpart model with model=TRUE.

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

  • minbucket是每個葉節點所含最小樣本數,故如果minbucket越小可以分越多類;越多則越少類。


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
[1] 0.68235
pred = predict(rf1, TS, type='prob')[,2]
table(TS$Reverse, pred > 0.5) %>% {sum(diag(.))/sum(.)}
[1] 0.68824

1.9 Cross Validation & Parameter Tuning - caret package

library(caret)
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
  input string 1 is invalid in this locale
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)
CART 

396 samples
  6 predictor
  2 classes: '0', '1' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 357, 356, 356, 356, 357, 356, ... 
Resampling results across tuning parameters:

  cp    Accuracy  Kappa   
  0.01  0.61359   0.207035
  0.02  0.61365   0.206251
  0.03  0.62385   0.231274
  0.04  0.63141   0.256349
  0.05  0.64410   0.284019
  0.06  0.64410   0.284019
  0.07  0.64410   0.284019
  0.08  0.64410   0.284019
  0.09  0.64410   0.284019
  0.10  0.64410   0.284019
  0.11  0.64410   0.284019
  0.12  0.64410   0.284019
  0.13  0.64410   0.284019
  0.14  0.64410   0.284019
  0.15  0.64410   0.284019
  0.16  0.64410   0.284019
  0.17  0.64410   0.284019
  0.18  0.64410   0.284019
  0.19  0.62103   0.230632
  0.20  0.58301   0.137651
  0.21  0.58301   0.137651
  0.22  0.55301   0.048387
  0.23  0.54788   0.032258
  0.24  0.54538   0.017685
  0.25  0.54538   0.000000
  0.26  0.54538   0.000000
  0.27  0.54538   0.000000
  0.28  0.54538   0.000000
  0.29  0.54538   0.000000
  0.30  0.54538   0.000000
  0.31  0.54538   0.000000
  0.32  0.54538   0.000000
  0.33  0.54538   0.000000
  0.34  0.54538   0.000000
  0.35  0.54538   0.000000
  0.36  0.54538   0.000000
  0.37  0.54538   0.000000
  0.38  0.54538   0.000000
  0.39  0.54538   0.000000
  0.40  0.54538   0.000000
  0.41  0.54538   0.000000
  0.42  0.54538   0.000000
  0.43  0.54538   0.000000
  0.44  0.54538   0.000000
  0.45  0.54538   0.000000
  0.46  0.54538   0.000000
  0.47  0.54538   0.000000
  0.48  0.54538   0.000000
  0.49  0.54538   0.000000
  0.50  0.54538   0.000000

Accuracy was used to select the optimal model using the largest value.
The final value used for the model was cp = 0.18.

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

  • 找出accuracy最高的cp,並從中找出cp最大的,故選0.18,因為cp越大代表複雜度越低。


1.10 The Final Model

rpart2 = rpart(Reverse ~ ., TR[,3:9], method="class", cp=0.18)
pred = predict(rpart2, TS, type='prob')[,2]
table(TS$Reverse, pred > 0.5) %>% {sum(diag(.)) / sum(.)}  # 0.7235
[1] 0.72353
rpart.plot(rpart2)
Bad 'data' field in model 'call' field.
         To make this warning go away:
             Call rpart.plot with roundint=FALSE,
             or rebuild the rpart model with model=TRUE.


【學習重點】

  • Classification & Regression Tree rpart +學習用rpart去做分類樹和迴歸樹的運用。
  • A Bag of Tree - Random Forest randomForest
    +學習隨機森林模型。 +比較跟決策樹的差異。
  • Overfitting : Curse of Complexity +模型越複雜,越可能發生過度配適的問題。
  • Method Parameter : Cost of Compexity +使用cp來觀察模型的複雜度。
  • Parameter Tuning by CV (caret package) +利用train函數進行參數調整。 +。



2 D2HawkEye

2.1 Prepare & Examine Data

rm(list=ls(all=TRUE))
D = read.csv("ClaimsData.csv")
# Percentage of patients in each cost bucket
table(D$bucket2009)/nrow(D)

        1         2         3         4         5 
0.6712678 0.1901704 0.0894663 0.0433249 0.0057707 
# 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
           1       2        3        4         5
[1,] 0.67127 0.19017 0.089466 0.043325 0.0057707
[2,] 0.67127 0.19017 0.089468 0.043326 0.0057714
[3,] 0.67127 0.19017 0.089464 0.043324 0.0057696
# Data Exploration 
mean(TR$age)                 # 72.64
[1] 72.638
mean(TR$diabetes)            # .3809 
[1] 0.3809

2.2 Smart Baseline

# Baseline Accuracy
acc.base = table(TS$bucket2009, TS$bucket2008) %>% {sum(diag(.)) / sum(.)}
acc.base   # acc.base = .6838
[1] 0.68381
# 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
     [,1] [,2] [,3] [,4] [,5]
[1,]    0    1    2    3    4
[2,]    2    0    1    2    3
[3,]    4    2    0    1    2
[4,]    6    4    2    0    1
[5,]    8    6    4    2    0
# 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
[1] 0.73861
# Dumb baseline - Using the most frequent class
acc.dumb = (table(TS$bucket2009)/nrow(TS))[1]                 
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
  input string 1 is invalid in this locale
pen.dumb = sum(table(TS$bucket2009) * c(0,2,4,6,8))/nrow(TS)  
c(acc.dumb, pen.dumb)   # 0.6713 1.0443
      1         
0.67127 1.04430 
#看模型好壞要加入penalty matrix,其越低越好

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)
Bad 'data' field in model 'call' field.
         To make this warning go away:
             Call prp with roundint=FALSE,
             or rebuild the rpart model with model=TRUE.

# 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
[1] 0.71267
cm = as.matrix(table(TS$bucket2009, pred))
pen.rpart1 = sum(cm*Penalty)/nrow(TS)
pen.rpart1    # pen.rpart1: .75789 <- .7386
[1] 0.75789

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 
               )
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
  input string 1 is invalid in this locale
#尋求最低的penalty,而非最高的accuracy
pred = predict(rpart2, TS, type = "class")
acc.rpart2 = table(TS$bucket2009, pred) %>% {sum(diag(.))/sum(.)}
acc.rpart2    # acc.rpart2 = .64727   
[1] 0.64727
cm = as.matrix(table(TS$bucket2009, pred))
pen.rpart2 = sum(cm*Penalty)/nrow(TS)    
pen.rpart2    # pen.rpart2: .64182 
[1] 0.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("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)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.385   0.449   0.538   0.555   0.624   0.871 
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)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    5.0    17.0    21.2    22.5    25.0    50.0 
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)

Call:
lm(formula = MEDV ~ LAT + LON, data = D)

Residuals:
   Min     1Q Median     3Q    Max 
-16.46  -5.59  -1.30   3.69  28.13 

Coefficients:
            Estimate Std. Error t value          Pr(>|t|)    
(Intercept) -3178.47     484.94   -6.55 0.000000000138619 ***
LAT             8.05       6.33    1.27               0.2    
LON           -40.27       5.18   -7.77 0.000000000000045 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 8.69 on 503 degrees of freedom
Multiple R-squared:  0.107, Adjusted R-squared:  0.104 
F-statistic: 30.2 on 2 and 503 DF,  p-value: 0.000000000000416

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)

Call:
lm(formula = MEDV ~ LAT + LON + CRIM + ZN + INDUS + CHAS + NOX + 
    RM + AGE + DIS + RAD + TAX + PTRATIO, data = train)

Residuals:
   Min     1Q Median     3Q    Max 
-14.51  -2.71  -0.68   1.79  36.88 

Coefficients:
              Estimate Std. Error t value    Pr(>|t|)    
(Intercept) -252.29051  436.71008   -0.58       0.564    
LAT            1.54396    5.19196    0.30       0.766    
LON           -2.98711    4.78583   -0.62       0.533    
CRIM          -0.18080    0.04391   -4.12 0.000047725 ***
ZN             0.03250    0.01877    1.73       0.084 .  
INDUS         -0.04297    0.08473   -0.51       0.612    
CHAS           2.90418    1.22005    2.38       0.018 *  
NOX          -21.61279    5.41371   -3.99 0.000079778 ***
RM             6.28440    0.48270   13.02     < 2e-16 ***
AGE           -0.04430    0.01785   -2.48       0.014 *  
DIS           -1.57736    0.28418   -5.55 0.000000056 ***
RAD            0.24509    0.09728    2.52       0.012 *  
TAX           -0.01112    0.00545   -2.04       0.042 *  
PTRATIO       -0.98349    0.19389   -5.07 0.000000638 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 5.6 on 350 degrees of freedom
Multiple R-squared:  0.665, Adjusted R-squared:  0.653 
F-statistic: 53.4 on 13 and 350 DF,  p-value: <2e-16
# Make predictions
linreg.pred = predict(linreg, newdata=test)
linreg.sse = sum((linreg.pred - test$MEDV)^2)
linreg.sse
[1] 3037.1

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
[1] 4329

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
CART 

364 samples
 13 predictor

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 327, 326, 328, 329, 328, 327, ... 
Resampling results across tuning parameters:

  cp     RMSE    Rsquared  MAE   
  0.000  4.7290  0.75979   3.0684
  0.001  4.7446  0.75837   3.1046
  0.002  4.7563  0.75688   3.0909
  0.003  4.8067  0.74908   3.1510
  0.004  4.8756  0.74179   3.2299
  0.005  4.9174  0.73682   3.2811
  0.006  4.9539  0.73155   3.3217
  0.007  4.8985  0.73604   3.2903
  0.008  4.9334  0.72974   3.3231
  0.009  4.9056  0.73195   3.3081
  0.010  4.9056  0.73195   3.3081

RMSE was used to select the optimal model using the smallest value.
The final value used for the model was cp = 0.
# Extract tree
best.tree = tr$finalModel
prp(best.tree)
Bad 'data' field in model 'call' field.
         To make this warning go away:
             Call prp with roundint=FALSE,
             or rebuild the rpart model with model=TRUE.

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

3.8 Ensembling

en.pred = (best.tree.pred + linreg.pred)/2
sum((en.pred - test$MEDV)^2)
[1] 2598.6
#       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








LS0tDQp0aXRsZTogIkFTNC0wIOiqsuWgguethuiomCINCmF1dGhvcjogIkdyb3VwMyDlkLPmmIfmrL0gTTA2NDAzMDAxMiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCmBgYHtyfQ0KcGFja2FnZXMgPSBjKA0KICAiZHBseXIiLCJnZ3Bsb3QyIiwiZDNoZWF0bWFwIiwiZ29vZ2xlVmlzIiwiZGV2dG9vbHMiLCJwbG90bHkiLCAieGdib29zdCIsDQogICJtYWdyaXR0ciIsImNhVG9vbHMiLCJST0NSIiwiY29ycnBsb3QiLCAicnBhcnQiLCAicnBhcnQucGxvdCIsDQogICJkb1BhcmFsbGVsIiwgImNhcmV0IiwgImdsbW5ldCIsICJNYXRyaXgiLCAiZTEwNzEiLCAicmFuZG9tRm9yZXN0Ig0KICApDQpleGlzdGluZyA9IGFzLmNoYXJhY3RlcihpbnN0YWxsZWQucGFja2FnZXMoKVssMV0pDQpmb3IocGtnIGluIHBhY2thZ2VzWyEocGFja2FnZXMgJWluJSBleGlzdGluZyldKSBpbnN0YWxsLnBhY2thZ2VzKHBrZykNCmBgYA0KDQpgYGB7ciBlY2hvPVQsIG1lc3NhZ2U9RiwgY2FjaGU9Riwgd2FybmluZz1GfQ0Kcm0obGlzdD1scyhhbGw9VCkpDQpvcHRpb25zKGRpZ2l0cz01LCBzY2lwZW49MTIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShjYVRvb2xzKQ0KbGlicmFyeShycGFydCkNCmxpYnJhcnkocnBhcnQucGxvdCkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KYGBgDQoNCi0gLSAtDQoNCiMjIyAxLiBTdXByaW1lIENvdXJ0IERlY2lzaW9uDQoNCiMjIyMgMS4xIFByZXBhcmUgRGF0YQ0KYGBge3J9DQpEID0gcmVhZC5jc3YoInN0ZXZlbnMuY3N2IikNCg0KbGlicmFyeShjYVRvb2xzKQ0Kc2V0LnNlZWQoMzAwMCkNCnNwbCA9IHNhbXBsZS5zcGxpdChEJFJldmVyc2UsIFNwbGl0UmF0aW8gPSAwLjcpDQpUUiA9IHN1YnNldChELCBzcGwpDQpUUyA9IHN1YnNldChELCAhc3BsKQ0KYGBgDQoNCiMjIyMgMS4yIENBUlQgKENsYXNzaWZpY2F0aW9uICYgUmVncmVzc2lvbiBUcmVlKSAtIGBycGFydDo6cnBhcnQoKWANCmBgYHtyfQ0KbGlicmFyeShycGFydCkNCnJwYXJ0MSA9IHJwYXJ0KFJldmVyc2UgfiAuLCBUUlssMzo5XSwgICAgICAgICAgIyBzaW1wbGlmeSB0aGUgZm9ybXVsYQ0KICAgICAgICAgICAgICAgbWV0aG9kPSJjbGFzcyIsIG1pbmJ1Y2tldD0yNSkNCmBgYA0KDQojIyMjIDEuMyBQbG90IERlY2lzaW9uIFRyZWUNCmBgYHtyfQ0KbGlicmFyeShycGFydC5wbG90KQ0KcHJwKHJwYXJ0MSkNCnJwYXJ0LnBsb3QocnBhcnQxLGNleD0wLjYpDQpgYGANCg0KIyMjIyAxLjQgTWFrZSBQcmVkaWN0aW9uDQpgYGB7cn0NCnByZWQgPSBwcmVkaWN0KHJwYXJ0MSwgVFMsIHR5cGUgPSAiY2xhc3MiKSAgIyBwcmVkaWN0IGNsYXNzZXMNCnggPSB0YWJsZShhY3R1YWwgPSBUUyRSZXZlcnNlLCBwcmVkKTsgeA0Kc3VtKGRpYWcoeCkpL3N1bSh4KSAgICAgICAgICAgICAgICAgICAgICAgICAjIC42NTg4Mg0KYGBgDQoNCiMjIyMgMS41IFJPQyAmIEFVQw0KYGBge3J9DQpsaWJyYXJ5KFJPQ1IpDQpQcmVkaWN0Uk9DID0gcHJlZGljdChycGFydDEsIFRTKSAgICAgICAgICAgICAgIyBwcmVkaWN0IHByb2IuDQpoZWFkKFByZWRpY3RST0MpDQpwZXJmID0gcHJlZGljdGlvbihQcmVkaWN0Uk9DWywyXSwgVFMkUmV2ZXJzZSkNCnBlcmYgPSBwZXJmb3JtYW5jZShwZXJmLCAidHByIiwgImZwciIpDQpwbG90KHBlcmYpDQpgYGANCg0KYGBge3J9DQpwcmVkID0gcHJlZGljdChycGFydDEsIFRTKVssMl0gICAgICMgcHJvYi4gb2YgUmV2ZXJzZSA9IDEgICAgICAgICANCmNvbEFVQyhwcmVkLCBUUyRSZXZlcnNlLCBUKSAgICAgICAgIyBBVUMgPSAwLjY5MjcNCmBgYA0KDQoNCiMjIyMgMS42IERQUCBvZiBEZWNpc2lvbiBUcmVlDQoNCmBgYHtyfQ0KcnBhcnQucGxvdChycGFydDEsY2V4PTAuNzUsdmFybGVuPTMsZmFjbGVuPTIsYm94LnBhbGV0dGU9IkduUmQiKQ0KYGBgDQoNCg0KYGBge3J9DQpzb3VyY2UoIn4vYmlnIGRhdGEvVW5pdDQvQ0Q0L0RQUC5SIikNCmF1YyA9IERQUChwcmVkaWN0KHJwYXJ0MSlbLDJdLCBUUiRSZXZlcnNlLCAxKSAgICAgIyBBVUMgPSAwLjc4ODQNCmBgYA0KDQojIyMjIOOAkFFVSVotMeOAkeavlOi8g+S7peS4imBwcnAoKWDjgIFgcnBhcnQucGxvdCgpYOWSjGBEUFAoKWDpgJnlub7lvLXlnJblvaINCg0KKyDkuIDmo7XmsbrnrZbmqLnnmoTlvaLni4DvvIwg5ZKM55Sx5a6D5omA55Si55Sf55qE6aCQ5ris5qmf546H5YiG5biD5LmL6ZaT77yM5pyJ5LuA6bq86Zec5L+C5ZGiID8gIOS7peS4ikRQUCDlnJbkuK3nmoTliIbluIPpu57mlbjjgIHkvY3nva7lkozpq5jluqbvvIzliIbliKXmnIPlsI3mh4nliLDmsbrnrZbmqLnnmoTku4DpurznibnlvrXlkaI/DQoNCisgMS7mnIDntYLliIbpoZ7nmoTmg4Xms4ENCisgMi7kvY3nva466LaK5b6A5Y+z6YKK77yM6aCQ5ris57SF57WQ5p6c54K657SF55qE5qmf546H6LaK6auYO+WPjeS5i++8jOi2iuW+gOW3pumCiu+8jOmgkOa4rOe0heeCuue0heeahOapn+eOh+i2iuS9juOAgumrmOW6pjrku6PooajliIbpoZ7nmoTmg4Xms4HjgIINCg0KDQo8YnI+DQoNCiMjIyMgMS43IFRoZSBlZmZlY3Qgb2YgYG1pbmJ1Y2tldGANCmBgYHtyfQ0KdDUgPSBycGFydChSZXZlcnNlIH4gLiwgVFJbLDM6OV0sIG1ldGhvZD0iY2xhc3MiLCBtaW5idWNrZXQ9NSkNCnBycCh0NSkNCmBgYA0KDQpgYGB7ciBmaWcuaGVpZ2h0PTJ9DQp0MTAwID0gcnBhcnQoUmV2ZXJzZSB+IC4sIFRSWywzOjldLCBtZXRob2Q9ImNsYXNzIiwgbWluYnVja2V0PTEwMCkNCnBycCh0MTAwKQ0KYGBgDQoNCg0KIyMjI+OAkFFVSVotMuOAkUJhc2VkIG9uIHRoZSAyIHBsb3RzIGFib3ZlLCB3aGF0IGlzIHRoZSBlZmZlY3Qgb2YgYG1pbmJ1Y2tldGA/IA0KDQorIG1pbmJ1Y2tldOaYr+avj+WAi+iRieevgOm7nuaJgOWQq+acgOWwj+aoo+acrOaVuO+8jOaVheWmguaenG1pbmJ1Y2tldOi2iuWwj+WPr+S7peWIhui2iuWkmumhnjvotorlpJrliYfotorlsJHpoZ7jgIINCg0KPGJyPg0KDQojIyMjIDEuOCBSYW5kb20gRm9yZXN0IE1ldGhvZCAtIGByYW5kb21Gb3Jlc3Q6OnJhbmRvbUZvcmVzdCgpYA0KYGBge3J9DQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNClRSJFJldmVyc2UgPSBhcy5mYWN0b3IoVFIkUmV2ZXJzZSkgICMgQ29udmVydCBvdXRjb21lIHRvIGZhY3Rvcg0KVFMkUmV2ZXJzZSA9IGFzLmZhY3RvcihUUyRSZXZlcnNlKQ0KDQojIEJ1aWxkIG1vZGVsDQpyZjEgPSByYW5kb21Gb3Jlc3QoUmV2ZXJzZSB+IC4sIFRSWywzOjldLCBudHJlZT0yMDAsIG5vZGVzaXplPTI1KQ0KDQojIE1ha2UgcHJlZGljdGlvbnMNCnByZWQgPSBwcmVkaWN0KHJmMSwgVFMpDQp4ID0gdGFibGUoVFMkUmV2ZXJzZSwgcHJlZCkNCnN1bShkaWFnKHgpKSAvIHN1bSh4KSAgICAgICAgICAjIDAuNjgyNA0KYGBgDQoNCmBgYHtyfQ0KcHJlZCA9IHByZWRpY3QocmYxLCBUUywgdHlwZT0ncHJvYicpWywyXQ0KdGFibGUoVFMkUmV2ZXJzZSwgcHJlZCA+IDAuNSkgJT4lIHtzdW0oZGlhZyguKSkvc3VtKC4pfQ0KYGBgDQoNCg0KIyMjIyAxLjkgQ3Jvc3MgVmFsaWRhdGlvbiAmIFBhcmFtZXRlciBUdW5pbmcgLSBgY2FyZXRgIHBhY2thZ2UNCmBgYHtyfQ0KbGlicmFyeShjYXJldCkNCg0KY3YxID0gdHJhaW4oDQogIFJldmVyc2UgfiAuLCBUUlssMzo5XSwgbWV0aG9kID0gInJwYXJ0IiwgDQogIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2Q9ImN2IiwgbnVtYmVyPTEwKSwgIyAxMCBmb2xkIENWDQogIHR1bmVHcmlkPWV4cGFuZC5ncmlkKGNwID0gc2VxKDAuMDEsMC41LDAuMDEpKSAgICAgIyBwYXJhbWV0ZXIgY29tYmluYXRpb24NCiAgKQ0KY3YxOyBwbG90KGN2MSkNCiPlsI/mqKPmnKzkvb/nlKhyZXBlYXRjdixyZXBlYXQ9NCA7IGV4cGFuZC5ncmlk6KOh6Z2i5Y+v5Lul5pS+5b6I5aSa5pa55rOV5Y+D5pW4DQpgYGANCg0KIyMjI+OAkFFVSVotM+OAkVdoaWNoIGBjcGAgd2hpY2ggd2Ugc2hvdWxkIHdlIGNob29zZT8gV2h5PyANCg0KKyDmib7lh7phY2N1cmFjeeacgOmrmOeahGNw77yM5Lim5b6e5Lit5om+5Ye6Y3DmnIDlpKfnmoTvvIzmlYXpgbgwLjE477yM5Zug54K6Y3DotorlpKfku6PooajopIfpm5zluqbotorkvY7jgIINCg0KPGJyPg0KDQojIyMjIDEuMTAgVGhlIEZpbmFsIE1vZGVsDQpgYGB7cn0NCnJwYXJ0MiA9IHJwYXJ0KFJldmVyc2UgfiAuLCBUUlssMzo5XSwgbWV0aG9kPSJjbGFzcyIsIGNwPTAuMTgpDQpwcmVkID0gcHJlZGljdChycGFydDIsIFRTLCB0eXBlPSdwcm9iJylbLDJdDQp0YWJsZShUUyRSZXZlcnNlLCBwcmVkID4gMC41KSAlPiUge3N1bShkaWFnKC4pKSAvIHN1bSguKX0gICMgMC43MjM1DQpgYGANCg0KYGBge3IgZmlnLndpZHRoPTN9DQpycGFydC5wbG90KHJwYXJ0MikNCmBgYA0KPGJyPg0KDQojIyMj44CQ5a2457+S6YeN6bue44CRDQoNCisgQ2xhc3NpZmljYXRpb24gJiBSZWdyZXNzaW9uIFRyZWUgIGBycGFydGANCiAgICAr5a2457+S55SocnBhcnTljrvlgZrliIbpoZ7mqLnlkozov7TmrbjmqLnnmoTpgYvnlKjjgIINCiAgICArDQorIEEgQmFnIG9mIFRyZWUgLSBSYW5kb20gRm9yZXN0IGByYW5kb21Gb3Jlc3RgICANCiAgICAr5a2457+S6Zqo5qmf5qOu5p6X5qih5Z6L44CCDQogICAgK+avlOi8g+i3n+axuuetluaoueeahOW3rueVsOOAgg0KKyBPdmVyZml0dGluZyA6IEN1cnNlIG9mIENvbXBsZXhpdHkNCiAgICAr5qih5Z6L6LaK6KSH6Zuc77yM6LaK5Y+v6IO955m855Sf6YGO5bqm6YWN6YGp55qE5ZWP6aGM44CCDQogICAgKw0KKyBNZXRob2QgUGFyYW1ldGVyIDogQ29zdCBvZiBDb21wZXhpdHkgDQogICAgK+S9v+eUqGNw5L6G6KeA5a+f5qih5Z6L55qE6KSH6Zuc5bqm44CCDQogICAgKw0KKyBQYXJhbWV0ZXIgVHVuaW5nIGJ5IENWIChgY2FyZXRgIHBhY2thZ2UpDQogICAgK+WIqeeUqHRyYWlu5Ye95pW46YCy6KGM5Y+D5pW46Kq/5pW044CCDQogICAgK+OAgg0KDQo8YnI+DQoNCi0gLSAtDQoNCiMjIyAyIEQySGF3a0V5ZQ0KDQojIyMjIDIuMSBQcmVwYXJlICYgRXhhbWluZSBEYXRhIA0KYGBge3Igd2FybmluZz1GLCBtZXNzYWdlPUYsIGVycm9yPUZ9DQpybShsaXN0PWxzKGFsbD1UUlVFKSkNCkQgPSByZWFkLmNzdigiQ2xhaW1zRGF0YS5jc3YiKQ0KDQojIFBlcmNlbnRhZ2Ugb2YgcGF0aWVudHMgaW4gZWFjaCBjb3N0IGJ1Y2tldA0KdGFibGUoRCRidWNrZXQyMDA5KS9ucm93KEQpDQpgYGANCg0KYGBge3J9DQojIFNwbGl0IHRoZSBkYXRhDQpsaWJyYXJ5KGNhVG9vbHMpDQpzZXQuc2VlZCg4OCkNCnNwbCA9IHNhbXBsZS5zcGxpdChEJGJ1Y2tldDIwMDksIFNwbGl0UmF0aW8gPSAwLjYpDQpUUiA9IHN1YnNldChELCBzcGwpDQpUUyA9IHN1YnNldChELCAhc3BsKQ0KYGBgDQoNCmBgYHtyfQ0KIyBzYXBwbHkoKTogQXBwbHkgRnVuY3Rpb24gJiBBZ2dyZWdhdGUgT3V0cHV0DQpzYXBwbHkobGlzdChELCBUUiwgVFMpLCBmdW5jdGlvbih4KSANCiAgdGFibGUoeCRidWNrZXQyMDA5KSAlPiUgcHJvcC50YWJsZSkgJT4lIHQNCmBgYA0KDQpgYGB7cn0NCiMgRGF0YSBFeHBsb3JhdGlvbiANCm1lYW4oVFIkYWdlKSAgICAgICAgICAgICAgICAgIyA3Mi42NA0KbWVhbihUUiRkaWFiZXRlcykgICAgICAgICAgICAjIC4zODA5IA0KYGBgDQoNCiMjIyMgMi4yIFNtYXJ0IEJhc2VsaW5lDQpgYGB7cn0NCiMgQmFzZWxpbmUgQWNjdXJhY3kNCmFjYy5iYXNlID0gdGFibGUoVFMkYnVja2V0MjAwOSwgVFMkYnVja2V0MjAwOCkgJT4lIHtzdW0oZGlhZyguKSkgLyBzdW0oLil9DQphY2MuYmFzZSAgICMgYWNjLmJhc2UgPSAuNjgzOA0KYGBgDQoNCmBgYHtyfQ0KIyBQZW5hbHR5IE1hdHJpeA0KUGVuYWx0eSA9IG1hdHJpeChjKA0KICAwLDEsMiwzLDQsDQogIDIsMCwxLDIsMywNCiAgNCwyLDAsMSwyLA0KICA2LDQsMiwwLDEsDQogIDgsNiw0LDIsMCksIGJ5cm93PVRSVUUsIG5yb3c9NSkNClBlbmFsdHkNCmBgYA0KDQpgYGB7cn0NCiMgUGVuYWx0eSBFcnJvciBvZiBCYXNlbGluZSBNZXRob2QNCmNtID0gYXMubWF0cml4KHRhYmxlKFRTJGJ1Y2tldDIwMDksIFRTJGJ1Y2tldDIwMDgpKQ0KcGVuLmJhc2UgPSBzdW0oY20qUGVuYWx0eSkgLyBucm93KFRTKQ0KcGVuLmJhc2UgICMgcGVuLmJhc2U6IC43Mzg2DQpgYGANCg0KYGBge3J9DQojIER1bWIgYmFzZWxpbmUgLSBVc2luZyB0aGUgbW9zdCBmcmVxdWVudCBjbGFzcw0KYWNjLmR1bWIgPSAodGFibGUoVFMkYnVja2V0MjAwOSkvbnJvdyhUUykpWzFdICAgICAgICAgICAgICAgICANCnBlbi5kdW1iID0gc3VtKHRhYmxlKFRTJGJ1Y2tldDIwMDkpICogYygwLDIsNCw2LDgpKS9ucm93KFRTKSAgDQpjKGFjYy5kdW1iLCBwZW4uZHVtYikgICAjIDAuNjcxMyAxLjA0NDMNCiPnnIvmqKHlnovlpb3lo57opoHliqDlhaVwZW5hbHR5IG1hdHJpeO+8jOWFtui2iuS9jui2iuWlvQ0KYGBgDQoNCiMjIyMgMi4zIENBUlQgTW9kZWwNCmBgYHtyfQ0KbGlicmFyeShycGFydCkNCmxpYnJhcnkocnBhcnQucGxvdCkNCnJwYXJ0MSA9IHJwYXJ0KGJ1Y2tldDIwMDkgfiAuLCBkYXRhPVRSW2MoMToxNCwgMTYpXSwgDQogICAgICAgICAgICAgICBtZXRob2Q9ImNsYXNzIiwgY3A9MC4wMDAwNSkNCnBycChycGFydDEpDQpgYGANCg0KYGBge3J9DQojIE1ha2UgcHJlZGljdGlvbnMNCnByZWQgPSBwcmVkaWN0KHJwYXJ0MSwgbmV3ZGF0YSA9IFRTLCB0eXBlID0gImNsYXNzIikNCmFjYy5ycGFydDEgPSB0YWJsZShUUyRidWNrZXQyMDA5LCBwcmVkKSAlPiUgeyBzdW0oZGlhZyguKSkgL25yb3coVFMpfQ0KYWNjLnJwYXJ0MSAgICAjIGFjYy5ycGFydDEgPSAuNzEyNjcgPC0gLjY4MzgxDQpjbSA9IGFzLm1hdHJpeCh0YWJsZShUUyRidWNrZXQyMDA5LCBwcmVkKSkNCnBlbi5ycGFydDEgPSBzdW0oY20qUGVuYWx0eSkvbnJvdyhUUykNCnBlbi5ycGFydDEgICAgIyBwZW4ucnBhcnQxOiAuNzU3ODkgPC0gLjczODYNCmBgYA0KDQojIyMjIDIuNCBDQVJUIE1vZGVsIHdpdGggQ3VzdG9taXplZCBMb3NzIE1hdHJpeA0KYGBge3J9DQpycGFydDIgPSBycGFydChidWNrZXQyMDA5IH4gLiwgZGF0YT1UUltjKDE6MTQsIDE2KV0sIA0KICAgICAgICAgICAgICAgbWV0aG9kPSJjbGFzcyIsIGNwPTAuMDAwMDUsIA0KICAgICAgICAgICAgICAgcGFybXM9bGlzdChsb3NzPVBlbmFsdHkpICAgICAjIGN1c3RvbWl6ZWQgbG9zcyBmdW5jdGlvbiANCiAgICAgICAgICAgICAgICkNCiPlsIvmsYLmnIDkvY7nmoRwZW5hbHR577yM6ICM6Z2e5pyA6auY55qEYWNjdXJhY3kNCnByZWQgPSBwcmVkaWN0KHJwYXJ0MiwgVFMsIHR5cGUgPSAiY2xhc3MiKQ0KYWNjLnJwYXJ0MiA9IHRhYmxlKFRTJGJ1Y2tldDIwMDksIHByZWQpICU+JSB7c3VtKGRpYWcoLikpL3N1bSguKX0NCmFjYy5ycGFydDIgICAgIyBhY2MucnBhcnQyID0gLjY0NzI3ICAgDQpjbSA9IGFzLm1hdHJpeCh0YWJsZShUUyRidWNrZXQyMDA5LCBwcmVkKSkNCnBlbi5ycGFydDIgPSBzdW0oY20qUGVuYWx0eSkvbnJvdyhUUykgICAgDQpwZW4ucnBhcnQyICAgICMgcGVuLnJwYXJ0MjogLjY0MTgyIA0KYGBgDQoNCmBgYHtyfQ0KIyBTdW1tYXJ5DQpkYXRhLmZyYW1lKA0KICBhY2N1cmFjeSA9IGMoYWNjLmR1bWIsIGFjYy5iYXNlLCBhY2MucnBhcnQxLCBhY2MucnBhcnQyKSwNCiAgcGVuYWx0eSA9IGMocGVuLmR1bWIsIHBlbi5iYXNlLCBwZW4ucnBhcnQxLCBwZW4ucnBhcnQyKSwNCiAgcm93Lm5hbWVzID0gYygiZHVtYiIsImJhc2UiLCJDQVJUIiwiQ0FSVCB3L0xvc3NNeCIpKQ0KYGBgDQo8YnI+DQoNCiMjIyMg44CQ5a2457+S6YeN6bue44CRDQoNCisgNSBjbGFzc2VzOiA1WDUgY29uZnVzc2lvbi9wZW5hbHR5KHBheW9mZikgbWF0cmljZXMgDQogICAgK+S6huino+S9leisgua3t+a3huefqemZo+WSjOWgseWEn+efqemZo+OAgg0KICAgICvku5blgJHnmoTmhI/nvqnlkoznlKjms5XjgIINCisgZHVtYiB2cy4gc21hcnQgYmFzZWxpbmUNCiAgICAr5YWp56iu5pa55rOV55qE5pWI5p6c5q+U6LyD44CCDQogICAgKw0KKyBtb2RlbDE6IGFjY3VyYWN5IHVwLCBwZW5hbHR5IHVwDQogICAgKyANCiAgICArDQorIG1vZGVsMjogYWNjdXJhY3kgZG93biwgcGVuYWx0eSBkb3duDQogICAgKyANCiAgICArDQorIGFjY3VyYWN5ICE9IHByb2ZpdGFiaWxpdHkNCiAgICArIA0KICAgICsNCisgImN1c3RvbWl6ZWQiIG1vZGVsIG9wdGltaXplIGNyaXRlcmlhIGluIGNsYXNzaWZpY2F0aW9uIG1ldGhvZHMgKG5vdCBhbHdheXMgYXZhaWxhYmxlKQ0KICAgICsgDQogICAgKw0KKyBUaGlzIENWL1BUIHByb2Nlc3MgaXMgb3ZlciBzaW1wbGlmaWVkDQogICAgKyANCiAgICArDQorIHdlIHdpbGwgY292ZXIgYSBiZXR0ZXIgcHJvY2VzcyBuZXh0IHRpbWUNCiAgICArIA0KICAgICsNCg0KDQo8YnI+DQoNCi0gLSAtDQoNCiMjIyAzIEJvc3RvbiBIb3VzZSBQcmljZQ0KDQpgYGB7cn0NCnJtKGxpc3Q9bHMoYWxsPVRSVUUpKQ0KRCA9IHJlYWQuY3N2KCJib3N0b24uY3N2IikNCmBgYA0KDQojIyMjIDMuMSBFeGFtaW5lIERhdGENCmBgYHtyfQ0KIyBQbG90IG9ic2VydmF0aW9ucw0KcGxvdChEJExPTiwgRCRMQVQpDQoNCiMgVHJhY3RzIGFsb25nc2lkZSB0aGUgQ2hhcmxlcyBSaXZlcg0Kd2l0aChELCBwb2ludHMoTE9OW0NIQVM9PTFdLCBMQVRbQ0hBUz09MV0sIGNvbD0iYmx1ZSIsIHBjaD0xOSkpDQpwb2ludHMoRCRMT05bRCRDSEFTPT0xXSwgRCRMQVRbRCRDSEFTPT0xXSwgY29sPSJibHVlIiwgcGNoPTE5KQ0KDQojIFBsb3QgTUlUDQp3aXRoKEQsIHBvaW50cyhMT05bVFJBQ1Q9PTM1MzFdLCBMQVRbVFJBQ1Q9PTM1MzFdLCBjb2w9InJlZCIsIHBjaD0xOSkgKQ0KDQojIFBsb3QgcG9sdXRpb24NCnN1bW1hcnkoRCROT1gpDQpwb2ludHMoRCRMT05bRCROT1g+PTAuNTVdLCBEJExBVFtEJE5PWD49MC41NV0sIGNvbD0iZ3JlZW4iLCBwY2g9MjApDQoNCiMgUGxvdCBwcmljZXMNCnBsb3QoRCRMT04sIEQkTEFUKQ0Kc3VtbWFyeShEJE1FRFYpDQpwb2ludHMoRCRMT05bRCRNRURWPj0yMS4yXSwgRCRMQVRbRCRNRURWPj0yMS4yXSwgY29sPSJyZWQiLCBwY2g9MjApDQpgYGANCg0KIyMjIyAzLjIgTGluZWFyIFJlZ3Jlc3Npb24gdXNpbmcgTEFUIGFuZCBMT04NCmBgYHtyfQ0KbGF0bG9ubG0gPSBsbShNRURWIH4gTEFUICsgTE9OLCBkYXRhPUQpDQpzdW1tYXJ5KGxhdGxvbmxtKQ0KYGBgDQoNCiMjIyMgMy4zIFZpc3VhbGl6ZSByZWdyZXNzaW9uIG91dHB1dA0KYGBge3J9DQpwbG90KEQkTE9OLCBEJExBVCkNCnBvaW50cyhEJExPTltEJE1FRFY+PTIxLjJdLCBEJExBVFtEJE1FRFY+PTIxLjJdLCBjb2w9InJlZCIsIHBjaD0yMCkNCg0KIyBsYXRsb25sbSRmaXR0ZWQudmFsdWVzDQpwb2ludHMoRCRMT05bbGF0bG9ubG0kZml0dGVkLnZhbHVlcyA+PSAyMS4yXSwgDQogICAgICAgRCRMQVRbbGF0bG9ubG0kZml0dGVkLnZhbHVlcyA+PSAyMS4yXSwgDQogICAgICAgY29sPSJibHVlIiwgcGNoPSJ4IikNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkocnBhcnQpDQpsaWJyYXJ5KHJwYXJ0LnBsb3QpDQojIENBUlQgbW9kZWwNCmxhdGxvbnRyZWUgPSBycGFydChNRURWIH4gTEFUICsgTE9OLCBkYXRhPUQpDQpwcnAobGF0bG9udHJlZSkNCg0KIyBWaXN1YWxpemUgb3V0cHV0DQpwbG90KEQkTE9OLCBEJExBVCkNCnBvaW50cyhEJExPTltEJE1FRFY+PTIxLjJdLCBEJExBVFtEJE1FRFY+PTIxLjJdLCANCiAgICAgICBjb2w9InJlZCIsIHBjaD0yMCkNCg0KZml0dGVkdmFsdWVzID0gcHJlZGljdChsYXRsb250cmVlKQ0KcG9pbnRzKEQkTE9OW2ZpdHRlZHZhbHVlcz4yMS4yXSwgDQogICAgICAgRCRMQVRbZml0dGVkdmFsdWVzPj0yMS4yXSwgY29sPSJibHVlIiwgcGNoPSJ4IikNCmBgYA0KDQojIyMjIDMuNCBTaW1wbGlmeSB0cmVlIGJ5IGluY3JlYXNpbmcgbWluYnVja2V0DQpgYGB7cn0NCmxhdGxvbnRyZWUgPSBycGFydChNRURWIH4gTEFUICsgTE9OLCBkYXRhPUQsIG1pbmJ1Y2tldD01MCkNCnJwYXJ0LnBsb3QobGF0bG9udHJlZSkNCmBgYA0KDQpgYGB7cn0NCiMgVmlzdWFsaXplIE91dHB1dA0KcGxvdChEJExPTixEJExBVCkNCmFibGluZSh2PS03MS4wNykNCmFibGluZShoPTQyLjIxKQ0KYWJsaW5lKGg9NDIuMTcpDQpwb2ludHMoRCRMT05bRCRNRURWPj0yMS4yXSwgDQogICAgICAgRCRMQVRbRCRNRURWPj0yMS4yXSwgY29sPSJyZWQiLCBwY2g9MjApDQpgYGANCg0KIyMjIyAzLjUgVXNlIGFsbCB0aGUgdmFyaWFibGVzDQpgYGB7cn0NCiMgU3BsaXQgdGhlIGRhdGENCmxpYnJhcnkoY2FUb29scykNCnNldC5zZWVkKDEyMykNCnNwbGl0ID0gc2FtcGxlLnNwbGl0KEQkTUVEViwgU3BsaXRSYXRpbyA9IDAuNykNCnRyYWluID0gc3Vic2V0KEQsIHNwbGl0PT1UUlVFKQ0KdGVzdCA9IHN1YnNldChELCBzcGxpdD09RkFMU0UpDQoNCiMgQ3JlYXRlIGxpbmVhciByZWdyZXNzaW9uDQpsaW5yZWcgPSBsbShNRURWIH4gTEFUICsgTE9OICsgQ1JJTSArIFpOICsgSU5EVVMgKyBDSEFTICsgTk9YICsgUk0gKyANCiAgICAgICAgICAgICAgQUdFICsgRElTICsgUkFEICsgVEFYICsgUFRSQVRJTywgZGF0YT10cmFpbikNCnN1bW1hcnkobGlucmVnKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMNCmxpbnJlZy5wcmVkID0gcHJlZGljdChsaW5yZWcsIG5ld2RhdGE9dGVzdCkNCmxpbnJlZy5zc2UgPSBzdW0oKGxpbnJlZy5wcmVkIC0gdGVzdCRNRURWKV4yKQ0KbGlucmVnLnNzZQ0KYGBgDQoNCiMjIyMgMy42IENyZWF0ZSBhIENBUlQgbW9kZWwNCmBgYHtyfQ0KdHJlZSA9IHJwYXJ0KE1FRFYgfiBMQVQgKyBMT04gKyBDUklNICsgWk4gKyBJTkRVUyArIENIQVMgKyBOT1ggKyBSTSArIA0KICAgICAgICAgICAgICAgQUdFICsgRElTICsgUkFEICsgVEFYICsgUFRSQVRJTywgZGF0YT10cmFpbikNCnBycCh0cmVlKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMNCnRyZWUucHJlZCA9IHByZWRpY3QodHJlZSwgbmV3ZGF0YT10ZXN0KQ0KdHJlZS5zc2UgPSBzdW0oKHRyZWUucHJlZCAtIHRlc3QkTUVEVileMikNCnRyZWUuc3NlDQpgYGANCg0KIyMjIyAzLjcgTG9hZCBsaWJyYXJpZXMgZm9yIGNyb3NzLXZhbGlkYXRpb24NCmBgYHtyfQ0KbGlicmFyeShjYXJldCkNCg0KIyBOdW1iZXIgb2YgZm9sZHMNCnRyLmNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApDQoNCiMgY3AgdmFsdWVzDQpjcC5ncmlkID0gZXhwYW5kLmdyaWQoIC5jcCA9ICgwOjEwKSowLjAwMSkNCg0KIyBDcm9zcy12YWxpZGF0aW9uDQp0ciA9IHRyYWluKE1FRFYgfiBMQVQgKyBMT04gKyBDUklNICsgWk4gKyBJTkRVUyArIENIQVMgKyBOT1ggKyBSTSArIA0KICAgICAgICAgICAgIEFHRSArIERJUyArIFJBRCArIFRBWCArIFBUUkFUSU8sIA0KICAgICAgICAgICBkYXRhID0gdHJhaW4sIG1ldGhvZCA9ICJycGFydCIsIA0KICAgICAgICAgICB0ckNvbnRyb2wgPSB0ci5jb250cm9sLCB0dW5lR3JpZCA9IGNwLmdyaWQpDQp0cg0KDQojIEV4dHJhY3QgdHJlZQ0KYmVzdC50cmVlID0gdHIkZmluYWxNb2RlbA0KcHJwKGJlc3QudHJlZSkNCg0KIyBNYWtlIHByZWRpY3Rpb25zDQpiZXN0LnRyZWUucHJlZCA9IHByZWRpY3QoYmVzdC50cmVlLCBuZXdkYXRhPXRlc3QpDQpiZXN0LnRyZWUuc3NlID0gc3VtKChiZXN0LnRyZWUucHJlZCAtIHRlc3QkTUVEVileMikNCmJlc3QudHJlZS5zc2UNCmBgYA0KDQojIyMjIDMuOCBFbnNlbWJsaW5nDQpgYGB7cn0NCmVuLnByZWQgPSAoYmVzdC50cmVlLnByZWQgKyBsaW5yZWcucHJlZCkvMg0Kc3VtKChlbi5wcmVkIC0gdGVzdCRNRURWKV4yKQ0KYGBgDQoNCg0KYGBge3J9DQojICAgICAgIE1FVEhPRAkgU1NFDQojICAgICAgICAgICBsbQkzMDM3DQojICAgICAgICAgcnBhcnQJNDMyOQ0KIyAgICAgIHJwYXJ0LmN2CTM2NjANCiMgICBsbStycGFydC5jdgkyNTk5ICANCmBgYA0KPGJyPg0KDQojIyMjIOOAkOWtuOe/kumHjem7nuOAkQ0KDQorIGRyYXdpbmcgbWFwIHdpdGggc3BhdGlhbCBkYXRhIA0KICAgICsNCiAgICArDQorIHRyZWUgaW4gc3BhdGlhbCBkYXRhDQogICAgKw0KICAgICsNCisgdHJlZSB2cy4gbGluZWFyIHJlZ3Jlc3Npb24NCiAgICArDQogICAgKw0KKyB0aGUgY29uY2VwdCBvZiBlbnNlbWJsaW5nDQogICAgKw0KICAgICsNCg0KPGJyPg0KDQotIC0gLQ0KDQo8YnI+PGJyPjxicj48YnI+PGJyPg0KDQo8c3R5bGU+DQouY2FwdGlvbiB7DQogIGNvbG9yOiAjNzc3Ow0KICBtYXJnaW4tdG9wOiAxMHB4Ow0KfQ0KcCBjb2RlIHsNCiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7DQp9DQpwcmUgew0KICB3b3JkLWJyZWFrOiBub3JtYWw7DQogIHdvcmQtd3JhcDogbm9ybWFsOw0KICBsaW5lLWhlaWdodDogMTsNCn0NCnByZSBjb2RlIHsNCiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7DQp9DQpwLGxpIHsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCi5yew0KICBsaW5lLWhlaWdodDogMS4yOw0KfQ0KDQp0aXRsZXsNCiAgY29sb3I6ICNjYzAwMDA7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpib2R5ew0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDEsaDIsaDMsaDQsaDV7DQogIGNvbG9yOiAjMDA2NmZmOw0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDQsaDV7DQogIGJhY2tncm91bmQ6ICNjY2ZmZmY7DQp9DQoNCjwvc3R5bGU+DQoNCg0KDQoNCg0KDQo=