Valores NA (missing values) y cómo rellenarlos

Partimos de uno de los datasets de pruebas que hay en RStudio.

#library(help="datasets") #lista completa (cogeremos el dataset llamado attenu, por ejemplo)
data(attenu)
attach(attenu)
The following objects are masked from attenu (pos = 8):

    accel, dist, event, mag, station

The following objects are masked from attenu (pos = 13):

    accel, dist, event, mag, station

The following objects are masked from attenu (pos = 27):

    accel, dist, event, mag, station
help(attenu)
boxplot(attenu, cex.axis=0.5) 

Vemos que la variable dist es asimetrica y tiene outliers. Vamos que las variables mag y accel son leptocúrticas (apretadas en torno a la media)

summary(attenu)
     event            mag           station         dist            accel        
 Min.   : 1.00   Min.   :5.000   117    :  5   Min.   :  0.50   Min.   :0.00300  
 1st Qu.: 9.00   1st Qu.:5.300   1028   :  4   1st Qu.: 11.32   1st Qu.:0.04425  
 Median :18.00   Median :6.100   113    :  4   Median : 23.40   Median :0.11300  
 Mean   :14.74   Mean   :6.084   112    :  3   Mean   : 45.60   Mean   :0.15422  
 3rd Qu.:20.00   3rd Qu.:6.600   135    :  3   3rd Qu.: 47.55   3rd Qu.:0.21925  
 Max.   :23.00   Max.   :7.700   (Other):147   Max.   :370.00   Max.   :0.81000  
                                 NA's   : 16                                     

Matriz de parejas de features (CORRELACIONES)

Hacemos una análisis exploratorio de las correlaciones. Matriz de correlaciones (entre variables) y funcion de densidad de probabilidad (univariable):

library(car)
scatterplotMatrix(x=attenu)

O también construirla usando una función personalizada por nosotros:

pairs(attenu, gap=0, lower.panel = panel.smooth, upper.panel = function(x,y){
  panel.smooth(x,y)
  par(usr=c(0,1,0,1))
  correlacion <- cor(x,y,use="complete.obs")
  text(0.6,0.7,col="blue", cex=1.2, round(correlacion, digits=2))
}  )

Es importante ver si hay alguna correlación fácilmente visible (lineal…).

IDs de los elementos que tienen valores NA en cualquiera de sus features (columnas):

which(is.na(attenu$event))
integer(0)
which(is.na(attenu$mag))
integer(0)
which(is.na(attenu$station))
 [1]  79  81  94  96  99 107 108 114 116 118 123 126 128 155 156 160
which(is.na(attenu$dist))
integer(0)
which(is.na(attenu$accel))
integer(0)

Vemos que sólo la variable station tiene valores NA. También se pueden coger sólo las filas en las que todas las features están rellenas (sin ningún valor NA):

attenu[complete.cases(attenu),]

Si queremos ELIMINAR aquellas filas (margin=1 significa ROWS) que tengan más de un valor NA: 1. Aplicar la función SUM sobre filas, contando el número de elementos NA. 2. Si hay uno o más, saco sus índices (which) 3. Creo un vector con esos índices y se lo resto al dataset attenu.

filas_con_na <- apply(attenu, MARGIN = 1, FUN = function(x){ sum(is.na(x)) >=1})
indices_de_filas_con_na <- which(filas_con_na)
datos_sinhuecos=attenu[ -c( indices_de_filas_con_na ) , ]

Comprobamos que ya no hay HUECOS (con algún valor NA):

attenu[!complete.cases(datos_sinhuecos),]

ANALISIS DE COMPONENTES PRINCIPALES (PCA)

Si tenemos muchas features, conviene reducirlas para que el análisis de correlaciones sea más sencillo. Es MUY importante indicar que trabajamos con la matriz de correlaciones (en vez de la de covarianzas): cor=TRUE Además, PCA sólo trabaja con features que sean NUMERICAS, así que debemos comprobar que todas las columnas (features) son numericas

drops <- c("station")
datos_para_pca <- attenu[ , !(names(attenu) %in% drops)]
datos_sin_na <- na.omit(datos_para_pca) # limpiar los NA
sapply(datos_sin_na, class) #clase/modo de cada feature
    event       mag      dist     accel 
"numeric" "numeric" "numeric" "numeric" 
sapply(datos_sin_na, typeof) #tipo de cada feature
   event      mag     dist    accel 
"double" "double" "double" "double" 
pca_attenu <- princomp(datos_sin_na, cor=TRUE)  
pca_attenu
Call:
princomp(x = datos_sin_na, cor = TRUE)

Standard deviations:
   Comp.1    Comp.2    Comp.3    Comp.4 
1.4752709 1.0373500 0.6701831 0.5462007 

 4  variables and  182 observations.
plot(pca_attenu)

summary(pca_attenu)
Importance of components:
                          Comp.1    Comp.2    Comp.3     Comp.4
Standard deviation     1.4752709 1.0373500 0.6701831 0.54620074
Proportion of Variance 0.5441061 0.2690237 0.1122864 0.07458381
Cumulative Proportion  0.5441061 0.8131298 0.9254162 1.00000000
biplot(pca_attenu) #PC2 vs PC1

#Los pesos de cada individuo (fila) proyectado en las componentes (PC1, PC2...)
pca_pesos <- pca_attenu$scores
pca_pesos_pc1pc2 <- pca_pesos[, 1:2] #Pesos sólo en las componentes que más influyen en la varianza total
plot(pca_pesos_pc1pc2[,1], pca_pesos_pc1pc2[,2]) #misma gráfica que veiamos en el biplot

Vemos que la componente 1 tiene un peso del 54.41% sobre las varianzas; la 2 tiene 26.9%; etc. Cogemos la PC1 y PC2, que suman un 81% de la influencia en la varianza total.

Vemos también los pesos que tienen las variables de entrada (features) dentro de las componentes compuestas (PC1, PC2…)., mirando el plot de flechas:

CLUSTERING (habiendo hecho PCA antes)

Vamos a mostrar un subtipo de “clustering jerárquico” llamado Agglomerative Nesting (AGNES).

Hay tres tipos de métodos de unión en clustering:

library(cluster)
agnes_single <- agnes(x = datos_sin_na, method = "single")
plot(agnes_single) # Cuando veamos espacios entre las muestras, es la separacion entre clusters. Es DIFICIL de ver.

Con Single, vemos que hay 3 clusters (si height=50), fijándose en los huecos que hay en el gráfico “banner”, que separan a los clusters.

library(cluster)
agnes_complete <- agnes(x = datos_sin_na, method = "complete")
plot(agnes_complete) # Cuando veamos espacios entre las muestras, es la separacion entre clusters. Es DIFICIL de ver.

Con Complete, se ven claramente 3 clusters (si height=200).

library(cluster)
agnes_average <- agnes(x = datos_sin_na, method = "average")
plot(agnes_average) # Cuando veamos espacios entre las muestras, es la separacion entre clusters. Es DIFICIL de ver.

Con Average, se ven 3 clusters (si height=100). Donde más claro lo vemos es con el método “complete”, así que creamos los clusters vistos con COLORES. Esos colores los pintamos sobre los pesos que ya habiamos calculado para PCA (de las componentes PC1 y PC2).

complete_3_clusters=cutree(agnes_complete,3)
plot(pca_pesos,col=complete_3_clusters)

REGRESIÓN LINEAL SIMPLE

Link: http://rpubs.com/joser/RegresionSimple Datos entrada:

grasas_tabla <- read.table('http://verso.mat.uam.es/~joser.berrendero/datos/EdadPesoGrasas.txt', header = TRUE)
names(grasas_tabla)
[1] "peso"   "edad"   "grasas"
pairs(grasas_tabla)

cor(grasas_tabla)
            peso      edad    grasas
peso   1.0000000 0.2400133 0.2652935
edad   0.2400133 1.0000000 0.8373534
grasas 0.2652935 0.8373534 1.0000000

Vemos correlación (0.83) entre las variables edad y grasas, como era de esperar a simple vista. El gráfico muestra también la relación lineal (a ojo).

Recta de mínimos cuadrados: Usamos un MODELO LINEAL (lm) para calcular una regresión lineal muy sencilla, de grasas respecto de la edad: \(y =102.575+5.321*x\)

O mejor aun: \(y = 102.575 (+/-29.6376) + 5.321(+/-0.7243) * x\)

regresion <- lm(grasas ~ edad, data = grasas_tabla) #grasas dependiente de la edad
summary(regresion)

Call:
lm(formula = grasas ~ edad, data = grasas_tabla)

Residuals:
    Min      1Q  Median      3Q     Max 
-63.478 -26.816  -3.854  28.315  90.881 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 102.5751    29.6376   3.461  0.00212 ** 
edad          5.3207     0.7243   7.346 1.79e-07 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 43.46 on 23 degrees of freedom
Multiple R-squared:  0.7012,    Adjusted R-squared:  0.6882 
F-statistic: 53.96 on 1 and 23 DF,  p-value: 1.794e-07
plot(grasas_tabla$edad, grasas_tabla$grasas, xlab='Edad', ylab='Grasas')
abline(regresion)

plot(regresion)

Vemos que el R2 es 0.7012 –> Bondad de la recta de ajuste.

PREDICCION PARA NUEVAS ENTRADAS:

Usar la recta:

nuevas.edades <- data.frame(edad = seq(30, 50))
prediccion_nueva<-predict(regresion, nuevas.edades)
prediccion_nueva
       1        2        3        4        5        6        7        8        9       10       11       12       13       14       15 
262.1954 267.5161 272.8368 278.1575 283.4781 288.7988 294.1195 299.4402 304.7608 310.0815 315.4022 320.7229 326.0435 331.3642 336.6849 
      16       17       18       19       20       21 
342.0056 347.3263 352.6469 357.9676 363.2883 368.6090 

Explicación del modelo de regresión simple:

  • La columna t-value es el estadístico t –> Cociente entre cada estimador y su error típico. Permite realizar los contrastes de hipótesis nula (que sean 0) –> H00=0 y H01=0
  • Los p-valores aparecen en la columna Pr(>|t|) –> Probabilidad de superar el umbral –> Como son muy pequeños (menores que 0.01 ó 0.05, utilizados normalmente) ==> Se rechazan ambas hipótesis nulas.
  • El estimador de la desviación típica (σ) de los errores aparece como Residual standard error y su valor en el ejemplo es 43.5
  • Los intervalos de confianza para los parámetros se obtienen con el comando confint. El parámetro level permite elegir el nivel de confianza (por defecto es 0.95):
confint(regresion)
                2.5 %     97.5 %
(Intercept) 41.265155 163.885130
edad         3.822367   6.818986
confint(regresion, level = 0.90)
                  5 %       95 %
(Intercept) 51.780153 153.370132
edad         4.079335   6.562018

Los “intervalos de confianza para la respuesta media” y los “intervalos de predicción para la respuesta” se pueden obtener usando el comando predict. Por ejemplo, el siguiente código calcula y representa los dos tipos de intervalos para el rango de edades que va de 20 a 60 años (los de predicción en rojo):

nuevas.edades <- data.frame(edad = seq(20, 60))
# Grafico de dispersion y recta
plot(grasas_tabla$edad, grasas_tabla$grasas, xlab='Edad', ylab='Grasas')
abline(regresion)
# Intervalos de confianza de la respuesta media:
# ic es una matriz con tres columnas: la primera es la prediccion, las otras dos son los extremos del intervalo
ic <- predict(regresion, nuevas.edades, interval = 'confidence')
lines(nuevas.edades$edad, ic[, 2], lty = 2, col = 'green')
lines(nuevas.edades$edad, ic[, 3], lty = 2, col = 'green')
# Intervalos de prediccion
ic <- predict(regresion, nuevas.edades, interval = 'prediction')
lines(nuevas.edades$edad, ic[, 2], lty = 2, col = 'red')
lines(nuevas.edades$edad, ic[, 3], lty = 2, col = 'red')

Análisis de la varianza:

anova(regresion)
Analysis of Variance Table

Response: grasas
          Df Sum Sq Mean Sq F value    Pr(>F)    
edad       1 101933  101933  53.964 1.794e-07 ***
Residuals 23  43444    1889                      
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Diagnóstico del modelo:

residuos <- rstandard(regresion)
valores.ajustados <- fitted(regresion)
plot(valores.ajustados, residuos)

#Prueba de normalidad (sencilla) para saber si la muestra tiene una distribucion NORMAL
qqnorm(residuos)
qqline(residuos)

Como tiene pocas muestras, podríamos haber pintado una t-Student encima, para poder comparar.

Otras pruebas de normalidad –> Kolmogorov-Smirnov y Shapiro-Wilk.

Ejemplo interesante:

x <- 1:20
w <- 1 + sqrt(x)/2 #desviaciones estandar (pesos)
y <- x + w*rnorm(x)
dummy <-- data.frame(x=x, y=y)
fm <- lm(y~x, data=dummy) #MODELO simple linear regression
summary(fm)

Call:
lm(formula = y ~ x, data = dummy)

Residuals:
    Min      1Q  Median      3Q     Max 
-5.4670 -1.4875 -0.3557  1.7932  7.1525 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   0.7369     1.4954   0.493    0.628    
x             1.0565     0.1248   8.464 1.09e-07 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.219 on 18 degrees of freedom
Multiple R-squared:  0.7992,    Adjusted R-squared:  0.788 
F-statistic: 71.63 on 1 and 18 DF,  p-value: 1.089e-07
fm1 <- lm(y~x, data=dummy, weight=1/w^2) #MODELO con weighted regression
summary(fm1)

Call:
lm(formula = y ~ x, data = dummy, weights = 1/w^2)

Weighted Residuals:
    Min      1Q  Median      3Q     Max 
-1.8149 -0.7074 -0.1920  0.7042  2.3323 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   1.0708     1.0537   1.016    0.323    
x             1.0880     0.1074  10.126 7.36e-09 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.164 on 18 degrees of freedom
Multiple R-squared:  0.8507,    Adjusted R-squared:  0.8424 
F-statistic: 102.5 on 1 and 18 DF,  p-value: 7.359e-09

Resto del análisis:

attach(dummy) #hacemos visibles las columnas del dataframe, para trabajar directamente con sus nombres.
The following objects are masked _by_ .GlobalEnv:

    x, y
lrf <-lowess(x,y, f = 2/3, iter = 3, delta = 0.01 * diff(range(x))) #Funcion de regresion local no parametrica: LOWESS smoother (locally-weighted polynomial regression)
summary(lrf)
  Length Class  Mode   
x 20     -none- numeric
y 20     -none- numeric
plot(x,y)
lines(x, lrf$y, col="blue") #pintamos la linea de la REGRESION LINEAL
abline(0,1, lty=3, col="magenta") #linea de regresion verdadera (intercept 0, slope=1) (linea de puntos)
abline(coef(fm), col="green") #linea de regresion sin pesos
abline(coef(fm1), col="red") #linea de regresion con pesos

detach()
plot( fitted(fm), resid(fm), xlab="Fitted values", ylab="Residuals", main="Residuals vs Fitted" ) #Analisis de heterodicidad

qqnorm( resid(fm), main="Residuals Rankit Plot" ) #Analisis de skewness, kurtosis y outliers (no muy util en este caso)

#rm( fm, fm1, lrf, x, dummy) #limpieza de variables

Manual de CRAN-INTRO.ap.8.2

Lo básico:

attach(faithful)
The following objects are masked from faithful (pos = 8):

    eruptions, waiting

The following objects are masked from faithful (pos = 13):

    eruptions, waiting

The following objects are masked from faithful (pos = 24):

    eruptions, waiting
summary(eruptions) #Como he hecho attach, evito hacer esto: summary(faithful$eruptions)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.600   2.163   4.000   3.488   4.454   5.100 
fivenum(eruptions) #Pinta un "dibujo" hecho con numeros, que parece un histograma
[1] 1.6000 2.1585 4.0000 4.4585 5.1000
stem(eruptions)

  The decimal point is 1 digit(s) to the left of the |

  16 | 070355555588
  18 | 000022233333335577777777888822335777888
  20 | 00002223378800035778
  22 | 0002335578023578
  24 | 00228
  26 | 23
  28 | 080
  30 | 7
  32 | 2337
  34 | 250077
  36 | 0000823577
  38 | 2333335582225577
  40 | 0000003357788888002233555577778
  42 | 03335555778800233333555577778
  44 | 02222335557780000000023333357778888
  46 | 0000233357700000023578
  48 | 00000022335800333
  50 | 0370

Dibujos:

hist(eruptions,seq(from=1.6, to=5.2, by=0.2), probability = TRUE)
lines(density(eruptions, bw=0.1))
rug(eruptions)

La función de densidad (CDF) es:

plot(ecdf(eruptions), do.points=FALSE, verticals = TRUE)

Si cogemos sólo aquellos con X>3:

erupciones_largas<-eruptions[eruptions>3]
hist(erupciones_largas)

plot(ecdf(erupciones_largas), do.points=FALSE, verticals = TRUE)
x<-seq(3, 5.4, 0.01)
#Usando la distribucion NORMAL (norm)
lines(x, pnorm(x, mean = mean(erupciones_largas), sd=sqrt(var(erupciones_largas))), lty=3)  #linea de puntos(lty=3)

PRUEBAS DE NORMALIDAD:

  1. Q-Q (Quantile-Quantile)
par(pty="s") #pinta la caja cuadrada (en vez de que sea rectangular por defecto)
qqnorm(erupciones_largas)
qqline(erupciones_largas)

#La COMPARAMOS con una t-Student de 5 grados de libertad:
puntos_t_student <- rt(250, df = 5)
qqnorm(puntos_t_student)
qqline(puntos_t_student)

#Pintamos el qqplot
qqplot(qt( ppoints(250), df=5 ), puntos_t_student, xlab = "QQplot para la densidad t-Student")
qqline(puntos_t_student)

  1. Test Shapiro-Wilk:
shapiro.test(erupciones_largas)

    Shapiro-Wilk normality test

data:  erupciones_largas
W = 0.97934, p-value = 0.01052
  1. Test Kolmogorov-Smirnov:
ks.test(erupciones_largas, "pnorm", mean=mean(erupciones_largas), sd=sqrt(var(erupciones_largas)))
ties should not be present for the Kolmogorov-Smirnov test

    One-sample Kolmogorov-Smirnov test

data:  erupciones_largas
D = 0.066133, p-value = 0.4284
alternative hypothesis: two-sided

REGRESION LOGISTICA

challenger <- read.table('http://verso.mat.uam.es/~joser.berrendero/datos/challenger.txt', header = TRUE)
table(challenger$defecto)

 0  1 
16  7 
colores <- NULL
colores[challenger$defecto==0] <- 'green'
colores[challenger$defecto==1] <- 'red'
plot(challenger$temp, challenger$defecto, pch = 21, bg = colores, xlab = 'Temperatura', ylab = 'Probabilidad de defectos')
legend('bottomleft', c('No defecto', 'Si defecto'), pch = 21, col = c('green', 'red'))

DISTRIBUCIONES DE PROBABILIDAD

Hay muchas: binomial (binom), Poisson (pois), normal (norm), exponencial (exp), t-Student (t), chi-cuadrado (chisq), F (f)…

MAQUINAS DE VECTOR SOPORTE (SVM)

Busca el hiperplano que separe las observaciones según su clases (clasificación). Buena explicación: https://www.youtube.com/watch?v=ImhjKyc88x0

Link de mi ejemplo: http://rpubs.com/joser/svm

library(MASS)
library(e1071)
load(url('http://www.uam.es/joser.berrendero/datos/practica-svm-io.RData'))
# Mostramos 10 filas
head(breast.cancer2)

Gráficamente, pintamos la correlación entre las 2 features de entrada:

# Prepara los datos
x <- cbind(breast.cancer2$x.smoothness, breast.cancer2$x.concavepoints)
head(x)
        [,1]     [,2]
[1,] 0.09779 0.047810
[2,] 0.10750 0.031100
[3,] 0.10240 0.020760
[4,] 0.08983 0.029230
[5,] 0.08600 0.005917
[6,] 0.10310 0.027490
y <- breast.cancer2$y
head(y)
[1] 0 0 0 0 0 0
Levels: 0 1
n0 <- sum(y==0)
n1 <- sum(y==1)
# Para que los graficos queden mas bonitos (rojo = maligno, verde = benigno)
colores <- c(rep('green',n0),rep('red',n1))
pchn <- 21
# Diagrama de dispersion
plot(x, pch = pchn, bg = colores, xlab='smoothness', ylab='concavepoints')

SVM lineal

Siendo la variable y un factor, queremos predecir usando el resto de features.

Para calcular la regla de clasificación SVM lineal con C=10, se usa la función svm del paquete e1071. El primer argumento y~. indica que la variable y (que debe ser necesariamente un factor) se desea predecir en términos del resto de variables del fichero. La sintaxis es similar a la que utilizaríamos para ajustar un modelo lineal o un modelo de regresión logística. El segundo argumento indica el fichero en el que están las variables que vamos a usar. El argumento kernel corresponde al núcleo que representa el producto escalar que queremos utilizar. La opción linear corresponde a k(x,y)=x′y. El argumento cost determina la penalización que ponemos a los errores de clasificación. Con el fin de estimar la probabilidad de clasificar erróneamente una observación se puede utilizar validación cruzada, dividiendo la muestra en, por ejemplo, dos partes. Ello se consigue fijando cross=2. Finalmente, scale=FALSE se usa para usar los datos no estandarizados (por defecto, sí se estandarizan).

C <- 10
svm.lineal <- svm(y~., data=breast.cancer2, kernel='linear', cost=C, cross=2, scale=FALSE) #Su type es C-classification porque y es un factor
summary(svm.lineal)

Call:
svm(formula = y ~ ., data = breast.cancer2, kernel = "linear", cost = C, cross = 2, scale = FALSE)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  linear 
       cost:  10 
      gamma:  0.5 

Number of Support Vectors:  258

 ( 129 129 )


Number of Classes:  2 

Levels: 
 0 1

2-fold cross-validation on training data:

Total Accuracy: 88.92794 
Single Accuracies:
 89.43662 88.42105 
x.svm <- x[svm.lineal$index,]
w <- crossprod(x.svm, svm.lineal$coefs)
w0 <- svm.lineal$rho
plot(x, pch = pchn, bg = colores, xlab='smoothness', ylab='concavepoints')
abline(w0/w[2], -w[1]/w[2], lwd=2, col='blue')

#Estadisticas: precisión y cobertura
table(predict(svm.lineal), breast.cancer2$y, dnn=c("Prediction", "Actual"))
          Actual
Prediction   0   1
         0 342  43
         1  15 169
library(caret)
confusionMatrix(breast.cancer2$y, predict(svm.lineal))
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0 342  15
         1  43 169
                                          
               Accuracy : 0.8981          
                 95% CI : (0.8702, 0.9217)
    No Information Rate : 0.6766          
    P-Value [Acc > NIR] : < 2.2e-16       
                                          
                  Kappa : 0.776           
 Mcnemar's Test P-Value : 0.0003922       
                                          
            Sensitivity : 0.8883          
            Specificity : 0.9185          
         Pos Pred Value : 0.9580          
         Neg Pred Value : 0.7972          
             Prevalence : 0.6766          
         Detection Rate : 0.6011          
   Detection Prevalence : 0.6274          
      Balanced Accuracy : 0.9034          
                                          
       'Positive' Class : 0               
                                          

Indica que hay 258 vectores soporte. Usando validación cruzada con la muestra dividida en dos partes se estima una probabilidad de acierto en la clasificación de aproximadamente el 88%. Podemos cambiar el parámetro de penalización para ver si estos valores aumentan o disminuyen.

SVM cuadrático

svm.cuadratico <- svm(formula = y~., data = breast.cancer2, kernel='polynomial', degree=2, gamma=1, coef0=1, cost=C, cross=10, scale=FALSE)
summary(svm.cuadratico)

Call:
svm(formula = y ~ ., data = breast.cancer2, kernel = "polynomial", degree = 2, gamma = 1, coef0 = 1, cost = C, 
    cross = 10, scale = FALSE)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  polynomial 
       cost:  10 
     degree:  2 
      gamma:  1 
     coef.0:  1 

Number of Support Vectors:  211

 ( 106 105 )


Number of Classes:  2 

Levels: 
 0 1

10-fold cross-validation on training data:

Total Accuracy: 90.50967 
Single Accuracies:
 87.5 87.7193 91.22807 92.98246 92.98246 92.98246 91.22807 87.7193 87.7193 92.98246 
#Estadisticas: precisión y cobertura
table(predict(svm.cuadratico), breast.cancer2$y, dnn=c("Prediction", "Actual"))
          Actual
Prediction   0   1
         0 340  36
         1  17 176
library(caret)
confusionMatrix(breast.cancer2$y, predict(svm.cuadratico))
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0 340  17
         1  36 176
                                          
               Accuracy : 0.9069          
                 95% CI : (0.8799, 0.9294)
    No Information Rate : 0.6608          
    P-Value [Acc > NIR] : < 2e-16         
                                          
                  Kappa : 0.7971          
 Mcnemar's Test P-Value : 0.01342         
                                          
            Sensitivity : 0.9043          
            Specificity : 0.9119          
         Pos Pred Value : 0.9524          
         Neg Pred Value : 0.8302          
             Prevalence : 0.6608          
         Detection Rate : 0.5975          
   Detection Prevalence : 0.6274          
      Balanced Accuracy : 0.9081          
                                          
       'Positive' Class : 0               
                                          

SVM radial

svm.radial <- svm(formula = y~., data = breast.cancer2, kernel='radial', degree=2, gamma=1, coef0=1, cost=C, cross=10, scale=FALSE)
summary(svm.radial)

Call:
svm(formula = y ~ ., data = breast.cancer2, kernel = "radial", degree = 2, gamma = 1, coef0 = 1, cost = C, cross = 10, 
    scale = FALSE)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  10 
      gamma:  1 

Number of Support Vectors:  212

 ( 106 106 )


Number of Classes:  2 

Levels: 
 0 1

10-fold cross-validation on training data:

Total Accuracy: 90.15817 
Single Accuracies:
 92.85714 91.22807 87.7193 89.47368 92.98246 94.73684 87.7193 89.47368 84.21053 91.22807 
#Estadisticas: precisión y cobertura
table(predict(svm.radial), breast.cancer2$y, dnn=c("Prediction", "Actual"))
          Actual
Prediction   0   1
         0 340  36
         1  17 176
library(caret)
confusionMatrix(breast.cancer2$y, predict(svm.radial))
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0 340  17
         1  36 176
                                          
               Accuracy : 0.9069          
                 95% CI : (0.8799, 0.9294)
    No Information Rate : 0.6608          
    P-Value [Acc > NIR] : < 2e-16         
                                          
                  Kappa : 0.7971          
 Mcnemar's Test P-Value : 0.01342         
                                          
            Sensitivity : 0.9043          
            Specificity : 0.9119          
         Pos Pred Value : 0.9524          
         Neg Pred Value : 0.8302          
             Prevalence : 0.6608          
         Detection Rate : 0.5975          
   Detection Prevalence : 0.6274          
      Balanced Accuracy : 0.9081          
                                          
       'Positive' Class : 0               
                                          

Se ve que es mejor el kernel LINEAL (grado), pero debemos mirar el accuracy y cobertura –> Gana CUADRATICO, parece.

ARBOLES DE DECISIÓN

Link: https://www.youtube.com/watch?v=mgh4KdbYHv0 Es un algoritmo de CLASIFICACIÓN supervisada. La variable dependiente predicha puede ser:

  • Cuantitativa –> Árbol de REGRESIÓN
  • Cualitativa –> Árbol de CLASIFICACIÓN

Cargar dataset

library(rpart) #Algoritmo para árbol de decisión
library(rpart.plot) #Plot de árbol de decisión
library("C50") #paquete que tiene el dataset de entrada y el algoritmo (rpart)
data(churn) #DATASET de ejemplo sobre CHURN (clientes que abandonan una empresa)

Preparar set de datos, dividiendo en entrenamiento y test

churn_juntos <- rbind(churnTest, churnTrain) #junta filas
head(churn_juntos, n=3L) #FEATURES disponibles (5000x20). Mostramos 3 filas
churn <- churn_juntos[, c(4,7,8,16,19,17,20)] #Usaremos solo algunas columnas (features)
names(churn) # nombres en ingles
[1] "international_plan"            "total_day_minutes"             "total_day_calls"               "total_intl_minutes"           
[5] "number_customer_service_calls" "total_intl_calls"              "churn"                        
names(churn) <- c("Tiene plan internacional", "Minutos al dia", "Llamadas al dia", "Minutos internacionales", "Reclamaciones", "Llamadas internacionales", "Cancelacion") #los renombro a nombres en espanhol
ind <- sample(2, nrow(churn), replace=TRUE, prob=c(0.6, 0.4)) #train (60%) y test (40%)
trainData <- churn[ind==1, ] #train
testData <- churn[ind==2, ] #test

Crear árbol de decisión

ArbolRpart <- rpart(Cancelacion ~ ., method="class", data=trainData) #Formula: variable dependiente (Cancelacion) depende todas las otras

Gráficos

print(ArbolRpart)                         
n= 2965 

node), split, n, loss, yval, (yprob)
      * denotes terminal node

 1) root 2965 425 no (0.14333895 0.85666105)  
   2) Minutos al dia>=253.9 255 121 yes (0.52549020 0.47450980)  
     4) Minutos al dia>=316.35 19   0 yes (1.00000000 0.00000000) *
     5) Minutos al dia< 316.35 236 115 no (0.48728814 0.51271186)  
      10) Minutos al dia>=265.4 150  65 yes (0.56666667 0.43333333) *
      11) Minutos al dia< 265.4 86  30 no (0.34883721 0.65116279) *
   3) Minutos al dia< 253.9 2710 291 no (0.10738007 0.89261993)  
     6) Reclamaciones>=3.5 221 108 no (0.48868778 0.51131222)  
      12) Minutos al dia< 160.3 88  13 yes (0.85227273 0.14772727) *
      13) Minutos al dia>=160.3 133  33 no (0.24812030 0.75187970) *
     7) Reclamaciones< 3.5 2489 183 no (0.07352350 0.92647650)  
      14) Tiene plan internacional=yes 226  80 no (0.35398230 0.64601770)  
        28) Minutos internacionales>=13.05 42   0 yes (1.00000000 0.00000000) *
        29) Minutos internacionales< 13.05 184  38 no (0.20652174 0.79347826)  
          58) Llamadas internacionales< 2.5 34   0 yes (1.00000000 0.00000000) *
          59) Llamadas internacionales>=2.5 150   4 no (0.02666667 0.97333333) *
      15) Tiene plan internacional=no 2263 103 no (0.04551480 0.95448520) *
rpart.plot(ArbolRpart,extra=4)  # extra=4:probabilidad de observaciones por clase

printcp(ArbolRpart)             # estadísticas de resultados

Classification tree:
rpart(formula = Cancelacion ~ ., data = trainData, method = "class")

Variables actually used in tree construction:
[1] Llamadas internacionales Minutos al dia           Minutos internacionales  Reclamaciones            Tiene plan internacional

Root node error: 425/2965 = 0.14334

n= 2965 

        CP nsplit rel error  xerror     xstd
1 0.058824      0   1.00000 1.00000 0.044896
2 0.049412      3   0.82353 0.85647 0.042046
3 0.030588      6   0.64471 0.67529 0.037883
4 0.010000      8   0.58353 0.62353 0.036551
plotcp(ArbolRpart)              # evolución del error a medida que se incrementan los nodos

# Podado del árbol
pArbolRpart<- prune(ArbolRpart, cp= ArbolRpart$cptable[which.min(ArbolRpart$cptable[,"xerror"]),"CP"])
pArbolRpart<- prune(ArbolRpart, cp= 0.011111)
printcp(pArbolRpart)

Classification tree:
rpart(formula = Cancelacion ~ ., data = trainData, method = "class")

Variables actually used in tree construction:
[1] Llamadas internacionales Minutos al dia           Minutos internacionales  Reclamaciones            Tiene plan internacional

Root node error: 425/2965 = 0.14334

n= 2965 

        CP nsplit rel error  xerror     xstd
1 0.058824      0   1.00000 1.00000 0.044896
2 0.049412      3   0.82353 0.85647 0.042046
3 0.030588      6   0.64471 0.67529 0.037883
4 0.010000      8   0.58353 0.62353 0.036551

Predice las cancelaciones en testData

# Validamos la capacidad de predicción del árbol con el fichero de validación
testPredRpart <- predict(ArbolRpart, newdata = testData, type = "class")
# Visualizamos una matriz de confusión
table(testPredRpart, testData$Cancelacion)
             
testPredRpart  yes   no
          yes  190   61
          no    92 1692

Estadística del modelo

# Calculamos el % de aciertos
sum(testPredRpart == testData$Cancelacion) / length(testData$Cancelacion)*100
[1] 92.48157

SVM (otro ejemplo)

Modelo SVM sobre datos de iris:

data(iris)
attach(iris)
The following objects are masked from iris (pos = 8):

    Petal.Length, Petal.Width, Sepal.Length, Sepal.Width, Species

The following objects are masked from iris (pos = 13):

    Petal.Length, Petal.Width, Sepal.Length, Sepal.Width, Species

The following objects are masked from iris (pos = 19):

    Petal.Length, Petal.Width, Sepal.Length, Sepal.Width, Species
## classification mode
# default with factor response:
model <- svm(Species ~ ., data = iris)
# alternatively the traditional interface:
x <- subset(iris, select = -Species)
y <- Species
model <- svm(x, y) 
print(model)

Call:
svm.default(x = x, y = y)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  1 
      gamma:  0.25 

Number of Support Vectors:  51
summary(model)

Call:
svm.default(x = x, y = y)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  1 
      gamma:  0.25 

Number of Support Vectors:  51

 ( 8 22 21 )


Number of Classes:  3 

Levels: 
 setosa versicolor virginica

Testeo con datos de entrenamiento (claro, el modelo está muy ajustado para ellos):

# test with train data
pred <- predict(model, x)
# (same as:)
#pred <- fitted(model)
# Check accuracy:
table(pred, y)
            y
pred         setosa versicolor virginica
  setosa         50          0         0
  versicolor      0         48         2
  virginica       0          2        48
# compute decision values and probabilities:
pred <- predict(model, x, decision.values = TRUE)
attr(pred, "decision.values")[1:4,]
  setosa/versicolor setosa/virginica versicolor/virginica
1          1.196152         1.091757            0.6708810
2          1.064621         1.056185            0.8483518
3          1.180842         1.074542            0.6439798
4          1.110699         1.053012            0.6782041
# visualize (classes by color, SV by crosses):
distancias_escaladas <- cmdscale(dist(iris[,-5]))
plot(distancias_escaladas,
     col = as.integer(iris[,5]),
     pch = c("o","+")[1:150 %in% model$index + 1])

Modelo de REGRESION en 2 dimensiones:

## try regression mode on two dimensions
# create data
x <- seq(0.1, 5, by = 0.05)
y <- log(x) + rnorm(x, sd = 0.2)
# estimate model and predict input values
m   <- svm(x, y)
summary(m)

Call:
svm.default(x = x, y = y)


Parameters:
   SVM-Type:  eps-regression 
 SVM-Kernel:  radial 
       cost:  1 
      gamma:  1 
    epsilon:  0.1 


Number of Support Vectors:  79
new <- predict(m, x)
# visualize
plot(x, y)
points(x, log(x), col = 2)
points(x, new, col = 4)

## density-estimation

Creamos otras features con distribución normal y modelamos:

# create 2-dim. normal with rho=0:
X <- data.frame(a = rnorm(1000), b = rnorm(1000))
attach(X)
The following objects are masked from X (pos = 7):

    a, b

The following objects are masked from X (pos = 8):

    a, b

The following objects are masked from X (pos = 12):

    a, b

The following objects are masked from X (pos = 13):

    a, b

The following objects are masked from X (pos = 18):

    a, b

The following objects are masked from X (pos = 19):

    a, b
# MODELO:
#traditional way:
#m <- svm(X, gamma = 0.1)
# formula interface:
m <- svm(~., data = X, gamma = 0.1)
# Otra forma:X <- data.frame(a = rnorm(1000), b = rnorm(1000))
attach(X)
The following objects are masked from X (pos = 3):

    a, b

The following objects are masked from X (pos = 8):

    a, b

The following objects are masked from X (pos = 9):

    a, b

The following objects are masked from X (pos = 13):

    a, b

The following objects are masked from X (pos = 14):

    a, b

The following objects are masked from X (pos = 19):

    a, b

The following objects are masked from X (pos = 20):

    a, b
#m <- svm(~ a + b, gamma = 0.1)
summary(m)

Call:
svm(formula = ~., data = X, gamma = 0.1)


Parameters:
   SVM-Type:  one-classification 
 SVM-Kernel:  radial 
      gamma:  0.1 
         nu:  0.5 

Number of Support Vectors:  500




Number of Classes: 1
# test:
newdata <- data.frame(a = c(0, 4), b = c(0, 4))
predict(m, newdata)
    1     2 
 TRUE FALSE 
# visualize:
plot(X, col = 1:1000 %in% m$index + 1, xlim = c(-5,5), ylim=c(-5,5))
points(newdata, pch = "+", col = 2, cex = 5)

# weights: (example not particularly sensible)
i2 <- iris
levels(i2$Species)[3] <- "versicolor"
summary(i2$Species)
    setosa versicolor 
        50        100 
wts <- 100 / table(i2$Species)
wts

    setosa versicolor 
         2          1 
m <- svm(Species ~ ., data = i2, class.weights = wts)

SVM (ejemplo perros y gatos)

Link: https://www.youtube.com/watch?v=ImhjKyc88x0

library(e1071)
data(cats, package = 'MASS') # Features: sexo (sex), peso (bwt), peso_corazon (hwt)
#Datasets: train y test
ind <- sample(x=2, size = nrow(cats), replace = TRUE, prob = c(0.7,0.3)) #vector 1:144 de índices. Sus valores están en el rango entre 1:x; en este caso, los unos aparecerán un 70% y los doses un 30%.
testset <- cats[ind == 1,] #Coge las filas de CATS en aquellos indices que 'ind' toma valor 1
trainset <- cats[ind == 2,] #Coge las filas de CATS en aquellos indices que 'ind' toma valor 2
#Entrenamos el modelo
modelo_svm <- svm(Sex~., data=trainset, kernel = "radial")
prediccion <- predict(modelo_svm, newdata = testset[-1])
#Resultado y porcentaje de acierto
plot(modelo_svm, cats)

MC <- table(testset[,1], prediccion) #matriz de confusión
MC
   prediccion
     F  M
  F  7 23
  M  4 58
acierto <- ( sum(diag(MC)) )/ (sum(MC))
acierto
[1] 0.7065217

Otro ejemplo completo

Link: https://machinelearningmastery.com/how-to-estimate-model-accuracy-in-r-using-the-caret-package/

Data split:

# load the libraries
library(caret)
library(klaR)
# load the iris dataset
data(iris)
# define an 80%/20% train/test split of the dataset
split=0.80
trainIndex <- createDataPartition(iris$Species, p=split, list=FALSE)
data_train <- iris[ trainIndex,]
data_test <- iris[-trainIndex,]
# train a naive bayes model
model <- NaiveBayes(Species~., data=data_train)
# make predictions
x_test <- data_test[,1:4]
y_test <- data_test[,5]
predictions <- predict(model, x_test)
# summarize results
confusionMatrix(predictions$class, y_test)
Confusion Matrix and Statistics

            Reference
Prediction   setosa versicolor virginica
  setosa         10          0         0
  versicolor      0         10         2
  virginica       0          0         8

Overall Statistics
                                          
               Accuracy : 0.9333          
                 95% CI : (0.7793, 0.9918)
    No Information Rate : 0.3333          
    P-Value [Acc > NIR] : 8.747e-12       
                                          
                  Kappa : 0.9             
 Mcnemar's Test P-Value : NA              

Statistics by Class:

                     Class: setosa Class: versicolor Class: virginica
Sensitivity                 1.0000            1.0000           0.8000
Specificity                 1.0000            0.9000           1.0000
Pos Pred Value              1.0000            0.8333           1.0000
Neg Pred Value              1.0000            1.0000           0.9091
Prevalence                  0.3333            0.3333           0.3333
Detection Rate              0.3333            0.3333           0.2667
Detection Prevalence        0.3333            0.4000           0.2667
Balanced Accuracy           1.0000            0.9500           0.9000

Bootstrap:

# load the library
library(caret)
# load the iris dataset
data(iris)
# define training control
train_control <- trainControl(method="boot", number=100)
# train the model
model <- train(Species~., data=iris, trControl=train_control, method="nb")
# summarize results
print(model)
Naive Bayes 

150 samples
  4 predictors
  3 classes: 'setosa', 'versicolor', 'virginica' 

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

  usekernel  Accuracy   Kappa    
  FALSE      0.9518209  0.9270189
   TRUE      0.9537661  0.9300203

Tuning parameter 'fL' was held constant at a value of 0
Tuning parameter 'adjust' was held constant at a value of 1
Accuracy was used to select the optimal model using the largest value.
The final values used for the model were fL = 0, usekernel = TRUE and adjust = 1.

k-fold Cross Validation:

# load the library
library(caret)
# load the iris dataset
data(iris)
# define training control
train_control <- trainControl(method="cv", number=10)
# fix the parameters of the algorithm
grid <- expand.grid(.fL=c(0), .usekernel=c(FALSE))
# train the model
model <- train(Species~., data=iris, trControl=train_control, method="nb")#, tuneGrid=grid
# summarize results
print(model)
Naive Bayes 

150 samples
  4 predictors
  3 classes: 'setosa', 'versicolor', 'virginica' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 135, 135, 135, 135, 135, 135, ... 
Resampling results across tuning parameters:

  usekernel  Accuracy  Kappa
  FALSE      0.96      0.94 
   TRUE      0.96      0.94 

Tuning parameter 'fL' was held constant at a value of 0
Tuning parameter 'adjust' was held constant at a value of 1
Accuracy was used to select the optimal model using the largest value.
The final values used for the model were fL = 0, usekernel = FALSE and adjust = 1.

Repeated k-fold Cross Validation:

# load the library
library(caret)
# load the iris dataset
data(iris)
# define training control
train_control <- trainControl(method="repeatedcv", number=10, repeats=3)
# train the model
model <- train(Species~., data=iris, trControl=train_control, method="nb")
# summarize results
print(model)
Naive Bayes 

150 samples
  4 predictors
  3 classes: 'setosa', 'versicolor', 'virginica' 

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

  usekernel  Accuracy   Kappa    
  FALSE      0.9555556  0.9333333
   TRUE      0.9600000  0.9400000

Tuning parameter 'fL' was held constant at a value of 0
Tuning parameter 'adjust' was held constant at a value of 1
Accuracy was used to select the optimal model using the largest value.
The final values used for the model were fL = 0, usekernel = TRUE and adjust = 1.

Leave One Out Cross Validation:

# load the library
library(caret)
# load the iris dataset
data(iris)
# define training control
train_control <- trainControl(method="LOOCV")
# train the model
model <- train(Species~., data=iris, trControl=train_control, method="nb")
# summarize results
print(model)
Naive Bayes 

150 samples
  4 predictors
  3 classes: 'setosa', 'versicolor', 'virginica' 

No pre-processing
Resampling: Leave-One-Out Cross-Validation 
Summary of sample sizes: 149, 149, 149, 149, 149, 149, ... 
Resampling results across tuning parameters:

  usekernel  Accuracy   Kappa
  FALSE      0.9533333  0.93 
   TRUE      0.9600000  0.94 

Tuning parameter 'fL' was held constant at a value of 0
Tuning parameter 'adjust' was held constant at a value of 1
Accuracy was used to select the optimal model using the largest value.
The final values used for the model were fL = 0, usekernel = TRUE and adjust = 1.

Otro ejemplo SVM vs RPART:

library(e1071)
library(rpart)
#DATOS:
data(Ozone, package="mlbench")
## Split en Datasets (train y test)
index     <- 1:nrow(Ozone)
tercio <- trunc(length(index)/3) #usamos un 30% para test
testindex <- sample(index, tercio, replace = FALSE, prob = NULL)
testset   <- na.omit(Ozone[testindex,]) #TEST Quita las filas que tengan ALGUN valor NA
trainset  <- na.omit(Ozone[-testindex,])#TRAIN Quita las filas que tengan ALGUN valor NA
#Columna TARGET
indice_target <- which( colnames(trainset) == "V4" )
## svm (MAQUINAS VECTOR SOPORTE) para REGRESIÓN NO LINEAL
svm.model <- svm(formula = V4 ~ ., data = trainset, cost = 1000, gamma = 0.0001)
svm.pred  <- predict(svm.model, testset[,-indice_target])
svm_error <- crossprod(svm.pred - testset[,indice_target]) / length(testindex)
svm_error
         [,1]
[1,] 10.00099
## rpart (ARBOLES DE REGRESION) para REGRESIÓN NO LINEAL
rpart.model <- rpart(formula = V4 ~ ., data = trainset)
rpart.pred  <- predict(rpart.model, testset[,-indice_target])
rpart_error <- crossprod(rpart.pred - testset[,indice_target]) / length(testindex)
rpart_error
         [,1]
[1,] 23.82726

ENSEMBLING

Link: https://www.datacamp.com/community/tutorials/ensemble-r-machine-learning

library("SuperLearner")
library(MASS) #Data
# Train and test sets
train <- Pima.tr
test <- Pima.te
head(train)
summary(train)
     npreg            glu              bp              skin            bmi             ped              age         type    
 Min.   : 0.00   Min.   : 56.0   Min.   : 38.00   Min.   : 7.00   Min.   :18.20   Min.   :0.0850   Min.   :21.00   No :132  
 1st Qu.: 1.00   1st Qu.:100.0   1st Qu.: 64.00   1st Qu.:20.75   1st Qu.:27.57   1st Qu.:0.2535   1st Qu.:23.00   Yes: 68  
 Median : 2.00   Median :120.5   Median : 70.00   Median :29.00   Median :32.80   Median :0.3725   Median :28.00            
 Mean   : 3.57   Mean   :124.0   Mean   : 71.26   Mean   :29.21   Mean   :32.31   Mean   :0.4608   Mean   :32.11            
 3rd Qu.: 6.00   3rd Qu.:144.0   3rd Qu.: 78.00   3rd Qu.:36.00   3rd Qu.:36.50   3rd Qu.:0.6160   3rd Qu.:39.25            
 Max.   :14.00   Max.   :199.0   Max.   :110.00   Max.   :99.00   Max.   :47.90   Max.   :2.2880   Max.   :63.00            
help(Pima.tr)

El campo target es type (que es un factor), que indica si el paciente tiene diabetes o no.

#Since the type column was a factor, R will encode it to 1 and 2, but this is not what you want: ideally, you would like to work with the type encoded as 0 and 1, which are "No" and "Yes", respectively. In the above code chunk, you subtract 1 from the whole set to get your 0-1 encoding. R will also encode this in the factor order.
y <- as.numeric(train[,8]) - 1
ytest <- as.numeric(test[,8]) - 1
#Predictores y respuestas
x <- data.frame(train[,1:7])
xtest <- data.frame(test[,1:7])
#MODELOS DISPONIBLES:
listWrappers()
All prediction algorithm wrappers in SuperLearner:
 [1] "SL.bartMachine"      "SL.bayesglm"         "SL.biglasso"         "SL.caret"            "SL.caret.rpart"     
 [6] "SL.cforest"          "SL.dbarts"           "SL.earth"            "SL.extraTrees"       "SL.gam"             
[11] "SL.gbm"              "SL.glm"              "SL.glm.interaction"  "SL.glmnet"           "SL.ipredbagg"       
[16] "SL.kernelKnn"        "SL.knn"              "SL.ksvm"             "SL.lda"              "SL.leekasso"        
[21] "SL.lm"               "SL.loess"            "SL.logreg"           "SL.mean"             "SL.nnet"            
[26] "SL.nnls"             "SL.polymars"         "SL.qda"              "SL.randomForest"     "SL.ranger"          
[31] "SL.ridge"            "SL.rpart"            "SL.rpartPrune"       "SL.speedglm"         "SL.speedlm"         
[36] "SL.step"             "SL.stepAIC"          "SL.step.forward"     "SL.step.interaction" "SL.svm"             
[41] "SL.template"         "SL.xgboost"         

All screening algorithm wrappers in SuperLearner:
[1] "All"
[1] "screen.corP"           "screen.corRank"        "screen.glmnet"         "screen.randomForest"   "screen.SIS"           
[6] "screen.template"       "screen.ttest"          "write.screen.template"
#Modelos, usando cross-validation (con 5 intentos):
set.seed(150)
model <- SuperLearner(y, 
                      x,
                      SL.library=list("SL.ranger",
                                      "SL.ksvm",
                                      "SL.ipredbagg",
                                      "SL.bayesglm"))
# Return the model
summary(model)
                  Length Class  Mode       
call                4    -none- call       
libraryNames        4    -none- character  
SL.library          2    -none- list       
SL.predict        200    -none- numeric    
coef                4    -none- numeric    
library.predict   800    -none- numeric    
Z                 800    -none- numeric    
cvRisk              4    -none- numeric    
family             11    family list       
fitLibrary          4    -none- list       
varNames            7    -none- character  
validRows          10    -none- list       
method              3    -none- list       
whichScreen         7    -none- logical    
control             2    -none- list       
cvControl           4    -none- list       
errorsInCVLibrary   4    -none- logical    
errorsInLibrary     4    -none- logical    
metaOptimizer       8    nnls   list       
env               115    -none- environment
times               3    -none- list       

Predicciones con SuperLearner:

LS0tCnRpdGxlOiAiTUkgRVNUVURJTyBTT0JSRSBSIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCiMjIyBWYWxvcmVzIE5BIChtaXNzaW5nIHZhbHVlcykgeSBjw7NtbyByZWxsZW5hcmxvcwpQYXJ0aW1vcyBkZSB1bm8gZGUgbG9zIGRhdGFzZXRzIGRlIHBydWViYXMgcXVlIGhheSBlbiBSU3R1ZGlvLgpgYGB7cn0KI2xpYnJhcnkoaGVscD0iZGF0YXNldHMiKSAjbGlzdGEgY29tcGxldGEgKGNvZ2VyZW1vcyBlbCBkYXRhc2V0IGxsYW1hZG8gYXR0ZW51LCBwb3IgZWplbXBsbykKZGF0YShhdHRlbnUpCmF0dGFjaChhdHRlbnUpCmhlbHAoYXR0ZW51KQpib3hwbG90KGF0dGVudSwgY2V4LmF4aXM9MC41KSAKYGBgClZlbW9zIHF1ZSBsYSB2YXJpYWJsZSBkaXN0IGVzIGFzaW1ldHJpY2EgeSB0aWVuZSBvdXRsaWVycy4KVmFtb3MgcXVlIGxhcyB2YXJpYWJsZXMgbWFnIHkgYWNjZWwgc29uIGxlcHRvY8O6cnRpY2FzIChhcHJldGFkYXMgZW4gdG9ybm8gYSBsYSBtZWRpYSkKCmBgYHtyfQpzdW1tYXJ5KGF0dGVudSkKYGBgCgoKCgojIyMgTWF0cml6IGRlIHBhcmVqYXMgZGUgZmVhdHVyZXMgKENPUlJFTEFDSU9ORVMpCkhhY2Vtb3MgdW5hIGFuw6FsaXNpcyBleHBsb3JhdG9yaW8gZGUgbGFzIGNvcnJlbGFjaW9uZXMuCk1hdHJpeiBkZSBjb3JyZWxhY2lvbmVzIChlbnRyZSB2YXJpYWJsZXMpIHkgZnVuY2lvbiBkZSBkZW5zaWRhZCBkZSBwcm9iYWJpbGlkYWQgKHVuaXZhcmlhYmxlKToKYGBge3J9CmxpYnJhcnkoY2FyKQpzY2F0dGVycGxvdE1hdHJpeCh4PWF0dGVudSkKYGBgCk8gdGFtYmnDqW4gY29uc3RydWlybGEgdXNhbmRvIHVuYSBmdW5jacOzbiBwZXJzb25hbGl6YWRhIHBvciBub3NvdHJvczoKYGBge3J9CnBhaXJzKGF0dGVudSwgZ2FwPTAsIGxvd2VyLnBhbmVsID0gcGFuZWwuc21vb3RoLCB1cHBlci5wYW5lbCA9IGZ1bmN0aW9uKHgseSl7CiAgcGFuZWwuc21vb3RoKHgseSkKICBwYXIodXNyPWMoMCwxLDAsMSkpCiAgY29ycmVsYWNpb24gPC0gY29yKHgseSx1c2U9ImNvbXBsZXRlLm9icyIpCiAgdGV4dCgwLjYsMC43LGNvbD0iYmx1ZSIsIGNleD0xLjIsIHJvdW5kKGNvcnJlbGFjaW9uLCBkaWdpdHM9MikpCn0gICkKYGBgCkVzIGltcG9ydGFudGUgdmVyIHNpIGhheSBhbGd1bmEgY29ycmVsYWNpw7NuIGbDoWNpbG1lbnRlIHZpc2libGUgKGxpbmVhbC4uLikuCgpJRHMgZGUgbG9zIGVsZW1lbnRvcyBxdWUgdGllbmVuIHZhbG9yZXMgTkEgZW4gY3VhbHF1aWVyYSBkZSBzdXMgZmVhdHVyZXMgKGNvbHVtbmFzKToKYGBge3J9CndoaWNoKGlzLm5hKGF0dGVudSRldmVudCkpCndoaWNoKGlzLm5hKGF0dGVudSRtYWcpKQp3aGljaChpcy5uYShhdHRlbnUkc3RhdGlvbikpCndoaWNoKGlzLm5hKGF0dGVudSRkaXN0KSkKd2hpY2goaXMubmEoYXR0ZW51JGFjY2VsKSkKYGBgClZlbW9zIHF1ZSBzw7NsbyBsYSB2YXJpYWJsZSBzdGF0aW9uIHRpZW5lIHZhbG9yZXMgTkEuClRhbWJpw6luIHNlIHB1ZWRlbiBjb2dlciBzw7NsbyBsYXMgZmlsYXMgZW4gbGFzIHF1ZSB0b2RhcyBsYXMgZmVhdHVyZXMgZXN0w6FuIHJlbGxlbmFzIChzaW4gbmluZ8O6biB2YWxvciBOQSk6CmBgYHtyfQphdHRlbnVbY29tcGxldGUuY2FzZXMoYXR0ZW51KSxdCmBgYApTaSBxdWVyZW1vcyBFTElNSU5BUiBhcXVlbGxhcyBmaWxhcyAobWFyZ2luPTEgc2lnbmlmaWNhIFJPV1MpIHF1ZSB0ZW5nYW4gbcOhcyBkZSB1biB2YWxvciBOQToKMS4gQXBsaWNhciBsYSBmdW5jacOzbiBTVU0gc29icmUgZmlsYXMsIGNvbnRhbmRvIGVsIG7Dum1lcm8gZGUgZWxlbWVudG9zIE5BLgoyLiBTaSBoYXkgdW5vIG8gbcOhcywgc2FjbyBzdXMgw61uZGljZXMgKHdoaWNoKQozLiBDcmVvIHVuIHZlY3RvciBjb24gZXNvcyDDrW5kaWNlcyB5IHNlIGxvIHJlc3RvIGFsIGRhdGFzZXQgYXR0ZW51LgoKYGBge3J9CmZpbGFzX2Nvbl9uYSA8LSBhcHBseShhdHRlbnUsIE1BUkdJTiA9IDEsIEZVTiA9IGZ1bmN0aW9uKHgpeyBzdW0oaXMubmEoeCkpID49MX0pCmluZGljZXNfZGVfZmlsYXNfY29uX25hIDwtIHdoaWNoKGZpbGFzX2Nvbl9uYSkKZGF0b3Nfc2luaHVlY29zPWF0dGVudVsgLWMoIGluZGljZXNfZGVfZmlsYXNfY29uX25hICkgLCBdCmBgYAoKQ29tcHJvYmFtb3MgcXVlIHlhIG5vIGhheSBIVUVDT1MgKGNvbiBhbGfDum4gdmFsb3IgTkEpOgpgYGB7cn0KYXR0ZW51WyFjb21wbGV0ZS5jYXNlcyhkYXRvc19zaW5odWVjb3MpLF0KYGBgCiMjIyBBTkFMSVNJUyBERSBDT01QT05FTlRFUyBQUklOQ0lQQUxFUyAoUENBKQpTaSB0ZW5lbW9zIG11Y2hhcyBmZWF0dXJlcywgY29udmllbmUgcmVkdWNpcmxhcyBwYXJhIHF1ZSBlbCBhbsOhbGlzaXMgZGUgY29ycmVsYWNpb25lcyBzZWEgbcOhcyBzZW5jaWxsby4KRXMgTVVZIGltcG9ydGFudGUgaW5kaWNhciBxdWUgdHJhYmFqYW1vcyBjb24gbGEgbWF0cml6IGRlIGNvcnJlbGFjaW9uZXMgKGVuIHZleiBkZSBsYSBkZSBjb3Zhcmlhbnphcyk6IGNvcj1UUlVFCkFkZW3DoXMsIFBDQSBzw7NsbyB0cmFiYWphIGNvbiBmZWF0dXJlcyBxdWUgc2VhbiBOVU1FUklDQVMsIGFzw60gcXVlIGRlYmVtb3MgY29tcHJvYmFyIHF1ZSB0b2RhcyBsYXMgY29sdW1uYXMgKGZlYXR1cmVzKSBzb24gbnVtZXJpY2FzCmBgYHtyfQpkcm9wcyA8LSBjKCJzdGF0aW9uIikKZGF0b3NfcGFyYV9wY2EgPC0gYXR0ZW51WyAsICEobmFtZXMoYXR0ZW51KSAlaW4lIGRyb3BzKV0KZGF0b3Nfc2luX25hIDwtIG5hLm9taXQoZGF0b3NfcGFyYV9wY2EpICMgbGltcGlhciBsb3MgTkEKc2FwcGx5KGRhdG9zX3Npbl9uYSwgY2xhc3MpICNjbGFzZS9tb2RvIGRlIGNhZGEgZmVhdHVyZQpzYXBwbHkoZGF0b3Nfc2luX25hLCB0eXBlb2YpICN0aXBvIGRlIGNhZGEgZmVhdHVyZQpwY2FfYXR0ZW51IDwtIHByaW5jb21wKGRhdG9zX3Npbl9uYSwgY29yPVRSVUUpICAKcGNhX2F0dGVudQpwbG90KHBjYV9hdHRlbnUpCnN1bW1hcnkocGNhX2F0dGVudSkKYmlwbG90KHBjYV9hdHRlbnUpICNQQzIgdnMgUEMxCgojTG9zIHBlc29zIGRlIGNhZGEgaW5kaXZpZHVvIChmaWxhKSBwcm95ZWN0YWRvIGVuIGxhcyBjb21wb25lbnRlcyAoUEMxLCBQQzIuLi4pCnBjYV9wZXNvcyA8LSBwY2FfYXR0ZW51JHNjb3JlcwpwY2FfcGVzb3NfcGMxcGMyIDwtIHBjYV9wZXNvc1ssIDE6Ml0gI1Blc29zIHPDs2xvIGVuIGxhcyBjb21wb25lbnRlcyBxdWUgbcOhcyBpbmZsdXllbiBlbiBsYSB2YXJpYW56YSB0b3RhbApwbG90KHBjYV9wZXNvc19wYzFwYzJbLDFdLCBwY2FfcGVzb3NfcGMxcGMyWywyXSkgI21pc21hIGdyw6FmaWNhIHF1ZSB2ZWlhbW9zIGVuIGVsIGJpcGxvdApgYGAKVmVtb3MgcXVlIGxhIGNvbXBvbmVudGUgMSB0aWVuZSB1biBwZXNvIGRlbCA1NC40MSUgc29icmUgbGFzIHZhcmlhbnphczsgbGEgMiB0aWVuZSAyNi45JTsgZXRjLgpDb2dlbW9zIGxhIFBDMSB5IFBDMiwgcXVlIHN1bWFuIHVuIDgxJSBkZSBsYSBpbmZsdWVuY2lhIGVuIGxhIHZhcmlhbnphIHRvdGFsLgoKVmVtb3MgdGFtYmnDqW4gbG9zIHBlc29zIHF1ZSB0aWVuZW4gbGFzIHZhcmlhYmxlcyBkZSBlbnRyYWRhIChmZWF0dXJlcykgZGVudHJvIGRlIGxhcyBjb21wb25lbnRlcyBjb21wdWVzdGFzIChQQzEsIFBDMi4uLikuLCBtaXJhbmRvIGVsIHBsb3QgZGUgKipmbGVjaGFzKio6CgotIFBDMSAoZWplIGhvcml6b250YWwpIGVzdMOhIGFmZWN0YWRhIHBvciBsYXMgdmFyaWFibGVzOiBldmVudCwgZGlzdCwgbWFnIHkgYWNjZWwuIChlcyBkZWNpciwgcG9yIHRvZGFzIGxhcyBmZWF0dXJlcyBkZSBlbnRyYWRhLCBwZXJvIGVuIHByb3BvcmNpw7NuIGRpZmVyZW50ZSkuCi0gUEMyIChlamUgdmVydGljYWwpOiBlc3TDoSBhZmVjdGFkYSBwb3IgbWFnIHkgYWNjZWwsIHByaW5jaXBhbG1lbnRlLgoKCiMjIyBDTFVTVEVSSU5HIChoYWJpZW5kbyBoZWNobyBQQ0EgYW50ZXMpClZhbW9zIGEgbW9zdHJhciB1biBzdWJ0aXBvIGRlICJjbHVzdGVyaW5nIGplcsOhcnF1aWNvIiBsbGFtYWRvICpBZ2dsb21lcmF0aXZlIE5lc3RpbmcgKEFHTkVTKSouCgpIYXkgdHJlcyB0aXBvcyBkZSBtw6l0b2RvcyBkZSB1bmnDs24gZW4gY2x1c3RlcmluZzoKCi0gKlNpbmdsZSBsaW5rYWdlKjogc2Vnw7puIGxhIE1JTklNQSBkaXN0YW5jaWEgZW50cmUgZG9zIGVsZW1lbnRvcyBkZSBjbHVzdGVycyBkaWZlcmVudGVzLgotICpDb21wbGV0ZSBsaW5rYWdlKjogc2Vnw7puIGxhIE1BWElNQSBkaXN0YW5jaWEgZW50cmUgZG9zIGVsZW1lbnRvcyBkZSBjbHVzdGVycyBkaWZlcmVudGVzLgotICpBdmVyYWdlIGxpbmthZ2UqOiBzZWfDum4gbGEgZGlzdGFuY2lhIE1FRElBIGVudHJlIGRvcyBlbGVtZW50b3MgZGUgY2x1c3RlcnMgZGlmZXJlbnRlcy4KCmBgYHtyfQpsaWJyYXJ5KGNsdXN0ZXIpCmFnbmVzX3NpbmdsZSA8LSBhZ25lcyh4ID0gZGF0b3Nfc2luX25hLCBtZXRob2QgPSAic2luZ2xlIikKcGxvdChhZ25lc19zaW5nbGUpICMgQ3VhbmRvIHZlYW1vcyBlc3BhY2lvcyBlbnRyZSBsYXMgbXVlc3RyYXMsIGVzIGxhIHNlcGFyYWNpb24gZW50cmUgY2x1c3RlcnMuIEVzIERJRklDSUwgZGUgdmVyLgpgYGAKQ29uIFNpbmdsZSwgdmVtb3MgcXVlIGhheSAzIGNsdXN0ZXJzIChzaSBoZWlnaHQ9NTApLCBmaWrDoW5kb3NlIGVuIGxvcyBodWVjb3MgcXVlIGhheSBlbiBlbCBncsOhZmljbyAiYmFubmVyIiwgcXVlIHNlcGFyYW4gYSBsb3MgY2x1c3RlcnMuCmBgYHtyfQpsaWJyYXJ5KGNsdXN0ZXIpCmFnbmVzX2NvbXBsZXRlIDwtIGFnbmVzKHggPSBkYXRvc19zaW5fbmEsIG1ldGhvZCA9ICJjb21wbGV0ZSIpCnBsb3QoYWduZXNfY29tcGxldGUpICMgQ3VhbmRvIHZlYW1vcyBlc3BhY2lvcyBlbnRyZSBsYXMgbXVlc3RyYXMsIGVzIGxhIHNlcGFyYWNpb24gZW50cmUgY2x1c3RlcnMuIEVzIERJRklDSUwgZGUgdmVyLgpgYGAKQ29uIENvbXBsZXRlLCBzZSB2ZW4gY2xhcmFtZW50ZSAzIGNsdXN0ZXJzIChzaSBoZWlnaHQ9MjAwKS4KYGBge3J9CmxpYnJhcnkoY2x1c3RlcikKYWduZXNfYXZlcmFnZSA8LSBhZ25lcyh4ID0gZGF0b3Nfc2luX25hLCBtZXRob2QgPSAiYXZlcmFnZSIpCnBsb3QoYWduZXNfYXZlcmFnZSkgIyBDdWFuZG8gdmVhbW9zIGVzcGFjaW9zIGVudHJlIGxhcyBtdWVzdHJhcywgZXMgbGEgc2VwYXJhY2lvbiBlbnRyZSBjbHVzdGVycy4gRXMgRElGSUNJTCBkZSB2ZXIuCmBgYApDb24gQXZlcmFnZSwgc2UgdmVuIDMgY2x1c3RlcnMgKHNpIGhlaWdodD0xMDApLgpEb25kZSBtw6FzIGNsYXJvIGxvIHZlbW9zIGVzIGNvbiBlbCBtw6l0b2RvICJjb21wbGV0ZSIsIGFzw60gcXVlIGNyZWFtb3MgbG9zICpjbHVzdGVycyogdmlzdG9zIGNvbiAqQ09MT1JFUyouCkVzb3MgY29sb3JlcyBsb3MgcGludGFtb3Mgc29icmUgbG9zIHBlc29zIHF1ZSB5YSBoYWJpYW1vcyBjYWxjdWxhZG8gcGFyYSBQQ0EgKGRlIGxhcyBjb21wb25lbnRlcyBQQzEgeSBQQzIpLgpgYGB7cn0KY29tcGxldGVfM19jbHVzdGVycz1jdXRyZWUoYWduZXNfY29tcGxldGUsMykKcGxvdChwY2FfcGVzb3MsY29sPWNvbXBsZXRlXzNfY2x1c3RlcnMpCmBgYAoKCgojIyMgUkVHUkVTScOTTiBMSU5FQUwgU0lNUExFCkxpbms6IDxodHRwOi8vcnB1YnMuY29tL2pvc2VyL1JlZ3Jlc2lvblNpbXBsZT4KRGF0b3MgZW50cmFkYToKYGBge3J9CmdyYXNhc190YWJsYSA8LSByZWFkLnRhYmxlKCdodHRwOi8vdmVyc28ubWF0LnVhbS5lcy9+am9zZXIuYmVycmVuZGVyby9kYXRvcy9FZGFkUGVzb0dyYXNhcy50eHQnLCBoZWFkZXIgPSBUUlVFKQpuYW1lcyhncmFzYXNfdGFibGEpCnBhaXJzKGdyYXNhc190YWJsYSkKY29yKGdyYXNhc190YWJsYSkKYGBgClZlbW9zIGNvcnJlbGFjacOzbiAoMC44MykgZW50cmUgbGFzIHZhcmlhYmxlcyBlZGFkIHkgZ3Jhc2FzLCBjb21vIGVyYSBkZSBlc3BlcmFyIGEgc2ltcGxlIHZpc3RhLiBFbCBncsOhZmljbyBtdWVzdHJhIHRhbWJpw6luIGxhIHJlbGFjacOzbiBsaW5lYWwgKGEgb2pvKS4KClJlY3RhIGRlIG3DrW5pbW9zIGN1YWRyYWRvczoKVXNhbW9zIHVuIE1PREVMTyBMSU5FQUwgKGxtKSBwYXJhIGNhbGN1bGFyIHVuYSByZWdyZXNpw7NuIGxpbmVhbCBtdXkgc2VuY2lsbGEsIGRlIGdyYXNhcyByZXNwZWN0byBkZSBsYSBlZGFkOiAkeSA9MTAyLjU3NSs1LjMyMSp4JAoKTyBtZWpvciBhdW46ICAkeSA9IDEwMi41NzUgKCsvLTI5LjYzNzYpICsgNS4zMjEoKy8tMC43MjQzKSAqIHgkCgpgYGB7cn0KcmVncmVzaW9uIDwtIGxtKGdyYXNhcyB+IGVkYWQsIGRhdGEgPSBncmFzYXNfdGFibGEpICNncmFzYXMgZGVwZW5kaWVudGUgZGUgbGEgZWRhZApzdW1tYXJ5KHJlZ3Jlc2lvbikKCnBsb3QoZ3Jhc2FzX3RhYmxhJGVkYWQsIGdyYXNhc190YWJsYSRncmFzYXMsIHhsYWI9J0VkYWQnLCB5bGFiPSdHcmFzYXMnKQphYmxpbmUocmVncmVzaW9uKQoKcGxvdChyZWdyZXNpb24pCmBgYApWZW1vcyBxdWUgZWwgUl4yXiBlcyAwLjcwMTIgLS0+IEJvbmRhZCBkZSBsYSByZWN0YSBkZSBhanVzdGUuCgojIyMjIFBSRURJQ0NJT04gUEFSQSBOVUVWQVMgRU5UUkFEQVM6ClVzYXIgbGEgcmVjdGE6CmBgYHtyfQpudWV2YXMuZWRhZGVzIDwtIGRhdGEuZnJhbWUoZWRhZCA9IHNlcSgzMCwgNTApKQpwcmVkaWNjaW9uX251ZXZhPC1wcmVkaWN0KHJlZ3Jlc2lvbiwgbnVldmFzLmVkYWRlcykKcHJlZGljY2lvbl9udWV2YQpgYGAKCiMjIyMgRXhwbGljYWNpw7NuIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBzaW1wbGU6Ci0gTGEgY29sdW1uYSB0LXZhbHVlIGVzIGVsIGVzdGFkw61zdGljbyB0IC0tPiBDb2NpZW50ZSBlbnRyZSBjYWRhIGVzdGltYWRvciB5IHN1IGVycm9yIHTDrXBpY28uIFBlcm1pdGUgcmVhbGl6YXIgbG9zIGNvbnRyYXN0ZXMgZGUgaGlww7N0ZXNpcyBudWxhIChxdWUgc2VhbiAwKSAtLT4gSH4wfjrOsn4wfj0wIHkgSH4wfjrOsn4xfj0wIAotIExvcyAgcC12YWxvcmVzIGFwYXJlY2VuIGVuIGxhIGNvbHVtbmEgUHIoPnx0fCkgLS0+IFByb2JhYmlsaWRhZCBkZSBzdXBlcmFyIGVsIHVtYnJhbCAtLT4gQ29tbyBzb24gbXV5IHBlcXVlw7FvcyAobWVub3JlcyBxdWUgMC4wMSDDsyAwLjA1LCB1dGlsaXphZG9zIG5vcm1hbG1lbnRlKSA9PT4gU2UgcmVjaGF6YW4gYW1iYXMgaGlww7N0ZXNpcyBudWxhcy4KLSBFbCBlc3RpbWFkb3IgZGUgbGEgZGVzdmlhY2nDs24gdMOtcGljYSAoz4MpIGRlIGxvcyBlcnJvcmVzIGFwYXJlY2UgY29tbyBSZXNpZHVhbCBzdGFuZGFyZCBlcnJvciB5IHN1IHZhbG9yIGVuIGVsIGVqZW1wbG8gZXMgNDMuNQotIExvcyBpbnRlcnZhbG9zIGRlIGNvbmZpYW56YSBwYXJhIGxvcyBwYXLDoW1ldHJvcyBzZSBvYnRpZW5lbiBjb24gZWwgY29tYW5kbyAqKmNvbmZpbnQqKi4gRWwgcGFyw6FtZXRybyBsZXZlbCBwZXJtaXRlIGVsZWdpciBlbCBuaXZlbCBkZSBjb25maWFuemEgKHBvciBkZWZlY3RvIGVzIDAuOTUpOgpgYGB7cn0KY29uZmludChyZWdyZXNpb24pCmNvbmZpbnQocmVncmVzaW9uLCBsZXZlbCA9IDAuOTApCmBgYAoKTG9zICJpbnRlcnZhbG9zIGRlIGNvbmZpYW56YSBwYXJhIGxhIHJlc3B1ZXN0YSBtZWRpYSIgeSBsb3MgImludGVydmFsb3MgZGUgcHJlZGljY2nDs24gcGFyYSBsYSByZXNwdWVzdGEiIHNlIHB1ZWRlbiBvYnRlbmVyIHVzYW5kbyBlbCBjb21hbmRvIHByZWRpY3QuIApQb3IgZWplbXBsbywgZWwgc2lndWllbnRlIGPDs2RpZ28gY2FsY3VsYSB5IHJlcHJlc2VudGEgbG9zIGRvcyB0aXBvcyBkZSBpbnRlcnZhbG9zIHBhcmEgZWwgcmFuZ28gZGUgZWRhZGVzIHF1ZSB2YSBkZSAyMCBhIDYwIGHDsW9zIChsb3MgZGUgcHJlZGljY2nDs24gZW4gcm9qbyk6CgpgYGB7cn0KbnVldmFzLmVkYWRlcyA8LSBkYXRhLmZyYW1lKGVkYWQgPSBzZXEoMjAsIDYwKSkKIyBHcmFmaWNvIGRlIGRpc3BlcnNpb24geSByZWN0YQpwbG90KGdyYXNhc190YWJsYSRlZGFkLCBncmFzYXNfdGFibGEkZ3Jhc2FzLCB4bGFiPSdFZGFkJywgeWxhYj0nR3Jhc2FzJykKYWJsaW5lKHJlZ3Jlc2lvbikKCiMgSW50ZXJ2YWxvcyBkZSBjb25maWFuemEgZGUgbGEgcmVzcHVlc3RhIG1lZGlhOgojIGljIGVzIHVuYSBtYXRyaXogY29uIHRyZXMgY29sdW1uYXM6IGxhIHByaW1lcmEgZXMgbGEgcHJlZGljY2lvbiwgbGFzIG90cmFzIGRvcyBzb24gbG9zIGV4dHJlbW9zIGRlbCBpbnRlcnZhbG8KaWMgPC0gcHJlZGljdChyZWdyZXNpb24sIG51ZXZhcy5lZGFkZXMsIGludGVydmFsID0gJ2NvbmZpZGVuY2UnKQpsaW5lcyhudWV2YXMuZWRhZGVzJGVkYWQsIGljWywgMl0sIGx0eSA9IDIsIGNvbCA9ICdncmVlbicpCmxpbmVzKG51ZXZhcy5lZGFkZXMkZWRhZCwgaWNbLCAzXSwgbHR5ID0gMiwgY29sID0gJ2dyZWVuJykKCiMgSW50ZXJ2YWxvcyBkZSBwcmVkaWNjaW9uCmljIDwtIHByZWRpY3QocmVncmVzaW9uLCBudWV2YXMuZWRhZGVzLCBpbnRlcnZhbCA9ICdwcmVkaWN0aW9uJykKbGluZXMobnVldmFzLmVkYWRlcyRlZGFkLCBpY1ssIDJdLCBsdHkgPSAyLCBjb2wgPSAncmVkJykKbGluZXMobnVldmFzLmVkYWRlcyRlZGFkLCBpY1ssIDNdLCBsdHkgPSAyLCBjb2wgPSAncmVkJykKYGBgCiMjIyMgQW7DoWxpc2lzIGRlIGxhIHZhcmlhbnphOgpgYGB7cn0KYW5vdmEocmVncmVzaW9uKQpgYGAKCkRpYWduw7NzdGljbyBkZWwgbW9kZWxvOgpgYGB7cn0KcmVzaWR1b3MgPC0gcnN0YW5kYXJkKHJlZ3Jlc2lvbikKdmFsb3Jlcy5hanVzdGFkb3MgPC0gZml0dGVkKHJlZ3Jlc2lvbikKcGxvdCh2YWxvcmVzLmFqdXN0YWRvcywgcmVzaWR1b3MpCgojUHJ1ZWJhIGRlIG5vcm1hbGlkYWQgKHNlbmNpbGxhKSBwYXJhIHNhYmVyIHNpIGxhIG11ZXN0cmEgdGllbmUgdW5hIGRpc3RyaWJ1Y2lvbiBOT1JNQUwKcXFub3JtKHJlc2lkdW9zKQpxcWxpbmUocmVzaWR1b3MpCmBgYApDb21vIHRpZW5lIHBvY2FzIG11ZXN0cmFzLCBwb2Ryw61hbW9zIGhhYmVyIHBpbnRhZG8gdW5hIHQtU3R1ZGVudCBlbmNpbWEsIHBhcmEgcG9kZXIgY29tcGFyYXIuCgpPdHJhcyBwcnVlYmFzIGRlIG5vcm1hbGlkYWQgLS0+IEtvbG1vZ29yb3YtU21pcm5vdiAgeSBTaGFwaXJvLVdpbGsuCgojIyMjIEVqZW1wbG8gaW50ZXJlc2FudGU6IApgYGB7cn0KeCA8LSAxOjIwCncgPC0gMSArIHNxcnQoeCkvMiAjZGVzdmlhY2lvbmVzIGVzdGFuZGFyIChwZXNvcykKeSA8LSB4ICsgdypybm9ybSh4KQoKZHVtbXkgPC0tIGRhdGEuZnJhbWUoeD14LCB5PXkpCgpmbSA8LSBsbSh5fngsIGRhdGE9ZHVtbXkpICNNT0RFTE8gc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uCnN1bW1hcnkoZm0pCgpmbTEgPC0gbG0oeX54LCBkYXRhPWR1bW15LCB3ZWlnaHQ9MS93XjIpICNNT0RFTE8gY29uIHdlaWdodGVkIHJlZ3Jlc3Npb24Kc3VtbWFyeShmbTEpCmBgYApSZXN0byBkZWwgYW7DoWxpc2lzOgpgYGB7cn0KYXR0YWNoKGR1bW15KSAjaGFjZW1vcyB2aXNpYmxlcyBsYXMgY29sdW1uYXMgZGVsIGRhdGFmcmFtZSwgcGFyYSB0cmFiYWphciBkaXJlY3RhbWVudGUgY29uIHN1cyBub21icmVzLgpscmYgPC1sb3dlc3MoeCx5LCBmID0gMi8zLCBpdGVyID0gMywgZGVsdGEgPSAwLjAxICogZGlmZihyYW5nZSh4KSkpICNGdW5jaW9uIGRlIHJlZ3Jlc2lvbiBsb2NhbCBubyBwYXJhbWV0cmljYTogTE9XRVNTIHNtb290aGVyIChsb2NhbGx5LXdlaWdodGVkIHBvbHlub21pYWwgcmVncmVzc2lvbikKc3VtbWFyeShscmYpCnBsb3QoeCx5KQpsaW5lcyh4LCBscmYkeSwgY29sPSJibHVlIikgI3BpbnRhbW9zIGxhIGxpbmVhIGRlIGxhIFJFR1JFU0lPTiBMSU5FQUwKCmFibGluZSgwLDEsIGx0eT0zLCBjb2w9Im1hZ2VudGEiKSAjbGluZWEgZGUgcmVncmVzaW9uIHZlcmRhZGVyYSAoaW50ZXJjZXB0IDAsIHNsb3BlPTEpIChsaW5lYSBkZSBwdW50b3MpCmFibGluZShjb2VmKGZtKSwgY29sPSJncmVlbiIpICNsaW5lYSBkZSByZWdyZXNpb24gc2luIHBlc29zCmFibGluZShjb2VmKGZtMSksIGNvbD0icmVkIikgI2xpbmVhIGRlIHJlZ3Jlc2lvbiBjb24gcGVzb3MKCmRldGFjaCgpCgpwbG90KCBmaXR0ZWQoZm0pLCByZXNpZChmbSksIHhsYWI9IkZpdHRlZCB2YWx1ZXMiLCB5bGFiPSJSZXNpZHVhbHMiLCBtYWluPSJSZXNpZHVhbHMgdnMgRml0dGVkIiApICNBbmFsaXNpcyBkZSBoZXRlcm9kaWNpZGFkCnFxbm9ybSggcmVzaWQoZm0pLCBtYWluPSJSZXNpZHVhbHMgUmFua2l0IFBsb3QiICkgI0FuYWxpc2lzIGRlIHNrZXduZXNzLCBrdXJ0b3NpcyB5IG91dGxpZXJzIChubyBtdXkgdXRpbCBlbiBlc3RlIGNhc28pCiNybSggZm0sIGZtMSwgbHJmLCB4LCBkdW1teSkgI2xpbXBpZXphIGRlIHZhcmlhYmxlcwpgYGAKIyMgTWFudWFsIGRlIENSQU4tSU5UUk8uYXAuOC4yCkxvIGLDoXNpY286CmBgYHtyfQphdHRhY2goZmFpdGhmdWwpCgpzdW1tYXJ5KGVydXB0aW9ucykgI0NvbW8gaGUgaGVjaG8gYXR0YWNoLCBldml0byBoYWNlciBlc3RvOiBzdW1tYXJ5KGZhaXRoZnVsJGVydXB0aW9ucykKZml2ZW51bShlcnVwdGlvbnMpICNQaW50YSB1biAiZGlidWpvIiBoZWNobyBjb24gbnVtZXJvcywgcXVlIHBhcmVjZSB1biBoaXN0b2dyYW1hCnN0ZW0oZXJ1cHRpb25zKQpgYGAKRGlidWpvczoKYGBge3J9Cmhpc3QoZXJ1cHRpb25zLHNlcShmcm9tPTEuNiwgdG89NS4yLCBieT0wLjIpLCBwcm9iYWJpbGl0eSA9IFRSVUUpCmxpbmVzKGRlbnNpdHkoZXJ1cHRpb25zLCBidz0wLjEpKQpydWcoZXJ1cHRpb25zKQpgYGAKTGEgZnVuY2nDs24gZGUgZGVuc2lkYWQgKENERikgZXM6CmBgYHtyfQpwbG90KGVjZGYoZXJ1cHRpb25zKSwgZG8ucG9pbnRzPUZBTFNFLCB2ZXJ0aWNhbHMgPSBUUlVFKQpgYGAKU2kgY29nZW1vcyBzw7NsbyBhcXVlbGxvcyBjb24gWD4zOgpgYGB7cn0KZXJ1cGNpb25lc19sYXJnYXM8LWVydXB0aW9uc1tlcnVwdGlvbnM+M10KaGlzdChlcnVwY2lvbmVzX2xhcmdhcykKcGxvdChlY2RmKGVydXBjaW9uZXNfbGFyZ2FzKSwgZG8ucG9pbnRzPUZBTFNFLCB2ZXJ0aWNhbHMgPSBUUlVFKQp4PC1zZXEoMywgNS40LCAwLjAxKQoKI1VzYW5kbyBsYSBkaXN0cmlidWNpb24gTk9STUFMIChub3JtKQpsaW5lcyh4LCBwbm9ybSh4LCBtZWFuID0gbWVhbihlcnVwY2lvbmVzX2xhcmdhcyksIHNkPXNxcnQodmFyKGVydXBjaW9uZXNfbGFyZ2FzKSkpLCBsdHk9MykgICNsaW5lYSBkZSBwdW50b3MobHR5PTMpCmBgYAoKIyMjIFBSVUVCQVMgREUgTk9STUFMSURBRDoKCjEuIFEtUSAoUXVhbnRpbGUtUXVhbnRpbGUpCmBgYHtyfQpwYXIocHR5PSJzIikgI3BpbnRhIGxhIGNhamEgY3VhZHJhZGEgKGVuIHZleiBkZSBxdWUgc2VhIHJlY3Rhbmd1bGFyIHBvciBkZWZlY3RvKQpxcW5vcm0oZXJ1cGNpb25lc19sYXJnYXMpCnFxbGluZShlcnVwY2lvbmVzX2xhcmdhcykKCiNMYSBDT01QQVJBTU9TIGNvbiB1bmEgdC1TdHVkZW50IGRlIDUgZ3JhZG9zIGRlIGxpYmVydGFkOgpwdW50b3NfdF9zdHVkZW50IDwtIHJ0KDI1MCwgZGYgPSA1KQpxcW5vcm0ocHVudG9zX3Rfc3R1ZGVudCkKcXFsaW5lKHB1bnRvc190X3N0dWRlbnQpCgojUGludGFtb3MgZWwgcXFwbG90CnFxcGxvdChxdCggcHBvaW50cygyNTApLCBkZj01ICksIHB1bnRvc190X3N0dWRlbnQsIHhsYWIgPSAiUVFwbG90IHBhcmEgbGEgZGVuc2lkYWQgdC1TdHVkZW50IikKcXFsaW5lKHB1bnRvc190X3N0dWRlbnQpCmBgYAoKMi4gVGVzdCBTaGFwaXJvLVdpbGs6CmBgYHtyfQpzaGFwaXJvLnRlc3QoZXJ1cGNpb25lc19sYXJnYXMpCmBgYAoKMy4gVGVzdCBLb2xtb2dvcm92LVNtaXJub3Y6CmBgYHtyfQprcy50ZXN0KGVydXBjaW9uZXNfbGFyZ2FzLCAicG5vcm0iLCBtZWFuPW1lYW4oZXJ1cGNpb25lc19sYXJnYXMpLCBzZD1zcXJ0KHZhcihlcnVwY2lvbmVzX2xhcmdhcykpKQpgYGAKCgojIyMgUkVHUkVTSU9OIExPR0lTVElDQQoKYGBge3J9CmNoYWxsZW5nZXIgPC0gcmVhZC50YWJsZSgnaHR0cDovL3ZlcnNvLm1hdC51YW0uZXMvfmpvc2VyLmJlcnJlbmRlcm8vZGF0b3MvY2hhbGxlbmdlci50eHQnLCBoZWFkZXIgPSBUUlVFKQp0YWJsZShjaGFsbGVuZ2VyJGRlZmVjdG8pCgpjb2xvcmVzIDwtIE5VTEwKY29sb3Jlc1tjaGFsbGVuZ2VyJGRlZmVjdG89PTBdIDwtICdncmVlbicKY29sb3Jlc1tjaGFsbGVuZ2VyJGRlZmVjdG89PTFdIDwtICdyZWQnCnBsb3QoY2hhbGxlbmdlciR0ZW1wLCBjaGFsbGVuZ2VyJGRlZmVjdG8sIHBjaCA9IDIxLCBiZyA9IGNvbG9yZXMsIHhsYWIgPSAnVGVtcGVyYXR1cmEnLCB5bGFiID0gJ1Byb2JhYmlsaWRhZCBkZSBkZWZlY3RvcycpCmxlZ2VuZCgnYm90dG9tbGVmdCcsIGMoJ05vIGRlZmVjdG8nLCAnU2kgZGVmZWN0bycpLCBwY2ggPSAyMSwgY29sID0gYygnZ3JlZW4nLCAncmVkJykpCmBgYAoKCiMjIyBESVNUUklCVUNJT05FUyBERSBQUk9CQUJJTElEQUQKSGF5IG11Y2hhczogYmlub21pYWwgKGJpbm9tKSwgUG9pc3NvbiAocG9pcyksIG5vcm1hbCAobm9ybSksIGV4cG9uZW5jaWFsIChleHApLCB0LVN0dWRlbnQgKHQpLCBjaGktY3VhZHJhZG8gKGNoaXNxKSwgRiAoZikuLi4KCgojIyMgTUFRVUlOQVMgREUgVkVDVE9SIFNPUE9SVEUgKFNWTSkKQnVzY2EgZWwgaGlwZXJwbGFubyBxdWUgc2VwYXJlIGxhcyBvYnNlcnZhY2lvbmVzIHNlZ8O6biBzdSBjbGFzZXMgKGNsYXNpZmljYWNpw7NuKS4KQnVlbmEgZXhwbGljYWNpw7NuOiA8aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1JbWhqS3ljODh4MD4KCkxpbmsgZGUgbWkgZWplbXBsbzogPGh0dHA6Ly9ycHVicy5jb20vam9zZXIvc3ZtPgpgYGB7cn0KbGlicmFyeShNQVNTKQpsaWJyYXJ5KGUxMDcxKQpsb2FkKHVybCgnaHR0cDovL3d3dy51YW0uZXMvam9zZXIuYmVycmVuZGVyby9kYXRvcy9wcmFjdGljYS1zdm0taW8uUkRhdGEnKSkKCiMgTW9zdHJhbW9zIDEwIGZpbGFzCmhlYWQoYnJlYXN0LmNhbmNlcjIpCmBgYAoKR3LDoWZpY2FtZW50ZSwgcGludGFtb3MgbGEgY29ycmVsYWNpw7NuIGVudHJlIGxhcyAyIGZlYXR1cmVzIGRlIGVudHJhZGE6CmBgYHtyfQojIFByZXBhcmEgbG9zIGRhdG9zCnggPC0gY2JpbmQoYnJlYXN0LmNhbmNlcjIkeC5zbW9vdGhuZXNzLCBicmVhc3QuY2FuY2VyMiR4LmNvbmNhdmVwb2ludHMpCmhlYWQoeCkKeSA8LSBicmVhc3QuY2FuY2VyMiR5CmhlYWQoeSkKbjAgPC0gc3VtKHk9PTApCm4xIDwtIHN1bSh5PT0xKQojIFBhcmEgcXVlIGxvcyBncmFmaWNvcyBxdWVkZW4gbWFzIGJvbml0b3MgKHJvam8gPSBtYWxpZ25vLCB2ZXJkZSA9IGJlbmlnbm8pCmNvbG9yZXMgPC0gYyhyZXAoJ2dyZWVuJyxuMCkscmVwKCdyZWQnLG4xKSkKcGNobiA8LSAyMQoKIyBEaWFncmFtYSBkZSBkaXNwZXJzaW9uCnBsb3QoeCwgcGNoID0gcGNobiwgYmcgPSBjb2xvcmVzLCB4bGFiPSdzbW9vdGhuZXNzJywgeWxhYj0nY29uY2F2ZXBvaW50cycpCmBgYAoKIyMjIyBTVk0gbGluZWFsClNpZW5kbyBsYSB2YXJpYWJsZSAqKnkqKiB1biBmYWN0b3IsIHF1ZXJlbW9zIHByZWRlY2lyIHVzYW5kbyBlbCByZXN0byBkZSBmZWF0dXJlcy4KClBhcmEgY2FsY3VsYXIgbGEgcmVnbGEgZGUgY2xhc2lmaWNhY2nDs24gU1ZNIGxpbmVhbCBjb24gQz0xMCwgc2UgdXNhIGxhIGZ1bmNpw7NuIHN2bSBkZWwgcGFxdWV0ZSBlMTA3MS4gCkVsIHByaW1lciBhcmd1bWVudG8geX4uIGluZGljYSBxdWUgbGEgdmFyaWFibGUgeSAocXVlIGRlYmUgc2VyIG5lY2VzYXJpYW1lbnRlIHVuIGZhY3Rvcikgc2UgZGVzZWEgcHJlZGVjaXIgZW4gdMOpcm1pbm9zIGRlbCByZXN0byBkZSB2YXJpYWJsZXMgZGVsIGZpY2hlcm8uIExhIHNpbnRheGlzIGVzIHNpbWlsYXIgYSBsYSBxdWUgdXRpbGl6YXLDrWFtb3MgcGFyYSBhanVzdGFyIHVuIG1vZGVsbyBsaW5lYWwgbyB1biBtb2RlbG8gZGUgcmVncmVzacOzbiBsb2fDrXN0aWNhLgpFbCBzZWd1bmRvIGFyZ3VtZW50byBpbmRpY2EgZWwgZmljaGVybyBlbiBlbCBxdWUgZXN0w6FuIGxhcyB2YXJpYWJsZXMgcXVlIHZhbW9zIGEgdXNhci4gCkVsIGFyZ3VtZW50byBrZXJuZWwgY29ycmVzcG9uZGUgYWwgbsO6Y2xlbyBxdWUgcmVwcmVzZW50YSBlbCBwcm9kdWN0byBlc2NhbGFyIHF1ZSBxdWVyZW1vcyB1dGlsaXphci4gCkxhIG9wY2nDs24gbGluZWFyIGNvcnJlc3BvbmRlIGEgayh4LHkpPXjigLJ5LiAKRWwgYXJndW1lbnRvIGNvc3QgZGV0ZXJtaW5hIGxhIHBlbmFsaXphY2nDs24gcXVlIHBvbmVtb3MgYSBsb3MgZXJyb3JlcyBkZSBjbGFzaWZpY2FjacOzbi4gCkNvbiBlbCBmaW4gZGUgZXN0aW1hciBsYSBwcm9iYWJpbGlkYWQgZGUgY2xhc2lmaWNhciBlcnLDs25lYW1lbnRlIHVuYSBvYnNlcnZhY2nDs24gc2UgcHVlZGUgdXRpbGl6YXIgdmFsaWRhY2nDs24gY3J1emFkYSwgZGl2aWRpZW5kbyBsYSBtdWVzdHJhIGVuLCBwb3IgZWplbXBsbywgZG9zIHBhcnRlcy4gRWxsbyBzZSBjb25zaWd1ZSBmaWphbmRvIGNyb3NzPTIuIApGaW5hbG1lbnRlLCBzY2FsZT1GQUxTRSBzZSB1c2EgcGFyYSB1c2FyIGxvcyBkYXRvcyBubyBlc3RhbmRhcml6YWRvcyAocG9yIGRlZmVjdG8sIHPDrSBzZSBlc3RhbmRhcml6YW4pLgpgYGB7cn0KQyA8LSAxMApzdm0ubGluZWFsIDwtIHN2bSh5fi4sIGRhdGE9YnJlYXN0LmNhbmNlcjIsIGtlcm5lbD0nbGluZWFyJywgY29zdD1DLCBjcm9zcz0yLCBzY2FsZT1GQUxTRSkgI1N1IHR5cGUgZXMgQy1jbGFzc2lmaWNhdGlvbiBwb3JxdWUgeSBlcyB1biBmYWN0b3IKc3VtbWFyeShzdm0ubGluZWFsKQoKeC5zdm0gPC0geFtzdm0ubGluZWFsJGluZGV4LF0KdyA8LSBjcm9zc3Byb2QoeC5zdm0sIHN2bS5saW5lYWwkY29lZnMpCncwIDwtIHN2bS5saW5lYWwkcmhvCgpwbG90KHgsIHBjaCA9IHBjaG4sIGJnID0gY29sb3JlcywgeGxhYj0nc21vb3RobmVzcycsIHlsYWI9J2NvbmNhdmVwb2ludHMnKQphYmxpbmUodzAvd1syXSwgLXdbMV0vd1syXSwgbHdkPTIsIGNvbD0nYmx1ZScpCgojRXN0YWRpc3RpY2FzOiBwcmVjaXNpw7NuIHkgY29iZXJ0dXJhCnRhYmxlKHByZWRpY3Qoc3ZtLmxpbmVhbCksIGJyZWFzdC5jYW5jZXIyJHksIGRubj1jKCJQcmVkaWN0aW9uIiwgIkFjdHVhbCIpKQpsaWJyYXJ5KGNhcmV0KQpjb25mdXNpb25NYXRyaXgoYnJlYXN0LmNhbmNlcjIkeSwgcHJlZGljdChzdm0ubGluZWFsKSkKYGBgCkluZGljYSBxdWUgaGF5IDI1OCB2ZWN0b3JlcyBzb3BvcnRlLgpVc2FuZG8gdmFsaWRhY2nDs24gY3J1emFkYSBjb24gbGEgbXVlc3RyYSBkaXZpZGlkYSBlbiBkb3MgcGFydGVzIHNlIGVzdGltYSB1bmEgcHJvYmFiaWxpZGFkIGRlIGFjaWVydG8gZW4gbGEgY2xhc2lmaWNhY2nDs24gZGUgYXByb3hpbWFkYW1lbnRlIGVsIDg4JS4gUG9kZW1vcyBjYW1iaWFyIGVsIHBhcsOhbWV0cm8gZGUgcGVuYWxpemFjacOzbiBwYXJhIHZlciBzaSBlc3RvcyB2YWxvcmVzIGF1bWVudGFuIG8gZGlzbWludXllbi4KCiMjIyMgU1ZNIGN1YWRyw6F0aWNvCmBgYHtyfQpzdm0uY3VhZHJhdGljbyA8LSBzdm0oZm9ybXVsYSA9IHl+LiwgZGF0YSA9IGJyZWFzdC5jYW5jZXIyLCBrZXJuZWw9J3BvbHlub21pYWwnLCBkZWdyZWU9MiwgZ2FtbWE9MSwgY29lZjA9MSwgY29zdD1DLCBjcm9zcz0xMCwgc2NhbGU9RkFMU0UpCnN1bW1hcnkoc3ZtLmN1YWRyYXRpY28pCgojRXN0YWRpc3RpY2FzOiBwcmVjaXNpw7NuIHkgY29iZXJ0dXJhCnRhYmxlKHByZWRpY3Qoc3ZtLmN1YWRyYXRpY28pLCBicmVhc3QuY2FuY2VyMiR5LCBkbm49YygiUHJlZGljdGlvbiIsICJBY3R1YWwiKSkKbGlicmFyeShjYXJldCkKY29uZnVzaW9uTWF0cml4KGJyZWFzdC5jYW5jZXIyJHksIHByZWRpY3Qoc3ZtLmN1YWRyYXRpY28pKQpgYGAKCiMjIyMgU1ZNIHJhZGlhbApgYGB7cn0Kc3ZtLnJhZGlhbCA8LSBzdm0oZm9ybXVsYSA9IHl+LiwgZGF0YSA9IGJyZWFzdC5jYW5jZXIyLCBrZXJuZWw9J3JhZGlhbCcsIGRlZ3JlZT0yLCBnYW1tYT0xLCBjb2VmMD0xLCBjb3N0PUMsIGNyb3NzPTEwLCBzY2FsZT1GQUxTRSkKc3VtbWFyeShzdm0ucmFkaWFsKQoKI0VzdGFkaXN0aWNhczogcHJlY2lzacOzbiB5IGNvYmVydHVyYQp0YWJsZShwcmVkaWN0KHN2bS5yYWRpYWwpLCBicmVhc3QuY2FuY2VyMiR5LCBkbm49YygiUHJlZGljdGlvbiIsICJBY3R1YWwiKSkKbGlicmFyeShjYXJldCkKY29uZnVzaW9uTWF0cml4KGJyZWFzdC5jYW5jZXIyJHksIHByZWRpY3Qoc3ZtLnJhZGlhbCkpCmBgYApTZSB2ZSBxdWUgZXMgbWVqb3IgZWwga2VybmVsIExJTkVBTCAoZ3JhZG8pLCBwZXJvIGRlYmVtb3MgbWlyYXIgZWwgKmFjY3VyYWN5KiB5ICpjb2JlcnR1cmEqIC0tPiBHYW5hIENVQURSQVRJQ08sIHBhcmVjZS4KCgoKIyMjIEFSQk9MRVMgREUgREVDSVNJw5NOCkxpbms6IDxodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PW1naDRLZGJZSHYwPgpFcyB1biBhbGdvcml0bW8gZGUgQ0xBU0lGSUNBQ0nDk04gc3VwZXJ2aXNhZGEuIExhIHZhcmlhYmxlIGRlcGVuZGllbnRlIHByZWRpY2hhIHB1ZWRlIHNlcjoKCi0gQ3VhbnRpdGF0aXZhIC0tPiDDgXJib2wgZGUgUkVHUkVTScOTTgotIEN1YWxpdGF0aXZhIC0tPiDDgXJib2wgZGUgQ0xBU0lGSUNBQ0nDk04KCiMjIyMgQ2FyZ2FyIGRhdGFzZXQKYGBge3J9CmxpYnJhcnkocnBhcnQpICNBbGdvcml0bW8gcGFyYSDDoXJib2wgZGUgZGVjaXNpw7NuCmxpYnJhcnkocnBhcnQucGxvdCkgI1Bsb3QgZGUgw6FyYm9sIGRlIGRlY2lzacOzbgpsaWJyYXJ5KCJDNTAiKSAjcGFxdWV0ZSBxdWUgdGllbmUgZWwgZGF0YXNldCBkZSBlbnRyYWRhIHkgZWwgYWxnb3JpdG1vIChycGFydCkKZGF0YShjaHVybikgI0RBVEFTRVQgZGUgZWplbXBsbyBzb2JyZSBDSFVSTiAoY2xpZW50ZXMgcXVlIGFiYW5kb25hbiB1bmEgZW1wcmVzYSkKYGBgCiMjIyMgUHJlcGFyYXIgc2V0IGRlIGRhdG9zLCBkaXZpZGllbmRvIGVuIGVudHJlbmFtaWVudG8geSB0ZXN0CmBgYHtyfQpjaHVybl9qdW50b3MgPC0gcmJpbmQoY2h1cm5UZXN0LCBjaHVyblRyYWluKSAjanVudGEgZmlsYXMKaGVhZChjaHVybl9qdW50b3MsIG49M0wpICNGRUFUVVJFUyBkaXNwb25pYmxlcyAoNTAwMHgyMCkuIE1vc3RyYW1vcyAzIGZpbGFzCmNodXJuIDwtIGNodXJuX2p1bnRvc1ssIGMoNCw3LDgsMTYsMTksMTcsMjApXSAjVXNhcmVtb3Mgc29sbyBhbGd1bmFzIGNvbHVtbmFzIChmZWF0dXJlcykKbmFtZXMoY2h1cm4pICMgbm9tYnJlcyBlbiBpbmdsZXMKbmFtZXMoY2h1cm4pIDwtIGMoIlRpZW5lIHBsYW4gaW50ZXJuYWNpb25hbCIsICJNaW51dG9zIGFsIGRpYSIsICJMbGFtYWRhcyBhbCBkaWEiLCAiTWludXRvcyBpbnRlcm5hY2lvbmFsZXMiLCAiUmVjbGFtYWNpb25lcyIsICJMbGFtYWRhcyBpbnRlcm5hY2lvbmFsZXMiLCAiQ2FuY2VsYWNpb24iKSAjbG9zIHJlbm9tYnJvIGEgbm9tYnJlcyBlbiBlc3BhbmhvbAoKaW5kIDwtIHNhbXBsZSgyLCBucm93KGNodXJuKSwgcmVwbGFjZT1UUlVFLCBwcm9iPWMoMC42LCAwLjQpKSAjdHJhaW4gKDYwJSkgeSB0ZXN0ICg0MCUpCnRyYWluRGF0YSA8LSBjaHVybltpbmQ9PTEsIF0gI3RyYWluCnRlc3REYXRhIDwtIGNodXJuW2luZD09MiwgXSAjdGVzdApgYGAKIyMjIyBDcmVhciDDoXJib2wgZGUgZGVjaXNpw7NuCmBgYHtyfQpBcmJvbFJwYXJ0IDwtIHJwYXJ0KENhbmNlbGFjaW9uIH4gLiwgbWV0aG9kPSJjbGFzcyIsIGRhdGE9dHJhaW5EYXRhKSAjRm9ybXVsYTogdmFyaWFibGUgZGVwZW5kaWVudGUgKENhbmNlbGFjaW9uKSBkZXBlbmRlIHRvZGFzIGxhcyBvdHJhcwpgYGAKCiMjIyMgR3LDoWZpY29zCmBgYHtyfQpwcmludChBcmJvbFJwYXJ0KSAgICAgICAgICAgICAgICAgICAgICAgICAKcnBhcnQucGxvdChBcmJvbFJwYXJ0LGV4dHJhPTQpICAjIGV4dHJhPTQ6cHJvYmFiaWxpZGFkIGRlIG9ic2VydmFjaW9uZXMgcG9yIGNsYXNlCgpwcmludGNwKEFyYm9sUnBhcnQpICAgICAgICAgICAgICMgZXN0YWTDrXN0aWNhcyBkZSByZXN1bHRhZG9zCnBsb3RjcChBcmJvbFJwYXJ0KSAgICAgICAgICAgICAgIyBldm9sdWNpw7NuIGRlbCBlcnJvciBhIG1lZGlkYSBxdWUgc2UgaW5jcmVtZW50YW4gbG9zIG5vZG9zCmBgYAoKYGBge3J9CiMgUG9kYWRvIGRlbCDDoXJib2wKcEFyYm9sUnBhcnQ8LSBwcnVuZShBcmJvbFJwYXJ0LCBjcD0gQXJib2xScGFydCRjcHRhYmxlW3doaWNoLm1pbihBcmJvbFJwYXJ0JGNwdGFibGVbLCJ4ZXJyb3IiXSksIkNQIl0pCnBBcmJvbFJwYXJ0PC0gcHJ1bmUoQXJib2xScGFydCwgY3A9IDAuMDExMTExKQpwcmludGNwKHBBcmJvbFJwYXJ0KQpgYGAKCiMjIyMgUHJlZGljZSBsYXMgY2FuY2VsYWNpb25lcyBlbiB0ZXN0RGF0YSAKYGBge3J9CiMgVmFsaWRhbW9zIGxhIGNhcGFjaWRhZCBkZSBwcmVkaWNjacOzbiBkZWwgw6FyYm9sIGNvbiBlbCBmaWNoZXJvIGRlIHZhbGlkYWNpw7NuCnRlc3RQcmVkUnBhcnQgPC0gcHJlZGljdChBcmJvbFJwYXJ0LCBuZXdkYXRhID0gdGVzdERhdGEsIHR5cGUgPSAiY2xhc3MiKQoKIyBWaXN1YWxpemFtb3MgdW5hIG1hdHJpeiBkZSBjb25mdXNpw7NuCnRhYmxlKHRlc3RQcmVkUnBhcnQsIHRlc3REYXRhJENhbmNlbGFjaW9uKQpgYGAKCiMjIyMgRXN0YWTDrXN0aWNhIGRlbCBtb2RlbG8gIApgYGB7cn0KIyBDYWxjdWxhbW9zIGVsICUgZGUgYWNpZXJ0b3MKc3VtKHRlc3RQcmVkUnBhcnQgPT0gdGVzdERhdGEkQ2FuY2VsYWNpb24pIC8gbGVuZ3RoKHRlc3REYXRhJENhbmNlbGFjaW9uKSoxMDAKYGBgCgoKIyMjIFNWTSAob3RybyBlamVtcGxvKQpNb2RlbG8gU1ZNIHNvYnJlIGRhdG9zIGRlIGlyaXM6CmBgYHtyfQpkYXRhKGlyaXMpCmF0dGFjaChpcmlzKQoKIyMgY2xhc3NpZmljYXRpb24gbW9kZQojIGRlZmF1bHQgd2l0aCBmYWN0b3IgcmVzcG9uc2U6Cm1vZGVsIDwtIHN2bShTcGVjaWVzIH4gLiwgZGF0YSA9IGlyaXMpCgojIGFsdGVybmF0aXZlbHkgdGhlIHRyYWRpdGlvbmFsIGludGVyZmFjZToKeCA8LSBzdWJzZXQoaXJpcywgc2VsZWN0ID0gLVNwZWNpZXMpCnkgPC0gU3BlY2llcwptb2RlbCA8LSBzdm0oeCwgeSkgCgpwcmludChtb2RlbCkKc3VtbWFyeShtb2RlbCkKYGBgClRlc3RlbyBjb24gZGF0b3MgZGUgZW50cmVuYW1pZW50byAoY2xhcm8sIGVsIG1vZGVsbyBlc3TDoSBtdXkgYWp1c3RhZG8gcGFyYSBlbGxvcyk6CmBgYHtyfQojIHRlc3Qgd2l0aCB0cmFpbiBkYXRhCnByZWQgPC0gcHJlZGljdChtb2RlbCwgeCkKIyAoc2FtZSBhczopCiNwcmVkIDwtIGZpdHRlZChtb2RlbCkKCiMgQ2hlY2sgYWNjdXJhY3k6CnRhYmxlKHByZWQsIHkpCgojIGNvbXB1dGUgZGVjaXNpb24gdmFsdWVzIGFuZCBwcm9iYWJpbGl0aWVzOgpwcmVkIDwtIHByZWRpY3QobW9kZWwsIHgsIGRlY2lzaW9uLnZhbHVlcyA9IFRSVUUpCmF0dHIocHJlZCwgImRlY2lzaW9uLnZhbHVlcyIpWzE6NCxdCgojIHZpc3VhbGl6ZSAoY2xhc3NlcyBieSBjb2xvciwgU1YgYnkgY3Jvc3Nlcyk6CmRpc3RhbmNpYXNfZXNjYWxhZGFzIDwtIGNtZHNjYWxlKGRpc3QoaXJpc1ssLTVdKSkKcGxvdChkaXN0YW5jaWFzX2VzY2FsYWRhcywKICAgICBjb2wgPSBhcy5pbnRlZ2VyKGlyaXNbLDVdKSwKICAgICBwY2ggPSBjKCJvIiwiKyIpWzE6MTUwICVpbiUgbW9kZWwkaW5kZXggKyAxXSkKCmBgYApNb2RlbG8gZGUgUkVHUkVTSU9OIGVuIDIgZGltZW5zaW9uZXM6CmBgYHtyfQojIyB0cnkgcmVncmVzc2lvbiBtb2RlIG9uIHR3byBkaW1lbnNpb25zCgojIGNyZWF0ZSBkYXRhCnggPC0gc2VxKDAuMSwgNSwgYnkgPSAwLjA1KQp5IDwtIGxvZyh4KSArIHJub3JtKHgsIHNkID0gMC4yKQoKIyBlc3RpbWF0ZSBtb2RlbCBhbmQgcHJlZGljdCBpbnB1dCB2YWx1ZXMKbSAgIDwtIHN2bSh4LCB5KQpzdW1tYXJ5KG0pCm5ldyA8LSBwcmVkaWN0KG0sIHgpCgojIHZpc3VhbGl6ZQpwbG90KHgsIHkpCnBvaW50cyh4LCBsb2coeCksIGNvbCA9IDIpCnBvaW50cyh4LCBuZXcsIGNvbCA9IDQpCgojIyBkZW5zaXR5LWVzdGltYXRpb24KCmBgYApDcmVhbW9zIG90cmFzIGZlYXR1cmVzIGNvbiBkaXN0cmlidWNpw7NuIG5vcm1hbCB5IG1vZGVsYW1vczoKYGBge3J9CiMgY3JlYXRlIDItZGltLiBub3JtYWwgd2l0aCByaG89MDoKWCA8LSBkYXRhLmZyYW1lKGEgPSBybm9ybSgxMDAwKSwgYiA9IHJub3JtKDEwMDApKQphdHRhY2goWCkKCiMgTU9ERUxPOgojdHJhZGl0aW9uYWwgd2F5OgojbSA8LSBzdm0oWCwgZ2FtbWEgPSAwLjEpCiMgZm9ybXVsYSBpbnRlcmZhY2U6Cm0gPC0gc3ZtKH4uLCBkYXRhID0gWCwgZ2FtbWEgPSAwLjEpCiMgT3RyYSBmb3JtYTpYIDwtIGRhdGEuZnJhbWUoYSA9IHJub3JtKDEwMDApLCBiID0gcm5vcm0oMTAwMCkpCmF0dGFjaChYKQoKI20gPC0gc3ZtKH4gYSArIGIsIGdhbW1hID0gMC4xKQoKc3VtbWFyeShtKQoKIyB0ZXN0OgpuZXdkYXRhIDwtIGRhdGEuZnJhbWUoYSA9IGMoMCwgNCksIGIgPSBjKDAsIDQpKQpwcmVkaWN0KG0sIG5ld2RhdGEpCgojIHZpc3VhbGl6ZToKcGxvdChYLCBjb2wgPSAxOjEwMDAgJWluJSBtJGluZGV4ICsgMSwgeGxpbSA9IGMoLTUsNSksIHlsaW09YygtNSw1KSkKcG9pbnRzKG5ld2RhdGEsIHBjaCA9ICIrIiwgY29sID0gMiwgY2V4ID0gNSkKCiMgd2VpZ2h0czogKGV4YW1wbGUgbm90IHBhcnRpY3VsYXJseSBzZW5zaWJsZSkKaTIgPC0gaXJpcwpsZXZlbHMoaTIkU3BlY2llcylbM10gPC0gInZlcnNpY29sb3IiCnN1bW1hcnkoaTIkU3BlY2llcykKd3RzIDwtIDEwMCAvIHRhYmxlKGkyJFNwZWNpZXMpCnd0cwptIDwtIHN2bShTcGVjaWVzIH4gLiwgZGF0YSA9IGkyLCBjbGFzcy53ZWlnaHRzID0gd3RzKQpgYGAKCgojIyMgU1ZNIChlamVtcGxvIHBlcnJvcyB5IGdhdG9zKQpMaW5rOiA8aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1JbWhqS3ljODh4MD4KYGBge3J9CmxpYnJhcnkoZTEwNzEpCmRhdGEoY2F0cywgcGFja2FnZSA9ICdNQVNTJykgIyBGZWF0dXJlczogc2V4byAoc2V4KSwgcGVzbyAoYnd0KSwgcGVzb19jb3Jhem9uIChod3QpCgojRGF0YXNldHM6IHRyYWluIHkgdGVzdAppbmQgPC0gc2FtcGxlKHg9Miwgc2l6ZSA9IG5yb3coY2F0cyksIHJlcGxhY2UgPSBUUlVFLCBwcm9iID0gYygwLjcsMC4zKSkgI3ZlY3RvciAxOjE0NCBkZSDDrW5kaWNlcy4gU3VzIHZhbG9yZXMgZXN0w6FuIGVuIGVsIHJhbmdvIGVudHJlIDE6eDsgZW4gZXN0ZSBjYXNvLCBsb3MgdW5vcyBhcGFyZWNlcsOhbiB1biA3MCUgeSBsb3MgZG9zZXMgdW4gMzAlLgp0ZXN0c2V0IDwtIGNhdHNbaW5kID09IDEsXSAjQ29nZSBsYXMgZmlsYXMgZGUgQ0FUUyBlbiBhcXVlbGxvcyBpbmRpY2VzIHF1ZSAnaW5kJyB0b21hIHZhbG9yIDEKdHJhaW5zZXQgPC0gY2F0c1tpbmQgPT0gMixdICNDb2dlIGxhcyBmaWxhcyBkZSBDQVRTIGVuIGFxdWVsbG9zIGluZGljZXMgcXVlICdpbmQnIHRvbWEgdmFsb3IgMgoKI0VudHJlbmFtb3MgZWwgbW9kZWxvCm1vZGVsb19zdm0gPC0gc3ZtKFNleH4uLCBkYXRhPXRyYWluc2V0LCBrZXJuZWwgPSAicmFkaWFsIikKcHJlZGljY2lvbiA8LSBwcmVkaWN0KG1vZGVsb19zdm0sIG5ld2RhdGEgPSB0ZXN0c2V0Wy0xXSkKCiNSZXN1bHRhZG8geSBwb3JjZW50YWplIGRlIGFjaWVydG8KcGxvdChtb2RlbG9fc3ZtLCBjYXRzKQpNQyA8LSB0YWJsZSh0ZXN0c2V0WywxXSwgcHJlZGljY2lvbikgI21hdHJpeiBkZSBjb25mdXNpw7NuCk1DCmFjaWVydG8gPC0gKCBzdW0oZGlhZyhNQykpICkvIChzdW0oTUMpKQphY2llcnRvCgpgYGAKCgojIyNPdHJvIGVqZW1wbG8gY29tcGxldG8KTGluazogPGh0dHBzOi8vbWFjaGluZWxlYXJuaW5nbWFzdGVyeS5jb20vaG93LXRvLWVzdGltYXRlLW1vZGVsLWFjY3VyYWN5LWluLXItdXNpbmctdGhlLWNhcmV0LXBhY2thZ2UvPgoKIyMjI0RhdGEgc3BsaXQ6CmBgYHtyfQojIGxvYWQgdGhlIGxpYnJhcmllcwpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KGtsYVIpCiMgbG9hZCB0aGUgaXJpcyBkYXRhc2V0CmRhdGEoaXJpcykKIyBkZWZpbmUgYW4gODAlLzIwJSB0cmFpbi90ZXN0IHNwbGl0IG9mIHRoZSBkYXRhc2V0CnNwbGl0PTAuODAKdHJhaW5JbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGlyaXMkU3BlY2llcywgcD1zcGxpdCwgbGlzdD1GQUxTRSkKZGF0YV90cmFpbiA8LSBpcmlzWyB0cmFpbkluZGV4LF0KZGF0YV90ZXN0IDwtIGlyaXNbLXRyYWluSW5kZXgsXQojIHRyYWluIGEgbmFpdmUgYmF5ZXMgbW9kZWwKbW9kZWwgPC0gTmFpdmVCYXllcyhTcGVjaWVzfi4sIGRhdGE9ZGF0YV90cmFpbikKIyBtYWtlIHByZWRpY3Rpb25zCnhfdGVzdCA8LSBkYXRhX3Rlc3RbLDE6NF0KeV90ZXN0IDwtIGRhdGFfdGVzdFssNV0KcHJlZGljdGlvbnMgPC0gcHJlZGljdChtb2RlbCwgeF90ZXN0KQojIHN1bW1hcml6ZSByZXN1bHRzCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9ucyRjbGFzcywgeV90ZXN0KQpgYGAKIyMjI0Jvb3RzdHJhcDoKYGBge3J9CiMgbG9hZCB0aGUgbGlicmFyeQpsaWJyYXJ5KGNhcmV0KQojIGxvYWQgdGhlIGlyaXMgZGF0YXNldApkYXRhKGlyaXMpCiMgZGVmaW5lIHRyYWluaW5nIGNvbnRyb2wKdHJhaW5fY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kPSJib290IiwgbnVtYmVyPTEwMCkKIyB0cmFpbiB0aGUgbW9kZWwKbW9kZWwgPC0gdHJhaW4oU3BlY2llc34uLCBkYXRhPWlyaXMsIHRyQ29udHJvbD10cmFpbl9jb250cm9sLCBtZXRob2Q9Im5iIikKIyBzdW1tYXJpemUgcmVzdWx0cwpwcmludChtb2RlbCkKYGBgCiMjIyNrLWZvbGQgQ3Jvc3MgVmFsaWRhdGlvbjoKYGBge3J9CiMgbG9hZCB0aGUgbGlicmFyeQpsaWJyYXJ5KGNhcmV0KQojIGxvYWQgdGhlIGlyaXMgZGF0YXNldApkYXRhKGlyaXMpCiMgZGVmaW5lIHRyYWluaW5nIGNvbnRyb2wKdHJhaW5fY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kPSJjdiIsIG51bWJlcj0xMCkKIyBmaXggdGhlIHBhcmFtZXRlcnMgb2YgdGhlIGFsZ29yaXRobQpncmlkIDwtIGV4cGFuZC5ncmlkKC5mTD1jKDApLCAudXNla2VybmVsPWMoRkFMU0UpKQojIHRyYWluIHRoZSBtb2RlbAptb2RlbCA8LSB0cmFpbihTcGVjaWVzfi4sIGRhdGE9aXJpcywgdHJDb250cm9sPXRyYWluX2NvbnRyb2wsIG1ldGhvZD0ibmIiKSMsIHR1bmVHcmlkPWdyaWQKIyBzdW1tYXJpemUgcmVzdWx0cwpwcmludChtb2RlbCkKYGBgCiMjIyNSZXBlYXRlZCBrLWZvbGQgQ3Jvc3MgVmFsaWRhdGlvbjoKYGBge3J9CiMgbG9hZCB0aGUgbGlicmFyeQpsaWJyYXJ5KGNhcmV0KQojIGxvYWQgdGhlIGlyaXMgZGF0YXNldApkYXRhKGlyaXMpCiMgZGVmaW5lIHRyYWluaW5nIGNvbnRyb2wKdHJhaW5fY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kPSJyZXBlYXRlZGN2IiwgbnVtYmVyPTEwLCByZXBlYXRzPTMpCiMgdHJhaW4gdGhlIG1vZGVsCm1vZGVsIDwtIHRyYWluKFNwZWNpZXN+LiwgZGF0YT1pcmlzLCB0ckNvbnRyb2w9dHJhaW5fY29udHJvbCwgbWV0aG9kPSJuYiIpCiMgc3VtbWFyaXplIHJlc3VsdHMKcHJpbnQobW9kZWwpCmBgYAojIyMjTGVhdmUgT25lIE91dCBDcm9zcyBWYWxpZGF0aW9uOgpgYGB7cn0KIyBsb2FkIHRoZSBsaWJyYXJ5CmxpYnJhcnkoY2FyZXQpCiMgbG9hZCB0aGUgaXJpcyBkYXRhc2V0CmRhdGEoaXJpcykKIyBkZWZpbmUgdHJhaW5pbmcgY29udHJvbAp0cmFpbl9jb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2Q9IkxPT0NWIikKIyB0cmFpbiB0aGUgbW9kZWwKbW9kZWwgPC0gdHJhaW4oU3BlY2llc34uLCBkYXRhPWlyaXMsIHRyQ29udHJvbD10cmFpbl9jb250cm9sLCBtZXRob2Q9Im5iIikKIyBzdW1tYXJpemUgcmVzdWx0cwpwcmludChtb2RlbCkKYGBgCgojIyNPdHJvIGVqZW1wbG8gU1ZNIHZzIFJQQVJUOgpgYGB7cn0KbGlicmFyeShlMTA3MSkKbGlicmFyeShycGFydCkKCiNEQVRPUzoKZGF0YShPem9uZSwgcGFja2FnZT0ibWxiZW5jaCIpCgojIyBTcGxpdCBlbiBEYXRhc2V0cyAodHJhaW4geSB0ZXN0KQppbmRleCAgICAgPC0gMTpucm93KE96b25lKQp0ZXJjaW8gPC0gdHJ1bmMobGVuZ3RoKGluZGV4KS8zKSAjdXNhbW9zIHVuIDMwJSBwYXJhIHRlc3QKdGVzdGluZGV4IDwtIHNhbXBsZShpbmRleCwgdGVyY2lvLCByZXBsYWNlID0gRkFMU0UsIHByb2IgPSBOVUxMKQp0ZXN0c2V0ICAgPC0gbmEub21pdChPem9uZVt0ZXN0aW5kZXgsXSkgI1RFU1QgUXVpdGEgbGFzIGZpbGFzIHF1ZSB0ZW5nYW4gQUxHVU4gdmFsb3IgTkEKdHJhaW5zZXQgIDwtIG5hLm9taXQoT3pvbmVbLXRlc3RpbmRleCxdKSNUUkFJTiBRdWl0YSBsYXMgZmlsYXMgcXVlIHRlbmdhbiBBTEdVTiB2YWxvciBOQQoKI0NvbHVtbmEgVEFSR0VUCmluZGljZV90YXJnZXQgPC0gd2hpY2goIGNvbG5hbWVzKHRyYWluc2V0KSA9PSAiVjQiICkKCiMjIHN2bSAoTUFRVUlOQVMgVkVDVE9SIFNPUE9SVEUpIHBhcmEgUkVHUkVTScOTTiBOTyBMSU5FQUwKc3ZtLm1vZGVsIDwtIHN2bShmb3JtdWxhID0gVjQgfiAuLCBkYXRhID0gdHJhaW5zZXQsIGNvc3QgPSAxMDAwLCBnYW1tYSA9IDAuMDAwMSkKc3ZtLnByZWQgIDwtIHByZWRpY3Qoc3ZtLm1vZGVsLCB0ZXN0c2V0WywtaW5kaWNlX3RhcmdldF0pCnN2bV9lcnJvciA8LSBjcm9zc3Byb2Qoc3ZtLnByZWQgLSB0ZXN0c2V0WyxpbmRpY2VfdGFyZ2V0XSkgLyBsZW5ndGgodGVzdGluZGV4KQpzdm1fZXJyb3IKCiMjIHJwYXJ0IChBUkJPTEVTIERFIFJFR1JFU0lPTikgcGFyYSBSRUdSRVNJw5NOIE5PIExJTkVBTApycGFydC5tb2RlbCA8LSBycGFydChmb3JtdWxhID0gVjQgfiAuLCBkYXRhID0gdHJhaW5zZXQpCnJwYXJ0LnByZWQgIDwtIHByZWRpY3QocnBhcnQubW9kZWwsIHRlc3RzZXRbLC1pbmRpY2VfdGFyZ2V0XSkKcnBhcnRfZXJyb3IgPC0gY3Jvc3Nwcm9kKHJwYXJ0LnByZWQgLSB0ZXN0c2V0WyxpbmRpY2VfdGFyZ2V0XSkgLyBsZW5ndGgodGVzdGluZGV4KQpycGFydF9lcnJvcgpgYGAKCiMjI0VOU0VNQkxJTkcKTGluazogPGh0dHBzOi8vd3d3LmRhdGFjYW1wLmNvbS9jb21tdW5pdHkvdHV0b3JpYWxzL2Vuc2VtYmxlLXItbWFjaGluZS1sZWFybmluZz4KCmBgYHtyfQpsaWJyYXJ5KCJTdXBlckxlYXJuZXIiKQoKbGlicmFyeShNQVNTKSAjRGF0YQojIFRyYWluIGFuZCB0ZXN0IHNldHMKdHJhaW4gPC0gUGltYS50cgp0ZXN0IDwtIFBpbWEudGUKCmhlYWQodHJhaW4pCnN1bW1hcnkodHJhaW4pCmhlbHAoUGltYS50cikKYGBgCkVsIGNhbXBvIHRhcmdldCBlcyAqdHlwZSogKHF1ZSBlcyB1biBmYWN0b3IpLCBxdWUgaW5kaWNhIHNpIGVsIHBhY2llbnRlIHRpZW5lIGRpYWJldGVzIG8gbm8uCmBgYHtyfQojU2luY2UgdGhlIHR5cGUgY29sdW1uIHdhcyBhIGZhY3RvciwgUiB3aWxsIGVuY29kZSBpdCB0byAxIGFuZCAyLCBidXQgdGhpcyBpcyBub3Qgd2hhdCB5b3Ugd2FudDogaWRlYWxseSwgeW91IHdvdWxkIGxpa2UgdG8gd29yayB3aXRoIHRoZSB0eXBlIGVuY29kZWQgYXMgMCBhbmQgMSwgd2hpY2ggYXJlICJObyIgYW5kICJZZXMiLCByZXNwZWN0aXZlbHkuIEluIHRoZSBhYm92ZSBjb2RlIGNodW5rLCB5b3Ugc3VidHJhY3QgMSBmcm9tIHRoZSB3aG9sZSBzZXQgdG8gZ2V0IHlvdXIgMC0xIGVuY29kaW5nLiBSIHdpbGwgYWxzbyBlbmNvZGUgdGhpcyBpbiB0aGUgZmFjdG9yIG9yZGVyLgp5IDwtIGFzLm51bWVyaWModHJhaW5bLDhdKSAtIDEKeXRlc3QgPC0gYXMubnVtZXJpYyh0ZXN0Wyw4XSkgLSAxCgojUHJlZGljdG9yZXMgeSByZXNwdWVzdGFzCnggPC0gZGF0YS5mcmFtZSh0cmFpblssMTo3XSkKeHRlc3QgPC0gZGF0YS5mcmFtZSh0ZXN0WywxOjddKQoKI01PREVMT1MgRElTUE9OSUJMRVM6Cmxpc3RXcmFwcGVycygpCgojTW9kZWxvcywgdXNhbmRvIGNyb3NzLXZhbGlkYXRpb24gKGNvbiA1IGludGVudG9zKToKc2V0LnNlZWQoMTUwKQptb2RlbCA8LSBTdXBlckxlYXJuZXIoeSwgCiAgICAgICAgICAgICAgICAgICAgICB4LAogICAgICAgICAgICAgICAgICAgICAgU0wubGlicmFyeT1saXN0KCJTTC5yYW5nZXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJTTC5rc3ZtIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU0wuaXByZWRiYWdnIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU0wuYmF5ZXNnbG0iKSkKIyBSZXR1cm4gdGhlIG1vZGVsCnN1bW1hcnkobW9kZWwpCgpgYGAKClByZWRpY2Npb25lcyBjb24gU3VwZXJMZWFybmVyOgpgYGB7cn0KcHJlZGljdGlvbnMgPC0gcHJlZGljdC5TdXBlckxlYXJuZXIobW9kZWwsIG5ld2RhdGEgPSB4dGVzdCkKCiN0aGUgb3ZlcmFsbCBlbnNlbWJsZSBwcmVkaWN0aW9ucwpoZWFkKHByZWRpY3Rpb25zJHByZWQpCgojdGhlIGluZGl2aWR1YWwgbGlicmFyeSBwcmVkaWN0aW9uczoKaGVhZChwcmVkaWN0aW9ucyRsaWJyYXJ5LnByZWRpY3QpCgoKI0VuIGVsIGNhc28gZGUgY2xhc2lmaWNhY2nDs24gYmlub21pYWwsIGRlYmVtb3MgZmlqYXIgdW4gdW1icmFsIChwb3IgZWplbXBsbyAwLjUpIHkgdmVyIGxvcyBxdWUgY2FlbiBlbiBjYWRhIGxhZG8KbGlicmFyeShkcGx5cikKY29udi5wcmVkcyA8LSBpZmVsc2UocHJlZGljdGlvbnMkcHJlZCA+PSAwLjUsMSwwKQpsaWJyYXJ5KGNhcmV0KQojY20gPC0gY29uZnVzaW9uTWF0cml4KGRhdGEgPWNvbnYucHJlZHMsIHJlZmVyZW5jZSA9IHl0ZXN0KQojY20gIyBTZSBvYnRpZW5lICAwLjc5MjE2ODcgYWNjdXJhY3kKYGBgCgoKCgoK