PRELIMINARIES

Cleaning the RStudio environment

rm(list = ls()) # clean global environment
cat("\014")  # clean the console

Default directory

# CHANGE TO THE CORRESPONDING WORKING DIRECTORY
knitr::opts_knit$set(root.dir = '/home/harpo/Dropbox/ongoing-work/git-repos/inta-vine-quality/')
#knitr::opts_knit$set(root.dir = '/home/rodralez/hostdir/jobs/demand-planning/')

EXPLORATION

Loading Dataset

#vines_dataset<-readr::read_csv("datasets/vine_train_newnames.csv", col_types =cols())
#vines_dataset
vines_dataset<-readr::read_csv("rawdata/2022/vine_raw.csv",col_types = cols(
  Temporada = col_character(),
  ID = col_double(),
  ubi_Finca = col_character(),
  ubi_x = col_double(),
  ubi_y = col_double(),
  `ubi_Altura s.n.m.` = col_double(),
  ubi_Zona = col_character(),
  ubi_Distrito = col_character(),
  `uva_L hollejo` = col_double(),
  uva_a_hollejo = col_double(),
  uva_b_hollejo = col_double(),
  uva_h_hollejo = col_double(),
  uva_C_hollejo = col_double(),
  `uva_FT(mg/ghollejo)` = col_double(),
  `uva_FT(mg/gbaya)` = col_double(),
  `uva_FT(mg/baya)` = col_double(),
  `uva_TAN(mg/ghollejo)` = col_double(),
  `uva_TAN(mg/gbaya)` = col_double(),
  `uva_TAN(mg/baya)` = col_double(),
  `uva_ANT(mg/ghollejo)` = col_double(),
  `uva_ANT(mg/gbaya)` = col_double(),
  `uva_ANT(mg/baya)` = col_double(),
  uva_Acti_antirr_hollejo = col_double(),
  `Uva_Semillas/baya` = col_double(),
  `uva_Peso semillas/baya` = col_double(),
  uva_Brix = col_double(),
  uva_pH_mosto = col_double(),
  `uva_Acidez Mosto` = col_double(),
  `uva_ NPA_Enz(mg/L)` = col_double(),
  `uva_ Potasio_Enz(mg/L)` = col_double(),
  `uva_ NPA_Foss(mg/L)` = col_double(),
  `uva_ Potasio_Foss(mg/L)` = col_double(),
  planta_Peso_racimo = col_double(),
  planta_Peso_de_baya = col_double(),
  `planta_Bayas/racimo` = col_double(),
  planta_Ravaz = col_double(),
  `planta_Long Cordon` = col_double(),
  `planta_Nro de Racimos/m` = col_double(),
  `planta_ Peso de poda/m` = col_double(),
  `planta_Dist. hileras` = col_double(),
  `planta_Dist. plantas` = col_double(),
  `planta_Plantas/ha` = col_double(),
  `planta_Rnto/ha` = col_double(),
  planta_ndvi = col_double(),
  suelo_Aspecto = col_double(),
  `suelo_Vol. Sedimentacion (%)` = col_double(),
  suelo_Textura = col_character(),
  `suelo_Indice fondo Valle` = col_double(),
  suelo_Pendiente = col_double(),
  `eco_Estacion datos clima` = col_character(),
  `eco_GDA historico` = col_double(),
  `eco_Temp. Mínima °C` = col_double(),
  `eco_Temp. Media °C` = col_double(),
  `eco_Temp. Máxima °C` = col_double(),
  `eco_Amplitud Térmica °C` = col_double(),
  `eco_Precipitaciones (mm) Temp 2020` = col_double(),
  `eco_Grados Días Acum. Temp` = col_double(),
  `eco_Evapotranspiración mm Temp` = col_double(),
  `eco_Días con Temp.>35°C Temp` = col_double(),
  `eco_Radiacíon W/m2 Temp` = col_double(),
  `eco_Velocidad Media Viento km/h Temp` = col_double(),
  `eco_I. de Fresco Nocturno Temp` = col_double(),
  `eco_Temp. Media Marzo °C Temp` = col_double(),
  `eco_Ampl. Térmica Marzo °C Temp` = col_double(),
  `eco_Horas de Frío Temp` = col_double(),
  Calidad = col_character()
)
)
#spec(vines_dataset)
#vines_dataset <- vines_dataset %>% filter(Temporada == 2020)
vines_dataset %>% group_by(Temporada) %>% count()

Training dataset dimension 56 x 211

Removing unnecesary data

#removed_numeric_var<-c("ubi_x","ubi_y","ID")
removed_numeric_var<-c("ID","ubi_x","ubi_y")




vines_dataset <- vines_dataset %>% select(-all_of(removed_numeric_var))

Removing columns with too many missing values

missing_vals<-vines_dataset  %>% group_by(Temporada) %>% select_if(function(x) any(is.na(x)))  %>%
  summarise_each(funs(sum(is.na(.)))) %>% reshape2::melt() %>% filter(Temporada == "2020" & value == 87) %>% select(variable)
Using Temporada as id variables
missing_vals<-rbind(missing_vals, "eco_Horas de Frío Temp") %>% unlist() %>% unname

vines_dataset<-vines_dataset %>% select(-all_of(missing_vals))

Train and Test split:

set.seed(7)
test_dataset  <- vines_dataset %>% sample_frac(0.2)
train_dataset <-dplyr::setdiff(vines_dataset, test_dataset)
vines_dataset<-train_dataset
vines_dataset %>% group_by(Temporada) %>% count()

Basic Distribution Information

Categorical Variables

vines_dataset_factors<-vines_dataset %>% select_if(~class(.) == 'character')
names(vines_dataset_factors) %>% as.data.frame()
 skimr::skim(vines_dataset_factors %>% select(-Calidad))# %>% knitr::kable() %>% kable_styling(font_size = 9)
── Data Summary ────────────────────────
                           Values                      
Name                       vines_dataset_factors %>%...
Number of rows             211                         
Number of columns          6                           
_______________________                                
Column type frequency:                                 
  character                6                           
________________________                               
Group variables            None                        

Numerical Variables

vines_dataset_numeric<-vines_dataset %>% select_if(~class(.) == 'numeric')
names(vines_dataset_numeric) %>% as.data.frame()
skimr::skim(vines_dataset_numeric) %>% arrange(desc(n_missing)) #%>% knitr::kable() %>% kable_styling(font_size = 9)
── Data Summary ────────────────────────
                           Values               
Name                       vines_dataset_numeric
Number of rows             211                  
Number of columns          49                   
_______________________                         
Column type frequency:                          
  numeric                  49                   
________________________                        
Group variables            None                 

Class Distribution

vines_dataset %>% group_by(Calidad) %>% summarise(total=n()) %>%
  ggplot()+
  geom_col(aes(x=Calidad,y=total,fill=Calidad))+
  ggdark::dark_theme_bw()

ANALISYS NUMERICAL VARIABLES

Dealing Missing Data

res<-VIM::aggr(vines_dataset_numeric, 
          combined = FALSE , 
          numbers = TRUE, 
          sortCombs = TRUE, 
          sortVars = TRUE, 
          labels=names(vines_dataset_numeric),
          cex.axis=.4, 
          varheight = FALSE,
          cex.numbers=0.8,
          cex.lab=0.8,
          prop = TRUE)

 Variables sorted by number of missings: 

Missing values imputation

vines_dataset_numeric_imputed <- kNN(vines_dataset_numeric)
vines_dataset_numeric<-vines_dataset_numeric_imputed %>% select(-ends_with("_imp"))
#vines_dataset_numeric %>% select(-NAME)

Correlation Matrix

Checking Correlation with Class

vines_dataset_numeric_wcalnum <- vines_dataset_numeric %>% 
  tibble::add_column(Calidad = as.factor(vines_dataset$Calidad)) %>% 
   tibble::add_column(ubi_Zona = as.factor(vines_dataset$ubi_Zona)) %>% 
  mutate(Calidad = as.numeric(Calidad),
         ubi_Zona = as.numeric(ubi_Zona))

library(d3heatmap)
vines_dataset_numeric_cor_matrix<-cor(vines_dataset_numeric_wcalnum ,method="spearman")

vines_dataset_numeric_cor_matrix %>% as.data.frame() %>% tibble::rownames_to_column("class") %>% 
  filter(class=="Calidad") %>% 
  reshape2::melt() %>% select(variable, value) %>%
  arrange(desc(abs(value))) %>%
  ggplot()+
  geom_col(aes(x=variable,y=value,fill=variable))+
  ggdark::dark_theme_bw()
Using class as id variables

  
#heatmap(postop_data_cor_matrix)
d3heatmap(vines_dataset_numeric_cor_matrix ,colors = "Blues",cexRow = 0.8, cexCol = 0.8)
NA

Matrix considering all variables


library(d3heatmap)
vines_dataset_numeric_cor_matrix<-cor(vines_dataset_numeric ,method="spearman")
#heatmap(postop_data_cor_matrix)
d3heatmap(vines_dataset_numeric_cor_matrix ,colors = "Blues",cexRow = 0.8, cexCol = 0.8)

PCA Elipses for clases

library(FactoMineR)
#vines_dataset_numeric %>% select(-highlyCorrelated_var) %>% tibble::add_column(Calidad=vines_dataset$Calidad)
res_pca = PCA(vines_dataset_numeric  %>% 
                tibble::add_column(Calidad=vines_dataset$Calidad)
              , scale.unit=TRUE, 
              ncp=6, 
              graph=F,
              quali.sup=50, #colid for Calidad
              )

plot(res_pca,choix="ind",habillage=50)
par(mfcol=c(1,2))

plot(res_pca,choix="var",habillage="none",invisible = "ind") # para las variables

plotellipses(res_pca, invisible="ind",xlim=c(-10,10),ylim=c(-6,6))

List of highly correlated variables

highlyCorrelated <- caret::findCorrelation(vines_dataset_numeric_cor_matrix, cutoff=0.9, verbose = F)
highlyCorrelated_var<-vines_dataset_numeric_cor_matrix[,highlyCorrelated] %>% as.data.frame() %>% names()
highlyCorrelated_var
[1] "uva_C_hollejo"              "uva_FT(mg/ghollejo)"        "eco_Grados Días Acum. Temp" "uva_ANT(mg/baya)"           "uva_ANT(mg/ghollejo)"      
[6] "uva_TAN(mg/gbaya)"          "uva_TAN(mg/ghollejo)"      

Correlation Matrix with highly correlated removed

#highlyCorrelated_var %>% length()
#vines_dataset_numeric_cor_matrix[-highlyCorrelated,-highlyCorrelated]  %>% nrow()
d3heatmap(vines_dataset_numeric_cor_matrix[-highlyCorrelated,-highlyCorrelated] ,colors = "Blues",cexRow = 0.8, cexCol = 0.8)
NA

Convert class to numeric

#vines_dataset %>% mutate()
#vines_dataset$Calidad %>% as.numeric(as.factor(vines_dataset$Calidad))

FEATURE SELECTION

Bootstrap resampling

100 new datasets used for evaluating feature selection algorithms

vines_dataset_numeric_reduced<-vines_dataset_numeric  %>% 
  select(-all_of(highlyCorrelated_var))  %>% 
  #select(names(var_importance[1:11])) %>%
  tibble::add_column(Calidad=as.factor(vines_dataset$Calidad))

resamples<-rsample::bootstraps(vines_dataset_numeric_reduced,strata= Calidad,times = 100  )
num_of_feat<-20

BORUTA for feature selection

vines_dataset_bootstrap<-rsample::analysis(resamples$splits[[sample(1:100,1)]])
val_dataset  <- vines_dataset_bootstrap   %>% group_by(Calidad) %>% sample_n(2) %>% ungroup()
train_dataset <-dplyr::setdiff(vines_dataset_numeric_reduced,val_dataset)

var_importance_boruta<-Boruta(Calidad ~ . ,data=train_dataset)

var_importance_boruta<-attStats(var_importance_boruta) %>% filter(decision=='Confirmed') %>% select(meanImp) %>% arrange(desc(meanImp)) %>% top_n(num_of_feat)
Selecting by meanImp
var_importance_boruta<-var_importance_boruta %>% add_rownames('variable')
var_importance_boruta_final<-apply(var_importance_boruta$variable %>% t() ,2, function(x) stringr::str_replace_all(x, pattern="`",replacement = "")) %>% as.data.frame()
names(var_importance_boruta_final)<-"variable"
var_importance_boruta_final

CART Features selection ad-hoc using importance metric

var_importance<-c()
for (i in  1:100){
vines_dataset_bootstrap<-rsample::analysis(resamples$splits[[i]])
val_dataset  <- vines_dataset_bootstrap   %>% group_by(Calidad) %>% sample_n(2) %>% ungroup()
train_dataset <-dplyr::setdiff(vines_dataset_numeric_reduced,val_dataset)

tree<-rpart::rpart(Calidad~.,
                   data=train_dataset,
                   control = rpart.control(minsplit = 10),)
var_importance<- c(var_importance,tree$variable.importance[1:num_of_feat])
#rpart.plot(tree,type=1,
#           extra=101, box.palette="GnBu",
#           branch.lty=3, shadow.col="gray", nn=TRUE
#        )
}
var_importance_cart_final<-data.frame(variable=names(var_importance),value=var_importance) %>% group_by(variable) %>% summarise(n=n()) %>% arrange(desc(n)) %>% top_n(20)
Selecting by n
var_importance_cart_final %>% as.data.frame()

RANDOM FORESTS feature importance

vines_dataset_bootstrap<-rsample::analysis(resamples$splits[[sample(1:100,1)]])
val_dataset  <- vines_dataset_bootstrap   %>% group_by(Calidad) %>% sample_n(2) %>% ungroup()
train_dataset <-dplyr::setdiff(vines_dataset_numeric_reduced,val_dataset)

rf_names<-apply(names(train_dataset) %>% t() ,2, function(x) stringr::str_replace_all(x, pattern=" ",replacement = "_"))
rf_names<-apply(rf_names %>% t() ,2, function(x) stringr::str_replace_all(x, pattern="[/()%°>]+",replacement = "_"))

train_dataset_rf<-train_dataset
names(train_dataset_rf)<-rf_names

rf<-randomForest::randomForest(Calidad ~ .,data=train_dataset_rf,importance=TRUE)
var_importance_rf_final<-randomForest::importance(rf) %>% as.data.frame() %>% arrange(desc(MeanDecreaseGini)) %>% add_rownames('variable')  %>% top_n(num_of_feat) %>% select(variable)
Selecting by MeanDecreaseGini
var_importance_rf_final<- data.frame(variable=names(train_dataset[,which(names(train_dataset_rf) %in% var_importance_rf_final$variable)]) )
var_importance_rf_final

GLMNET Feature Selection

library(glmnet)
vines_dataset_bootstrap<-rsample::analysis(resamples$splits[[sample(1:100,1)]])
val_dataset  <- vines_dataset_bootstrap   %>% group_by(Calidad) %>% sample_n(2) %>% ungroup()
train_dataset <-dplyr::setdiff(vines_dataset_numeric_reduced,val_dataset)
glmfit<-cv.glmnet(x = train_dataset %>% select(-Calidad) %>% as.matrix(),
       y = train_dataset$Calidad %>% as.factor(),
       family = "multinomial")
#coef(glmfit,s = 'lambda.min')

'%ni%'<-Negate('%in%')
c<-coef(glmfit,s='lambda.min',exact=TRUE)


var_importance_glmnet<-purrr::map(c, function(x) {
  inds <- which(x != 0)
  variables <- row.names(x)[inds]
  variables <- variables[variables %ni% '(Intercept)']
}) 

#do.call(rbind,var_importance_glmnet) %>% as.data.frame()


var_importance_glmnet_final<-var_importance_glmnet %>% unlist() %>% unique() %>% as.data.frame()
names(var_importance_glmnet_final)<-"variable"

var_importance_glmnet_final

Selected variables per class

var_importance_glmnet
$A0
 [1] "uva_L hollejo"                        "uva_a_hollejo"                        "uva_ANT(mg/gbaya)"                   
 [4] "uva_Acti_antirr_hollejo"              "uva_Acidez Mosto"                     "planta_Long Cordon"                  
 [7] "planta_Nro de Racimos/m"              "planta_ Peso de poda/m"               "planta_Dist. hileras"                
[10] "planta_Rnto/ha"                       "suelo_Aspecto"                        "suelo_Vol. Sedimentacion (%)"        
[13] "eco_GDA historico"                    "eco_Temp. Mínima °C"                  "eco_Evapotranspiración mm Temp"      
[16] "eco_Velocidad Media Viento km/h Temp" "eco_Ampl. Térmica Marzo °C Temp"     

$A1
 [1] "ubi_Altura s.n.m."                  "uva_a_hollejo"                      "uva_b_hollejo"                      "uva_FT(mg/gbaya)"                  
 [5] "uva_FT(mg/baya)"                    "uva_Brix"                           "uva_Acidez Mosto"                   "planta_Long Cordon"                
 [9] "planta_Dist. hileras"               "planta_Plantas/ha"                  "planta_ndvi"                        "suelo_Vol. Sedimentacion (%)"      
[13] "suelo_Indice fondo Valle"           "eco_Precipitaciones (mm) Temp 2020" "eco_Evapotranspiración mm Temp"     "eco_I. de Fresco Nocturno Temp"    
[17] "eco_Ampl. Térmica Marzo °C Temp"   

$A2
 [1] "ubi_Altura s.n.m."                  "uva_L hollejo"                      "uva_h_hollejo"                      "uva_FT(mg/gbaya)"                  
 [5] "uva_FT(mg/baya)"                    "uva_ANT(mg/gbaya)"                  "uva_Acti_antirr_hollejo"            "uva_Brix"                          
 [9] "uva_pH_mosto"                       "uva_Acidez Mosto"                   "planta_Peso_racimo"                 "planta_Long Cordon"                
[13] "planta_ Peso de poda/m"             "planta_Plantas/ha"                  "suelo_Aspecto"                      "eco_Temp. Máxima °C"               
[17] "eco_Precipitaciones (mm) Temp 2020" "eco_I. de Fresco Nocturno Temp"    

$A3
 [1] "uva_L hollejo"                        "uva_a_hollejo"                        "uva_TAN(mg/baya)"                    
 [4] "uva_ANT(mg/gbaya)"                    "uva_pH_mosto"                         "uva_Acidez Mosto"                    
 [7] "planta_Bayas/racimo"                  "planta_Ravaz"                         "planta_Nro de Racimos/m"             
[10] "planta_Dist. hileras"                 "planta_Dist. plantas"                 "planta_Rnto/ha"                      
[13] "planta_ndvi"                          "suelo_Aspecto"                        "suelo_Vol. Sedimentacion (%)"        
[16] "suelo_Indice fondo Valle"             "eco_GDA historico"                    "eco_Temp. Mínima °C"                 
[19] "eco_Temp. Media °C"                   "eco_Temp. Máxima °C"                  "eco_Velocidad Media Viento km/h Temp"
[22] "eco_I. de Fresco Nocturno Temp"      

$A5
 [1] "ubi_Altura s.n.m."                    "uva_a_hollejo"                        "uva_TAN(mg/baya)"                    
 [4] "uva_ANT(mg/gbaya)"                    "uva_Acti_antirr_hollejo"              "uva_Acidez Mosto"                    
 [7] "planta_Peso_racimo"                   "planta_ Peso de poda/m"               "planta_Dist. hileras"                
[10] "planta_Dist. plantas"                 "planta_ndvi"                          "suelo_Vol. Sedimentacion (%)"        
[13] "suelo_Indice fondo Valle"             "eco_Temp. Mínima °C"                  "eco_Temp. Máxima °C"                 
[16] "eco_Radiacíon W/m2 Temp"              "eco_Velocidad Media Viento km/h Temp"

$B
[1] "ubi_Altura s.n.m."            "planta_Nro de Racimos/m"      "planta_Plantas/ha"            "planta_Rnto/ha"               "suelo_Vol. Sedimentacion (%)"
[6] "suelo_Indice fondo Valle"     "eco_Radiacíon W/m2 Temp"     

$C
 [1] "uva_pH_mosto"                 "planta_Long Cordon"           "planta_Nro de Racimos/m"      "planta_ Peso de poda/m"      
 [5] "planta_Dist. hileras"         "planta_Rnto/ha"               "suelo_Aspecto"                "suelo_Vol. Sedimentacion (%)"
 [9] "suelo_Pendiente"              "eco_GDA historico"            "eco_Días con Temp.>35°C Temp"

FINAL VARIABLE SELECTION

selected_variables<-var_importance_boruta_final


#selected_variables<-data.frame(variable=c("planta_Peso_racimo",        
#"uva_Acidez Mosto",            
#"uva_TAN(mg/baya)",
#"planta_Bayas/racimo",
#"eco_Amplitud Térmica °C"))

Selected variables range


vines_dataset_numeric_reduced %>% select(selected_variables$variable) %>% reshape2::melt() %>%
  ggplot()+
  facet_wrap(~variable,scales = 'free',ncol = 10)+
  geom_boxplot(aes(x=variable,y=value,fill=variable),color='gray')+
  ggdark::dark_theme_bw()+
    theme(axis.text.x = element_text(angle = 45, hjust = 1))+
  theme(legend.position="none",
        axis.title.x=element_blank(),
        axis.text.x=element_blank(),
        axis.ticks.x=element_blank())+
  theme( strip.text = element_text(size = 6))
No id variables; using all as measure variables

NA

PCA Ellipses for classes with selected variables


calidad_index<-vines_dataset_numeric_reduced  %>% 
               select(selected_variables$variable,Calidad)
calidad_index<- which(colnames(calidad_index)=="Calidad")
library(FactoMineR)
res_pca = PCA(vines_dataset_numeric_reduced  %>% 
               select(selected_variables$variable,Calidad)
              , scale.unit=TRUE, 
              ncp=6, 
              graph=F,
              quali.sup=calidad_index, #colid for Calidad
              )

plot(res_pca,choix="ind",habillage=calidad_index)

#par(mfcol=c(1,2))
plot(res_pca,choix="var",habillage="none",invisible = "ind") # para las variables

plotellipses(res_pca, invisible="ind",xlim=c(-6,6),ylim=c(-6,6))

NA
NA

CART

vines_dataset_bootstrap<-rsample::analysis(resamples$splits[[sample(1:100,1)]])
val_dataset  <- vines_dataset_bootstrap   %>% group_by(Calidad) %>% sample_n(2) %>% ungroup()
train_dataset <-dplyr::setdiff(vines_dataset_numeric_reduced,val_dataset)

tree<-rpart::rpart(Calidad~.,
                   data=train_dataset %>% 
                     select(selected_variables$variable,Calidad),
                   control = rpart.control(minsplit = 5),)
rpart.plot(tree,type=1,
           extra=101, box.palette="GnBu",
           branch.lty=3, shadow.col="gray", nn=TRUE
        )

predictions<-predict(tree,val_dataset,type = 'class')
caret::confusionMatrix(val_dataset$Calidad %>% as.factor(),predictions)
Confusion Matrix and Statistics

          Reference
Prediction A0 A1 A2 A3 A5 B C
        A0  1  0  1  0  0 0 0
        A1  0  1  0  1  0 0 0
        A2  0  0  2  0  0 0 0
        A3  0  0  0  2  0 0 0
        A5  0  0  0  0  2 0 0
        B   0  0  0  0  0 2 0
        C   0  0  0  0  0 0 2

Overall Statistics
                                          
               Accuracy : 0.8571          
                 95% CI : (0.5719, 0.9822)
    No Information Rate : 0.2143          
    P-Value [Acc > NIR] : 5.491e-07       
                                          
                  Kappa : 0.8333          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: A0 Class: A1 Class: A2 Class: A3 Class: A5 Class: B Class: C
Sensitivity            1.00000   1.00000    0.6667    0.6667    1.0000   1.0000   1.0000
Specificity            0.92308   0.92308    1.0000    1.0000    1.0000   1.0000   1.0000
Pos Pred Value         0.50000   0.50000    1.0000    1.0000    1.0000   1.0000   1.0000
Neg Pred Value         1.00000   1.00000    0.9167    0.9167    1.0000   1.0000   1.0000
Prevalence             0.07143   0.07143    0.2143    0.2143    0.1429   0.1429   0.1429
Detection Rate         0.07143   0.07143    0.1429    0.1429    0.1429   0.1429   0.1429
Detection Prevalence   0.14286   0.14286    0.1429    0.1429    0.1429   0.1429   0.1429
Balanced Accuracy      0.96154   0.96154    0.8333    0.8333    1.0000   1.0000   1.0000
#printcp(tree)
tree$variable.importance %>% as.data.frame()

MODEL EVALUATION

CART

Cross Validation 3x10

library(caret)
library(doMC)
registerDoMC(cores = 4)
ctrl_fast <- trainControl(
  method = "repeatedcv",
  repeats = 3,
  number = 10,
  returnResamp = 'final',
  savePredictions = 'final',
  verboseIter = F,
  classProbs = TRUE,
  allowParallel = T
)

#rf_grid <-  expand.grid(.mtry = c(5))
cartFit <- caret::train(
  x = vines_dataset_numeric_reduced %>% 
                     select(selected_variables$variable) %>% na.omit(),
  y = vines_dataset_numeric_reduced %>% 
                     select(Calidad) %>% unlist() %>% as.factor(),
  method = "rpart",
  tuneLength=10,
  #tuneGrid = rf_grid,
  #verbose = 2,
  trControl = ctrl_fast,
  #ntree = 200
)

cartFit$results %>%
  ggplot(aes(x = cp, y = Accuracy)) +
  geom_point(color = 'red') +
  geom_errorbar(
    aes(ymin = Accuracy - AccuracySD, ymax = Accuracy + AccuracySD),
    width = .02,
    color = 'yellow'
  ) +
  ggdark::dark_theme_bw() +
  labs(title="CART: Mean and Standard deviation after hyper-parameter (cp) tuning")+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))


cartFit
CART 

211 samples
 20 predictor
  7 classes: 'A0', 'A1', 'A2', 'A3', 'A5', 'B', 'C' 

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 191, 190, 189, 190, 188, 190, ... 
Resampling results across tuning parameters:

  cp           Accuracy   Kappa     
  0.000000000  0.7988500  0.75317271
  0.006451613  0.7988500  0.75317271
  0.025806452  0.7705709  0.71894250
  0.058064516  0.6778088  0.60511602
  0.067741935  0.6497616  0.56997075
  0.077419355  0.6276413  0.54186397
  0.083870968  0.5884946  0.49203823
  0.116129032  0.4896998  0.35191515
  0.141935484  0.4315757  0.25981722
  0.193548387  0.2919904  0.04469957

Accuracy was used to select the optimal model using the largest value.
The final value used for the model was cp = 0.006451613.

Learning Curves


cart_data <-
  learning_curve_dat(dat = vines_dataset_numeric_reduced %>% 
                     select(selected_variables$variable,Calidad),
                     outcome = "Calidad",
                     #test_prop = 0.6,
                     proportion = seq(0.3,1,0.1),
                     ## `train` arguments1
                     method = "rpart",
                     metric = "Accuracy",
                     trControl = ctrl_fast,
                     verbose = F)

ggplot(cart_data, aes(x = Training_Size, y = Accuracy, color = Data)) +
  geom_smooth(method = loess, span = .8) +
  ggdark::dark_theme_bw()+
  labs(title="CART: Learning curves on training and resampled datasets")

NA
NA

Random Forests

Cross Validation 3x10

#rf_grid <-  expand.grid(.mtry = c(5))
rfFit <- caret::train(
  x = vines_dataset_numeric_reduced %>% 
                     select(selected_variables$variable) %>% na.omit(),
  y = vines_dataset_numeric_reduced %>% 
                     select(Calidad) %>% unlist() %>% as.factor(),
  method = "rf",
  tuneLength=10,
  #tuneGrid = rf_grid,
  #verbose = 2,
  trControl = ctrl_fast,
  #ntree = 200
)

rfFit$results %>%
  ggplot(aes(x = mtry, y = Accuracy)) +
  geom_point(color = 'red') +
  geom_errorbar(
    aes(ymin = Accuracy - AccuracySD, ymax = Accuracy + AccuracySD),
    width = .02,
    color = 'yellow'
  ) +
  ggdark::dark_theme_bw() +
  labs(title="Random Forest: Mean and Standard deviation after hyper-parameter (mtry) tuning")+
  theme(axis.text.x = element_text(angle = 45, hjust = 1))


rfFit
Random Forest 

211 samples
 20 predictor
  7 classes: 'A0', 'A1', 'A2', 'A3', 'A5', 'B', 'C' 

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 3 times) 
Summary of sample sizes: 190, 189, 191, 188, 190, 190, ... 
Resampling results across tuning parameters:

  mtry  Accuracy   Kappa    
   2    0.9671178  0.9598590
   4    0.9717416  0.9655283
   6    0.9684877  0.9615164
   8    0.9700750  0.9634609
  10    0.9700750  0.9634609
  12    0.9702130  0.9636306
  14    0.9654439  0.9577923
  16    0.9668932  0.9595670
  18    0.9638629  0.9557744
  20    0.9624795  0.9541345

Accuracy was used to select the optimal model using the largest value.
The final value used for the model was mtry = 4.

Learning Curves

set.seed(21052025)
rf_data <-
  learning_curve_dat(dat = vines_dataset_numeric_reduced %>% 
                     select(selected_variables$variable,Calidad),
                     outcome = "Calidad",
                     #test_prop = 0.6,
                     proportion = seq(0.3,1,0.1),
                     ## `train` arguments1
                     method = "ranger",
                     metric = "Accuracy",
                     trControl = ctrl_fast,
                     verbose = F)

ggplot(rf_data, aes(x = Training_Size, y = Accuracy, color = Data)) +
  geom_smooth(method = loess, span = .8) +
  ggdark::dark_theme_bw()+
  labs(title="Random Forests: Learning curves on training and resampled datasets")

Predictions

test_dataset_numeric<-test_dataset %>% select_if(~class(.) == 'numeric')
test_dataset_numeric_imputed <- kNN(test_dataset_numeric)
test_dataset_numeric<-test_dataset_numeric_imputed %>% select(-ends_with("_imp"))
preds<-predict(rfFit,test_dataset_numeric %>% 
                     select(selected_variables$variable))

caret::confusionMatrix(data = as.factor(preds), 
                       reference = as.factor(test_dataset$Calidad)
                       )
Confusion Matrix and Statistics

          Reference
Prediction A0 A1 A2 A3 A5  B  C
        A0  8  0  0  0  0  0  0
        A1  0  9  0  0  0  0  0
        A2  0  0 10  0  0  0  0
        A3  0  0  0 11  0  0  0
        A5  0  0  0  1  2  0  0
        B   0  0  0  0  0  7  0
        C   0  0  0  0  0  0  5

Overall Statistics
                                          
               Accuracy : 0.9811          
                 95% CI : (0.8993, 0.9995)
    No Information Rate : 0.2264          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.9775          
                                          
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: A0 Class: A1 Class: A2 Class: A3 Class: A5 Class: B Class: C
Sensitivity             1.0000    1.0000    1.0000    0.9167   1.00000   1.0000  1.00000
Specificity             1.0000    1.0000    1.0000    1.0000   0.98039   1.0000  1.00000
Pos Pred Value          1.0000    1.0000    1.0000    1.0000   0.66667   1.0000  1.00000
Neg Pred Value          1.0000    1.0000    1.0000    0.9762   1.00000   1.0000  1.00000
Prevalence              0.1509    0.1698    0.1887    0.2264   0.03774   0.1321  0.09434
Detection Rate          0.1509    0.1698    0.1887    0.2075   0.03774   0.1321  0.09434
Detection Prevalence    0.1509    0.1698    0.1887    0.2075   0.05660   0.1321  0.09434
Balanced Accuracy       1.0000    1.0000    1.0000    0.9583   0.99020   1.0000  1.00000

GLMNET

Cross Validation 3x10

Learning Curves

LS0tCnRpdGxlOiAiUC4gRXhwbG9yYXRvcnkgQW5hbHlzaXM6IFZpbmVzIDIwMjEgMjBGIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgZmlnX3dpZHRoOiAxMAogICAgZmlnX2hlaWdodDogNQogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgdG9jX2NvbGxhcHNlZDogdHJ1ZQoKLS0tCgojIFBSRUxJTUlOQVJJRVMKCkNsZWFuaW5nIHRoZSBSU3R1ZGlvIGVudmlyb25tZW50CgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpybShsaXN0ID0gbHMoKSkgIyBjbGVhbiBnbG9iYWwgZW52aXJvbm1lbnQKY2F0KCJcMDE0IikgICMgY2xlYW4gdGhlIGNvbnNvbGUKYGBgCgpEZWZhdWx0IGRpcmVjdG9yeQoKYGBge3Igc2V0dXAsIHdhcm5pbmc9RkFMU0V9CiMgQ0hBTkdFIFRPIFRIRSBDT1JSRVNQT05ESU5HIFdPUktJTkcgRElSRUNUT1JZCmtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gJy9ob21lL2hhcnBvL0Ryb3Bib3gvb25nb2luZy13b3JrL2dpdC1yZXBvcy9pbnRhLXZpbmUtcXVhbGl0eS8nKQoja25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSAnL2hvbWUvcm9kcmFsZXovaG9zdGRpci9qb2JzL2RlbWFuZC1wbGFubmluZy8nKQpgYGAKCmBgYHtyIGluY2x1ZGU9RkFMU0V9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShza2ltcikKbGlicmFyeShWSU0pCmxpYnJhcnkocnBhcnQpCmxpYnJhcnkocnBhcnQucGxvdCkKbGlicmFyeShCb3J1dGEpICNmb3IgZmVhdHVyZSBzZWxlY3Rpb24KbGlicmFyeShrYWJsZUV4dHJhKSAjIGZvciBwcmV0dHkgcHJpbnRpbmcgdGFibGVzCgpgYGAKCgojIEVYUExPUkFUSU9OCiMjIExvYWRpbmcgIERhdGFzZXQKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CiN2aW5lc19kYXRhc2V0PC1yZWFkcjo6cmVhZF9jc3YoImRhdGFzZXRzL3ZpbmVfdHJhaW5fbmV3bmFtZXMuY3N2IiwgY29sX3R5cGVzID1jb2xzKCkpCiN2aW5lc19kYXRhc2V0CnZpbmVzX2RhdGFzZXQ8LXJlYWRyOjpyZWFkX2NzdigicmF3ZGF0YS8yMDIyL3ZpbmVfcmF3LmNzdiIsY29sX3R5cGVzID0gY29scygKICBUZW1wb3JhZGEgPSBjb2xfY2hhcmFjdGVyKCksCiAgSUQgPSBjb2xfZG91YmxlKCksCiAgdWJpX0ZpbmNhID0gY29sX2NoYXJhY3RlcigpLAogIHViaV94ID0gY29sX2RvdWJsZSgpLAogIHViaV95ID0gY29sX2RvdWJsZSgpLAogIGB1YmlfQWx0dXJhIHMubi5tLmAgPSBjb2xfZG91YmxlKCksCiAgdWJpX1pvbmEgPSBjb2xfY2hhcmFjdGVyKCksCiAgdWJpX0Rpc3RyaXRvID0gY29sX2NoYXJhY3RlcigpLAogIGB1dmFfTCBob2xsZWpvYCA9IGNvbF9kb3VibGUoKSwKICB1dmFfYV9ob2xsZWpvID0gY29sX2RvdWJsZSgpLAogIHV2YV9iX2hvbGxlam8gPSBjb2xfZG91YmxlKCksCiAgdXZhX2hfaG9sbGVqbyA9IGNvbF9kb3VibGUoKSwKICB1dmFfQ19ob2xsZWpvID0gY29sX2RvdWJsZSgpLAogIGB1dmFfRlQobWcvZ2hvbGxlam8pYCA9IGNvbF9kb3VibGUoKSwKICBgdXZhX0ZUKG1nL2diYXlhKWAgPSBjb2xfZG91YmxlKCksCiAgYHV2YV9GVChtZy9iYXlhKWAgPSBjb2xfZG91YmxlKCksCiAgYHV2YV9UQU4obWcvZ2hvbGxlam8pYCA9IGNvbF9kb3VibGUoKSwKICBgdXZhX1RBTihtZy9nYmF5YSlgID0gY29sX2RvdWJsZSgpLAogIGB1dmFfVEFOKG1nL2JheWEpYCA9IGNvbF9kb3VibGUoKSwKICBgdXZhX0FOVChtZy9naG9sbGVqbylgID0gY29sX2RvdWJsZSgpLAogIGB1dmFfQU5UKG1nL2diYXlhKWAgPSBjb2xfZG91YmxlKCksCiAgYHV2YV9BTlQobWcvYmF5YSlgID0gY29sX2RvdWJsZSgpLAogIHV2YV9BY3RpX2FudGlycl9ob2xsZWpvID0gY29sX2RvdWJsZSgpLAogIGBVdmFfU2VtaWxsYXMvYmF5YWAgPSBjb2xfZG91YmxlKCksCiAgYHV2YV9QZXNvIHNlbWlsbGFzL2JheWFgID0gY29sX2RvdWJsZSgpLAogIHV2YV9Ccml4ID0gY29sX2RvdWJsZSgpLAogIHV2YV9wSF9tb3N0byA9IGNvbF9kb3VibGUoKSwKICBgdXZhX0FjaWRleiBNb3N0b2AgPSBjb2xfZG91YmxlKCksCiAgYHV2YV8gTlBBX0VueihtZy9MKWAgPSBjb2xfZG91YmxlKCksCiAgYHV2YV8gUG90YXNpb19FbnoobWcvTClgID0gY29sX2RvdWJsZSgpLAogIGB1dmFfIE5QQV9Gb3NzKG1nL0wpYCA9IGNvbF9kb3VibGUoKSwKICBgdXZhXyBQb3Rhc2lvX0Zvc3MobWcvTClgID0gY29sX2RvdWJsZSgpLAogIHBsYW50YV9QZXNvX3JhY2ltbyA9IGNvbF9kb3VibGUoKSwKICBwbGFudGFfUGVzb19kZV9iYXlhID0gY29sX2RvdWJsZSgpLAogIGBwbGFudGFfQmF5YXMvcmFjaW1vYCA9IGNvbF9kb3VibGUoKSwKICBwbGFudGFfUmF2YXogPSBjb2xfZG91YmxlKCksCiAgYHBsYW50YV9Mb25nIENvcmRvbmAgPSBjb2xfZG91YmxlKCksCiAgYHBsYW50YV9Ocm8gZGUgUmFjaW1vcy9tYCA9IGNvbF9kb3VibGUoKSwKICBgcGxhbnRhXyBQZXNvIGRlIHBvZGEvbWAgPSBjb2xfZG91YmxlKCksCiAgYHBsYW50YV9EaXN0LiBoaWxlcmFzYCA9IGNvbF9kb3VibGUoKSwKICBgcGxhbnRhX0Rpc3QuIHBsYW50YXNgID0gY29sX2RvdWJsZSgpLAogIGBwbGFudGFfUGxhbnRhcy9oYWAgPSBjb2xfZG91YmxlKCksCiAgYHBsYW50YV9SbnRvL2hhYCA9IGNvbF9kb3VibGUoKSwKICBwbGFudGFfbmR2aSA9IGNvbF9kb3VibGUoKSwKICBzdWVsb19Bc3BlY3RvID0gY29sX2RvdWJsZSgpLAogIGBzdWVsb19Wb2wuIFNlZGltZW50YWNpb24gKCUpYCA9IGNvbF9kb3VibGUoKSwKICBzdWVsb19UZXh0dXJhID0gY29sX2NoYXJhY3RlcigpLAogIGBzdWVsb19JbmRpY2UgZm9uZG8gVmFsbGVgID0gY29sX2RvdWJsZSgpLAogIHN1ZWxvX1BlbmRpZW50ZSA9IGNvbF9kb3VibGUoKSwKICBgZWNvX0VzdGFjaW9uIGRhdG9zIGNsaW1hYCA9IGNvbF9jaGFyYWN0ZXIoKSwKICBgZWNvX0dEQSBoaXN0b3JpY29gID0gY29sX2RvdWJsZSgpLAogIGBlY29fVGVtcC4gTcOtbmltYSDCsENgID0gY29sX2RvdWJsZSgpLAogIGBlY29fVGVtcC4gTWVkaWEgwrBDYCA9IGNvbF9kb3VibGUoKSwKICBgZWNvX1RlbXAuIE3DoXhpbWEgwrBDYCA9IGNvbF9kb3VibGUoKSwKICBgZWNvX0FtcGxpdHVkIFTDqXJtaWNhIMKwQ2AgPSBjb2xfZG91YmxlKCksCiAgYGVjb19QcmVjaXBpdGFjaW9uZXMgKG1tKSBUZW1wIDIwMjBgID0gY29sX2RvdWJsZSgpLAogIGBlY29fR3JhZG9zIETDrWFzIEFjdW0uIFRlbXBgID0gY29sX2RvdWJsZSgpLAogIGBlY29fRXZhcG90cmFuc3BpcmFjacOzbiBtbSBUZW1wYCA9IGNvbF9kb3VibGUoKSwKICBgZWNvX0TDrWFzIGNvbiBUZW1wLj4zNcKwQyBUZW1wYCA9IGNvbF9kb3VibGUoKSwKICBgZWNvX1JhZGlhY8Otb24gVy9tMiBUZW1wYCA9IGNvbF9kb3VibGUoKSwKICBgZWNvX1ZlbG9jaWRhZCBNZWRpYSBWaWVudG8ga20vaCBUZW1wYCA9IGNvbF9kb3VibGUoKSwKICBgZWNvX0kuIGRlIEZyZXNjbyBOb2N0dXJubyBUZW1wYCA9IGNvbF9kb3VibGUoKSwKICBgZWNvX1RlbXAuIE1lZGlhIE1hcnpvIMKwQyBUZW1wYCA9IGNvbF9kb3VibGUoKSwKICBgZWNvX0FtcGwuIFTDqXJtaWNhIE1hcnpvIMKwQyBUZW1wYCA9IGNvbF9kb3VibGUoKSwKICBgZWNvX0hvcmFzIGRlIEZyw61vIFRlbXBgID0gY29sX2RvdWJsZSgpLAogIENhbGlkYWQgPSBjb2xfY2hhcmFjdGVyKCkKKQopCiNzcGVjKHZpbmVzX2RhdGFzZXQpCiN2aW5lc19kYXRhc2V0IDwtIHZpbmVzX2RhdGFzZXQgJT4lIGZpbHRlcihUZW1wb3JhZGEgPT0gMjAyMCkKdmluZXNfZGF0YXNldCAlPiUgZ3JvdXBfYnkoVGVtcG9yYWRhKSAlPiUgY291bnQoKQpgYGAKClRyYWluaW5nIGRhdGFzZXQgZGltZW5zaW9uIGByIHZpbmVzX2RhdGFzZXQgJT4lIG5jb2woKWAgeCBgciB2aW5lc19kYXRhc2V0ICU+JSBucm93KClgCgoKIyMgUmVtb3ZpbmcgdW5uZWNlc2FyeSBkYXRhCmBgYHtyIHdhcm5pbmc9RkFMU0V9CiNyZW1vdmVkX251bWVyaWNfdmFyPC1jKCJ1YmlfeCIsInViaV95IiwiSUQiKQpyZW1vdmVkX251bWVyaWNfdmFyPC1jKCJJRCIsInViaV94IiwidWJpX3kiKQoKCgoKdmluZXNfZGF0YXNldCA8LSB2aW5lc19kYXRhc2V0ICU+JSBzZWxlY3QoLWFsbF9vZihyZW1vdmVkX251bWVyaWNfdmFyKSkKYGBgCgojIyBSZW1vdmluZyBjb2x1bW5zIHdpdGggdG9vIG1hbnkgbWlzc2luZyB2YWx1ZXMKCmBgYHtyIHdhcm5pbmc9RkFMU0V9Cm1pc3NpbmdfdmFsczwtdmluZXNfZGF0YXNldCAgJT4lIGdyb3VwX2J5KFRlbXBvcmFkYSkgJT4lIHNlbGVjdF9pZihmdW5jdGlvbih4KSBhbnkoaXMubmEoeCkpKSAgJT4lCiAgc3VtbWFyaXNlX2VhY2goZnVucyhzdW0oaXMubmEoLikpKSkgJT4lIHJlc2hhcGUyOjptZWx0KCkgJT4lIGZpbHRlcihUZW1wb3JhZGEgPT0gIjIwMjAiICYgdmFsdWUgPT0gODcpICU+JSBzZWxlY3QodmFyaWFibGUpCgoKbWlzc2luZ192YWxzPC1yYmluZChtaXNzaW5nX3ZhbHMsICJlY29fSG9yYXMgZGUgRnLDrW8gVGVtcCIpICU+JSB1bmxpc3QoKSAlPiUgdW5uYW1lCgp2aW5lc19kYXRhc2V0PC12aW5lc19kYXRhc2V0ICU+JSBzZWxlY3QoLWFsbF9vZihtaXNzaW5nX3ZhbHMpKQpgYGAKCiMjIFRyYWluIGFuZCBUZXN0IHNwbGl0OgoKYGBge3Igd2FybmluZz1GQUxTRX0Kc2V0LnNlZWQoNykKdGVzdF9kYXRhc2V0ICA8LSB2aW5lc19kYXRhc2V0ICU+JSBzYW1wbGVfZnJhYygwLjIpCnRyYWluX2RhdGFzZXQgPC1kcGx5cjo6c2V0ZGlmZih2aW5lc19kYXRhc2V0LCB0ZXN0X2RhdGFzZXQpCnZpbmVzX2RhdGFzZXQ8LXRyYWluX2RhdGFzZXQKdmluZXNfZGF0YXNldCAlPiUgZ3JvdXBfYnkoVGVtcG9yYWRhKSAlPiUgY291bnQoKQpgYGAKCgoKIyMgQmFzaWMgRGlzdHJpYnV0aW9uIEluZm9ybWF0aW9uCiMjIyBDYXRlZ29yaWNhbCBWYXJpYWJsZXMKYGBge3Igd2FybmluZz1GQUxTRX0KdmluZXNfZGF0YXNldF9mYWN0b3JzPC12aW5lc19kYXRhc2V0ICU+JSBzZWxlY3RfaWYofmNsYXNzKC4pID09ICdjaGFyYWN0ZXInKQpuYW1lcyh2aW5lc19kYXRhc2V0X2ZhY3RvcnMpICU+JSBhcy5kYXRhLmZyYW1lKCkKYGBgCmBgYHtyIHdhcm5pbmc9RkFMU0V9CiBza2ltcjo6c2tpbSh2aW5lc19kYXRhc2V0X2ZhY3RvcnMgJT4lIHNlbGVjdCgtQ2FsaWRhZCkpIyAlPiUga25pdHI6OmthYmxlKCkgJT4lIGthYmxlX3N0eWxpbmcoZm9udF9zaXplID0gOSkKYGBgCgojIyMgTnVtZXJpY2FsIFZhcmlhYmxlcwoKYGBge3Igd2FybmluZz1GQUxTRX0KdmluZXNfZGF0YXNldF9udW1lcmljPC12aW5lc19kYXRhc2V0ICU+JSBzZWxlY3RfaWYofmNsYXNzKC4pID09ICdudW1lcmljJykKbmFtZXModmluZXNfZGF0YXNldF9udW1lcmljKSAlPiUgYXMuZGF0YS5mcmFtZSgpCmBgYAoKYGBge3Igd2FybmluZz1GQUxTRX0Kc2tpbXI6OnNraW0odmluZXNfZGF0YXNldF9udW1lcmljKSAlPiUgYXJyYW5nZShkZXNjKG5fbWlzc2luZykpICMlPiUga25pdHI6OmthYmxlKCkgJT4lIGthYmxlX3N0eWxpbmcoZm9udF9zaXplID0gOSkKYGBgCgoKIyMjIENsYXNzIERpc3RyaWJ1dGlvbgoKYGBge3Igd2FybmluZz1GQUxTRX0KdmluZXNfZGF0YXNldCAlPiUgZ3JvdXBfYnkoQ2FsaWRhZCkgJT4lIHN1bW1hcmlzZSh0b3RhbD1uKCkpICU+JQogIGdncGxvdCgpKwogIGdlb21fY29sKGFlcyh4PUNhbGlkYWQseT10b3RhbCxmaWxsPUNhbGlkYWQpKSsKICBnZ2Rhcms6OmRhcmtfdGhlbWVfYncoKQpgYGAKCgojIEFOQUxJU1lTIE5VTUVSSUNBTCBWQVJJQUJMRVMKCgoKCiMjIERlYWxpbmcgTWlzc2luZyBEYXRhCgpgYGB7ciBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD0xMiwgd2FybmluZz1GQUxTRX0KcmVzPC1WSU06OmFnZ3IodmluZXNfZGF0YXNldF9udW1lcmljLCAKICAgICAgICAgIGNvbWJpbmVkID0gRkFMU0UgLCAKICAgICAgICAgIG51bWJlcnMgPSBUUlVFLCAKICAgICAgICAgIHNvcnRDb21icyA9IFRSVUUsIAogICAgICAgICAgc29ydFZhcnMgPSBUUlVFLCAKICAgICAgICAgIGxhYmVscz1uYW1lcyh2aW5lc19kYXRhc2V0X251bWVyaWMpLAogICAgICAgICAgY2V4LmF4aXM9LjQsIAogICAgICAgICAgdmFyaGVpZ2h0ID0gRkFMU0UsCiAgICAgICAgICBjZXgubnVtYmVycz0wLjgsCiAgICAgICAgICBjZXgubGFiPTAuOCwKICAgICAgICAgIHByb3AgPSBUUlVFKQpgYGAKCgoKCgojIyMgTWlzc2luZyB2YWx1ZXMgaW1wdXRhdGlvbgpgYGB7ciB3YXJuaW5nPUZBTFNFfQp2aW5lc19kYXRhc2V0X251bWVyaWNfaW1wdXRlZCA8LSBrTk4odmluZXNfZGF0YXNldF9udW1lcmljKQp2aW5lc19kYXRhc2V0X251bWVyaWM8LXZpbmVzX2RhdGFzZXRfbnVtZXJpY19pbXB1dGVkICU+JSBzZWxlY3QoLWVuZHNfd2l0aCgiX2ltcCIpKQojdmluZXNfZGF0YXNldF9udW1lcmljICU+JSBzZWxlY3QoLU5BTUUpCmBgYAoKIyMgQ29ycmVsYXRpb24gTWF0cml4CgojIyBDaGVja2luZyAgQ29ycmVsYXRpb24gd2l0aCBDbGFzcwpgYGB7ciBldmFsPUZBTFNFLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MTgsIGluY2x1ZGU9RkFMU0Usd2FybmluZz1GQUxTRX0KdmluZXNfZGF0YXNldF9udW1lcmljICU+JSAKICB0aWJibGU6OmFkZF9jb2x1bW4oQ2FsaWRhZCA9IGFzLmZhY3Rvcih2aW5lc19kYXRhc2V0JENhbGlkYWQpKSAgJT4lIHJlc2hhcGUyOjptZWx0KCkgJT4lIGZpbHRlcih2YXJpYWJsZSAlaW4lIGMoImVjb19HREEgaGlzdG9yaWNvIiwidWJpX0FsdHVyYSBzLm4ubS4iKSkgJT4lCiAgZ2dwbG90KCkrCiAgZmFjZXRfd3JhcCh+Q2FsaWRhZCxuY29sPTgpKwogICNmYWNldF93cmFwKH52YXJpYWJsZSxzY2FsZXMgPSAnZnJlZScsbmNvbCA9IDEwKSsKICBnZW9tX2JveHBsb3QoYWVzKHg9dmFyaWFibGUseT12YWx1ZSxmaWxsPXZhcmlhYmxlKSxjb2xvcj0nZ3JheScpKwogIGdnZGFyazo6ZGFya190aGVtZV9idygpKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkrCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSsKICAgdGhlbWUoIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDYpKQoKCiMgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIsCiMgICAgICAgIGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCksCiMgICAgICAgIGF4aXMudGV4dC54PWVsZW1lbnRfYmxhbmsoKSwKIyAgICAgICAgYXhpcy50aWNrcy54PWVsZW1lbnRfYmxhbmsoKSkrCiAKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTE4LCBmaWcud2lkdGg9MTgsd2FybmluZz1GQUxTRX0KdmluZXNfZGF0YXNldF9udW1lcmljX3djYWxudW0gPC0gdmluZXNfZGF0YXNldF9udW1lcmljICU+JSAKICB0aWJibGU6OmFkZF9jb2x1bW4oQ2FsaWRhZCA9IGFzLmZhY3Rvcih2aW5lc19kYXRhc2V0JENhbGlkYWQpKSAlPiUgCiAgIHRpYmJsZTo6YWRkX2NvbHVtbih1YmlfWm9uYSA9IGFzLmZhY3Rvcih2aW5lc19kYXRhc2V0JHViaV9ab25hKSkgJT4lIAogIG11dGF0ZShDYWxpZGFkID0gYXMubnVtZXJpYyhDYWxpZGFkKSwKICAgICAgICAgdWJpX1pvbmEgPSBhcy5udW1lcmljKHViaV9ab25hKSkKCmxpYnJhcnkoZDNoZWF0bWFwKQp2aW5lc19kYXRhc2V0X251bWVyaWNfY29yX21hdHJpeDwtY29yKHZpbmVzX2RhdGFzZXRfbnVtZXJpY193Y2FsbnVtICxtZXRob2Q9InNwZWFybWFuIikKCnZpbmVzX2RhdGFzZXRfbnVtZXJpY19jb3JfbWF0cml4ICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJjbGFzcyIpICU+JSAKICBmaWx0ZXIoY2xhc3M9PSJDYWxpZGFkIikgJT4lIAogIHJlc2hhcGUyOjptZWx0KCkgJT4lIHNlbGVjdCh2YXJpYWJsZSwgdmFsdWUpICU+JQogIGFycmFuZ2UoZGVzYyhhYnModmFsdWUpKSkgJT4lCiAgZ2dwbG90KCkrCiAgZ2VvbV9jb2woYWVzKHg9dmFyaWFibGUseT12YWx1ZSxmaWxsPXZhcmlhYmxlKSkrCiAgZ2dkYXJrOjpkYXJrX3RoZW1lX2J3KCkKICAKI2hlYXRtYXAocG9zdG9wX2RhdGFfY29yX21hdHJpeCkKZDNoZWF0bWFwKHZpbmVzX2RhdGFzZXRfbnVtZXJpY19jb3JfbWF0cml4ICxjb2xvcnMgPSAiQmx1ZXMiLGNleFJvdyA9IDAuOCwgY2V4Q29sID0gMC44KQoKYGBgCgoKIyMjIE1hdHJpeCBjb25zaWRlcmluZyBhbGwgdmFyaWFibGVzCmBgYHtyIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD0xMix3YXJuaW5nPUZBTFNFfQoKbGlicmFyeShkM2hlYXRtYXApCnZpbmVzX2RhdGFzZXRfbnVtZXJpY19jb3JfbWF0cml4PC1jb3IodmluZXNfZGF0YXNldF9udW1lcmljICxtZXRob2Q9InNwZWFybWFuIikKI2hlYXRtYXAocG9zdG9wX2RhdGFfY29yX21hdHJpeCkKZDNoZWF0bWFwKHZpbmVzX2RhdGFzZXRfbnVtZXJpY19jb3JfbWF0cml4ICxjb2xvcnMgPSAiQmx1ZXMiLGNleFJvdyA9IDAuOCwgY2V4Q29sID0gMC44KQpgYGAKCgoKIyMgUENBIEVsaXBzZXMgZm9yIGNsYXNlcyAKYGBge3IgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTEyLHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoRmFjdG9NaW5lUikKI3ZpbmVzX2RhdGFzZXRfbnVtZXJpYyAlPiUgc2VsZWN0KC1oaWdobHlDb3JyZWxhdGVkX3ZhcikgJT4lIHRpYmJsZTo6YWRkX2NvbHVtbihDYWxpZGFkPXZpbmVzX2RhdGFzZXQkQ2FsaWRhZCkKcmVzX3BjYSA9IFBDQSh2aW5lc19kYXRhc2V0X251bWVyaWMgICU+JSAKICAgICAgICAgICAgICAgIHRpYmJsZTo6YWRkX2NvbHVtbihDYWxpZGFkPXZpbmVzX2RhdGFzZXQkQ2FsaWRhZCkKICAgICAgICAgICAgICAsIHNjYWxlLnVuaXQ9VFJVRSwgCiAgICAgICAgICAgICAgbmNwPTYsIAogICAgICAgICAgICAgIGdyYXBoPUYsCiAgICAgICAgICAgICAgcXVhbGkuc3VwPTUwLCAjY29saWQgZm9yIENhbGlkYWQKICAgICAgICAgICAgICApCgpwbG90KHJlc19wY2EsY2hvaXg9ImluZCIsaGFiaWxsYWdlPTUwKQpwYXIobWZjb2w9YygxLDIpKQpwbG90KHJlc19wY2EsY2hvaXg9InZhciIsaGFiaWxsYWdlPSJub25lIixpbnZpc2libGUgPSAiaW5kIikgIyBwYXJhIGxhcyB2YXJpYWJsZXMKcGxvdGVsbGlwc2VzKHJlc19wY2EsIGludmlzaWJsZT0iaW5kIix4bGltPWMoLTEwLDEwKSx5bGltPWMoLTYsNikpCmBgYAoKCiMjIExpc3Qgb2YgaGlnaGx5IGNvcnJlbGF0ZWQgdmFyaWFibGVzCmBgYHtyfQpoaWdobHlDb3JyZWxhdGVkIDwtIGNhcmV0OjpmaW5kQ29ycmVsYXRpb24odmluZXNfZGF0YXNldF9udW1lcmljX2Nvcl9tYXRyaXgsIGN1dG9mZj0wLjksIHZlcmJvc2UgPSBGKQpoaWdobHlDb3JyZWxhdGVkX3ZhcjwtdmluZXNfZGF0YXNldF9udW1lcmljX2Nvcl9tYXRyaXhbLGhpZ2hseUNvcnJlbGF0ZWRdICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIG5hbWVzKCkKaGlnaGx5Q29ycmVsYXRlZF92YXIKYGBgCgojIyBDb3JyZWxhdGlvbiBNYXRyaXggd2l0aCAgaGlnaGx5IGNvcnJlbGF0ZWQgcmVtb3ZlZAoKYGBge3IgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OCx3YXJuaW5nPUZBTFNFfQojaGlnaGx5Q29ycmVsYXRlZF92YXIgJT4lIGxlbmd0aCgpCiN2aW5lc19kYXRhc2V0X251bWVyaWNfY29yX21hdHJpeFstaGlnaGx5Q29ycmVsYXRlZCwtaGlnaGx5Q29ycmVsYXRlZF0gICU+JSBucm93KCkKZDNoZWF0bWFwKHZpbmVzX2RhdGFzZXRfbnVtZXJpY19jb3JfbWF0cml4Wy1oaWdobHlDb3JyZWxhdGVkLC1oaWdobHlDb3JyZWxhdGVkXSAsY29sb3JzID0gIkJsdWVzIixjZXhSb3cgPSAwLjgsIGNleENvbCA9IDAuOCkKCmBgYAoKCgoKIyMgQ29udmVydCBjbGFzcyB0byBudW1lcmljCmBgYHtyICx3YXJuaW5nPUZBTFNFfQojdmluZXNfZGF0YXNldCAlPiUgbXV0YXRlKCkKI3ZpbmVzX2RhdGFzZXQkQ2FsaWRhZCAlPiUgYXMubnVtZXJpYyhhcy5mYWN0b3IodmluZXNfZGF0YXNldCRDYWxpZGFkKSkKYGBgCgojIEZFQVRVUkUgU0VMRUNUSU9OCiMjIEJvb3RzdHJhcCByZXNhbXBsaW5nCjEwMCBuZXcgZGF0YXNldHMgdXNlZCBmb3IgZXZhbHVhdGluZyBmZWF0dXJlIHNlbGVjdGlvbiBhbGdvcml0aG1zCmBgYHtyICx3YXJuaW5nPUZBTFNFfQp2aW5lc19kYXRhc2V0X251bWVyaWNfcmVkdWNlZDwtdmluZXNfZGF0YXNldF9udW1lcmljICAlPiUgCiAgc2VsZWN0KC1hbGxfb2YoaGlnaGx5Q29ycmVsYXRlZF92YXIpKSAgJT4lIAogICNzZWxlY3QobmFtZXModmFyX2ltcG9ydGFuY2VbMToxMV0pKSAlPiUKICB0aWJibGU6OmFkZF9jb2x1bW4oQ2FsaWRhZD1hcy5mYWN0b3IodmluZXNfZGF0YXNldCRDYWxpZGFkKSkKCnJlc2FtcGxlczwtcnNhbXBsZTo6Ym9vdHN0cmFwcyh2aW5lc19kYXRhc2V0X251bWVyaWNfcmVkdWNlZCxzdHJhdGE9IENhbGlkYWQsdGltZXMgPSAxMDAgICkKbnVtX29mX2ZlYXQ8LTIwCmBgYAoKIyMgQk9SVVRBIGZvciBmZWF0dXJlIHNlbGVjdGlvbgpgYGB7ciAsd2FybmluZz1GQUxTRX0KdmluZXNfZGF0YXNldF9ib290c3RyYXA8LXJzYW1wbGU6OmFuYWx5c2lzKHJlc2FtcGxlcyRzcGxpdHNbW3NhbXBsZSgxOjEwMCwxKV1dKQp2YWxfZGF0YXNldCAgPC0gdmluZXNfZGF0YXNldF9ib290c3RyYXAgICAlPiUgZ3JvdXBfYnkoQ2FsaWRhZCkgJT4lIHNhbXBsZV9uKDIpICU+JSB1bmdyb3VwKCkKdHJhaW5fZGF0YXNldCA8LWRwbHlyOjpzZXRkaWZmKHZpbmVzX2RhdGFzZXRfbnVtZXJpY19yZWR1Y2VkLHZhbF9kYXRhc2V0KQoKdmFyX2ltcG9ydGFuY2VfYm9ydXRhPC1Cb3J1dGEoQ2FsaWRhZCB+IC4gLGRhdGE9dHJhaW5fZGF0YXNldCkKCnZhcl9pbXBvcnRhbmNlX2JvcnV0YTwtYXR0U3RhdHModmFyX2ltcG9ydGFuY2VfYm9ydXRhKSAlPiUgZmlsdGVyKGRlY2lzaW9uPT0nQ29uZmlybWVkJykgJT4lIHNlbGVjdChtZWFuSW1wKSAlPiUgYXJyYW5nZShkZXNjKG1lYW5JbXApKSAlPiUgdG9wX24obnVtX29mX2ZlYXQpCnZhcl9pbXBvcnRhbmNlX2JvcnV0YTwtdmFyX2ltcG9ydGFuY2VfYm9ydXRhICU+JSBhZGRfcm93bmFtZXMoJ3ZhcmlhYmxlJykKdmFyX2ltcG9ydGFuY2VfYm9ydXRhX2ZpbmFsPC1hcHBseSh2YXJfaW1wb3J0YW5jZV9ib3J1dGEkdmFyaWFibGUgJT4lIHQoKSAsMiwgZnVuY3Rpb24oeCkgc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKHgsIHBhdHRlcm49ImAiLHJlcGxhY2VtZW50ID0gIiIpKSAlPiUgYXMuZGF0YS5mcmFtZSgpCm5hbWVzKHZhcl9pbXBvcnRhbmNlX2JvcnV0YV9maW5hbCk8LSJ2YXJpYWJsZSIKdmFyX2ltcG9ydGFuY2VfYm9ydXRhX2ZpbmFsCmBgYAoKIyMgQ0FSVCBGZWF0dXJlcyBzZWxlY3Rpb24gYWQtaG9jIHVzaW5nIGltcG9ydGFuY2UgbWV0cmljCmBgYHtyICx3YXJuaW5nPUZBTFNFfQp2YXJfaW1wb3J0YW5jZTwtYygpCmZvciAoaSBpbiAgMToxMDApewp2aW5lc19kYXRhc2V0X2Jvb3RzdHJhcDwtcnNhbXBsZTo6YW5hbHlzaXMocmVzYW1wbGVzJHNwbGl0c1tbaV1dKQp2YWxfZGF0YXNldCAgPC0gdmluZXNfZGF0YXNldF9ib290c3RyYXAgICAlPiUgZ3JvdXBfYnkoQ2FsaWRhZCkgJT4lIHNhbXBsZV9uKDIpICU+JSB1bmdyb3VwKCkKdHJhaW5fZGF0YXNldCA8LWRwbHlyOjpzZXRkaWZmKHZpbmVzX2RhdGFzZXRfbnVtZXJpY19yZWR1Y2VkLHZhbF9kYXRhc2V0KQoKdHJlZTwtcnBhcnQ6OnJwYXJ0KENhbGlkYWR+LiwKICAgICAgICAgICAgICAgICAgIGRhdGE9dHJhaW5fZGF0YXNldCwKICAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBycGFydC5jb250cm9sKG1pbnNwbGl0ID0gMTApLCkKdmFyX2ltcG9ydGFuY2U8LSBjKHZhcl9pbXBvcnRhbmNlLHRyZWUkdmFyaWFibGUuaW1wb3J0YW5jZVsxOm51bV9vZl9mZWF0XSkKI3JwYXJ0LnBsb3QodHJlZSx0eXBlPTEsCiMgICAgICAgICAgIGV4dHJhPTEwMSwgYm94LnBhbGV0dGU9IkduQnUiLAojICAgICAgICAgICBicmFuY2gubHR5PTMsIHNoYWRvdy5jb2w9ImdyYXkiLCBubj1UUlVFCiMgICAgICAgICkKfQoKYGBgCgoKYGBge3IgLHdhcm5pbmc9RkFMU0V9CnZhcl9pbXBvcnRhbmNlX2NhcnRfZmluYWw8LWRhdGEuZnJhbWUodmFyaWFibGU9bmFtZXModmFyX2ltcG9ydGFuY2UpLHZhbHVlPXZhcl9pbXBvcnRhbmNlKSAlPiUgZ3JvdXBfYnkodmFyaWFibGUpICU+JSBzdW1tYXJpc2Uobj1uKCkpICU+JSBhcnJhbmdlKGRlc2MobikpICU+JSB0b3BfbigyMCkKdmFyX2ltcG9ydGFuY2VfY2FydF9maW5hbCAlPiUgYXMuZGF0YS5mcmFtZSgpCmBgYAojIyBSQU5ET00gRk9SRVNUUyBmZWF0dXJlIGltcG9ydGFuY2UKYGBge3IgLHdhcm5pbmc9RkFMU0V9CnZpbmVzX2RhdGFzZXRfYm9vdHN0cmFwPC1yc2FtcGxlOjphbmFseXNpcyhyZXNhbXBsZXMkc3BsaXRzW1tzYW1wbGUoMToxMDAsMSldXSkKdmFsX2RhdGFzZXQgIDwtIHZpbmVzX2RhdGFzZXRfYm9vdHN0cmFwICAgJT4lIGdyb3VwX2J5KENhbGlkYWQpICU+JSBzYW1wbGVfbigyKSAlPiUgdW5ncm91cCgpCnRyYWluX2RhdGFzZXQgPC1kcGx5cjo6c2V0ZGlmZih2aW5lc19kYXRhc2V0X251bWVyaWNfcmVkdWNlZCx2YWxfZGF0YXNldCkKCnJmX25hbWVzPC1hcHBseShuYW1lcyh0cmFpbl9kYXRhc2V0KSAlPiUgdCgpICwyLCBmdW5jdGlvbih4KSBzdHJpbmdyOjpzdHJfcmVwbGFjZV9hbGwoeCwgcGF0dGVybj0iICIscmVwbGFjZW1lbnQgPSAiXyIpKQpyZl9uYW1lczwtYXBwbHkocmZfbmFtZXMgJT4lIHQoKSAsMiwgZnVuY3Rpb24oeCkgc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKHgsIHBhdHRlcm49IlsvKCklwrA+XSsiLHJlcGxhY2VtZW50ID0gIl8iKSkKCnRyYWluX2RhdGFzZXRfcmY8LXRyYWluX2RhdGFzZXQKbmFtZXModHJhaW5fZGF0YXNldF9yZik8LXJmX25hbWVzCgpyZjwtcmFuZG9tRm9yZXN0OjpyYW5kb21Gb3Jlc3QoQ2FsaWRhZCB+IC4sZGF0YT10cmFpbl9kYXRhc2V0X3JmLGltcG9ydGFuY2U9VFJVRSkKdmFyX2ltcG9ydGFuY2VfcmZfZmluYWw8LXJhbmRvbUZvcmVzdDo6aW1wb3J0YW5jZShyZikgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgYXJyYW5nZShkZXNjKE1lYW5EZWNyZWFzZUdpbmkpKSAlPiUgYWRkX3Jvd25hbWVzKCd2YXJpYWJsZScpICAlPiUgdG9wX24obnVtX29mX2ZlYXQpICU+JSBzZWxlY3QodmFyaWFibGUpCnZhcl9pbXBvcnRhbmNlX3JmX2ZpbmFsPC0gZGF0YS5mcmFtZSh2YXJpYWJsZT1uYW1lcyh0cmFpbl9kYXRhc2V0Wyx3aGljaChuYW1lcyh0cmFpbl9kYXRhc2V0X3JmKSAlaW4lIHZhcl9pbXBvcnRhbmNlX3JmX2ZpbmFsJHZhcmlhYmxlKV0pICkKdmFyX2ltcG9ydGFuY2VfcmZfZmluYWwKYGBgCiMjIEdMTU5FVCBGZWF0dXJlIFNlbGVjdGlvbgpgYGB7ciAsd2FybmluZz1GQUxTRX0KbGlicmFyeShnbG1uZXQpCnZpbmVzX2RhdGFzZXRfYm9vdHN0cmFwPC1yc2FtcGxlOjphbmFseXNpcyhyZXNhbXBsZXMkc3BsaXRzW1tzYW1wbGUoMToxMDAsMSldXSkKdmFsX2RhdGFzZXQgIDwtIHZpbmVzX2RhdGFzZXRfYm9vdHN0cmFwICAgJT4lIGdyb3VwX2J5KENhbGlkYWQpICU+JSBzYW1wbGVfbigyKSAlPiUgdW5ncm91cCgpCnRyYWluX2RhdGFzZXQgPC1kcGx5cjo6c2V0ZGlmZih2aW5lc19kYXRhc2V0X251bWVyaWNfcmVkdWNlZCx2YWxfZGF0YXNldCkKZ2xtZml0PC1jdi5nbG1uZXQoeCA9IHRyYWluX2RhdGFzZXQgJT4lIHNlbGVjdCgtQ2FsaWRhZCkgJT4lIGFzLm1hdHJpeCgpLAogICAgICAgeSA9IHRyYWluX2RhdGFzZXQkQ2FsaWRhZCAlPiUgYXMuZmFjdG9yKCksCiAgICAgICBmYW1pbHkgPSAibXVsdGlub21pYWwiKQojY29lZihnbG1maXQscyA9ICdsYW1iZGEubWluJykKCiclbmklJzwtTmVnYXRlKCclaW4lJykKYzwtY29lZihnbG1maXQscz0nbGFtYmRhLm1pbicsZXhhY3Q9VFJVRSkKCgp2YXJfaW1wb3J0YW5jZV9nbG1uZXQ8LXB1cnJyOjptYXAoYywgZnVuY3Rpb24oeCkgewogIGluZHMgPC0gd2hpY2goeCAhPSAwKQogIHZhcmlhYmxlcyA8LSByb3cubmFtZXMoeClbaW5kc10KICB2YXJpYWJsZXMgPC0gdmFyaWFibGVzW3ZhcmlhYmxlcyAlbmklICcoSW50ZXJjZXB0KSddCn0pIAoKI2RvLmNhbGwocmJpbmQsdmFyX2ltcG9ydGFuY2VfZ2xtbmV0KSAlPiUgYXMuZGF0YS5mcmFtZSgpCgoKdmFyX2ltcG9ydGFuY2VfZ2xtbmV0X2ZpbmFsPC12YXJfaW1wb3J0YW5jZV9nbG1uZXQgJT4lIHVubGlzdCgpICU+JSB1bmlxdWUoKSAlPiUgYXMuZGF0YS5mcmFtZSgpCm5hbWVzKHZhcl9pbXBvcnRhbmNlX2dsbW5ldF9maW5hbCk8LSJ2YXJpYWJsZSIKCnZhcl9pbXBvcnRhbmNlX2dsbW5ldF9maW5hbApgYGAKCiMjIyBTZWxlY3RlZCB2YXJpYWJsZXMgcGVyIGNsYXNzCmBgYHtyICx3YXJuaW5nPUZBTFNFfQp2YXJfaW1wb3J0YW5jZV9nbG1uZXQKYGBgCgojIEZJTkFMIFZBUklBQkxFIFNFTEVDVElPTgpgYGB7ciAsd2FybmluZz1GQUxTRX0Kc2VsZWN0ZWRfdmFyaWFibGVzPC12YXJfaW1wb3J0YW5jZV9ib3J1dGFfZmluYWwKCgojc2VsZWN0ZWRfdmFyaWFibGVzPC1kYXRhLmZyYW1lKHZhcmlhYmxlPWMoInBsYW50YV9QZXNvX3JhY2ltbyIsCQkKIyJ1dmFfQWNpZGV6IE1vc3RvIiwJCQkKIyJ1dmFfVEFOKG1nL2JheWEpIiwKIyJwbGFudGFfQmF5YXMvcmFjaW1vIiwKIyJlY29fQW1wbGl0dWQgVMOpcm1pY2EgwrBDIikpCgoKCmBgYAojIyMgU2VsZWN0ZWQgdmFyaWFibGVzIHJhbmdlCmBgYHtyIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEyfQoKdmluZXNfZGF0YXNldF9udW1lcmljX3JlZHVjZWQgJT4lIHNlbGVjdChzZWxlY3RlZF92YXJpYWJsZXMkdmFyaWFibGUpICU+JSByZXNoYXBlMjo6bWVsdCgpICU+JQogIGdncGxvdCgpKwogIGZhY2V0X3dyYXAofnZhcmlhYmxlLHNjYWxlcyA9ICdmcmVlJyxuY29sID0gMTApKwogIGdlb21fYm94cGxvdChhZXMoeD12YXJpYWJsZSx5PXZhbHVlLGZpbGw9dmFyaWFibGUpLGNvbG9yPSdncmF5JykrCiAgZ2dkYXJrOjpkYXJrX3RoZW1lX2J3KCkrCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiLAogICAgICAgIGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0Lng9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueD1lbGVtZW50X2JsYW5rKCkpKwogIHRoZW1lKCBzdHJpcC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemUgPSA2KSkKICAKYGBgCgoKCiMjIFBDQSBFbGxpcHNlcyBmb3IgY2xhc3NlcyB3aXRoIHNlbGVjdGVkICB2YXJpYWJsZXMKYGBge3IgZmlnLmhlaWdodD0xMiwgZmlnLndpZHRoPTEyfQoKY2FsaWRhZF9pbmRleDwtdmluZXNfZGF0YXNldF9udW1lcmljX3JlZHVjZWQgICU+JSAKICAgICAgICAgICAgICAgc2VsZWN0KHNlbGVjdGVkX3ZhcmlhYmxlcyR2YXJpYWJsZSxDYWxpZGFkKQpjYWxpZGFkX2luZGV4PC0gd2hpY2goY29sbmFtZXMoY2FsaWRhZF9pbmRleCk9PSJDYWxpZGFkIikKbGlicmFyeShGYWN0b01pbmVSKQpyZXNfcGNhID0gUENBKHZpbmVzX2RhdGFzZXRfbnVtZXJpY19yZWR1Y2VkICAlPiUgCiAgICAgICAgICAgICAgIHNlbGVjdChzZWxlY3RlZF92YXJpYWJsZXMkdmFyaWFibGUsQ2FsaWRhZCkKICAgICAgICAgICAgICAsIHNjYWxlLnVuaXQ9VFJVRSwgCiAgICAgICAgICAgICAgbmNwPTYsIAogICAgICAgICAgICAgIGdyYXBoPUYsCiAgICAgICAgICAgICAgcXVhbGkuc3VwPWNhbGlkYWRfaW5kZXgsICNjb2xpZCBmb3IgQ2FsaWRhZAogICAgICAgICAgICAgICkKCnBsb3QocmVzX3BjYSxjaG9peD0iaW5kIixoYWJpbGxhZ2U9Y2FsaWRhZF9pbmRleCkKI3BhcihtZmNvbD1jKDEsMikpCnBsb3QocmVzX3BjYSxjaG9peD0idmFyIixoYWJpbGxhZ2U9Im5vbmUiLGludmlzaWJsZSA9ICJpbmQiKSAjIHBhcmEgbGFzIHZhcmlhYmxlcwpwbG90ZWxsaXBzZXMocmVzX3BjYSwgaW52aXNpYmxlPSJpbmQiLHhsaW09YygtNiw2KSx5bGltPWMoLTYsNikpCgoKYGBgCgojIyBDQVJUCmBgYHtyIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD0xMiAsd2FybmluZz1GQUxTRX0KdmluZXNfZGF0YXNldF9ib290c3RyYXA8LXJzYW1wbGU6OmFuYWx5c2lzKHJlc2FtcGxlcyRzcGxpdHNbW3NhbXBsZSgxOjEwMCwxKV1dKQp2YWxfZGF0YXNldCAgPC0gdmluZXNfZGF0YXNldF9ib290c3RyYXAgICAlPiUgZ3JvdXBfYnkoQ2FsaWRhZCkgJT4lIHNhbXBsZV9uKDIpICU+JSB1bmdyb3VwKCkKdHJhaW5fZGF0YXNldCA8LWRwbHlyOjpzZXRkaWZmKHZpbmVzX2RhdGFzZXRfbnVtZXJpY19yZWR1Y2VkLHZhbF9kYXRhc2V0KQoKdHJlZTwtcnBhcnQ6OnJwYXJ0KENhbGlkYWR+LiwKICAgICAgICAgICAgICAgICAgIGRhdGE9dHJhaW5fZGF0YXNldCAlPiUgCiAgICAgICAgICAgICAgICAgICAgIHNlbGVjdChzZWxlY3RlZF92YXJpYWJsZXMkdmFyaWFibGUsQ2FsaWRhZCksCiAgICAgICAgICAgICAgICAgICBjb250cm9sID0gcnBhcnQuY29udHJvbChtaW5zcGxpdCA9IDUpLCkKcnBhcnQucGxvdCh0cmVlLHR5cGU9MSwKICAgICAgICAgICBleHRyYT0xMDEsIGJveC5wYWxldHRlPSJHbkJ1IiwKICAgICAgICAgICBicmFuY2gubHR5PTMsIHNoYWRvdy5jb2w9ImdyYXkiLCBubj1UUlVFCiAgICAgICAgKQpwcmVkaWN0aW9uczwtcHJlZGljdCh0cmVlLHZhbF9kYXRhc2V0LHR5cGUgPSAnY2xhc3MnKQpjYXJldDo6Y29uZnVzaW9uTWF0cml4KHZhbF9kYXRhc2V0JENhbGlkYWQgJT4lIGFzLmZhY3RvcigpLHByZWRpY3Rpb25zKQojcHJpbnRjcCh0cmVlKQoKYGBgCgpgYGB7ciAsd2FybmluZz1GQUxTRX0KdHJlZSR2YXJpYWJsZS5pbXBvcnRhbmNlICU+JSBhcy5kYXRhLmZyYW1lKCkKYGBgCgojIE1PREVMIEVWQUxVQVRJT04KIyMgQ0FSVCAgCiMjIyBDcm9zcyBWYWxpZGF0aW9uIDN4MTAKYGBge3IgLHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoZG9NQykKcmVnaXN0ZXJEb01DKGNvcmVzID0gNCkKY3RybF9mYXN0IDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsCiAgcmVwZWF0cyA9IDMsCiAgbnVtYmVyID0gMTAsCiAgcmV0dXJuUmVzYW1wID0gJ2ZpbmFsJywKICBzYXZlUHJlZGljdGlvbnMgPSAnZmluYWwnLAogIHZlcmJvc2VJdGVyID0gRiwKICBjbGFzc1Byb2JzID0gVFJVRSwKICBhbGxvd1BhcmFsbGVsID0gVAopCgojcmZfZ3JpZCA8LSAgZXhwYW5kLmdyaWQoLm10cnkgPSBjKDUpKQpjYXJ0Rml0IDwtIGNhcmV0Ojp0cmFpbigKICB4ID0gdmluZXNfZGF0YXNldF9udW1lcmljX3JlZHVjZWQgJT4lIAogICAgICAgICAgICAgICAgICAgICBzZWxlY3Qoc2VsZWN0ZWRfdmFyaWFibGVzJHZhcmlhYmxlKSAlPiUgbmEub21pdCgpLAogIHkgPSB2aW5lc19kYXRhc2V0X251bWVyaWNfcmVkdWNlZCAlPiUgCiAgICAgICAgICAgICAgICAgICAgIHNlbGVjdChDYWxpZGFkKSAlPiUgdW5saXN0KCkgJT4lIGFzLmZhY3RvcigpLAogIG1ldGhvZCA9ICJycGFydCIsCiAgdHVuZUxlbmd0aD0xMCwKICAjdHVuZUdyaWQgPSByZl9ncmlkLAogICN2ZXJib3NlID0gMiwKICB0ckNvbnRyb2wgPSBjdHJsX2Zhc3QsCiAgI250cmVlID0gMjAwCikKCmNhcnRGaXQkcmVzdWx0cyAlPiUKICBnZ3Bsb3QoYWVzKHggPSBjcCwgeSA9IEFjY3VyYWN5KSkgKwogIGdlb21fcG9pbnQoY29sb3IgPSAncmVkJykgKwogIGdlb21fZXJyb3JiYXIoCiAgICBhZXMoeW1pbiA9IEFjY3VyYWN5IC0gQWNjdXJhY3lTRCwgeW1heCA9IEFjY3VyYWN5ICsgQWNjdXJhY3lTRCksCiAgICB3aWR0aCA9IC4wMiwKICAgIGNvbG9yID0gJ3llbGxvdycKICApICsKICBnZ2Rhcms6OmRhcmtfdGhlbWVfYncoKSArCiAgbGFicyh0aXRsZT0iQ0FSVDogTWVhbiBhbmQgU3RhbmRhcmQgZGV2aWF0aW9uIGFmdGVyIGh5cGVyLXBhcmFtZXRlciAoY3ApIHR1bmluZyIpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCgpjYXJ0Rml0CmBgYAojIyMgTGVhcm5pbmcgQ3VydmVzCmBgYHtyICx3YXJuaW5nPUZBTFNFfQoKY2FydF9kYXRhIDwtCiAgbGVhcm5pbmdfY3VydmVfZGF0KGRhdCA9IHZpbmVzX2RhdGFzZXRfbnVtZXJpY19yZWR1Y2VkICU+JSAKICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KHNlbGVjdGVkX3ZhcmlhYmxlcyR2YXJpYWJsZSxDYWxpZGFkKSwKICAgICAgICAgICAgICAgICAgICAgb3V0Y29tZSA9ICJDYWxpZGFkIiwKICAgICAgICAgICAgICAgICAgICAgI3Rlc3RfcHJvcCA9IDAuNiwKICAgICAgICAgICAgICAgICAgICAgcHJvcG9ydGlvbiA9IHNlcSgwLjMsMSwwLjEpLAogICAgICAgICAgICAgICAgICAgICAjIyBgdHJhaW5gIGFyZ3VtZW50czEKICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IiwKICAgICAgICAgICAgICAgICAgICAgbWV0cmljID0gIkFjY3VyYWN5IiwKICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY3RybF9mYXN0LAogICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gRikKCmdncGxvdChjYXJ0X2RhdGEsIGFlcyh4ID0gVHJhaW5pbmdfU2l6ZSwgeSA9IEFjY3VyYWN5LCBjb2xvciA9IERhdGEpKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gbG9lc3MsIHNwYW4gPSAuOCkgKwogIGdnZGFyazo6ZGFya190aGVtZV9idygpKwogIGxhYnModGl0bGU9IkNBUlQ6IExlYXJuaW5nIGN1cnZlcyBvbiB0cmFpbmluZyBhbmQgcmVzYW1wbGVkIGRhdGFzZXRzIikKCgpgYGAKCiMjIFJhbmRvbSBGb3Jlc3RzIAojIyMgQ3Jvc3MgVmFsaWRhdGlvbiAzeDEwCmBgYHtyICx3YXJuaW5nPUZBTFNFfQojcmZfZ3JpZCA8LSAgZXhwYW5kLmdyaWQoLm10cnkgPSBjKDUpKQpyZkZpdCA8LSBjYXJldDo6dHJhaW4oCiAgeCA9IHZpbmVzX2RhdGFzZXRfbnVtZXJpY19yZWR1Y2VkICU+JSAKICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KHNlbGVjdGVkX3ZhcmlhYmxlcyR2YXJpYWJsZSkgJT4lIG5hLm9taXQoKSwKICB5ID0gdmluZXNfZGF0YXNldF9udW1lcmljX3JlZHVjZWQgJT4lIAogICAgICAgICAgICAgICAgICAgICBzZWxlY3QoQ2FsaWRhZCkgJT4lIHVubGlzdCgpICU+JSBhcy5mYWN0b3IoKSwKICBtZXRob2QgPSAicmYiLAogIHR1bmVMZW5ndGg9MTAsCiAgI3R1bmVHcmlkID0gcmZfZ3JpZCwKICAjdmVyYm9zZSA9IDIsCiAgdHJDb250cm9sID0gY3RybF9mYXN0LAogICNudHJlZSA9IDIwMAopCgpyZkZpdCRyZXN1bHRzICU+JQogIGdncGxvdChhZXMoeCA9IG10cnksIHkgPSBBY2N1cmFjeSkpICsKICBnZW9tX3BvaW50KGNvbG9yID0gJ3JlZCcpICsKICBnZW9tX2Vycm9yYmFyKAogICAgYWVzKHltaW4gPSBBY2N1cmFjeSAtIEFjY3VyYWN5U0QsIHltYXggPSBBY2N1cmFjeSArIEFjY3VyYWN5U0QpLAogICAgd2lkdGggPSAuMDIsCiAgICBjb2xvciA9ICd5ZWxsb3cnCiAgKSArCiAgZ2dkYXJrOjpkYXJrX3RoZW1lX2J3KCkgKwogIGxhYnModGl0bGU9IlJhbmRvbSBGb3Jlc3Q6IE1lYW4gYW5kIFN0YW5kYXJkIGRldmlhdGlvbiBhZnRlciBoeXBlci1wYXJhbWV0ZXIgKG10cnkpIHR1bmluZyIpKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCgpyZkZpdApgYGAKIyMjIExlYXJuaW5nIEN1cnZlcyAKYGBge3IgLHdhcm5pbmc9RkFMU0V9CnNldC5zZWVkKDIxMDUyMDI1KQpyZl9kYXRhIDwtCiAgbGVhcm5pbmdfY3VydmVfZGF0KGRhdCA9IHZpbmVzX2RhdGFzZXRfbnVtZXJpY19yZWR1Y2VkICU+JSAKICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KHNlbGVjdGVkX3ZhcmlhYmxlcyR2YXJpYWJsZSxDYWxpZGFkKSwKICAgICAgICAgICAgICAgICAgICAgb3V0Y29tZSA9ICJDYWxpZGFkIiwKICAgICAgICAgICAgICAgICAgICAgI3Rlc3RfcHJvcCA9IDAuNiwKICAgICAgICAgICAgICAgICAgICAgcHJvcG9ydGlvbiA9IHNlcSgwLjMsMSwwLjEpLAogICAgICAgICAgICAgICAgICAgICAjIyBgdHJhaW5gIGFyZ3VtZW50czEKICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJhbmdlciIsCiAgICAgICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJBY2N1cmFjeSIsCiAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGN0cmxfZmFzdCwKICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEYpCgpnZ3Bsb3QocmZfZGF0YSwgYWVzKHggPSBUcmFpbmluZ19TaXplLCB5ID0gQWNjdXJhY3ksIGNvbG9yID0gRGF0YSkpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSBsb2Vzcywgc3BhbiA9IC44KSArCiAgZ2dkYXJrOjpkYXJrX3RoZW1lX2J3KCkrCiAgbGFicyh0aXRsZT0iUmFuZG9tIEZvcmVzdHM6IExlYXJuaW5nIGN1cnZlcyBvbiB0cmFpbmluZyBhbmQgcmVzYW1wbGVkIGRhdGFzZXRzIikKYGBgCgojIyMgUHJlZGljdGlvbnMKCmBgYHtyICx3YXJuaW5nPUZBTFNFfQp0ZXN0X2RhdGFzZXRfbnVtZXJpYzwtdGVzdF9kYXRhc2V0ICU+JSBzZWxlY3RfaWYofmNsYXNzKC4pID09ICdudW1lcmljJykKdGVzdF9kYXRhc2V0X251bWVyaWNfaW1wdXRlZCA8LSBrTk4odGVzdF9kYXRhc2V0X251bWVyaWMpCnRlc3RfZGF0YXNldF9udW1lcmljPC10ZXN0X2RhdGFzZXRfbnVtZXJpY19pbXB1dGVkICU+JSBzZWxlY3QoLWVuZHNfd2l0aCgiX2ltcCIpKQpwcmVkczwtcHJlZGljdChyZkZpdCx0ZXN0X2RhdGFzZXRfbnVtZXJpYyAlPiUgCiAgICAgICAgICAgICAgICAgICAgIHNlbGVjdChzZWxlY3RlZF92YXJpYWJsZXMkdmFyaWFibGUpKQoKY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeChkYXRhID0gYXMuZmFjdG9yKHByZWRzKSwgCiAgICAgICAgICAgICAgICAgICAgICAgcmVmZXJlbmNlID0gYXMuZmFjdG9yKHRlc3RfZGF0YXNldCRDYWxpZGFkKQogICAgICAgICAgICAgICAgICAgICAgICkKYGBgCgoKIyMgR0xNTkVUCiMjIyBDcm9zcyBWYWxpZGF0aW9uIDN4MTAKYGBge3IgZXZhbD1GQUxTRSwgZmlnLmhlaWdodD00LCBmaWcud2lkdGg9MTIsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CiNyZl9ncmlkIDwtICBleHBhbmQuZ3JpZCgubXRyeSA9IGMoNSkpCmdsbUZpdCA8LSBjYXJldDo6dHJhaW4oCiAgeCA9IHZpbmVzX2RhdGFzZXRfbnVtZXJpY19yZWR1Y2VkICU+JSAKICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KHNlbGVjdGVkX3ZhcmlhYmxlcyR2YXJpYWJsZSkgJT4lIG5hLm9taXQoKSwKICB5ID0gdmluZXNfZGF0YXNldF9udW1lcmljX3JlZHVjZWQgJT4lIAogICAgICAgICAgICAgICAgICAgICBzZWxlY3QoQ2FsaWRhZCkgJT4lIHVubGlzdCgpICU+JSBhcy5mYWN0b3IoKSwKICBtZXRob2QgPSAiZ2xtbmV0IiwKICB0dW5lTGVuZ3RoPTEwLAogICN0dW5lR3JpZCA9IHJmX2dyaWQsCiAgI3ZlcmJvc2UgPSAyLAogIHRyQ29udHJvbCA9IGN0cmxfZmFzdCwKICAjbnRyZWUgPSAyMDAKKQoKZ2xtX3Jlc3VsdHM8LWdsbUZpdCRyZXN1bHRzICU+JSBtdXRhdGVfYXQoMiwgcm91bmQsIDQpICU+JSBhcnJhbmdlKGRlc2MoQWNjdXJhY3kpKSAlPiV0aWR5cjo6dW5pdGUocGFyYW1ldGVycyxhbHBoYTpsYW1iZGEpIApnbG1fcmVzdWx0cyRwYXJhbWV0ZXJzPC1mYWN0b3IoZ2xtX3Jlc3VsdHMkcGFyYW1ldGVycyxsZXZlbHM9dW5pcXVlKGdsbV9yZXN1bHRzJHBhcmFtZXRlcnMpKQpnbG1fcmVzdWx0cyAlPiUgZ2dwbG90KGFlcyh4ID0gcGFyYW1ldGVycywgeSA9IEFjY3VyYWN5KSkgKwogIGdlb21fcG9pbnQoY29sb3IgPSAncmVkJykgKwogIGdlb21fZXJyb3JiYXIoCiAgICBhZXMoeW1pbiA9IEFjY3VyYWN5IC0gQWNjdXJhY3lTRCwgeW1heCA9IEFjY3VyYWN5ICsgQWNjdXJhY3lTRCksCiAgICB3aWR0aCA9IDAuMDIsCiAgICBjb2xvciA9ICd5ZWxsb3cnCiAgKSArCiAgZ2dkYXJrOjpkYXJrX3RoZW1lX2J3KCkgKwogIGxhYnModGl0bGU9IkVsYXN0aWMgTmV0OiBNZWFuIGFuZCBTdGFuZGFyZCBkZXZpYXRpb24gYWZ0ZXIgaHlwZXItcGFyYW1ldGVyIChtdHJ5KSB0dW5pbmciKSsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKSsKICB0aGVtZShheGlzLnRleHQ9ZWxlbWVudF90ZXh0KHNpemU9NikpCgpnbG1GaXQKYGBgCgoKIyMjIExlYXJuaW5nIEN1cnZlcwpgYGB7ciBldmFsPUZBTFNFLCBtZXNzYWdlPVRSVUUsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CnNldC5zZWVkKDIxMDUyMDI2KQojc2V0LnNlZWQoMjEwNTIwMjUpCmdsbW5ldF9kYXRhIDwtCiAgbGVhcm5pbmdfY3VydmVfZGF0KGRhdCA9IHZpbmVzX2RhdGFzZXRfbnVtZXJpY19yZWR1Y2VkICU+JSAKICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KHNlbGVjdGVkX3ZhcmlhYmxlcyR2YXJpYWJsZSxDYWxpZGFkKSwKICAgICAgICAgICAgICAgICAgICAgb3V0Y29tZSA9ICJDYWxpZGFkIiwKICAgICAgICAgICAgICAgICAgICAgI3Rlc3RfcHJvcCA9IDAuNiwKICAgICAgICAgICAgICAgICAgICAgcHJvcG9ydGlvbiA9IHNlcSgwLjMsMSwwLjEpLAogICAgICAgICAgICAgICAgICAgICAjIyBgdHJhaW5gIGFyZ3VtZW50czEKICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImdsbW5ldCIsCiAgICAgICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJBY2N1cmFjeSIsCiAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGN0cmxfZmFzdCwKICAgICAgICAgICAgICAgICAgICAgdmVyYm9zZSA9IEYpCgpnZ3Bsb3QoZ2xtbmV0X2RhdGEsIGFlcyh4ID0gVHJhaW5pbmdfU2l6ZSwgeSA9IEFjY3VyYWN5LCBjb2xvciA9IERhdGEpKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gbG9lc3MsIHNwYW4gPSAuOCkgKwogIGdnZGFyazo6ZGFya190aGVtZV9idygpKwogIGxhYnModGl0bGU9IkVsYXN0aWMgTmV0OiBMZWFybmluZyBjdXJ2ZXMgb24gdHJhaW5pbmcgYW5kIHJlc2FtcGxlZCBkYXRhc2V0cyIpCmBgYAo=