【A】 Definitions
機率、勝率(Odd)、Logit
par(cex=0.8, mfcol=c(1,2))
curve(x/(1-x), 0.02, 0.98, col='cyan',lwd=2, main='odd')
abline(v=seq(0,1,0.1), h=seq(0,50,5), col='lightgray', lty=3)
#格線
curve(log(x/(1-x)), 0.005, 0.995, lwd=2, col='purple', main="logit")
abline(v=seq(0,1,0.1), h=seq(-5,5,1), col='lightgray', lty=3)

Logistic Function & Logistic Regression
Linear Model: \(y = f(x) = b_0 + b_1x_1 + b_2x_2 + ...\)
General Linear Model(GLM): \(y = Link(f(x))\)
Logistic Regression: \(logit(y) = log(\frac{p}{1-p}) = f(x) \text{ where } p = prob[y=1]\)
Logistic Function: \(Logistic(F_x) = \frac{1}{1+Exp(-F_x)} = \frac{Exp(F_x)}{1+Exp(F_x)}\)
par(cex=0.8)
curve(1/(1+exp(-x)), -5, 5, col='blue', lwd=2,main="Logistic Function",
xlab="f(x): the logit of y = 1", ylab="the probability of y = 1")
abline(v=-5:5, h=seq(0,1,0.1), col='lightgray', lty=2)
abline(v=0,h=0.5,col='pink')
points(0,0.5,pch=20,cex=1.5,col='red')

【Q】What are the definiion of logit & logistic function? What is the relationship between them?
logit是odd取log
logistic function是把logit轉換成y=1的機率
【B】glm(, family=binomial)
glm()的功能:在 \(\{x\}\) (自變數)的空間之中,找出區隔 \(y\) 的(類別)界線
Q = read.csv("data/quality.csv") # Read in dataset
glm1 = glm(PoorCare~OfficeVisits+Narcotics, Q, family=binomial)
#family參數是表示有很多不同的linking function
summary(glm1)
Call:
glm(formula = PoorCare ~ OfficeVisits + Narcotics, family = binomial,
data = Q)
Deviance Residuals:
Min 1Q Median 3Q Max
-2.377 -0.627 -0.510 -0.154 2.119
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.5402 0.4500 -5.64 0.000000017 ***
OfficeVisits 0.0627 0.0240 2.62 0.00892 **
Narcotics 0.1099 0.0326 3.37 0.00076 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 147.88 on 130 degrees of freedom
Residual deviance: 116.45 on 128 degrees of freedom
AIC: 122.4
Number of Fisher Scoring iterations: 5
b = coef(glm1); b # extract the regression coef
(Intercept) OfficeVisits Narcotics
-2.54021 0.06273 0.10990
Given OfficeVisits=3, Narcotics=4, what are the predicted logit, odd and probability?
logit = sum(b * c(1, 3, 4)) #intercepts截距用1乘 係數x變數值
odd = exp(logit)
prob = odd/(1+odd)
c(logit=logit, odd=odd, prob=prob)
logit odd prob
-1.9124 0.1477 0.1287
【Q】What if OfficeVisits=2, Narcotics=3?
logit = sum(b * c(1, 2, 3)) #intercepts截距用1乘 係數x變數值
odd = exp(logit) #poor care的機率
prob = odd/(1+odd)
c(logit=logit, odd=odd, prob=prob)
logit odd prob
-2.0851 0.1243 0.1106
We can plot the line of logit = 0 or prob = 0.5 on the plane of \(X\)
par(cex=0.8)
plot(Q$OfficeVisits, Q$Narcotics, col=1+Q$PoorCare,pch=20) #col顏色從1算起,但poorCare從0,所以才+1
abline(-b[1]/b[3], -b[2]/b[3])

Furthermore, we can translate probability, logit and coefficents to intercept & slope …
\[f(x) = b_1 + b_2 x_2 + b_3 x_3 = g \Rightarrow x_3 = \frac{g - b_1}{b_3} - \frac{b_2}{b_3}x_2\]
p = seq(0.1,0.9,0.1)
logit = log(p/(1-p))
data.frame(prob = p, logit)
then mark the contours of proabilities into the scatter plot
par(cex=0.7)
plot(Q$OfficeVisits, Q$Narcotics, col=1+Q$PoorCare,
pch=20, cex=1.3, xlab='X2', ylab='X3')
for(g in logit) {
abline((g-b[1])/b[3], -b[2]/b[3], col=ifelse(g==0,'blue','cyan')) }

【Q】What do the blue/cyan lines means?
代表0.1~0.9的機率
【Q】Given any point in the figure above, how can you tell its (predicted) probability approximately?
越接近哪一條線,就大約是那個機率
【D】The Distribution of Predicted Probability (DPP)
Confusion matrix is not fixed. It changes by Threshold …
設定threshold(臨界機率) 決定機率在多少以上,預測為y=1。定義了threshold才會有confusion matrix,移動threshold就會有不一樣的confusion matrix
library(caTools)
DPP2 = function(pred,class,tvalue,breaks=0.01) {
mx = table(class == tvalue, pred > 0.5)
tn = sum(class != tvalue & pred <= 0.5)
fn = sum(class == tvalue & pred <= 0.5)
fp = sum(class != tvalue & pred > 0.5)
tp = sum(class == tvalue & pred > 0.5)
acc = (tn + tp)/length(pred)
sens = tp/(fn+tp)
spec = tn/(tn+fp)
auc = colAUC(pred,class)
data.frame(pred,class) %>%
ggplot(aes(x=pred, fill=class)) +
geom_histogram(col='gray',alpha=0.5,breaks=seq(0,1,breaks)) +
xlim(0,1) + theme_bw() + xlab("predicted probability") +
ggtitle(
sprintf("Distribution of Prob[class = \'%s\']", tvalue),
sprintf("AUC=%.3f, Acc=%.3f, Sens=%.3f, Spec=%.3f",
auc, acc, sens, spec) )
}
ROC:
TPR和FPR的權衡。0~1 不同thresold的得出的TPR和FPR得到的曲線 ####AUC: ROC曲線下的面積。代表模型在臨界機率中的辨識能力 ####DPP: distribution of predicted probability
N1 = 300; N2 = 100 #兩群分類
DPP2(pred = c(rnorm(N1,0.125,0.03), rnorm(N2,0.375,0.03)),
class = c(rep('B',N1), rep('A',N2)),
tvalue = 'A')

#predicted probalility 代表 預測為A的機率
#acc = 0.75 是因為在threshold0.5下,紅色的部分都猜錯了,acc = 1 - 100/400
#sens(sensitivity,TPR) = TP / FN+TP
【Q】Is it possible to have AUC = ACC = SENS = SPEC = 1? Can you modify the code to make it happen?
#不可能
N1 = 300; N2 = 100 #兩群分類
DPP2(pred = c(rnorm(N1,0.125,0.03), rnorm(N2,0.625,0.03)),
class = c(rep('B',N1), rep('A',N2)),
tvalue = 'A')

【Q】Is it possible to have AUC = ACC = SENS = SPEC = 0? Can you modify the code to make that happen?
#
#
【E】Modeling Expert
E1: Random Split
set.seed(88)
split = sample.split(Q$PoorCare, SplitRatio = 0.75)
table(split) %>% prop.table()
split
FALSE TRUE
0.2443 0.7557
table(y = Q$PoorCare, split) %>% prop.table(2)
split
y FALSE TRUE
0 0.7500 0.7475
1 0.2500 0.2525
TR = subset(Q, split == TRUE)
TS = subset(Q, split == FALSE)
E2: Build Model
glm1 = glm(PoorCare ~ OfficeVisits + Narcotics, TR, family=binomial)
summary(glm1)
Call:
glm(formula = PoorCare ~ OfficeVisits + Narcotics, family = binomial,
data = TR)
Deviance Residuals:
Min 1Q Median 3Q Max
-2.0630 -0.6316 -0.5050 -0.0969 2.1669
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -2.6461 0.5236 -5.05 0.00000043 ***
OfficeVisits 0.0821 0.0305 2.69 0.0072 **
Narcotics 0.0763 0.0321 2.38 0.0173 *
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 111.888 on 98 degrees of freedom
Residual deviance: 89.127 on 96 degrees of freedom
AIC: 95.13
Number of Fisher Scoring iterations: 4
E3: Prediction & Evaluation
pred = predict(glm1, type='response')
mx = table(TR$PoorCare, pred > 0.5); mx
FALSE TRUE
0 70 4
1 15 10
c(accuracy = sum(diag(mx))/sum(mx),
sensitivity = mx[2,2]/sum(mx[2,]),
specificity = mx[1,1]/sum(mx[1,]))
accuracy sensitivity specificity
0.8081 0.4000 0.9459
E4: ROC & AUC
library(ROCR)
ROCRpred = prediction(pred, TR$PoorCare)
ROCRperf = performance(ROCRpred, "tpr", "fpr")
par(cex=0.8)
plot(ROCRperf, colorize=TRUE, print.cutoffs.at=seq(0,1,0.1))

as.numeric(performance(ROCRpred, "auc")@y.values)
[1] 0.7746
caTools::colAUC(pred, TR$PoorCare)
[,1]
0 vs. 1 0.7746
【F】Framingham Heart Study
source("DPP.R") #載入DPP.R
F1: Reading & Splitting
F = read.csv("data/framingham.csv")
set.seed(1000)
split = sample.split(F$TenYearCHD, SplitRatio = 0.65)
TR = subset(F, split==TRUE)
TS = subset(F, split==FALSE)
F2: Logistic Regression Model
glm2 = glm(TenYearCHD ~ ., TR, family=binomial) #train時會自動去掉na
summary(glm2)
Call:
glm(formula = TenYearCHD ~ ., family = binomial, data = TR)
Deviance Residuals:
Min 1Q Median 3Q Max
-1.849 -0.601 -0.426 -0.284 2.837
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -7.88657 0.89073 -8.85 < 2e-16 ***
male 0.52846 0.13544 3.90 0.0000955212349 ***
age 0.06206 0.00834 7.44 0.0000000000001 ***
education -0.05892 0.06243 -0.94 0.3453
currentSmoker 0.09324 0.19401 0.48 0.6308
cigsPerDay 0.01501 0.00783 1.92 0.0551 .
BPMeds 0.31122 0.28741 1.08 0.2789
prevalentStroke 1.16579 0.57121 2.04 0.0413 *
prevalentHyp 0.31582 0.17176 1.84 0.0660 .
diabetes -0.42149 0.40799 -1.03 0.3016
totChol 0.00384 0.00138 2.79 0.0053 **
sysBP 0.01134 0.00457 2.48 0.0130 *
diaBP -0.00474 0.00800 -0.59 0.5535
BMI 0.01072 0.01616 0.66 0.5069
heartRate -0.00810 0.00531 -1.52 0.1274
glucose 0.00893 0.00284 3.15 0.0016 **
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 2020.7 on 2384 degrees of freedom
Residual deviance: 1792.3 on 2369 degrees of freedom
(371 observations deleted due to missingness)
AIC: 1824
Number of Fisher Scoring iterations: 5
F3: Prediction & Evaluation
pred = predict(glm2, newdata=TS, type="response") #type= "response" 直接給機率
y = TS$TenYearCHD[!is.na(pred)] # remove 實際值裡的NA
pred = pred[!is.na(pred)]#預測值
mx = table(y, pred > 0.5); mx
y FALSE TRUE
0 1069 6
1 187 11
c(accuracy = sum(diag(mx))/sum(mx),
sensitivity = mx[2,2]/sum(mx[2,]),
specificity = mx[1,1]/sum(mx[1,]))
accuracy sensitivity specificity
0.84839 0.05556 0.99442
F4: AUC & DPP
par(cex=0.7)
auc = DPP(pred, y, 1, b=seq(0,1,0.02)) # 0.74211

#green: 沒有心臟病
#red: 有心臟病
F5: Expected Result & Optimization
【payoff matrix 風險成本or獲利】 自己決定、推估的。一但決定出來,就能回去調整cutoff,得到期望值(confusion matrix X payoff)
在商務情境下希望exp payoff是最高的
用程式跑過全部的cutoff下得到的exp payoff,就能得出最高exp payoff下的cutoff
預測不會等於決策,而是要先知道商業情境,才能知道要做什麼動作
range(pred)
[1] 0.01641 0.72958
payoff = matrix(c(0,-100,-10,-60),2,2)
cutoff = seq(0.02, 0.64, 0.01) #seq 從range(pred)來
result = sapply(cutoff, function(p) sum(table(y,pred>p)*payoff) )
#table(y,pred>p)就是confusion matrix
#sum(table(y,pred>p)*payoff 就是 exp payoff
i = which.max(result)
par(cex=0.7)
plot(cutoff, result, type='l', col='cyan', lwd=2, main=sprintf(
"Optomal Expected Result: $%d @ %.2f",result[i],cutoff[i]))
abline(v=seq(0,1,0.05),h=seq(-23000,-17000,500),col='lightgray',lty=3)
abline(v=cutoff[i],col='red')

【Q】如果什麼都不做,期望報酬是多少?
#什麼都不做的意思: 全部都視為沒有心臟病,不給予治療
#set cutoff = 1 (機率 <1 都是沒有心臟病)
payoff = matrix(c(0,-100,-10,-60),2,2)
mx = matrix(c(table(y,pred>=0),0,0),2,2);mx
[,1] [,2]
[1,] 1075 0
[2,] 198 0
result = sum(mx*payoff)
result
[1] -19800
【Q】如果每位病人都做呢?
#每個病人都視為心臟病,給予治療,
mx = matrix(c(0,0,table(y,pred>=0)),2,2);mx
[,1] [,2]
[1,] 0 1075
[2,] 0 198
result2 = sum(mx*payoff)
result2
[1] -22630
【Q】以上哪一種做法期望報酬比較高?
#什麼都不做比較高
【Q】在所有的商務情境都是這種狀況嗎?
#不是
【Q】你可以模擬出「全做」比「全不做」還要好的狀況、並舉出一個會發生這種狀況的商務情境嗎?
#
#
#
F6: Simulation
#將下面程式碼貼到R script跑才能跑出來
library(manipulate)
p0 = par(mfrow=c(2,1),cex=0.8)
manipulate({
Y0 = -22000; Y1 = -12000
mx = matrix(c(true_neg, false_neg, false_pos, true_pos),2,2)
cx = seq(0.02, 0.64, 0.01)
rx = sapply(cx, function(p) sum(table(y, pred>p)*mx) )
i = which.max(rx)
plot(cx, rx, type='l',col='cyan',lwd=2,main=sprintf(
"Optomal Expected Result: $%d @ %.2f, T:%d",rx[i],cx[i],sum(pred>cx[i])),
ylim=c(Y0,Y1))
abline(v=cx[i],col='red')
abline(v=seq(0,1,0.1),h=seq(Y0,Y1,2000),col='lightgray',lty=3)
DPP(pred, y, 1, b=seq(0,1,0.02))
abline(v=cx[i],col='red')
},
true_neg = slider(-100,100,0,step=5),
false_neg = slider(-100,100,-100,step=5),
false_pos = slider(-100,100,-10,step=5),
true_pos = slider(-100,100,-60,step=5)
)

【Q】有五種成本分別為 $5, $10, $15, $20, $30 的藥,它們分別可以將風險成本從 $100 降低到 $70, $60, $50, $40, $25,哪一種藥的期望效益是最大的呢?
#用simulator
#將FP(只付出藥的成本)改為$5~$30
#將TP改為$70~$25
#1. $5 $70 result: -$17415
#2. $10 $60 result: -$17500
#3. $15 $50 result: -$17405
#4. $20 $40 result: -$17400
#5. $5 $70 result: -$17655
#第四種狀況的期望效益是最大的
LS0tCnRpdGxlOiAiQVMzLTAgR3JvdXAtMCIKYXV0aG9yOiAi6Zmz6Z+75Y2JIEIwMzQwMjAwMjcsIC4uLiIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3IgZWNobz1ULCBtZXNzYWdlPUYsIGNhY2hlPUYsIHdhcm5pbmc9Rn0Kcm0obGlzdD1scyhhbGw9VCkpCm9wdGlvbnMoZGlnaXRzPTQsIHNjaXBlbj0xMikKbGlicmFyeShkcGx5cik7IGxpYnJhcnkoZ2dwbG90MikKYGBgCgoKLSAtIC0KCiMjIyDjgJBB44CRIERlZmluaXRpb25zCgojIyMjIOapn+eOh+OAgeWLneeOhyhPZGQp44CBTG9naXQKCisgT2RkID0gICRwLygxLXApJAoKKyBMb2dpdCA9ICRsb2cob2RkKSQgPSAkbG9nKFxmcmFje3B9ezE9cH0pJAoKKyAkbyA9IHAvKDEtcCkkIDsgJHAgPSBvLygxK28pJCA7ICAkbG9naXQgPSBsb2cobykkCgpgYGB7ciBmaWcuaGVpZ2h0PTMuNiwgZmlnLndpZHRoPTd9CnBhcihjZXg9MC44LCBtZmNvbD1jKDEsMikpCmN1cnZlKHgvKDEteCksIDAuMDIsIDAuOTgsIGNvbD0nY3lhbicsbHdkPTIsIG1haW49J29kZCcpCmFibGluZSh2PXNlcSgwLDEsMC4xKSwgaD1zZXEoMCw1MCw1KSwgY29sPSdsaWdodGdyYXknLCBsdHk9MykKI+agvOe3mgpjdXJ2ZShsb2coeC8oMS14KSksIDAuMDA1LCAwLjk5NSwgbHdkPTIsIGNvbD0ncHVycGxlJywgbWFpbj0ibG9naXQiKQphYmxpbmUodj1zZXEoMCwxLDAuMSksIGg9c2VxKC01LDUsMSksIGNvbD0nbGlnaHRncmF5JywgbHR5PTMpCgpgYGAKCiMjIyMgTG9naXN0aWMgRnVuY3Rpb24gJiBMb2dpc3RpYyBSZWdyZXNzaW9uCgorIExpbmVhciBNb2RlbDogJHkgPSBmKHgpID0gYl8wICsgYl8xeF8xICsgYl8yeF8yICsgLi4uJAoKKyBHZW5lcmFsIExpbmVhciBNb2RlbChHTE0pOiAkeSA9IExpbmsoZih4KSkkIAoKKyBMb2dpc3RpYyBSZWdyZXNzaW9uOiAkbG9naXQoeSkgPSBsb2coXGZyYWN7cH17MS1wfSkgPSBmKHgpIFx0ZXh0eyB3aGVyZSB9IHAgPSBwcm9iW3k9MV0kIAoKKyBMb2dpc3RpYyBGdW5jdGlvbjogJExvZ2lzdGljKEZfeCkgPSBcZnJhY3sxfXsxK0V4cCgtRl94KX0gPSBcZnJhY3tFeHAoRl94KX17MStFeHAoRl94KX0kCgpgYGB7ciAgZmlnLndpZHRoPTQsIGZpZy5oZWlnaHQ9NH0KcGFyKGNleD0wLjgpCmN1cnZlKDEvKDErZXhwKC14KSksIC01LCA1LCBjb2w9J2JsdWUnLCBsd2Q9MixtYWluPSJMb2dpc3RpYyBGdW5jdGlvbiIsCiAgICAgIHhsYWI9ImYoeCk6IHRoZSBsb2dpdCBvZiB5ID0gMSIsIHlsYWI9InRoZSBwcm9iYWJpbGl0eSBvZiB5ID0gMSIpCmFibGluZSh2PS01OjUsIGg9c2VxKDAsMSwwLjEpLCBjb2w9J2xpZ2h0Z3JheScsIGx0eT0yKQphYmxpbmUodj0wLGg9MC41LGNvbD0ncGluaycpCnBvaW50cygwLDAuNSxwY2g9MjAsY2V4PTEuNSxjb2w9J3JlZCcpCmBgYAoK44CQKipRKirjgJFXaGF0IGFyZSB0aGUgZGVmaW5paW9uIG9mIGBsb2dpdGAgJiBgbG9naXN0aWMgZnVuY3Rpb25gPyBXaGF0IGlzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGVtPwoKbG9naXTmmK9vZGTlj5Zsb2cgPGJyPgpsb2dpc3RpYyBmdW5jdGlvbuaYr+aKimxvZ2l06L2J5o+b5oiQeT0x55qE5qmf546HCgo8YnI+CgotIC0gLQoKIyMjIOOAkELjgJFgZ2xtKCwgZmFtaWx5PWJpbm9taWFsKWAKCmBnbG0oKWDnmoTlip/og73vvJrlnKggJFx7eFx9JCAo6Ieq6K6K5pW4KeeahOepuumWk+S5i+S4re+8jOaJvuWHuuWNgOmalCAkeSQg55qEKOmhnuWIpSnnlYznt5oKCmBgYHtyfQpRID0gcmVhZC5jc3YoImRhdGEvcXVhbGl0eS5jc3YiKSAgIyBSZWFkIGluIGRhdGFzZXQKZ2xtMSA9IGdsbShQb29yQ2FyZX5PZmZpY2VWaXNpdHMrTmFyY290aWNzLCBRLCBmYW1pbHk9Ymlub21pYWwpCiNmYW1pbHnlj4PmlbjmmK/ooajnpLrmnInlvojlpJrkuI3lkIznmoRsaW5raW5nIGZ1bmN0aW9uCnN1bW1hcnkoZ2xtMSkKYGBgCgpgYGB7cn0KYiA9IGNvZWYoZ2xtMSk7IGIgICAjIGV4dHJhY3QgdGhlIHJlZ3Jlc3Npb24gY29lZgpgYGAKCkdpdmVuIGBPZmZpY2VWaXNpdHM9MywgTmFyY290aWNzPTRgLCB3aGF0IGFyZSB0aGUgcHJlZGljdGVkIGxvZ2l0LCBvZGQgYW5kIHByb2JhYmlsaXR5PwpgYGB7cn0KbG9naXQgPSBzdW0oYiAqIGMoMSwgMywgNCkpICNpbnRlcmNlcHRz5oiq6Led55SoMeS5mCDkv4Lmlbh46K6K5pW45YC8Cm9kZCA9IGV4cChsb2dpdCkgCnByb2IgPSBvZGQvKDErb2RkKQpjKGxvZ2l0PWxvZ2l0LCBvZGQ9b2RkLCBwcm9iPXByb2IpCmBgYAoK44CQKipRKirjgJFXaGF0IGlmIGBPZmZpY2VWaXNpdHM9MiwgTmFyY290aWNzPTNgPwpgYGB7cn0KbG9naXQgPSBzdW0oYiAqIGMoMSwgMiwgMykpICNpbnRlcmNlcHRz5oiq6Led55SoMeS5mCDkv4Lmlbh46K6K5pW45YC8Cm9kZCA9IGV4cChsb2dpdCkgI3Bvb3IgY2FyZeeahOapn+eOhwpwcm9iID0gb2RkLygxK29kZCkKYyhsb2dpdD1sb2dpdCwgb2RkPW9kZCwgcHJvYj1wcm9iKQpgYGAKCldlIGNhbiBwbG90IHRoZSBsaW5lIG9mIGBsb2dpdCA9IDBgIG9yIGBwcm9iID0gMC41YCBvbiB0aGUgcGxhbmUgb2YgJFgkCmBgYHtyIGZpZy53aWR0aD0zLjYsIGZpZy5oZWlnaHQ9My42fQpwYXIoY2V4PTAuOCkKcGxvdChRJE9mZmljZVZpc2l0cywgUSROYXJjb3RpY3MsIGNvbD0xK1EkUG9vckNhcmUscGNoPTIwKSAjY29s6aGP6Imy5b6eMeeul+i1t++8jOS9hnBvb3JDYXJl5b6eMO+8jOaJgOS7peaJjSsxCmFibGluZSgtYlsxXS9iWzNdLCAtYlsyXS9iWzNdKQpgYGAKCkZ1cnRoZXJtb3JlLCB3ZSBjYW4gdHJhbnNsYXRlIHByb2JhYmlsaXR5LCBsb2dpdCBhbmQgY29lZmZpY2VudHMgdG8gaW50ZXJjZXB0ICYgc2xvcGUgLi4uCgokJGYoeCkgPSBiXzEgKyBiXzIgeF8yICsgYl8zIHhfMyA9IGcgXFJpZ2h0YXJyb3cgIHhfMyA9IFxmcmFje2cgLSBiXzF9e2JfM30gLSBcZnJhY3tiXzJ9e2JfM314XzIkJAoKCmBgYHtyICBmaWcud2lkdGg9My42LCBmaWcuaGVpZ2h0PTMuNn0KcCA9IHNlcSgwLjEsMC45LDAuMSkKbG9naXQgPSBsb2cocC8oMS1wKSkKZGF0YS5mcmFtZShwcm9iID0gcCwgbG9naXQpCmBgYAoKdGhlbiBtYXJrIHRoZSBjb250b3VycyBvZiBwcm9hYmlsaXRpZXMgaW50byB0aGUgc2NhdHRlciBwbG90IApgYGB7ciAgZmlnLndpZHRoPTMuNiwgZmlnLmhlaWdodD0zLjZ9CnBhcihjZXg9MC43KQpwbG90KFEkT2ZmaWNlVmlzaXRzLCBRJE5hcmNvdGljcywgY29sPTErUSRQb29yQ2FyZSwKICAgICBwY2g9MjAsIGNleD0xLjMsIHhsYWI9J1gyJywgeWxhYj0nWDMnKQpmb3IoZyBpbiBsb2dpdCkgewogIGFibGluZSgoZy1iWzFdKS9iWzNdLCAtYlsyXS9iWzNdLCBjb2w9aWZlbHNlKGc9PTAsJ2JsdWUnLCdjeWFuJykpIH0KYGBgCgrjgJAqKlEqKuOAkVdoYXQgZG8gdGhlIGJsdWUvY3lhbiBsaW5lcyBtZWFucz88YnI+CuS7o+ihqDAuMX4wLjnnmoTmqZ/njoc8YnI+CgrjgJAqKlEqKuOAkUdpdmVuIGFueSBwb2ludCBpbiB0aGUgZmlndXJlIGFib3ZlLCBob3cgY2FuIHlvdSB0ZWxsIGl0cyAocHJlZGljdGVkKSBwcm9iYWJpbGl0eSBhcHByb3hpbWF0ZWx5Pwo8YnI+Cui2iuaOpei/keWTquS4gOainee3mu+8jOWwseWkp+e0hOaYr+mCo+WAi+apn+eOhwoKLSAtIC0KCiMjIyDjgJBD44CRVGhlIENvbmZ1c2lvbiBNYXRyaXgKCgohW0ZpZ3VyZSAxIC0gQ29uZnVzaW9uIE1hdHJpeF0ocmVzL2NvbmZ1c2lvbl9tYXRyaXguanBnKQoKCjxicj4KCi0gLSAtCiMjIyDjgJBE44CRVGhlIERpc3RyaWJ1dGlvbiBvZiBQcmVkaWN0ZWQgUHJvYmFiaWxpdHkgKERQUCkKCkNvbmZ1c2lvbiBtYXRyaXggaXMgbm90IGZpeGVkLiBJdCBjaGFuZ2VzIGJ5IGBUaHJlc2hvbGRgIC4uLjxicj4K6Kit5a6aYHRocmVzaG9sZGAo6Ieo55WM5qmf546HKSDmsbrlrprmqZ/njoflnKjlpJrlsJHku6XkuIrvvIzpoJDmuKzngrp5PTHjgILlrprnvqnkuoZ0aHJlc2hvbGTmiY3mnIPmnIljb25mdXNpb24gbWF0cml477yM56e75YuVdGhyZXNob2xk5bCx5pyD5pyJ5LiN5LiA5qij55qEY29uZnVzaW9uIG1hdHJpeAoKIVtGaWd1cmUgMiAtIERpc3QuIFByZWRpZWN0ZWQgUHJvYi5dKHJlcy9kcHAuanBnKQoKCgpgYGB7cn0KbGlicmFyeShjYVRvb2xzKQpEUFAyID0gZnVuY3Rpb24ocHJlZCxjbGFzcyx0dmFsdWUsYnJlYWtzPTAuMDEpIHsKICBteCA9IHRhYmxlKGNsYXNzID09IHR2YWx1ZSwgcHJlZCA+IDAuNSkgCiAgdG4gPSBzdW0oY2xhc3MgIT0gdHZhbHVlICYgcHJlZCA8PSAwLjUpCiAgZm4gPSBzdW0oY2xhc3MgPT0gdHZhbHVlICYgcHJlZCA8PSAwLjUpCiAgZnAgPSBzdW0oY2xhc3MgIT0gdHZhbHVlICYgcHJlZCA+IDAuNSkKICB0cCA9IHN1bShjbGFzcyA9PSB0dmFsdWUgJiBwcmVkID4gMC41KQogIGFjYyA9ICh0biArIHRwKS9sZW5ndGgocHJlZCkKICBzZW5zID0gdHAvKGZuK3RwKQogIHNwZWMgPSB0bi8odG4rZnApCiAgYXVjID0gY29sQVVDKHByZWQsY2xhc3MpCiAgZGF0YS5mcmFtZShwcmVkLGNsYXNzKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHg9cHJlZCwgZmlsbD1jbGFzcykpICsKICAgIGdlb21faGlzdG9ncmFtKGNvbD0nZ3JheScsYWxwaGE9MC41LGJyZWFrcz1zZXEoMCwxLGJyZWFrcykpICsKICAgIHhsaW0oMCwxKSArIHRoZW1lX2J3KCkgKyB4bGFiKCJwcmVkaWN0ZWQgcHJvYmFiaWxpdHkiKSArIAogICAgZ2d0aXRsZSgKICAgICAgc3ByaW50ZigiRGlzdHJpYnV0aW9uIG9mIFByb2JbY2xhc3MgPSBcJyVzXCddIiwgdHZhbHVlKSwKICAgICAgc3ByaW50ZigiQVVDPSUuM2YsIEFjYz0lLjNmLCBTZW5zPSUuM2YsIFNwZWM9JS4zZiIsCiAgICAgICAgICAgICAgYXVjLCBhY2MsIHNlbnMsIHNwZWMpICkgCiAgfQoKYGBgCgojIyMjUk9DOiAKVFBS5ZKMRlBS55qE5qyK6KGh44CCMH4xIOS4jeWQjHRocmVzb2xk55qE5b6X5Ye655qEVFBS5ZKMRlBS5b6X5Yiw55qE5puy57eaCiMjIyNBVUM6IApST0Pmm7Lnt5rkuIvnmoTpnaLnqY3jgILku6PooajmqKHlnovlnKjoh6jnlYzmqZ/njofkuK3nmoTovqjorZjog73lipsKIyMjI0RQUDoKZGlzdHJpYnV0aW9uIG9mIHByZWRpY3RlZCBwcm9iYWJpbGl0eQoKYGBge3IgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Mi41fQpOMSA9IDMwMDsgTjIgPSAxMDAgI+WFqee+pOWIhumhngpEUFAyKHByZWQgPSBjKHJub3JtKE4xLDAuMTI1LDAuMDMpLCBybm9ybShOMiwwLjM3NSwwLjAzKSksCiAgICAgY2xhc3MgPSBjKHJlcCgnQicsTjEpLCByZXAoJ0EnLE4yKSksIAogICAgIHR2YWx1ZSA9ICdBJykKCiNwcmVkaWN0ZWQgcHJvYmFsaWxpdHkg5Luj6KGoIOmgkOa4rOeCukHnmoTmqZ/njocKI2FjYyA9IDAuNzUg5piv5Zug54K65ZyodGhyZXNob2xkMC415LiL77yM57SF6Imy55qE6YOo5YiG6YO954yc6Yyv5LqG77yMYWNjID0gMSAtIDEwMC80MDAKI3NlbnMoc2Vuc2l0aXZpdHksVFBSKSA9IFRQIC8gRk4rVFAKYGBgCgrjgJAqKlEqKuOAkUlzIGl0IHBvc3NpYmxlIHRvIGhhdmUgYEFVQyA9IEFDQyA9IFNFTlMgPSBTUEVDID0gMWA/IENhbiB5b3UgbW9kaWZ5IHRoZSBjb2RlIHRvIG1ha2UgaXQgaGFwcGVuPwoKYGBge3J9CiPkuI3lj6/og70KYGBgCgoKYGBge3IgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Mi41fQpOMSA9IDMwMDsgTjIgPSAxMDAgI+WFqee+pOWIhumhngpEUFAyKHByZWQgPSBjKHJub3JtKE4xLDAuMTI1LDAuMDMpLCBybm9ybShOMiwwLjYyNSwwLjAzKSksCiAgICAgY2xhc3MgPSBjKHJlcCgnQicsTjEpLCByZXAoJ0EnLE4yKSksIAogICAgIHR2YWx1ZSA9ICdBJykKYGBgCgoK44CQKipRKirjgJFJcyBpdCBwb3NzaWJsZSB0byBoYXZlIGBBVUMgPSBBQ0MgPSBTRU5TID0gU1BFQyA9IDBgPyBDYW4geW91IG1vZGlmeSB0aGUgY29kZSB0byBtYWtlIHRoYXQgaGFwcGVuPwoKYGBge3IgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Mi41fQojIAojIApgYGAKPGJyPgoKLSAtIC0KIyMjIOOAkEXjgJFNb2RlbGluZyBFeHBlcnQKCiMjIyMgRTE6IFJhbmRvbSBTcGxpdApgYGB7cn0Kc2V0LnNlZWQoODgpCnNwbGl0ID0gc2FtcGxlLnNwbGl0KFEkUG9vckNhcmUsIFNwbGl0UmF0aW8gPSAwLjc1KQp0YWJsZShzcGxpdCkgJT4lIHByb3AudGFibGUoKQp0YWJsZSh5ID0gUSRQb29yQ2FyZSwgc3BsaXQpICU+JSBwcm9wLnRhYmxlKDIpCmBgYAoKYGBge3J9ClRSID0gc3Vic2V0KFEsIHNwbGl0ID09IFRSVUUpClRTID0gc3Vic2V0KFEsIHNwbGl0ID09IEZBTFNFKQpgYGAKCiMjIyMgRTI6IEJ1aWxkIE1vZGVsCmBgYHtyfQpnbG0xID0gZ2xtKFBvb3JDYXJlIH4gT2ZmaWNlVmlzaXRzICsgTmFyY290aWNzLCBUUiwgZmFtaWx5PWJpbm9taWFsKQpzdW1tYXJ5KGdsbTEpCmBgYAoKIyMjIyBFMzogUHJlZGljdGlvbiAmIEV2YWx1YXRpb24KYGBge3J9CnByZWQgPSBwcmVkaWN0KGdsbTEsIHR5cGU9J3Jlc3BvbnNlJykKbXggPSB0YWJsZShUUiRQb29yQ2FyZSwgcHJlZCA+IDAuNSk7IG14CmMoYWNjdXJhY3kgPSBzdW0oZGlhZyhteCkpL3N1bShteCksCiAgc2Vuc2l0aXZpdHkgPSBteFsyLDJdL3N1bShteFsyLF0pLAogIHNwZWNpZmljaXR5ID0gbXhbMSwxXS9zdW0obXhbMSxdKSkKYGBgCgojIyMjIEU0OiBST0MgJiBBVUMKYGBge3IgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0KbGlicmFyeShST0NSKQpST0NScHJlZCA9IHByZWRpY3Rpb24ocHJlZCwgVFIkUG9vckNhcmUpClJPQ1JwZXJmID0gcGVyZm9ybWFuY2UoUk9DUnByZWQsICJ0cHIiLCAiZnByIikKcGFyKGNleD0wLjgpCnBsb3QoUk9DUnBlcmYsIGNvbG9yaXplPVRSVUUsIHByaW50LmN1dG9mZnMuYXQ9c2VxKDAsMSwwLjEpKQpgYGAKCmBgYHtyfQphcy5udW1lcmljKHBlcmZvcm1hbmNlKFJPQ1JwcmVkLCAiYXVjIilAeS52YWx1ZXMpCmNhVG9vbHM6OmNvbEFVQyhwcmVkLCBUUiRQb29yQ2FyZSkKYGBgCgo8YnI+CgotIC0gLQojIyMg44CQRuOAkUZyYW1pbmdoYW0gSGVhcnQgU3R1ZHkKCmBgYHtyfQpzb3VyY2UoIkRQUC5SIikgI+i8ieWFpURQUC5SCmBgYAoKIyMjIyBGMTogUmVhZGluZyAmIFNwbGl0dGluZwpgYGB7cn0KRiA9IHJlYWQuY3N2KCJkYXRhL2ZyYW1pbmdoYW0uY3N2IikKc2V0LnNlZWQoMTAwMCkKc3BsaXQgPSBzYW1wbGUuc3BsaXQoRiRUZW5ZZWFyQ0hELCBTcGxpdFJhdGlvID0gMC42NSkKVFIgPSBzdWJzZXQoRiwgc3BsaXQ9PVRSVUUpClRTID0gc3Vic2V0KEYsIHNwbGl0PT1GQUxTRSkKYGBgCgojIyMjIEYyOiBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsCmBgYHtyfQpnbG0yID0gZ2xtKFRlblllYXJDSEQgfiAuLCBUUiwgZmFtaWx5PWJpbm9taWFsKSAjdHJhaW7mmYLmnIPoh6rli5XljrvmjoluYQpzdW1tYXJ5KGdsbTIpCmBgYAoKIyMjIyBGMzogUHJlZGljdGlvbiAmIEV2YWx1YXRpb24KYGBge3J9CnByZWQgPSBwcmVkaWN0KGdsbTIsIG5ld2RhdGE9VFMsIHR5cGU9InJlc3BvbnNlIikgI3R5cGU9ICJyZXNwb25zZSIg55u05o6l57Wm5qmf546HCnkgPSBUUyRUZW5ZZWFyQ0hEWyFpcy5uYShwcmVkKV0gIyByZW1vdmUg5a+m6Zqb5YC86KOh55qETkEKcHJlZCA9IHByZWRbIWlzLm5hKHByZWQpXSPpoJDmuKzlgLwKCm14ID0gdGFibGUoeSwgcHJlZCA+IDAuNSk7IG14CmMoYWNjdXJhY3kgPSBzdW0oZGlhZyhteCkpL3N1bShteCksCiAgc2Vuc2l0aXZpdHkgPSBteFsyLDJdL3N1bShteFsyLF0pLAogIHNwZWNpZmljaXR5ID0gbXhbMSwxXS9zdW0obXhbMSxdKSkKYGBgCgojIyMjIEY0OiBBVUMgJiBEUFAKYGBge3IgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9Mi40fQpwYXIoY2V4PTAuNykKYXVjID0gRFBQKHByZWQsIHksIDEsIGI9c2VxKDAsMSwwLjAyKSkgICMgMC43NDIxMQojZ3JlZW46IOaykuacieW/g+iHn+eXhSAKI3JlZDog5pyJ5b+D6Ief55eFCmBgYAoKIyMjIyBGNTogRXhwZWN0ZWQgUmVzdWx0ICYgT3B0aW1pemF0aW9uCgoKIVtGaWd1cmUgMyAtIFN0YXJ0ZWdpYyBPcHRpbWl6YXRpb25dKHJlcy9vcHRpbWl6YXRpb24uanBnKQoK44CQcGF5b2ZmIG1hdHJpeCDpoqjpmqrmiJDmnKxvcueNsuWIqeOAkQroh6rlt7HmsbrlrprjgIHmjqjkvLDnmoTjgILkuIDkvYbmsbrlrprlh7rkvobvvIzlsLHog73lm57ljrvoqr/mlbRjdXRvZmbvvIzlvpfliLDmnJ/mnJvlgLwoY29uZnVzaW9uIG1hdHJpeCBYIHBheW9mZik8YnI+CuWcqOWVhuWLmeaDheWig+S4i+W4jOacm2V4cCBwYXlvZmbmmK/mnIDpq5jnmoQ8YnI+CueUqOeoi+W8j+i3kemBjuWFqOmDqOeahGN1dG9mZuS4i+W+l+WIsOeahGV4cCBwYXlvZmbvvIzlsLHog73lvpflh7rmnIDpq5hleHAgcGF5b2Zm5LiL55qEY3V0b2ZmCgo8YnI+CgrpoJDmuKzkuI3mnIPnrYnmlrzmsbrnrZbvvIzogIzmmK/opoHlhYjnn6XpgZPllYbmpa3mg4XlooPvvIzmiY3og73nn6XpgZPopoHlgZrku4Dpurzli5XkvZwKCmBgYHtyfQpyYW5nZShwcmVkKQpgYGAKCgpgYGB7ciBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD00fQpwYXlvZmYgPSBtYXRyaXgoYygwLC0xMDAsLTEwLC02MCksMiwyKSAKY3V0b2ZmID0gc2VxKDAuMDIsIDAuNjQsIDAuMDEp44CAI3NlcSDlvp5yYW5nZShwcmVkKeS+hgpyZXN1bHQgPSBzYXBwbHkoY3V0b2ZmLCBmdW5jdGlvbihwKSBzdW0odGFibGUoeSxwcmVkPnApKnBheW9mZikgKQojdGFibGUoeSxwcmVkPnAp5bCx5pivY29uZnVzaW9uIG1hdHJpeAojc3VtKHRhYmxlKHkscHJlZD5wKSpwYXlvZmYg5bCx5pivIGV4cCBwYXlvZmYKCmkgPSB3aGljaC5tYXgocmVzdWx0KQpwYXIoY2V4PTAuNykKcGxvdChjdXRvZmYsIHJlc3VsdCwgdHlwZT0nbCcsIGNvbD0nY3lhbicsIGx3ZD0yLCBtYWluPXNwcmludGYoCiAgIk9wdG9tYWwgRXhwZWN0ZWQgUmVzdWx0OiAkJWQgQCAlLjJmIixyZXN1bHRbaV0sY3V0b2ZmW2ldKSkKYWJsaW5lKHY9c2VxKDAsMSwwLjA1KSxoPXNlcSgtMjMwMDAsLTE3MDAwLDUwMCksY29sPSdsaWdodGdyYXknLGx0eT0zKQphYmxpbmUodj1jdXRvZmZbaV0sY29sPSdyZWQnKQoKYGBgCgrjgJAqKlEqKuOAkeWmguaenOS7gOm6vOmDveS4jeWBmu+8jOacn+acm+WgsemFrOaYr+WkmuWwke+8nwoKYGBge3J9CiPku4Dpurzpg73kuI3lgZrnmoTmhI/mgJ06IOWFqOmDqOmDveimlueCuuaykuacieW/g+iHn+eXhe+8jOS4jee1puS6iOayu+eZggojc2V0IGN1dG9mZiA9IDEgKOapn+eOhyA8MSDpg73mmK/mspLmnInlv4Poh5/nl4UpCgpwYXlvZmYgPSBtYXRyaXgoYygwLC0xMDAsLTEwLC02MCksMiwyKSAgCgpteCA9IG1hdHJpeChjKHRhYmxlKHkscHJlZD49MCksMCwwKSwyLDIpO214CgpyZXN1bHQgPSBzdW0obXgqcGF5b2ZmKQoKcmVzdWx0CgpgYGAKCgrjgJAqKlEqKuOAkeWmguaenOavj+S9jeeXheS6uumDveWBmuWRou+8nwoKYGBge3J9CiPmr4/lgIvnl4Xkurrpg73oppbngrrlv4Poh5/nl4XvvIzntabkuojmsrvnmYLvvIwKbXggPSBtYXRyaXgoYygwLDAsdGFibGUoeSxwcmVkPj0wKSksMiwyKTtteAoKcmVzdWx0MiA9IHN1bShteCpwYXlvZmYpCgpyZXN1bHQyCgpgYGAKCgrjgJAqKlEqKuOAkeS7peS4iuWTquS4gOeoruWBmuazleacn+acm+WgsemFrOavlOi8g+mrmO+8nwoKYGBge3J9CiPku4Dpurzpg73kuI3lgZrmr5TovIPpq5gKCmBgYAoKCuOAkCoqUSoq44CR5Zyo5omA5pyJ55qE5ZWG5YuZ5oOF5aKD6YO95piv6YCZ56iu54uA5rOB5ZeO77yfCgpgYGB7cn0KI+S4jeaYrwoKYGBgCgoK44CQKipRKirjgJHkvaDlj6/ku6XmqKHmk6zlh7rjgIzlhajlgZrjgI3mr5TjgIzlhajkuI3lgZrjgI3pgoTopoHlpb3nmoTni4Dms4HjgIHkuKboiInlh7rkuIDlgIvmnIPnmbznlJ/pgJnnqK7ni4Dms4HnmoTllYbli5nmg4XlooPll47vvJ8KCgpgYGB7cn0KIwojCiMKYGBgCgo8YnI+CgojIyMjIEY2OiBTaW11bGF0aW9uCmBgYHtyIGZpZy53aWR0aD02LCBmaWcuaGVpZ2h0PTZ9Cgoj5bCH5LiL6Z2i56iL5byP56K86LK85YiwUiBzY3JpcHTot5HmiY3og73ot5Hlh7rkvoYKbGlicmFyeShtYW5pcHVsYXRlKQpwMCA9IHBhcihtZnJvdz1jKDIsMSksY2V4PTAuOCkKbWFuaXB1bGF0ZSh7CiAgWTAgPSAtMjIwMDA7IFkxID0gLTEyMDAwCiAgbXggPSBtYXRyaXgoYyh0cnVlX25lZywgZmFsc2VfbmVnLCBmYWxzZV9wb3MsIHRydWVfcG9zKSwyLDIpIAogIGN4ID0gc2VxKDAuMDIsIDAuNjQsIDAuMDEpCiAgcnggPSBzYXBwbHkoY3gsIGZ1bmN0aW9uKHApIHN1bSh0YWJsZSh5LCBwcmVkPnApKm14KSApCiAgaSA9IHdoaWNoLm1heChyeCkKICBwbG90KGN4LCByeCwgdHlwZT0nbCcsY29sPSdjeWFuJyxsd2Q9MixtYWluPXNwcmludGYoCiAgICAiT3B0b21hbCBFeHBlY3RlZCBSZXN1bHQ6ICQlZCBAICUuMmYsIFQ6JWQiLHJ4W2ldLGN4W2ldLHN1bShwcmVkPmN4W2ldKSksCiAgICB5bGltPWMoWTAsWTEpKQogIGFibGluZSh2PWN4W2ldLGNvbD0ncmVkJykKICBhYmxpbmUodj1zZXEoMCwxLDAuMSksaD1zZXEoWTAsWTEsMjAwMCksY29sPSdsaWdodGdyYXknLGx0eT0zKQogIERQUChwcmVkLCB5LCAxLCBiPXNlcSgwLDEsMC4wMikpCiAgYWJsaW5lKHY9Y3hbaV0sY29sPSdyZWQnKQogIH0sCiAgdHJ1ZV9uZWcgID0gc2xpZGVyKC0xMDAsMTAwLDAsc3RlcD01KSwKICBmYWxzZV9uZWcgPSBzbGlkZXIoLTEwMCwxMDAsLTEwMCxzdGVwPTUpLAogIGZhbHNlX3BvcyA9IHNsaWRlcigtMTAwLDEwMCwtMTAsc3RlcD01KSwKICB0cnVlX3BvcyAgPSBzbGlkZXIoLTEwMCwxMDAsLTYwLHN0ZXA9NSkKICApIAoKYGBgCgoK44CQKipRKirjgJHmnInkupTnqK7miJDmnKzliIbliKXngrogYCQ1LCAkMTAsICQxNSwgJDIwLCAkMzBgIOeahOiXpe+8jOWug+WAkeWIhuWIpeWPr+S7peWwh+miqOmaquaIkOacrOW+niBgJDEwMGAg6ZmN5L2O5YiwIGAkNzAsICQ2MCwgJDUwLCAkNDAsICQyNWDvvIzlk6rkuIDnqK7ol6XnmoTmnJ/mnJvmlYjnm4rmmK/mnIDlpKfnmoTlkaLvvJ8KCgpgYGB7cn0KI+eUqHNpbXVsYXRvcgoj5bCHRlAo5Y+q5LuY5Ye66Jel55qE5oiQ5pysKeaUueeCuiQ1fiQzMAoj5bCHVFDmlLnngrokNzB+JDI1CgojMS4gJDUgJDcwIHJlc3VsdDogLSQxNzQxNQojMi4gJDEwICQ2MCByZXN1bHQ6IC0kMTc1MDAKIzMuICQxNSAkNTAgcmVzdWx0OiAtJDE3NDA1CiM0LiAkMjAgJDQwIHJlc3VsdDogLSQxNzQwMAojNS4gJDUgJDcwIHJlc3VsdDogLSQxNzY1NQoKI+esrOWbm+eorueLgOazgeeahOacn+acm+aViOebiuaYr+acgOWkp+eahAoKYGBgCgo8YnI+CgotIC0gLQojIyMg44CQR+OAkeWIhuaekOa1geeoi++8muizh+aWmeOAgeaooeWei+OAgemgkOa4rOOAgeaxuuetlgoKIVtGaWd1cmUgNCAtIOizh+aWmeOAgeaooeWei+OAgemgkOa4rOOAgeaxuuetll0ocmVzL2Zsb3cuanBnKQoKCgo8YnI+PGJyPjxicj48YnI+PGJyPgoKCgoKCgoKCgo=