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",'C') #清除國家偵測設定
[1] "C/C/C/C/C/en_US.UTF-8"
rm(list=ls(all=T))
options(digits=4, scipen=12) #控制小數點, scipen=12讓科學記號不那麼常出現
library(dplyr)

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)   # class是讓他結果輸出類別, prob則是機率, 都不選也是機率

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.6588

1.5 ROC & AUC

library(ROCR)
PredictROC = predict(rpart1, TS)              # predict 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
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.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()這幾張圖形

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

DPP的分佈點數(0/1)是決策樹紅/綠之分別;DPP分佈位置代表決策樹的預測機率(第二個數字);DPP高度為決策樹中的各水桶佔總資料的比例(第三個數字)。


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的數字愈大,決策樹就會長得愈小(簡單);反之,數字愈小,樹就愈大(複雜)。


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  #diag是對角線
[1] 0.6824
pred = predict(rf1, TS, type='prob')[,2]  #Y=的機率
table(TS$Reverse, pred > 0.5) %>% {sum(diag(.))/sum(.)}
[1] 0.6882

1.9 Cross Validation & Parameter Tuning - caret package

pred = predict(rf1, TS, type='prob')[,2]   # 可以預測兩類別的時候要看第二行,所以用[,2]
table(TS$Reverse, pred > 0.5) %>% {sum(diag(.))/sum(.)}   # 簡潔寫法(同上面的Accuracy算法)
[1] 0.68824

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

  • 我們應選擇0.18的cp。因為cp愈高,模型的複雜度就愈低;若Acc都一樣(呈水平線),我們選擇其中cp最大的。


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

決策樹可以做到類別與連續變數的分析。當要用的是類別時,方法則要選擇“class”;而當我們需要連續變化的機率,就使用“prob”。

  • Tree Bagging - Random Forest randomForest

在做Tree Bagging時,我們使用ntree來調整隨機森林中樹的大小,由此來告訴R做到多少就不要再往下長了。

  • Overfitting : Curse of Complexity

當模型做得太複雜會導致過度適配Trianing的模型,但這樣會遠離Testing的模型,且我們真正想推估的是未來可能發生的狀況,若一心追求模型的準確度,將達不到我們真正的目的。

  • Method Parameter : Cost of Compexity

那要如何對抗因過度適配產生的複雜度成本,就要靠方法參數(alpha,lamda,…),而非直接選取變數(x)來選擇模型。

  • Parameter Tuning by CV (caret package)

所謂方法參數,就是用cp來控制,但我們不知道到底哪個cp值最好,這時就是不斷地用caret來做cross validation,值到做出我們滿意的模型。

  • 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  #x隨著前面的變數而變(D,TR,TS) #sapply不只是做出來 還會彙整成表
          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
acc.base = table(TS$bucket2009, TS$bucket2008) %>% 
  {sum(diag(.)) / sum(.)}    # .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
cm = as.matrix(table(TS$bucket2009, TS$bucket2008))
pen.base = sum(cm*Penalty) / nrow(TS)    # pen: .7386
#因為有5*5的matrix(五個類別),不能做AUC(僅兩個類別)
#把全部的cm*payoff加總並平均,得出每個平均的penalty
# if we use the most popular class as baseline
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

CART Model

# 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 (accuracy差不多,但penalty差很多,表示這個方式較好)
      1         
0.67127 1.04430 
# 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
cm
   pred
        1     2     3     4     5
  1 94310 25295  3087   286     0
  2  7176 18942  8079   643     0
  3  3590  7706  4692   401     1
  4  1304  3193  2803   636     1
  5   135   356   408   156     2
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
cm*Penalty #最壞的決策是高估(3,1)=14360。
   pred
        1     2     3     4     5
  1     0 25295  6174   858     0
  2 14352     0  8079  1286     0
  3 14360 15412     0   401     2
  4  7824 12772  5606     0     1
  5  1080  2136  1632   312     0
  • dumb vs. smart baseline

dumb baseline是將Y都猜1,準確率也不會與一般的baseline差太多,方便快速,然而penalty會大幅提升(acc=0.6731,penalty=1.044);而smart baseline是一種進階版的baseline,也就是想辦法拉高底線,此例是用2008年的類別直接預測2009年的類別(acc=0.6838,penalty=0.7386),相較之下,acc差不多,但penalty明顯下降,故應選smart baseline。

  • model1: accuracy up, penalty up

全部都猜1的模型,完全沒有在區分類別,即使正確性很高,沒有辨識率,就不是一個好的模型。

  • model2: accuracy down, penalty down

第二種模型是以今年的資料去預測明年,至少還有在分辨不同類別,因此penalty下降了。

  • accuracy != profitability

綜合上述,我們比較在意降低penalty而非增加acc。

  • “customized” model optimize criteria in classification methods (not always available)

客製化模型在做模型的當下,就已告訴決策樹預設好的penalty(parms=list(loss=Penalty)),給模型做一個加權數,模型預測只要沒猜中,類別就算錯,沒有猜多猜少(高估低估)的差別,最終做出來的樹不是acc最高的樹,而是penalty最低的樹(acc=0.6472,penalty=0.6418)。但不是每種模型都可以透過matrix加入penalty做這種最佳化。

  • This CV/PT process is over simplified

這例題中並沒有做到cross validation,簡化了他如何選擇cp的過程。

  • 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

最後結果得出,決策樹的預測比線性回歸模型的預測還要差,因此我們知道,並非所有議題都適合用決策樹來分,有時候回歸模型的表現會更好(SSE較小)。

  • the concept of ensembling

Model ensembling是將兩種模型合併起來做平均,其的預測能力將能打敗其他單一種預測方式的模型。







LS0tCnRpdGxlOiAiQVM0LTAiCmF1dGhvcjogIkdST1VQNeKAlOKAlOaWvemHh+W9o+OAgemZs+aAoeWuieOAgealiuWHseWAq+OAgeWUkOaAneeQquOAgeWHjOWBieiqoCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3J9CnBhY2thZ2VzID0gYygKICAiZHBseXIiLCJnZ3Bsb3QyIiwiZDNoZWF0bWFwIiwiZ29vZ2xlVmlzIiwiZGV2dG9vbHMiLCJwbG90bHkiLCAieGdib29zdCIsCiAgIm1hZ3JpdHRyIiwiY2FUb29scyIsIlJPQ1IiLCJjb3JycGxvdCIsICJycGFydCIsICJycGFydC5wbG90IiwKICAiZG9QYXJhbGxlbCIsICJjYXJldCIsICJnbG1uZXQiLCAiTWF0cml4IiwgImUxMDcxIiwgInJhbmRvbUZvcmVzdCIKICApCmV4aXN0aW5nID0gYXMuY2hhcmFjdGVyKGluc3RhbGxlZC5wYWNrYWdlcygpWywxXSkKZm9yKHBrZyBpbiBwYWNrYWdlc1shKHBhY2thZ2VzICVpbiUgZXhpc3RpbmcpXSkgaW5zdGFsbC5wYWNrYWdlcyhwa2cpCmBgYAoKYGBge3IgZWNobz1ULCBtZXNzYWdlPUYsIGNhY2hlPUYsIHdhcm5pbmc9Rn0KU3lzLnNldGxvY2FsZSgiTENfQUxMIiwnQycpICPmuIXpmaTlnIvlrrblgbXmuKzoqK3lrpoKcm0obGlzdD1scyhhbGw9VCkpCm9wdGlvbnMoZGlnaXRzPTQsIHNjaXBlbj0xMikgI+aOp+WItuWwj+aVuOm7niwgc2NpcGVuPTEy6K6T56eR5a246KiY6Jmf5LiN6YKj6bq85bi45Ye654++CmxpYnJhcnkoZHBseXIpCmBgYAoKLSAtIC0KCiMjIyAxLiBTdXByaW1lIENvdXJ0IERlY2lzaW9uCgojIyMjIDEuMSBQcmVwYXJlIERhdGEKYGBge3J9CkQgPSByZWFkLmNzdigiZGF0YS9zdGV2ZW5zLmNzdiIpCgpsaWJyYXJ5KGNhVG9vbHMpCnNldC5zZWVkKDMwMDApCnNwbCA9IHNhbXBsZS5zcGxpdChEJFJldmVyc2UsIFNwbGl0UmF0aW8gPSAwLjcpClRSID0gc3Vic2V0KEQsIHNwbCkKVFMgPSBzdWJzZXQoRCwgIXNwbCkKYGBgCgojIyMjIDEuMiBDQVJUIChDbGFzc2lmaWNhdGlvbiAmIFJlZ3Jlc3Npb24gVHJlZSkgLSBgcnBhcnQ6OnJwYXJ0KClgCmBgYHtyfQpsaWJyYXJ5KHJwYXJ0KQpycGFydDEgPSBycGFydChSZXZlcnNlIH4gLiwgVFJbLDM6OV0sICAgICAgICAgICMgc2ltcGxpZnkgdGhlIGZvcm11bGEKICAgICAgICAgICAgICAgbWV0aG9kPSJjbGFzcyIsIG1pbmJ1Y2tldD0yNSkgICAjIGNsYXNz5piv6K6T5LuW57WQ5p6c6Ly45Ye66aGe5YilLCBwcm9i5YmH5piv5qmf546HLCDpg73kuI3pgbjkuZ/mmK/mqZ/njocKYGBgCgojIyMjIDEuMyBQbG90IERlY2lzaW9uIFRyZWUKYGBge3J9CmxpYnJhcnkocnBhcnQucGxvdCkKcHJwKHJwYXJ0MSkKcnBhcnQucGxvdChycGFydDEsY2V4PTAuNikKYGBgCgojIyMjIDEuNCBNYWtlIFByZWRpY3Rpb24KYGBge3J9CnByZWQgPSBwcmVkaWN0KHJwYXJ0MSwgVFMsIHR5cGUgPSAiY2xhc3MiKSAgIyBwcmVkaWN0IGNsYXNzZXMKeCA9IHRhYmxlKGFjdHVhbCA9IFRTJFJldmVyc2UsIHByZWQpOyB4CnN1bShkaWFnKHgpKS9zdW0oeCkgICAgICAgICAgICAgICAgICAgICAgICAgIyAuNjU4ODIKYGBgCgojIyMjIDEuNSBST0MgJiBBVUMKYGBge3J9CmxpYnJhcnkoUk9DUikKUHJlZGljdFJPQyA9IHByZWRpY3QocnBhcnQxLCBUUykgICAgICAgICAgICAgICMgcHJlZGljdCBwcm9iLgpoZWFkKFByZWRpY3RST0MpCnBlcmYgPSBwcmVkaWN0aW9uKFByZWRpY3RST0NbLDJdLCBUUyRSZXZlcnNlKQpwZXJmID0gcGVyZm9ybWFuY2UocGVyZiwgInRwciIsICJmcHIiKQpwbG90KHBlcmYpCmBgYAoKYGBge3J9CnByZWQgPSBwcmVkaWN0KHJwYXJ0MSwgVFMpWywyXSAgICAgIyBwcm9iLiBvZiBSZXZlcnNlID0gMSAgICAgICAgIApjb2xBVUMocHJlZCwgVFMkUmV2ZXJzZSwgVCkgICAgICAgICMgQVVDID0gMC42OTI3CmBgYAoKCiMjIyMgMS42IERQUCBvZiBEZWNpc2lvbiBUcmVlCmBgYHtyfQpzb3VyY2UoIkRQUC5SIikKYXVjID0gRFBQKHByZWQsIFRTJFJldmVyc2UsIDEpICAgICAjIEFVQyA9IDAuNjkyNwpgYGAKCiMjIyMg44CQUVVJWi0x44CR5q+U6LyD5Lul5LiKYHBycCgpYOOAgWBycGFydC5wbG90KClg5ZKMYERQUCgpYOmAmeW5vuW8teWcluW9ogoKKyDkuIDmo7XmsbrnrZbmqLnnmoTlvaLni4DvvIwg5ZKM55Sx5a6D5omA55Si55Sf55qE6aCQ5ris5qmf546H5YiG5biD5LmL6ZaT77yM5pyJ5LuA6bq86Zec5L+C5ZGiID8gIOS7peS4ikRQUCDlnJbkuK3nmoTliIbluIPpu57mlbjjgIHkvY3nva7lkozpq5jluqbvvIzliIbliKXmnIPlsI3mh4nliLDmsbrnrZbmqLnnmoTku4DpurznibnlvrXlkaI/CgpEUFDnmoTliIbkvYjpu57mlbgoMC8xKeaYr+axuuetluaouee0hS/ntqDkuYvliIbliKU7RFBQ5YiG5L2I5L2N572u5Luj6KGo5rG6562W5qi555qE6aCQ5ris5qmf546HKOesrOS6jOWAi+aVuOWtlyk7RFBQ6auY5bqm54K65rG6562W5qi55Lit55qE5ZCE5rC05qG25L2U57i96LOH5paZ55qE5q+U5L6LKOesrOS4ieWAi+aVuOWtlynjgIIKCgo8YnI+CgojIyMjIDEuNyBUaGUgZWZmZWN0IG9mIGBtaW5idWNrZXRgCmBgYHtyfQp0NSA9IHJwYXJ0KFJldmVyc2UgfiAuLCBUUlssMzo5XSwgbWV0aG9kPSJjbGFzcyIsIG1pbmJ1Y2tldD01KQpwcnAodDUpCmBgYAoKYGBge3IgZmlnLmhlaWdodD0yfQp0MTAwID0gcnBhcnQoUmV2ZXJzZSB+IC4sIFRSWywzOjldLCBtZXRob2Q9ImNsYXNzIiwgbWluYnVja2V0PTEwMCkKcHJwKHQxMDApCmBgYAoKCiMjIyPjgJBRVUlaLTLjgJFCYXNlZCBvbiB0aGUgMiBwbG90cyBhYm92ZSwgd2hhdCBpcyB0aGUgZWZmZWN0IG9mIGBtaW5idWNrZXRgPyAKCisgbWluYnVja2V055qE5pW45a2X5oSI5aSnLOaxuuetluaoueWwseacg+mVt+W+l+aEiOWwjyjnsKHllq4pO+WPjeS5iyzmlbjlrZfmhIjlsI8s5qi55bCx5oSI5aSnKOikh+mbnCnjgIIKCjxicj4KCiMjIyMgMS44IFJhbmRvbSBGb3Jlc3QgTWV0aG9kIC0gYHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KClgCmBgYHtyfQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKVFIkUmV2ZXJzZSA9IGFzLmZhY3RvcihUUiRSZXZlcnNlKSAgIyBDb252ZXJ0IG91dGNvbWUgdG8gZmFjdG9yClRTJFJldmVyc2UgPSBhcy5mYWN0b3IoVFMkUmV2ZXJzZSkKCiMgQnVpbGQgbW9kZWwKcmYxID0gcmFuZG9tRm9yZXN0KFJldmVyc2UgfiAuLCBUUlssMzo5XSwgbnRyZWU9MjAwLCBub2Rlc2l6ZT0yNSkKCiMgTWFrZSBwcmVkaWN0aW9ucwpwcmVkID0gcHJlZGljdChyZjEsIFRTKQp4ID0gdGFibGUoVFMkUmV2ZXJzZSwgcHJlZCkKc3VtKGRpYWcoeCkpIC8gc3VtKHgpICAgICAgICAgICMgMC42ODI0ICAjZGlhZ+aYr+Wwjeinkue3mgpgYGAKCmBgYHtyfQpwcmVkID0gcHJlZGljdChyZjEsIFRTLCB0eXBlPSdwcm9iJylbLDJdICAjWT3nmoTmqZ/njocKdGFibGUoVFMkUmV2ZXJzZSwgcHJlZCA+IDAuNSkgJT4lIHtzdW0oZGlhZyguKSkvc3VtKC4pfQpgYGAKCgojIyMjIDEuOSBDcm9zcyBWYWxpZGF0aW9uICYgUGFyYW1ldGVyIFR1bmluZyAtIGBjYXJldGAgcGFja2FnZQpgYGB7cn0KbGlicmFyeShjYXJldCkKbnVtRm9sZHMgPSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIG51bWJlcj0xMCkgIyAxMCBmb2xkIENWCmNwR3JpZCA9IGV4cGFuZC5ncmlkKGNwID0gc2VxKDAuMDEsMC41LDAuMDEpKSAgICMgcGFyYW1ldGVyIGNvbWJpbmF0aW9uCgpjdjEgPSB0cmFpbihSZXZlcnNlIH4gLiwgVFJbLDM6OV0sIG1ldGhvZCA9ICJycGFydCIsIAogICAgICAgICAgICB0ckNvbnRyb2w9bnVtRm9sZHMsIHR1bmVHcmlkPWNwR3JpZCkKY3YxOyBwbG90KGN2MSkKYGBgCgojIyMj44CQUVVJWi0z44CRV2hpY2ggYGNwYCB3aGljaCB3ZSBzaG91bGQgd2UgY2hvb3NlPyBXaHk/IAoKKyDmiJHlgJHmh4npgbjmk4cwLjE455qEY3DjgILlm6DngrpjcOaEiOmrmCzmqKHlnovnmoTopIfpm5zluqblsLHmhIjkvY476IulQWNj6YO95LiA5qijKOWRiOawtOW5s+e3miks5oiR5YCR6YG45pOH5YW25LitY3DmnIDlpKfnmoTjgIIKCjxicj4KCiMjIyMgMS4xMCBUaGUgRmluYWwgTW9kZWwKYGBge3J9CnJwYXJ0MiA9IHJwYXJ0KFJldmVyc2UgfiAuLCBUUlssMzo5XSwgbWV0aG9kPSJjbGFzcyIsIGNwPTAuMTkpCnByZWQgPSBwcmVkaWN0KHJwYXJ0MiwgVFMsIHR5cGU9J3Byb2InKVssMl0KdGFibGUoVFMkUmV2ZXJzZSwgcHJlZCA+IDAuNSkgJT4lIHtzdW0oZGlhZyguKSkgLyBzdW0oLil9ICAjIDAuNzIzNQpgYGAKCmBgYHtyIGZpZy53aWR0aD0zfQpycGFydC5wbG90KHJwYXJ0MikKYGBgCgojIyMj44CQ6KiO6KuW77ya44CRCgorIENsYXNzaWZpY2F0aW9uICYgUmVncmVzc2lvbiBUcmVlICBgcnBhcnRgCgogIOaxuuetluaoueWPr+S7peWBmuWIsOmhnuWIpeiIh+mAo+e6jOiuiuaVuOeahOWIhuaekOOAgueVtuimgeeUqOeahOaYr+mhnuWIpeaZgizmlrnms5XliYfopoHpgbjmk4ciY2xhc3MiO+iAjOeVtuaIkeWAkemcgOimgemAo+e6jOiuiuWMlueahOapn+eOhyzlsLHkvb/nlKgicHJvYiLjgIIKCisgVHJlZSBCYWdnaW5nIC0gUmFuZG9tIEZvcmVzdCBgcmFuZG9tRm9yZXN0YCAgCgogIOWcqOWBmlRyZWUgQmFnZ2luZ+aZgizmiJHlgJHkvb/nlKhudHJlZeS+huiqv+aVtOmaqOapn+ajruael+S4reaoueeahOWkp+WwjyznlLHmraTkvoblkYroqLRS5YGa5Yiw5aSa5bCR5bCx5LiN6KaB5YaN5b6A5LiL6ZW35LqG44CCCgorIE92ZXJmaXR0aW5nIDogQ3Vyc2Ugb2YgQ29tcGxleGl0eQoKICDnlbbmqKHlnovlgZrlvpflpKropIfpm5zmnIPlsI7oh7TpgY7luqbpganphY1UcmlhbmluZ+eahOaooeWeiyzkvYbpgJnmqKPmnIPpgaDpm6JUZXN0aW5n55qE5qih5Z6LLOS4lOaIkeWAkeecn+ato+aDs+aOqOS8sOeahOaYr+acquS+huWPr+iDveeZvOeUn+eahOeLgOazgSzoi6XkuIDlv4Pov73msYLmqKHlnovnmoTmupbnorrluqYs5bCH6YGU5LiN5Yiw5oiR5YCR55yf5q2j55qE55uu55qE44CCCgorIE1ldGhvZCBQYXJhbWV0ZXIgOiBDb3N0IG9mIENvbXBleGl0eSAKCiAg6YKj6KaB5aaC5L2V5bCN5oqX5Zug6YGO5bqm6YGp6YWN55Si55Sf55qE6KSH6Zuc5bqm5oiQ5pysLOWwseimgemdoOaWueazleWPg+aVuChhbHBoYSxsYW1kYSwuLi4pLOiAjOmdnuebtOaOpemBuOWPluiuiuaVuCh4KeS+humBuOaTh+aooeWei+OAggoKKyBQYXJhbWV0ZXIgVHVuaW5nIGJ5IENWIChgY2FyZXRgIHBhY2thZ2UpCgogIOaJgOisguaWueazleWPg+aVuCzlsLHmmK/nlKhjcOS+huaOp+WItizkvYbmiJHlgJHkuI3nn6XpgZPliLDlupXlk6rlgItjcOWAvOacgOWlvSzpgJnmmYLlsLHmmK/kuI3mlrflnLDnlKhjYXJldOS+huWBmmNyb3NzIHZhbGlkYXRpb24s5YC85Yiw5YGa5Ye65oiR5YCR5ru/5oSP55qE5qih5Z6L44CCCgorIFRoZSBwb3dlciBvZiBtYWNoaW5lIHZzLiBUaGUgdmFsdWUgb2YgaHVtYW5pdHkKCiAg5Zug54K66ZyA6KaB5LiN5pa36YeN6KSH5ZiX6Kmm5LiN5ZCM55qE5Y+D5pW45YC8LOWwsemrlOePvuS6huapn+WZqOioiOeul+mrmOaViOeahOmHjeimgeaApzvkvYbmmK/mnIDntYLlnKjkvZzmsbrnrZbmmYIs6YKE5piv6KaB6Z2g5Lq65YCR5bCN6K2w6aGM55qE5pWP5oSf5oCn5Y+K6IOM5pmv55+l6K2Y5omN6IO95YGa5Yik5pa3LOapn+WZqOe1gueptuWPquaYr+W5q+W/meW7uuaooeWeiyzogIzmqKHlnovlu7rlrozmiY3mmK/nnJ/mraPnmoTplovlp4vjgIIKCi0gLSAtCgojIyMgMiBEMkhhd2tFeWUKCiMjIyMgMi4xIFByZXBhcmUgJiBFeGFtaW5lIERhdGEgCmBgYHtyfQpybShsaXN0PWxzKGFsbD1UUlVFKSkKRCA9IHJlYWQuY3N2KCJkYXRhL0NsYWltc0RhdGEuY3N2IikKCiMgUGVyY2VudGFnZSBvZiBwYXRpZW50cyBpbiBlYWNoIGNvc3QgYnVja2V0CnRhYmxlKEQkYnVja2V0MjAwOSkvbnJvdyhEKQpgYGAKCmBgYHtyfQojIFNwbGl0IHRoZSBkYXRhCmxpYnJhcnkoY2FUb29scykKc2V0LnNlZWQoODgpCnNwbCA9IHNhbXBsZS5zcGxpdChEJGJ1Y2tldDIwMDksIFNwbGl0UmF0aW8gPSAwLjYpClRSID0gc3Vic2V0KEQsIHNwbCkKVFMgPSBzdWJzZXQoRCwgIXNwbCkKYGBgCgpgYGB7cn0KIyBDaGVjayBQZXJjZW50YWdlcyBBbW9uZyBTdWJzZXRzCmxpYnJhcnkoZHBseXIpCnRhYmxlKFRSJGJ1Y2tldDIwMDkpICU+JSBwcm9wLnRhYmxlCnRhYmxlKFRTJGJ1Y2tldDIwMDkpICU+JSBwcm9wLnRhYmxlCnRhYmxlKEQkYnVja2V0MjAwOSkgJT4lIHByb3AudGFibGUKCiMgc2FwcGx5KCk6IEFwcGx5IEZ1bmN0aW9uICYgQWdncmVnYXRlIE91dHB1dApzYXBwbHkobGlzdChELCBUUiwgVFMpLCBmdW5jdGlvbih4KSAKICB0YWJsZSh4JGJ1Y2tldDIwMDkpICU+JSBwcm9wLnRhYmxlKSAlPiUgdCAgI3jpmqjokZfliY3pnaLnmoTorormlbjogIzoroooRCxUUixUUykgI3NhcHBseeS4jeWPquaYr+WBmuWHuuS+hiDpgoTmnIPlvZnmlbTmiJDooagKYGBgCgpgYGB7cn0KIyBEYXRhIEV4cGxvcmF0aW9uIAptZWFuKFRSJGFnZSkgICAgICAgICAgICAgICAgICMgNzIuNjQKbWVhbihUUiRkaWFiZXRlcykgICAgICAgICAgICAjIC4zODA5IApgYGAKCiMjIyMgMi4yIEJhc2VsaW5lIE1vZGVsIApgYGB7cn0KIyBCYXNlbGluZSBBY2N1cmFjeQphY2MuYmFzZSA9IHRhYmxlKFRTJGJ1Y2tldDIwMDksIFRTJGJ1Y2tldDIwMDgpICU+JSAKICB7c3VtKGRpYWcoLikpIC8gc3VtKC4pfSAgICAjIC42ODM4CmBgYAoKYGBge3J9CiMgUGVuYWx0eSBNYXRyaXgKUGVuYWx0eSA9IG1hdHJpeChjKAogIDAsMSwyLDMsNCwKICAyLDAsMSwyLDMsCiAgNCwyLDAsMSwyLAogIDYsNCwyLDAsMSwKICA4LDYsNCwyLDApLCBieXJvdz1UUlVFLCBucm93PTUpClBlbmFsdHkKYGBgCgpgYGB7cn0KIyBQZW5hbHR5IEVycm9yIG9mIEJhc2VsaW5lIE1ldGhvZApjbSA9IGFzLm1hdHJpeCh0YWJsZShUUyRidWNrZXQyMDA5LCBUUyRidWNrZXQyMDA4KSkKcGVuLmJhc2UgPSBzdW0oY20qUGVuYWx0eSkgLyBucm93KFRTKSAgICAjIHBlbjogLjczODYKI+WboOeCuuaciTUqNeeahG1hdHJpeCjkupTlgIvpoZ7liKUpLOS4jeiDveWBmkFVQyjlg4XlhanlgIvpoZ7liKUpCiPmiorlhajpg6jnmoRjbSpwYXlvZmbliqDnuL3kuKblubPlnYcs5b6X5Ye65q+P5YCL5bmz5Z2H55qEcGVuYWx0eQpgYGAKCmBgYHtyfQojIGlmIHdlIHVzZSB0aGUgbW9zdCBwb3B1bGFyIGNsYXNzIGFzIGJhc2VsaW5lCmFjYy5kdW1iID0gKHRhYmxlKFRTJGJ1Y2tldDIwMDkpL25yb3coVFMpKVsxXSAgICAgICAgICAgICAgICAgIyBhY2M6IC42NzEzIApwZW4uZHVtYiA9IHN1bSh0YWJsZShUUyRidWNrZXQyMDA5KSAqIGMoMCwyLDQsNiw4KSkvbnJvdyhUUykgICMgcGVuOiAxLjA0NApgYGAKCiMjIyMgQ0FSVCBNb2RlbApgYGB7cn0KbGlicmFyeShycGFydCkKbGlicmFyeShycGFydC5wbG90KQpycGFydDEgPSBycGFydChidWNrZXQyMDA5IH4gLiwgZGF0YT1UUltjKDE6MTQsIDE2KV0sIAogICAgICAgICAgICAgICBtZXRob2Q9ImNsYXNzIiwgY3A9MC4wMDAwNSkKcHJwKHJwYXJ0MSkKYGBgCgpgYGB7cn0KIyBNYWtlIHByZWRpY3Rpb25zCnByZWQgPSBwcmVkaWN0KHJwYXJ0MSwgbmV3ZGF0YSA9IFRTLCB0eXBlID0gImNsYXNzIikKYWNjLnJwYXJ0MSA9IHRhYmxlKFRTJGJ1Y2tldDIwMDksIHByZWQpICU+JSAKICB7IHN1bShkaWFnKC4pKSAvbnJvdyhUUyl9ICAgICAgICAgICAgIyBhY2M6IC43MTI2NyA8LSAuNjgzODEKCiMgUGVuYWx0eSBFcnJvcgpjbSA9IGFzLm1hdHJpeCh0YWJsZShUUyRidWNrZXQyMDA5LCBwcmVkKSkKcGVuLnJwYXJ0MSA9IHN1bShjbSpQZW5hbHR5KS9ucm93KFRTKSAgIyBwZW46IC43NTc4OSA8LSAuNzM4NgpgYGAKCiMjIyMgQ0FSVCBNb2RlbCB3aXRoIEN1c3RvbWl6ZWQgTG9zcyBNYXRyaXgKYGBge3J9CnJwYXJ0MiA9IHJwYXJ0KGJ1Y2tldDIwMDkgfiAuLCBkYXRhPVRSW2MoMToxNCwgMTYpXSwgCiAgICAgICAgICAgICAgIG1ldGhvZD0iY2xhc3MiLCBjcD0wLjAwMDA1LCAKICAgICAgICAgICAgICAgcGFybXM9bGlzdChsb3NzPVBlbmFsdHkpKQoKcHJlZCA9IHByZWRpY3QocnBhcnQyLCBUUywgdHlwZSA9ICJjbGFzcyIpCmFjYy5ycGFydDIgPSB0YWJsZShUUyRidWNrZXQyMDA5LCBwcmVkKSAlPiUgCiAge3N1bShkaWFnKC4pKS9zdW0oLil9ICAgICAgICAgICAgICAgICAgIyBhY2MucnBhcnQyOiAuNjQ3MjcgICAKCmNtID0gYXMubWF0cml4KHRhYmxlKFRTJGJ1Y2tldDIwMDksIHByZWQpKQpwZW4ucnBhcnQyID0gc3VtKGNtKlBlbmFsdHkpL25yb3coVFMpICAgICMgcGVuLnJwYXJ0MjogLjY0MTgyIApgYGAKCmBgYHtyfQojIFN1bW1hcnkKZGF0YS5mcmFtZSgKICBhY2N1cmFjeSA9IGMoYWNjLmR1bWIsIGFjYy5iYXNlLCBhY2MucnBhcnQxLCBhY2MucnBhcnQyKSwKICBwZW5hbHR5ID0gYyhwZW4uZHVtYiwgcGVuLmJhc2UsIHBlbi5ycGFydDEsIHBlbi5ycGFydDIpLAogIHJvdy5uYW1lcyA9IGMoImR1bWIiLCJiYXNlIiwiQ0FSVCIsIkNBUlQgdy9Mb3NzTXgiKSkKYGBgCgojIyMjIOOAkOiojuirluOAkQoKKyA1IGNsYXNzZXM6IDVYNSBjb25mdXNzaW9uL3BlbmFsdHkocGF5b2ZmKSBtYXRyaWNlcyAKYGBge3J9CmNtClBlbmFsdHkKY20qUGVuYWx0eSAj5pyA5aOe55qE5rG6562W5piv6auY5LywKDMsMSk9MTQzNjDjgIIKYGBgCgorIGR1bWIgdnMuIHNtYXJ0IGJhc2VsaW5lCgogIGR1bWIgYmFzZWxpbmXmmK/lsIdZ6YO954ycMSzmupbnorrnjofkuZ/kuI3mnIPoiIfkuIDoiKznmoRiYXNlbGluZeW3ruWkquWkmizmlrnkvr/lv6vpgJ8s54S26ICMcGVuYWx0eeacg+Wkp+W5heaPkOWNhyhhY2M9MC42NzMxLHBlbmFsdHk9MS4wNDQpO+iAjHNtYXJ0IGJhc2VsaW5l5piv5LiA56iu6YCy6ZqO54mI55qEYmFzZWxpbmUs5Lmf5bCx5piv5oOz6L6m5rOV5ouJ6auY5bqV57eaLOatpOS+i+aYr+eUqDIwMDjlubTnmoTpoZ7liKXnm7TmjqXpoJDmuKwyMDA55bm055qE6aGe5YilKGFjYz0wLjY4MzgscGVuYWx0eT0wLjczODYpLOebuOi8g+S5i+S4iyxhY2Plt67kuI3lpJos5L2GcGVuYWx0eeaYjumhr+S4i+mZjSzmlYXmh4npgbhzbWFydCBiYXNlbGluZeOAggoKKyBtb2RlbDE6IGFjY3VyYWN5IHVwLCBwZW5hbHR5IHVwCgogIOWFqOmDqOmDveeMnDHnmoTmqKHlnoss5a6M5YWo5rKS5pyJ5Zyo5Y2A5YiG6aGe5YilLOWNs+S9v+ato+eiuuaAp+W+iOmrmCzmspLmnInovqjorZjnjocs5bCx5LiN5piv5LiA5YCL5aW955qE5qih5Z6L44CCCgorIG1vZGVsMjogYWNjdXJhY3kgZG93biwgcGVuYWx0eSBkb3duCgogIOesrOS6jOeoruaooeWei+aYr+S7peS7iuW5tOeahOizh+aWmeWOu+mgkOa4rOaYjuW5tCzoh7PlsJHpgoTmnInlnKjliIbovqjkuI3lkIzpoZ7liKUs5Zug5q2kcGVuYWx0eeS4i+mZjeS6huOAggoKKyBhY2N1cmFjeSAhPSBwcm9maXRhYmlsaXR5CgogIOe2nOWQiOS4iui/sCzmiJHlgJHmr5TovIPlnKjmhI/pmY3kvY5wZW5hbHR56ICM6Z2e5aKe5YqgYWNj44CCCgorICJjdXN0b21pemVkIiBtb2RlbCBvcHRpbWl6ZSBjcml0ZXJpYSBpbiBjbGFzc2lmaWNhdGlvbiBtZXRob2RzIChub3QgYWx3YXlzIGF2YWlsYWJsZSkKCiAg5a6i6KO95YyW5qih5Z6L5Zyo5YGa5qih5Z6L55qE55W25LiLLOWwseW3suWRiuiotOaxuuetluaouemgkOioreWlveeahHBlbmFsdHkocGFybXM9bGlzdChsb3NzPVBlbmFsdHkpKSzntabmqKHlnovlgZrkuIDlgIvliqDmrIrmlbgs5qih5Z6L6aCQ5ris5Y+q6KaB5rKS54yc5LitLOmhnuWIpeWwseeul+mMryzmspLmnInnjJzlpJrnjJzlsJEo6auY5Lyw5L2O5LywKeeahOW3ruWIpSzmnIDntYLlgZrlh7rkvobnmoTmqLnkuI3mmK9hY2PmnIDpq5jnmoTmqLks6ICM5pivcGVuYWx0eeacgOS9jueahOaouShhY2M9MC42NDcyLHBlbmFsdHk9MC42NDE4KeOAguS9huS4jeaYr+avj+eoruaooeWei+mDveWPr+S7pemAj+mBjm1hdHJpeOWKoOWFpXBlbmFsdHnlgZrpgJnnqK7mnIDkvbPljJbjgIIKCisgVGhpcyBDVi9QVCBwcm9jZXNzIGlzIG92ZXIgc2ltcGxpZmllZAoKICDpgJnkvovpoYzkuK3kuKbmspLmnInlgZrliLBjcm9zcyB2YWxpZGF0aW9uLOewoeWMluS6huS7luWmguS9lemBuOaTh2Nw55qE6YGO56iL44CCCgorIHdlIHdpbGwgY292ZXIgYSBiZXR0ZXIgcHJvY2VzcyBuZXh0IHRpbWUKICAKCi0gLSAtCgojIyMgMyBCb3N0b24gSG91c2UgUHJpY2UKCmBgYHtyfQpybShsaXN0PWxzKGFsbD1UUlVFKSkKRCA9IHJlYWQuY3N2KCJkYXRhL2Jvc3Rvbi5jc3YiKQpgYGAKCiMjIyMgMy4xIEV4YW1pbmUgRGF0YQpgYGB7cn0KIyBQbG90IG9ic2VydmF0aW9ucwpwbG90KEQkTE9OLCBEJExBVCkKCiMgVHJhY3RzIGFsb25nc2lkZSB0aGUgQ2hhcmxlcyBSaXZlcgp3aXRoKEQsIAogICAgIHBvaW50cyhMT05bQ0hBUz09MV0sIExBVFtDSEFTPT0xXSwgY29sPSJibHVlIiwgcGNoPTE5KSkKCiMgUGxvdCBNSVQKd2l0aChELCAKICAgICBwb2ludHMoTE9OW1RSQUNUPT0zNTMxXSwgTEFUW1RSQUNUPT0zNTMxXSwgY29sPSJyZWQiLCBwY2g9MTkpICkKCiMgUGxvdCBwb2x1dGlvbgpzdW1tYXJ5KEQkTk9YKQpwb2ludHMoRCRMT05bRCROT1g+PTAuNTVdLCBEJExBVFtEJE5PWD49MC41NV0sIAogICAgICAgY29sPSJncmVlbiIsIHBjaD0yMCkKCiMgUGxvdCBwcmljZXMKcGxvdChEJExPTiwgRCRMQVQpCnN1bW1hcnkoRCRNRURWKQpwb2ludHMoRCRMT05bRCRNRURWPj0yMS4yXSwgRCRMQVRbRCRNRURWPj0yMS4yXSwgCiAgICAgICBjb2w9InJlZCIsIHBjaD0yMCkKYGBgCgojIyMjIExpbmVhciBSZWdyZXNzaW9uIHVzaW5nIExBVCBhbmQgTE9OCmBgYHtyfQpsYXRsb25sbSA9IGxtKE1FRFYgfiBMQVQgKyBMT04sIGRhdGE9RCkKc3VtbWFyeShsYXRsb25sbSkKYGBgCgojIyMjIFZpc3VhbGl6ZSByZWdyZXNzaW9uIG91dHB1dApgYGB7cn0KcGxvdChEJExPTiwgRCRMQVQpCnBvaW50cyhEJExPTltEJE1FRFY+PTIxLjJdLCBEJExBVFtEJE1FRFY+PTIxLjJdLCAKICAgICAgIGNvbD0icmVkIiwgcGNoPTIwKQoKIyBsYXRsb25sbSRmaXR0ZWQudmFsdWVzCnBvaW50cyhEJExPTltsYXRsb25sbSRmaXR0ZWQudmFsdWVzID49IDIxLjJdLCAKICAgICAgIEQkTEFUW2xhdGxvbmxtJGZpdHRlZC52YWx1ZXMgPj0gMjEuMl0sIAogICAgICAgY29sPSJibHVlIiwgcGNoPSJ4IikKYGBgCgojIExvYWQgQ0FSVCBwYWNrYWdlcwpgYGB7cn0KbGlicmFyeShycGFydCkKbGlicmFyeShycGFydC5wbG90KQojIENBUlQgbW9kZWwKbGF0bG9udHJlZSA9IHJwYXJ0KE1FRFYgfiBMQVQgKyBMT04sIGRhdGE9RCkKcHJwKGxhdGxvbnRyZWUpCgojIFZpc3VhbGl6ZSBvdXRwdXQKcGxvdChEJExPTiwgRCRMQVQpCnBvaW50cyhEJExPTltEJE1FRFY+PTIxLjJdLCBEJExBVFtEJE1FRFY+PTIxLjJdLCAKICAgICAgIGNvbD0icmVkIiwgcGNoPTIwKQoKZml0dGVkdmFsdWVzID0gcHJlZGljdChsYXRsb250cmVlKQpwb2ludHMoRCRMT05bZml0dGVkdmFsdWVzPjIxLjJdLCAKICAgICAgIEQkTEFUW2ZpdHRlZHZhbHVlcz49MjEuMl0sIGNvbD0iYmx1ZSIsIHBjaD0ieCIpCmBgYAoKIyMjIyBTaW1wbGlmeSB0cmVlIGJ5IGluY3JlYXNpbmcgbWluYnVja2V0CmBgYHtyfQpsYXRsb250cmVlID0gcnBhcnQoTUVEViB+IExBVCArIExPTiwgZGF0YT1ELCBtaW5idWNrZXQ9NTApCnJwYXJ0LnBsb3QobGF0bG9udHJlZSkKYGBgCgpgYGB7cn0KIyBWaXN1YWxpemUgT3V0cHV0CnBsb3QoRCRMT04sRCRMQVQpCmFibGluZSh2PS03MS4wNykKYWJsaW5lKGg9NDIuMjEpCmFibGluZShoPTQyLjE3KQpwb2ludHMoRCRMT05bRCRNRURWPj0yMS4yXSwgCiAgICAgICBEJExBVFtEJE1FRFY+PTIxLjJdLCBjb2w9InJlZCIsIHBjaD0yMCkKYGBgCgojIyMjIFVzZSBhbGwgdGhlIHZhcmlhYmxlcwpgYGB7cn0KIyBTcGxpdCB0aGUgZGF0YQpsaWJyYXJ5KGNhVG9vbHMpCnNldC5zZWVkKDEyMykKc3BsaXQgPSBzYW1wbGUuc3BsaXQoRCRNRURWLCBTcGxpdFJhdGlvID0gMC43KQp0cmFpbiA9IHN1YnNldChELCBzcGxpdD09VFJVRSkKdGVzdCA9IHN1YnNldChELCBzcGxpdD09RkFMU0UpCgojIENyZWF0ZSBsaW5lYXIgcmVncmVzc2lvbgpsaW5yZWcgPSBsbShNRURWIH4gTEFUICsgTE9OICsgQ1JJTSArIFpOICsgSU5EVVMgKyBDSEFTICsgTk9YICsgUk0gKyAKICAgICAgICAgICAgICBBR0UgKyBESVMgKyBSQUQgKyBUQVggKyBQVFJBVElPLCBkYXRhPXRyYWluKQpzdW1tYXJ5KGxpbnJlZykKCiMgTWFrZSBwcmVkaWN0aW9ucwpsaW5yZWcucHJlZCA9IHByZWRpY3QobGlucmVnLCBuZXdkYXRhPXRlc3QpCmxpbnJlZy5zc2UgPSBzdW0oKGxpbnJlZy5wcmVkIC0gdGVzdCRNRURWKV4yKQpsaW5yZWcuc3NlCmBgYAoKIyMjIyBDcmVhdGUgYSBDQVJUIG1vZGVsCmBgYHtyfQp0cmVlID0gcnBhcnQoTUVEViB+IExBVCArIExPTiArIENSSU0gKyBaTiArIElORFVTICsgQ0hBUyArIE5PWCArIFJNICsgCiAgICAgICAgICAgICAgIEFHRSArIERJUyArIFJBRCArIFRBWCArIFBUUkFUSU8sIGRhdGE9dHJhaW4pCnBycCh0cmVlKQoKIyBNYWtlIHByZWRpY3Rpb25zCnRyZWUucHJlZCA9IHByZWRpY3QodHJlZSwgbmV3ZGF0YT10ZXN0KQp0cmVlLnNzZSA9IHN1bSgodHJlZS5wcmVkIC0gdGVzdCRNRURWKV4yKQp0cmVlLnNzZQpgYGAKCiMjIyMgTG9hZCBsaWJyYXJpZXMgZm9yIGNyb3NzLXZhbGlkYXRpb24KYGBge3J9CmxpYnJhcnkoY2FyZXQpCgojIE51bWJlciBvZiBmb2xkcwp0ci5jb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQoKIyBjcCB2YWx1ZXMKY3AuZ3JpZCA9IGV4cGFuZC5ncmlkKCAuY3AgPSAoMDoxMCkqMC4wMDEpCgojIENyb3NzLXZhbGlkYXRpb24KdHIgPSB0cmFpbihNRURWIH4gTEFUICsgTE9OICsgQ1JJTSArIFpOICsgSU5EVVMgKyBDSEFTICsgTk9YICsgUk0gKyAKICAgICAgICAgICAgIEFHRSArIERJUyArIFJBRCArIFRBWCArIFBUUkFUSU8sIAogICAgICAgICAgIGRhdGEgPSB0cmFpbiwgbWV0aG9kID0gInJwYXJ0IiwgCiAgICAgICAgICAgdHJDb250cm9sID0gdHIuY29udHJvbCwgdHVuZUdyaWQgPSBjcC5ncmlkKQp0cgoKIyBFeHRyYWN0IHRyZWUKYmVzdC50cmVlID0gdHIkZmluYWxNb2RlbApwcnAoYmVzdC50cmVlKQoKIyBNYWtlIHByZWRpY3Rpb25zCmJlc3QudHJlZS5wcmVkID0gcHJlZGljdChiZXN0LnRyZWUsIG5ld2RhdGE9dGVzdCkKYmVzdC50cmVlLnNzZSA9IHN1bSgoYmVzdC50cmVlLnByZWQgLSB0ZXN0JE1FRFYpXjIpCmJlc3QudHJlZS5zc2UKYGBgCgojIyMjIEVuc2VtYmxpbmcKYGBge3J9CmVuLnByZWQgPSAoYmVzdC50cmVlLnByZWQgKyBsaW5yZWcucHJlZCkvMgpzdW0oKGVuLnByZWQgLSB0ZXN0JE1FRFYpXjIpCmBgYAoKCmBgYHtyfQojICAgICAgIE1FVEhPRAkgU1NFCiMgICAgICAgICAgIGxtCTMwMzcKIyAgICAgICAgIHJwYXJ0CTQzMjkKIyAgICAgIHJwYXJ0LmN2CTM2NjAKIyAgIGxtK3JwYXJ0LmN2CTI1OTkgIApgYGAKCgojIyMjIOOAkOiojuirluOAkQoKKyBkcmF3aW5nIG1hcCB3aXRoIHNwYXRpYWwgZGF0YSAKCisgdHJlZSBpbiBzcGF0aWFsIGRhdGEKCiAg5YiG5p6Q5Zyo56m66ZaT6LOH5paZ55qE5rG6562W5qi5LOe2k+e3r+W6puacg+aYr+S4gOWAi+W+iOmHjeimgeeahOiuiuaVuOOAggoKKyB0cmVlIHZzLiBsaW5lYXIgcmVncmVzc2lvbgoKICDmnIDlvozntZDmnpzlvpflh7os5rG6562W5qi555qE6aCQ5ris5q+U57ea5oCn5Zue5q245qih5Z6L55qE6aCQ5ris6YKE6KaB5beuLOWboOatpOaIkeWAkeefpemBkyzkuKbpnZ7miYDmnInorbDpoYzpg73pganlkIjnlKjmsbrnrZbmqLnkvobliIYs5pyJ5pmC5YCZ5Zue5q245qih5Z6L55qE6KGo54++5pyD5pu05aW9KFNTRei8g+WwjynjgIIKCisgdGhlIGNvbmNlcHQgb2YgZW5zZW1ibGluZwoKICBNb2RlbCBlbnNlbWJsaW5n5piv5bCH5YWp56iu5qih5Z6L5ZCI5L216LW35L6G5YGa5bmz5Z2HLOWFtueahOmgkOa4rOiDveWKm+Wwh+iDveaJk+aVl+WFtuS7luWWruS4gOeorumgkOa4rOaWueW8j+eahOaooeWei+OAggoKLSAtIC0KCjxicj48YnI+PGJyPjxicj48YnI+Cgo8c3R5bGU+Ci5jYXB0aW9uIHsKICBjb2xvcjogIzc3NzsKICBtYXJnaW4tdG9wOiAxMHB4Owp9CnAgY29kZSB7CiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7Cn0KcHJlIHsKICB3b3JkLWJyZWFrOiBub3JtYWw7CiAgd29yZC13cmFwOiBub3JtYWw7CiAgbGluZS1oZWlnaHQ6IDE7Cn0KcHJlIGNvZGUgewogIHdoaXRlLXNwYWNlOiBpbmhlcml0Owp9CnAsbGkgewogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOwp9CgoucnsKICBsaW5lLWhlaWdodDogMS4yOwp9Cgp0aXRsZXsKICBjb2xvcjogI2NjMDAwMDsKICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsKfQoKYm9keXsKICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsKfQoKaDEsaDIsaDMsaDQsaDV7CiAgY29sb3I6ICMwMDY2ZmY7CiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7Cn0KCmg0LGg1ewogIGJhY2tncm91bmQ6ICNjY2ZmZmY7Cn0KCjwvc3R5bGU+CgoKCgoKCg==