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
      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)
Loading required package: gplots

Attaching package: 'gplots'

The following object is masked from 'package:stats':

    lowess
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")

source("DPP.R")
auc = DPP(predict(rpart1)[,2], TR$Reverse, 1)     # AUC = 0.7884

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

  • 一棵決策樹的形狀, 和由它所產生的預測機率分布之間,有什麼關係呢 ? 以上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?


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)
Loading required package: lattice
Loading required package: ggplot2

Attaching package: 'ggplot2'

The following object is masked from 'package:randomForest':

    margin
numFolds = trainControl(method="cv", number=10) # 10 fold CV
cpGrid = 
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?


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
[1] 0.72353
rpart.plot(rpart2)


【討論】

  • Classification & Regression Tree rpart
  • A Bag of Tree - Random Forest randomForest
  • Overfitting : Curse of Complexity
  • Model Parameter : Cost of Compexity
  • Parameter Tuning by CV (caret package)



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)

        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]                 
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 

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
[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 
               )
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"))
              accuracy penalty
dumb           0.67127 1.04430
base           0.68381 0.73861
CART           0.71267 0.75789
CART w/LossMx  0.64727 0.64182


【討論】

  • 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)
   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)

# 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








LS0tDQp0aXRsZTogIkFTNC0wIOiqsuWgguethuiomCINCmF1dGhvcjogIuWNk+mbjeeEtiBEOTk0MDEwMDAxIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KYGBge3J9DQpwYWNrYWdlcyA9IGMoDQogICJkcGx5ciIsImdncGxvdDIiLCJkM2hlYXRtYXAiLCJnb29nbGVWaXMiLCJkZXZ0b29scyIsInBsb3RseSIsICJ4Z2Jvb3N0IiwNCiAgIm1hZ3JpdHRyIiwiY2FUb29scyIsIlJPQ1IiLCJjb3JycGxvdCIsICJycGFydCIsICJycGFydC5wbG90IiwNCiAgImRvUGFyYWxsZWwiLCAiY2FyZXQiLCAiZ2xtbmV0IiwgIk1hdHJpeCIsICJlMTA3MSIsICJyYW5kb21Gb3Jlc3QiDQogICkNCmV4aXN0aW5nID0gYXMuY2hhcmFjdGVyKGluc3RhbGxlZC5wYWNrYWdlcygpWywxXSkNCmZvcihwa2cgaW4gcGFja2FnZXNbIShwYWNrYWdlcyAlaW4lIGV4aXN0aW5nKV0pIGluc3RhbGwucGFja2FnZXMocGtnKQ0KYGBgDQoNCmBgYHtyIGVjaG89VCwgbWVzc2FnZT1GLCBjYWNoZT1GLCB3YXJuaW5nPUZ9DQpybShsaXN0PWxzKGFsbD1UKSkNCm9wdGlvbnMoZGlnaXRzPTUsIHNjaXBlbj0xMikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGNhVG9vbHMpDQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQpgYGANCg0KLSAtIC0NCg0KIyMjIDEuIFN1cHJpbWUgQ291cnQgRGVjaXNpb24NCg0KIyMjIyAxLjEgUHJlcGFyZSBEYXRhDQpgYGB7cn0NCkQgPSByZWFkLmNzdigiZGF0YS9zdGV2ZW5zLmNzdiIpDQoNCmxpYnJhcnkoY2FUb29scykNCnNldC5zZWVkKDMwMDApDQpzcGwgPSBzYW1wbGUuc3BsaXQoRCRSZXZlcnNlLCBTcGxpdFJhdGlvID0gMC43KQ0KVFIgPSBzdWJzZXQoRCwgc3BsKQ0KVFMgPSBzdWJzZXQoRCwgIXNwbCkNCmBgYA0KDQojIyMjIDEuMiBDQVJUIChDbGFzc2lmaWNhdGlvbiAmIFJlZ3Jlc3Npb24gVHJlZSkgLSBgcnBhcnQ6OnJwYXJ0KClgDQpgYGB7cn0NCmxpYnJhcnkocnBhcnQpDQpycGFydDEgPSBycGFydChSZXZlcnNlIH4gLiwgVFJbLDM6OV0sICAgICAgICAgICMgc2ltcGxpZnkgdGhlIGZvcm11bGENCiAgICAgICAgICAgICAgIG1ldGhvZD0iY2xhc3MiLCBtaW5idWNrZXQ9MjUpDQpgYGANCg0KIyMjIyAxLjMgUGxvdCBEZWNpc2lvbiBUcmVlDQpgYGB7cn0NCmxpYnJhcnkocnBhcnQucGxvdCkNCnBycChycGFydDEpDQpycGFydC5wbG90KHJwYXJ0MSxjZXg9MC42KQ0KYGBgDQoNCiMjIyMgMS40IE1ha2UgUHJlZGljdGlvbg0KYGBge3J9DQpwcmVkID0gcHJlZGljdChycGFydDEsIFRTLCB0eXBlID0gImNsYXNzIikgICMgcHJlZGljdCBjbGFzc2VzDQp4ID0gdGFibGUoYWN0dWFsID0gVFMkUmV2ZXJzZSwgcHJlZCk7IHgNCnN1bShkaWFnKHgpKS9zdW0oeCkgICAgICAgICAgICAgICAgICAgICAgICAgIyAuNjU4ODINCmBgYA0KDQojIyMjIDEuNSBST0MgJiBBVUMNCmBgYHtyfQ0KbGlicmFyeShST0NSKQ0KUHJlZGljdFJPQyA9IHByZWRpY3QocnBhcnQxLCBUUykgICAgICAgICAgICAgICMgcHJlZGljdCBwcm9iLg0KaGVhZChQcmVkaWN0Uk9DKQ0KcGVyZiA9IHByZWRpY3Rpb24oUHJlZGljdFJPQ1ssMl0sIFRTJFJldmVyc2UpDQpwZXJmID0gcGVyZm9ybWFuY2UocGVyZiwgInRwciIsICJmcHIiKQ0KcGxvdChwZXJmKQ0KYGBgDQoNCmBgYHtyfQ0KcHJlZCA9IHByZWRpY3QocnBhcnQxLCBUUylbLDJdICAgICAjIHByb2IuIG9mIFJldmVyc2UgPSAxICAgICAgICAgDQpjb2xBVUMocHJlZCwgVFMkUmV2ZXJzZSwgVCkgICAgICAgICMgQVVDID0gMC42OTI3DQpgYGANCg0KDQojIyMjIDEuNiBEUFAgb2YgRGVjaXNpb24gVHJlZQ0KDQpgYGB7cn0NCnJwYXJ0LnBsb3QocnBhcnQxLGNleD0wLjc1LHZhcmxlbj0zLGZhY2xlbj0yLGJveC5wYWxldHRlPSJHblJkIikNCmBgYA0KDQoNCmBgYHtyfQ0Kc291cmNlKCJEUFAuUiIpDQphdWMgPSBEUFAocHJlZGljdChycGFydDEpWywyXSwgVFIkUmV2ZXJzZSwgMSkgICAgICMgQVVDID0gMC43ODg0DQpgYGANCg0KIyMjIyDjgJBRVUlaLTHjgJHmr5TovIPku6XkuIpgcHJwKClg44CBYHJwYXJ0LnBsb3QoKWDlkoxgRFBQKClg6YCZ5bm+5by15ZyW5b2iDQoNCisg5LiA5qO15rG6562W5qi555qE5b2i54uA77yMIOWSjOeUseWug+aJgOeUoueUn+eahOmgkOa4rOapn+eOh+WIhuW4g+S5i+mWk++8jOacieS7gOm6vOmXnOS/guWRoiA/ICDku6XkuIpEUFAg5ZyW5Lit55qE5YiG5biD6bue5pW444CB5L2N572u5ZKM6auY5bqm77yM5YiG5Yil5pyD5bCN5oeJ5Yiw5rG6562W5qi555qE5LuA6bq854m55b615ZGiPw0KDQorIA0KDQo8YnI+DQoNCiMjIyMgMS43IFRoZSBlZmZlY3Qgb2YgYG1pbmJ1Y2tldGANCmBgYHtyfQ0KdDUgPSBycGFydChSZXZlcnNlIH4gLiwgVFJbLDM6OV0sIG1ldGhvZD0iY2xhc3MiLCBtaW5idWNrZXQ9NSkNCnBycCh0NSkNCmBgYA0KDQpgYGB7ciBmaWcuaGVpZ2h0PTJ9DQp0MTAwID0gcnBhcnQoUmV2ZXJzZSB+IC4sIFRSWywzOjldLCBtZXRob2Q9ImNsYXNzIiwgbWluYnVja2V0PTEwMCkNCnBycCh0MTAwKQ0KYGBgDQoNCg0KIyMjI+OAkFFVSVotMuOAkUJhc2VkIG9uIHRoZSAyIHBsb3RzIGFib3ZlLCB3aGF0IGlzIHRoZSBlZmZlY3Qgb2YgYG1pbmJ1Y2tldGA/IA0KDQorIA0KDQo8YnI+DQoNCiMjIyMgMS44IFJhbmRvbSBGb3Jlc3QgTWV0aG9kIC0gYHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KClgDQpgYGB7cn0NCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KVFIkUmV2ZXJzZSA9IGFzLmZhY3RvcihUUiRSZXZlcnNlKSAgIyBDb252ZXJ0IG91dGNvbWUgdG8gZmFjdG9yDQpUUyRSZXZlcnNlID0gYXMuZmFjdG9yKFRTJFJldmVyc2UpDQoNCiMgQnVpbGQgbW9kZWwNCnJmMSA9IHJhbmRvbUZvcmVzdChSZXZlcnNlIH4gLiwgVFJbLDM6OV0sIG50cmVlPTIwMCwgbm9kZXNpemU9MjUpDQoNCiMgTWFrZSBwcmVkaWN0aW9ucw0KcHJlZCA9IHByZWRpY3QocmYxLCBUUykNCnggPSB0YWJsZShUUyRSZXZlcnNlLCBwcmVkKQ0Kc3VtKGRpYWcoeCkpIC8gc3VtKHgpICAgICAgICAgICMgMC42ODI0DQpgYGANCg0KYGBge3J9DQpwcmVkID0gcHJlZGljdChyZjEsIFRTLCB0eXBlPSdwcm9iJylbLDJdDQp0YWJsZShUUyRSZXZlcnNlLCBwcmVkID4gMC41KSAlPiUge3N1bShkaWFnKC4pKS9zdW0oLil9DQpgYGANCg0KDQojIyMjIDEuOSBDcm9zcyBWYWxpZGF0aW9uICYgUGFyYW1ldGVyIFR1bmluZyAtIGBjYXJldGAgcGFja2FnZQ0KYGBge3J9DQpsaWJyYXJ5KGNhcmV0KQ0KbnVtRm9sZHMgPSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIG51bWJlcj0xMCkgIyAxMCBmb2xkIENWDQpjcEdyaWQgPSANCg0KY3YxID0gdHJhaW4oDQogIFJldmVyc2UgfiAuLCBUUlssMzo5XSwgbWV0aG9kID0gInJwYXJ0IiwgDQogIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2Q9ImN2IiwgbnVtYmVyPTEwKSwgIyAxMCBmb2xkIENWDQogIHR1bmVHcmlkPWV4cGFuZC5ncmlkKGNwID0gc2VxKDAuMDEsMC41LDAuMDEpKSAgICAgIyBwYXJhbWV0ZXIgY29tYmluYXRpb24NCiAgKQ0KY3YxOyBwbG90KGN2MSkNCmBgYA0KDQojIyMj44CQUVVJWi0z44CRV2hpY2ggYGNwYCB3aGljaCB3ZSBzaG91bGQgd2UgY2hvb3NlPyBXaHk/IA0KDQorDQoNCjxicj4NCg0KIyMjIyAxLjEwIFRoZSBGaW5hbCBNb2RlbA0KYGBge3J9DQpycGFydDIgPSBycGFydChSZXZlcnNlIH4gLiwgVFJbLDM6OV0sIG1ldGhvZD0iY2xhc3MiLCBjcD0wLjE5KQ0KcHJlZCA9IHByZWRpY3QocnBhcnQyLCBUUywgdHlwZT0ncHJvYicpWywyXQ0KdGFibGUoVFMkUmV2ZXJzZSwgcHJlZCA+IDAuNSkgJT4lIHtzdW0oZGlhZyguKSkgLyBzdW0oLil9ICAjIDAuNzIzNQ0KYGBgDQoNCmBgYHtyIGZpZy53aWR0aD0zfQ0KcnBhcnQucGxvdChycGFydDIpDQpgYGANCjxicj4NCg0KIyMjI+OAkOiojuirluOAkQ0KDQorIENsYXNzaWZpY2F0aW9uICYgUmVncmVzc2lvbiBUcmVlICBgcnBhcnRgDQogICAgKw0KICAgICsNCisgQSBCYWcgb2YgVHJlZSAtIFJhbmRvbSBGb3Jlc3QgYHJhbmRvbUZvcmVzdGAgIA0KICAgICsNCiAgICArDQorIE92ZXJmaXR0aW5nIDogQ3Vyc2Ugb2YgQ29tcGxleGl0eQ0KICAgICsNCiAgICArDQorIE1vZGVsIFBhcmFtZXRlciA6IENvc3Qgb2YgQ29tcGV4aXR5IA0KICAgICsNCiAgICArDQorIFBhcmFtZXRlciBUdW5pbmcgYnkgQ1YgKGBjYXJldGAgcGFja2FnZSkNCiAgICArDQogICAgKw0KDQo8YnI+DQoNCi0gLSAtDQoNCiMjIyAyIEQySGF3a0V5ZQ0KDQojIyMjIDIuMSBQcmVwYXJlICYgRXhhbWluZSBEYXRhIA0KYGBge3Igd2FybmluZz1GLCBtZXNzYWdlPUYsIGVycm9yPUZ9DQpybShsaXN0PWxzKGFsbD1UUlVFKSkNCkQgPSByZWFkLmNzdigiZGF0YS9DbGFpbXNEYXRhLmNzdiIpDQoNCiMgUGVyY2VudGFnZSBvZiBwYXRpZW50cyBpbiBlYWNoIGNvc3QgYnVja2V0DQp0YWJsZShEJGJ1Y2tldDIwMDkpL25yb3coRCkNCmBgYA0KDQpgYGB7cn0NCiMgU3BsaXQgdGhlIGRhdGENCmxpYnJhcnkoY2FUb29scykNCnNldC5zZWVkKDg4KQ0Kc3BsID0gc2FtcGxlLnNwbGl0KEQkYnVja2V0MjAwOSwgU3BsaXRSYXRpbyA9IDAuNikNClRSID0gc3Vic2V0KEQsIHNwbCkNClRTID0gc3Vic2V0KEQsICFzcGwpDQpgYGANCg0KYGBge3J9DQojIHNhcHBseSgpOiBBcHBseSBGdW5jdGlvbiAmIEFnZ3JlZ2F0ZSBPdXRwdXQNCnNhcHBseShsaXN0KEQsIFRSLCBUUyksIGZ1bmN0aW9uKHgpIA0KICB0YWJsZSh4JGJ1Y2tldDIwMDkpICU+JSBwcm9wLnRhYmxlKSAlPiUgdA0KYGBgDQoNCmBgYHtyfQ0KIyBEYXRhIEV4cGxvcmF0aW9uIA0KbWVhbihUUiRhZ2UpICAgICAgICAgICAgICAgICAjIDcyLjY0DQptZWFuKFRSJGRpYWJldGVzKSAgICAgICAgICAgICMgLjM4MDkgDQpgYGANCg0KIyMjIyAyLjIgU21hcnQgQmFzZWxpbmUNCmBgYHtyfQ0KIyBCYXNlbGluZSBBY2N1cmFjeQ0KYWNjLmJhc2UgPSB0YWJsZShUUyRidWNrZXQyMDA5LCBUUyRidWNrZXQyMDA4KSAlPiUge3N1bShkaWFnKC4pKSAvIHN1bSguKX0NCmFjYy5iYXNlICAgIyBhY2MuYmFzZSA9IC42ODM4DQpgYGANCg0KYGBge3J9DQojIFBlbmFsdHkgTWF0cml4DQpQZW5hbHR5ID0gbWF0cml4KGMoDQogIDAsMSwyLDMsNCwNCiAgMiwwLDEsMiwzLA0KICA0LDIsMCwxLDIsDQogIDYsNCwyLDAsMSwNCiAgOCw2LDQsMiwwKSwgYnlyb3c9VFJVRSwgbnJvdz01KQ0KUGVuYWx0eQ0KYGBgDQoNCmBgYHtyfQ0KIyBQZW5hbHR5IEVycm9yIG9mIEJhc2VsaW5lIE1ldGhvZA0KY20gPSBhcy5tYXRyaXgodGFibGUoVFMkYnVja2V0MjAwOSwgVFMkYnVja2V0MjAwOCkpDQpwZW4uYmFzZSA9IHN1bShjbSpQZW5hbHR5KSAvIG5yb3coVFMpDQpwZW4uYmFzZSAgIyBwZW4uYmFzZTogLjczODYNCmBgYA0KDQpgYGB7cn0NCiMgRHVtYiBiYXNlbGluZSAtIFVzaW5nIHRoZSBtb3N0IGZyZXF1ZW50IGNsYXNzDQphY2MuZHVtYiA9ICh0YWJsZShUUyRidWNrZXQyMDA5KS9ucm93KFRTKSlbMV0gICAgICAgICAgICAgICAgIA0KcGVuLmR1bWIgPSBzdW0odGFibGUoVFMkYnVja2V0MjAwOSkgKiBjKDAsMiw0LDYsOCkpL25yb3coVFMpICANCmMoYWNjLmR1bWIsIHBlbi5kdW1iKSAgICMgMC42NzEzIDEuMDQ0Mw0KYGBgDQoNCiMjIyMgMi4zIENBUlQgTW9kZWwNCmBgYHtyfQ0KbGlicmFyeShycGFydCkNCmxpYnJhcnkocnBhcnQucGxvdCkNCnJwYXJ0MSA9IHJwYXJ0KGJ1Y2tldDIwMDkgfiAuLCBkYXRhPVRSW2MoMToxNCwgMTYpXSwgDQogICAgICAgICAgICAgICBtZXRob2Q9ImNsYXNzIiwgY3A9MC4wMDAwNSkNCnBycChycGFydDEpDQpgYGANCg0KYGBge3J9DQojIE1ha2UgcHJlZGljdGlvbnMNCnByZWQgPSBwcmVkaWN0KHJwYXJ0MSwgbmV3ZGF0YSA9IFRTLCB0eXBlID0gImNsYXNzIikNCmFjYy5ycGFydDEgPSB0YWJsZShUUyRidWNrZXQyMDA5LCBwcmVkKSAlPiUgeyBzdW0oZGlhZyguKSkgL25yb3coVFMpfQ0KYWNjLnJwYXJ0MSAgICAjIGFjYy5ycGFydDEgPSAuNzEyNjcgPC0gLjY4MzgxDQpjbSA9IGFzLm1hdHJpeCh0YWJsZShUUyRidWNrZXQyMDA5LCBwcmVkKSkNCnBlbi5ycGFydDEgPSBzdW0oY20qUGVuYWx0eSkvbnJvdyhUUykNCnBlbi5ycGFydDEgICAgIyBwZW4ucnBhcnQxOiAuNzU3ODkgPC0gLjczODYNCmBgYA0KDQojIyMjIDIuNCBDQVJUIE1vZGVsIHdpdGggQ3VzdG9taXplZCBMb3NzIE1hdHJpeA0KYGBge3J9DQpycGFydDIgPSBycGFydChidWNrZXQyMDA5IH4gLiwgZGF0YT1UUltjKDE6MTQsIDE2KV0sIA0KICAgICAgICAgICAgICAgbWV0aG9kPSJjbGFzcyIsIGNwPTAuMDAwMDUsIA0KICAgICAgICAgICAgICAgcGFybXM9bGlzdChsb3NzPVBlbmFsdHkpICAgICAjIGN1c3RvbWl6ZWQgbG9zcyBmdW5jdGlvbiANCiAgICAgICAgICAgICAgICkNCg0KcHJlZCA9IHByZWRpY3QocnBhcnQyLCBUUywgdHlwZSA9ICJjbGFzcyIpDQphY2MucnBhcnQyID0gdGFibGUoVFMkYnVja2V0MjAwOSwgcHJlZCkgJT4lIHtzdW0oZGlhZyguKSkvc3VtKC4pfQ0KYWNjLnJwYXJ0MiAgICAjIGFjYy5ycGFydDIgPSAuNjQ3MjcgICANCmNtID0gYXMubWF0cml4KHRhYmxlKFRTJGJ1Y2tldDIwMDksIHByZWQpKQ0KcGVuLnJwYXJ0MiA9IHN1bShjbSpQZW5hbHR5KS9ucm93KFRTKSAgICANCnBlbi5ycGFydDIgICAgIyBwZW4ucnBhcnQyOiAuNjQxODIgDQpgYGANCg0KYGBge3J9DQojIFN1bW1hcnkNCmRhdGEuZnJhbWUoDQogIGFjY3VyYWN5ID0gYyhhY2MuZHVtYiwgYWNjLmJhc2UsIGFjYy5ycGFydDEsIGFjYy5ycGFydDIpLA0KICBwZW5hbHR5ID0gYyhwZW4uZHVtYiwgcGVuLmJhc2UsIHBlbi5ycGFydDEsIHBlbi5ycGFydDIpLA0KICByb3cubmFtZXMgPSBjKCJkdW1iIiwiYmFzZSIsIkNBUlQiLCJDQVJUIHcvTG9zc014IikpDQpgYGANCjxicj4NCg0KIyMjIyDjgJDoqI7oq5bjgJENCg0KKyA1IGNsYXNzZXM6IDVYNSBjb25mdXNzaW9uL3BlbmFsdHkocGF5b2ZmKSBtYXRyaWNlcyANCiAgICArIA0KICAgICsNCisgZHVtYiB2cy4gc21hcnQgYmFzZWxpbmUNCiAgICArIA0KICAgICsNCisgbW9kZWwxOiBhY2N1cmFjeSB1cCwgcGVuYWx0eSB1cA0KICAgICsgDQogICAgKw0KKyBtb2RlbDI6IGFjY3VyYWN5IGRvd24sIHBlbmFsdHkgZG93bg0KICAgICsgDQogICAgKw0KKyBhY2N1cmFjeSAhPSBwcm9maXRhYmlsaXR5DQogICAgKyANCiAgICArDQorICJjdXN0b21pemVkIiBtb2RlbCBvcHRpbWl6ZSBjcml0ZXJpYSBpbiBjbGFzc2lmaWNhdGlvbiBtZXRob2RzIChub3QgYWx3YXlzIGF2YWlsYWJsZSkNCiAgICArIA0KICAgICsNCisgVGhpcyBDVi9QVCBwcm9jZXNzIGlzIG92ZXIgc2ltcGxpZmllZA0KICAgICsgDQogICAgKw0KKyB3ZSB3aWxsIGNvdmVyIGEgYmV0dGVyIHByb2Nlc3MgbmV4dCB0aW1lDQogICAgKyANCiAgICArDQoNCg0KPGJyPg0KDQotIC0gLQ0KDQojIyMgMyBCb3N0b24gSG91c2UgUHJpY2UNCg0KYGBge3J9DQpybShsaXN0PWxzKGFsbD1UUlVFKSkNCkQgPSByZWFkLmNzdigiZGF0YS9ib3N0b24uY3N2IikNCmBgYA0KDQojIyMjIDMuMSBFeGFtaW5lIERhdGENCmBgYHtyfQ0KIyBQbG90IG9ic2VydmF0aW9ucw0KcGxvdChEJExPTiwgRCRMQVQpDQoNCiMgVHJhY3RzIGFsb25nc2lkZSB0aGUgQ2hhcmxlcyBSaXZlcg0Kd2l0aChELCBwb2ludHMoTE9OW0NIQVM9PTFdLCBMQVRbQ0hBUz09MV0sIGNvbD0iYmx1ZSIsIHBjaD0xOSkpDQpwb2ludHMoRCRMT05bRCRDSEFTPT0xXSwgRCRMQVRbRCRDSEFTPT0xXSwgY29sPSJibHVlIiwgcGNoPTE5KQ0KDQojIFBsb3QgTUlUDQp3aXRoKEQsIHBvaW50cyhMT05bVFJBQ1Q9PTM1MzFdLCBMQVRbVFJBQ1Q9PTM1MzFdLCBjb2w9InJlZCIsIHBjaD0xOSkgKQ0KDQojIFBsb3QgcG9sdXRpb24NCnN1bW1hcnkoRCROT1gpDQpwb2ludHMoRCRMT05bRCROT1g+PTAuNTVdLCBEJExBVFtEJE5PWD49MC41NV0sIGNvbD0iZ3JlZW4iLCBwY2g9MjApDQoNCiMgUGxvdCBwcmljZXMNCnBsb3QoRCRMT04sIEQkTEFUKQ0Kc3VtbWFyeShEJE1FRFYpDQpwb2ludHMoRCRMT05bRCRNRURWPj0yMS4yXSwgRCRMQVRbRCRNRURWPj0yMS4yXSwgY29sPSJyZWQiLCBwY2g9MjApDQpgYGANCg0KIyMjIyAzLjIgTGluZWFyIFJlZ3Jlc3Npb24gdXNpbmcgTEFUIGFuZCBMT04NCmBgYHtyfQ0KbGF0bG9ubG0gPSBsbShNRURWIH4gTEFUICsgTE9OLCBkYXRhPUQpDQpzdW1tYXJ5KGxhdGxvbmxtKQ0KYGBgDQoNCiMjIyMgMy4zIFZpc3VhbGl6ZSByZWdyZXNzaW9uIG91dHB1dA0KYGBge3J9DQpwbG90KEQkTE9OLCBEJExBVCkNCnBvaW50cyhEJExPTltEJE1FRFY+PTIxLjJdLCBEJExBVFtEJE1FRFY+PTIxLjJdLCBjb2w9InJlZCIsIHBjaD0yMCkNCg0KIyBsYXRsb25sbSRmaXR0ZWQudmFsdWVzDQpwb2ludHMoRCRMT05bbGF0bG9ubG0kZml0dGVkLnZhbHVlcyA+PSAyMS4yXSwgDQogICAgICAgRCRMQVRbbGF0bG9ubG0kZml0dGVkLnZhbHVlcyA+PSAyMS4yXSwgDQogICAgICAgY29sPSJibHVlIiwgcGNoPSJ4IikNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkocnBhcnQpDQpsaWJyYXJ5KHJwYXJ0LnBsb3QpDQojIENBUlQgbW9kZWwNCmxhdGxvbnRyZWUgPSBycGFydChNRURWIH4gTEFUICsgTE9OLCBkYXRhPUQpDQpwcnAobGF0bG9udHJlZSkNCg0KIyBWaXN1YWxpemUgb3V0cHV0DQpwbG90KEQkTE9OLCBEJExBVCkNCnBvaW50cyhEJExPTltEJE1FRFY+PTIxLjJdLCBEJExBVFtEJE1FRFY+PTIxLjJdLCANCiAgICAgICBjb2w9InJlZCIsIHBjaD0yMCkNCg0KZml0dGVkdmFsdWVzID0gcHJlZGljdChsYXRsb250cmVlKQ0KcG9pbnRzKEQkTE9OW2ZpdHRlZHZhbHVlcz4yMS4yXSwgDQogICAgICAgRCRMQVRbZml0dGVkdmFsdWVzPj0yMS4yXSwgY29sPSJibHVlIiwgcGNoPSJ4IikNCmBgYA0KDQojIyMjIDMuNCBTaW1wbGlmeSB0cmVlIGJ5IGluY3JlYXNpbmcgbWluYnVja2V0DQpgYGB7cn0NCmxhdGxvbnRyZWUgPSBycGFydChNRURWIH4gTEFUICsgTE9OLCBkYXRhPUQsIG1pbmJ1Y2tldD01MCkNCnJwYXJ0LnBsb3QobGF0bG9udHJlZSkNCmBgYA0KDQpgYGB7cn0NCiMgVmlzdWFsaXplIE91dHB1dA0KcGxvdChEJExPTixEJExBVCkNCmFibGluZSh2PS03MS4wNykNCmFibGluZShoPTQyLjIxKQ0KYWJsaW5lKGg9NDIuMTcpDQpwb2ludHMoRCRMT05bRCRNRURWPj0yMS4yXSwgDQogICAgICAgRCRMQVRbRCRNRURWPj0yMS4yXSwgY29sPSJyZWQiLCBwY2g9MjApDQpgYGANCg0KIyMjIyAzLjUgVXNlIGFsbCB0aGUgdmFyaWFibGVzDQpgYGB7cn0NCiMgU3BsaXQgdGhlIGRhdGENCmxpYnJhcnkoY2FUb29scykNCnNldC5zZWVkKDEyMykNCnNwbGl0ID0gc2FtcGxlLnNwbGl0KEQkTUVEViwgU3BsaXRSYXRpbyA9IDAuNykNCnRyYWluID0gc3Vic2V0KEQsIHNwbGl0PT1UUlVFKQ0KdGVzdCA9IHN1YnNldChELCBzcGxpdD09RkFMU0UpDQoNCiMgQ3JlYXRlIGxpbmVhciByZWdyZXNzaW9uDQpsaW5yZWcgPSBsbShNRURWIH4gTEFUICsgTE9OICsgQ1JJTSArIFpOICsgSU5EVVMgKyBDSEFTICsgTk9YICsgUk0gKyANCiAgICAgICAgICAgICAgQUdFICsgRElTICsgUkFEICsgVEFYICsgUFRSQVRJTywgZGF0YT10cmFpbikNCnN1bW1hcnkobGlucmVnKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMNCmxpbnJlZy5wcmVkID0gcHJlZGljdChsaW5yZWcsIG5ld2RhdGE9dGVzdCkNCmxpbnJlZy5zc2UgPSBzdW0oKGxpbnJlZy5wcmVkIC0gdGVzdCRNRURWKV4yKQ0KbGlucmVnLnNzZQ0KYGBgDQoNCiMjIyMgMy42IENyZWF0ZSBhIENBUlQgbW9kZWwNCmBgYHtyfQ0KdHJlZSA9IHJwYXJ0KE1FRFYgfiBMQVQgKyBMT04gKyBDUklNICsgWk4gKyBJTkRVUyArIENIQVMgKyBOT1ggKyBSTSArIA0KICAgICAgICAgICAgICAgQUdFICsgRElTICsgUkFEICsgVEFYICsgUFRSQVRJTywgZGF0YT10cmFpbikNCnBycCh0cmVlKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMNCnRyZWUucHJlZCA9IHByZWRpY3QodHJlZSwgbmV3ZGF0YT10ZXN0KQ0KdHJlZS5zc2UgPSBzdW0oKHRyZWUucHJlZCAtIHRlc3QkTUVEVileMikNCnRyZWUuc3NlDQpgYGANCg0KIyMjIyAzLjcgTG9hZCBsaWJyYXJpZXMgZm9yIGNyb3NzLXZhbGlkYXRpb24NCmBgYHtyfQ0KbGlicmFyeShjYXJldCkNCg0KIyBOdW1iZXIgb2YgZm9sZHMNCnRyLmNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApDQoNCiMgY3AgdmFsdWVzDQpjcC5ncmlkID0gZXhwYW5kLmdyaWQoIC5jcCA9ICgwOjEwKSowLjAwMSkNCg0KIyBDcm9zcy12YWxpZGF0aW9uDQp0ciA9IHRyYWluKE1FRFYgfiBMQVQgKyBMT04gKyBDUklNICsgWk4gKyBJTkRVUyArIENIQVMgKyBOT1ggKyBSTSArIA0KICAgICAgICAgICAgIEFHRSArIERJUyArIFJBRCArIFRBWCArIFBUUkFUSU8sIA0KICAgICAgICAgICBkYXRhID0gdHJhaW4sIG1ldGhvZCA9ICJycGFydCIsIA0KICAgICAgICAgICB0ckNvbnRyb2wgPSB0ci5jb250cm9sLCB0dW5lR3JpZCA9IGNwLmdyaWQpDQp0cg0KDQojIEV4dHJhY3QgdHJlZQ0KYmVzdC50cmVlID0gdHIkZmluYWxNb2RlbA0KcHJwKGJlc3QudHJlZSkNCg0KIyBNYWtlIHByZWRpY3Rpb25zDQpiZXN0LnRyZWUucHJlZCA9IHByZWRpY3QoYmVzdC50cmVlLCBuZXdkYXRhPXRlc3QpDQpiZXN0LnRyZWUuc3NlID0gc3VtKChiZXN0LnRyZWUucHJlZCAtIHRlc3QkTUVEVileMikNCmJlc3QudHJlZS5zc2UNCmBgYA0KDQojIyMjIDMuOCBFbnNlbWJsaW5nDQpgYGB7cn0NCmVuLnByZWQgPSAoYmVzdC50cmVlLnByZWQgKyBsaW5yZWcucHJlZCkvMg0Kc3VtKChlbi5wcmVkIC0gdGVzdCRNRURWKV4yKQ0KYGBgDQoNCg0KYGBge3J9DQojICAgICAgIE1FVEhPRAkgU1NFDQojICAgICAgICAgICBsbQkzMDM3DQojICAgICAgICAgcnBhcnQJNDMyOQ0KIyAgICAgIHJwYXJ0LmN2CTM2NjANCiMgICBsbStycGFydC5jdgkyNTk5ICANCmBgYA0KPGJyPg0KDQojIyMjIOOAkOiojuirluOAkQ0KDQorIGRyYXdpbmcgbWFwIHdpdGggc3BhdGlhbCBkYXRhIA0KICAgICsNCiAgICArDQorIHRyZWUgaW4gc3BhdGlhbCBkYXRhDQogICAgKw0KICAgICsNCisgdHJlZSB2cy4gbGluZWFyIHJlZ3Jlc3Npb24NCiAgICArDQogICAgKw0KKyB0aGUgY29uY2VwdCBvZiBlbnNlbWJsaW5nDQogICAgKw0KICAgICsNCg0KPGJyPg0KDQotIC0gLQ0KDQo8YnI+PGJyPjxicj48YnI+PGJyPg0KDQo8c3R5bGU+DQouY2FwdGlvbiB7DQogIGNvbG9yOiAjNzc3Ow0KICBtYXJnaW4tdG9wOiAxMHB4Ow0KfQ0KcCBjb2RlIHsNCiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7DQp9DQpwcmUgew0KICB3b3JkLWJyZWFrOiBub3JtYWw7DQogIHdvcmQtd3JhcDogbm9ybWFsOw0KICBsaW5lLWhlaWdodDogMTsNCn0NCnByZSBjb2RlIHsNCiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7DQp9DQpwLGxpIHsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCi5yew0KICBsaW5lLWhlaWdodDogMS4yOw0KfQ0KDQp0aXRsZXsNCiAgY29sb3I6ICNjYzAwMDA7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpib2R5ew0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDEsaDIsaDMsaDQsaDV7DQogIGNvbG9yOiAjMDA2NmZmOw0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDQsaDV7DQogIGJhY2tncm91bmQ6ICNjY2ZmZmY7DQp9DQoNCjwvc3R5bGU+DQoNCg0KDQoNCg0KDQo=