rm(list=ls(all=T))
options(digits=4, scipen=12)
library(dplyr); library(ggplot2)
Attaching package: 愼㸱愼㸵dplyr愼㸱愼㸶
The following objects are masked from 愼㸱愼㸵package:stats愼㸱愼㸶:
filter, lag
The following objects are masked from 愼㸱愼㸵package:base愼㸱愼㸶:
intersect, setdiff, setequal, union
Use suppressPackageStartupMessages() to eliminate package startup messages.
【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 ligit & logistic function? What is the relationship between them?
logit : 機率的一種算法 => log(odd) logistic function : 把logit轉成y=1的機率。
【B】glm(, family=binomial)
glm()的功能:在 \(\{x\}\) 的空間之中,找出區隔 \(y\) 的(類別)界線
Q = read.csv("data/quality.csv")
glm1 = glm(PoorCare~OfficeVisits+Narcotics, Q, family=binomial)
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))
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_q = sum(b * c(1, 2, 3))
odd_q = exp(logit_q)
prob_q = odd_q / (1+odd_q) # => (p / 1-p) / (1/(1-p)) = p*(1-p) / 1(1-p) = p
prob_q2 = 1 / (1+exp(-1*logit_q)) #Another way to calculate probabilitys from logit.
c(logit=logit_q, odd=odd_q, prob=prob_q)
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)
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? 因為接近0.7的線,所以該點機率大約為0.7
【D】The Distribution of Predicted Probability (DPP)
Confusion matrix is not fixed. It changes by Threshold …
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) )
}
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')

【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?
N1 = 300; N2 = 100
DPP2(pred = c(rnorm(N1,0.875,0.03), rnorm(N2,0.125,0.03)),
class = c(rep('B',N1), rep('A',N2)),
tvalue = 'A')

【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")
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)
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, TS, 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

F5: Expected Result & Optimization
payoff = matrix(c(0,-100,-10,-60),2,2)
cutoff = seq(0.02, 0.7, 0.01)
result = sapply(cutoff, function(p) sum(table(y,pred>p)*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】如果什麼都不做,期望報酬是多少? 趨近於-20500元
【Q】如果每位病人都做呢? -22580元
【Q】以上哪一種做法期望報酬比較高? 什麼都不做
【Q】在所有的商務情境都是這種狀況嗎? 當誤判的成本極高時,常會出現這樣的狀況。
【Q】你可以模擬出「全做」比「全不做」還要好的狀況、並舉出一個會發生這種狀況的商務情境嗎? 如果做錯了不會有任何損失,但做對了會有很棒的利潤,則可以去做。
當你不做卻發生意外(False Negative)的時候產生的成本遠高於做時候,就可以去做。(飛安定期檢查)
payoff = matrix(c(100,0,0,100),2,2)
cutoff = seq(0.02, 0.7, 0.01)
result = sapply(cutoff, function(p) sum(table(y,pred>p)*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')

F6: Simulation
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)
)
par(p0)

【Q】有五種成本分別為 $5, $10, $15, $20, $30 的藥,它們分別可以將風險成本從 $100 降低到 $70, $60, $50, $40, $25,哪一種藥的期望效益是最大的呢?
#1 $5$70 Expected Result:-17415
#2 $10$60 Expected Result:-17500
#3 $15$50 Expected Result:-17450
#4 $20$40 Expected Result:-17400
#5 $30$25 Expected Result:-17655
#ans: -17400
#哪一種藥的期望效益是最大的呢?($20/$40)這組最大。"
LS0tDQp0aXRsZTogIkFTMy0wIg0KYXV0aG9yOiAiR3JvdXAgMiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCmBgYHtyIGVjaG89VCwgbWVzc2FnZT1GLCBjYWNoZT1GLCB3YXJuaW5nPUZ9DQpybShsaXN0PWxzKGFsbD1UKSkNCm9wdGlvbnMoZGlnaXRzPTQsIHNjaXBlbj0xMikNCmxpYnJhcnkoZHBseXIpOyBsaWJyYXJ5KGdncGxvdDIpDQpgYGANCg0KDQotIC0gLQ0KDQojIyMg44CQQeOAkSBEZWZpbml0aW9ucw0KDQojIyMjIOapn+eOh+OAgeWLneeOhyhPZGQp44CBTG9naXQNCg0KKyBPZGQgPSAgJHAvKDEtcCkkDQoNCisgTG9naXQgPSAkbG9nKG9kZCkkID0gJGxvZyhcZnJhY3twfXsxPXB9KSQNCg0KKyAkbyA9IHAvKDEtcCkkIDsgJHAgPSBvLygxK28pJCA7ICAkbG9naXQgPSBsb2cobykkDQoNCmBgYHtyIGZpZy5oZWlnaHQ9My42LCBmaWcud2lkdGg9N30NCnBhcihjZXg9MC44LCBtZmNvbD1jKDEsMikpDQpjdXJ2ZSh4LygxLXgpLCAwLjAyLCAwLjk4LCBjb2w9J2N5YW4nLGx3ZD0yLCBtYWluPSdvZGQnKQ0KYWJsaW5lKHY9c2VxKDAsMSwwLjEpLCBoPXNlcSgwLDUwLDUpLCBjb2w9J2xpZ2h0Z3JheScsIGx0eT0zKQ0KY3VydmUobG9nKHgvKDEteCkpLCAwLjAwNSwgMC45OTUsIGx3ZD0yLCBjb2w9J3B1cnBsZScsIG1haW49ImxvZ2l0IikNCmFibGluZSh2PXNlcSgwLDEsMC4xKSwgaD1zZXEoLTUsNSwxKSwgY29sPSdsaWdodGdyYXknLCBsdHk9MykNCg0KYGBgDQoNCiMjIyMgTG9naXN0aWMgRnVuY3Rpb24gJiBMb2dpc3RpYyBSZWdyZXNzaW9uDQoNCisgTGluZWFyIE1vZGVsOiAkeSA9IGYoeCkgPSBiXzAgKyBiXzF4XzEgKyBiXzJ4XzIgKyAuLi4kDQoNCisgR2VuZXJhbCBMaW5lYXIgTW9kZWwoR0xNKTogJHkgPSBMaW5rKGYoeCkpJCANCg0KKyBMb2dpc3RpYyBSZWdyZXNzaW9uOiAkbG9naXQoeSkgPSBsb2coXGZyYWN7cH17MS1wfSkgPSBmKHgpIFx0ZXh0eyB3aGVyZSB9IHAgPSBwcm9iW3k9MV0kIA0KDQorIExvZ2lzdGljIEZ1bmN0aW9uOiAkTG9naXN0aWMoRl94KSA9IFxmcmFjezF9ezErRXhwKC1GX3gpfSA9IFxmcmFje0V4cChGX3gpfXsxK0V4cChGX3gpfSQNCg0KYGBge3IgIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTR9DQpwYXIoY2V4PTAuOCkNCmN1cnZlKDEvKDErZXhwKC14KSksIC01LCA1LCBjb2w9J2JsdWUnLCBsd2Q9MixtYWluPSJMb2dpc3RpYyBGdW5jdGlvbiIsDQogICAgICB4bGFiPSJmKHgpOiB0aGUgbG9naXQgb2YgeSA9IDEiLCB5bGFiPSJ0aGUgcHJvYmFiaWxpdHkgb2YgeSA9IDEiKQ0KYWJsaW5lKHY9LTU6NSwgaD1zZXEoMCwxLDAuMSksIGNvbD0nbGlnaHRncmF5JywgbHR5PTIpDQphYmxpbmUodj0wLGg9MC41LGNvbD0ncGluaycpDQpwb2ludHMoMCwwLjUscGNoPTIwLGNleD0xLjUsY29sPSdyZWQnKQ0KYGBgDQoNCuOAkCoqUSoq44CRV2hhdCBhcmUgdGhlIGRlZmluaWlvbiBvZiBgbGlnaXRgICYgYGxvZ2lzdGljIGZ1bmN0aW9uYD8gV2hhdCBpcyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlbT8NCg0KbG9naXQgOiDmqZ/njofnmoTkuIDnqK7nrpfms5UgPT4gbG9nKG9kZCkNCmxvZ2lzdGljIGZ1bmN0aW9uIDog5oqKbG9naXTovYnmiJB5PTHnmoTmqZ/njofjgIINCg0KPGJyPg0KDQotIC0gLQ0KDQojIyMg44CQQuOAkWBnbG0oLCBmYW1pbHk9Ymlub21pYWwpYA0KDQpgZ2xtKClg55qE5Yqf6IO977ya5ZyoICRce3hcfSQg55qE56m66ZaT5LmL5Lit77yM5om+5Ye65Y2A6ZqUICR5JCDnmoQo6aGe5YilKeeVjOe3mg0KDQpgYGB7cn0NClEgPSByZWFkLmNzdigiZGF0YS9xdWFsaXR5LmNzdiIpDQpnbG0xID0gZ2xtKFBvb3JDYXJlfk9mZmljZVZpc2l0cytOYXJjb3RpY3MsIFEsIGZhbWlseT1iaW5vbWlhbCkNCnN1bW1hcnkoZ2xtMSkNCmBgYA0KDQpgYGB7cn0NCmIgPSBjb2VmKGdsbTEpOyBiICAgIyBleHRyYWN0IHRoZSByZWdyZXNzaW9uIGNvZWYNCmBgYA0KDQpHaXZlbiBgT2ZmaWNlVmlzaXRzPTMsIE5hcmNvdGljcz00YCwgd2hhdCBhcmUgdGhlIHByZWRpY3RlZCBsb2dpdCwgb2RkIGFuZCBwcm9iYWJpbGl0eT8NCmBgYHtyfQ0KbG9naXQgPSBzdW0oYiAqIGMoMSwgMywgNCkpDQpvZGQgPSBleHAobG9naXQpDQpwcm9iID0gb2RkLygxK29kZCkNCmMobG9naXQ9bG9naXQsIG9kZD1vZGQsIHByb2I9cHJvYikNCmBgYA0KDQrjgJAqKlEqKuOAkVdoYXQgaWYgYE9mZmljZVZpc2l0cz0yLCBOYXJjb3RpY3M9M2A/DQpgYGB7cn0NCmxvZ2l0X3EgPSBzdW0oYiAqIGMoMSwgMiwgMykpDQpvZGRfcSA9IGV4cChsb2dpdF9xKQ0KcHJvYl9xID0gb2RkX3EgLyAoMStvZGRfcSkgIyA9PiAocCAvIDEtcCkgLyAoMS8oMS1wKSkgPSBwKigxLXApIC8gMSgxLXApID0gcA0KcHJvYl9xMiA9IDEgLyAoMStleHAoLTEqbG9naXRfcSkpICNBbm90aGVyIHdheSB0byBjYWxjdWxhdGUgcHJvYmFiaWxpdHlzIGZyb20gbG9naXQuDQpjKGxvZ2l0PWxvZ2l0X3EsIG9kZD1vZGRfcSwgcHJvYj1wcm9iX3EpDQojDQpgYGANCg0KV2UgY2FuIHBsb3QgdGhlIGxpbmUgb2YgYGxvZ2l0ID0gMGAgb3IgYHByb2IgPSAwLjVgIG9uIHRoZSBwbGFuZSBvZiAkWCQNCmBgYHtyIGZpZy53aWR0aD0zLjYsIGZpZy5oZWlnaHQ9My42fQ0KcGFyKGNleD0wLjgpDQpwbG90KFEkT2ZmaWNlVmlzaXRzLCBRJE5hcmNvdGljcywgY29sPTErUSRQb29yQ2FyZSxwY2g9MjApDQphYmxpbmUoLWJbMV0vYlszXSwgLWJbMl0vYlszXSkNCmBgYA0KDQpGdXJ0aGVybW9yZSwgd2UgY2FuIHRyYW5zbGF0ZSBwcm9iYWJpbGl0eSwgbG9naXQgYW5kIGNvZWZmaWNlbnRzIHRvIGludGVyY2VwdCAmIHNsb3BlIC4uLg0KDQokJGYoeCkgPSBiXzEgKyBiXzIgeF8yICsgYl8zIHhfMyA9IGcgXFJpZ2h0YXJyb3cgIHhfMyA9IFxmcmFje2cgLSBiXzF9e2JfM30gLSBcZnJhY3tiXzJ9e2JfM314XzIkJA0KDQoNCmBgYHtyICBmaWcud2lkdGg9My42LCBmaWcuaGVpZ2h0PTMuNn0NCnAgPSBzZXEoMC4xLDAuOSwwLjEpDQpsb2dpdCA9IGxvZyhwLygxLXApKQ0KZGF0YS5mcmFtZShwcm9iID0gcCwgbG9naXQpDQpgYGANCg0KdGhlbiBtYXJrIHRoZSBjb250b3VycyBvZiBwcm9hYmlsaXRpZXMgaW50byB0aGUgc2NhdHRlciBwbG90IA0KYGBge3IgIGZpZy53aWR0aD0zLjYsIGZpZy5oZWlnaHQ9My42fQ0KcGFyKGNleD0wLjcpDQpwbG90KFEkT2ZmaWNlVmlzaXRzLCBRJE5hcmNvdGljcywgY29sPTErUSRQb29yQ2FyZSwNCiAgICAgcGNoPTIwLCBjZXg9MS4zLCB4bGFiPSdYMicsIHlsYWI9J1gzJykNCmZvcihnIGluIGxvZ2l0KSB7DQogIGFibGluZSgoZy1iWzFdKS9iWzNdLCAtYlsyXS9iWzNdLCBjb2w9aWZlbHNlKGc9PTAsJ2JsdWUnLCdjeWFuJykpIH0NCmBgYA0KDQrjgJAqKlEqKuOAkVdoYXQgZG8gdGhlIGJsdWUvY3lhbiBsaW5lcyBtZWFucz8NCiDmqZ/njoflvp4wLjF+MC4555qE57eaDQrjgJAqKlEqKuOAkUdpdmVuIGFueSBwb2ludCBpbiB0aGUgZmlndXJlIGFib3ZlLCBob3cgY2FuIHlvdSB0ZWxsIGl0cyAocHJlZGljdGVkKSBwcm9iYWJpbGl0eSBhcHByb3hpbWF0ZWx5Pw0KIOWboOeCuuaOpei/kTAuN+eahOe3mu+8jOaJgOS7peipsum7nuapn+eOh+Wkp+e0hOeCujAuNw0KPGJyPg0KDQotIC0gLQ0KDQojIyMg44CQQ+OAkVRoZSBDb25mdXNpb24gTWF0cml4DQoNCg0KIVtGaWd1cmUgMSAtIENvbmZ1c2lvbiBNYXRyaXhdKHJlcy9jb25mdXNpb25fbWF0cml4LmpwZykNCg0KDQo8YnI+DQoNCi0gLSAtDQojIyMg44CQROOAkVRoZSBEaXN0cmlidXRpb24gb2YgUHJlZGljdGVkIFByb2JhYmlsaXR5IChEUFApDQoNCkNvbmZ1c2lvbiBtYXRyaXggaXMgbm90IGZpeGVkLiBJdCBjaGFuZ2VzIGJ5IGBUaHJlc2hvbGRgIC4uLg0KDQohW0ZpZ3VyZSAyIC0gRGlzdC4gUHJlZGllY3RlZCBQcm9iLl0ocmVzL2RwcC5qcGcpDQoNCg0KDQpgYGB7cn0NCmxpYnJhcnkoY2FUb29scykNCkRQUDIgPSBmdW5jdGlvbihwcmVkLGNsYXNzLHR2YWx1ZSxicmVha3M9MC4wMSkgew0KICBteCA9IHRhYmxlKGNsYXNzID09IHR2YWx1ZSwgcHJlZCA+IDAuNSkgDQogIHRuID0gc3VtKGNsYXNzICE9IHR2YWx1ZSAmIHByZWQgPD0gMC41KQ0KICBmbiA9IHN1bShjbGFzcyA9PSB0dmFsdWUgJiBwcmVkIDw9IDAuNSkNCiAgZnAgPSBzdW0oY2xhc3MgIT0gdHZhbHVlICYgcHJlZCA+IDAuNSkNCiAgdHAgPSBzdW0oY2xhc3MgPT0gdHZhbHVlICYgcHJlZCA+IDAuNSkNCiAgYWNjID0gKHRuICsgdHApL2xlbmd0aChwcmVkKQ0KICBzZW5zID0gdHAvKGZuK3RwKQ0KICBzcGVjID0gdG4vKHRuK2ZwKQ0KICBhdWMgPSBjb2xBVUMocHJlZCxjbGFzcykNCiAgZGF0YS5mcmFtZShwcmVkLGNsYXNzKSAlPiUgDQogICAgZ2dwbG90KGFlcyh4PXByZWQsIGZpbGw9Y2xhc3MpKSArDQogICAgZ2VvbV9oaXN0b2dyYW0oY29sPSdncmF5JyxhbHBoYT0wLjUsYnJlYWtzPXNlcSgwLDEsYnJlYWtzKSkgKw0KICAgIHhsaW0oMCwxKSArIHRoZW1lX2J3KCkgKyB4bGFiKCJwcmVkaWN0ZWQgcHJvYmFiaWxpdHkiKSArIA0KICAgIGdndGl0bGUoDQogICAgICBzcHJpbnRmKCJEaXN0cmlidXRpb24gb2YgUHJvYltjbGFzcyA9IFwnJXNcJ10iLCB0dmFsdWUpLA0KICAgICAgc3ByaW50ZigiQVVDPSUuM2YsIEFjYz0lLjNmLCBTZW5zPSUuM2YsIFNwZWM9JS4zZiIsDQogICAgICAgICAgICAgIGF1YywgYWNjLCBzZW5zLCBzcGVjKSApIA0KICB9DQoNCmBgYA0KDQpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD0yLjV9DQpOMSA9IDMwMDsgTjIgPSAxMDANCkRQUDIocHJlZCA9IGMocm5vcm0oTjEsMC4xMjUsMC4wMyksIHJub3JtKE4yLDAuMzc1LDAuMDMpKSwNCiAgICAgY2xhc3MgPSBjKHJlcCgnQicsTjEpLCByZXAoJ0EnLE4yKSksIA0KICAgICB0dmFsdWUgPSAnQScpDQpgYGANCg0K44CQKipRKirjgJFJcyBpdCBwb3NzaWJsZSB0byBoYXZlIGBBVUMgPSBBQ0MgPSBTRU5TID0gU1BFQyA9IDFgPyBDYW4geW91IG1vZGlmeSB0aGUgY29kZSB0byBtYWtlIGl0IGhhcHBlbj8NCg0KYGBge3IgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Mi41fQ0KTjEgPSAzMDA7IE4yID0gMTAwDQpEUFAyKHByZWQgPSBjKHJub3JtKE4xLDAuMTI1LDAuMDMpLCBybm9ybShOMiwwLjYyNSwwLjAzKSksDQogICAgIGNsYXNzID0gYyhyZXAoJ0InLE4xKSwgcmVwKCdBJyxOMikpLCANCiAgICAgdHZhbHVlID0gJ0EnKQ0KYGBgDQoNCg0K44CQKipRKirjgJFJcyBpdCBwb3NzaWJsZSB0byBoYXZlIGBBVUMgPSBBQ0MgPSBTRU5TID0gU1BFQyA9IDBgPyBDYW4geW91IG1vZGlmeSB0aGUgY29kZSB0byBtYWtlIHRoYXQgaGFwcGVuPw0KDQpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD0yLjV9DQpOMSA9IDMwMDsgTjIgPSAxMDANCkRQUDIocHJlZCA9IGMocm5vcm0oTjEsMC44NzUsMC4wMyksIHJub3JtKE4yLDAuMTI1LDAuMDMpKSwNCiAgICAgY2xhc3MgPSBjKHJlcCgnQicsTjEpLCByZXAoJ0EnLE4yKSksIA0KICAgICB0dmFsdWUgPSAnQScpDQpgYGANCjxicj4NCg0KLSAtIC0NCiMjIyDjgJBF44CRTW9kZWxpbmcgRXhwZXJ0DQoNCiMjIyMgRTE6IFJhbmRvbSBTcGxpdA0KYGBge3J9DQpzZXQuc2VlZCg4OCkNCnNwbGl0ID0gc2FtcGxlLnNwbGl0KFEkUG9vckNhcmUsIFNwbGl0UmF0aW8gPSAwLjc1KQ0KdGFibGUoc3BsaXQpICU+JSBwcm9wLnRhYmxlKCkNCnRhYmxlKHkgPSBRJFBvb3JDYXJlLCBzcGxpdCkgJT4lIHByb3AudGFibGUoMikNCmBgYA0KDQpgYGB7cn0NClRSID0gc3Vic2V0KFEsIHNwbGl0ID09IFRSVUUpDQpUUyA9IHN1YnNldChRLCBzcGxpdCA9PSBGQUxTRSkNCmBgYA0KDQojIyMjIEUyOiBCdWlsZCBNb2RlbA0KYGBge3J9DQpnbG0xID0gZ2xtKFBvb3JDYXJlIH4gT2ZmaWNlVmlzaXRzICsgTmFyY290aWNzLCBUUiwgZmFtaWx5PWJpbm9taWFsKQ0Kc3VtbWFyeShnbG0xKQ0KYGBgDQoNCiMjIyMgRTM6IFByZWRpY3Rpb24gJiBFdmFsdWF0aW9uDQpgYGB7cn0NCnByZWQgPSBwcmVkaWN0KGdsbTEsIHR5cGU9J3Jlc3BvbnNlJykNCm14ID0gdGFibGUoVFIkUG9vckNhcmUsIHByZWQgPiAwLjUpOyBteA0KYyhhY2N1cmFjeSA9IHN1bShkaWFnKG14KSkvc3VtKG14KSwNCiAgc2Vuc2l0aXZpdHkgPSBteFsyLDJdL3N1bShteFsyLF0pLA0KICBzcGVjaWZpY2l0eSA9IG14WzEsMV0vc3VtKG14WzEsXSkpDQpgYGANCg0KIyMjIyBFNDogUk9DICYgQVVDDQpgYGB7ciBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQ0KbGlicmFyeShST0NSKQ0KUk9DUnByZWQgPSBwcmVkaWN0aW9uKHByZWQsIFRSJFBvb3JDYXJlKQ0KUk9DUnBlcmYgPSBwZXJmb3JtYW5jZShST0NScHJlZCwgInRwciIsICJmcHIiKQ0KcGFyKGNleD0wLjgpDQpwbG90KFJPQ1JwZXJmLCBjb2xvcml6ZT1UUlVFLCBwcmludC5jdXRvZmZzLmF0PXNlcSgwLDEsMC4xKSkNCmBgYA0KDQpgYGB7cn0NCmFzLm51bWVyaWMocGVyZm9ybWFuY2UoUk9DUnByZWQsICJhdWMiKUB5LnZhbHVlcykNCmNhVG9vbHM6OmNvbEFVQyhwcmVkLCBUUiRQb29yQ2FyZSkNCmBgYA0KDQo8YnI+DQoNCi0gLSAtDQojIyMg44CQRuOAkUZyYW1pbmdoYW0gSGVhcnQgU3R1ZHkNCg0KYGBge3J9DQpzb3VyY2UoIkRQUC5SIikNCmBgYA0KDQojIyMjIEYxOiBSZWFkaW5nICYgU3BsaXR0aW5nDQpgYGB7cn0NCkYgPSByZWFkLmNzdigiZGF0YS9mcmFtaW5naGFtLmNzdiIpDQpzZXQuc2VlZCgxMDAwKQ0Kc3BsaXQgPSBzYW1wbGUuc3BsaXQoRiRUZW5ZZWFyQ0hELCBTcGxpdFJhdGlvID0gMC42NSkNClRSID0gc3Vic2V0KEYsIHNwbGl0PT1UUlVFKQ0KVFMgPSBzdWJzZXQoRiwgc3BsaXQ9PUZBTFNFKQ0KYGBgDQoNCiMjIyMgRjI6IExvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwNCmBgYHtyfQ0KZ2xtMiA9IGdsbShUZW5ZZWFyQ0hEIH4gLiwgVFIsIGZhbWlseT1iaW5vbWlhbCkNCnN1bW1hcnkoZ2xtMikNCmBgYA0KDQojIyMjIEYzOiBQcmVkaWN0aW9uICYgRXZhbHVhdGlvbg0KYGBge3J9DQpwcmVkID0gcHJlZGljdChnbG0yLCBUUywgdHlwZT0icmVzcG9uc2UiKQ0KeSA9IFRTJFRlblllYXJDSERbIWlzLm5hKHByZWQpXSAgICAgICAgICAgICAjIHJlbW92ZSBOQQ0KcHJlZCA9IHByZWRbIWlzLm5hKHByZWQpXQ0KDQpteCA9IHRhYmxlKHksIHByZWQgPiAwLjUpOyBteA0KYyhhY2N1cmFjeSA9IHN1bShkaWFnKG14KSkvc3VtKG14KSwNCiAgc2Vuc2l0aXZpdHkgPSBteFsyLDJdL3N1bShteFsyLF0pLA0KICBzcGVjaWZpY2l0eSA9IG14WzEsMV0vc3VtKG14WzEsXSkpDQpgYGANCg0KIyMjIyBGNDogQVVDICYgRFBQDQpgYGB7ciBmaWcud2lkdGg9NywgZmlnLmhlaWdodD0yLjR9DQpwYXIoY2V4PTAuNykNCmF1YyA9IERQUChwcmVkLCB5LCAxLCBiPXNlcSgwLDEsMC4wMikpICAjIDAuNzQyMTENCmBgYA0KDQojIyMjIEY1OiBFeHBlY3RlZCBSZXN1bHQgJiBPcHRpbWl6YXRpb24NCg0KDQohW0ZpZ3VyZSAzIC0gU3RhcnRlZ2ljIE9wdGltaXphdGlvbl0ocmVzL29wdGltaXphdGlvbi5qcGcpDQoNCg0KYGBge3IgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NH0NCnBheW9mZiA9IG1hdHJpeChjKDAsLTEwMCwtMTAsLTYwKSwyLDIpIA0KY3V0b2ZmID0gc2VxKDAuMDIsIDAuNywgMC4wMSkNCnJlc3VsdCA9IHNhcHBseShjdXRvZmYsIGZ1bmN0aW9uKHApIHN1bSh0YWJsZSh5LHByZWQ+cCkqcGF5b2ZmKSApDQppID0gd2hpY2gubWF4KHJlc3VsdCkNCnBhcihjZXg9MC43KQ0KcGxvdChjdXRvZmYsIHJlc3VsdCwgdHlwZT0nbCcsIGNvbD0nY3lhbicsIGx3ZD0yLCBtYWluPXNwcmludGYoDQogICJPcHRvbWFsIEV4cGVjdGVkIFJlc3VsdDogJCVkIEAgJS4yZiIscmVzdWx0W2ldLGN1dG9mZltpXSkpDQphYmxpbmUodj1zZXEoMCwxLDAuMDUpLGg9c2VxKC0yMzAwMCwtMTcwMDAsNTAwKSxjb2w9J2xpZ2h0Z3JheScsbHR5PTMpDQphYmxpbmUodj1jdXRvZmZbaV0sY29sPSdyZWQnKQ0KYGBgDQoNCuOAkCoqUSoq44CR5aaC5p6c5LuA6bq86YO95LiN5YGa77yM5pyf5pyb5aCx6YWs5piv5aSa5bCR77yfDQogIOi2qOi/keaWvC0yMDUwMOWFgw0KICA8YnI+DQrjgJAqKlEqKuOAkeWmguaenOavj+S9jeeXheS6uumDveWBmuWRou+8nw0KICAtMjI1ODDlhYMNCiAgPGJyPg0K44CQKipRKirjgJHku6XkuIrlk6rkuIDnqK7lgZrms5XmnJ/mnJvloLHphazmr5TovIPpq5jvvJ8NCiAgIOS7gOm6vOmDveS4jeWBmg0KICA8YnI+DQrjgJAqKlEqKuOAkeWcqOaJgOacieeahOWVhuWLmeaDheWig+mDveaYr+mAmeeorueLgOazgeWXju+8nw0KICAg55W26Kqk5Yik55qE5oiQ5pys5qW16auY5pmC77yM5bi45pyD5Ye654++6YCZ5qij55qE54uA5rOB44CCDQogIDxicj4NCuOAkCoqUSoq44CR5L2g5Y+v5Lul5qih5pOs5Ye644CM5YWo5YGa44CN5q+U44CM5YWo5LiN5YGa44CN6YKE6KaB5aW955qE54uA5rOB44CB5Lim6IiJ5Ye65LiA5YCL5pyD55m855Sf6YCZ56iu54uA5rOB55qE5ZWG5YuZ5oOF5aKD5ZeO77yfDQogICDlpoLmnpzlgZrpjK/kuobkuI3mnIPmnInku7vkvZXmkI3lpLHvvIzkvYblgZrlsI3kuobmnIPmnInlvojmo5LnmoTliKnmvaTvvIzliYflj6/ku6XljrvlgZrjgII8YnI+DQogICDnlbbkvaDkuI3lgZrljbvnmbznlJ/mhI/lpJYoRmFsc2UgIE5lZ2F0aXZlKeeahOaZguWAmeeUoueUn+eahOaIkOacrOmBoOmrmOaWvOWBmuaZguWAme+8jOWwseWPr+S7peWOu+WBmuOAgu+8iOmjm+WuieWumuacn+aqouafpSkNCg0KYGBge3J9DQpwYXlvZmYgPSBtYXRyaXgoYygxMDAsMCwwLDEwMCksMiwyKSANCmN1dG9mZiA9IHNlcSgwLjAyLCAwLjcsIDAuMDEpDQoNCnJlc3VsdCA9IHNhcHBseShjdXRvZmYsIGZ1bmN0aW9uKHApIHN1bSh0YWJsZSh5LHByZWQ+cCkqcGF5b2ZmKSApDQoNCmkgPSB3aGljaC5tYXgocmVzdWx0KQ0KcGFyKGNleD0wLjcpDQpwbG90KGN1dG9mZiwgcmVzdWx0LCB0eXBlPSdsJywgY29sPSdjeWFuJywgbHdkPTIsIG1haW49c3ByaW50ZigNCiAgIk9wdG9tYWwgRXhwZWN0ZWQgUmVzdWx0OiAkJWQgQCAlLjJmIixyZXN1bHRbaV0sY3V0b2ZmW2ldKSkNCmFibGluZSh2PXNlcSgwLDEsMC4wNSksaD1zZXEoLTIzMDAwLC0xNzAwMCw1MDApLGNvbD0nbGlnaHRncmF5JyxsdHk9MykNCmFibGluZSh2PWN1dG9mZltpXSxjb2w9J3JlZCcpDQpgYGANCg0KPGJyPg0KDQojIyMjIEY2OiBTaW11bGF0aW9uDQpgYGB7ciBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD02fQ0KbGlicmFyeShtYW5pcHVsYXRlKQ0KcDAgPSBwYXIobWZyb3c9YygyLDEpLGNleD0wLjgpDQptYW5pcHVsYXRlKHsNCiAgWTAgPSAtMjIwMDA7IFkxID0gLTEyMDAwDQogIG14ID0gbWF0cml4KGModHJ1ZV9uZWcsIGZhbHNlX25lZywgZmFsc2VfcG9zLCB0cnVlX3BvcyksMiwyKSANCiAgY3ggPSBzZXEoMC4wMiwgMC42NCwgMC4wMSkNCiAgcnggPSBzYXBwbHkoY3gsIGZ1bmN0aW9uKHApIHN1bSh0YWJsZSh5LCBwcmVkPnApKm14KSApDQogIGkgPSB3aGljaC5tYXgocngpDQogIHBsb3QoY3gsIHJ4LCB0eXBlPSdsJyxjb2w9J2N5YW4nLGx3ZD0yLG1haW49c3ByaW50ZigNCiAgICAiT3B0b21hbCBFeHBlY3RlZCBSZXN1bHQ6ICQlZCBAICUuMmYsIFQ6JWQiLHJ4W2ldLGN4W2ldLHN1bShwcmVkPmN4W2ldKSksDQogICAgeWxpbT1jKFkwLFkxKSkNCiAgYWJsaW5lKHY9Y3hbaV0sY29sPSdyZWQnKQ0KICBhYmxpbmUodj1zZXEoMCwxLDAuMSksaD1zZXEoWTAsWTEsMjAwMCksY29sPSdsaWdodGdyYXknLGx0eT0zKQ0KICBEUFAocHJlZCwgeSwgMSwgYj1zZXEoMCwxLDAuMDIpKQ0KICBhYmxpbmUodj1jeFtpXSxjb2w9J3JlZCcpDQogIH0sDQogIHRydWVfbmVnICA9IHNsaWRlcigtMTAwLDEwMCwwLHN0ZXA9NSksDQogIGZhbHNlX25lZyA9IHNsaWRlcigtMTAwLDEwMCwtMTAwLHN0ZXA9NSksDQogIGZhbHNlX3BvcyA9IHNsaWRlcigtMTAwLDEwMCwtMTAsc3RlcD01KSwNCiAgdHJ1ZV9wb3MgID0gc2xpZGVyKC0xMDAsMTAwLC02MCxzdGVwPTUpDQogICkgDQpwYXIocDApDQpgYGANCg0KDQrjgJAqKlEqKuOAkeacieS6lOeoruaIkOacrOWIhuWIpeeCuiBgJDUsICQxMCwgJDE1LCAkMjAsICQzMGAg55qE6Jel77yM5a6D5YCR5YiG5Yil5Y+v5Lul5bCH6aKo6Zqq5oiQ5pys5b6eIGAkMTAwYCDpmY3kvY7liLAgYCQ3MCwgJDYwLCAkNTAsICQ0MCwgJDI1YO+8jOWTquS4gOeoruiXpeeahOacn+acm+aViOebiuaYr+acgOWkp+eahOWRou+8nw0KDQoNCmBgYHtyfQ0KIzEgJDUkNzAgIEV4cGVjdGVkIFJlc3VsdDotMTc0MTUNCg0KIzIgJDEwJDYwIEV4cGVjdGVkIFJlc3VsdDotMTc1MDANCg0KIzMgJDE1JDUwIEV4cGVjdGVkIFJlc3VsdDotMTc0NTANCg0KIzQgJDIwJDQwIEV4cGVjdGVkIFJlc3VsdDotMTc0MDANCg0KIzUgJDMwJDI1IEV4cGVjdGVkIFJlc3VsdDotMTc2NTUNCg0KI2FuczogLTE3NDAwDQoj5ZOq5LiA56iu6Jel55qE5pyf5pyb5pWI55uK5piv5pyA5aSn55qE5ZGi77yfKCQyMC8kNDAp6YCZ57WE5pyA5aSn44CCDQpgYGANCg0KPGJyPg0KDQotIC0gLQ0KIyMjIOOAkEfjgJHliIbmnpDmtYHnqIvvvJros4fmlpnjgIHmqKHlnovjgIHpoJDmuKzjgIHmsbrnrZYNCg0KIVtGaWd1cmUgNCAtIOizh+aWmeOAgeaooeWei+OAgemgkOa4rOOAgeaxuuetll0ocmVzL2Zsb3cuanBnKQ0KDQoNCg0KPGJyPjxicj48YnI+PGJyPjxicj4NCg0KDQoNCg0KDQoNCg0KDQoNCg==