rm(list=ls(all=T))
options(digits=4, scipen=12)
library(dplyr); library(ggplot2)

【A】 Definitions

機率、勝率(Odd)、Logit

  • Odd = \(p/(1-p)\)

  • Logit = \(log(odd)\) = \(log(\frac{p}{1=p})\)

  • \(o = p/(1-p)\) ; \(p = o/(1+o)\) ; \(logit = log(o)\)

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? A:logit:f(x),ln(odd),ln(p/(1-p)) +A:logit:log(p/1-p) logistic function 把logit變成odd



【B】glm(, family=binomial)

glm()的功能:在 \(\{x\}\) 的空間之中,找出區隔 \(y\) 的(類別)界線

getwd()
[1] "C:/NSYSU_Data_Analytic_SummerVacation/Week2/0713"
Q = read.csv("./data/quality.csv")  # Read in dataset
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
                
(Intercept)  ***
OfficeVisits ** 
Narcotics    ***
---
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 = sum(b * c(1, 2, 3))
odd = exp(logit)
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)
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? A:機率為0.1~0.9的線

Q】Given any point in the figure above, how can you tell its (predicted) probability approximately? A:我們可以畫一條機率為0.5的線, 點若位在線的右方機率>0.5 位於線的左方機率<0.5



【C】The Confusion Matrix

Figure 1 - Confusion Matrix

Figure 1 - Confusion Matrix



【D】The Distribution of Predicted Probability (DPP)

Confusion matrix is not fixed. It changes by Threshold

Figure 2 - Dist. Prediected Prob.

Figure 2 - Dist. Prediected Prob.

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.1,0.03), rnorm(N2,0.6,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?

A:AUC不可能<0.5,Acc、Sens、Spec可以是0

N1 = 300; N2 = 100
DPP2(pred = c(rnorm(N1,0.8,0.03), rnorm(N2,0.2,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

#install.packages('ROCR')
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
(Intercept)     -7.88657    0.89073   -8.85
male             0.52846    0.13544    3.90
age              0.06206    0.00834    7.44
education       -0.05892    0.06243   -0.94
currentSmoker    0.09324    0.19401    0.48
cigsPerDay       0.01501    0.00783    1.92
BPMeds           0.31122    0.28741    1.08
prevalentStroke  1.16579    0.57121    2.04
prevalentHyp     0.31582    0.17176    1.84
diabetes        -0.42149    0.40799   -1.03
totChol          0.00384    0.00138    2.79
sysBP            0.01134    0.00457    2.48
diaBP           -0.00474    0.00800   -0.59
BMI              0.01072    0.01616    0.66
heartRate       -0.00810    0.00531   -1.52
glucose          0.00893    0.00284    3.15
                       Pr(>|z|)    
(Intercept)             < 2e-16 ***
male            0.0000955212349 ***
age             0.0000000000001 ***
education                0.3453    
currentSmoker            0.6308    
cigsPerDay               0.0551 .  
BPMeds                   0.2789    
prevalentStroke          0.0413 *  
prevalentHyp             0.0660 .  
diabetes                 0.3016    
totChol                  0.0053 ** 
sysBP                    0.0130 *  
diaBP                    0.5535    
BMI                      0.5069    
heartRate                0.1274    
glucose                  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

Figure 3 - Startegic Optimization

Figure 3 - Startegic Optimization

payoff = matrix(c(0,-100,-10,-60),2,2) 
cutoff = seq(0.02, 0.64, 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】如果什麼都不做,期望報酬是多少?

payoff = matrix(c(0,-100,-10,-60),2,2) 
cutoff = seq(0.02, 0.7296, 0.01)
result = sapply(cutoff, function(p) sum(table(y,pred>p)*payoff) )
i = 71
par(cex=0.9)
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=0.7,col='red')

Q】如果每位病人都做呢?

payoff = matrix(c(0,-100,-10,-60),2,2) 
cutoff = seq(0.02, 0.7296, 0.01)
result = sapply(cutoff, function(p) sum(table(y,pred>p)*payoff) )
i = 1
par(cex=0.9)
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=0.02,col='red')

Q】以上哪一種做法期望報酬比較高? 全不做比較好,期望值是-19810,比全做還高

Q】在所有的商務情境都是這種狀況嗎? 不是

Q】你可以模擬出「全做」比「全不做」還要好的狀況、並舉出一個會發生這種狀況的商務情境嗎?

投資新創公司 投資失敗不會賠太多成本 投資成功可能賺很多回來 payoff: [,1] [,2] [1,] 0 -10 [2,] 0 100

payoff = matrix(c(0,0,-10,100),2,2) 
cutoff = seq(0.02, 0.7296, 0.01)
result = sapply(cutoff, function(p) sum(table(y,pred>p)*payoff) )
i = 1
par(cex=0.9)
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=0.02,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,哪一種藥的期望效益是最大的呢?

payoff = matrix(c(0,-100,-5,-70),2,2) 
cutoff = seq(0.02, 0.7296, 0.01)
table(y,pred>0.02) 
   
y   FALSE TRUE
  0     5 1070
  1     0  198
sum(table(y,pred>0.02)*payoff)
[1] -19210
sum(table(y,pred>0.03)*payoff)
[1] -18975
class(result)
[1] "numeric"
result = sapply(cutoff, function(p) sum(table(y,pred>p)*payoff) )
i = which.max(result)
par(cex=0.9)
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=0.14,col='red')

# (5,70) -17415, 0.14
# (10,60) -17500, 0.2
# (15,50) -17450,0.2
# (20,40) -17400, 0.2
# (30,25) -17655, 0.22
#第五種"



【G】分析流程:資料、模型、預測、決策

Figure 4 - 資料、模型、預測、決策

Figure 4 - 資料、模型、預測、決策






LS0tDQp0aXRsZTogIkFTMy0wIEdyb3VwLTEiDQphdXRob3I6ICLlionogrLpipggTTA2NDAyMDAyNSAuLi4iDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciBlY2hvPVQsIG1lc3NhZ2U9RiwgY2FjaGU9Riwgd2FybmluZz1GfQ0KDQpybShsaXN0PWxzKGFsbD1UKSkNCm9wdGlvbnMoZGlnaXRzPTQsIHNjaXBlbj0xMikNCmxpYnJhcnkoZHBseXIpOyBsaWJyYXJ5KGdncGxvdDIpDQpgYGANCg0KDQotIC0gLQ0KDQojIyMg44CQQeOAkSBEZWZpbml0aW9ucw0KDQojIyMjIOapn+eOh+OAgeWLneeOhyhPZGQp44CBTG9naXQNCg0KKyBPZGQgPSAgJHAvKDEtcCkkDQoNCisgTG9naXQgPSAkbG9nKG9kZCkkID0gJGxvZyhcZnJhY3twfXsxPXB9KSQNCg0KKyAkbyA9IHAvKDEtcCkkIDsgJHAgPSBvLygxK28pJCA7ICAkbG9naXQgPSBsb2cobykkDQoNCmBgYHtyIGZpZy5oZWlnaHQ9My42LCBmaWcud2lkdGg9N30NCnBhcihjZXg9MC44LCBtZmNvbD1jKDEsMikpDQpjdXJ2ZSh4LygxLXgpLCAwLjAyLCAwLjk4LCBjb2w9J2N5YW4nLGx3ZD0yLCBtYWluPSdvZGQnKQ0KYWJsaW5lKHY9c2VxKDAsMSwwLjEpLCBoPXNlcSgwLDUwLDUpLCBjb2w9J2xpZ2h0Z3JheScsIGx0eT0zKQ0KY3VydmUobG9nKHgvKDEteCkpLCAwLjAwNSwgMC45OTUsIGx3ZD0yLCBjb2w9J3B1cnBsZScsIG1haW49ImxvZ2l0IikNCmFibGluZSh2PXNlcSgwLDEsMC4xKSwgaD1zZXEoLTUsNSwxKSwgY29sPSdsaWdodGdyYXknLCBsdHk9MykNCg0KYGBgDQoNCiMjIyMgTG9naXN0aWMgRnVuY3Rpb24gJiBMb2dpc3RpYyBSZWdyZXNzaW9uDQoNCisgTGluZWFyIE1vZGVsOiAkeSA9IGYoeCkgPSBiXzAgKyBiXzF4XzEgKyBiXzJ4XzIgKyAuLi4kDQoNCisgR2VuZXJhbCBMaW5lYXIgTW9kZWwoR0xNKTogJHkgPSBMaW5rKGYoeCkpJCANCg0KKyBMb2dpc3RpYyBSZWdyZXNzaW9uOiAkbG9naXQoeSkgPSBsb2coXGZyYWN7cH17MS1wfSkgPSBmKHgpIFx0ZXh0eyB3aGVyZSB9IHAgPSBwcm9iW3k9MV0kIA0KDQorIExvZ2lzdGljIEZ1bmN0aW9uOiAkTG9naXN0aWMoRl94KSA9IFxmcmFjezF9ezErRXhwKC1GX3gpfSA9IFxmcmFje0V4cChGX3gpfXsxK0V4cChGX3gpfSQNCg0KYGBge3IgIGZpZy53aWR0aD00LCBmaWcuaGVpZ2h0PTR9DQpwYXIoY2V4PTAuOCkNCmN1cnZlKDEvKDErZXhwKC14KSksIC01LCA1LCBjb2w9J2JsdWUnLCBsd2Q9MixtYWluPSJMb2dpc3RpYyBGdW5jdGlvbiIsDQogICAgICB4bGFiPSJmKHgpOiB0aGUgbG9naXQgb2YgeSA9IDEiLCB5bGFiPSJ0aGUgcHJvYmFiaWxpdHkgb2YgeSA9IDEiKQ0KYWJsaW5lKHY9LTU6NSwgaD1zZXEoMCwxLDAuMSksIGNvbD0nbGlnaHRncmF5JywgbHR5PTIpDQphYmxpbmUodj0wLGg9MC41LGNvbD0ncGluaycpDQpwb2ludHMoMCwwLjUscGNoPTIwLGNleD0xLjUsY29sPSdyZWQnKQ0KYGBgDQoNCuOAkCoqUSoq44CRV2hhdCBhcmUgdGhlIGRlZmluaWlvbiBvZiBgbG9naXRgICYgYGxvZ2lzdGljIGZ1bmN0aW9uYD8gV2hhdCBpcyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlbT8NCkE6bG9naXQ6Zih4KSxsbihvZGQpLGxuKHAvKDEtcCkpCStBOmxvZ2l0OmxvZyhwLzEtcCkNCiAgbG9naXN0aWMgZnVuY3Rpb24g5oqKbG9naXTorormiJBvZGQNCg0KDQoNCg0KPGJyPg0KDQotIC0gLQ0KDQojIyMg44CQQuOAkWBnbG0oLCBmYW1pbHk9Ymlub21pYWwpYA0KDQpgZ2xtKClg55qE5Yqf6IO977ya5ZyoICRce3hcfSQg55qE56m66ZaT5LmL5Lit77yM5om+5Ye65Y2A6ZqUICR5JCDnmoQo6aGe5YilKeeVjOe3mg0KDQpgYGB7cn0NCmdldHdkKCkNClEgPSByZWFkLmNzdigiLi9kYXRhL3F1YWxpdHkuY3N2IikgICMgUmVhZCBpbiBkYXRhc2V0DQpnbG0xID0gZ2xtKFBvb3JDYXJlfk9mZmljZVZpc2l0cytOYXJjb3RpY3MsIFEsIGZhbWlseT1iaW5vbWlhbCkNCnN1bW1hcnkoZ2xtMSkNCmBgYA0KDQpgYGB7cn0NCmIgPSBjb2VmKGdsbTEpOyBiICAgIyBleHRyYWN0IHRoZSByZWdyZXNzaW9uIGNvZWYNCmBgYA0KDQpHaXZlbiBgT2ZmaWNlVmlzaXRzPTMsIE5hcmNvdGljcz00YCwgd2hhdCBhcmUgdGhlIHByZWRpY3RlZCBsb2dpdCwgb2RkIGFuZCBwcm9iYWJpbGl0eT8NCmBgYHtyfQ0KbG9naXQgPSBzdW0oYiAqIGMoMSwgMywgNCkpDQpvZGQgPSBleHAobG9naXQpDQpwcm9iID0gb2RkLygxK29kZCkNCmMobG9naXQ9bG9naXQsIG9kZD1vZGQsIHByb2I9cHJvYikNCg0KYGBgDQoNCuOAkCoqUSoq44CRV2hhdCBpZiBgT2ZmaWNlVmlzaXRzPTIsIE5hcmNvdGljcz0zYD8NCmBgYHtyfQ0KbG9naXQgPSBzdW0oYiAqIGMoMSwgMiwgMykpDQpvZGQgPSBleHAobG9naXQpDQpwcm9iID0gb2RkLygxK29kZCkNCmMobG9naXQ9bG9naXQsIG9kZD1vZGQsIHByb2I9cHJvYikNCg0KYGBgDQoNCldlIGNhbiBwbG90IHRoZSBsaW5lIG9mIGBsb2dpdCA9IDBgIG9yIGBwcm9iID0gMC41YCBvbiB0aGUgcGxhbmUgb2YgJFgkDQpgYGB7ciBmaWcud2lkdGg9My42LCBmaWcuaGVpZ2h0PTMuNn0NCnBhcihjZXg9MC44KQ0KcGxvdChRJE9mZmljZVZpc2l0cywgUSROYXJjb3RpY3MsIGNvbD0xK1EkUG9vckNhcmUscGNoPTIwKQ0KYWJsaW5lKC1iWzFdL2JbM10sIC1iWzJdL2JbM10pDQpgYGANCg0KRnVydGhlcm1vcmUsIHdlIGNhbiB0cmFuc2xhdGUgcHJvYmFiaWxpdHksIGxvZ2l0IGFuZCBjb2VmZmljZW50cyB0byBpbnRlcmNlcHQgJiBzbG9wZSAuLi4NCg0KJCRmKHgpID0gYl8xICsgYl8yIHhfMiArIGJfMyB4XzMgPSBnIFxSaWdodGFycm93ICB4XzMgPSBcZnJhY3tnIC0gYl8xfXtiXzN9IC0gXGZyYWN7Yl8yfXtiXzN9eF8yJCQNCg0KDQpgYGB7ciAgZmlnLndpZHRoPTMuNiwgZmlnLmhlaWdodD0zLjZ9DQpwID0gc2VxKDAuMSwwLjksMC4xKQ0KbG9naXQgPSBsb2cocC8oMS1wKSkNCmRhdGEuZnJhbWUocHJvYiA9IHAsIGxvZ2l0KQ0KYGBgDQoNCnRoZW4gbWFyayB0aGUgY29udG91cnMgb2YgcHJvYWJpbGl0aWVzIGludG8gdGhlIHNjYXR0ZXIgcGxvdCANCmBgYHtyICBmaWcud2lkdGg9My42LCBmaWcuaGVpZ2h0PTMuNn0NCnBhcihjZXg9MC43KQ0KcGxvdChRJE9mZmljZVZpc2l0cywgUSROYXJjb3RpY3MsIGNvbD0xK1EkUG9vckNhcmUsDQogICAgIHBjaD0yMCwgY2V4PTEuMywgeGxhYj0nWDInLCB5bGFiPSdYMycpDQpmb3IoZyBpbiBsb2dpdCkgew0KICBhYmxpbmUoKGctYlsxXSkvYlszXSwgLWJbMl0vYlszXSwgY29sPWlmZWxzZShnPT0wLCdibHVlJywnY3lhbicpKSB9DQpgYGANCg0K44CQKipRKirjgJFXaGF0IGRvIHRoZSBibHVlL2N5YW4gbGluZXMgbWVhbnM/DQpBOuapn+eOh+eCujAuMX4wLjnnmoTnt5oNCg0KDQoNCuOAkCoqUSoq44CRR2l2ZW4gYW55IHBvaW50IGluIHRoZSBmaWd1cmUgYWJvdmUsIGhvdyBjYW4geW91IHRlbGwgaXRzIChwcmVkaWN0ZWQpIHByb2JhYmlsaXR5IGFwcHJveGltYXRlbHk/DQpBOuaIkeWAkeWPr+S7peeVq+S4gOaineapn+eOh+eCujAuNeeahOe3mu+8jA0K6bue6Iul5L2N5Zyo57ea55qE5Y+z5pa55qmf546HPjAuNQ0K5L2N5pa857ea55qE5bem5pa55qmf546HPDAuNQ0KDQo8YnI+DQoNCi0gLSAtDQoNCiMjIyDjgJBD44CRVGhlIENvbmZ1c2lvbiBNYXRyaXgNCg0KDQohW0ZpZ3VyZSAxIC0gQ29uZnVzaW9uIE1hdHJpeF0ocmVzL2NvbmZ1c2lvbl9tYXRyaXguanBnKQ0KDQoNCjxicj4NCg0KLSAtIC0NCiMjIyDjgJBE44CRVGhlIERpc3RyaWJ1dGlvbiBvZiBQcmVkaWN0ZWQgUHJvYmFiaWxpdHkgKERQUCkNCg0KQ29uZnVzaW9uIG1hdHJpeCBpcyBub3QgZml4ZWQuIEl0IGNoYW5nZXMgYnkgYFRocmVzaG9sZGAgLi4uDQoNCiFbRmlndXJlIDIgLSBEaXN0LiBQcmVkaWVjdGVkIFByb2IuXShyZXMvZHBwLmpwZykNCg0KDQoNCmBgYHtyfQ0KbGlicmFyeShjYVRvb2xzKQ0KRFBQMiA9IGZ1bmN0aW9uKHByZWQsY2xhc3MsdHZhbHVlLGJyZWFrcz0wLjAxKSB7DQogIG14ID0gdGFibGUoY2xhc3MgPT0gdHZhbHVlLCBwcmVkID4gMC41KSANCiAgdG4gPSBzdW0oY2xhc3MgIT0gdHZhbHVlICYgcHJlZCA8PSAwLjUpDQogIGZuID0gc3VtKGNsYXNzID09IHR2YWx1ZSAmIHByZWQgPD0gMC41KQ0KICBmcCA9IHN1bShjbGFzcyAhPSB0dmFsdWUgJiBwcmVkID4gMC41KQ0KICB0cCA9IHN1bShjbGFzcyA9PSB0dmFsdWUgJiBwcmVkID4gMC41KQ0KICBhY2MgPSAodG4gKyB0cCkvbGVuZ3RoKHByZWQpDQogIHNlbnMgPSB0cC8oZm4rdHApDQogIHNwZWMgPSB0bi8odG4rZnApDQogIGF1YyA9IGNvbEFVQyhwcmVkLGNsYXNzKQ0KICBkYXRhLmZyYW1lKHByZWQsY2xhc3MpICU+JSANCiAgICBnZ3Bsb3QoYWVzKHg9cHJlZCwgZmlsbD1jbGFzcykpICsNCiAgICBnZW9tX2hpc3RvZ3JhbShjb2w9J2dyYXknLGFscGhhPTAuNSxicmVha3M9c2VxKDAsMSxicmVha3MpKSArDQogICAgeGxpbSgwLDEpICsgdGhlbWVfYncoKSArIHhsYWIoInByZWRpY3RlZCBwcm9iYWJpbGl0eSIpICsgDQogICAgZ2d0aXRsZSgNCiAgICAgIHNwcmludGYoIkRpc3RyaWJ1dGlvbiBvZiBQcm9iW2NsYXNzID0gXCclc1wnXSIsIHR2YWx1ZSksDQogICAgICBzcHJpbnRmKCJBVUM9JS4zZiwgQWNjPSUuM2YsIFNlbnM9JS4zZiwgU3BlYz0lLjNmIiwNCiAgICAgICAgICAgICAgYXVjLCBhY2MsIHNlbnMsIHNwZWMpICkgDQogIH0NCg0KYGBgDQoNCmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTIuNX0NCk4xID0gMzAwOyBOMiA9IDEwMA0KRFBQMihwcmVkID0gYyhybm9ybShOMSwwLjEyNSwwLjAzKSwgcm5vcm0oTjIsMC4zNzUsMC4wMykpLA0KICAgICBjbGFzcyA9IGMocmVwKCdCJyxOMSksIHJlcCgnQScsTjIpKSwgDQogICAgIHR2YWx1ZSA9ICdBJykNCmBgYA0KDQrjgJAqKlEqKuOAkUlzIGl0IHBvc3NpYmxlIHRvIGhhdmUgYEFVQyA9IEFDQyA9IFNFTlMgPSBTUEVDID0gMWA/IENhbiB5b3UgbW9kaWZ5IHRoZSBjb2RlIHRvIG1ha2UgaXQgaGFwcGVuPw0KDQpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD0yLjV9DQpOMSA9IDMwMDsgTjIgPSAxMDANCkRQUDIocHJlZCA9IGMocm5vcm0oTjEsMC4xLDAuMDMpLCBybm9ybShOMiwwLjYsMC4wMykpLA0KICAgICBjbGFzcyA9IGMocmVwKCdCJyxOMSksIHJlcCgnQScsTjIpKSwgDQogICAgIHR2YWx1ZSA9ICdBJykNCg0KDQpgYGANCg0KDQrjgJAqKlEqKuOAkUlzIGl0IHBvc3NpYmxlIHRvIGhhdmUgYEFVQyA9IEFDQyA9IFNFTlMgPSBTUEVDID0gMGA/IENhbiB5b3UgbW9kaWZ5IHRoZSBjb2RlIHRvIG1ha2UgdGhhdCBoYXBwZW4/DQoNCkE6QVVD5LiN5Y+v6IO9PDAuNSxBY2PjgIFTZW5z44CBU3BlY+WPr+S7peaYrzANCmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTIuNX0NCk4xID0gMzAwOyBOMiA9IDEwMA0KRFBQMihwcmVkID0gYyhybm9ybShOMSwwLjgsMC4wMyksIHJub3JtKE4yLDAuMiwwLjAzKSksDQogICAgIGNsYXNzID0gYyhyZXAoJ0InLE4xKSwgcmVwKCdBJyxOMikpLCANCiAgICAgdHZhbHVlID0gJ0EnKQ0KYGBgDQo8YnI+DQoNCi0gLSAtDQojIyMg44CQReOAkU1vZGVsaW5nIEV4cGVydA0KDQojIyMjIEUxOiBSYW5kb20gU3BsaXQNCmBgYHtyfQ0Kc2V0LnNlZWQoODgpDQpzcGxpdCA9IHNhbXBsZS5zcGxpdChRJFBvb3JDYXJlLCBTcGxpdFJhdGlvID0gMC43NSkNCnRhYmxlKHNwbGl0KSAlPiUgcHJvcC50YWJsZSgpDQp0YWJsZSh5ID0gUSRQb29yQ2FyZSwgc3BsaXQpICU+JSBwcm9wLnRhYmxlKDIpDQpgYGANCg0KYGBge3J9DQpUUiA9IHN1YnNldChRLCBzcGxpdCA9PSBUUlVFKQ0KVFMgPSBzdWJzZXQoUSwgc3BsaXQgPT0gRkFMU0UpDQpgYGANCg0KIyMjIyBFMjogQnVpbGQgTW9kZWwNCmBgYHtyfQ0KZ2xtMSA9IGdsbShQb29yQ2FyZSB+IE9mZmljZVZpc2l0cyArIE5hcmNvdGljcywgVFIsIGZhbWlseT1iaW5vbWlhbCkNCnN1bW1hcnkoZ2xtMSkNCmBgYA0KDQojIyMjIEUzOiBQcmVkaWN0aW9uICYgRXZhbHVhdGlvbg0KYGBge3J9DQpwcmVkID0gcHJlZGljdChnbG0xLCB0eXBlPSdyZXNwb25zZScpDQpteCA9IHRhYmxlKFRSJFBvb3JDYXJlLCBwcmVkID4gMC41KTsgbXgNCmMoYWNjdXJhY3kgPSBzdW0oZGlhZyhteCkpL3N1bShteCksDQogIHNlbnNpdGl2aXR5ID0gbXhbMiwyXS9zdW0obXhbMixdKSwNCiAgc3BlY2lmaWNpdHkgPSBteFsxLDFdL3N1bShteFsxLF0pKQ0KYGBgDQoNCiMjIyMgRTQ6IFJPQyAmIEFVQw0KYGBge3IgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0NCiNpbnN0YWxsLnBhY2thZ2VzKCdST0NSJykNCmxpYnJhcnkoUk9DUikNClJPQ1JwcmVkID0gcHJlZGljdGlvbihwcmVkLCBUUiRQb29yQ2FyZSkNClJPQ1JwZXJmID0gcGVyZm9ybWFuY2UoUk9DUnByZWQsICJ0cHIiLCAiZnByIikNCnBhcihjZXg9MC44KQ0KcGxvdChST0NScGVyZiwgY29sb3JpemU9VFJVRSwgcHJpbnQuY3V0b2Zmcy5hdD1zZXEoMCwxLDAuMSkpDQpgYGANCg0KYGBge3J9DQphcy5udW1lcmljKHBlcmZvcm1hbmNlKFJPQ1JwcmVkLCAiYXVjIilAeS52YWx1ZXMpDQpjYVRvb2xzOjpjb2xBVUMocHJlZCwgVFIkUG9vckNhcmUpDQpgYGANCg0KPGJyPg0KDQotIC0gLQ0KIyMjIOOAkEbjgJFGcmFtaW5naGFtIEhlYXJ0IFN0dWR5DQoNCmBgYHtyfQ0Kc291cmNlKCJEUFAuUiIpDQpgYGANCg0KIyMjIyBGMTogUmVhZGluZyAmIFNwbGl0dGluZw0KYGBge3J9DQpGID0gcmVhZC5jc3YoIi4vZGF0YS9mcmFtaW5naGFtLmNzdiIpDQpzZXQuc2VlZCgxMDAwKQ0Kc3BsaXQgPSBzYW1wbGUuc3BsaXQoRiRUZW5ZZWFyQ0hELCBTcGxpdFJhdGlvID0gMC42NSkNClRSID0gc3Vic2V0KEYsIHNwbGl0PT1UUlVFKQ0KVFMgPSBzdWJzZXQoRiwgc3BsaXQ9PUZBTFNFKQ0KYGBgDQoNCiMjIyMgRjI6IExvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwNCmBgYHtyfQ0KZ2xtMiA9IGdsbShUZW5ZZWFyQ0hEIH4gLiwgVFIsIGZhbWlseT1iaW5vbWlhbCkNCnN1bW1hcnkoZ2xtMikNCmBgYA0KDQojIyMjIEYzOiBQcmVkaWN0aW9uICYgRXZhbHVhdGlvbg0KYGBge3J9DQpwcmVkID0gcHJlZGljdChnbG0yLCBUUywgdHlwZT0icmVzcG9uc2UiKQ0KeSA9IFRTJFRlblllYXJDSERbIWlzLm5hKHByZWQpXSAgICAgICAgICAgICAjIHJlbW92ZSBOQQ0KcHJlZCA9IHByZWRbIWlzLm5hKHByZWQpXQ0KDQpteCA9IHRhYmxlKHksIHByZWQgPiAwLjUpOyBteA0KYyhhY2N1cmFjeSA9IHN1bShkaWFnKG14KSkvc3VtKG14KSwNCiAgc2Vuc2l0aXZpdHkgPSBteFsyLDJdL3N1bShteFsyLF0pLA0KICBzcGVjaWZpY2l0eSA9IG14WzEsMV0vc3VtKG14WzEsXSkpDQpgYGANCg0KIyMjIyBGNDogQVVDICYgRFBQDQpgYGB7ciBmaWcud2lkdGg9NywgZmlnLmhlaWdodD0yLjR9DQpwYXIoY2V4PTAuNykNCmF1YyA9IERQUChwcmVkLCB5LCAxLCBiPXNlcSgwLDEsMC4wMikpICAjIDAuNzQyMTENCmBgYA0KDQojIyMjIEY1OiBFeHBlY3RlZCBSZXN1bHQgJiBPcHRpbWl6YXRpb24NCg0KDQohW0ZpZ3VyZSAzIC0gU3RhcnRlZ2ljIE9wdGltaXphdGlvbl0ocmVzL29wdGltaXphdGlvbi5qcGcpDQoNCg0KYGBge3IgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NH0NCnBheW9mZiA9IG1hdHJpeChjKDAsLTEwMCwtMTAsLTYwKSwyLDIpIA0KY3V0b2ZmID0gc2VxKDAuMDIsIDAuNjQsIDAuMDEpDQoNCnJlc3VsdCA9IHNhcHBseShjdXRvZmYsIGZ1bmN0aW9uKHApIHN1bSh0YWJsZSh5LHByZWQ+cCkqcGF5b2ZmKSApDQoNCg0KaSA9IHdoaWNoLm1heChyZXN1bHQpDQpwYXIoY2V4PTAuNykNCnBsb3QoY3V0b2ZmLCByZXN1bHQsIHR5cGU9J2wnLCBjb2w9J2N5YW4nLCBsd2Q9MiwgbWFpbj1zcHJpbnRmKA0KICAiT3B0b21hbCBFeHBlY3RlZCBSZXN1bHQ6ICQlZCBAICUuMmYiLHJlc3VsdFtpXSxjdXRvZmZbaV0pKQ0KYWJsaW5lKHY9c2VxKDAsMSwwLjA1KSxoPXNlcSgtMjMwMDAsLTE3MDAwLDUwMCksY29sPSdsaWdodGdyYXknLGx0eT0zKQ0KYWJsaW5lKHY9Y3V0b2ZmW2ldLGNvbD0ncmVkJykNCg0KDQpgYGANCg0K44CQKipRKirjgJHlpoLmnpzku4Dpurzpg73kuI3lgZrvvIzmnJ/mnJvloLHphazmmK/lpJrlsJHvvJ8NCmBgYHtyfQ0KcGF5b2ZmID0gbWF0cml4KGMoMCwtMTAwLC0xMCwtNjApLDIsMikgDQpjdXRvZmYgPSBzZXEoMC4wMiwgMC43Mjk2LCAwLjAxKQ0KDQoNCnJlc3VsdCA9IHNhcHBseShjdXRvZmYsIGZ1bmN0aW9uKHApIHN1bSh0YWJsZSh5LHByZWQ+cCkqcGF5b2ZmKSApDQoNCg0KaSA9IDcxDQpwYXIoY2V4PTAuOSkNCnBsb3QoY3V0b2ZmLCByZXN1bHQsIHR5cGU9J2wnLCBjb2w9J2N5YW4nLCBsd2Q9MiwgbWFpbj1zcHJpbnRmKA0KICAiT3B0b21hbCBFeHBlY3RlZCBSZXN1bHQ6ICQlZCBAICUuMmYiLHJlc3VsdFtpXSxjdXRvZmZbaV0pKQ0KYWJsaW5lKHY9c2VxKDAsMSwwLjA1KSxoPXNlcSgtMjMwMDAsLTE3MDAwLDUwMCksY29sPSdsaWdodGdyYXknLGx0eT0zKQ0KYWJsaW5lKHY9MC43LGNvbD0ncmVkJykNCg0KDQpgYGANCg0K44CQKipRKirjgJHlpoLmnpzmr4/kvY3nl4Xkurrpg73lgZrlkaLvvJ8NCmBgYHtyfQ0KcGF5b2ZmID0gbWF0cml4KGMoMCwtMTAwLC0xMCwtNjApLDIsMikgDQpjdXRvZmYgPSBzZXEoMC4wMiwgMC43Mjk2LCAwLjAxKQ0KDQoNCg0KDQpyZXN1bHQgPSBzYXBwbHkoY3V0b2ZmLCBmdW5jdGlvbihwKSBzdW0odGFibGUoeSxwcmVkPnApKnBheW9mZikgKQ0KDQoNCmkgPSAxDQpwYXIoY2V4PTAuOSkNCnBsb3QoY3V0b2ZmLCByZXN1bHQsIHR5cGU9J2wnLCBjb2w9J2N5YW4nLCBsd2Q9MiwgbWFpbj1zcHJpbnRmKA0KICAiT3B0b21hbCBFeHBlY3RlZCBSZXN1bHQ6ICQlZCBAICUuMmYiLHJlc3VsdFtpXSxjdXRvZmZbaV0pKQ0KYWJsaW5lKHY9c2VxKDAsMSwwLjA1KSxoPXNlcSgtMjMwMDAsLTE3MDAwLDUwMCksY29sPSdsaWdodGdyYXknLGx0eT0zKQ0KYWJsaW5lKHY9MC4wMixjb2w9J3JlZCcpDQoNCmBgYA0KDQrjgJAqKlEqKuOAkeS7peS4iuWTquS4gOeoruWBmuazleacn+acm+WgsemFrOavlOi8g+mrmO+8nw0K5YWo5LiN5YGa5q+U6LyD5aW977yM5pyf5pyb5YC85pivLTE5ODEw77yM5q+U5YWo5YGa6YKE6auYDQoNCuOAkCoqUSoq44CR5Zyo5omA5pyJ55qE5ZWG5YuZ5oOF5aKD6YO95piv6YCZ56iu54uA5rOB5ZeO77yfDQrkuI3mmK8NCg0K44CQKipRKirjgJHkvaDlj6/ku6XmqKHmk6zlh7rjgIzlhajlgZrjgI3mr5TjgIzlhajkuI3lgZrjgI3pgoTopoHlpb3nmoTni4Dms4HjgIHkuKboiInlh7rkuIDlgIvmnIPnmbznlJ/pgJnnqK7ni4Dms4HnmoTllYbli5nmg4XlooPll47vvJ8NCg0K5oqV6LOH5paw5Ym15YWs5Y+4DQrmipXos4flpLHmlZfkuI3mnIPos6DlpKrlpJrmiJDmnKwNCuaKleizh+aIkOWKn+WPr+iDveizuuW+iOWkmuWbnuS+hg0KcGF5b2ZmOg0KICAgICBbLDFdIFssMl0NClsxLF0gICAgMCAgLTEwDQpbMixdICAgIDAgIDEwMA0KDQoNCg0KYGBge3J9DQpwYXlvZmYgPSBtYXRyaXgoYygwLDAsLTEwLDEwMCksMiwyKSANCg0KY3V0b2ZmID0gc2VxKDAuMDIsIDAuNzI5NiwgMC4wMSkNCg0KDQoNCg0KcmVzdWx0ID0gc2FwcGx5KGN1dG9mZiwgZnVuY3Rpb24ocCkgc3VtKHRhYmxlKHkscHJlZD5wKSpwYXlvZmYpICkNCg0KDQppID0gMQ0KcGFyKGNleD0wLjkpDQpwbG90KGN1dG9mZiwgcmVzdWx0LCB0eXBlPSdsJywgY29sPSdjeWFuJywgbHdkPTIsIG1haW49c3ByaW50ZigNCiAgIk9wdG9tYWwgRXhwZWN0ZWQgUmVzdWx0OiAkJWQgQCAlLjJmIixyZXN1bHRbaV0sY3V0b2ZmW2ldKSkNCmFibGluZSh2PXNlcSgwLDEsMC4wNSksaD1zZXEoLTIzMDAwLC0xNzAwMCw1MDApLGNvbD0nbGlnaHRncmF5JyxsdHk9MykNCmFibGluZSh2PTAuMDIsY29sPSdyZWQnKQ0KYGBgDQoNCjxicj4NCg0KIyMjIyBGNjogU2ltdWxhdGlvbg0KYGBge3IgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9Nn0NCmxpYnJhcnkobWFuaXB1bGF0ZSkNCnAwID0gcGFyKG1mcm93PWMoMiwxKSxjZXg9MC44KQ0KbWFuaXB1bGF0ZSh7DQogIFkwID0gLTIyMDAwOyBZMSA9IC0xMjAwMA0KICBteCA9IG1hdHJpeChjKHRydWVfbmVnLCBmYWxzZV9uZWcsIGZhbHNlX3BvcywgdHJ1ZV9wb3MpLDIsMikgDQogIGN4ID0gc2VxKDAuMDIsIDAuNjQsIDAuMDEpDQogIHJ4ID0gc2FwcGx5KGN4LCBmdW5jdGlvbihwKSBzdW0odGFibGUoeSwgcHJlZD5wKSpteCkgKQ0KICBpID0gd2hpY2gubWF4KHJ4KQ0KICBwbG90KGN4LCByeCwgdHlwZT0nbCcsY29sPSdjeWFuJyxsd2Q9MixtYWluPXNwcmludGYoDQogICAgIk9wdG9tYWwgRXhwZWN0ZWQgUmVzdWx0OiAkJWQgQCAlLjJmLCBUOiVkIixyeFtpXSxjeFtpXSxzdW0ocHJlZD5jeFtpXSkpLA0KICAgIHlsaW09YyhZMCxZMSkpDQogIGFibGluZSh2PWN4W2ldLGNvbD0ncmVkJykNCiAgYWJsaW5lKHY9c2VxKDAsMSwwLjEpLGg9c2VxKFkwLFkxLDIwMDApLGNvbD0nbGlnaHRncmF5JyxsdHk9MykNCiAgRFBQKHByZWQsIHksIDEsIGI9c2VxKDAsMSwwLjAyKSkNCiAgYWJsaW5lKHY9Y3hbaV0sY29sPSdyZWQnKQ0KICB9LA0KICB0cnVlX25lZyAgPSBzbGlkZXIoLTEwMCwxMDAsMCxzdGVwPTUpLA0KICBmYWxzZV9uZWcgPSBzbGlkZXIoLTEwMCwxMDAsLTEwMCxzdGVwPTUpLA0KICBmYWxzZV9wb3MgPSBzbGlkZXIoLTEwMCwxMDAsLTEwLHN0ZXA9NSksDQogIHRydWVfcG9zICA9IHNsaWRlcigtMTAwLDEwMCwtNjAsc3RlcD01KQ0KICApIA0KcGFyKHAwKQ0KYGBgDQoNCg0K44CQKipRKirjgJHmnInkupTnqK7miJDmnKzliIbliKXngrogYCQ1LCAkMTAsICQxNSwgJDIwLCAkMzBgIOeahOiXpe+8jOWug+WAkeWIhuWIpeWPr+S7peWwh+miqOmaquaIkOacrOW+niBgJDEwMGAg6ZmN5L2O5YiwIGAkNzAsICQ2MCwgJDUwLCAkNDAsICQyNWDvvIzlk6rkuIDnqK7ol6XnmoTmnJ/mnJvmlYjnm4rmmK/mnIDlpKfnmoTlkaLvvJ8NCmBgYHtyfQ0KcGF5b2ZmID0gbWF0cml4KGMoMCwtMTAwLC01LC03MCksMiwyKSANCmN1dG9mZiA9IHNlcSgwLjAyLCAwLjcyOTYsIDAuMDEpDQoNCnRhYmxlKHkscHJlZD4wLjAyKSANCnN1bSh0YWJsZSh5LHByZWQ+MC4wMikqcGF5b2ZmKQ0Kc3VtKHRhYmxlKHkscHJlZD4wLjAzKSpwYXlvZmYpDQpjbGFzcyhyZXN1bHQpDQoNCg0KcmVzdWx0ID0gc2FwcGx5KGN1dG9mZiwgZnVuY3Rpb24ocCkgc3VtKHRhYmxlKHkscHJlZD5wKSpwYXlvZmYpICkNCg0KDQppID0gd2hpY2gubWF4KHJlc3VsdCkNCnBhcihjZXg9MC45KQ0KcGxvdChjdXRvZmYsIHJlc3VsdCwgdHlwZT0nbCcsIGNvbD0nY3lhbicsIGx3ZD0yLCBtYWluPXNwcmludGYoDQogICJPcHRvbWFsIEV4cGVjdGVkIFJlc3VsdDogJCVkIEAgJS4yZiIscmVzdWx0W2ldLGN1dG9mZltpXSkpDQphYmxpbmUodj1zZXEoMCwxLDAuMDUpLGg9c2VxKC0yMzAwMCwtMTcwMDAsNTAwKSxjb2w9J2xpZ2h0Z3JheScsbHR5PTMpDQphYmxpbmUodj0wLjE0LGNvbD0ncmVkJykNCg0KYGBgDQoNCg0KYGBge3J9DQoNCiMgKDUsNzApIC0xNzQxNSwgMC4xNA0KIyAoMTAsNjApIC0xNzUwMCwgMC4yDQojICgxNSw1MCkgLTE3NDUwLDAuMg0KIyAoMjAsNDApIC0xNzQwMCwgMC4yDQojICgzMCwyNSkgLTE3NjU1LCAwLjIyDQoj56ys5LqU56iuDQpgYGANCg0KPGJyPg0KDQotIC0gLQ0KIyMjIOOAkEfjgJHliIbmnpDmtYHnqIvvvJros4fmlpnjgIHmqKHlnovjgIHpoJDmuKzjgIHmsbrnrZYNCg0KIVtGaWd1cmUgNCAtIOizh+aWmeOAgeaooeWei+OAgemgkOa4rOOAgeaxuuetll0ocmVzL2Zsb3cuanBnKQ0KDQoNCg0KPGJyPjxicj48YnI+PGJyPjxicj4NCg0KDQoNCg0KDQoNCg0KDQoNCg==