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

library(ROCR)
PredictROC = predict(rpart1, TS) # predict prob.
head(PredictROC)
0 1
1 0.30357 0.69643
3 0.30357 0.69643
4 0.40000 0.60000
6 0.40000 0.60000
8 0.40000 0.60000
21 0.30357 0.69643
perf = prediction(PredictROC[,2], TS$Reverse)
perf = performance(perf, "tpr", "fpr")
plot(perf)

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

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

【QUIZ-1】比較以上prp()、rpart.plot()和DPP()這幾張圖形
- 一棵決策樹的形狀, 和由它所產生的預測機率分布之間,有什麼關係呢 ? 以上DPP 圖中的分布點數、位置和高度,分別會對應到決策樹的什麼特徵呢?
- 柱狀圖偏左和偏右的形狀,假如此決策樹預測yes的機率較no高,此決策樹的形狀就會偏向右邊,反之若是no的機率較高,便會偏向左邊。
- (1)minbucket的值設的越小,決策樹最後的分類結果就會越多,DDP柱狀圖也會因此越多。 (2)紅色柱狀和綠色柱狀重疊越多(橘色部分),預測越不準 (3)決策樹上的機率剛好能對應到旁邊數量單位的比率,DDP能將決策樹呈的結果以柱狀圖呈現,方便理解。
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?
- If we set meanbucket too small, it may become overfit to the training set, but if we set it too large, the model will become too simple, either of them cause poor model.
1.8 Random Forest Method - randomForest::randomForest()
library(randomForest)
randomForest 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)
# Make predictions
pred = predict(rf1, TS)
x = table(TS$Reverse, pred)
sum(diag(x)) / sum(x) # 0.6824
[1] 0.6824
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)
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 = 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: 357, 356, 356, 356, 357, 356, ...
Resampling results across tuning parameters:
cp Accuracy Kappa
0.01 0.6136 0.20703
0.02 0.6137 0.20625
0.03 0.6238 0.23127
0.04 0.6314 0.25635
0.05 0.6441 0.28402
0.06 0.6441 0.28402
0.07 0.6441 0.28402
0.08 0.6441 0.28402
0.09 0.6441 0.28402
0.10 0.6441 0.28402
0.11 0.6441 0.28402
0.12 0.6441 0.28402
0.13 0.6441 0.28402
0.14 0.6441 0.28402
0.15 0.6441 0.28402
0.16 0.6441 0.28402
0.17 0.6441 0.28402
0.18 0.6441 0.28402
0.19 0.6210 0.23063
0.20 0.5830 0.13765
0.21 0.5830 0.13765
0.22 0.5530 0.04839
0.23 0.5479 0.03226
0.24 0.5454 0.01768
0.25 0.5454 0.00000
0.26 0.5454 0.00000
0.27 0.5454 0.00000
0.28 0.5454 0.00000
0.29 0.5454 0.00000
0.30 0.5454 0.00000
0.31 0.5454 0.00000
0.32 0.5454 0.00000
0.33 0.5454 0.00000
0.34 0.5454 0.00000
0.35 0.5454 0.00000
0.36 0.5454 0.00000
0.37 0.5454 0.00000
0.38 0.5454 0.00000
0.39 0.5454 0.00000
0.40 0.5454 0.00000
0.41 0.5454 0.00000
0.42 0.5454 0.00000
0.43 0.5454 0.00000
0.44 0.5454 0.00000
0.45 0.5454 0.00000
0.46 0.5454 0.00000
0.47 0.5454 0.00000
0.48 0.5454 0.00000
0.49 0.5454 0.00000
0.50 0.5454 0.00000
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?
- cp=0.19, 他有最高的accuracy,設在0.19能夠降低樹的複雜性。
1.10 The Final Model
rpart2 = rpart(Reverse ~ ., TR[,3:9], method="class", cp=0.19)
pred = predict(rpart2, TS, type='prob')[,2]
table(TS$Reverse, pred > 0.5) %>% {sum(diag(.)) / sum(.)} # 0.7235
[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 classfication呈現類別, regression呈現機率
- Tree Bagging - Random Forest
randomForest
多個決策術得分類器,隨機森林準確度更高,但會犧牲對圖的解釋性,因為個training set切的不一樣所以每個分配的indepedent variable也會不同
- Overfitting : Curse of Complexity overfitting代表在追求預設trainging set data的準確性而製作model的同時,可能將meanbucket,cp等參數設的太過符合training data,導致在預測其他data時反而失真。
- Model Parameter : Cost of Compexity 參數的多寡會影響模型複雜度,而這會影響到建立與使用一個模型的時間成本
- Parameter Tuning by CV (
caret package) 透過CV找到最佳參數,反覆驗證能提高準確性
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
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 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
# 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
library(rpart)
library(rpart.plot)
rpart1 = rpart(bucket2009 ~ ., data=TR[c(1:14, 16)],
method="class", cp=0.00005)
prp(rpart1)
Bad 'data' field in model 'call' field.
To make this warning go away:
Call prp with roundint=FALSE,
or rebuild the rpart model with model=TRUE.

# Make predictions
pred = predict(rpart1, newdata = TS, type = "class")
acc.rpart1 = table(TS$bucket2009, pred) %>%
{ sum(diag(.)) /nrow(TS)} # acc: .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
rpart2 = rpart(bucket2009 ~ ., data=TR[c(1:14, 16)],
method="class", cp=0.00005,
parms=list(loss=Penalty) # customized loss function 將penalty納入模型考慮【
)
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
input string 1 is invalid in this locale
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
【討論】
- 5 classes: 5X5 confussion/penalty(payoff) matrices penalty成本矩陣
- dumb vs. smart baseline dumb 以最多的類別作為預測基準 (最多是1,全預測為1) smart 以之前的資料作為預測基準 (去年你是2,今年也預測你2)
- model1: accuracy up, penalty up accuracy最高時,penalty不一定對低
- model2: accuracy down, penalty down 加入懲罰矩陣當參數後,會選擇penalty最低的結果,但accuracy不一定是最高的
- accuracy != profitability
- “customized” model optimize criteria in classification methods (not always available) penalty矩陣是我們自己訂的,不一定在每個model都適用
- This CV/PT process is over simplified 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 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)

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)

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

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 linear regression 可以看每個參數對結果的邊際效用 tree 是分析非線性關係
- the concept of ensembling 把兩個簡單的模型再做一個新的模型,可以綜效兩個模型觀察到的特性
LS0tDQp0aXRsZTogIkFTNC0wIOiqsuWgguethuiomCINCmF1dGhvcjogIkdyb3VwMiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCmBgYHtyfQ0KcGFja2FnZXMgPSBjKA0KICAiZHBseXIiLCJnZ3Bsb3QyIiwiZDNoZWF0bWFwIiwiZ29vZ2xlVmlzIiwiZGV2dG9vbHMiLCJwbG90bHkiLCAieGdib29zdCIsDQogICJtYWdyaXR0ciIsImNhVG9vbHMiLCJST0NSIiwiY29ycnBsb3QiLCAicnBhcnQiLCAicnBhcnQucGxvdCIsDQogICJkb1BhcmFsbGVsIiwgImNhcmV0IiwgImdsbW5ldCIsICJNYXRyaXgiLCAiZTEwNzEiLCAicmFuZG9tRm9yZXN0Ig0KICApDQpleGlzdGluZyA9IGFzLmNoYXJhY3RlcihpbnN0YWxsZWQucGFja2FnZXMoKVssMV0pDQpmb3IocGtnIGluIHBhY2thZ2VzWyEocGFja2FnZXMgJWluJSBleGlzdGluZyldKSBpbnN0YWxsLnBhY2thZ2VzKHBrZykNCmBgYA0KDQpgYGB7ciBlY2hvPVQsIG1lc3NhZ2U9RiwgY2FjaGU9Riwgd2FybmluZz1GfQ0Kcm0obGlzdD1scyhhbGw9VCkpDQpvcHRpb25zKGRpZ2l0cz00LCBzY2lwZW49MTIpDQpsaWJyYXJ5KGRwbHlyKQ0KYGBgDQoNCi0gLSAtDQoNCiMjIyAxLiBTdXByaW1lIENvdXJ0IERlY2lzaW9uDQoNCiMjIyMgMS4xIFByZXBhcmUgRGF0YQ0KYGBge3J9DQpEID0gcmVhZC5jc3YoImRhdGEvc3RldmVucy5jc3YiKQ0KDQpsaWJyYXJ5KGNhVG9vbHMpDQpzZXQuc2VlZCgzMDAwKQ0Kc3BsID0gc2FtcGxlLnNwbGl0KEQkUmV2ZXJzZSwgU3BsaXRSYXRpbyA9IDAuNykNClRSID0gc3Vic2V0KEQsIHNwbCkNClRTID0gc3Vic2V0KEQsICFzcGwpDQpgYGANCg0KIyMjIyAxLjIgQ0FSVCAoQ2xhc3NpZmljYXRpb24gJiBSZWdyZXNzaW9uIFRyZWUpIC0gYHJwYXJ0OjpycGFydCgpYA0KYGBge3J9DQpsaWJyYXJ5KHJwYXJ0KQ0KcnBhcnQxID0gcnBhcnQoUmV2ZXJzZSB+IC4sIFRSWywzOjldLCAgICAgICAgICAjIHNpbXBsaWZ5IHRoZSBmb3JtdWxhDQogICAgICAgICAgICAgICBtZXRob2Q9ImNsYXNzIiwgbWluYnVja2V0PTI1KQ0KYGBgDQoNCiMjIyMgMS4zIFBsb3QgRGVjaXNpb24gVHJlZQ0KYGBge3J9DQpsaWJyYXJ5KHJwYXJ0LnBsb3QpDQpwcnAocnBhcnQxKQ0KcnBhcnQucGxvdChycGFydDEsY2V4PTAuNikNCmBgYA0KDQojIyMjIDEuNCBNYWtlIFByZWRpY3Rpb24NCmBgYHtyfQ0KcHJlZCA9IHByZWRpY3QocnBhcnQxLCBUUywgdHlwZSA9ICJjbGFzcyIpICAjIHByZWRpY3QgY2xhc3Nlcw0KeCA9IHRhYmxlKGFjdHVhbCA9IFRTJFJldmVyc2UsIHByZWQpOyB4DQpzdW0oZGlhZyh4KSkvc3VtKHgpICAgICAgICAgICAgICAgICAgICAgICAgICMgLjY1ODgyDQpgYGANCg0KIyMjIyAxLjUgUk9DICYgQVVDDQpgYGB7cn0NCmxpYnJhcnkoUk9DUikNClByZWRpY3RST0MgPSBwcmVkaWN0KHJwYXJ0MSwgVFMpICAgICAgICAgICAgICAjIHByZWRpY3QgcHJvYi4NCmhlYWQoUHJlZGljdFJPQykNCnBlcmYgPSBwcmVkaWN0aW9uKFByZWRpY3RST0NbLDJdLCBUUyRSZXZlcnNlKQ0KcGVyZiA9IHBlcmZvcm1hbmNlKHBlcmYsICJ0cHIiLCAiZnByIikNCnBsb3QocGVyZikNCmBgYA0KDQpgYGB7cn0NCnByZWQgPSBwcmVkaWN0KHJwYXJ0MSwgVFMpWywyXSAgICAgIyBwcm9iLiBvZiBSZXZlcnNlID0gMSAgICAgICAgIA0KY29sQVVDKHByZWQsIFRTJFJldmVyc2UsIFQpICAgICAgICAjIEFVQyA9IDAuNjkyNw0KYGBgDQoNCg0KIyMjIyAxLjYgRFBQIG9mIERlY2lzaW9uIFRyZWUNCg0KYGBge3J9DQpycGFydC5wbG90KHJwYXJ0MSxjZXg9MC43NSx2YXJsZW49MyxmYWNsZW49Mixib3gucGFsZXR0ZT0iR25SZCIpDQpgYGANCg0KDQpgYGB7cn0NCnNvdXJjZSgiRFBQLlIiKQ0KYXVjID0gRFBQKHByZWRpY3QocnBhcnQxKVssMl0sIFRSJFJldmVyc2UsIDEpICAgICAjIEFVQyA9IDAuNzg4NA0KYGBgDQoNCiMjIyMg44CQUVVJWi0x44CR5q+U6LyD5Lul5LiKYHBycCgpYOOAgWBycGFydC5wbG90KClg5ZKMYERQUCgpYOmAmeW5vuW8teWcluW9og0KDQorIOS4gOajteaxuuetluaoueeahOW9oueLgO+8jCDlkoznlLHlroPmiYDnlKLnlJ/nmoTpoJDmuKzmqZ/njofliIbluIPkuYvplpPvvIzmnInku4Dpurzpl5zkv4LlkaIgPyAg5Lul5LiKRFBQIOWcluS4reeahOWIhuW4g+m7nuaVuOOAgeS9jee9ruWSjOmrmOW6pu+8jOWIhuWIpeacg+WwjeaHieWIsOaxuuetluaoueeahOS7gOm6vOeJueW+teWRoj8NCjEuICDmn7Hni4DlnJblgY/lt6blkozlgY/lj7PnmoTlvaLni4DvvIzlgYflpoLmraTmsbrnrZbmqLnpoJDmuKx5ZXPnmoTmqZ/njofovINub+mrmO+8jOatpOaxuuetluaoueeahOW9oueLgOWwseacg+WBj+WQkeWPs+mCiu+8jOWPjeS5i+iLpeaYr25v55qE5qmf546H6LyD6auY77yM5L6/5pyD5YGP5ZCR5bem6YKK44CCDQoyLg0KKDEpbWluYnVja2V055qE5YC86Kit55qE6LaK5bCP77yM5rG6562W5qi55pyA5b6M55qE5YiG6aGe57WQ5p6c5bCx5pyD6LaK5aSa77yMRERQ5p+x54uA5ZyW5Lmf5pyD5Zug5q2k6LaK5aSa44CCDQooMinntIXoibLmn7Hni4DlkozntqDoibLmn7Hni4Dph43nlorotorlpJrvvIjmqZjoibLpg6jliIbvvInvvIzpoJDmuKzotorkuI3mupYNCigzKeaxuuetluaoueS4iueahOapn+eOh+WJm+WlveiDveWwjeaHieWIsOaXgemCiuaVuOmHj+WWruS9jeeahOavlOeOh++8jEREUOiDveWwh+axuuetluaoueWRiOeahOe1kOaenOS7peafseeLgOWcluWRiOePvu+8jOaWueS+v+eQhuino+OAgg0KKyANCg0KPGJyPg0KDQojIyMjIDEuNyBUaGUgZWZmZWN0IG9mIGBtaW5idWNrZXRgDQpgYGB7cn0NCnQ1ID0gcnBhcnQoUmV2ZXJzZSB+IC4sIFRSWywzOjldLCBtZXRob2Q9ImNsYXNzIiwgbWluYnVja2V0PTUpDQpwcnAodDUpDQpgYGANCg0KYGBge3IgZmlnLmhlaWdodD0yfQ0KdDEwMCA9IHJwYXJ0KFJldmVyc2UgfiAuLCBUUlssMzo5XSwgbWV0aG9kPSJjbGFzcyIsIG1pbmJ1Y2tldD0xMDApDQpwcnAodDEwMCkNCmBgYA0KDQoNCiMjIyPjgJBRVUlaLTLjgJFCYXNlZCBvbiB0aGUgMiBwbG90cyBhYm92ZSwgd2hhdCBpcyB0aGUgZWZmZWN0IG9mIGBtaW5idWNrZXRgPyANCg0KKyBJZiB3ZSBzZXQgbWVhbmJ1Y2tldCB0b28gc21hbGwsIGl0IG1heSBiZWNvbWUgb3ZlcmZpdCB0byB0aGUgdHJhaW5pbmcgc2V0LCBidXQgaWYgd2Ugc2V0IGl0IHRvbyBsYXJnZSwgdGhlIG1vZGVsIHdpbGwgYmVjb21lIHRvbyBzaW1wbGUsIGVpdGhlciBvZiB0aGVtIGNhdXNlIHBvb3IgbW9kZWwuIA0KDQo8YnI+DQoNCiMjIyMgMS44IFJhbmRvbSBGb3Jlc3QgTWV0aG9kIC0gYHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KClgDQpgYGB7cn0NCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KVFIkUmV2ZXJzZSA9IGFzLmZhY3RvcihUUiRSZXZlcnNlKSAgIyBDb252ZXJ0IG91dGNvbWUgdG8gZmFjdG9yDQpUUyRSZXZlcnNlID0gYXMuZmFjdG9yKFRTJFJldmVyc2UpDQoNCiMgQnVpbGQgbW9kZWwNCnJmMSA9IHJhbmRvbUZvcmVzdChSZXZlcnNlIH4gLiwgVFJbLDM6OV0sIG50cmVlPTIwMCwgbm9kZXNpemU9MjUpDQoNCiMgTWFrZSBwcmVkaWN0aW9ucw0KcHJlZCA9IHByZWRpY3QocmYxLCBUUykNCnggPSB0YWJsZShUUyRSZXZlcnNlLCBwcmVkKQ0Kc3VtKGRpYWcoeCkpIC8gc3VtKHgpICAgICAgICAgICMgMC42ODI0DQpgYGANCg0KYGBge3J9DQpwcmVkID0gcHJlZGljdChyZjEsIFRTLCB0eXBlPSdwcm9iJylbLDJdDQp0YWJsZShUUyRSZXZlcnNlLCBwcmVkID4gMC41KSAlPiUge3N1bShkaWFnKC4pKS9zdW0oLil9DQpgYGANCg0KDQojIyMjIDEuOSBDcm9zcyBWYWxpZGF0aW9uICYgUGFyYW1ldGVyIFR1bmluZyAtIGBjYXJldGAgcGFja2FnZQ0KYGBge3J9DQpsaWJyYXJ5KGNhcmV0KQ0KbnVtRm9sZHMgPSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIG51bWJlcj0xMCkgIyAxMCBmb2xkIENWDQpjcEdyaWQgPSBleHBhbmQuZ3JpZChjcCA9IHNlcSgwLjAxLDAuNSwwLjAxKSkgICAjIHBhcmFtZXRlciBjb21iaW5hdGlvbg0KDQpjdjEgPSB0cmFpbihSZXZlcnNlIH4gLiwgVFJbLDM6OV0sIG1ldGhvZCA9ICJycGFydCIsIA0KICAgICAgICAgICAgdHJDb250cm9sPW51bUZvbGRzLCB0dW5lR3JpZD1jcEdyaWQpDQpjdjE7IHBsb3QoY3YxKQ0KYGBgDQoNCiMjIyPjgJBRVUlaLTPjgJFXaGljaCBgY3BgIHdoaWNoIHdlIHNob3VsZCB3ZSBjaG9vc2U/IFdoeT8gDQoNCisgY3A9MC4xOSwg5LuW5pyJ5pyA6auY55qEYWNjdXJhY3nvvIzoqK3lnKgwLjE56IO95aSg6ZmN5L2O5qi555qE6KSH6Zuc5oCn44CCDQoNCjxicj4NCg0KIyMjIyAxLjEwIFRoZSBGaW5hbCBNb2RlbA0KYGBge3J9DQpycGFydDIgPSBycGFydChSZXZlcnNlIH4gLiwgVFJbLDM6OV0sIG1ldGhvZD0iY2xhc3MiLCBjcD0wLjE5KQ0KcHJlZCA9IHByZWRpY3QocnBhcnQyLCBUUywgdHlwZT0ncHJvYicpWywyXQ0KdGFibGUoVFMkUmV2ZXJzZSwgcHJlZCA+IDAuNSkgJT4lIHtzdW0oZGlhZyguKSkgLyBzdW0oLil9ICAjIDAuNzIzNQ0KYGBgDQoNCmBgYHtyIGZpZy53aWR0aD0zfQ0KcnBhcnQucGxvdChycGFydDIpDQpgYGANCg0KIyMjI+OAkOiojuirlu+8muOAkQ0KDQorIENsYXNzaWZpY2F0aW9uICYgUmVncmVzc2lvbiBUcmVlICBgcnBhcnRgDQpjbGFzc2ZpY2F0aW9u5ZGI54++6aGe5YilLCByZWdyZXNzaW9u5ZGI54++5qmf546HDQorIFRyZWUgQmFnZ2luZyAtIFJhbmRvbSBGb3Jlc3QgYHJhbmRvbUZvcmVzdGAgIA0K5aSa5YCL5rG6562W6KGT5b6X5YiG6aGe5Zmo77yM6Zqo5qmf5qOu5p6X5rqW56K65bqm5pu06auY77yM5L2G5pyD54qn54my5bCN5ZyW55qE6Kej6YeL5oCn77yM5Zug54K65YCLdHJhaW5pbmcgc2V05YiH55qE5LiN5LiA5qij5omA5Lul5q+P5YCL5YiG6YWN55qEaW5kZXBlZGVudCB2YXJpYWJsZeS5n+acg+S4jeWQjA0KKyBPdmVyZml0dGluZyA6IEN1cnNlIG9mIENvbXBsZXhpdHkNCm92ZXJmaXR0aW5n5Luj6KGo5Zyo6L+95rGC6aCQ6KitdHJhaW5naW5nIHNldCBkYXRh55qE5rqW56K65oCn6ICM6KO95L2cbW9kZWznmoTlkIzmmYLvvIzlj6/og73lsIdtZWFuYnVja2V0LGNw562J5Y+D5pW46Kit55qE5aSq6YGO56ym5ZCIdHJhaW5pbmcgZGF0Ye+8jOWwjuiHtOWcqOmgkOa4rOWFtuS7lmRhdGHmmYLlj43ogIzlpLHnnJ/jgIINCisgTW9kZWwgUGFyYW1ldGVyIDogQ29zdCBvZiBDb21wZXhpdHkgDQrlj4PmlbjnmoTlpJrlr6HmnIPlvbHpn7/mqKHlnovopIfpm5zluqbvvIzogIzpgJnmnIPlvbHpn7/liLDlu7rnq4voiIfkvb/nlKjkuIDlgIvmqKHlnovnmoTmmYLplpPmiJDmnKwNCisgUGFyYW1ldGVyIFR1bmluZyBieSBDViAoYGNhcmV0YCBwYWNrYWdlKQ0K6YCP6YGO77yj77y25om+5Yiw5pyA5L2z5Y+D5pW477yM5Y+N6KaG6amX6K2J6IO95o+Q6auY5rqW56K65oCnDQoNCi0gLSAtDQoNCiMjIyAyIEQySGF3a0V5ZQ0KDQojIyMjIDIuMSBQcmVwYXJlICYgRXhhbWluZSBEYXRhIA0KYGBge3Igd2FybmluZz1GLCBtZXNzYWdlPUZ9DQpybShsaXN0PWxzKGFsbD1UUlVFKSkNCkQgPSByZWFkLmNzdigiZGF0YS9DbGFpbXNEYXRhLmNzdiIpDQoNCiMgUGVyY2VudGFnZSBvZiBwYXRpZW50cyBpbiBlYWNoIGNvc3QgYnVja2V0DQp0YWJsZShEJGJ1Y2tldDIwMDkpL25yb3coRCkNCmBgYA0KDQpgYGB7cn0NCiMgU3BsaXQgdGhlIGRhdGENCmxpYnJhcnkoY2FUb29scykNCnNldC5zZWVkKDg4KQ0Kc3BsID0gc2FtcGxlLnNwbGl0KEQkYnVja2V0MjAwOSwgU3BsaXRSYXRpbyA9IDAuNikNClRSID0gc3Vic2V0KEQsIHNwbCkNClRTID0gc3Vic2V0KEQsICFzcGwpDQpgYGANCg0KYGBge3J9DQojIENoZWNrIFBlcmNlbnRhZ2VzIEFtb25nIFN1YnNldHMNCmxpYnJhcnkoZHBseXIpDQp0YWJsZShUUiRidWNrZXQyMDA5KSAlPiUgcHJvcC50YWJsZQ0KdGFibGUoVFMkYnVja2V0MjAwOSkgJT4lIHByb3AudGFibGUNCnRhYmxlKEQkYnVja2V0MjAwOSkgJT4lIHByb3AudGFibGUNCg0KIyBzYXBwbHkoKTogQXBwbHkgRnVuY3Rpb24gJiBBZ2dyZWdhdGUgT3V0cHV0DQpzYXBwbHkobGlzdChELCBUUiwgVFMpLCBmdW5jdGlvbih4KSANCiAgdGFibGUoeCRidWNrZXQyMDA5KSAlPiUgcHJvcC50YWJsZSkgJT4lIHQNCmBgYA0KDQpgYGB7cn0NCiMgRGF0YSBFeHBsb3JhdGlvbiANCm1lYW4oVFIkYWdlKSAgICAgICAgICAgICAgICAgIyA3Mi42NA0KbWVhbihUUiRkaWFiZXRlcykgICAgICAgICAgICAjIC4zODA5IA0KYGBgDQoNCiMjIyMgMi4yIEJhc2VsaW5lIE1vZGVsIA0KYGBge3J9DQojIEJhc2VsaW5lIEFjY3VyYWN5DQphY2MuYmFzZSA9IHRhYmxlKFRTJGJ1Y2tldDIwMDksIFRTJGJ1Y2tldDIwMDgpICU+JSANCiAge3N1bShkaWFnKC4pKSAvIHN1bSguKX0gICAgIyAuNjgzOA0KYGBgDQoNCmBgYHtyfQ0KIyBQZW5hbHR5IE1hdHJpeA0KUGVuYWx0eSA9IG1hdHJpeChjKA0KICAwLDEsMiwzLDQsDQogIDIsMCwxLDIsMywNCiAgNCwyLDAsMSwyLA0KICA2LDQsMiwwLDEsDQogIDgsNiw0LDIsMCksIGJ5cm93PVRSVUUsIG5yb3c9NSkNClBlbmFsdHkNCmBgYA0KDQpgYGB7cn0NCiMgUGVuYWx0eSBFcnJvciBvZiBCYXNlbGluZSBNZXRob2QNCmNtID0gYXMubWF0cml4KHRhYmxlKFRTJGJ1Y2tldDIwMDksIFRTJGJ1Y2tldDIwMDgpKQ0KcGVuLmJhc2UgPSBzdW0oY20qUGVuYWx0eSkgLyBucm93KFRTKSAgICAjIHBlbjogLjczODYNCmBgYA0KDQpgYGB7cn0NCiMgaWYgd2UgdXNlIHRoZSBtb3N0IHBvcHVsYXIgY2xhc3MgYXMgYmFzZWxpbmUNCmFjYy5kdW1iID0gKHRhYmxlKFRTJGJ1Y2tldDIwMDkpL25yb3coVFMpKVsxXSAgICAgICAgICAgICAgICAgIyBhY2M6IC42NzEzIA0KcGVuLmR1bWIgPSBzdW0odGFibGUoVFMkYnVja2V0MjAwOSkgKiBjKDAsMiw0LDYsOCkpL25yb3coVFMpICAjIHBlbjogMS4wNDQNCmBgYA0KDQojIyMjIENBUlQgTW9kZWwNCmBgYHtyfQ0KbGlicmFyeShycGFydCkNCmxpYnJhcnkocnBhcnQucGxvdCkNCnJwYXJ0MSA9IHJwYXJ0KGJ1Y2tldDIwMDkgfiAuLCBkYXRhPVRSW2MoMToxNCwgMTYpXSwgDQogICAgICAgICAgICAgICBtZXRob2Q9ImNsYXNzIiwgY3A9MC4wMDAwNSkNCnBycChycGFydDEpDQpgYGANCg0KYGBge3J9DQojIE1ha2UgcHJlZGljdGlvbnMNCnByZWQgPSBwcmVkaWN0KHJwYXJ0MSwgbmV3ZGF0YSA9IFRTLCB0eXBlID0gImNsYXNzIikNCmFjYy5ycGFydDEgPSB0YWJsZShUUyRidWNrZXQyMDA5LCBwcmVkKSAlPiUgDQogIHsgc3VtKGRpYWcoLikpIC9ucm93KFRTKX0gICAgICAgICAgICAjIGFjYzogLjcxMjY3IDwtIC42ODM4MQ0KDQojIFBlbmFsdHkgRXJyb3INCmNtID0gYXMubWF0cml4KHRhYmxlKFRTJGJ1Y2tldDIwMDksIHByZWQpKQ0KcGVuLnJwYXJ0MSA9IHN1bShjbSpQZW5hbHR5KS9ucm93KFRTKSAgIyBwZW46IC43NTc4OSA8LSAuNzM4Ng0KYGBgDQoNCiMjIyMgQ0FSVCBNb2RlbCB3aXRoIEN1c3RvbWl6ZWQgTG9zcyBNYXRyaXgNCmBgYHtyfQ0KcnBhcnQyID0gcnBhcnQoYnVja2V0MjAwOSB+IC4sIGRhdGE9VFJbYygxOjE0LCAxNildLCANCiAgICAgICAgICAgICAgIG1ldGhvZD0iY2xhc3MiLCBjcD0wLjAwMDA1LCANCiAgICAgICAgICAgICAgIHBhcm1zPWxpc3QobG9zcz1QZW5hbHR5KSkNCg0KcHJlZCA9IHByZWRpY3QocnBhcnQyLCBUUywgdHlwZSA9ICJjbGFzcyIpDQphY2MucnBhcnQyID0gdGFibGUoVFMkYnVja2V0MjAwOSwgcHJlZCkgJT4lIA0KICB7c3VtKGRpYWcoLikpL3N1bSguKX0gICAgICAgICAgICAgICAgICAjIGFjYy5ycGFydDI6IC42NDcyNyAgIA0KDQpjbSA9IGFzLm1hdHJpeCh0YWJsZShUUyRidWNrZXQyMDA5LCBwcmVkKSkNCnBlbi5ycGFydDIgPSBzdW0oY20qUGVuYWx0eSkvbnJvdyhUUykgICAgIyBwZW4ucnBhcnQyOiAuNjQxODIgDQpgYGANCg0KYGBge3J9DQojIFN1bW1hcnkNCmRhdGEuZnJhbWUoDQogIGFjY3VyYWN5ID0gYyhhY2MuZHVtYiwgYWNjLmJhc2UsIGFjYy5ycGFydDEsIGFjYy5ycGFydDIpLA0KICBwZW5hbHR5ID0gYyhwZW4uZHVtYiwgcGVuLmJhc2UsIHBlbi5ycGFydDEsIHBlbi5ycGFydDIpLA0KICByb3cubmFtZXMgPSBjKCJkdW1iIiwiYmFzZSIsIkNBUlQiLCJDQVJUIHcvTG9zc014IikpDQpgYGANCg0KIyMjIyDjgJDoqI7oq5bjgJENCg0KKyA1IGNsYXNzZXM6IDVYNSBjb25mdXNzaW9uL3BlbmFsdHkocGF5b2ZmKSBtYXRyaWNlcw0KcGVuYWx0eeaIkOacrOefqemZow0KKyBkdW1iIHZzLiBzbWFydCBiYXNlbGluZQ0KZHVtYiDku6XmnIDlpJrnmoTpoZ7liKXkvZzngrrpoJDmuKzln7rmupYg77yI5pyA5aSa5pivMSzlhajpoJDmuKzngroxKQ0Kc21hcnQg5Lul5LmL5YmN55qE6LOH5paZ5L2c54K66aCQ5ris5Z+65rqWICAo5Y675bm05L2g5pivMu+8jOS7iuW5tOS5n+mgkOa4rOS9oDIpDQorIG1vZGVsMTogYWNjdXJhY3kgdXAsIHBlbmFsdHkgdXANCmFjY3VyYWN55pyA6auY5pmC77yMcGVuYWx0eeS4jeS4gOWumuWwjeS9jg0KKyBtb2RlbDI6IGFjY3VyYWN5IGRvd24sIHBlbmFsdHkgZG93bg0K5Yqg5YWl5oey572w55+p6Zmj55W25Y+D5pW45b6M77yM5pyD6YG45pOHcGVuYWx0eeacgOS9jueahOe1kOaenO+8jOS9hmFjY3VyYWN55LiN5LiA5a6a5piv5pyA6auY55qEDQorIGFjY3VyYWN5ICE9IHByb2ZpdGFiaWxpdHkNCisgImN1c3RvbWl6ZWQiIG1vZGVsIG9wdGltaXplIGNyaXRlcmlhIGluIGNsYXNzaWZpY2F0aW9uIG1ldGhvZHMgKG5vdCBhbHdheXMgYXZhaWxhYmxlKQ0KcGVuYWx0eeefqemZo+aYr+aIkeWAkeiHquW3seiogueahO+8jOS4jeS4gOWumuWcqOavj+WAi21vZGVs6YO96YGp55SoDQorIFRoaXMgQ1YvUFQgcHJvY2VzcyBpcyBvdmVyIHNpbXBsaWZpZWQNCmNw5pyA5aW957aT6YGO5Y+N6KaG6amX6K2J5LmL5b6M5YaN5rG65a6aDQorIHdlIHdpbGwgY292ZXIgYSBiZXR0ZXIgcHJvY2VzcyBuZXh0IHRpbWUNCg0KLSAtIC0NCg0KIyMjIDMgQm9zdG9uIEhvdXNlIFByaWNlDQoNCmBgYHtyfQ0Kcm0obGlzdD1scyhhbGw9VFJVRSkpDQpEID0gcmVhZC5jc3YoImRhdGEvYm9zdG9uLmNzdiIpDQpgYGANCg0KIyMjIyAzLjEgRXhhbWluZSBEYXRhDQpgYGB7cn0NCiMgUGxvdCBvYnNlcnZhdGlvbnMNCnBsb3QoRCRMT04sIEQkTEFUKQ0KDQojIFRyYWN0cyBhbG9uZ3NpZGUgdGhlIENoYXJsZXMgUml2ZXINCndpdGgoRCwgDQogICAgIHBvaW50cyhMT05bQ0hBUz09MV0sIExBVFtDSEFTPT0xXSwgY29sPSJibHVlIiwgcGNoPTE5KSkNCg0KIyBQbG90IE1JVA0Kd2l0aChELCANCiAgICAgcG9pbnRzKExPTltUUkFDVD09MzUzMV0sIExBVFtUUkFDVD09MzUzMV0sIGNvbD0icmVkIiwgcGNoPTE5KSApDQoNCiMgUGxvdCBwb2x1dGlvbg0Kc3VtbWFyeShEJE5PWCkNCnBvaW50cyhEJExPTltEJE5PWD49MC41NV0sIEQkTEFUW0QkTk9YPj0wLjU1XSwgDQogICAgICAgY29sPSJncmVlbiIsIHBjaD0yMCkNCg0KIyBQbG90IHByaWNlcw0KcGxvdChEJExPTiwgRCRMQVQpDQpzdW1tYXJ5KEQkTUVEVikNCnBvaW50cyhEJExPTltEJE1FRFY+PTIxLjJdLCBEJExBVFtEJE1FRFY+PTIxLjJdLCANCiAgICAgICBjb2w9InJlZCIsIHBjaD0yMCkNCmBgYA0KDQojIyMjIExpbmVhciBSZWdyZXNzaW9uIHVzaW5nIExBVCBhbmQgTE9ODQpgYGB7cn0NCmxhdGxvbmxtID0gbG0oTUVEViB+IExBVCArIExPTiwgZGF0YT1EKQ0Kc3VtbWFyeShsYXRsb25sbSkNCmBgYA0KDQojIyMjIFZpc3VhbGl6ZSByZWdyZXNzaW9uIG91dHB1dA0KYGBge3J9DQpwbG90KEQkTE9OLCBEJExBVCkNCnBvaW50cyhEJExPTltEJE1FRFY+PTIxLjJdLCBEJExBVFtEJE1FRFY+PTIxLjJdLCANCiAgICAgICBjb2w9InJlZCIsIHBjaD0yMCkNCg0KIyBsYXRsb25sbSRmaXR0ZWQudmFsdWVzDQpwb2ludHMoRCRMT05bbGF0bG9ubG0kZml0dGVkLnZhbHVlcyA+PSAyMS4yXSwgDQogICAgICAgRCRMQVRbbGF0bG9ubG0kZml0dGVkLnZhbHVlcyA+PSAyMS4yXSwgDQogICAgICAgY29sPSJibHVlIiwgcGNoPSJ4IikNCmBgYA0KDQojIExvYWQgQ0FSVCBwYWNrYWdlcw0KYGBge3J9DQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KIyBDQVJUIG1vZGVsDQpsYXRsb250cmVlID0gcnBhcnQoTUVEViB+IExBVCArIExPTiwgZGF0YT1EKQ0KcHJwKGxhdGxvbnRyZWUpDQoNCiMgVmlzdWFsaXplIG91dHB1dA0KcGxvdChEJExPTiwgRCRMQVQpDQpwb2ludHMoRCRMT05bRCRNRURWPj0yMS4yXSwgRCRMQVRbRCRNRURWPj0yMS4yXSwgDQogICAgICAgY29sPSJyZWQiLCBwY2g9MjApDQoNCmZpdHRlZHZhbHVlcyA9IHByZWRpY3QobGF0bG9udHJlZSkNCnBvaW50cyhEJExPTltmaXR0ZWR2YWx1ZXM+MjEuMl0sIA0KICAgICAgIEQkTEFUW2ZpdHRlZHZhbHVlcz49MjEuMl0sIGNvbD0iYmx1ZSIsIHBjaD0ieCIpDQpgYGANCg0KIyMjIyBTaW1wbGlmeSB0cmVlIGJ5IGluY3JlYXNpbmcgbWluYnVja2V0DQpgYGB7cn0NCmxhdGxvbnRyZWUgPSBycGFydChNRURWIH4gTEFUICsgTE9OLCBkYXRhPUQsIG1pbmJ1Y2tldD01MCkNCnJwYXJ0LnBsb3QobGF0bG9udHJlZSkNCmBgYA0KDQpgYGB7cn0NCiMgVmlzdWFsaXplIE91dHB1dA0KcGxvdChEJExPTixEJExBVCkNCmFibGluZSh2PS03MS4wNykNCmFibGluZShoPTQyLjIxKQ0KYWJsaW5lKGg9NDIuMTcpDQpwb2ludHMoRCRMT05bRCRNRURWPj0yMS4yXSwgDQogICAgICAgRCRMQVRbRCRNRURWPj0yMS4yXSwgY29sPSJyZWQiLCBwY2g9MjApDQpgYGANCg0KIyMjIyBVc2UgYWxsIHRoZSB2YXJpYWJsZXMNCmBgYHtyfQ0KIyBTcGxpdCB0aGUgZGF0YQ0KbGlicmFyeShjYVRvb2xzKQ0Kc2V0LnNlZWQoMTIzKQ0Kc3BsaXQgPSBzYW1wbGUuc3BsaXQoRCRNRURWLCBTcGxpdFJhdGlvID0gMC43KQ0KdHJhaW4gPSBzdWJzZXQoRCwgc3BsaXQ9PVRSVUUpDQp0ZXN0ID0gc3Vic2V0KEQsIHNwbGl0PT1GQUxTRSkNCg0KIyBDcmVhdGUgbGluZWFyIHJlZ3Jlc3Npb24NCmxpbnJlZyA9IGxtKE1FRFYgfiBMQVQgKyBMT04gKyBDUklNICsgWk4gKyBJTkRVUyArIENIQVMgKyBOT1ggKyBSTSArIA0KICAgICAgICAgICAgICBBR0UgKyBESVMgKyBSQUQgKyBUQVggKyBQVFJBVElPLCBkYXRhPXRyYWluKQ0Kc3VtbWFyeShsaW5yZWcpDQoNCiMgTWFrZSBwcmVkaWN0aW9ucw0KbGlucmVnLnByZWQgPSBwcmVkaWN0KGxpbnJlZywgbmV3ZGF0YT10ZXN0KQ0KbGlucmVnLnNzZSA9IHN1bSgobGlucmVnLnByZWQgLSB0ZXN0JE1FRFYpXjIpDQpsaW5yZWcuc3NlDQpgYGANCg0KIyMjIyBDcmVhdGUgYSBDQVJUIG1vZGVsDQpgYGB7cn0NCnRyZWUgPSBycGFydChNRURWIH4gTEFUICsgTE9OICsgQ1JJTSArIFpOICsgSU5EVVMgKyBDSEFTICsgTk9YICsgUk0gKyANCiAgICAgICAgICAgICAgIEFHRSArIERJUyArIFJBRCArIFRBWCArIFBUUkFUSU8sIGRhdGE9dHJhaW4pDQpwcnAodHJlZSkNCg0KIyBNYWtlIHByZWRpY3Rpb25zDQp0cmVlLnByZWQgPSBwcmVkaWN0KHRyZWUsIG5ld2RhdGE9dGVzdCkNCnRyZWUuc3NlID0gc3VtKCh0cmVlLnByZWQgLSB0ZXN0JE1FRFYpXjIpDQp0cmVlLnNzZQ0KYGBgDQoNCiMjIyMgTG9hZCBsaWJyYXJpZXMgZm9yIGNyb3NzLXZhbGlkYXRpb24NCmBgYHtyfQ0KbGlicmFyeShjYXJldCkNCg0KIyBOdW1iZXIgb2YgZm9sZHMNCnRyLmNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApDQoNCiMgY3AgdmFsdWVzDQpjcC5ncmlkID0gZXhwYW5kLmdyaWQoIC5jcCA9ICgwOjEwKSowLjAwMSkNCg0KIyBDcm9zcy12YWxpZGF0aW9uDQp0ciA9IHRyYWluKE1FRFYgfiBMQVQgKyBMT04gKyBDUklNICsgWk4gKyBJTkRVUyArIENIQVMgKyBOT1ggKyBSTSArIA0KICAgICAgICAgICAgIEFHRSArIERJUyArIFJBRCArIFRBWCArIFBUUkFUSU8sIA0KICAgICAgICAgICBkYXRhID0gdHJhaW4sIG1ldGhvZCA9ICJycGFydCIsIA0KICAgICAgICAgICB0ckNvbnRyb2wgPSB0ci5jb250cm9sLCB0dW5lR3JpZCA9IGNwLmdyaWQpDQp0cg0KDQojIEV4dHJhY3QgdHJlZQ0KYmVzdC50cmVlID0gdHIkZmluYWxNb2RlbA0KcHJwKGJlc3QudHJlZSkNCg0KIyBNYWtlIHByZWRpY3Rpb25zDQpiZXN0LnRyZWUucHJlZCA9IHByZWRpY3QoYmVzdC50cmVlLCBuZXdkYXRhPXRlc3QpDQpiZXN0LnRyZWUuc3NlID0gc3VtKChiZXN0LnRyZWUucHJlZCAtIHRlc3QkTUVEVileMikNCmJlc3QudHJlZS5zc2UNCmBgYA0KDQojIyMjIEVuc2VtYmxpbmcNCmBgYHtyfQ0KZW4ucHJlZCA9IChiZXN0LnRyZWUucHJlZCArIGxpbnJlZy5wcmVkKS8yDQpzdW0oKGVuLnByZWQgLSB0ZXN0JE1FRFYpXjIpDQpgYGANCg0KDQpgYGB7cn0NCiMgICAgICAgTUVUSE9ECSBTU0UNCiMgICAgICAgICAgIGxtCTMwMzcNCiMgICAgICAgICBycGFydAk0MzI5DQojICAgICAgcnBhcnQuY3YJMzY2MA0KIyAgIGxtK3JwYXJ0LmN2CTI1OTkgIA0KYGBgDQoNCg0KIyMjIyDjgJDoqI7oq5bjgJENCg0KKyBkcmF3aW5nIG1hcCB3aXRoIHNwYXRpYWwgZGF0YSANCisgdHJlZSBpbiBzcGF0aWFsIGRhdGENCisgdHJlZSB2cy4gbGluZWFyIHJlZ3Jlc3Npb24NCmxpbmVhciByZWdyZXNzaW9uIOWPr+S7peeci+avj+WAi+WPg+aVuOWwjee1kOaenOeahOmCiumam+aViOeUqA0KdHJlZSDmmK/liIbmnpDpnZ7nt5rmgKfpl5zkv4INCisgdGhlIGNvbmNlcHQgb2YgZW5zZW1ibGluZw0K5oqK5YWp5YCL57Ch5Zau55qE5qih5Z6L5YaN5YGa5LiA5YCL5paw55qE5qih5Z6L77yM5Y+v5Lul57ac5pWI5YWp5YCL5qih5Z6L6KeA5a+f5Yiw55qE54m55oCnDQoNCi0gLSAtDQoNCjxicj48YnI+PGJyPjxicj48YnI+DQoNCjxzdHlsZT4NCi5jYXB0aW9uIHsNCiAgY29sb3I6ICM3Nzc7DQogIG1hcmdpbi10b3A6IDEwcHg7DQp9DQpwIGNvZGUgew0KICB3aGl0ZS1zcGFjZTogaW5oZXJpdDsNCn0NCnByZSB7DQogIHdvcmQtYnJlYWs6IG5vcm1hbDsNCiAgd29yZC13cmFwOiBub3JtYWw7DQogIGxpbmUtaGVpZ2h0OiAxOw0KfQ0KcHJlIGNvZGUgew0KICB3aGl0ZS1zcGFjZTogaW5oZXJpdDsNCn0NCnAsbGkgew0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KLnJ7DQogIGxpbmUtaGVpZ2h0OiAxLjI7DQp9DQoNCnRpdGxlew0KICBjb2xvcjogI2NjMDAwMDsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCmJvZHl7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpoMSxoMixoMyxoNCxoNXsNCiAgY29sb3I6ICMwMDY2ZmY7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpoNCxoNXsNCiAgYmFja2dyb3VuZDogI2NjZmZmZjsNCn0NCg0KPC9zdHlsZT4NCg0KDQoNCg0KDQoNCg==