# 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)
Sys.setlocale("LC_ALL", "English")
[1] "LC_COLLATE=English_United States.1252;LC_CTYPE=English_United States.1252;LC_MONETARY=English_United States.1252;LC_NUMERIC=C;LC_TIME=English_United States.1252"
rm(list=ls(all=T))
options(digits=4, scipen=12)
library(dplyr)
package 㤼㸱dplyr㤼㸲 was built under R version 3.4.3
Attaching package: 㤼㸱dplyr㤼㸲

The following objects are masked from 㤼㸱package:stats㤼㸲:

    filter, lag

The following objects are masked from 㤼㸱package:base㤼㸲:

    intersect, setdiff, setequal, union

1. Suprime Court Decision

大法官會議
要預測其中一位大法官steven的決策
美國的司法體系有三個層級的法院: * district court、circuit court、supreme court,判決不服就往上上訴。



DV: 1=reverse翻盤、0=affirm維持原判 IDV: * circuit court of origin 最早從哪個法院上來 * issue area * type of peririoner: 上訴者的身分 * ideological: 判決的意識形態(conservative,liberal) * unconsitutional: 是否違憲

決策數的優點: * 不怕缺項 * 不怕共線性 * 易解釋 * Y的分布是比較細碎的、一塊一塊,用決策樹會比用線性模型效果來的好


1.1 Prepare Data

D = read.csv("data/stevens.csv")
library(caTools)
set.seed(3000)
spl = sample.split(D$Reverse, SplitRatio = 0.7)
#依據相同的D$Reverse的比率 分成7:3 #兩份裡D$Reverse的比率是一樣的
#返回boolean
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) 
#設定minbucket,terminal node裡最小資料數,到達minbucket就不在往下分支了

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.

#terminal node裡的資料不一定會完全都是0或1,而是靠比例去判斷
rpart.plot(rpart1,cex=0.6) #cex控制字大小 #rpart.plot是比較好的畫樹方法
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")  
# type = class 直接返回類別
# type = prob 和logistic regression一樣返回機率,要轉成類別就在confusion matrix-> pred = pred > 0.5
x = table(actual = TS$Reverse, pred); x
      pred
actual  0  1
     0 41 36
     1 22 71
sum(diag(x))/sum(x)                         # ACC = .65882
[1] 0.6588

1.5 ROC & AUC

library(ROCR)
PredictROC = predict(rpart1, TS)# predict prob,class預設為prob
head(PredictROC) 
        0      1
1  0.3036 0.6964
3  0.3036 0.6964
4  0.4000 0.6000
6  0.4000 0.6000
8  0.4000 0.6000
21 0.3036 0.6964
#返回0,1兩類別的比例
#決策樹可能一次分好多類,不像邏輯式回歸只有兩類
#? 1.3.4.6...
#測量model在testing data的表現
#prediction是ROCR裡的用法
perf = prediction(PredictROC[,2], TS$Reverse)
perf = performance(perf, "tpr", "fpr")
plot(perf) #TPR與FPR的曲線(也就是ROC)

#用一般colAUC的作法
pred = predict(rpart1, TS)[,2]     # prob. of Reverse = 1         
colAUC(pred, TS$Reverse, T)        # AUC = 0.6927
          [,1]
0 vs. 1 0.6927

1.6 DPP of Decision Tree

source("DPP.R")
auc = DPP(pred, TS$Reverse, 1)     # AUC = 0.6927

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

  • rpart.plot()比起prp(),多了在每個節點裡

    • 類別的比例
    • 資料數量(比例)
    • 以顏色區分代表的類別(同色系代表預測相同的類別,而顏色越重代表比例越高)
  • 一棵決策樹的形狀, 和由它所產生的預測機率分布之間,有什麼關係呢 ? 以上DPP 圖中的分布點數、位置和高度,分別會對應到決策樹的什麼特徵呢?

    • A:每一根直方圖對應到terminal node(沒有少一根,是0.21有重疊兩根),高度是趴數,棕色是紅綠色重疊


1.7 The effect of minbucket

t5 = rpart(Reverse ~ ., TR[,3:9], method="class", minbucket=5)
rpart.plot(t5,cex=0.5) 
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.

t100 = rpart(Reverse ~ ., TR[,3:9], method="class", minbucket=100)
rpart.plot(t100,cex=0.5)
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.

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

  • minbucket是指每個水桶裡資料的最小數量
  • 設的越小,分支就可能越多,但可能導致over fitting
  • 越大,分支就越少,但model可能太簡單
  • 如果兩個模型的ACC相同,越簡單的那個是越好的
決策數的方法參數

決策數的方法參數


1.8 Random Forest Method - randomForest::randomForest()

同時間找出許多小樹,不依靠單一個樹去做決定,以小樹的平均決策作為最後決策的基礎。

random => 代表隨機給每棵樹看到不同的x變項,且每棵樹也會看到不同的資料點(可以重複)

each tree is built from a bagged/bootstrapped sample of data

library(randomForest)
package 㤼㸱randomForest㤼㸲 was built under R version 3.4.4randomForest 4.6-14
Type rfNews() to see new features/changes/bug fixes.

Attaching package: 㤼㸱randomForest㤼㸲

The following object is masked from 㤼㸱package:dplyr㤼㸲:

    combine
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)
#ntree 樹的數量
#nodesize是subset裡資料的最小數(= CART的minbucket)
# Make predictions
#方法一 直接返回class
pred = predict(rf1, TS)
x = table(TS$Reverse, pred) ; x #confusion matrix
   pred
     0  1
  0 43 34
  1 20 73
sum(diag(x)) / sum(x)          # ACC = 0.6824
[1] 0.6824
library(magrittr)
package 㤼㸱magrittr㤼㸲 was built under R version 3.4.3
#方法二 返回類別 2的機率
pred = predict(rf1, TS, type='prob')[,2]
table(TS$Reverse, pred > 0.5) %>% {sum(diag(.))/sum(.)}
[1] 0.6882

1.9 Cross Validation & Parameter Tuning - caret package

library(caret)
package 㤼㸱caret㤼㸲 was built under R version 3.4.4Loading required package: lattice
Loading required package: ggplot2
package 㤼㸱ggplot2㤼㸲 was built under R version 3.4.3
Attaching package: 㤼㸱ggplot2㤼㸲

The following object is masked from 㤼㸱package:randomForest㤼㸲:

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

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

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

  cp    Accuracy  Kappa   
  0.01  0.6165    0.210701
  0.02  0.6265    0.234283
  0.03  0.6214    0.231257
  0.04  0.6290    0.249679
  0.05  0.6340    0.260502
  0.06  0.6442    0.282955
  0.07  0.6442    0.282955
  0.08  0.6442    0.282955
  0.09  0.6442    0.282955
  0.10  0.6442    0.282955
  0.11  0.6442    0.282955
  0.12  0.6442    0.282955
  0.13  0.6442    0.282955
  0.14  0.6442    0.282955
  0.15  0.6442    0.282955
  0.16  0.6442    0.282955
  0.17  0.6442    0.282955
  0.18  0.6442    0.282955
  0.19  0.6442    0.282955
  0.20  0.6237    0.234955
  0.21  0.5555    0.053780
  0.22  0.5455    0.024488
  0.23  0.5404    0.005108
  0.24  0.5404    0.005108
  0.25  0.5454    0.000000
  0.26  0.5454    0.000000
  0.27  0.5454    0.000000
  0.28  0.5454    0.000000
  0.29  0.5454    0.000000
  0.30  0.5454    0.000000
  0.31  0.5454    0.000000
  0.32  0.5454    0.000000
  0.33  0.5454    0.000000
  0.34  0.5454    0.000000
  0.35  0.5454    0.000000
  0.36  0.5454    0.000000
  0.37  0.5454    0.000000
  0.38  0.5454    0.000000
  0.39  0.5454    0.000000
  0.40  0.5454    0.000000
  0.41  0.5454    0.000000
  0.42  0.5454    0.000000
  0.43  0.5454    0.000000
  0.44  0.5454    0.000000
  0.45  0.5454    0.000000
  0.46  0.5454    0.000000
  0.47  0.5454    0.000000
  0.48  0.5454    0.000000
  0.49  0.5454    0.000000
  0.50  0.5454    0.000000

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

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

cp = cost of complexity
多設一個節點,損失下降如果比設定的cp高,就不再往下分支了
cp越高,代表model越不複雜
如果ACC一樣,越簡單的模型是越好的


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.7235
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
  • Tree Bagging - Random Forest randomForest
  • Overfitting : Curse of Complexity
  • Model Parameter : Cost of Compexity
  • Parameter Tuning by CV (caret package)
  • The power of machine vs. The value of humanity

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.671268 0.190170 0.089466 0.043325 0.005771 
# Split the data
library(caTools)
set.seed(88)
spl = sample.split(D$bucket2009, SplitRatio = 0.6)
TR = subset(D, spl)
TS = subset(D, !spl)
# Check Percentages Among Subsets
library(dplyr)
table(TR$bucket2009) %>% prop.table

       1        2        3        4        5 
0.671266 0.190169 0.089468 0.043326 0.005771 
table(TS$bucket2009) %>% prop.table

      1       2       3       4       5 
0.67127 0.19017 0.08946 0.04332 0.00577 
table(D$bucket2009) %>% prop.table

       1        2        3        4        5 
0.671268 0.190170 0.089466 0.043325 0.005771 
# 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.6713 0.1902 0.08947 0.04332 0.005771
[2,] 0.6713 0.1902 0.08947 0.04333 0.005771
[3,] 0.6713 0.1902 0.08946 0.04332 0.005770
# Data Exploration 
mean(TR$age)                 # 平均年紀72.64
[1] 72.64
mean(TR$diabetes)            # 得糖尿病的比例.3809 
[1] 0.3809

2.2 Baseline Model

# Baseline Accuracy
# 在多類別的狀況下,要怎麼預測呢?
#試著用2008資料來預測2009年的看看
acc.base = table(TS$bucket2009, TS$bucket2008) %>% 
  {sum(diag(.)) / sum(.)}    # ACC=.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
     [,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
#用2008猜2009
cm = as.matrix(table(TS$bucket2009, TS$bucket2008))
pen.base = sum(cm*Penalty) / nrow(TS)    
# 平均penalty
# pen: .7386
# if we use the most popular class as baseline
#所有人都猜1
acc.dumb = (table(TS$bucket2009)/nrow(TS))[1]                 
# acc: .6713 
pen.dumb = sum(table(TS$bucket2009) * c(0,2,4,6,8))/nrow(TS)  
# pen: 1.044
#可看出用2008猜2009的 比 baseline 的penalty少

CART Model

library(rpart)
library(rpart.plot)
rpart1 = rpart(bucket2009 ~ ., data=TR[c(1:14, 16)], 
               method="class", cp=0.00005)
#cp是用CV找出來的
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: .71267 <- .68381
# Penalty Error
cm = as.matrix(table(TS$bucket2009, pred))
pen.rpart1 = sum(cm*Penalty)/nrow(TS)  # pen: .75789 <- .7386

CART Model with Customized Loss Matrix

rpart2 = rpart(bucket2009 ~ ., data=TR[c(1:14, 16)], 
               method="class", cp=0.00005, 
               parms=list(loss=Penalty))
#
pred = predict(rpart2, TS, type = "class")
acc.rpart2 = table(TS$bucket2009, pred) %>% 
  {sum(diag(.))/sum(.)}                  # acc.rpart2: .64727   
cm = as.matrix(table(TS$bucket2009, pred))
pen.rpart2 = sum(cm*Penalty)/nrow(TS)    # 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))
# 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)

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

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

Load CART packages

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

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)

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

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

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.729  0.7598    3.068
  0.001  4.745  0.7584    3.105
  0.002  4.756  0.7569    3.091
  0.003  4.807  0.7491    3.151
  0.004  4.876  0.7418    3.230
  0.005  4.917  0.7368    3.281
  0.006  4.954  0.7315    3.322
  0.007  4.899  0.7360    3.290
  0.008  4.933  0.7297    3.323
  0.009  4.906  0.7320    3.308
  0.010  4.906 0.7320   3.308

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

Ensembling

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






LS0tDQp0aXRsZTogIkFTNC0wIOiqsuWgguethuiomCINCmF1dGhvcjogIkIwMzQwMjAwMjcg6Zmz6Z+75Y2JIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KYGBge3J9DQojIHBhY2thZ2VzID0gYygNCiMgICAiZHBseXIiLCJnZ3Bsb3QyIiwiZDNoZWF0bWFwIiwiZ29vZ2xlVmlzIiwiZGV2dG9vbHMiLCJwbG90bHkiLCAieGdib29zdCIsDQojICAgIm1hZ3JpdHRyIiwiY2FUb29scyIsIlJPQ1IiLCJjb3JycGxvdCIsICJycGFydCIsICJycGFydC5wbG90IiwNCiMgICAiZG9QYXJhbGxlbCIsICJjYXJldCIsICJnbG1uZXQiLCAiTWF0cml4IiwgImUxMDcxIiwgInJhbmRvbUZvcmVzdCINCiMgICApDQojIGV4aXN0aW5nID0gYXMuY2hhcmFjdGVyKGluc3RhbGxlZC5wYWNrYWdlcygpWywxXSkNCiMgZm9yKHBrZyBpbiBwYWNrYWdlc1shKHBhY2thZ2VzICVpbiUgZXhpc3RpbmcpXSkgaW5zdGFsbC5wYWNrYWdlcyhwa2cpDQpgYGANCg0KYGBge3IgZWNobz1ULCBtZXNzYWdlPUYsIGNhY2hlPUYsIHdhcm5pbmc9Rn0NClN5cy5zZXRsb2NhbGUoIkxDX0FMTCIsICJFbmdsaXNoIikNCnJtKGxpc3Q9bHMoYWxsPVQpKQ0Kb3B0aW9ucyhkaWdpdHM9NCwgc2NpcGVuPTEyKQ0KbGlicmFyeShkcGx5cikNCmBgYA0KDQotIC0gLQ0KDQojIyMgMS4gU3VwcmltZSBDb3VydCBEZWNpc2lvbg0KDQrlpKfms5XlrpjmnIPorbA8YnI+DQropoHpoJDmuKzlhbbkuK3kuIDkvY3lpKfms5XlrphzdGV2ZW7nmoTmsbrnrZY8YnI+DQrnvo7lnIvnmoTlj7jms5Xpq5Tns7vmnInkuInlgIvlsaTntJrnmoTms5XpmaI6DQoqIGRpc3RyaWN0IGNvdXJ044CBY2lyY3VpdCBjb3VydOOAgXN1cHJlbWUgY291cnTvvIzliKTmsbrkuI3mnI3lsLHlvoDkuIrkuIroqLTjgIINCg0KPGJyPjxicj4NCg0KRFY6IDE9cmV2ZXJzZee/u+ebpOOAgTA9YWZmaXJt57at5oyB5Y6f5YikDQpJRFY6DQoqIGNpcmN1aXQgY291cnQgb2Ygb3JpZ2luIOacgOaXqeW+nuWTquWAi+azlemZouS4iuS+hg0KKiBpc3N1ZSBhcmVhDQoqIHR5cGUgb2YgcGVyaXJpb25lcjog5LiK6Ki06ICF55qE6Lqr5YiGDQoqIGlkZW9sb2dpY2FsOiDliKTmsbrnmoTmhI/orZjlvaLmhYsoY29uc2VydmF0aXZlLGxpYmVyYWwpDQoqIHVuY29uc2l0dXRpb25hbDog5piv5ZCm6YGV5oayDQoNCuaxuuetluaVuOeahOWEqum7nu+8mg0KKiDkuI3mgJXnvLrpoIUNCiog5LiN5oCV5YWx57ea5oCnDQoqIOaYk+ino+mHiw0KKiBZ55qE5YiG5biD5piv5q+U6LyD57Sw56KO55qE44CB5LiA5aGK5LiA5aGK77yM55So5rG6562W5qi55pyD5q+U55So57ea5oCn5qih5Z6L5pWI5p6c5L6G55qE5aW9DQoNCg0KDQoNCg0KPGJyPg0KDQojIyMjIDEuMSBQcmVwYXJlIERhdGENCmBgYHtyfQ0KRCA9IHJlYWQuY3N2KCJkYXRhL3N0ZXZlbnMuY3N2IikNCg0KbGlicmFyeShjYVRvb2xzKQ0Kc2V0LnNlZWQoMzAwMCkNCnNwbCA9IHNhbXBsZS5zcGxpdChEJFJldmVyc2UsIFNwbGl0UmF0aW8gPSAwLjcpDQoj5L6d5pOa55u45ZCM55qERCRSZXZlcnNl55qE5q+U546HIOWIhuaIkDc6MyAj5YWp5Lu96KOhRCRSZXZlcnNl55qE5q+U546H5piv5LiA5qij55qEDQoj6L+U5ZueYm9vbGVhbg0KDQpUUiA9IHN1YnNldChELCBzcGwpDQpUUyA9IHN1YnNldChELCAhc3BsKQ0KDQpgYGANCg0KIyMjIyAxLjIgQ0FSVCAoQ2xhc3NpZmljYXRpb24gJiBSZWdyZXNzaW9uIFRyZWUpIC0gYHJwYXJ0OjpycGFydCgpYA0KYGBge3J9DQpsaWJyYXJ5KHJwYXJ0KQ0KcnBhcnQxID0gcnBhcnQoUmV2ZXJzZSB+IC4sIFRSWywzOjldLCAgICAgICAgICANCiAgICAgICAgICAgICAgICMgc2ltcGxpZnkgdGhlIGZvcm11bGENCiAgICAgICAgICAgICAgIG1ldGhvZD0iY2xhc3MiLCBtaW5idWNrZXQ9MjUpIA0KI+ioreWumm1pbmJ1Y2tldO+8jHRlcm1pbmFsIG5vZGXoo6HmnIDlsI/os4fmlpnmlbjvvIzliLDpgZRtaW5idWNrZXTlsLHkuI3lnKjlvoDkuIvliIbmlK/kuoYNCmBgYA0KDQojIyMjIDEuMyBQbG90IERlY2lzaW9uIFRyZWUNCg0KYGBge3J9DQoNCmxpYnJhcnkocnBhcnQucGxvdCkNCnBycChycGFydDEpIA0KI3Rlcm1pbmFsIG5vZGXoo6HnmoTos4fmlpnkuI3kuIDlrprmnIPlrozlhajpg73mmK8w5oiWMe+8jOiAjOaYr+mdoOavlOS+i+WOu+WIpOaWtw0KcnBhcnQucGxvdChycGFydDEsY2V4PTAuNikgI2NleOaOp+WItuWtl+Wkp+WwjyAjcnBhcnQucGxvdOaYr+avlOi8g+WlveeahOeVq+aoueaWueazlQ0KDQpgYGANCg0KIyMjIyAxLjQgTWFrZSBQcmVkaWN0aW9uDQpgYGB7cn0NCnByZWQgPSBwcmVkaWN0KHJwYXJ0MSwgVFMsIHR5cGUgPSAiY2xhc3MiKSAgDQojIHR5cGUgPSBjbGFzcyDnm7TmjqXov5Tlm57poZ7liKUNCiMgdHlwZSA9IHByb2Ig5ZKMbG9naXN0aWMgcmVncmVzc2lvbuS4gOaoo+i/lOWbnuapn+eOh++8jOimgei9ieaIkOmhnuWIpeWwseWcqGNvbmZ1c2lvbiBtYXRyaXgtPiBwcmVkID0gcHJlZCA+IDAuNQ0KeCA9IHRhYmxlKGFjdHVhbCA9IFRTJFJldmVyc2UsIHByZWQpOyB4DQpzdW0oZGlhZyh4KSkvc3VtKHgpICAgICAgICAgICAgICAgICAgICAgICAgICMgQUNDID0gLjY1ODgyDQpgYGANCg0KIyMjIyAxLjUgUk9DICYgQVVDDQpgYGB7cn0NCmxpYnJhcnkoUk9DUikNClByZWRpY3RST0MgPSBwcmVkaWN0KHJwYXJ0MSwgVFMpIyBwcmVkaWN0IHByb2LvvIxjbGFzc+mgkOioreeCunByb2INCmhlYWQoUHJlZGljdFJPQykgDQoj6L+U5ZueMCwx5YWp6aGe5Yil55qE5q+U5L6LDQoj5rG6562W5qi55Y+v6IO95LiA5qyh5YiG5aW95aSa6aGe77yM5LiN5YOP6YKP6Lyv5byP5Zue5q245Y+q5pyJ5YWp6aGeDQojPyAxLjMuNC42Li4uDQpgYGANCg0KYGBge3J9DQoj5ris6YePbW9kZWzlnKh0ZXN0aW5nIGRhdGHnmoTooajnj74NCiNwcmVkaWN0aW9u5pivUk9DUuijoeeahOeUqOazlQ0KcGVyZiA9IHByZWRpY3Rpb24oUHJlZGljdFJPQ1ssMl0sIFRTJFJldmVyc2UpDQpwZXJmID0gcGVyZm9ybWFuY2UocGVyZiwgInRwciIsICJmcHIiKQ0KcGxvdChwZXJmKSAjVFBS6IiHRlBS55qE5puy57eaKOS5n+WwseaYr1JPQykNCmBgYA0KDQoNCmBgYHtyfQ0KI+eUqOS4gOiIrGNvbEFVQ+eahOS9nOazlQ0KcHJlZCA9IHByZWRpY3QocnBhcnQxLCBUUylbLDJdICAgICAjIHByb2IuIG9mIFJldmVyc2UgPSAxICAgICAgICAgDQpjb2xBVUMocHJlZCwgVFMkUmV2ZXJzZSwgVCkgICAgICAgICMgQVVDID0gMC42OTI3DQpgYGANCg0KDQojIyMjIDEuNiBEUFAgb2YgRGVjaXNpb24gVHJlZQ0KYGBge3J9DQpzb3VyY2UoIkRQUC5SIikNCmF1YyA9IERQUChwcmVkLCBUUyRSZXZlcnNlLCAxKSAgICAgIyBBVUMgPSAwLjY5MjcNCmBgYA0KDQojIyMjIOOAkFFVSVotMeOAkeavlOi8g+S7peS4imBwcnAoKWDjgIFgcnBhcnQucGxvdCgpYOWSjGBEUFAoKWDpgJnlub7lvLXlnJblvaINCg0KKyBycGFydC5wbG90KCnmr5TotbdwcnAoKe+8jOWkmuS6huWcqOavj+WAi+evgOm7nuijoQ0KICAgIA0KICAgICsg6aGe5Yil55qE5q+U5L6LDQogICAgKyDos4fmlpnmlbjph48o5q+U5L6LKQ0KICAgICsg5Lul6aGP6Imy5Y2A5YiG5Luj6KGo55qE6aGe5YilKOWQjOiJsuezu+S7o+ihqOmgkOa4rOebuOWQjOeahOmhnuWIpe+8jOiAjOmhj+iJsui2iumHjeS7o+ihqOavlOS+i+i2iumrmCkNCiAgDQorIOS4gOajteaxuuetluaoueeahOW9oueLgO+8jCDlkoznlLHlroPmiYDnlKLnlJ/nmoTpoJDmuKzmqZ/njofliIbluIPkuYvplpPvvIzmnInku4Dpurzpl5zkv4LlkaIgPyAg5Lul5LiKRFBQIOWcluS4reeahOWIhuW4g+m7nuaVuOOAgeS9jee9ruWSjOmrmOW6pu+8jOWIhuWIpeacg+WwjeaHieWIsOaxuuetluaoueeahOS7gOm6vOeJueW+teWRoj8NCg0KICAgICsgQTrmr4/kuIDmoLnnm7TmlrnlnJblsI3mh4nliLB0ZXJtaW5hbCBub2RlKOaykuacieWwkeS4gOague+8jOaYrzAuMjHmnInph43nlorlhanmoLkp77yM6auY5bqm5piv6La05pW477yM5qOV6Imy5piv57SF57ag6Imy6YeN55aKDQoNCg0KPGJyPg0KDQojIyMjIDEuNyBUaGUgZWZmZWN0IG9mIGBtaW5idWNrZXRgDQpgYGB7cn0NCnQ1ID0gcnBhcnQoUmV2ZXJzZSB+IC4sIFRSWywzOjldLCBtZXRob2Q9ImNsYXNzIiwgbWluYnVja2V0PTUpDQpycGFydC5wbG90KHQ1LGNleD0wLjUpIA0KYGBgDQoNCmBgYHtyIGZpZy5oZWlnaHQ9Mn0NCnQxMDAgPSBycGFydChSZXZlcnNlIH4gLiwgVFJbLDM6OV0sIG1ldGhvZD0iY2xhc3MiLCBtaW5idWNrZXQ9MTAwKQ0KcnBhcnQucGxvdCh0MTAwLGNleD0wLjUpDQpgYGANCg0KDQojIyMj44CQUVVJWi0y44CRQmFzZWQgb24gdGhlIDIgcGxvdHMgYWJvdmUsIHdoYXQgaXMgdGhlIGVmZmVjdCBvZiBgbWluYnVja2V0YD8gDQoNCisgbWluYnVja2V05piv5oyH5q+P5YCL5rC05qG26KOh6LOH5paZ55qE5pyA5bCP5pW46YePDQorIOioreeahOi2iuWwj++8jOWIhuaUr+WwseWPr+iDvei2iuWkmu+8jOS9huWPr+iDveWwjuiHtG92ZXIgZml0dGluZw0KKyDotorlpKfvvIzliIbmlK/lsLHotorlsJHvvIzkvYZtb2RlbOWPr+iDveWkquewoeWWrg0KKyDlpoLmnpzlhanlgIvmqKHlnovnmoRBQ0Pnm7jlkIzvvIzotornsKHllq7nmoTpgqPlgIvmmK/otorlpb3nmoQNCg0KIVvmsbrnrZbmlbjnmoTmlrnms5Xlj4PmlbhdKHJlcy81LlBORykNCg0KDQo8YnI+DQoNCiMjIyMgMS44IFJhbmRvbSBGb3Jlc3QgTWV0aG9kIC0gYHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KClgDQoNCuWQjOaZgumWk+aJvuWHuuioseWkmuWwj+aoue+8jOS4jeS+nemdoOWWruS4gOWAi+aoueWOu+WBmuaxuuWumu+8jOS7peWwj+aoueeahOW5s+Wdh+axuuetluS9nOeCuuacgOW+jOaxuuetlueahOWfuuekjuOAgjxicj4NCg0KcmFuZG9tID0+IOS7o+ihqOmaqOapn+e1puavj+ajteaoueeci+WIsOS4jeWQjOeahHjororpoIXvvIzkuJTmr4/mo7XmqLnkuZ/mnIPnnIvliLDkuI3lkIznmoTos4fmlpnpu54o5Y+v5Lul6YeN6KSHKTxicj4NCg0KZWFjaCB0cmVlIGlzIGJ1aWx0IGZyb20gYSAqYmFnZ2VkL2Jvb3RzdHJhcHBlZCogc2FtcGxlIG9mIGRhdGE8YnI+DQoNCmBgYHtyfQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQpUUiRSZXZlcnNlID0gYXMuZmFjdG9yKFRSJFJldmVyc2UpICAjIENvbnZlcnQgb3V0Y29tZSB0byBmYWN0b3INClRTJFJldmVyc2UgPSBhcy5mYWN0b3IoVFMkUmV2ZXJzZSkNCg0KIyBCdWlsZCBtb2RlbA0KcmYxID0gcmFuZG9tRm9yZXN0KFJldmVyc2UgfiAuLCBUUlssMzo5XSwgbnRyZWU9MjAwLCBub2Rlc2l6ZT0yNSkNCiNudHJlZSDmqLnnmoTmlbjph48NCiNub2Rlc2l6ZeaYr3N1YnNldOijoeizh+aWmeeahOacgOWwj+aVuCg9IENBUlTnmoRtaW5idWNrZXQpDQoNCiMgTWFrZSBwcmVkaWN0aW9ucw0KI+aWueazleS4gCDnm7TmjqXov5Tlm55jbGFzcw0KcHJlZCA9IHByZWRpY3QocmYxLCBUUykNCnggPSB0YWJsZShUUyRSZXZlcnNlLCBwcmVkKSA7IHggI2NvbmZ1c2lvbiBtYXRyaXgNCnN1bShkaWFnKHgpKSAvIHN1bSh4KSAgICAgICAgICAjIEFDQyA9IDAuNjgyNA0KYGBgDQoNCmBgYHtyfQ0KbGlicmFyeShtYWdyaXR0cikNCiPmlrnms5Xkuowg6L+U5Zue6aGe5YilIDLnmoTmqZ/njocNCnByZWQgPSBwcmVkaWN0KHJmMSwgVFMsIHR5cGU9J3Byb2InKVssMl0NCnRhYmxlKFRTJFJldmVyc2UsIHByZWQgPiAwLjUpICU+JSB7c3VtKGRpYWcoLikpL3N1bSguKX0NCmBgYA0KDQoNCiMjIyMgMS45IENyb3NzIFZhbGlkYXRpb24gJiBQYXJhbWV0ZXIgVHVuaW5nIC0gYGNhcmV0YCBwYWNrYWdlDQpgYGB7cn0NCmxpYnJhcnkoY2FyZXQpDQojdHJhaW5Db250cm9sIA0KbnVtRm9sZHMgPSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIG51bWJlcj0xMCkgIyAxMCBmb2xkIENWDQpjcEdyaWQgPSBleHBhbmQuZ3JpZChjcCA9IHNlcSgwLjAxLDAuNSwwLjAxKSkgICAjIHBhcmFtZXRlciBjb21iaW5hdGlvbg0KDQpjdjEgPSB0cmFpbihSZXZlcnNlIH4gLiwgVFJbLDM6OV0sIG1ldGhvZCA9ICJycGFydCIsIA0KICAgICAgICAgICAgdHJDb250cm9sPW51bUZvbGRzLCB0dW5lR3JpZD1jcEdyaWQpDQpjdjE7IHBsb3QoY3YxKQ0KYGBgDQoNCiMjIyPjgJBRVUlaLTPjgJFXaGljaCBgY3BgIHdoaWNoIHdlIHNob3VsZCB3ZSBjaG9vc2U/IFdoeT8gDQpjcCA9IGNvc3Qgb2YgY29tcGxleGl0eTxicj4NCuWkmuioreS4gOWAi+evgOm7nu+8jOaQjeWkseS4i+mZjeWmguaenOavlOioreWumueahGNw6auY77yM5bCx5LiN5YaN5b6A5LiL5YiG5pSv5LqGPGJyPg0KY3Dotorpq5jvvIzku6Pooahtb2RlbOi2iuS4jeikh+mbnDxicj4NCuWmguaenEFDQ+S4gOaoo++8jOi2iuewoeWWrueahOaooeWei+aYr+i2iuWlveeahDxicj4NCg0KDQo8YnI+DQoNCiMjIyMgMS4xMCBUaGUgRmluYWwgTW9kZWwNCmBgYHtyfQ0KcnBhcnQyID0gcnBhcnQoUmV2ZXJzZSB+IC4sIFRSWywzOjldLCBtZXRob2Q9ImNsYXNzIiwgY3A9MC4xOSkNCnByZWQgPSBwcmVkaWN0KHJwYXJ0MiwgVFMsIHR5cGU9J3Byb2InKVssMl0NCnRhYmxlKFRTJFJldmVyc2UsIHByZWQgPiAwLjUpICU+JSB7c3VtKGRpYWcoLikpIC8gc3VtKC4pfSAgIyAwLjcyMzUNCmBgYA0KDQpgYGB7ciBmaWcud2lkdGg9M30NCnJwYXJ0LnBsb3QocnBhcnQyKQ0KYGBgDQoNCiMjIyPjgJDoqI7oq5bvvJrjgJENCg0KKyBDbGFzc2lmaWNhdGlvbiAmIFJlZ3Jlc3Npb24gVHJlZSAgYHJwYXJ0YA0KICArDQogICsNCisgVHJlZSBCYWdnaW5nIC0gUmFuZG9tIEZvcmVzdCBgcmFuZG9tRm9yZXN0YCAgDQorIE92ZXJmaXR0aW5nIDogQ3Vyc2Ugb2YgQ29tcGxleGl0eQ0KKyBNb2RlbCBQYXJhbWV0ZXIgOiBDb3N0IG9mIENvbXBleGl0eSANCisgUGFyYW1ldGVyIFR1bmluZyBieSBDViAoYGNhcmV0YCBwYWNrYWdlKQ0KKyBUaGUgcG93ZXIgb2YgbWFjaGluZSB2cy4gVGhlIHZhbHVlIG9mIGh1bWFuaXR5DQoNCi0gLSAtDQoNCiMjIyAyIEQySGF3a0V5ZQ0KDQojIyMjIDIuMSBQcmVwYXJlICYgRXhhbWluZSBEYXRhIA0KYGBge3J9DQpybShsaXN0PWxzKGFsbD1UUlVFKSkNCkQgPSByZWFkLmNzdigiZGF0YS9DbGFpbXNEYXRhLmNzdiIpDQoNCiMgUGVyY2VudGFnZSBvZiBwYXRpZW50cyBpbiBlYWNoIGNvc3QgYnVja2V0DQp0YWJsZShEJGJ1Y2tldDIwMDkpL25yb3coRCkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBTcGxpdCB0aGUgZGF0YQ0KbGlicmFyeShjYVRvb2xzKQ0Kc2V0LnNlZWQoODgpDQpzcGwgPSBzYW1wbGUuc3BsaXQoRCRidWNrZXQyMDA5LCBTcGxpdFJhdGlvID0gMC42KQ0KVFIgPSBzdWJzZXQoRCwgc3BsKQ0KVFMgPSBzdWJzZXQoRCwgIXNwbCkNCmBgYA0KDQpgYGB7cn0NCiMgQ2hlY2sgUGVyY2VudGFnZXMgQW1vbmcgU3Vic2V0cw0KbGlicmFyeShkcGx5cikNCnRhYmxlKFRSJGJ1Y2tldDIwMDkpICU+JSBwcm9wLnRhYmxlDQp0YWJsZShUUyRidWNrZXQyMDA5KSAlPiUgcHJvcC50YWJsZQ0KdGFibGUoRCRidWNrZXQyMDA5KSAlPiUgcHJvcC50YWJsZQ0KDQojIHNhcHBseSgpOiBBcHBseSBGdW5jdGlvbiAmIEFnZ3JlZ2F0ZSBPdXRwdXQNCnNhcHBseShsaXN0KEQsIFRSLCBUUyksIGZ1bmN0aW9uKHgpIA0KICB0YWJsZSh4JGJ1Y2tldDIwMDkpICU+JSBwcm9wLnRhYmxlKSAlPiUgdA0KYGBgDQoNCmBgYHtyfQ0KIyBEYXRhIEV4cGxvcmF0aW9uIA0KbWVhbihUUiRhZ2UpICAgICAgICAgICAgICAgICAjIOW5s+Wdh+W5tOe0gDcyLjY0DQptZWFuKFRSJGRpYWJldGVzKSAgICAgICAgICAgICMg5b6X57OW5bC/55eF55qE5q+U5L6LLjM4MDkgDQpgYGANCg0KIyMjIyAyLjIgQmFzZWxpbmUgTW9kZWwgDQpgYGB7cn0NCiMgQmFzZWxpbmUgQWNjdXJhY3kNCiMg5Zyo5aSa6aGe5Yil55qE54uA5rOB5LiL77yM6KaB5oCO6bq86aCQ5ris5ZGiPw0KDQoj6Kmm6JGX55SoMjAwOOizh+aWmeS+humgkOa4rDIwMDnlubTnmoTnnIvnnIsNCmFjYy5iYXNlID0gdGFibGUoVFMkYnVja2V0MjAwOSwgVFMkYnVja2V0MjAwOCkgJT4lIA0KICB7c3VtKGRpYWcoLikpIC8gc3VtKC4pfSAgICAjIEFDQz0uNjgzOA0KYGBgDQoNCmBgYHtyfQ0KIyBQZW5hbHR5IE1hdHJpeCjpmqjokZfllYbmpa3mg4XlooPlnKjoqr/mlbQpDQpQZW5hbHR5ID0gbWF0cml4KGMoICPlnKjmraTmg4XlooPkuIvvvIznjJzpq5jmr5TnjJzkvY7lmrTph40o5oey572w6YeNKQ0KICAwLDEsMiwzLDQsDQogIDIsMCwxLDIsMywNCiAgNCwyLDAsMSwyLA0KICA2LDQsMiwwLDEsDQogIDgsNiw0LDIsMCksIGJ5cm93PVRSVUUsIG5yb3c9NSkNClBlbmFsdHkNCmBgYA0KDQpgYGB7cn0NCiMgUGVuYWx0eSBFcnJvciBvZiBCYXNlbGluZSBNZXRob2QNCiPnlKgyMDA454ycMjAwOQ0KY20gPSBhcy5tYXRyaXgodGFibGUoVFMkYnVja2V0MjAwOSwgVFMkYnVja2V0MjAwOCkpDQoNCnBlbi5iYXNlID0gc3VtKGNtKlBlbmFsdHkpIC8gbnJvdyhUUykgICAgDQojIOW5s+Wdh3BlbmFsdHkNCiMgcGVuOiAuNzM4Ng0KYGBgDQoNCmBgYHtyfQ0KIyBpZiB3ZSB1c2UgdGhlIG1vc3QgcG9wdWxhciBjbGFzcyBhcyBiYXNlbGluZQ0KI+aJgOacieS6uumDveeMnDENCmFjYy5kdW1iID0gKHRhYmxlKFRTJGJ1Y2tldDIwMDkpL25yb3coVFMpKVsxXSAgICAgICAgICAgICAgICAgDQojIGFjYzogLjY3MTMgDQpwZW4uZHVtYiA9IHN1bSh0YWJsZShUUyRidWNrZXQyMDA5KSAqIGMoMCwyLDQsNiw4KSkvbnJvdyhUUykgIA0KIyBwZW46IDEuMDQ0DQoNCiPlj6/nnIvlh7rnlKgyMDA454ycMjAwOeeahCDmr5QgYmFzZWxpbmUg55qEcGVuYWx0eeWwkQ0KYGBgDQoNCiMjIyMgQ0FSVCBNb2RlbA0KYGBge3J9DQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KcnBhcnQxID0gcnBhcnQoYnVja2V0MjAwOSB+IC4sIGRhdGE9VFJbYygxOjE0LCAxNildLCANCiAgICAgICAgICAgICAgIG1ldGhvZD0iY2xhc3MiLCBjcD0wLjAwMDA1KQ0KI2Nw5piv55SoQ1bmib7lh7rkvobnmoQNCnBycChycGFydDEpDQpgYGANCg0KYGBge3J9DQojIE1ha2UgcHJlZGljdGlvbnMNCnByZWQgPSBwcmVkaWN0KHJwYXJ0MSwgbmV3ZGF0YSA9IFRTLCB0eXBlID0gImNsYXNzIikNCmFjYy5ycGFydDEgPSB0YWJsZShUUyRidWNrZXQyMDA5LCBwcmVkKSAlPiUgDQogIHsgc3VtKGRpYWcoLikpIC9ucm93KFRTKX0gICAgICAgICAgICAjIGFjYzogLjcxMjY3IDwtIC42ODM4MQ0KDQojIFBlbmFsdHkgRXJyb3INCmNtID0gYXMubWF0cml4KHRhYmxlKFRTJGJ1Y2tldDIwMDksIHByZWQpKQ0KcGVuLnJwYXJ0MSA9IHN1bShjbSpQZW5hbHR5KS9ucm93KFRTKSAgIyBwZW46IC43NTc4OSA8LSAuNzM4Ng0KYGBgDQoNCiMjIyMgQ0FSVCBNb2RlbCB3aXRoIEN1c3RvbWl6ZWQgTG9zcyBNYXRyaXgNCmBgYHtyfQ0KcnBhcnQyID0gcnBhcnQoYnVja2V0MjAwOSB+IC4sIGRhdGE9VFJbYygxOjE0LCAxNildLCANCiAgICAgICAgICAgICAgIG1ldGhvZD0iY2xhc3MiLCBjcD0wLjAwMDA1LCANCiAgICAgICAgICAgICAgIHBhcm1zPWxpc3QobG9zcz1QZW5hbHR5KSkNCiMNCg0KcHJlZCA9IHByZWRpY3QocnBhcnQyLCBUUywgdHlwZSA9ICJjbGFzcyIpDQphY2MucnBhcnQyID0gdGFibGUoVFMkYnVja2V0MjAwOSwgcHJlZCkgJT4lIA0KICB7c3VtKGRpYWcoLikpL3N1bSguKX0gICAgICAgICAgICAgICAgICAjIGFjYy5ycGFydDI6IC42NDcyNyAgIA0KDQpjbSA9IGFzLm1hdHJpeCh0YWJsZShUUyRidWNrZXQyMDA5LCBwcmVkKSkNCnBlbi5ycGFydDIgPSBzdW0oY20qUGVuYWx0eSkvbnJvdyhUUykgICAgIyBwZW4ucnBhcnQyOiAuNjQxODIgDQpgYGANCg0KYGBge3J9DQojIFN1bW1hcnkNCmRhdGEuZnJhbWUoDQogIGFjY3VyYWN5ID0gYyhhY2MuZHVtYiwgYWNjLmJhc2UsIGFjYy5ycGFydDEsIGFjYy5ycGFydDIpLA0KICBwZW5hbHR5ID0gYyhwZW4uZHVtYiwgcGVuLmJhc2UsIHBlbi5ycGFydDEsIHBlbi5ycGFydDIpLA0KICByb3cubmFtZXMgPSBjKCJkdW1iIiwiYmFzZSIsIkNBUlQiLCJDQVJUIHcvTG9zc014IikpDQojDQpgYGANCg0KIyMjIyDjgJDoqI7oq5bjgJENCg0KKyA1IGNsYXNzZXM6IDVYNSBjb25mdXNzaW9uL3BlbmFsdHkocGF5b2ZmKSBtYXRyaWNlcyANCisgZHVtYiB2cy4gc21hcnQgYmFzZWxpbmUNCisgbW9kZWwxOiBhY2N1cmFjeSB1cCwgcGVuYWx0eSB1cA0KKyBtb2RlbDI6IGFjY3VyYWN5IGRvd24sIHBlbmFsdHkgZG93bg0KKyBhY2N1cmFjeSAhPSBwcm9maXRhYmlsaXR5DQorICJjdXN0b21pemVkIiBtb2RlbCBvcHRpbWl6ZSBjcml0ZXJpYSBpbiBjbGFzc2lmaWNhdGlvbiBtZXRob2RzIChub3QgYWx3YXlzIGF2YWlsYWJsZSkNCisgVGhpcyBDVi9QVCBwcm9jZXNzIGlzIG92ZXIgc2ltcGxpZmllZA0KKyB3ZSB3aWxsIGNvdmVyIGEgYmV0dGVyIHByb2Nlc3MgbmV4dCB0aW1lDQoNCi0gLSAtDQoNCiMjIyAzIEJvc3RvbiBIb3VzZSBQcmljZQ0KDQpgYGB7cn0NCnJtKGxpc3Q9bHMoYWxsPVRSVUUpKQ0KRCA9IHJlYWQuY3N2KCJkYXRhL2Jvc3Rvbi5jc3YiKQ0KYGBgDQoNCiMjIyMgMy4xIEV4YW1pbmUgRGF0YQ0KYGBge3J9DQojIFBsb3Qgb2JzZXJ2YXRpb25zDQpwbG90KEQkTE9OLCBEJExBVCkNCg0KIyBUcmFjdHMgYWxvbmdzaWRlIHRoZSBDaGFybGVzIFJpdmVyDQp3aXRoKEQsIA0KICAgICBwb2ludHMoTE9OW0NIQVM9PTFdLCBMQVRbQ0hBUz09MV0sIGNvbD0iYmx1ZSIsIHBjaD0xOSkpDQoNCiMgUGxvdCBNSVQNCndpdGgoRCwgDQogICAgIHBvaW50cyhMT05bVFJBQ1Q9PTM1MzFdLCBMQVRbVFJBQ1Q9PTM1MzFdLCBjb2w9InJlZCIsIHBjaD0xOSkgKQ0KDQojIFBsb3QgcG9sdXRpb24NCnN1bW1hcnkoRCROT1gpDQpwb2ludHMoRCRMT05bRCROT1g+PTAuNTVdLCBEJExBVFtEJE5PWD49MC41NV0sIA0KICAgICAgIGNvbD0iZ3JlZW4iLCBwY2g9MjApDQoNCiMgUGxvdCBwcmljZXMNCnBsb3QoRCRMT04sIEQkTEFUKQ0Kc3VtbWFyeShEJE1FRFYpDQpwb2ludHMoRCRMT05bRCRNRURWPj0yMS4yXSwgRCRMQVRbRCRNRURWPj0yMS4yXSwgDQogICAgICAgY29sPSJyZWQiLCBwY2g9MjApDQpgYGANCg0KIyMjIyBMaW5lYXIgUmVncmVzc2lvbiB1c2luZyBMQVQgYW5kIExPTg0KYGBge3J9DQpsYXRsb25sbSA9IGxtKE1FRFYgfiBMQVQgKyBMT04sIGRhdGE9RCkNCnN1bW1hcnkobGF0bG9ubG0pDQpgYGANCg0KIyMjIyBWaXN1YWxpemUgcmVncmVzc2lvbiBvdXRwdXQNCmBgYHtyfQ0KcGxvdChEJExPTiwgRCRMQVQpDQpwb2ludHMoRCRMT05bRCRNRURWPj0yMS4yXSwgRCRMQVRbRCRNRURWPj0yMS4yXSwgDQogICAgICAgY29sPSJyZWQiLCBwY2g9MjApDQoNCiMgbGF0bG9ubG0kZml0dGVkLnZhbHVlcw0KcG9pbnRzKEQkTE9OW2xhdGxvbmxtJGZpdHRlZC52YWx1ZXMgPj0gMjEuMl0sIA0KICAgICAgIEQkTEFUW2xhdGxvbmxtJGZpdHRlZC52YWx1ZXMgPj0gMjEuMl0sIA0KICAgICAgIGNvbD0iYmx1ZSIsIHBjaD0ieCIpDQpgYGANCg0KIyBMb2FkIENBUlQgcGFja2FnZXMNCmBgYHtyfQ0KbGlicmFyeShycGFydCkNCmxpYnJhcnkocnBhcnQucGxvdCkNCiMgQ0FSVCBtb2RlbA0KbGF0bG9udHJlZSA9IHJwYXJ0KE1FRFYgfiBMQVQgKyBMT04sIGRhdGE9RCkNCnBycChsYXRsb250cmVlKQ0KDQojIFZpc3VhbGl6ZSBvdXRwdXQNCnBsb3QoRCRMT04sIEQkTEFUKQ0KcG9pbnRzKEQkTE9OW0QkTUVEVj49MjEuMl0sIEQkTEFUW0QkTUVEVj49MjEuMl0sIA0KICAgICAgIGNvbD0icmVkIiwgcGNoPTIwKQ0KDQpmaXR0ZWR2YWx1ZXMgPSBwcmVkaWN0KGxhdGxvbnRyZWUpDQpwb2ludHMoRCRMT05bZml0dGVkdmFsdWVzPjIxLjJdLCANCiAgICAgICBEJExBVFtmaXR0ZWR2YWx1ZXM+PTIxLjJdLCBjb2w9ImJsdWUiLCBwY2g9IngiKQ0KYGBgDQoNCiMjIyMgU2ltcGxpZnkgdHJlZSBieSBpbmNyZWFzaW5nIG1pbmJ1Y2tldA0KYGBge3J9DQpsYXRsb250cmVlID0gcnBhcnQoTUVEViB+IExBVCArIExPTiwgZGF0YT1ELCBtaW5idWNrZXQ9NTApDQpycGFydC5wbG90KGxhdGxvbnRyZWUpDQpgYGANCg0KYGBge3J9DQojIFZpc3VhbGl6ZSBPdXRwdXQNCnBsb3QoRCRMT04sRCRMQVQpDQphYmxpbmUodj0tNzEuMDcpDQphYmxpbmUoaD00Mi4yMSkNCmFibGluZShoPTQyLjE3KQ0KcG9pbnRzKEQkTE9OW0QkTUVEVj49MjEuMl0sIA0KICAgICAgIEQkTEFUW0QkTUVEVj49MjEuMl0sIGNvbD0icmVkIiwgcGNoPTIwKQ0KYGBgDQoNCiMjIyMgVXNlIGFsbCB0aGUgdmFyaWFibGVzDQpgYGB7cn0NCiMgU3BsaXQgdGhlIGRhdGENCmxpYnJhcnkoY2FUb29scykNCnNldC5zZWVkKDEyMykNCnNwbGl0ID0gc2FtcGxlLnNwbGl0KEQkTUVEViwgU3BsaXRSYXRpbyA9IDAuNykNCnRyYWluID0gc3Vic2V0KEQsIHNwbGl0PT1UUlVFKQ0KdGVzdCA9IHN1YnNldChELCBzcGxpdD09RkFMU0UpDQoNCiMgQ3JlYXRlIGxpbmVhciByZWdyZXNzaW9uDQpsaW5yZWcgPSBsbShNRURWIH4gTEFUICsgTE9OICsgQ1JJTSArIFpOICsgSU5EVVMgKyBDSEFTICsgTk9YICsgUk0gKyANCiAgICAgICAgICAgICAgQUdFICsgRElTICsgUkFEICsgVEFYICsgUFRSQVRJTywgZGF0YT10cmFpbikNCnN1bW1hcnkobGlucmVnKQ0KDQojIE1ha2UgcHJlZGljdGlvbnMNCmxpbnJlZy5wcmVkID0gcHJlZGljdChsaW5yZWcsIG5ld2RhdGE9dGVzdCkNCmxpbnJlZy5zc2UgPSBzdW0oKGxpbnJlZy5wcmVkIC0gdGVzdCRNRURWKV4yKQ0KbGlucmVnLnNzZQ0KYGBgDQoNCiMjIyMgQ3JlYXRlIGEgQ0FSVCBtb2RlbA0KYGBge3J9DQp0cmVlID0gcnBhcnQoTUVEViB+IExBVCArIExPTiArIENSSU0gKyBaTiArIElORFVTICsgQ0hBUyArIE5PWCArIFJNICsgDQogICAgICAgICAgICAgICBBR0UgKyBESVMgKyBSQUQgKyBUQVggKyBQVFJBVElPLCBkYXRhPXRyYWluKQ0KcHJwKHRyZWUpDQoNCiMgTWFrZSBwcmVkaWN0aW9ucw0KdHJlZS5wcmVkID0gcHJlZGljdCh0cmVlLCBuZXdkYXRhPXRlc3QpDQp0cmVlLnNzZSA9IHN1bSgodHJlZS5wcmVkIC0gdGVzdCRNRURWKV4yKQ0KdHJlZS5zc2UNCmBgYA0KDQojIyMjIExvYWQgbGlicmFyaWVzIGZvciBjcm9zcy12YWxpZGF0aW9uDQpgYGB7cn0NCmxpYnJhcnkoY2FyZXQpDQoNCiMgTnVtYmVyIG9mIGZvbGRzDQp0ci5jb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQ0KDQojIGNwIHZhbHVlcw0KY3AuZ3JpZCA9IGV4cGFuZC5ncmlkKCAuY3AgPSAoMDoxMCkqMC4wMDEpDQoNCiMgQ3Jvc3MtdmFsaWRhdGlvbg0KdHIgPSB0cmFpbihNRURWIH4gTEFUICsgTE9OICsgQ1JJTSArIFpOICsgSU5EVVMgKyBDSEFTICsgTk9YICsgUk0gKyANCiAgICAgICAgICAgICBBR0UgKyBESVMgKyBSQUQgKyBUQVggKyBQVFJBVElPLCANCiAgICAgICAgICAgZGF0YSA9IHRyYWluLCBtZXRob2QgPSAicnBhcnQiLCANCiAgICAgICAgICAgdHJDb250cm9sID0gdHIuY29udHJvbCwgdHVuZUdyaWQgPSBjcC5ncmlkKQ0KdHINCg0KIyBFeHRyYWN0IHRyZWUNCmJlc3QudHJlZSA9IHRyJGZpbmFsTW9kZWwNCnBycChiZXN0LnRyZWUpDQoNCiMgTWFrZSBwcmVkaWN0aW9ucw0KYmVzdC50cmVlLnByZWQgPSBwcmVkaWN0KGJlc3QudHJlZSwgbmV3ZGF0YT10ZXN0KQ0KYmVzdC50cmVlLnNzZSA9IHN1bSgoYmVzdC50cmVlLnByZWQgLSB0ZXN0JE1FRFYpXjIpDQpiZXN0LnRyZWUuc3NlDQpgYGANCg0KIyMjIyBFbnNlbWJsaW5nDQpgYGB7cn0NCmVuLnByZWQgPSAoYmVzdC50cmVlLnByZWQgKyBsaW5yZWcucHJlZCkvMg0Kc3VtKChlbi5wcmVkIC0gdGVzdCRNRURWKV4yKQ0KYGBgDQoNCg0KYGBge3J9DQojICAgICAgIE1FVEhPRAkgU1NFDQojICAgICAgICAgICBsbQkzMDM3DQojICAgICAgICAgcnBhcnQJNDMyOQ0KIyAgICAgIHJwYXJ0LmN2CTM2NjANCiMgICBsbStycGFydC5jdgkyNTk5ICANCmBgYA0KDQoNCiMjIyMg44CQ6KiO6KuW44CRDQoNCisgZHJhd2luZyBtYXAgd2l0aCBzcGF0aWFsIGRhdGEgDQorIHRyZWUgaW4gc3BhdGlhbCBkYXRhDQorIHRyZWUgdnMuIGxpbmVhciByZWdyZXNzaW9uDQorIHRoZSBjb25jZXB0IG9mIGVuc2VtYmxpbmcNCg0KLSAtIC0NCg0KPGJyPjxicj48YnI+PGJyPjxicj4NCg0KPHN0eWxlPg0KLmNhcHRpb24gew0KICBjb2xvcjogIzc3NzsNCiAgbWFyZ2luLXRvcDogMTBweDsNCn0NCnAgY29kZSB7DQogIHdoaXRlLXNwYWNlOiBpbmhlcml0Ow0KfQ0KcHJlIHsNCiAgd29yZC1icmVhazogbm9ybWFsOw0KICB3b3JkLXdyYXA6IG5vcm1hbDsNCiAgbGluZS1oZWlnaHQ6IDE7DQp9DQpwcmUgY29kZSB7DQogIHdoaXRlLXNwYWNlOiBpbmhlcml0Ow0KfQ0KcCxsaSB7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQoucnsNCiAgbGluZS1oZWlnaHQ6IDEuMjsNCn0NCg0KdGl0bGV7DQogIGNvbG9yOiAjY2MwMDAwOw0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KYm9keXsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCmgxLGgyLGgzLGg0LGg1ew0KICBjb2xvcjogIzAwNjZmZjsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCmg0LGg1ew0KICBiYWNrZ3JvdW5kOiAjY2NmZmZmOw0KfQ0KDQo8L3N0eWxlPg0KDQoNCg0KDQoNCg0K