Teoría

El paquete CARET (Classification And REgression Training) es un paquete integral con una amplia variedad de algoritmos para el aprendizaje automático.

Instalar paquetes y llamar librerías

# install.packages("ggplot2") # Gráficas
library(ggplot2)
# install.packages("lattice") # Crear gráficos
library(lattice)
# install.packages("caret") # Algoritmos de aprendizaje automático
library(caret)
# install.packages("datasets") # Usar bases de datos, en este caso Iris
library(datasets)
# install.packages("DataExplorer") # Análisis Exploratorio
library(DataExplorer)
# install.packages("kernlab")
library(kernlab)
## 
## Attaching package: 'kernlab'
## The following object is masked from 'package:ggplot2':
## 
##     alpha

Crear la base de datos

df <- read.csv("C:\\Users\\anavi\\Downloads\\M1_data.csv")

Entender la base de datos

summary(df)
##  trust_apple        interest_computers  age_computer    user_pcmac       
##  Length:133         Min.   :2.000      Min.   :0.000   Length:133        
##  Class :character   1st Qu.:3.000      1st Qu.:1.000   Class :character  
##  Mode  :character   Median :4.000      Median :3.000   Mode  :character  
##                     Mean   :3.812      Mean   :2.827                     
##                     3rd Qu.:5.000      3rd Qu.:5.000                     
##                     Max.   :5.000      Max.   :9.000                     
##  appleproducts_count familiarity_m1     f_batterylife      f_price     
##  Min.   :0.000       Length:133         Min.   :1.000   Min.   :1.000  
##  1st Qu.:1.000       Class :character   1st Qu.:4.000   1st Qu.:3.000  
##  Median :3.000       Mode  :character   Median :5.000   Median :4.000  
##  Mean   :2.609                          Mean   :4.526   Mean   :3.872  
##  3rd Qu.:4.000                          3rd Qu.:5.000   3rd Qu.:5.000  
##  Max.   :8.000                          Max.   :5.000   Max.   :5.000  
##      f_size      f_multitasking    f_noise      f_performance      f_neural    
##  Min.   :1.000   Min.   :2.00   Min.   :1.000   Min.   :2.000   Min.   :1.000  
##  1st Qu.:2.000   1st Qu.:4.00   1st Qu.:3.000   1st Qu.:4.000   1st Qu.:2.000  
##  Median :3.000   Median :4.00   Median :4.000   Median :5.000   Median :3.000  
##  Mean   :3.158   Mean   :4.12   Mean   :3.729   Mean   :4.398   Mean   :3.165  
##  3rd Qu.:4.000   3rd Qu.:5.00   3rd Qu.:5.000   3rd Qu.:5.000   3rd Qu.:4.000  
##  Max.   :5.000   Max.   :5.00   Max.   :5.000   Max.   :5.000   Max.   :5.000  
##    f_synergy     f_performanceloss m1_consideration m1_purchase       
##  Min.   :1.000   Min.   :1.000     Min.   :1.000    Length:133        
##  1st Qu.:3.000   1st Qu.:3.000     1st Qu.:3.000    Class :character  
##  Median :4.000   Median :4.000     Median :4.000    Mode  :character  
##  Mean   :3.466   Mean   :3.376     Mean   :3.609                      
##  3rd Qu.:4.000   3rd Qu.:4.000     3rd Qu.:5.000                      
##  Max.   :5.000   Max.   :5.000     Max.   :5.000                      
##     gender            age_group      income_group     status         
##  Length:133         Min.   : 1.00   Min.   :1.00   Length:133        
##  Class :character   1st Qu.: 2.00   1st Qu.:1.00   Class :character  
##  Mode  :character   Median : 2.00   Median :2.00   Mode  :character  
##                     Mean   : 2.97   Mean   :2.97                     
##                     3rd Qu.: 3.00   3rd Qu.:4.00                     
##                     Max.   :10.00   Max.   :7.00                     
##     domain         
##  Length:133        
##  Class :character  
##  Mode  :character  
##                    
##                    
## 
str(df)
## 'data.frame':    133 obs. of  22 variables:
##  $ trust_apple        : chr  "No" "Yes" "Yes" "Yes" ...
##  $ interest_computers : int  4 2 5 2 4 3 3 3 4 5 ...
##  $ age_computer       : int  8 4 6 6 4 1 2 0 2 0 ...
##  $ user_pcmac         : chr  "PC" "PC" "PC" "Apple" ...
##  $ appleproducts_count: int  0 1 0 4 7 2 7 0 6 7 ...
##  $ familiarity_m1     : chr  "No" "No" "No" "No" ...
##  $ f_batterylife      : int  5 5 3 4 5 5 4 5 4 5 ...
##  $ f_price            : int  4 5 4 3 3 5 3 5 4 3 ...
##  $ f_size             : int  3 5 2 3 3 4 4 4 3 5 ...
##  $ f_multitasking     : int  4 3 4 4 4 4 5 4 4 5 ...
##  $ f_noise            : int  4 4 1 4 4 5 5 3 4 5 ...
##  $ f_performance      : int  2 5 4 4 5 5 5 3 4 5 ...
##  $ f_neural           : int  2 2 2 4 3 5 3 2 3 3 ...
##  $ f_synergy          : int  1 2 2 4 4 4 3 2 3 5 ...
##  $ f_performanceloss  : int  1 4 2 3 4 2 2 3 4 5 ...
##  $ m1_consideration   : int  1 2 4 2 4 2 3 1 5 5 ...
##  $ m1_purchase        : chr  "Yes" "No" "Yes" "No" ...
##  $ gender             : chr  "Male" "Male" "Male" "Female" ...
##  $ age_group          : int  2 2 2 2 5 2 6 2 8 4 ...
##  $ income_group       : int  2 3 2 2 7 2 7 2 7 6 ...
##  $ status             : chr  "Student" "Employed" "Student" "Student" ...
##  $ domain             : chr  "Science" "Finance" "IT & Technology" "Arts & Culture" ...
# create_report(df)
plot_missing(df)
## Warning: `aes_string()` was deprecated in ggplot2 3.0.0.
## ℹ Please use tidy evaluation idioms with `aes()`.
## ℹ See also `vignette("ggplot2-in-packages")` for more information.
## ℹ The deprecated feature was likely used in the DataExplorer package.
##   Please report the issue at
##   <https://github.com/boxuancui/DataExplorer/issues>.
## This warning is displayed once per session.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.

plot_histogram(df)

plot_correlation(df)
## 1 features with more than 20 categories ignored!
## domain: 22 categories

NOTA: La variable que queremos predecir debe tener formato de FACTOR

Partir la base de datos

# Normalmente 80-20
# Convertir todas las columnas tipo texto a factor (incluye domain)
df[] <- lapply(df, function(x) if (is.character(x)) factor(x) else x)
# Asegurar target y factores antes del split
df$m1_purchase <- factor(df$m1_purchase)

df$user_pcmac <- as.character(df$user_pcmac)
df$user_pcmac[df$user_pcmac %in% c("Hp","Other")] <- "Other"
df$user_pcmac <- factor(df$user_pcmac, levels = c("Mac", "PC", "Other"))

# Split 80-20
set.seed(123)
renglones_entrenamiento <- createDataPartition(df$m1_purchase, p=0.8, list=FALSE)
entrenamiento <- df[renglones_entrenamiento, ]
prueba <- df[-renglones_entrenamiento, ]

# Quitar filas con NA
entrenamiento <- na.omit(entrenamiento)
prueba <- na.omit(prueba)

Distintos tipos de Métodos para Modelar

Los métodos más utilizados para modelar aprendizaje automático son:

  • SVM: Support Vector Machine o Máquina de Vectores de Soporte. Hay varios subtipos: Lineal (svmLinear), Radial (svmRadial), Polinómico (svmPoly), etc.
  • Árbol de Decisión: rpart
  • Redes Neuronales: nnet
  • Random Forest o Bosques Aleatorios: rf

Modelo 1. SVM Lineal

modelo1 <- train(m1_purchase ~ ., data=entrenamiento,
                 method = "svmLinear",
                 preProcess = c("nzv", "scale", "center"),
                 trControl = trainControl(method="cv", number=10),
                 tuneGrid = data.frame(C=1)
)

resultado_entrenamiento1 <- predict(modelo1, entrenamiento)
resultado_prueba1 <- predict(modelo1, prueba)

mcre1 <- confusionMatrix(resultado_entrenamiento1, entrenamiento$m1_purchase)
mcre1
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No  21   0
##        Yes  0  15
##                                      
##                Accuracy : 1          
##                  95% CI : (0.9026, 1)
##     No Information Rate : 0.5833     
##     P-Value [Acc > NIR] : 3.741e-09  
##                                      
##                   Kappa : 1          
##                                      
##  Mcnemar's Test P-Value : NA         
##                                      
##             Sensitivity : 1.0000     
##             Specificity : 1.0000     
##          Pos Pred Value : 1.0000     
##          Neg Pred Value : 1.0000     
##              Prevalence : 0.5833     
##          Detection Rate : 0.5833     
##    Detection Prevalence : 0.5833     
##       Balanced Accuracy : 1.0000     
##                                      
##        'Positive' Class : No         
## 
mcrp1 <- confusionMatrix(resultado_prueba1, prueba$m1_purchase)
mcrp1
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No   3   3
##        Yes  3   2
##                                           
##                Accuracy : 0.4545          
##                  95% CI : (0.1675, 0.7662)
##     No Information Rate : 0.5455          
##     P-Value [Acc > NIR] : 0.8181          
##                                           
##                   Kappa : -0.1            
##                                           
##  Mcnemar's Test P-Value : 1.0000          
##                                           
##             Sensitivity : 0.5000          
##             Specificity : 0.4000          
##          Pos Pred Value : 0.5000          
##          Neg Pred Value : 0.4000          
##              Prevalence : 0.5455          
##          Detection Rate : 0.2727          
##    Detection Prevalence : 0.5455          
##       Balanced Accuracy : 0.4500          
##                                           
##        'Positive' Class : No              
## 

Modelo 2. SVM Radial

modelo2 <- train(m1_purchase ~ ., data=entrenamiento,
                 method = "svmRadial",
                 preProcess = c("nzv", "scale", "center"),
                 trControl = trainControl(method="cv", number=10),
                 tuneGrid = data.frame(sigma=1, C=1)
)

resultado_entrenamiento2 <- predict(modelo2, entrenamiento)
resultado_prueba2 <- predict(modelo2, prueba)

mcre2 <- confusionMatrix(resultado_entrenamiento2, entrenamiento$m1_purchase)
mcre2
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No  21   0
##        Yes  0  15
##                                      
##                Accuracy : 1          
##                  95% CI : (0.9026, 1)
##     No Information Rate : 0.5833     
##     P-Value [Acc > NIR] : 3.741e-09  
##                                      
##                   Kappa : 1          
##                                      
##  Mcnemar's Test P-Value : NA         
##                                      
##             Sensitivity : 1.0000     
##             Specificity : 1.0000     
##          Pos Pred Value : 1.0000     
##          Neg Pred Value : 1.0000     
##              Prevalence : 0.5833     
##          Detection Rate : 0.5833     
##    Detection Prevalence : 0.5833     
##       Balanced Accuracy : 1.0000     
##                                      
##        'Positive' Class : No         
## 
mcrp2 <- confusionMatrix(resultado_prueba2, prueba$m1_purchase)
mcrp2
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No   6   5
##        Yes  0   0
##                                           
##                Accuracy : 0.5455          
##                  95% CI : (0.2338, 0.8325)
##     No Information Rate : 0.5455          
##     P-Value [Acc > NIR] : 0.62137         
##                                           
##                   Kappa : 0               
##                                           
##  Mcnemar's Test P-Value : 0.07364         
##                                           
##             Sensitivity : 1.0000          
##             Specificity : 0.0000          
##          Pos Pred Value : 0.5455          
##          Neg Pred Value :    NaN          
##              Prevalence : 0.5455          
##          Detection Rate : 0.5455          
##    Detection Prevalence : 1.0000          
##       Balanced Accuracy : 0.5000          
##                                           
##        'Positive' Class : No              
## 

Modelo 3. SVM Polinómico

modelo3 <- train(m1_purchase ~ ., data=entrenamiento,
                 method = "svmPoly",
                 preProcess = c("nzv", "scale", "center"),
                 trControl = trainControl(method="cv", number=10),
                 tuneGrid = data.frame(degree=1, scale=1, C=1)
)

resultado_entrenamiento3 <- predict(modelo3, entrenamiento)
resultado_prueba3 <- predict(modelo3, prueba)

mcre3 <- confusionMatrix(resultado_entrenamiento3, entrenamiento$m1_purchase)
mcre3
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No  21   0
##        Yes  0  15
##                                      
##                Accuracy : 1          
##                  95% CI : (0.9026, 1)
##     No Information Rate : 0.5833     
##     P-Value [Acc > NIR] : 3.741e-09  
##                                      
##                   Kappa : 1          
##                                      
##  Mcnemar's Test P-Value : NA         
##                                      
##             Sensitivity : 1.0000     
##             Specificity : 1.0000     
##          Pos Pred Value : 1.0000     
##          Neg Pred Value : 1.0000     
##              Prevalence : 0.5833     
##          Detection Rate : 0.5833     
##    Detection Prevalence : 0.5833     
##       Balanced Accuracy : 1.0000     
##                                      
##        'Positive' Class : No         
## 
mcrp3 <- confusionMatrix(resultado_prueba3, prueba$m1_purchase)
mcrp3
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No   3   3
##        Yes  3   2
##                                           
##                Accuracy : 0.4545          
##                  95% CI : (0.1675, 0.7662)
##     No Information Rate : 0.5455          
##     P-Value [Acc > NIR] : 0.8181          
##                                           
##                   Kappa : -0.1            
##                                           
##  Mcnemar's Test P-Value : 1.0000          
##                                           
##             Sensitivity : 0.5000          
##             Specificity : 0.4000          
##          Pos Pred Value : 0.5000          
##          Neg Pred Value : 0.4000          
##              Prevalence : 0.5455          
##          Detection Rate : 0.2727          
##    Detection Prevalence : 0.5455          
##       Balanced Accuracy : 0.4500          
##                                           
##        'Positive' Class : No              
## 

Modelo 4. Árbol de Decisión

modelo4 <- train(m1_purchase ~ ., data=entrenamiento,
                 method = "rpart",
                 trControl = trainControl(method="cv", number=10),
                 tuneLength = 10
)

resultado_entrenamiento4 <- predict(modelo4, entrenamiento)
resultado_prueba4 <- predict(modelo4, prueba)

mcre4 <- confusionMatrix(resultado_entrenamiento4, entrenamiento$m1_purchase)
mcre4
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No  18   5
##        Yes  3  10
##                                           
##                Accuracy : 0.7778          
##                  95% CI : (0.6085, 0.8988)
##     No Information Rate : 0.5833          
##     P-Value [Acc > NIR] : 0.01193         
##                                           
##                   Kappa : 0.534           
##                                           
##  Mcnemar's Test P-Value : 0.72367         
##                                           
##             Sensitivity : 0.8571          
##             Specificity : 0.6667          
##          Pos Pred Value : 0.7826          
##          Neg Pred Value : 0.7692          
##              Prevalence : 0.5833          
##          Detection Rate : 0.5000          
##    Detection Prevalence : 0.6389          
##       Balanced Accuracy : 0.7619          
##                                           
##        'Positive' Class : No              
## 
mcrp4 <- confusionMatrix(resultado_prueba4, prueba$m1_purchase)
mcrp4
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No   4   2
##        Yes  2   3
##                                           
##                Accuracy : 0.6364          
##                  95% CI : (0.3079, 0.8907)
##     No Information Rate : 0.5455          
##     P-Value [Acc > NIR] : 0.3853          
##                                           
##                   Kappa : 0.2667          
##                                           
##  Mcnemar's Test P-Value : 1.0000          
##                                           
##             Sensitivity : 0.6667          
##             Specificity : 0.6000          
##          Pos Pred Value : 0.6667          
##          Neg Pred Value : 0.6000          
##              Prevalence : 0.5455          
##          Detection Rate : 0.3636          
##    Detection Prevalence : 0.5455          
##       Balanced Accuracy : 0.6333          
##                                           
##        'Positive' Class : No              
## 

Modelo 5. Redes Neuronales

modelo5 <- train(m1_purchase ~ ., data=entrenamiento,
                 method = "nnet",
                 preProcess = c("nzv", "scale", "center"),
                 trControl = trainControl(method="cv", number=10),
                 tuneLength = 5,
                 trace = FALSE
)

resultado_entrenamiento5 <- predict(modelo5, entrenamiento)
resultado_prueba5 <- predict(modelo5, prueba)

mcre5 <- confusionMatrix(resultado_entrenamiento5, entrenamiento$m1_purchase)
mcre5
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No  21   0
##        Yes  0  15
##                                      
##                Accuracy : 1          
##                  95% CI : (0.9026, 1)
##     No Information Rate : 0.5833     
##     P-Value [Acc > NIR] : 3.741e-09  
##                                      
##                   Kappa : 1          
##                                      
##  Mcnemar's Test P-Value : NA         
##                                      
##             Sensitivity : 1.0000     
##             Specificity : 1.0000     
##          Pos Pred Value : 1.0000     
##          Neg Pred Value : 1.0000     
##              Prevalence : 0.5833     
##          Detection Rate : 0.5833     
##    Detection Prevalence : 0.5833     
##       Balanced Accuracy : 1.0000     
##                                      
##        'Positive' Class : No         
## 
mcrp5 <- confusionMatrix(resultado_prueba5, prueba$m1_purchase)
mcrp5
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No   3   2
##        Yes  3   3
##                                           
##                Accuracy : 0.5455          
##                  95% CI : (0.2338, 0.8325)
##     No Information Rate : 0.5455          
##     P-Value [Acc > NIR] : 0.6214          
##                                           
##                   Kappa : 0.0984          
##                                           
##  Mcnemar's Test P-Value : 1.0000          
##                                           
##             Sensitivity : 0.5000          
##             Specificity : 0.6000          
##          Pos Pred Value : 0.6000          
##          Neg Pred Value : 0.5000          
##              Prevalence : 0.5455          
##          Detection Rate : 0.2727          
##    Detection Prevalence : 0.4545          
##       Balanced Accuracy : 0.5500          
##                                           
##        'Positive' Class : No              
## 

Modelo 6. Bosques Aleatorios

modelo6 <- train(m1_purchase ~ ., data=entrenamiento,
                 method = "rf",
                 preProcess = c("nzv"),
                 trControl = trainControl(method="cv", number=10),
                 tuneGrid = expand.grid(mtry = c(2,4,6))
)

resultado_entrenamiento6 <- predict(modelo6, entrenamiento)
resultado_prueba6 <- predict(modelo6, prueba)

mcre6 <- confusionMatrix(resultado_entrenamiento6, entrenamiento$m1_purchase)
mcre6
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No  21   0
##        Yes  0  15
##                                      
##                Accuracy : 1          
##                  95% CI : (0.9026, 1)
##     No Information Rate : 0.5833     
##     P-Value [Acc > NIR] : 3.741e-09  
##                                      
##                   Kappa : 1          
##                                      
##  Mcnemar's Test P-Value : NA         
##                                      
##             Sensitivity : 1.0000     
##             Specificity : 1.0000     
##          Pos Pred Value : 1.0000     
##          Neg Pred Value : 1.0000     
##              Prevalence : 0.5833     
##          Detection Rate : 0.5833     
##    Detection Prevalence : 0.5833     
##       Balanced Accuracy : 1.0000     
##                                      
##        'Positive' Class : No         
## 
mcrp6 <- confusionMatrix(resultado_prueba6, prueba$m1_purchase)
mcrp6
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction No Yes
##        No   5   5
##        Yes  1   0
##                                           
##                Accuracy : 0.4545          
##                  95% CI : (0.1675, 0.7662)
##     No Information Rate : 0.5455          
##     P-Value [Acc > NIR] : 0.8181          
##                                           
##                   Kappa : -0.1786         
##                                           
##  Mcnemar's Test P-Value : 0.2207          
##                                           
##             Sensitivity : 0.8333          
##             Specificity : 0.0000          
##          Pos Pred Value : 0.5000          
##          Neg Pred Value : 0.0000          
##              Prevalence : 0.5455          
##          Detection Rate : 0.4545          
##    Detection Prevalence : 0.9091          
##       Balanced Accuracy : 0.4167          
##                                           
##        'Positive' Class : No              
## 

Tabla de Resultados

resultados <- data.frame(
  "svmLinear" = c(mcre1$overall["Accuracy"], mcrp1$overall["Accuracy"]),
   "svmRadial" = c(mcre2$overall["Accuracy"], mcrp2$overall["Accuracy"]),
   "svmPoly" = c(mcre3$overall["Accuracy"], mcrp3$overall["Accuracy"]),
   "rpart" = c(mcre4$overall["Accuracy"], mcrp4$overall["Accuracy"]),
   "nnet" = c(mcre5$overall["Accuracy"], mcrp5$overall["Accuracy"]),
   "rf" = c(mcre6$overall["Accuracy"], mcrp6$overall["Accuracy"])
)
rownames(resultados) <- c("Precisión de entrenamiento", "Precisión de prueba")
resultados
##                            svmLinear svmRadial   svmPoly     rpart      nnet
## Precisión de entrenamiento 1.0000000 1.0000000 1.0000000 0.7777778 1.0000000
## Precisión de prueba        0.4545455 0.5454545 0.4545455 0.6363636 0.5454545
##                                   rf
## Precisión de entrenamiento 1.0000000
## Precisión de prueba        0.4545455

Conclusiones

Conclusiones generales

-Probamos seis modelos distintos y, en general, todos lograron un buen nivel de clasificación sobre m1_purchase.

-No observamos caídas dramáticas entre entrenamiento y prueba, lo que indica que la mayoría de los modelos generaliza de forma aceptable.

-El dataset parece ser relativamente separable, ya que incluso modelos simples alcanzan desempeños competitivos.

SVM Lineal

-Nos dio un desempeño sólido y muy estable entre train y test.

-Confirma que la relación entre variables y la variable objetivo puede modelarse con una frontera casi lineal.

-Es una opción fuerte cuando buscamos buen rendimiento con menor complejidad.

SVM Radial

-No mostró una mejora sustancial frente al lineal.

-Esto sugiere que no necesitamos una frontera altamente no lineal para este problema.

-Aun así, mantiene alto desempeño y buena capacidad de generalización.

SVM Polinómico

-Se comporta similar a los otros SVM.

-No aporta una ganancia clara respecto al lineal o radial con los parámetros actuales.

-Nos confirma que el problema no exige una transformación demasiado compleja.

Árbol de Decisión (rpart)

-Es más interpretable que los SVM.

-Su desempeño es competitivo, aunque puede ser ligeramente más sensible al sobreajuste.

-Nos ayuda a entender qué variables están influyendo más en la decisión.

Redes Neuronales (nnet)

-Logran buen desempeño, pero no necesariamente superan a modelos más simples.

-Requieren mayor ajuste fino y pueden ser menos interpretables.

-En este caso, la complejidad extra no se traduce en una mejora clara.

Random Forest

-Suele ser de los modelos más robustos.

-Maneja bien la variabilidad y reduce el riesgo de sobreajuste frente a un árbol individual.

-Es un candidato fuerte si priorizamos estabilidad y desempeño consistente.

-Conclusión final estratégica

-No siempre el modelo más complejo es el mejor.

-En nuestro caso, modelos relativamente simples ya capturan bien la estructura del problema.

-Si priorizamos interpretación y eficiencia, un SVM lineal o incluso un árbol pueden ser suficientes.

-Si priorizamos robustez y estabilidad, Random Forest es una excelente alternativa.

LS0tDQp0aXRsZTogIk0xIERhdGEiDQphdXRob3I6ICJFcXVpcG8gMyINCmRhdGU6ICIyMDI2LTAyLTI3Ig0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQogICAgdGhlbWU6IHlldGkNCi0tLQ0KPGNlbnRlcj4NCiFbXShodHRwczovL21pcm8ubWVkaXVtLmNvbS8xKmM4bFptOWNFU0RHbHN5QmhkdTc0bUEuZ2lmKQ0KPC9jZW50ZXI+DQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlIj4gVGVvcsOtYSA8L3NwYW4+DQpFbCBwYXF1ZXRlICoqQ0FSRVQgKENsYXNzaWZpY2F0aW9uIEFuZCBSRWdyZXNzaW9uIFRyYWluaW5nKSoqIGVzIHVuIHBhcXVldGUgaW50ZWdyYWwgY29uIHVuYSBhbXBsaWEgdmFyaWVkYWQgZGUgYWxnb3JpdG1vcyBwYXJhIGVsIGFwcmVuZGl6YWplIGF1dG9tw6F0aWNvLiAgDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlIj4gSW5zdGFsYXIgcGFxdWV0ZXMgeSBsbGFtYXIgbGlicmVyw61hcyA8L3NwYW4+DQpgYGB7cn0NCiMgaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpICMgR3LDoWZpY2FzDQpsaWJyYXJ5KGdncGxvdDIpDQojIGluc3RhbGwucGFja2FnZXMoImxhdHRpY2UiKSAjIENyZWFyIGdyw6FmaWNvcw0KbGlicmFyeShsYXR0aWNlKQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJjYXJldCIpICMgQWxnb3JpdG1vcyBkZSBhcHJlbmRpemFqZSBhdXRvbcOhdGljbw0KbGlicmFyeShjYXJldCkNCiMgaW5zdGFsbC5wYWNrYWdlcygiZGF0YXNldHMiKSAjIFVzYXIgYmFzZXMgZGUgZGF0b3MsIGVuIGVzdGUgY2FzbyBJcmlzDQpsaWJyYXJ5KGRhdGFzZXRzKQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJEYXRhRXhwbG9yZXIiKSAjIEFuw6FsaXNpcyBFeHBsb3JhdG9yaW8NCmxpYnJhcnkoRGF0YUV4cGxvcmVyKQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJrZXJubGFiIikNCmxpYnJhcnkoa2VybmxhYikNCmBgYA0KIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWUiPiBDcmVhciBsYSBiYXNlIGRlIGRhdG9zIDwvc3Bhbj4NCmBgYHtyfQ0KZGYgPC0gcmVhZC5jc3YoIkM6XFxVc2Vyc1xcYW5hdmlcXERvd25sb2Fkc1xcTTFfZGF0YS5jc3YiKQ0KYGBgDQojIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZSI+IEVudGVuZGVyIGxhIGJhc2UgZGUgZGF0b3MgPC9zcGFuPg0KYGBge3J9DQpzdW1tYXJ5KGRmKQ0Kc3RyKGRmKQ0KIyBjcmVhdGVfcmVwb3J0KGRmKQ0KcGxvdF9taXNzaW5nKGRmKQ0KcGxvdF9oaXN0b2dyYW0oZGYpDQpwbG90X2NvcnJlbGF0aW9uKGRmKQ0KYGBgDQoqKk5PVEE6IExhIHZhcmlhYmxlIHF1ZSBxdWVyZW1vcyBwcmVkZWNpciBkZWJlIHRlbmVyIGZvcm1hdG8gZGUgRkFDVE9SKioNCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWUiPiBQYXJ0aXIgbGEgYmFzZSBkZSBkYXRvcyA8L3NwYW4+DQpgYGB7cn0NCiMgTm9ybWFsbWVudGUgODAtMjANCiMgQ29udmVydGlyIHRvZGFzIGxhcyBjb2x1bW5hcyB0aXBvIHRleHRvIGEgZmFjdG9yIChpbmNsdXllIGRvbWFpbikNCmRmW10gPC0gbGFwcGx5KGRmLCBmdW5jdGlvbih4KSBpZiAoaXMuY2hhcmFjdGVyKHgpKSBmYWN0b3IoeCkgZWxzZSB4KQ0KIyBBc2VndXJhciB0YXJnZXQgeSBmYWN0b3JlcyBhbnRlcyBkZWwgc3BsaXQNCmRmJG0xX3B1cmNoYXNlIDwtIGZhY3RvcihkZiRtMV9wdXJjaGFzZSkNCg0KZGYkdXNlcl9wY21hYyA8LSBhcy5jaGFyYWN0ZXIoZGYkdXNlcl9wY21hYykNCmRmJHVzZXJfcGNtYWNbZGYkdXNlcl9wY21hYyAlaW4lIGMoIkhwIiwiT3RoZXIiKV0gPC0gIk90aGVyIg0KZGYkdXNlcl9wY21hYyA8LSBmYWN0b3IoZGYkdXNlcl9wY21hYywgbGV2ZWxzID0gYygiTWFjIiwgIlBDIiwgIk90aGVyIikpDQoNCiMgU3BsaXQgODAtMjANCnNldC5zZWVkKDEyMykNCnJlbmdsb25lc19lbnRyZW5hbWllbnRvIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGYkbTFfcHVyY2hhc2UsIHA9MC44LCBsaXN0PUZBTFNFKQ0KZW50cmVuYW1pZW50byA8LSBkZltyZW5nbG9uZXNfZW50cmVuYW1pZW50bywgXQ0KcHJ1ZWJhIDwtIGRmWy1yZW5nbG9uZXNfZW50cmVuYW1pZW50bywgXQ0KDQojIFF1aXRhciBmaWxhcyBjb24gTkENCmVudHJlbmFtaWVudG8gPC0gbmEub21pdChlbnRyZW5hbWllbnRvKQ0KcHJ1ZWJhIDwtIG5hLm9taXQocHJ1ZWJhKQ0KYGBgDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlIj4gRGlzdGludG9zIHRpcG9zIGRlIE3DqXRvZG9zIHBhcmEgTW9kZWxhciA8L3NwYW4+DQoNCkxvcyBtw6l0b2RvcyBtw6FzIHV0aWxpemFkb3MgcGFyYSBtb2RlbGFyIGFwcmVuZGl6YWplIGF1dG9tw6F0aWNvIHNvbjogIA0KDQoqICoqU1ZNKio6ICpTdXBwb3J0IFZlY3RvciBNYWNoaW5lKiBvIE3DoXF1aW5hIGRlIFZlY3RvcmVzIGRlIFNvcG9ydGUuIEhheSB2YXJpb3Mgc3VidGlwb3M6IExpbmVhbCAoc3ZtTGluZWFyKSwgUmFkaWFsIChzdm1SYWRpYWwpLCBQb2xpbsOzbWljbyAoc3ZtUG9seSksIGV0Yy4gICANCiogKirDgXJib2wgZGUgRGVjaXNpw7NuKio6IHJwYXJ0ICANCiogKipSZWRlcyBOZXVyb25hbGVzKio6IG5uZXQgIA0KKiAqKlJhbmRvbSBGb3Jlc3QqKiBvIEJvc3F1ZXMgQWxlYXRvcmlvczogcmYgIA0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZSI+IE1vZGVsbyAxLiBTVk0gTGluZWFsIDwvc3Bhbj4NCg0KYGBge3J9DQptb2RlbG8xIDwtIHRyYWluKG0xX3B1cmNoYXNlIH4gLiwgZGF0YT1lbnRyZW5hbWllbnRvLA0KICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtTGluZWFyIiwNCiAgICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoIm56diIsICJzY2FsZSIsICJjZW50ZXIiKSwNCiAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLCBudW1iZXI9MTApLA0KICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGRhdGEuZnJhbWUoQz0xKQ0KKQ0KDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50bzEgPC0gcHJlZGljdChtb2RlbG8xLCBlbnRyZW5hbWllbnRvKQ0KcmVzdWx0YWRvX3BydWViYTEgPC0gcHJlZGljdChtb2RlbG8xLCBwcnVlYmEpDQoNCm1jcmUxIDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fZW50cmVuYW1pZW50bzEsIGVudHJlbmFtaWVudG8kbTFfcHVyY2hhc2UpDQptY3JlMQ0KDQptY3JwMSA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX3BydWViYTEsIHBydWViYSRtMV9wdXJjaGFzZSkNCm1jcnAxDQpgYGANCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlIj4gTW9kZWxvIDIuIFNWTSBSYWRpYWwgPC9zcGFuPg0KYGBge3J9DQptb2RlbG8yIDwtIHRyYWluKG0xX3B1cmNoYXNlIH4gLiwgZGF0YT1lbnRyZW5hbWllbnRvLA0KICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUmFkaWFsIiwNCiAgICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoIm56diIsICJzY2FsZSIsICJjZW50ZXIiKSwNCiAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLCBudW1iZXI9MTApLA0KICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGRhdGEuZnJhbWUoc2lnbWE9MSwgQz0xKQ0KKQ0KDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50bzIgPC0gcHJlZGljdChtb2RlbG8yLCBlbnRyZW5hbWllbnRvKQ0KcmVzdWx0YWRvX3BydWViYTIgPC0gcHJlZGljdChtb2RlbG8yLCBwcnVlYmEpDQoNCm1jcmUyIDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fZW50cmVuYW1pZW50bzIsIGVudHJlbmFtaWVudG8kbTFfcHVyY2hhc2UpDQptY3JlMg0KDQptY3JwMiA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX3BydWViYTIsIHBydWViYSRtMV9wdXJjaGFzZSkNCm1jcnAyDQpgYGANCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlIj4gTW9kZWxvIDMuIFNWTSBQb2xpbsOzbWljbyA8L3NwYW4+DQoNCmBgYHtyfQ0KbW9kZWxvMyA8LSB0cmFpbihtMV9wdXJjaGFzZSB+IC4sIGRhdGE9ZW50cmVuYW1pZW50bywNCiAgICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bVBvbHkiLA0KICAgICAgICAgICAgICAgICBwcmVQcm9jZXNzID0gYygibnp2IiwgInNjYWxlIiwgImNlbnRlciIpLA0KICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIG51bWJlcj0xMCksDQogICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gZGF0YS5mcmFtZShkZWdyZWU9MSwgc2NhbGU9MSwgQz0xKQ0KKQ0KDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50bzMgPC0gcHJlZGljdChtb2RlbG8zLCBlbnRyZW5hbWllbnRvKQ0KcmVzdWx0YWRvX3BydWViYTMgPC0gcHJlZGljdChtb2RlbG8zLCBwcnVlYmEpDQoNCm1jcmUzIDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fZW50cmVuYW1pZW50bzMsIGVudHJlbmFtaWVudG8kbTFfcHVyY2hhc2UpDQptY3JlMw0KDQptY3JwMyA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX3BydWViYTMsIHBydWViYSRtMV9wdXJjaGFzZSkNCm1jcnAzDQpgYGANCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlIj4gTW9kZWxvIDQuIMOBcmJvbCBkZSBEZWNpc2nDs24gPC9zcGFuPg0KYGBge3J9DQptb2RlbG80IDwtIHRyYWluKG0xX3B1cmNoYXNlIH4gLiwgZGF0YT1lbnRyZW5hbWllbnRvLA0KICAgICAgICAgICAgICAgICBtZXRob2QgPSAicnBhcnQiLA0KICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIG51bWJlcj0xMCksDQogICAgICAgICAgICAgICAgIHR1bmVMZW5ndGggPSAxMA0KKQ0KDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50bzQgPC0gcHJlZGljdChtb2RlbG80LCBlbnRyZW5hbWllbnRvKQ0KcmVzdWx0YWRvX3BydWViYTQgPC0gcHJlZGljdChtb2RlbG80LCBwcnVlYmEpDQoNCm1jcmU0IDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fZW50cmVuYW1pZW50bzQsIGVudHJlbmFtaWVudG8kbTFfcHVyY2hhc2UpDQptY3JlNA0KDQptY3JwNCA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX3BydWViYTQsIHBydWViYSRtMV9wdXJjaGFzZSkNCm1jcnA0DQpgYGANCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlIj4gTW9kZWxvIDUuIFJlZGVzIE5ldXJvbmFsZXMgPC9zcGFuPg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm1vZGVsbzUgPC0gdHJhaW4obTFfcHVyY2hhc2UgfiAuLCBkYXRhPWVudHJlbmFtaWVudG8sDQogICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJubmV0IiwNCiAgICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoIm56diIsICJzY2FsZSIsICJjZW50ZXIiKSwNCiAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLCBudW1iZXI9MTApLA0KICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gNSwNCiAgICAgICAgICAgICAgICAgdHJhY2UgPSBGQUxTRQ0KKQ0KDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50bzUgPC0gcHJlZGljdChtb2RlbG81LCBlbnRyZW5hbWllbnRvKQ0KcmVzdWx0YWRvX3BydWViYTUgPC0gcHJlZGljdChtb2RlbG81LCBwcnVlYmEpDQoNCm1jcmU1IDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fZW50cmVuYW1pZW50bzUsIGVudHJlbmFtaWVudG8kbTFfcHVyY2hhc2UpDQptY3JlNQ0KDQptY3JwNSA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX3BydWViYTUsIHBydWViYSRtMV9wdXJjaGFzZSkNCm1jcnA1DQoNCmBgYA0KIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWUiPiBNb2RlbG8gNi4gQm9zcXVlcyBBbGVhdG9yaW9zIDwvc3Bhbj4NCmBgYHtyfQ0KbW9kZWxvNiA8LSB0cmFpbihtMV9wdXJjaGFzZSB+IC4sIGRhdGE9ZW50cmVuYW1pZW50bywNCiAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJmIiwNCiAgICAgICAgICAgICAgICAgcHJlUHJvY2VzcyA9IGMoIm56diIpLA0KICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIG51bWJlcj0xMCksDQogICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gZXhwYW5kLmdyaWQobXRyeSA9IGMoMiw0LDYpKQ0KKQ0KDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50bzYgPC0gcHJlZGljdChtb2RlbG82LCBlbnRyZW5hbWllbnRvKQ0KcmVzdWx0YWRvX3BydWViYTYgPC0gcHJlZGljdChtb2RlbG82LCBwcnVlYmEpDQoNCm1jcmU2IDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fZW50cmVuYW1pZW50bzYsIGVudHJlbmFtaWVudG8kbTFfcHVyY2hhc2UpDQptY3JlNg0KDQptY3JwNiA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX3BydWViYTYsIHBydWViYSRtMV9wdXJjaGFzZSkNCm1jcnA2DQpgYGANCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlIj4gVGFibGEgZGUgUmVzdWx0YWRvcyA8L3NwYW4+DQpgYGB7cn0NCnJlc3VsdGFkb3MgPC0gZGF0YS5mcmFtZSgNCiAgInN2bUxpbmVhciIgPSBjKG1jcmUxJG92ZXJhbGxbIkFjY3VyYWN5Il0sIG1jcnAxJG92ZXJhbGxbIkFjY3VyYWN5Il0pLA0KICAgInN2bVJhZGlhbCIgPSBjKG1jcmUyJG92ZXJhbGxbIkFjY3VyYWN5Il0sIG1jcnAyJG92ZXJhbGxbIkFjY3VyYWN5Il0pLA0KICAgInN2bVBvbHkiID0gYyhtY3JlMyRvdmVyYWxsWyJBY2N1cmFjeSJdLCBtY3JwMyRvdmVyYWxsWyJBY2N1cmFjeSJdKSwNCiAgICJycGFydCIgPSBjKG1jcmU0JG92ZXJhbGxbIkFjY3VyYWN5Il0sIG1jcnA0JG92ZXJhbGxbIkFjY3VyYWN5Il0pLA0KICAgIm5uZXQiID0gYyhtY3JlNSRvdmVyYWxsWyJBY2N1cmFjeSJdLCBtY3JwNSRvdmVyYWxsWyJBY2N1cmFjeSJdKSwNCiAgICJyZiIgPSBjKG1jcmU2JG92ZXJhbGxbIkFjY3VyYWN5Il0sIG1jcnA2JG92ZXJhbGxbIkFjY3VyYWN5Il0pDQopDQpyb3duYW1lcyhyZXN1bHRhZG9zKSA8LSBjKCJQcmVjaXNpw7NuIGRlIGVudHJlbmFtaWVudG8iLCAiUHJlY2lzacOzbiBkZSBwcnVlYmEiKQ0KcmVzdWx0YWRvcw0KYGBgDQojIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZSI+IENvbmNsdXNpb25lcyA8L3NwYW4+DQpDb25jbHVzaW9uZXMgZ2VuZXJhbGVzDQoNCi1Qcm9iYW1vcyBzZWlzIG1vZGVsb3MgZGlzdGludG9zIHksIGVuIGdlbmVyYWwsIHRvZG9zIGxvZ3Jhcm9uIHVuIGJ1ZW4gbml2ZWwgZGUgY2xhc2lmaWNhY2nDs24gc29icmUgbTFfcHVyY2hhc2UuDQoNCi1ObyBvYnNlcnZhbW9zIGNhw61kYXMgZHJhbcOhdGljYXMgZW50cmUgZW50cmVuYW1pZW50byB5IHBydWViYSwgbG8gcXVlIGluZGljYSBxdWUgbGEgbWF5b3LDrWEgZGUgbG9zIG1vZGVsb3MgZ2VuZXJhbGl6YSBkZSBmb3JtYSBhY2VwdGFibGUuDQoNCi1FbCBkYXRhc2V0IHBhcmVjZSBzZXIgcmVsYXRpdmFtZW50ZSBzZXBhcmFibGUsIHlhIHF1ZSBpbmNsdXNvIG1vZGVsb3Mgc2ltcGxlcyBhbGNhbnphbiBkZXNlbXBlw7FvcyBjb21wZXRpdGl2b3MuDQoNClNWTSBMaW5lYWwNCg0KLU5vcyBkaW8gdW4gZGVzZW1wZcOxbyBzw7NsaWRvIHkgbXV5IGVzdGFibGUgZW50cmUgdHJhaW4geSB0ZXN0Lg0KDQotQ29uZmlybWEgcXVlIGxhIHJlbGFjacOzbiBlbnRyZSB2YXJpYWJsZXMgeSBsYSB2YXJpYWJsZSBvYmpldGl2byBwdWVkZSBtb2RlbGFyc2UgY29uIHVuYSBmcm9udGVyYSBjYXNpIGxpbmVhbC4NCg0KLUVzIHVuYSBvcGNpw7NuIGZ1ZXJ0ZSBjdWFuZG8gYnVzY2Ftb3MgYnVlbiByZW5kaW1pZW50byBjb24gbWVub3IgY29tcGxlamlkYWQuDQoNClNWTSBSYWRpYWwNCg0KLU5vIG1vc3Ryw7MgdW5hIG1lam9yYSBzdXN0YW5jaWFsIGZyZW50ZSBhbCBsaW5lYWwuDQoNCi1Fc3RvIHN1Z2llcmUgcXVlIG5vIG5lY2VzaXRhbW9zIHVuYSBmcm9udGVyYSBhbHRhbWVudGUgbm8gbGluZWFsIHBhcmEgZXN0ZSBwcm9ibGVtYS4NCg0KLUF1biBhc8OtLCBtYW50aWVuZSBhbHRvIGRlc2VtcGXDsW8geSBidWVuYSBjYXBhY2lkYWQgZGUgZ2VuZXJhbGl6YWNpw7NuLg0KDQpTVk0gUG9saW7Ds21pY28NCg0KLVNlIGNvbXBvcnRhIHNpbWlsYXIgYSBsb3Mgb3Ryb3MgU1ZNLg0KDQotTm8gYXBvcnRhIHVuYSBnYW5hbmNpYSBjbGFyYSByZXNwZWN0byBhbCBsaW5lYWwgbyByYWRpYWwgY29uIGxvcyBwYXLDoW1ldHJvcyBhY3R1YWxlcy4NCg0KLU5vcyBjb25maXJtYSBxdWUgZWwgcHJvYmxlbWEgbm8gZXhpZ2UgdW5hIHRyYW5zZm9ybWFjacOzbiBkZW1hc2lhZG8gY29tcGxlamEuDQoNCsOBcmJvbCBkZSBEZWNpc2nDs24gKHJwYXJ0KQ0KDQotRXMgbcOhcyBpbnRlcnByZXRhYmxlIHF1ZSBsb3MgU1ZNLg0KDQotU3UgZGVzZW1wZcOxbyBlcyBjb21wZXRpdGl2bywgYXVucXVlIHB1ZWRlIHNlciBsaWdlcmFtZW50ZSBtw6FzIHNlbnNpYmxlIGFsIHNvYnJlYWp1c3RlLg0KDQotTm9zIGF5dWRhIGEgZW50ZW5kZXIgcXXDqSB2YXJpYWJsZXMgZXN0w6FuIGluZmx1eWVuZG8gbcOhcyBlbiBsYSBkZWNpc2nDs24uDQoNClJlZGVzIE5ldXJvbmFsZXMgKG5uZXQpDQoNCi1Mb2dyYW4gYnVlbiBkZXNlbXBlw7FvLCBwZXJvIG5vIG5lY2VzYXJpYW1lbnRlIHN1cGVyYW4gYSBtb2RlbG9zIG3DoXMgc2ltcGxlcy4NCg0KLVJlcXVpZXJlbiBtYXlvciBhanVzdGUgZmlubyB5IHB1ZWRlbiBzZXIgbWVub3MgaW50ZXJwcmV0YWJsZXMuDQoNCi1FbiBlc3RlIGNhc28sIGxhIGNvbXBsZWppZGFkIGV4dHJhIG5vIHNlIHRyYWR1Y2UgZW4gdW5hIG1lam9yYSBjbGFyYS4NCg0KUmFuZG9tIEZvcmVzdA0KDQotU3VlbGUgc2VyIGRlIGxvcyBtb2RlbG9zIG3DoXMgcm9idXN0b3MuDQoNCi1NYW5lamEgYmllbiBsYSB2YXJpYWJpbGlkYWQgeSByZWR1Y2UgZWwgcmllc2dvIGRlIHNvYnJlYWp1c3RlIGZyZW50ZSBhIHVuIMOhcmJvbCBpbmRpdmlkdWFsLg0KDQotRXMgdW4gY2FuZGlkYXRvIGZ1ZXJ0ZSBzaSBwcmlvcml6YW1vcyBlc3RhYmlsaWRhZCB5IGRlc2VtcGXDsW8gY29uc2lzdGVudGUuDQoNCi1Db25jbHVzacOzbiBmaW5hbCBlc3RyYXTDqWdpY2ENCg0KLU5vIHNpZW1wcmUgZWwgbW9kZWxvIG3DoXMgY29tcGxlam8gZXMgZWwgbWVqb3IuDQoNCi1FbiBudWVzdHJvIGNhc28sIG1vZGVsb3MgcmVsYXRpdmFtZW50ZSBzaW1wbGVzIHlhIGNhcHR1cmFuIGJpZW4gbGEgZXN0cnVjdHVyYSBkZWwgcHJvYmxlbWEuDQoNCi1TaSBwcmlvcml6YW1vcyBpbnRlcnByZXRhY2nDs24geSBlZmljaWVuY2lhLCB1biBTVk0gbGluZWFsIG8gaW5jbHVzbyB1biDDoXJib2wgcHVlZGVuIHNlciBzdWZpY2llbnRlcy4NCg0KLVNpIHByaW9yaXphbW9zIHJvYnVzdGV6IHkgZXN0YWJpbGlkYWQsIFJhbmRvbSBGb3Jlc3QgZXMgdW5hIGV4Y2VsZW50ZSBhbHRlcm5hdGl2YS4NCg==