# Terms
#
# root node
# decision node 
# branch 
# leaf node ( = terminal node )
#
# entropy , information gain
# cost / rule 

1. Decision Tree

# 적용시 퍼포먼스가 좋지 않을 수 있는 케이스 
# a large number of nominal features with many levels or it has a large number of numeric features 인 경우, 
# very large number of decisions and an overly complex tree 생성 => overfit 될 가능성도 커짐 
1.1 Divide & Conquer
# Recursive Partitioning ( Split )
#  - Decision trees are built using a heuristic called recursive partitioning
#  - subsets are sufciently homogenous, or another stopping criterion has been met 할 때까지 recursive 하게 subsetting 을 수행 
#
# Stopping Criterion
#  • All (or nearly all) of the examples at the node have the same class
#  • There are no remaining features to distinguish among the examples
#  • The tree has grown to a predefined size limit ( avoid overfitting )
1.2 Decision Tree >> C5.0
# ref : http://www.rulequest.com/
#
# C5.0 algorithm has become the industry standard to produce decision trees
# overfit / underfit 이 쉽게 될 수 있고, training data bias 에 취약하다는 단점이 있지만 
# ROI 가 잘나오는 기법이라는 장점도 있고, ensemble 로 단점이 개선되는 측면도 있다.
1.3 Choosing the Best Split
# Decision Tree 는 결국 Best Split 을 찾는 것이 목표이다. 
#
# Purity 
#  - Best Split 인지를 판단하는 지표중 하나, 얼마나 homogenous 하게 나눴냐 하는 척도 
#  - C5.0 측정방식
#    - entropy 
#       - quantifies the randomness ( or disorder ) 
#       - is measured in bits ( twoClass = (0 or 1), n-Classes = (0, log2(n)) )
#       - 알고리즘에서는, 각 split 시도시 homogeneity 의 Change 를 entroy 량의 변화값으로 측정한다.
#       - InformationGain(F) = Entropy(S1) - Entropy(S2)
#       - Minimize the total entropy ( = sum of each (weight * parition's entropy ) )
#    - Gini index, Chi-Squared statistic, Gain Ratio. 
#
# ex> two classes , 60% & 40%
-0.60 * log2(0.60) - 0.40 * log2(0.40)
[1] 0.9709506
curve(-x * log2(x) - (1-x)*log2(1-x), col = "red", ylab = "enthropy", lwd = 4)

1.4 Pruning the decision tree
# Pruning ( 가지치기 / merge )
#  - full tree : leaf node 가 순도 100% 인 tree ( entropy = 0 ) => overfitting 위험도 증가 
#  - pruning   : 적당한 수준에서 terminal node 를 결합 
#
# 방식
#  - ealry stopping ( pre-pruning ) , post-pruning => 주로 post-pruning ( 일단 다 키운다음에 merge )
1.5 C5.0 training with Boost
library(C50)
credit  <- read.csv("/Users/CA/Machine-Learning-with-R-datasets/credit.csv")
credit$default <- factor(credit$default)
fit.c50 <- C5.0( x = subset(credit, select = -c(default)),
                 y = credit$default, 
                 # an integer specifying the number of boosting iterations. A value of one indicates that a single model is used.
                 # C5.0 uses adaptive boosting 
                 trials = 10,  
                 control = C5.0Control())
print(fit.c50)

Call:
C5.0.default(x = subset(credit, select = -c(default)), y = credit$default, trials = 10, control = C5.0Control())

Classification Tree
Number of samples: 1000 
Number of predictors: 20 

Number of boosting iterations: 10 
Average tree size: 63.9 

Non-standard options: attempt to group attributes
# trails 로 셋팅한 10에 의해 10개의 tree 가 생성되었고, 
# 위의 summary 중 boost 항목을 통해, 10개 모델에 대한 boosting 결과를 볼 수 있으며, error 율이 매우 낮아짐을 확인할 수 있다.
fit.c50$boostResults
1.6 C5.0 with Cost Matrix
# Cost Matrix : 예측이 틀린 경우에 대해, 서로 다른 가중치를 두어, 평가하려는 경우 사용 
# ex> 스팸이 아닌 메일을 스팸이라고 할 때의 costA 와 스팸 메일을 정상이라고 할 때의 costB 를 따로 처리 ( costA >> costB )
library(C50)
credit  <- read.csv("/Users/CA/Machine-Learning-with-R-datasets/credit.csv")
credit <- transform(credit, default = ifelse(default == 1, "no", "yes"))
credit$default <- factor(credit$default)
# create cost matrix
matrix_dimensions <- list(c("no", "yes"), c("no", "yes"))
names(matrix_dimensions) <- c("predicted", "actual")
error_cost <- matrix(c(0, 1, 4, 0), nrow = 2, dimnames = matrix_dimensions)
print(error_cost)
         actual
predicted no yes
      no   0   4
      yes  1   0
fit.c50 <- C5.0( x = subset(credit, select = -c(default)),
                 y = credit$default, 
                 # an integer specifying the number of boosting iterations. A value of one indicates that a single model is used.
                 # C5.0 uses adaptive boosting 
                 trials = 10, 
                 costs = error_cost,
                 control = C5.0Control())
# summary 시, errors 와 함께 costs 가 추가된 것을 볼 수 있고, boost 로 인하여 error / cost 모두 떨어짐을 확인할 수 있다.
# summary(fit.c50)
1.7 C5.0 with Rules ( Classification Rules )
# antecedent ( 전제 )
# consequent ( 결론 )
# [antecedent] 키>185, 고향:서울, 채력등급>2 => [consequent] 청와대 헌병 
#
# Covering Algorithms
#  - 전체 학습패턴과 비어있는 규칙 집합으로부터 시작하여 최대한 많은 학습패턴을 만족시키는 규칙들을 만들고 
#  - 규칙 집합에 넣고 만족된 학습패턴은 제거한 후 나머지 학습패턴들에 대해서 다시 규칙을 만들어 넣는 것을 
#  - 반복하여 규칙 집합을 구성
#
#  - The 1R / RIPPER Algorithm
#    -- 1R : F1 == A 면 B 다 (1) , F2 == C 면 B 다 를 각각 평가하고, 에러율이 낮은 것을 택하는 방식 ( rule 에 feature 가 하나만 사용 ) 
#    -- RIPPER : rule 에 feature 여러개를 조합하여 사용 가능 
# 
# 1R / RIPPER ( RWeka package 를 통해 셋팅 가능 )
mr  <- read.csv("/Users/CA/Machine-Learning-with-R-datasets/mushrooms.csv")
library(RWeka)
mr.1R <- OneR(type ~ ., data = mr)
print(mr.1R)     # selected feature : odor ( Rule 의 갯수 1 개 )
odor:
    a   -> e
    c   -> p
    f   -> p
    l   -> e
    m   -> p
    n   -> e
    p   -> p
    s   -> p
    y   -> p
(8004/8124 instances correct)
summary(mr.1R)   # Accuracy 는 98% 로 높지만, 120개의 독버섯을 정상으로 판단;;

=== Summary ===

Correctly Classified Instances        8004               98.5229 %
Incorrectly Classified Instances       120                1.4771 %
Kappa statistic                          0.9704
Mean absolute error                      0.0148
Root mean squared error                  0.1215
Relative absolute error                  2.958  %
Root relative squared error             24.323  %
Total Number of Instances             8124     

=== Confusion Matrix ===

    a    b   <-- classified as
 4208    0 |    a = e
  120 3796 |    b = p
mr.JRip <- JRip(type ~ ., data = mr)
print(mr.JRip)    # selected features : odor, gill_size, ....  ( Rule 의 갯수 9 개 )
JRIP rules:
===========

(odor = f) => type=p (2160.0/0.0)
(gill_size = n) and (gill_color = b) => type=p (1152.0/0.0)
(gill_size = n) and (odor = p) => type=p (256.0/0.0)
(odor = c) => type=p (192.0/0.0)
(spore_print_color = r) => type=p (72.0/0.0)
(stalk_surface_below_ring = y) and (stalk_surface_above_ring = k) => type=p (68.0/0.0)
(habitat = l) and (cap_color = w) => type=p (8.0/0.0)
(stalk_color_above_ring = y) => type=p (8.0/0.0)
 => type=e (4208.0/0.0)

Number of Rules : 9
summary(mr.JRip)  # 100% 

=== Summary ===

Correctly Classified Instances        8124              100      %
Incorrectly Classified Instances         0                0      %
Kappa statistic                          1     
Mean absolute error                      0     
Root mean squared error                  0     
Relative absolute error                  0      %
Root relative squared error              0      %
Total Number of Instances             8124     

=== Confusion Matrix ===

    a    b   <-- classified as
 4208    0 |    a = e
    0 3916 |    b = p
# useful packages 
#
# rpart
# party
# + randomForest
A. rpart
# rpart pacakge 
# model lookup 을 보면 cp 가 튜닝할 수 있는 파라미터임을 알 수 있다.
library(rpart)
modelLookup("rpart")
# grow
fit.rpart <- rpart(Kyphosis ~ Age + Number + Start, method = "class", data = kyphosis)
printcp(fit.rpart) 

Classification tree:
rpart(formula = Kyphosis ~ Age + Number + Start, data = kyphosis, 
    method = "class")

Variables actually used in tree construction:
[1] Age   Start

Root node error: 17/81 = 0.20988

n= 81 

        CP nsplit rel error  xerror    xstd
1 0.176471      0   1.00000 1.00000 0.21559
2 0.019608      1   0.82353 0.94118 0.21078
3 0.010000      4   0.76471 0.94118 0.21078
#plotcp(fit.rpart)
#summary(fit.rpart)
plot(fit.rpart, uniform = T); text(fit.rpart, use.n = T, all = T, cex = .5)
post(fit.rpart)

# prune
head(fit.rpart$cptable)
          CP nsplit rel error    xerror      xstd
1 0.17647059      0 1.0000000 1.0000000 0.2155872
2 0.01960784      1 0.8235294 0.9411765 0.2107780
3 0.01000000      4 0.7647059 0.9411765 0.2107780
fit.rpart.prune <- prune(fit.rpart, cp = fit.rpart$cptable[which.min(fit.rpart$cptable[, "xerror"]), "CP"])
plot(fit.rpart.prune, uniform = T); text(fit.rpart.prune, use.n = T, all = T, cex = .5)

B. party
library(party)
fit <- ctree(Kyphosis ~ Age + Number + Start, data=kyphosis)
fit2 <- ctree(Mileage~Price + Country + Reliability + Type,  data=na.omit(cu.summary))
par(mfrow=c(1,2))
plot(fit, main="Conditional Inference Tree for Kyphosis")

plot(fit2)

C. Random Forest
library(randomForest)
fit <- randomForest(Kyphosis ~ Age + Number + Start,   data=kyphosis)
print(fit) 

Call:
 randomForest(formula = Kyphosis ~ Age + Number + Start, data = kyphosis) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 1

        OOB estimate of  error rate: 18.52%
Confusion matrix:
        absent present class.error
absent      61       3   0.0468750
present     12       5   0.7058824
importance(fit) 
       MeanDecreaseGini
Age            8.773154
Number         5.516590
Start         10.340227
D. caret parameter tuning ( train )
# rpart 패키지에서 수행했던 부분을, caret train 을 통해 수행해본다. 
library(caret)
modelLookup("rpart")
fit.caret <- train(Kyphosis ~ Age + Number + Start,   data=kyphosis, method = "rpart")
fit.caret
CART 

81 samples
 3 predictor
 2 classes: 'absent', 'present' 

No pre-processing
Resampling: Bootstrapped (25 reps) 
Summary of sample sizes: 81, 81, 81, 81, 81, 81, ... 
Resampling results across tuning parameters:

  cp          Accuracy   Kappa    
  0.00000000  0.7726512  0.3023266
  0.01960784  0.7726512  0.3023266
  0.17647059  0.7686655  0.2646701

Accuracy was used to select the optimal model using  the largest value.
The final value used for the model was cp = 0.01960784.
# ctree 를, caret train 을 통해 수행해본다. 
library(caret)
modelLookup("ctree")
fit.ctree <- train(Kyphosis ~ Age + Number + Start,   data=kyphosis, method = "ctree", tuneGrid = expand.grid(mincriterion = 0.95))
fit.ctree
Conditional Inference Tree 

81 samples
 3 predictor
 2 classes: 'absent', 'present' 

No pre-processing
Resampling: Bootstrapped (25 reps) 
Summary of sample sizes: 81, 81, 81, 81, 81, 81, ... 
Resampling results:

  Accuracy   Kappa    
  0.7594273  0.2196265

Tuning parameter 'mincriterion' was held constant at a value of 0.95
varImp(fit.ctree)
ROC curve variable importance

       Importance
Start      100.00
Number      61.89
Age          0.00
# References
#
# machine learning with R, CH.5
# https://rpubs.com/chengjiun/52658
LS0tCnRpdGxlOiAiRGVjaXNpb24gVHJlZSAoIOyekeyEsSDspJEgKSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3J9CiMgVGVybXMKIwojIHJvb3Qgbm9kZQojIGRlY2lzaW9uIG5vZGUgCiMgYnJhbmNoIAojIGxlYWYgbm9kZSAoID0gdGVybWluYWwgbm9kZSApCiMKIyBlbnRyb3B5ICwgaW5mb3JtYXRpb24gZ2FpbgojIGNvc3QgLyBydWxlIApgYGAKCgoKIyMjIyAxLiBEZWNpc2lvbiBUcmVlCmBgYHtyfQojIOyggeyaqeyLnCDtjbztj6zrqLzsiqTqsIAg7KKL7KeAIOyViuydhCDsiJgg7J6I64qUIOy8gOydtOyKpCAKIyBhIGxhcmdlIG51bWJlciBvZiBub21pbmFsIGZlYXR1cmVzIHdpdGggbWFueSBsZXZlbHMgb3IgaXQgaGFzIGEgbGFyZ2UgbnVtYmVyIG9mIG51bWVyaWMgZmVhdHVyZXMg7J24IOqyveyasCwgCiMgdmVyeSBsYXJnZSBudW1iZXIgb2YgZGVjaXNpb25zIGFuZCBhbiBvdmVybHkgY29tcGxleCB0cmVlIOyDneyEsSA9PiBvdmVyZml0IOuQoCDqsIDriqXshLHrj4Qg7Luk7KeQIApgYGAKCiMjIyMjIyAxLjEgRGl2aWRlICYgQ29ucXVlcgpgYGB7cn0KIyBSZWN1cnNpdmUgUGFydGl0aW9uaW5nICggU3BsaXQgKQojICAtIERlY2lzaW9uIHRyZWVzIGFyZSBidWlsdCB1c2luZyBhIGhldXJpc3RpYyBjYWxsZWQgcmVjdXJzaXZlIHBhcnRpdGlvbmluZwojICAtIHN1YnNldHMgYXJlIHN1ZmNpZW50bHkgaG9tb2dlbm91cywgb3IgYW5vdGhlciBzdG9wcGluZyBjcml0ZXJpb24gaGFzIGJlZW4gbWV0IO2VoCDrlYzquYzsp4AgcmVjdXJzaXZlIO2VmOqyjCBzdWJzZXR0aW5nIOydhCDsiJjtlokgCiMKIyBTdG9wcGluZyBDcml0ZXJpb24KIyAg4oCiIEFsbCAob3IgbmVhcmx5IGFsbCkgb2YgdGhlIGV4YW1wbGVzIGF0IHRoZSBub2RlIGhhdmUgdGhlIHNhbWUgY2xhc3MKIyAg4oCiIFRoZXJlIGFyZSBubyByZW1haW5pbmcgZmVhdHVyZXMgdG8gZGlzdGluZ3Vpc2ggYW1vbmcgdGhlIGV4YW1wbGVzCiMgIOKAoiBUaGUgdHJlZSBoYXMgZ3Jvd24gdG8gYSBwcmVkZWZpbmVkIHNpemUgbGltaXQgKCBhdm9pZCBvdmVyZml0dGluZyApCmBgYAohW10oL1VzZXJzL0NBL0Rvd25sb2Fkcy9kdC5wbmcpCgojIyMjIyMgMS4yIERlY2lzaW9uIFRyZWUgPj4gQzUuMApgYGB7cn0KIyByZWYgOiBodHRwOi8vd3d3LnJ1bGVxdWVzdC5jb20vCiMKIyBDNS4wIGFsZ29yaXRobSBoYXMgYmVjb21lIHRoZSBpbmR1c3RyeSBzdGFuZGFyZCB0byBwcm9kdWNlIGRlY2lzaW9uIHRyZWVzCiMgb3ZlcmZpdCAvIHVuZGVyZml0IOydtCDsib3qsowg65CgIOyImCDsnojqs6AsIHRyYWluaW5nIGRhdGEgYmlhcyDsl5Ag7Leo7JW97ZWY64uk64qUIOuLqOygkOydtCDsnojsp4Drp4wgCiMgUk9JIOqwgCDsnpjrgpjsmKTripQg6riw67KV7J20652864qUIOyepeygkOuPhCDsnojqs6AsIGVuc2VtYmxlIOuhnCDri6jsoJDsnbQg6rCc7ISg65CY64qUIOy4oeuptOuPhCDsnojri6QuCmBgYAohW10oL1VzZXJzL0NBL0Rvd25sb2Fkcy9jNTAucG5nKQoKIyMjIyMgMS4zIENob29zaW5nIHRoZSBCZXN0IFNwbGl0CmBgYHtyfQojIERlY2lzaW9uIFRyZWUg64qUIOqysOq1rSBCZXN0IFNwbGl0IOydhCDssL7ripQg6rKD7J20IOuqqe2RnOydtOuLpC4gCiMKIyBQdXJpdHkgCiMgIC0gQmVzdCBTcGxpdCDsnbjsp4Drpbwg7YyQ64uo7ZWY64qUIOyngO2RnOykkSDtlZjrgpgsIOyWvOuniOuCmCBob21vZ2Vub3VzIO2VmOqyjCDrgpjriLTrg5Ag7ZWY64qUIOyymeuPhCAKIyAgLSBDNS4wIOy4oeygleuwqeyLnQojICAgIC0gZW50cm9weSAKIyAgICAgICAtIHF1YW50aWZpZXMgdGhlIHJhbmRvbW5lc3MgKCBvciBkaXNvcmRlciApIAojICAgICAgIC0gaXMgbWVhc3VyZWQgaW4gYml0cyAoIHR3b0NsYXNzID0gKDAgb3IgMSksIG4tQ2xhc3NlcyA9ICgwLCBsb2cyKG4pKSApCiMgICAgICAgLSDslYzqs6Drpqzsppjsl5DshJzripQsIOqwgSBzcGxpdCDsi5zrj4Tsi5wgaG9tb2dlbmVpdHkg7J2YIENoYW5nZSDrpbwgZW50cm95IOufieydmCDrs4DtmZTqsJLsnLzroZwg7Lih7KCV7ZWc64ukLgojICAgICAgIC0gSW5mb3JtYXRpb25HYWluKEYpID0gRW50cm9weShTMSkgLSBFbnRyb3B5KFMyKQojICAgICAgIC0gTWluaW1pemUgdGhlIHRvdGFsIGVudHJvcHkgKCA9IHN1bSBvZiBlYWNoICh3ZWlnaHQgKiBwYXJpdGlvbidzIGVudHJvcHkgKSApCiMgICAgLSBHaW5pIGluZGV4LCBDaGktU3F1YXJlZCBzdGF0aXN0aWMsIEdhaW4gUmF0aW8uIAojCiMgZXg+IHR3byBjbGFzc2VzICwgNjAlICYgNDAlCi0wLjYwICogbG9nMigwLjYwKSAtIDAuNDAgKiBsb2cyKDAuNDApCmN1cnZlKC14ICogbG9nMih4KSAtICgxLXgpKmxvZzIoMS14KSwgY29sID0gInJlZCIsIHlsYWIgPSAiZW50aHJvcHkiLCBsd2QgPSA0KQpgYGAKIVtdKC9Vc2Vycy9DQS9Eb3dubG9hZHMvZW50cm9weS5wbmcpCiFbXSgvVXNlcnMvQ0EvRG93bmxvYWRzL2VudHJvcHkyLnBuZykKCiMjIyMjIDEuNCBQcnVuaW5nIHRoZSBkZWNpc2lvbiB0cmVlCmBgYHtyfQojIFBydW5pbmcgKCDqsIDsp4DsuZjquLAgLyBtZXJnZSApCiMgIC0gZnVsbCB0cmVlIDogbGVhZiBub2RlIOqwgCDsiJzrj4QgMTAwJSDsnbggdHJlZSAoIGVudHJvcHkgPSAwICkgPT4gb3ZlcmZpdHRpbmcg7JyE7ZeY64+EIOymneqwgCAKIyAgLSBwcnVuaW5nICAgOiDsoIHri7ntlZwg7IiY7KSA7JeQ7IScIHRlcm1pbmFsIG5vZGUg66W8IOqysO2VqSAKIwojIOuwqeyLnQojICAtIGVhbHJ5IHN0b3BwaW5nICggcHJlLXBydW5pbmcgKSAsIHBvc3QtcHJ1bmluZyA9PiDso7zroZwgcG9zdC1wcnVuaW5nICgg7J2864uoIOuLpCDtgqTsmrTri6TsnYzsl5AgbWVyZ2UgKQpgYGAKCiMjIyMjIDEuNSBDNS4wIHRyYWluaW5nIHdpdGggQm9vc3QKYGBge3J9CmxpYnJhcnkoQzUwKQpjcmVkaXQgIDwtIHJlYWQuY3N2KCIvVXNlcnMvQ0EvTWFjaGluZS1MZWFybmluZy13aXRoLVItZGF0YXNldHMvY3JlZGl0LmNzdiIpCmNyZWRpdCRkZWZhdWx0IDwtIGZhY3RvcihjcmVkaXQkZGVmYXVsdCkKCmZpdC5jNTAgPC0gQzUuMCggeCA9IHN1YnNldChjcmVkaXQsIHNlbGVjdCA9IC1jKGRlZmF1bHQpKSwKICAgICAgICAgICAgICAgICB5ID0gY3JlZGl0JGRlZmF1bHQsIAogICAgICAgICAgICAgICAgICMgYW4gaW50ZWdlciBzcGVjaWZ5aW5nIHRoZSBudW1iZXIgb2YgYm9vc3RpbmcgaXRlcmF0aW9ucy4gQSB2YWx1ZSBvZiBvbmUgaW5kaWNhdGVzIHRoYXQgYSBzaW5nbGUgbW9kZWwgaXMgdXNlZC4KICAgICAgICAgICAgICAgICAjIEM1LjAgdXNlcyBhZGFwdGl2ZSBib29zdGluZyAKICAgICAgICAgICAgICAgICB0cmlhbHMgPSAxMCwgIAogICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBDNS4wQ29udHJvbCgpKQpwcmludChmaXQuYzUwKQpgYGAKIVtdKC9Vc2Vycy9DQS9Eb3dubG9hZHMvc3VtbWFyeS5wbmcpCgpgYGB7cn0KIyB0cmFpbHMg66GcIOyFi+2Mhe2VnCAxMOyXkCDsnZjtlbQgMTDqsJzsnZggdHJlZSDqsIAg7IOd7ISx65CY7JeI6rOgLCAKIyDsnITsnZggc3VtbWFyeSDspJEgYm9vc3Qg7ZWt66qp7J2EIO2Gte2VtCwgMTDqsJwg66qo64247JeQIOuMgO2VnCBib29zdGluZyDqsrDqs7zrpbwg67O8IOyImCDsnojsnLzrqbAsIGVycm9yIOycqOydtCDrp6TsmrAg64Ku7JWE7KeQ7J2EIO2ZleyduO2VoCDsiJgg7J6I64ukLgpmaXQuYzUwJGJvb3N0UmVzdWx0cwpgYGAKCiMjIyMjIDEuNiBDNS4wIHdpdGggQ29zdCBNYXRyaXgKYGBge3J9CiMgQ29zdCBNYXRyaXggOiDsmIjsuKHsnbQg7YuA66awIOqyveyasOyXkCDrjIDtlbQsIOyEnOuhnCDri6Trpbgg6rCA7KSR7LmY66W8IOuRkOyWtCwg7Y+J6rCA7ZWY66Ck64qUIOqyveyasCDsgqzsmqkgCiMgZXg+IOyKpO2MuOydtCDslYTri4wg66mU7J287J2EIOyKpO2MuOydtOudvOqzoCDtlaAg65WM7J2YIGNvc3RBIOyZgCDsiqTtjLgg66mU7J287J2EIOygleyDgeydtOudvOqzoCDtlaAg65WM7J2YIGNvc3RCIOulvCDrlLDroZwg7LKY66asICggY29zdEEgPj4gY29zdEIgKQpsaWJyYXJ5KEM1MCkKY3JlZGl0ICA8LSByZWFkLmNzdigiL1VzZXJzL0NBL01hY2hpbmUtTGVhcm5pbmctd2l0aC1SLWRhdGFzZXRzL2NyZWRpdC5jc3YiKQpjcmVkaXQgPC0gdHJhbnNmb3JtKGNyZWRpdCwgZGVmYXVsdCA9IGlmZWxzZShkZWZhdWx0ID09IDEsICJubyIsICJ5ZXMiKSkKY3JlZGl0JGRlZmF1bHQgPC0gZmFjdG9yKGNyZWRpdCRkZWZhdWx0KQoKIyBjcmVhdGUgY29zdCBtYXRyaXgKbWF0cml4X2RpbWVuc2lvbnMgPC0gbGlzdChjKCJubyIsICJ5ZXMiKSwgYygibm8iLCAieWVzIikpCm5hbWVzKG1hdHJpeF9kaW1lbnNpb25zKSA8LSBjKCJwcmVkaWN0ZWQiLCAiYWN0dWFsIikKZXJyb3JfY29zdCA8LSBtYXRyaXgoYygwLCAxLCA0LCAwKSwgbnJvdyA9IDIsIGRpbW5hbWVzID0gbWF0cml4X2RpbWVuc2lvbnMpCnByaW50KGVycm9yX2Nvc3QpCmBgYAoKYGBge3J9CmZpdC5jNTAgPC0gQzUuMCggeCA9IHN1YnNldChjcmVkaXQsIHNlbGVjdCA9IC1jKGRlZmF1bHQpKSwKICAgICAgICAgICAgICAgICB5ID0gY3JlZGl0JGRlZmF1bHQsIAogICAgICAgICAgICAgICAgICMgYW4gaW50ZWdlciBzcGVjaWZ5aW5nIHRoZSBudW1iZXIgb2YgYm9vc3RpbmcgaXRlcmF0aW9ucy4gQSB2YWx1ZSBvZiBvbmUgaW5kaWNhdGVzIHRoYXQgYSBzaW5nbGUgbW9kZWwgaXMgdXNlZC4KICAgICAgICAgICAgICAgICAjIEM1LjAgdXNlcyBhZGFwdGl2ZSBib29zdGluZyAKICAgICAgICAgICAgICAgICB0cmlhbHMgPSAxMCwgCiAgICAgICAgICAgICAgICAgY29zdHMgPSBlcnJvcl9jb3N0LAogICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBDNS4wQ29udHJvbCgpKQoKIyBzdW1tYXJ5IOyLnCwgZXJyb3JzIOyZgCDtlajqu5ggY29zdHMg6rCAIOy2lOqwgOuQnCDqsoPsnYQg67O8IOyImCDsnojqs6AsIGJvb3N0IOuhnCDsnbjtlZjsl6wgZXJyb3IgLyBjb3N0IOuqqOuRkCDrlqjslrTsp5DsnYQg7ZmV7J247ZWgIOyImCDsnojri6QuCiMgc3VtbWFyeShmaXQuYzUwKQpgYGAKIVtdKC9Vc2Vycy9DQS9Eb3dubG9hZHMvc3VtbWFyeTIucG5nKQoKCiMjIyMjIDEuNyBDNS4wIHdpdGggUnVsZXMgKCBDbGFzc2lmaWNhdGlvbiBSdWxlcyApCmBgYHtyfQojIGFudGVjZWRlbnQgKCDsoITsoJwgKQojIGNvbnNlcXVlbnQgKCDqsrDroaAgKQojIFthbnRlY2VkZW50XSDtgqQ+MTg1LCDqs6DtlqU67ISc7Jq4LCDssYTroKXrk7HquIk+MiA9PiBbY29uc2VxdWVudF0g7LKt7JmA64yAIO2XjOuzkSAKIwojIENvdmVyaW5nIEFsZ29yaXRobXMKIyAgLSDsoITssrQg7ZWZ7Iq17Yyo7YS06rO8IOu5hOyWtOyeiOuKlCDqt5zsuZkg7KeR7ZWp7Jy866Gc67aA7YSwIOyLnOyeke2VmOyXrCDstZzrjIDtlZwg66eO7J2AIO2VmeyKte2MqO2EtOydhCDrp4zsobHsi5ztgqTripQg6rec7LmZ65Ok7J2EIOunjOuTpOqzoCAKIyAgLSDqt5zsuZkg7KeR7ZWp7JeQIOuEo+qzoCDrp4zsobHrkJwg7ZWZ7Iq17Yyo7YS07J2AIOygnOqxsO2VnCDtm4Qg64KY66i47KeAIO2VmeyKte2MqO2EtOuTpOyXkCDrjIDtlbTshJwg64uk7IucIOq3nOy5meydhCDrp4zrk6TslrQg64Sj64qUIOqyg+ydhCAKIyAgLSDrsJjrs7XtlZjsl6wg6rec7LmZIOynke2VqeydhCDqtazshLEKIwojICAtIFRoZSAxUiAvIFJJUFBFUiBBbGdvcml0aG0KIyAgICAtLSAxUiA6IEYxID09IEEg66m0IEIg64ukICgxKSAsIEYyID09IEMg66m0IEIg64ukIOulvCDqsIHqsIEg7Y+J6rCA7ZWY6rOgLCDsl5Drn6zsnKjsnbQg64Ku7J2AIOqyg+ydhCDtg53tlZjripQg67Cp7IudICggcnVsZSDsl5AgZmVhdHVyZSDqsIAg7ZWY64KY66eMIOyCrOyaqSApIAojICAgIC0tIFJJUFBFUiA6IHJ1bGUg7JeQIGZlYXR1cmUg7Jes65+s6rCc66W8IOyhsO2Vqe2VmOyXrCDsgqzsmqkg6rCA64qlIAojIAojIDFSIC8gUklQUEVSICggUldla2EgcGFja2FnZSDrpbwg7Ya17ZW0IOyFi+2MhSDqsIDriqUgKQpgYGAKIVtdKC9Vc2Vycy9DQS9Eb3dubG9hZHMvMXIucG5nKQoKCmBgYHtyfQptciAgPC0gcmVhZC5jc3YoIi9Vc2Vycy9DQS9NYWNoaW5lLUxlYXJuaW5nLXdpdGgtUi1kYXRhc2V0cy9tdXNocm9vbXMuY3N2IikKCmxpYnJhcnkoUldla2EpCm1yLjFSIDwtIE9uZVIodHlwZSB+IC4sIGRhdGEgPSBtcikKCnByaW50KG1yLjFSKSAgICAgIyBzZWxlY3RlZCBmZWF0dXJlIDogb2RvciAoIFJ1bGUg7J2YIOqwr+yImCAxIOqwnCApCnN1bW1hcnkobXIuMVIpICAgIyBBY2N1cmFjeSDripQgOTglIOuhnCDrhpLsp4Drp4wsIDEyMOqwnOydmCDrj4XrsoTshK/snYQg7KCV7IOB7Jy866GcIO2MkOuLqDs7CmBgYAoKYGBge3J9Cm1yLkpSaXAgPC0gSlJpcCh0eXBlIH4gLiwgZGF0YSA9IG1yKQpwcmludChtci5KUmlwKSAgICAjIHNlbGVjdGVkIGZlYXR1cmVzIDogb2RvciwgZ2lsbF9zaXplLCAuLi4uICAoIFJ1bGUg7J2YIOqwr+yImCA5IOqwnCApCnN1bW1hcnkobXIuSlJpcCkgICMgMTAwJSAKYGBgCgpgYGB7cn0KIyB1c2VmdWwgcGFja2FnZXMgCiMKIyBycGFydAojIHBhcnR5CiMgKyByYW5kb21Gb3Jlc3QKYGBgCgojIyMjIyMgQS4gcnBhcnQKYGBge3J9CiMgcnBhcnQgcGFjYWtnZSAKIyBtb2RlbCBsb29rdXAg7J2EIOuztOuptCBjcCDqsIAg7Yqc64ud7ZWgIOyImCDsnojripQg7YyM652866+47YSw7J6E7J2EIOyVjCDsiJgg7J6I64ukLgpsaWJyYXJ5KHJwYXJ0KQptb2RlbExvb2t1cCgicnBhcnQiKQpgYGAKCmBgYHtyfQojIGdyb3cKZml0LnJwYXJ0IDwtIHJwYXJ0KEt5cGhvc2lzIH4gQWdlICsgTnVtYmVyICsgU3RhcnQsIG1ldGhvZCA9ICJjbGFzcyIsIGRhdGEgPSBreXBob3NpcykKCnByaW50Y3AoZml0LnJwYXJ0KSAKI3Bsb3RjcChmaXQucnBhcnQpCiNzdW1tYXJ5KGZpdC5ycGFydCkKCnBsb3QoZml0LnJwYXJ0LCB1bmlmb3JtID0gVCk7IHRleHQoZml0LnJwYXJ0LCB1c2UubiA9IFQsIGFsbCA9IFQsIGNleCA9IC41KQpgYGAKYGBge3J9CiMgcHJ1bmUKaGVhZChmaXQucnBhcnQkY3B0YWJsZSkKZml0LnJwYXJ0LnBydW5lIDwtIHBydW5lKGZpdC5ycGFydCwgY3AgPSBmaXQucnBhcnQkY3B0YWJsZVt3aGljaC5taW4oZml0LnJwYXJ0JGNwdGFibGVbLCAieGVycm9yIl0pLCAiQ1AiXSkKcGxvdChmaXQucnBhcnQucHJ1bmUsIHVuaWZvcm0gPSBUKTsgdGV4dChmaXQucnBhcnQucHJ1bmUsIHVzZS5uID0gVCwgYWxsID0gVCwgY2V4ID0gLjUpCmBgYAoKIyMjIyMjIEIuIHBhcnR5CmBgYHtyfQpsaWJyYXJ5KHBhcnR5KQpmaXQgPC0gY3RyZWUoS3lwaG9zaXMgfiBBZ2UgKyBOdW1iZXIgKyBTdGFydCwgZGF0YT1reXBob3NpcykKZml0MiA8LSBjdHJlZShNaWxlYWdlflByaWNlICsgQ291bnRyeSArIFJlbGlhYmlsaXR5ICsgVHlwZSwgIGRhdGE9bmEub21pdChjdS5zdW1tYXJ5KSkKCnBhcihtZnJvdz1jKDEsMikpCnBsb3QoZml0LCBtYWluPSJDb25kaXRpb25hbCBJbmZlcmVuY2UgVHJlZSBmb3IgS3lwaG9zaXMiKQpwbG90KGZpdDIpCmBgYAoKIyMjIyMjIEMuIFJhbmRvbSBGb3Jlc3QKYGBge3J9CmxpYnJhcnkocmFuZG9tRm9yZXN0KQpmaXQgPC0gcmFuZG9tRm9yZXN0KEt5cGhvc2lzIH4gQWdlICsgTnVtYmVyICsgU3RhcnQsICAgZGF0YT1reXBob3NpcykKcHJpbnQoZml0KSAKaW1wb3J0YW5jZShmaXQpIApgYGAKCgojIyMjIyMgRC4gY2FyZXQgcGFyYW1ldGVyIHR1bmluZyAoIHRyYWluICkKYGBge3J9CiMgcnBhcnQg7Yyo7YKk7KeA7JeQ7IScIOyImO2Wie2WiOuNmCDrtoDrtoTsnYQsIGNhcmV0IHRyYWluIOydhCDthrXtlbQg7IiY7ZaJ7ZW067O464ukLiAKbGlicmFyeShjYXJldCkKbW9kZWxMb29rdXAoInJwYXJ0IikKZml0LmNhcmV0IDwtIHRyYWluKEt5cGhvc2lzIH4gQWdlICsgTnVtYmVyICsgU3RhcnQsICAgZGF0YT1reXBob3NpcywgbWV0aG9kID0gInJwYXJ0IikKZml0LmNhcmV0CmBgYAoKYGBge3J9CiMgY3RyZWUg66W8LCBjYXJldCB0cmFpbiDsnYQg7Ya17ZW0IOyImO2Wie2VtOuzuOuLpC4gCmxpYnJhcnkoY2FyZXQpCm1vZGVsTG9va3VwKCJjdHJlZSIpCmZpdC5jdHJlZSA8LSB0cmFpbihLeXBob3NpcyB+IEFnZSArIE51bWJlciArIFN0YXJ0LCAgIGRhdGE9a3lwaG9zaXMsIG1ldGhvZCA9ICJjdHJlZSIsIHR1bmVHcmlkID0gZXhwYW5kLmdyaWQobWluY3JpdGVyaW9uID0gMC45NSkpCmZpdC5jdHJlZQp2YXJJbXAoZml0LmN0cmVlKQpgYGAKCmBgYHtyfQojIFJlZmVyZW5jZXMKIwojIG1hY2hpbmUgbGVhcm5pbmcgd2l0aCBSLCBDSC41CiMgaHR0cHM6Ly9ycHVicy5jb20vY2hlbmdqaXVuLzUyNjU4CmBgYA==