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 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
【討論】
分析在空間資料的決策樹,經緯度會是一個很重要的變數。
- 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==