Pengaplikasian Metode Machine Learning SVM pada pengklasifikasian Sinta Score ITS

2022-10-14

Library

Beberapa Library yang digunakan pada praktikum kali ini :

library(caret)
library(tidyverse)
library(knitr)
library(ggplot2)
library(tidyr)
library(e1071)
library(ROCR)
library(rpart)
library(UBL)
library(googlesheets4)

Dataset

gs4_deauth()
data_sinta_SVM <- read_sheet("https://docs.google.com/spreadsheets/d/1DW_JRJop1LPqmxNue7tRawadTgf914KFFe4eXansaW4/edit?usp=sharing")
## ✔ Reading from "Data Sinta ITS".
## ✔ Range 'data_sinta_417'.
data_sinta_SVM$Rumpun_Ilmu  <- as.factor(data_sinta_SVM$Rumpun_Ilmu)
data_sinta_SVM$Jenjang <- as.factor(data_sinta_SVM$Jenjang)
data_sinta_SVM$Akreditasi  <- as.factor(data_sinta_SVM$Akreditasi)
data_sinta_SVM$y  <- as.factor(data_sinta_SVM$y)
data_sinta_SVM <- as.data.frame(data_sinta_SVM)
str(data_sinta_SVM)
## 'data.frame':    981 obs. of  7 variables:
##  $ Rumpun_Ilmu              : Factor w/ 7 levels "Ekonomi","Kesehatan",..: 1 1 1 1 1 1 1 1 1 1 ...
##  $ Jenjang                  : Factor w/ 4 levels "D4","S1","S2",..: 2 2 2 2 2 2 2 2 2 2 ...
##  $ Akreditasi               : Factor w/ 3 levels "Baik","Baik Sekali",..: 2 2 2 2 2 2 2 2 2 2 ...
##  $ Jumlah_Dosen_Total       : num  14 14 14 14 14 14 14 14 14 14 ...
##  $ Jumlah_Mahasiswa         : num  488 488 488 488 488 488 488 488 488 488 ...
##  $ Rasio_Dosen_per_Mahasiswa: num  0.104 0.104 0.104 0.104 0.104 ...
##  $ y                        : Factor w/ 2 levels "0","1": 2 2 1 1 1 1 1 1 1 1 ...
set.seed(414)
in.train <- createDataPartition(as.factor(data_sinta_SVM$y), p=0.75, list=FALSE) #partisi data
data_sinta_SVM_train <- data_sinta_SVM[in.train,] #data training utk modelling
data_sinta_SVM_test<- data_sinta_SVM[-in.train,] #data testing utk evaluasi model
cat("Frekuensi Data Training/Testing")
## Frekuensi Data Training/Testing
round((table(data_sinta_SVM_train$y)), digits = 4)
## 
##   0   1 
## 560 177
round((table(data_sinta_SVM_test$y)), digits = 4)
## 
##   0   1 
## 186  58
cat("\nProporsi Data Training/Testing")
## 
## Proporsi Data Training/Testing
round(prop.table(table(data_sinta_SVM_train$y)), digits = 4)
## 
##      0      1 
## 0.7598 0.2402
round(prop.table(table(data_sinta_SVM_test$y)), digits = 4)
## 
##      0      1 
## 0.7623 0.2377

SMOTE

pada kasus ini akan digunakan SMOTE sebagai perlakuan penanganan

set.seed(414)
data_sinta_SVM_train_smote <- SmoteClassif(form = y ~., dat = data_sinta_SVM_train, C.perc = list("0"=1,"1"=sum(data_sinta_SVM_train==0)/sum(data_sinta_SVM_train==1)),dist = "HVDM")
round((table(data_sinta_SVM_train_smote$y)), digits = 4)
## 
##   0   1 
## 560 560
round(prop.table(table(data_sinta_SVM_train_smote$y)), digits = 4)
## 
##   0   1 
## 0.5 0.5

Barplot sebelum vs Sesudah Smote

Sebelum

df1 <- as.data.frame(table(data_sinta_SVM_train$y))
my_bar1 <- barplot(df1$Freq, names.arg=df1$Var1, border=F,
                  col=c("coral", "cadetblue1"),
                  las=2, ylim=c(0,600), main="Perbandingan Frekuensi Klasifikasi Sinta Score")

text(my_bar1, df1$Freq+25, df1$Freq) 

Sesudah

df2 <- as.data.frame(table(data_sinta_SVM_train_smote$y))
my_bar2 <- barplot(df2$Freq, names.arg=df2$Var1, border=F,
                  col=c("coral", "cadetblue1"),
                  las=2, ylim=c(0,600), main="Perbandingan Frekuensi Klasifikasi Sinta Score")

text(my_bar2, df2$Freq+25, df2$Freq) 

Function Performa Model

perform <- function(pred,data){
  tabel <- caret::confusionMatrix(pred, data$y, positive="1")
  result <- c(tabel$overall[1],tabel$byClass[c(1:2,11)])
  return(result)
}

Model SVM Kernel Linear

model.svm1 <- svm(y ~.,data=data_sinta_SVM_train_smote,kernel="linear", scale=TRUE)
#model.svm1
pred.svm1 <- predict(model.svm1,data_sinta_SVM_test)
tabel1 <- caret::confusionMatrix(pred.svm1, as.factor(data_sinta_SVM_test$y), positive = "1")
#tabel1

Model SVM Kernel Sigmoid

model.svm2<- svm(y ~.,data=data_sinta_SVM_train_smote,kernel="sigmoid", scale=TRUE)
#model.svm2
pred.svm2 <- predict(model.svm2,data_sinta_SVM_test)
tabel2 <- caret::confusionMatrix(pred.svm2, as.factor(data_sinta_SVM_test$y), positive = "1")
#tabel2

Model SVM Kernel Radial

model.svm3<- svm(y ~.,data=data_sinta_SVM_train_smote,kernel="radial", scale=TRUE)
#model.svm3
pred.svm3<- predict(model.svm3,data_sinta_SVM_test)
tabel3 <- caret::confusionMatrix(pred.svm3, as.factor(data_sinta_SVM_test$y), positive = "1")
#tabel3

Model SVM Kernel Polynomial

model.svm4<- svm(y ~.,data=data_sinta_SVM_train_smote,kernel="polynomial", scale=TRUE)
#model.svm4
pred.svm4<- predict(model.svm4,data_sinta_SVM_test)
tabel4 <- caret::confusionMatrix(pred.svm4, as.factor(data_sinta_SVM_test$y), positive = "1")
#tabel4

Perbandingan Model

hasil_eval <- rbind(
  c(tabel1$overall[1], tabel1$byClass[1], tabel1$byClass[2]),
  c(tabel2$overall[1], tabel2$byClass[1], tabel2$byClass[2]),
  c(tabel3$overall[1], tabel3$byClass[1], tabel3$byClass[2]),
  c(tabel4$overall[1], tabel4$byClass[1], tabel4$byClass[2]))
row.names(hasil_eval) <- 
  c("SVM Kernel Linear","SVM Kernel Sigmoid",
    "SVM Kernel Radial", "SVM Kernel Polynomial")
hasil_eval <- as.data.frame(hasil_eval)
dplyr::arrange(.data = hasil_eval, desc(Accuracy))

Tuning Hyperparameter SVM

tuningsvm <- tune(svm,y~.,data=data_sinta_SVM_train_smote,
                  ranges=list(kernel=c("radial","linear","polynomial","sigmoid")))
tuningsvm$best.model
## 
## Call:
## best.tune(METHOD = svm, train.x = y ~ ., data = data_sinta_SVM_train_smote, 
##     ranges = list(kernel = c("radial", "linear", "polynomial", "sigmoid")))
## 
## 
## Parameters:
##    SVM-Type:  C-classification 
##  SVM-Kernel:  linear 
##        cost:  1 
## 
## Number of Support Vectors:  738
#Tune SVM to find the best hyperparameters
tune_svm <- tune(svm, y~.,data=data_sinta_SVM_train_smote,
              kernel="linear", ranges=list(cost=seq(.01,.1,.01)))
print(tune_svm)
## 
## Parameter tuning of 'svm':
## 
## - sampling method: 10-fold cross validation 
## 
## - best parameters:
##  cost
##  0.04
## 
## - best performance: 0.3116071
model.svm5<- svm(y ~.,data=data_sinta_SVM_train_smote,kernel="linear", cost=0.04)
model.svm5
## 
## Call:
## svm(formula = y ~ ., data = data_sinta_SVM_train_smote, kernel = "linear", 
##     cost = 0.04)
## 
## 
## Parameters:
##    SVM-Type:  C-classification 
##  SVM-Kernel:  linear 
##        cost:  0.04 
## 
## Number of Support Vectors:  837
pred.svm5<- predict(model.svm5,data_sinta_SVM_test)
pred.svm6<- predict(model.svm5,data_sinta_SVM_train)

tabel5 <- caret::confusionMatrix(pred.svm5, as.factor(data_sinta_SVM_test$y), positive = "1")
tabel5
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0 160  28
##          1  26  30
##                                           
##                Accuracy : 0.7787          
##                  95% CI : (0.7213, 0.8292)
##     No Information Rate : 0.7623          
##     P-Value [Acc > NIR] : 0.3027          
##                                           
##                   Kappa : 0.382           
##                                           
##  Mcnemar's Test P-Value : 0.8918          
##                                           
##             Sensitivity : 0.5172          
##             Specificity : 0.8602          
##          Pos Pred Value : 0.5357          
##          Neg Pred Value : 0.8511          
##              Prevalence : 0.2377          
##          Detection Rate : 0.1230          
##    Detection Prevalence : 0.2295          
##       Balanced Accuracy : 0.6887          
##                                           
##        'Positive' Class : 1               
## 
tabel6 <- caret::confusionMatrix(pred.svm6, as.factor(data_sinta_SVM_train$y), positive = "1")
tabel6
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0 469  82
##          1  91  95
##                                          
##                Accuracy : 0.7653         
##                  95% CI : (0.733, 0.7954)
##     No Information Rate : 0.7598         
##     P-Value [Acc > NIR] : 0.384          
##                                          
##                   Kappa : 0.3678         
##                                          
##  Mcnemar's Test P-Value : 0.543          
##                                          
##             Sensitivity : 0.5367         
##             Specificity : 0.8375         
##          Pos Pred Value : 0.5108         
##          Neg Pred Value : 0.8512         
##              Prevalence : 0.2402         
##          Detection Rate : 0.1289         
##    Detection Prevalence : 0.2524         
##       Balanced Accuracy : 0.6871         
##                                          
##        'Positive' Class : 1              
## 
hasil_eval <- rbind(
  c(tabel6$overall[1], tabel6$byClass["Balanced Accuracy"]),
  c(tabel5$overall[1], tabel5$byClass["Balanced Accuracy"]))
row.names(hasil_eval) <- 
  c("SVM Linear Training", "SVM Kernel Testing")
hasil_eval <- as.data.frame(hasil_eval)
dplyr::arrange(.data = hasil_eval, desc(Accuracy))

Evaluasi Model dengan Pengulangan

perulangan <- 100
df_akurasi <- data.frame("akurasi_svm" = numeric(), "akurasi_nb" = numeric(), "akurasi_pc" = numeric())
SVM_list <- vector(mode="list", length = perulangan)

for (i in 1:perulangan){
  in.train <- createDataPartition(as.factor(data_sinta_SVM$y),p=0.75,list=F)
  data_sinta_SVM_train <- data_sinta_SVM[in.train,] 
  data_sinta_SVM_test<- data_sinta_SVM[-in.train,] 
  data_sinta_SVM_train_smote <- SmoteClassif(form = y ~., dat = data_sinta_SVM_train, C.perc = list("0"=1,"1"=sum(data_sinta_SVM_train==0)/sum(data_sinta_SVM_train==1)),dist = "HVDM")

  #Model Support Vector Machine
  model.svm4<- svm(y ~.,data=data_sinta_SVM_train_smote,kernel="linear", cost=0.04)
  pred.svm4<- predict(model.svm4,data_sinta_SVM_test)
  tabel4 <- caret::confusionMatrix(pred.svm4, as.factor(data_sinta_SVM_test$y), positive = "1")
  akurasi<-as.data.frame(tabel4$overall)
  akurasi_svm<-akurasi[1,]

  # Update table akurasi
  df_akurasi<- rbind(df_akurasi, c(akurasi_svm))
  paste0("ulangan ", i, " selesai.\n")
}

colnames(df_akurasi) = c("SVM")
df_akurasi %>%
  as_tibble() %>%
  mutate(ulangan = 1:perulangan) %>%
  pivot_longer(-ulangan) %>%
  ggplot(aes(name, value)) +
  geom_boxplot()+xlab("Metode")+ylab("Akurasi")

library("e1071")
library("DALEX")
## Welcome to DALEX (version: 2.4.3).
## Find examples and detailed introduction at: http://ema.drwhy.ai/
## 
## Attaching package: 'DALEX'
## The following object is masked from 'package:dplyr':
## 
##     explain
library("ggplot2")
set.seed(41)
model.svm1 <- svm(y == "1" ~ ., data = data_sinta_SVM_train_smote, kernel="linear", cost=0.04, type = "C-classification", probability = TRUE)
set.seed(41)
explainer_svm <- DALEX::explain(model = model.svm1, 
                                data = data_sinta_SVM_train_smote[,-7], 
                                y = data_sinta_SVM_train_smote$y=="1", 
                                label = "Support Vector Machine")
## Preparation of a new explainer is initiated
##   -> model label       :  Support Vector Machine 
##   -> data              :  1120  rows  6  cols 
##   -> target variable   :  1120  values 
##   -> predict function  :  yhat.svm  will be used (  default  )
##   -> predicted values  :  No value for predict function target column. (  default  )
##   -> model_info        :  package e1071 , ver. 1.7.13 , task classification (  default  ) 
##   -> model_info        :  Model info detected classification task but 'y' is a logical . Converted to numeric.  (  NOTE  )
##   -> predicted values  :  numerical, min =  0.2509177 , mean =  0.4986054 , max =  0.8645471  
##   -> residual function :  difference between y and yhat (  default  )
##   -> residuals         :  numerical, min =  -0.8428362 , mean =  0.00139461 , max =  0.7490823  
##   A new explainer has been created!
set.seed(41)
vip_svm <- model_parts(explainer = explainer_svm, B = 1000)
vip_svm
plot(vip_svm) +
  ggtitle("Mean variable-importance over 1000 permutations", "") 

LS0tDQp0aXRsZTogIlBlbmdhcGxpa2FzaWFuIE1ldG9kZSBNYWNoaW5lIExlYXJuaW5nIFNWTSBwYWRhIHBlbmdrbGFzaWZpa2FzaWFuIFNpbnRhIFNjb3JlIElUUyINCmRhdGU6ICIyMDIyLTEwLTE0Ig0Kb3V0cHV0Og0KICBybWRmb3JtYXRzOjpkb3duY3V0ZToNCiAgICBkb3duY3V0ZV90aGVtZTogImNoYW9zIg0KICAgIHNlbGZfY29udGFpbmVkOiB0cnVlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMw0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIHRoZW1lOiBjZXJ1bGVhbg0KICAgIGhpZ2hsaWdodDogImthdGUiDQotLS0NCg0KIVtdKFBhcmFsZWwgMV80X1R1Z2FzVVRTLmpwZykNCg0KIyBMaWJyYXJ5DQoNCkJlYmVyYXBhIExpYnJhcnkgeWFuZyBkaWd1bmFrYW4gcGFkYSBwcmFrdGlrdW0ga2FsaSBpbmkgOg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgY29sbGFwc2U9VFJVRX0NCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShlMTA3MSkNCmxpYnJhcnkoUk9DUikNCmxpYnJhcnkocnBhcnQpDQpsaWJyYXJ5KFVCTCkNCmxpYnJhcnkoZ29vZ2xlc2hlZXRzNCkNCmBgYA0KDQojIyBEYXRhc2V0DQoNCmBgYHtyfQ0KZ3M0X2RlYXV0aCgpDQpkYXRhX3NpbnRhX1NWTSA8LSByZWFkX3NoZWV0KCJodHRwczovL2RvY3MuZ29vZ2xlLmNvbS9zcHJlYWRzaGVldHMvZC8xRFdfSlJKb3AxTFBxbXhOdWU3dFJhd2FkVGdmOTE0S0ZGZTRlWGFuc2FXNC9lZGl0P3VzcD1zaGFyaW5nIikNCmRhdGFfc2ludGFfU1ZNJFJ1bXB1bl9JbG11ICA8LSBhcy5mYWN0b3IoZGF0YV9zaW50YV9TVk0kUnVtcHVuX0lsbXUpDQpkYXRhX3NpbnRhX1NWTSRKZW5qYW5nIDwtIGFzLmZhY3RvcihkYXRhX3NpbnRhX1NWTSRKZW5qYW5nKQ0KZGF0YV9zaW50YV9TVk0kQWtyZWRpdGFzaSAgPC0gYXMuZmFjdG9yKGRhdGFfc2ludGFfU1ZNJEFrcmVkaXRhc2kpDQpkYXRhX3NpbnRhX1NWTSR5ICA8LSBhcy5mYWN0b3IoZGF0YV9zaW50YV9TVk0keSkNCmRhdGFfc2ludGFfU1ZNIDwtIGFzLmRhdGEuZnJhbWUoZGF0YV9zaW50YV9TVk0pDQpzdHIoZGF0YV9zaW50YV9TVk0pDQpgYGANCg0KYGBge3J9DQpzZXQuc2VlZCg0MTQpDQppbi50cmFpbiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGFzLmZhY3RvcihkYXRhX3NpbnRhX1NWTSR5KSwgcD0wLjc1LCBsaXN0PUZBTFNFKSAjcGFydGlzaSBkYXRhDQpkYXRhX3NpbnRhX1NWTV90cmFpbiA8LSBkYXRhX3NpbnRhX1NWTVtpbi50cmFpbixdICNkYXRhIHRyYWluaW5nIHV0ayBtb2RlbGxpbmcNCmRhdGFfc2ludGFfU1ZNX3Rlc3Q8LSBkYXRhX3NpbnRhX1NWTVstaW4udHJhaW4sXSAjZGF0YSB0ZXN0aW5nIHV0ayBldmFsdWFzaSBtb2RlbA0KY2F0KCJGcmVrdWVuc2kgRGF0YSBUcmFpbmluZy9UZXN0aW5nIikNCnJvdW5kKCh0YWJsZShkYXRhX3NpbnRhX1NWTV90cmFpbiR5KSksIGRpZ2l0cyA9IDQpDQpyb3VuZCgodGFibGUoZGF0YV9zaW50YV9TVk1fdGVzdCR5KSksIGRpZ2l0cyA9IDQpDQpjYXQoIlxuUHJvcG9yc2kgRGF0YSBUcmFpbmluZy9UZXN0aW5nIikNCnJvdW5kKHByb3AudGFibGUodGFibGUoZGF0YV9zaW50YV9TVk1fdHJhaW4keSkpLCBkaWdpdHMgPSA0KQ0Kcm91bmQocHJvcC50YWJsZSh0YWJsZShkYXRhX3NpbnRhX1NWTV90ZXN0JHkpKSwgZGlnaXRzID0gNCkNCmBgYA0KDQojIyBTTU9URQ0KDQpwYWRhIGthc3VzIGluaSBha2FuIGRpZ3VuYWthbiBTTU9URSBzZWJhZ2FpIHBlcmxha3VhbiBwZW5hbmdhbmFuDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0Kc2V0LnNlZWQoNDE0KQ0KZGF0YV9zaW50YV9TVk1fdHJhaW5fc21vdGUgPC0gU21vdGVDbGFzc2lmKGZvcm0gPSB5IH4uLCBkYXQgPSBkYXRhX3NpbnRhX1NWTV90cmFpbiwgQy5wZXJjID0gbGlzdCgiMCI9MSwiMSI9c3VtKGRhdGFfc2ludGFfU1ZNX3RyYWluPT0wKS9zdW0oZGF0YV9zaW50YV9TVk1fdHJhaW49PTEpKSxkaXN0ID0gIkhWRE0iKQ0Kcm91bmQoKHRhYmxlKGRhdGFfc2ludGFfU1ZNX3RyYWluX3Ntb3RlJHkpKSwgZGlnaXRzID0gNCkNCnJvdW5kKHByb3AudGFibGUodGFibGUoZGF0YV9zaW50YV9TVk1fdHJhaW5fc21vdGUkeSkpLCBkaWdpdHMgPSA0KQ0KYGBgDQoNCiMgQmFycGxvdCBzZWJlbHVtIHZzIFNlc3VkYWggYFNtb3RlYA0KDQojIyBTZWJlbHVtDQoNCmBgYHtyfQ0KZGYxIDwtIGFzLmRhdGEuZnJhbWUodGFibGUoZGF0YV9zaW50YV9TVk1fdHJhaW4keSkpDQpteV9iYXIxIDwtIGJhcnBsb3QoZGYxJEZyZXEsIG5hbWVzLmFyZz1kZjEkVmFyMSwgYm9yZGVyPUYsDQogICAgICAgICAgICAgICAgICBjb2w9YygiY29yYWwiLCAiY2FkZXRibHVlMSIpLA0KICAgICAgICAgICAgICAgICAgbGFzPTIsIHlsaW09YygwLDYwMCksIG1haW49IlBlcmJhbmRpbmdhbiBGcmVrdWVuc2kgS2xhc2lmaWthc2kgU2ludGEgU2NvcmUiKQ0KDQp0ZXh0KG15X2JhcjEsIGRmMSRGcmVxKzI1LCBkZjEkRnJlcSkgDQpgYGANCg0KIyMgU2VzdWRhaA0KDQpgYGB7cn0NCmRmMiA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGRhdGFfc2ludGFfU1ZNX3RyYWluX3Ntb3RlJHkpKQ0KbXlfYmFyMiA8LSBiYXJwbG90KGRmMiRGcmVxLCBuYW1lcy5hcmc9ZGYyJFZhcjEsIGJvcmRlcj1GLA0KICAgICAgICAgICAgICAgICAgY29sPWMoImNvcmFsIiwgImNhZGV0Ymx1ZTEiKSwNCiAgICAgICAgICAgICAgICAgIGxhcz0yLCB5bGltPWMoMCw2MDApLCBtYWluPSJQZXJiYW5kaW5nYW4gRnJla3VlbnNpIEtsYXNpZmlrYXNpIFNpbnRhIFNjb3JlIikNCg0KdGV4dChteV9iYXIyLCBkZjIkRnJlcSsyNSwgZGYyJEZyZXEpIA0KYGBgDQoNCiMgRnVuY3Rpb24gUGVyZm9ybWEgTW9kZWwNCg0KYGBge3J9DQpwZXJmb3JtIDwtIGZ1bmN0aW9uKHByZWQsZGF0YSl7DQogIHRhYmVsIDwtIGNhcmV0Ojpjb25mdXNpb25NYXRyaXgocHJlZCwgZGF0YSR5LCBwb3NpdGl2ZT0iMSIpDQogIHJlc3VsdCA8LSBjKHRhYmVsJG92ZXJhbGxbMV0sdGFiZWwkYnlDbGFzc1tjKDE6MiwxMSldKQ0KICByZXR1cm4ocmVzdWx0KQ0KfQ0KYGBgDQoNCiMjIE1vZGVsIFNWTSBLZXJuZWwgYExpbmVhcmANCg0KYGBge3J9DQptb2RlbC5zdm0xIDwtIHN2bSh5IH4uLGRhdGE9ZGF0YV9zaW50YV9TVk1fdHJhaW5fc21vdGUsa2VybmVsPSJsaW5lYXIiLCBzY2FsZT1UUlVFKQ0KI21vZGVsLnN2bTENCnByZWQuc3ZtMSA8LSBwcmVkaWN0KG1vZGVsLnN2bTEsZGF0YV9zaW50YV9TVk1fdGVzdCkNCnRhYmVsMSA8LSBjYXJldDo6Y29uZnVzaW9uTWF0cml4KHByZWQuc3ZtMSwgYXMuZmFjdG9yKGRhdGFfc2ludGFfU1ZNX3Rlc3QkeSksIHBvc2l0aXZlID0gIjEiKQ0KI3RhYmVsMQ0KYGBgDQoNCiMjIE1vZGVsIFNWTSBLZXJuZWwgYFNpZ21vaWRgDQoNCmBgYHtyfQ0KbW9kZWwuc3ZtMjwtIHN2bSh5IH4uLGRhdGE9ZGF0YV9zaW50YV9TVk1fdHJhaW5fc21vdGUsa2VybmVsPSJzaWdtb2lkIiwgc2NhbGU9VFJVRSkNCiNtb2RlbC5zdm0yDQpwcmVkLnN2bTIgPC0gcHJlZGljdChtb2RlbC5zdm0yLGRhdGFfc2ludGFfU1ZNX3Rlc3QpDQp0YWJlbDIgPC0gY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeChwcmVkLnN2bTIsIGFzLmZhY3RvcihkYXRhX3NpbnRhX1NWTV90ZXN0JHkpLCBwb3NpdGl2ZSA9ICIxIikNCiN0YWJlbDINCmBgYA0KDQojIyBNb2RlbCBTVk0gS2VybmVsIGBSYWRpYWxgDQoNCmBgYHtyfQ0KbW9kZWwuc3ZtMzwtIHN2bSh5IH4uLGRhdGE9ZGF0YV9zaW50YV9TVk1fdHJhaW5fc21vdGUsa2VybmVsPSJyYWRpYWwiLCBzY2FsZT1UUlVFKQ0KI21vZGVsLnN2bTMNCnByZWQuc3ZtMzwtIHByZWRpY3QobW9kZWwuc3ZtMyxkYXRhX3NpbnRhX1NWTV90ZXN0KQ0KdGFiZWwzIDwtIGNhcmV0Ojpjb25mdXNpb25NYXRyaXgocHJlZC5zdm0zLCBhcy5mYWN0b3IoZGF0YV9zaW50YV9TVk1fdGVzdCR5KSwgcG9zaXRpdmUgPSAiMSIpDQojdGFiZWwzDQpgYGANCg0KIyMgTW9kZWwgU1ZNIEtlcm5lbCBgUG9seW5vbWlhbGANCg0KYGBge3J9DQptb2RlbC5zdm00PC0gc3ZtKHkgfi4sZGF0YT1kYXRhX3NpbnRhX1NWTV90cmFpbl9zbW90ZSxrZXJuZWw9InBvbHlub21pYWwiLCBzY2FsZT1UUlVFKQ0KI21vZGVsLnN2bTQNCnByZWQuc3ZtNDwtIHByZWRpY3QobW9kZWwuc3ZtNCxkYXRhX3NpbnRhX1NWTV90ZXN0KQ0KdGFiZWw0IDwtIGNhcmV0Ojpjb25mdXNpb25NYXRyaXgocHJlZC5zdm00LCBhcy5mYWN0b3IoZGF0YV9zaW50YV9TVk1fdGVzdCR5KSwgcG9zaXRpdmUgPSAiMSIpDQojdGFiZWw0DQpgYGANCg0KIyMgUGVyYmFuZGluZ2FuIE1vZGVsDQoNCmBgYHtyfQ0KaGFzaWxfZXZhbCA8LSByYmluZCgNCiAgYyh0YWJlbDEkb3ZlcmFsbFsxXSwgdGFiZWwxJGJ5Q2xhc3NbMV0sIHRhYmVsMSRieUNsYXNzWzJdKSwNCiAgYyh0YWJlbDIkb3ZlcmFsbFsxXSwgdGFiZWwyJGJ5Q2xhc3NbMV0sIHRhYmVsMiRieUNsYXNzWzJdKSwNCiAgYyh0YWJlbDMkb3ZlcmFsbFsxXSwgdGFiZWwzJGJ5Q2xhc3NbMV0sIHRhYmVsMyRieUNsYXNzWzJdKSwNCiAgYyh0YWJlbDQkb3ZlcmFsbFsxXSwgdGFiZWw0JGJ5Q2xhc3NbMV0sIHRhYmVsNCRieUNsYXNzWzJdKSkNCnJvdy5uYW1lcyhoYXNpbF9ldmFsKSA8LSANCiAgYygiU1ZNIEtlcm5lbCBMaW5lYXIiLCJTVk0gS2VybmVsIFNpZ21vaWQiLA0KICAgICJTVk0gS2VybmVsIFJhZGlhbCIsICJTVk0gS2VybmVsIFBvbHlub21pYWwiKQ0KaGFzaWxfZXZhbCA8LSBhcy5kYXRhLmZyYW1lKGhhc2lsX2V2YWwpDQpkcGx5cjo6YXJyYW5nZSguZGF0YSA9IGhhc2lsX2V2YWwsIGRlc2MoQWNjdXJhY3kpKQ0KDQpgYGANCg0KIyBUdW5pbmcgSHlwZXJwYXJhbWV0ZXIgU1ZNDQoNCmBgYHtyfQ0KdHVuaW5nc3ZtIDwtIHR1bmUoc3ZtLHl+LixkYXRhPWRhdGFfc2ludGFfU1ZNX3RyYWluX3Ntb3RlLA0KICAgICAgICAgICAgICAgICAgcmFuZ2VzPWxpc3Qoa2VybmVsPWMoInJhZGlhbCIsImxpbmVhciIsInBvbHlub21pYWwiLCJzaWdtb2lkIikpKQ0KdHVuaW5nc3ZtJGJlc3QubW9kZWwNCmBgYA0KDQpgYGB7cn0NCiNUdW5lIFNWTSB0byBmaW5kIHRoZSBiZXN0IGh5cGVycGFyYW1ldGVycw0KdHVuZV9zdm0gPC0gdHVuZShzdm0sIHl+LixkYXRhPWRhdGFfc2ludGFfU1ZNX3RyYWluX3Ntb3RlLA0KICAgICAgICAgICAgICBrZXJuZWw9ImxpbmVhciIsIHJhbmdlcz1saXN0KGNvc3Q9c2VxKC4wMSwuMSwuMDEpKSkNCnByaW50KHR1bmVfc3ZtKQ0KYGBgDQoNCmBgYHtyfQ0KbW9kZWwuc3ZtNTwtIHN2bSh5IH4uLGRhdGE9ZGF0YV9zaW50YV9TVk1fdHJhaW5fc21vdGUsa2VybmVsPSJsaW5lYXIiLCBjb3N0PTAuMDQpDQptb2RlbC5zdm01DQpgYGANCg0KYGBge3J9DQpwcmVkLnN2bTU8LSBwcmVkaWN0KG1vZGVsLnN2bTUsZGF0YV9zaW50YV9TVk1fdGVzdCkNCnByZWQuc3ZtNjwtIHByZWRpY3QobW9kZWwuc3ZtNSxkYXRhX3NpbnRhX1NWTV90cmFpbikNCg0KdGFiZWw1IDwtIGNhcmV0Ojpjb25mdXNpb25NYXRyaXgocHJlZC5zdm01LCBhcy5mYWN0b3IoZGF0YV9zaW50YV9TVk1fdGVzdCR5KSwgcG9zaXRpdmUgPSAiMSIpDQp0YWJlbDUNCnRhYmVsNiA8LSBjYXJldDo6Y29uZnVzaW9uTWF0cml4KHByZWQuc3ZtNiwgYXMuZmFjdG9yKGRhdGFfc2ludGFfU1ZNX3RyYWluJHkpLCBwb3NpdGl2ZSA9ICIxIikNCnRhYmVsNg0KYGBgDQoNCmBgYHtyfQ0KaGFzaWxfZXZhbCA8LSByYmluZCgNCiAgYyh0YWJlbDYkb3ZlcmFsbFsxXSwgdGFiZWw2JGJ5Q2xhc3NbIkJhbGFuY2VkIEFjY3VyYWN5Il0pLA0KICBjKHRhYmVsNSRvdmVyYWxsWzFdLCB0YWJlbDUkYnlDbGFzc1siQmFsYW5jZWQgQWNjdXJhY3kiXSkpDQpyb3cubmFtZXMoaGFzaWxfZXZhbCkgPC0gDQogIGMoIlNWTSBMaW5lYXIgVHJhaW5pbmciLCAiU1ZNIEtlcm5lbCBUZXN0aW5nIikNCmhhc2lsX2V2YWwgPC0gYXMuZGF0YS5mcmFtZShoYXNpbF9ldmFsKQ0KZHBseXI6OmFycmFuZ2UoLmRhdGEgPSBoYXNpbF9ldmFsLCBkZXNjKEFjY3VyYWN5KSkNCg0KYGBgDQoNCiMgRXZhbHVhc2kgTW9kZWwgZGVuZ2FuIFBlbmd1bGFuZ2FuDQoNCmBgYHtyfQ0KcGVydWxhbmdhbiA8LSAxMDANCmRmX2FrdXJhc2kgPC0gZGF0YS5mcmFtZSgiYWt1cmFzaV9zdm0iID0gbnVtZXJpYygpLCAiYWt1cmFzaV9uYiIgPSBudW1lcmljKCksICJha3VyYXNpX3BjIiA9IG51bWVyaWMoKSkNClNWTV9saXN0IDwtIHZlY3Rvcihtb2RlPSJsaXN0IiwgbGVuZ3RoID0gcGVydWxhbmdhbikNCg0KZm9yIChpIGluIDE6cGVydWxhbmdhbil7DQogIGluLnRyYWluIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oYXMuZmFjdG9yKGRhdGFfc2ludGFfU1ZNJHkpLHA9MC43NSxsaXN0PUYpDQogIGRhdGFfc2ludGFfU1ZNX3RyYWluIDwtIGRhdGFfc2ludGFfU1ZNW2luLnRyYWluLF0gDQogIGRhdGFfc2ludGFfU1ZNX3Rlc3Q8LSBkYXRhX3NpbnRhX1NWTVstaW4udHJhaW4sXSANCiAgZGF0YV9zaW50YV9TVk1fdHJhaW5fc21vdGUgPC0gU21vdGVDbGFzc2lmKGZvcm0gPSB5IH4uLCBkYXQgPSBkYXRhX3NpbnRhX1NWTV90cmFpbiwgQy5wZXJjID0gbGlzdCgiMCI9MSwiMSI9c3VtKGRhdGFfc2ludGFfU1ZNX3RyYWluPT0wKS9zdW0oZGF0YV9zaW50YV9TVk1fdHJhaW49PTEpKSxkaXN0ID0gIkhWRE0iKQ0KDQogICNNb2RlbCBTdXBwb3J0IFZlY3RvciBNYWNoaW5lDQogIG1vZGVsLnN2bTQ8LSBzdm0oeSB+LixkYXRhPWRhdGFfc2ludGFfU1ZNX3RyYWluX3Ntb3RlLGtlcm5lbD0ibGluZWFyIiwgY29zdD0wLjA0KQ0KICBwcmVkLnN2bTQ8LSBwcmVkaWN0KG1vZGVsLnN2bTQsZGF0YV9zaW50YV9TVk1fdGVzdCkNCiAgdGFiZWw0IDwtIGNhcmV0Ojpjb25mdXNpb25NYXRyaXgocHJlZC5zdm00LCBhcy5mYWN0b3IoZGF0YV9zaW50YV9TVk1fdGVzdCR5KSwgcG9zaXRpdmUgPSAiMSIpDQogIGFrdXJhc2k8LWFzLmRhdGEuZnJhbWUodGFiZWw0JG92ZXJhbGwpDQogIGFrdXJhc2lfc3ZtPC1ha3VyYXNpWzEsXQ0KDQogICMgVXBkYXRlIHRhYmxlIGFrdXJhc2kNCiAgZGZfYWt1cmFzaTwtIHJiaW5kKGRmX2FrdXJhc2ksIGMoYWt1cmFzaV9zdm0pKQ0KICBwYXN0ZTAoInVsYW5nYW4gIiwgaSwgIiBzZWxlc2FpLlxuIikNCn0NCg0KY29sbmFtZXMoZGZfYWt1cmFzaSkgPSBjKCJTVk0iKQ0KZGZfYWt1cmFzaSAlPiUNCiAgYXNfdGliYmxlKCkgJT4lDQogIG11dGF0ZSh1bGFuZ2FuID0gMTpwZXJ1bGFuZ2FuKSAlPiUNCiAgcGl2b3RfbG9uZ2VyKC11bGFuZ2FuKSAlPiUNCiAgZ2dwbG90KGFlcyhuYW1lLCB2YWx1ZSkpICsNCiAgZ2VvbV9ib3hwbG90KCkreGxhYigiTWV0b2RlIikreWxhYigiQWt1cmFzaSIpDQogIA0KYGBgDQoNCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KCJlMTA3MSIpDQpsaWJyYXJ5KCJEQUxFWCIpDQpsaWJyYXJ5KCJnZ3Bsb3QyIikNCnNldC5zZWVkKDQxKQ0KbW9kZWwuc3ZtMSA8LSBzdm0oeSA9PSAiMSIgfiAuLCBkYXRhID0gZGF0YV9zaW50YV9TVk1fdHJhaW5fc21vdGUsIGtlcm5lbD0ibGluZWFyIiwgY29zdD0wLjA0LCB0eXBlID0gIkMtY2xhc3NpZmljYXRpb24iLCBwcm9iYWJpbGl0eSA9IFRSVUUpDQpzZXQuc2VlZCg0MSkNCmV4cGxhaW5lcl9zdm0gPC0gREFMRVg6OmV4cGxhaW4obW9kZWwgPSBtb2RlbC5zdm0xLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFfc2ludGFfU1ZNX3RyYWluX3Ntb3RlWywtN10sIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gZGF0YV9zaW50YV9TVk1fdHJhaW5fc21vdGUkeT09IjEiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSAiU3VwcG9ydCBWZWN0b3IgTWFjaGluZSIpDQpzZXQuc2VlZCg0MSkNCnZpcF9zdm0gPC0gbW9kZWxfcGFydHMoZXhwbGFpbmVyID0gZXhwbGFpbmVyX3N2bSwgQiA9IDEwMDApDQp2aXBfc3ZtDQpwbG90KHZpcF9zdm0pICsNCiAgZ2d0aXRsZSgiTWVhbiB2YXJpYWJsZS1pbXBvcnRhbmNlIG92ZXIgMTAwMCBwZXJtdXRhdGlvbnMiLCAiIikgDQpgYGANCg==