1. Preguntas

i) ¿Qué es Supervised Machine Learning y cuáles son algunas de sus aplicaciones en Inteligencia de Negocios?

El aprendizaje automático supervisado (SML) es un proceso en el que se entrena un modelo predictivo utilizando datos etiquetados como entradas y buscando predecir o clasificar una salida, una vez comprendido esto podemos utilizarlo en el mundo de los negocios mediante predicciones como lo pueden ser de ventas, análisis de riestos, etc. o en el caso de segmentación con el uso de técnicas de clustering para identificar grupos de clientes con características similares.

iii) ¿Qué es la R2 Ajustada? ¿Qué es la métrica RMSE? ¿Cuál es la diferencia entre la R2 Ajustada y la métrica RMSE?

La R2 (R cuadrada) es una medida que nos dice qué tan bien se ajusta un modelo a los datos, es decir que tanto porcentaje de ellos puede explicar, cuanto más alto sea el valor de R2, mejor se ajusta el modelo a los datos y más porcentaje de la variabilidad de la variable dependiente puede explicar La métrica RMSE es la medida que explica la magnitud promedio de los errores entre los valores predichos y los valores reales, es decir que entre menor sea el valor del RMSE, mejor será la capacidad predictiva del modelo, ya que indica que las predicciones del modelo están más cerca de los valores reales La principal diferencia entre estas 2 medidas es que la R2 evalúa la calidad general del modelo, mientras que el RMSE evalúa la precisión de las predicciones.

2. Análisis exploratorio (EDA)

2.1 Instalar paquetes y llamar librerías

library(dplyr)
library(ggplot2)
library(corrplot)
library(car)
library(lmtest)
library(lattice)
library(xgboost)
library(rpart)
library(randomForest)
library(neuralnet)

2.2 Importación base de datos

df<- read.csv("C:\\Users\\LuisD\\Documents\\Concentración\\MODULO 3\\health_insurance.csv")
dfrf<- read.csv("C:\\Users\\LuisD\\Documents\\Concentración\\MODULO 3\\health_insurance.csv")

#file.choose()

2.3 Primer acercamiento

summary(df)
##       age            sex                 bmi           children    
##  Min.   :18.00   Length:1338        Min.   :16.00   Min.   :0.000  
##  1st Qu.:27.00   Class :character   1st Qu.:26.30   1st Qu.:0.000  
##  Median :39.00   Mode  :character   Median :30.40   Median :1.000  
##  Mean   :39.21                      Mean   :30.67   Mean   :1.095  
##  3rd Qu.:51.00                      3rd Qu.:34.70   3rd Qu.:2.000  
##  Max.   :64.00                      Max.   :53.10   Max.   :5.000  
##     smoker             region             expenses    
##  Length:1338        Length:1338        Min.   : 1122  
##  Class :character   Class :character   1st Qu.: 4740  
##  Mode  :character   Mode  :character   Median : 9382  
##                                        Mean   :13270  
##                                        3rd Qu.:16640  
##                                        Max.   :63770
str(df)
## 'data.frame':    1338 obs. of  7 variables:
##  $ age     : int  19 18 28 33 32 31 46 37 37 60 ...
##  $ sex     : chr  "female" "male" "male" "male" ...
##  $ bmi     : num  27.9 33.8 33 22.7 28.9 25.7 33.4 27.7 29.8 25.8 ...
##  $ children: int  0 1 3 0 0 0 1 3 2 0 ...
##  $ smoker  : chr  "yes" "no" "no" "no" ...
##  $ region  : chr  "southwest" "southeast" "southeast" "northwest" ...
##  $ expenses: num  16885 1726 4449 21984 3867 ...
# df

2.4 Cambiar tipo de variables

Es necesario categorizar algunas variables para que pueda realizarse el primer método de SML (OLS)

df$sex <- ifelse(df$sex == "male", 1, 0)
df$smoker <- ifelse(df$smoker == "yes", 1, 0)
df$region <- as.numeric(factor(df$region, levels = c("southwest","southeast","northeast", "northwest"), labels = c(1,2,3,4)))
str(df)
## 'data.frame':    1338 obs. of  7 variables:
##  $ age     : int  19 18 28 33 32 31 46 37 37 60 ...
##  $ sex     : num  0 1 1 1 1 0 0 0 1 0 ...
##  $ bmi     : num  27.9 33.8 33 22.7 28.9 25.7 33.4 27.7 29.8 25.8 ...
##  $ children: int  0 1 3 0 0 0 1 3 2 0 ...
##  $ smoker  : num  1 0 0 0 0 0 0 0 0 0 ...
##  $ region  : num  1 2 2 4 4 2 2 4 3 4 ...
##  $ expenses: num  16885 1726 4449 21984 3867 ...

2.5 NA’s (Identificación y reemplazo)

na_count <- colSums(is.na(df))
na_count
##      age      sex      bmi children   smoker   region expenses 
##        0        0        0        0        0        0        0
#No contamos con NA's por lo que no es necesario remplazar ningun registro con mediana.

2.6 Medidas descriptivas

columnas_numericas <- sapply(df, is.numeric)

media <- colMeans(df[, columnas_numericas], na.rm = TRUE)
cat("Media:", media, "\n")
## Media: 39.20703 0.5052317 30.66547 1.094918 0.2047833 2.485052 13270.42
mediana <- sapply(df[, columnas_numericas], median, na.rm = TRUE) 
cat("Mediana:", mediana, "\n")
## Mediana: 39 1 30.4 1 0 2 9382.03
moda <- apply(df, 2, function(x) {
  unique_x <- unique(x[!is.na(x)])
  unique_x[which.max(tabulate(match(x, unique_x)))]})  
cat("Moda:", media, "\n")
## Moda: 39.20703 0.5052317 30.66547 1.094918 0.2047833 2.485052 13270.42

2.7 Medidas de dispersión

desviacion_estandar <- sapply(df, sd)
desviacion_estandar <- format(desviacion_estandar, scientific = FALSE)
desviacion_estandar
##             age             sex             bmi        children          smoker 
## "   14.0499604" "    0.5001596" "    6.0983822" "    1.2054927" "    0.4036940" 
##          region        expenses 
## "    1.1055720" "12110.0112397"
varianza <- sapply(df, var)
varianza <- format(varianza, scientific = FALSE)
varianza
##                 age                 sex                 bmi            children 
## "      197.4013867" "        0.2501596" "       37.1902653" "        1.4532127" 
##              smoker              region            expenses 
## "        0.1629689" "        1.2222895" "146652372.2258170"
range <- sapply(df, range)
range <- format(range, scientific = FALSE)
range
##      age        sex        bmi        children   smoker     region    
## [1,] "   18.00" "    0.00" "   16.00" "    0.00" "    0.00" "    1.00"
## [2,] "   64.00" "    1.00" "   53.10" "    5.00" "    1.00" "    4.00"
##      expenses  
## [1,] " 1121.87"
## [2,] "63770.43"
cuartiles <- apply(df, 2, quantile, probs = c(0, 0.25, 0.5, 0.75, 1))
rownames(cuartiles) <- c("0%", "25%", "50%", "75%", "100%")
colnames(cuartiles) <- colnames(df)
cuartiles
##      age sex  bmi children smoker region  expenses
## 0%    18   0 16.0        0      0      1  1121.870
## 25%   27   0 26.3        0      0      2  4740.288
## 50%   39   1 30.4        1      0      2  9382.030
## 75%   51   1 34.7        2      0      3 16639.915
## 100%  64   1 53.1        5      1      4 63770.430

2.8 Graficos para identificar patrones

# Distribución de géneros en gráfico de barras:
#barplot(table(df$sex), main="Distribución de género", xlab="Sexo", ylab="Frecuencia",ylim=c(0, 800))

# Distribución de fumadores en gráfico de barras:
barplot(table(df$smoker), main="Distribución de fumadores", xlab="Fumador o no fumador", ylab="Frecuencia",ylim=c(0, 1300))

# Histograma de la variable edad
hist(df$age, main="Histograma de Edad", xlab="Edad", ylab="Frecuencia")

# Histograma de la variable bmi
hist(df$bmi, main="Histograma de BMI", xlab="BMI", ylab="Frecuencia")

# Matriz de correlación para variables numéricas
corr_matrix <- cor(df[, c("age", "bmi", "children", "expenses")])
corrplot(corr_matrix, method="color")

# Box plot para 'expenses' vs. 'smoker'
boxplot(expenses ~ smoker, data=df, main="Box plot de Gastos por Fumador", xlab="Fumador", ylab="Gastos")

# Box plot para 'expenses' vs. 'region'
boxplot(expenses ~ region, data=df, main="Box plot de Gastos por Región", xlab="Región", ylab="Gastos")

#plot_normality(df, age, bmi, children)

3. Interpretación EDA

Tomando en cuenta los gráficos elaborados durante la anterior etapa podemos llegar a las siguientes conclusiones:
* Con respecto a las primeras 2 gráficas relacionadas a la distribución y cantidad de observaciones en las variables sex y smoker podemos ver que existe una caitdad similar de registros de hombres y mujeres, sin embargo dentro de la muestra contamos con un numero significante mayor de personas fumadoras a no fumadoras.
* De la misma forma podemos observar mediante el histograma de age que contamos con una gran variedad de registros de todas las edades, pues la diferencia entre las frecuencias del gráfico no es tan grande.
* En cuanto a la matriz de correlación entre cada variable, podemos observar una correlación mediana entre las viariables age y expenses y una correlación ligera entre las variables bmi y expenses, si tomamos en cuenta que expenses es nuestra variable dependiente puede ser que la correlación que presenta con estas dos variables independientes se vea reflejada mas adelante y las veamos como estadísticamente significantes para explicar el cambio en la variable Y.
* Finalmente mediante el uso de boxplots pudimos observar el comportamiento de la variable dependiente expenses cuando la relacionamos con la variable smoker, en este primer caso podemos observar una mediana bastante mayor dentro de las observaciones smoker-yes y también una mayor dispersión de observaciones, por el contrario del comportamiento presentado entre expenses y region pues no se observa una diferencia significativa entre las medias de las regiones, únicamente podemos observar un ligero aumento en cuanto a la variabilidad de las expenses relacionada a las regiones. * Tendremos que transformar la variable bmi para que no haya problemas de heterocedasticidad.

4. Modelos SML

4.0 Partición de datos y semilla

library(caret)
set.seed(123)
datos_partidos <- createDataPartition(y = df$expenses, p=0.8, list=F)
train = df[datos_partidos, ]
test <- df[-datos_partidos, ]

4.1 OLS Regresión

4.1.1 Creación del modelo

# Creación del modelo
ols_model <- lm(log(expenses)  ~ age + sex + log(bmi) + children + smoker + region, data = train)
summary(ols_model)
## 
## Call:
## lm(formula = log(expenses) ~ age + sex + log(bmi) + children + 
##     smoker + region, data = train)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -1.05636 -0.19007 -0.04657  0.07205  2.11194 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  5.9917571  0.2404797  24.916  < 2e-16 ***
## age          0.0339603  0.0009826  34.562  < 2e-16 ***
## sex         -0.0917478  0.0273668  -3.353 0.000829 ***
## log(bmi)     0.3891503  0.0690145   5.639 2.19e-08 ***
## children     0.0935763  0.0114271   8.189 7.48e-16 ***
## smoker       1.5683769  0.0337222  46.509  < 2e-16 ***
## region       0.0292215  0.0126078   2.318 0.020653 *  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 0.446 on 1065 degrees of freedom
## Multiple R-squared:  0.7669, Adjusted R-squared:  0.7656 
## F-statistic: 583.9 on 6 and 1065 DF,  p-value: < 2.2e-16

4.1.2 Prueba de Multicolinealidad

vif(ols_model)
##      age      sex log(bmi) children   smoker   region 
## 1.020795 1.009071 1.047174 1.003168 1.006477 1.027515
# Los valores de VIF son menores a 5 o 10 por lo que NO existe multicolinealidad

Tomando en cuenta los resultados de la prueba VIF podemos afirmar que NO EXISTE MULTICOLINEALIDAD

4.1.3 Prueba Heterocedasticidad

4.1.3.1 Visualización de residuales

# Como primer contacto hacemos un gráfico para ver los residuales del modelo
res<- residuals(ols_model)
plot(res, type="l")

El gráfico anterior parece ser prueba acerca de la existencia de Heterocedasticidad por lo que procederemos a realizar el BP-Test para confirmarlo

4.1.3.2 BP-Test

bptest(ols_model)
## 
##  studentized Breusch-Pagan test
## 
## data:  ols_model
## BP = 72.185, df = 6, p-value = 1.456e-13

Tomando en cuenta el resultado del Breusch-Pagan Test, observamos que este indica fuertemente la presencia de heterocedasticidad en el modelo OLS, por lo que es necesario realizar una estimación de mínimos cuadrados ponderados (WLS)…

4.1.3.3 WLS

wt <- 1 / lm(abs(ols_model$residuals) ~ ols_model$fitted.values)$fitted.values^2
wls_model<-lm(log(expenses) ~  age + sex + log(bmi) + children + smoker + region , data = train, weights=wt)
summary(wls_model)
## 
## Call:
## lm(formula = log(expenses) ~ age + sex + log(bmi) + children + 
##     smoker + region, data = train, weights = wt)
## 
## Weighted Residuals:
##     Min      1Q  Median      3Q     Max 
## -4.0223 -0.7588 -0.1465  0.3821  6.3784 
## 
## Coefficients:
##               Estimate Std. Error t value Pr(>|t|)    
## (Intercept)  5.7753850  0.2345984  24.618  < 2e-16 ***
## age          0.0308326  0.0009478  32.530  < 2e-16 ***
## sex         -0.0721799  0.0264208  -2.732   0.0064 ** 
## log(bmi)     0.4990262  0.0671977   7.426 2.28e-13 ***
## children     0.0803374  0.0108946   7.374 3.32e-13 ***
## smoker       1.5032775  0.0291502  51.570  < 2e-16 ***
## region       0.0232378  0.0121946   1.906   0.0570 .  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 1.57 on 1065 degrees of freedom
## Multiple R-squared:  0.7853, Adjusted R-squared:  0.7841 
## F-statistic: 649.2 on 6 and 1065 DF,  p-value: < 2.2e-16
str(train)
## 'data.frame':    1072 obs. of  7 variables:
##  $ age     : int  19 18 28 33 31 46 37 60 25 62 ...
##  $ sex     : num  0 1 1 1 0 0 0 0 1 0 ...
##  $ bmi     : num  27.9 33.8 33 22.7 25.7 33.4 27.7 25.8 26.2 26.3 ...
##  $ children: int  0 1 3 0 0 1 3 0 0 0 ...
##  $ smoker  : num  1 0 0 0 0 0 0 0 0 1 ...
##  $ region  : num  1 2 2 4 2 2 4 4 3 2 ...
##  $ expenses: num  16885 1726 4449 21984 3757 ...
bptest(wls_model)
## 
##  studentized Breusch-Pagan test
## 
## data:  wls_model
## BP = 13971, df = 6, p-value < 2.2e-16
#  Podemos observar que se ha logrado eliminar la heterocedasticidad aumentando el p-value a 1

vif(wls_model)
##      age      sex log(bmi) children   smoker   region 
## 1.017365 1.012836 1.048003 1.001651 1.008322 1.027750
# Podemos observar también que a pesar de que los coeficientes de VIF aumentaron, NO tenemos multicolinealidad

4.1.4 Normalidad de residuales

# Residuales del WLS
res_wls<- residuals(wls_model)
plot(res_wls, type="l")

# Gráfico Q-Q de residuales
qqnorm(res_wls)
qqline(res_wls)

# Create residual vs. fitted plot

plot(fitted(wls_model), resid(wls_model), main="Linear Regression Residual vs. Fitted Values", xlab="Fitted Values", ylab="Residuals")
abline(0,0)

hist(wls_model$residuals, xlab="Estimated Regression Residuals", main='Distribution of OLS Estimated Regression Residuals', col='lightblue', border="white" )

# Shaphiro Test
shaphiro <- shapiro.test(res_wls)
shaphiro
## 
##  Shapiro-Wilk normality test
## 
## data:  res_wls
## W = 0.88152, p-value < 2.2e-16

A pesar de que aparentemente no contamos con normalidad de residuales, debemos recordar que estamos evaluando un modelo de WLS por lo que podemos continuar a la comparación de RMSE y R2 de los modelos OLS y WLS…

4.1.5 Comparación de desempeño RMSE

# Predicciones del modelo OLS
predicciones_ols <- predict(ols_model, newdata = test)
# Convertir las predicciones de logaritmo a su escala original
predicciones_ols <- exp(predicciones_ols)

# Predicciones del modelo WLS
predicciones_wls <- predict(wls_model, newdata = test)
# Convertir las predicciones de logaritmo a su escala original
predicciones_wls <- exp(predicciones_wls)

# Calcular el RMSE para cada modelo
rmse_ols <- sqrt(mean((predicciones_ols - test$expenses)^2))
rmse_wls <- sqrt(mean((predicciones_wls - test$expenses)^2))

# Imprimir los RMSE
cat("RMSE del modelo OLS:", rmse_ols, "\n")
## RMSE del modelo OLS: 8217.117
cat("RMSE del modelo WLS:", rmse_wls, "\n")
## RMSE del modelo WLS: 7302.724

4.2 XGBoost Regresion

reg_xgb <- xgboost(data = xgb_train, max_depth = 3, nrounds = 59, verbose = 0)
prediction_xgb_test <- predict(reg_xgb, xgb_test)
rmse_XGB <- sqrt(mean((exp(prediction_xgb_test) - exp(test_y))^2))
cat("RMSE del modelo XGBoost:", rmse_ols, "\n")
## RMSE del modelo XGBoost: 8217.117

4.3 Decision Tree Regresion

dt_regresion <- rpart(log(expenses) ~ age + sex + bmi + children + smoker + region, data = train)
plot(dt_regresion, compress = TRUE)
text(dt_regresion, use.n = TRUE)

library(rpart.plot)
rpart.plot(dt_regresion)

decision_tree_prediction <- predict(dt_regresion,test)
rmse_dt <- sqrt(mean((exp(decision_tree_prediction) - df$expenses)^2))
cat("RMSE de Decision Trees:", rmse_dt, "\n")
## RMSE de Decision Trees: 16132.53

4.4 Random Forest Regresion

# Crear el modelo de Random Forest
library(randomForest)
dfrf$sex<- factor(dfrf$sex, levels=c("male", "female"))
dfrf$smoker<- factor(dfrf$smoker, levels=c("yes", "no"))
dfrf$region <- factor(dfrf$region, levels = c("southwest", "southeast", "northeast", "northwest"))

library(caret)
set.seed(123)
datos_partidosrf <- createDataPartition(y = dfrf$expenses, p=0.8, list=F)
trainrf = dfrf[datos_partidos, ]
testrf <- dfrf[-datos_partidos, ]

random_forest <- randomForest(log(expenses) ~ age + sex + bmi + children + smoker + region, data = trainrf, na.action = na.exclude)
random_forest
## 
## Call:
##  randomForest(formula = log(expenses) ~ age + sex + bmi + children +      smoker + region, data = trainrf, na.action = na.exclude) 
##                Type of random forest: regression
##                      Number of trees: 500
## No. of variables tried at each split: 2
## 
##           Mean of squared residuals: 0.1504463
##                     % Var explained: 82.25
predictions_rf <- predict(random_forest, testrf)
rmse_rf <- sqrt(mean((exp(predictions_rf) - df$expenses)^2))
cat("RMSE Random Forest:", rmse_rf, "\n")
## RMSE Random Forest: 15421.87

4.5 Red Neuronal

library(neuralnet)
nn_model <- neuralnet(expenses ~ age + sex + bmi + children + smoker + region, data = train, hidden = c(5, 3), linear.output = TRUE)
plot(nn_model)
predictions_nnet <- predict(nn_model, test)
rmse_nn <- sqrt(mean((exp(predictions_nnet) - test$expenses)^2))
cat("RMSE de Redes Neuronales:", rmse_nn, "\n")
## RMSE de Redes Neuronales: Inf

6. Evaluación y selección de modelos

rmsesy<- c(rmse_ols,rmse_wls,rmse_XGB,rmse_dt,rmse_rf)

rmsesx<- c("OLS", "WLS", "XGBoost", "DT", "RF")

df_RMSES<- data.frame(rmsesx=rmsesx,rmsesy=rmsesy)


barplot(df_RMSES$rmsesy, names= df_RMSES$rmsesx, col = "skyblue", 
        main = "Valores de RMSE para diferentes modelos", 
        xlab = "RMSE", ylab = "Valor de RMSE")

Tomando en cuenta los resultados presentados anteriormente, en donde comparamos los RMSE de los diferentes modelos realizados (RMSE de Neural Networks no fue incluido debido a valor sumamente alto). Poemos observar que el modelo con un mejor desempeño comprobado por ser en RMSE con el valor más pequeño es el XGBoost model # 7. Conclusiones ## a. EDA Durante el Análisis Exploratorio de Datos tuve la oportunidad de comenzar el análisis realizando un acercamiento principal mediante str y summary para saber el tipo de variables y un poco acerca de su comportamiento, también incluimos medidas de dispersión y descriptivas, a la hora de buscar NA’s no logramos identificar la presencia de alguno, finalmente concluimos el EDA mediante la visualización de histogramas para poder visualizar las frecuencias de las variables numéricas, también usamos boxplots para poder observar un poco del comprotamiento de la variable dependiente y su relación con las variables categóricas, fnalmente podemos visualizar un heatmap en el que se muestran las relaciones entre variables.

b. Modelo seleccionado

Como conclusión y basándonos en la comparación de RMSE’s de los distintos modelos, podemos llegar a la conclusión de que el modelo XGBoost es aquel que muestra un mejor desempeño, en caso de tener alguna duda en cuanto a la comparación, se sugiere implementar algunas otras técnicas como AIC. ### i. ¿Cuáles son las variables que contribuyen a explicar los cambios de la principal variable de estudio?
Las principales variables que contribuyen a la explicación de los cambios son:
* age
* bmi
* smoker yes-no

ii. ¿Cómo es el impacto de dichas variables explicativas sobre la variable dependiente?

El impacto de todas las variables anteriores es positivo, es decir que si el valor de estas aumenta, también lo hará el valor de expenses, en el caso de smoker se justifica debido a que 0=“no” y 1=“yes”

iii. ¿Los resultados estimados del modelo seleccionado son similares a los otros modelos estimados? ¿Cuáles son las diferencias?

Si comparamos los rasultados obtenidos de XGBoost con los resultados de los demás modelos podemos observar que las vaiables significativas o con mayor impacto son las mismas en la mayoría de los modelos, así como su capacidad explicativa de los modelos, sin embargo logró diferenciarse en el RMSE que como su nombre lo dice es la diferencia entre los valores predichos por un modelo y los valores observados.

LS0tDQp0aXRsZTogIkFjdGl2aWRhZCAxIOKAkyBNb2RlbG9zIGRlIFJlZ3Jlc2nDs24iDQphdXRob3I6ICJMdWlzIERhdmlkIFPDoW5jaGV6IENhc3RpbGxvIEEwMTI3NTY1NSINCmRhdGU6ICIyMDI0LTAzLTA0Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICB0aGVtZTogeWV0aQ0KICBwZGZfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCi0tLQ0KDQojIDEuIFByZWd1bnRhcw0KIyMgaSkgwr9RdcOpIGVzIFN1cGVydmlzZWQgTWFjaGluZSBMZWFybmluZyB5IGN1w6FsZXMgc29uIGFsZ3VuYXMgZGUgc3VzIGFwbGljYWNpb25lcyBlbiBJbnRlbGlnZW5jaWEgZGUgTmVnb2Npb3M/IA0KRWwgYXByZW5kaXphamUgYXV0b23DoXRpY28gc3VwZXJ2aXNhZG8gKFNNTCkgZXMgdW4gcHJvY2VzbyBlbiBlbCBxdWUgc2UgZW50cmVuYSB1biBtb2RlbG8gcHJlZGljdGl2byB1dGlsaXphbmRvIGRhdG9zIGV0aXF1ZXRhZG9zIGNvbW8gZW50cmFkYXMgeSBidXNjYW5kbyBwcmVkZWNpciBvIGNsYXNpZmljYXIgdW5hIHNhbGlkYSwgdW5hIHZleiBjb21wcmVuZGlkbyBlc3RvIHBvZGVtb3MgdXRpbGl6YXJsbyBlbiBlbCBtdW5kbyBkZSBsb3MgbmVnb2Npb3MgbWVkaWFudGUgcHJlZGljY2lvbmVzIGNvbW8gbG8gcHVlZGVuIHNlciBkZSB2ZW50YXMsIGFuw6FsaXNpcyBkZSByaWVzdG9zLCBldGMuIG8gZW4gZWwgY2FzbyBkZSBzZWdtZW50YWNpw7NuIGNvbiBlbCB1c28gZGUgdMOpY25pY2FzIGRlIGNsdXN0ZXJpbmcgcGFyYSBpZGVudGlmaWNhciBncnVwb3MgZGUgY2xpZW50ZXMgY29uIGNhcmFjdGVyw61zdGljYXMgc2ltaWxhcmVzLiAgDQoNCiMjIGlpaSkgwr9RdcOpIGVzIGxhIFIyIEFqdXN0YWRhPyDCv1F1w6kgZXMgbGEgbcOpdHJpY2EgUk1TRT8gwr9DdcOhbCBlcyBsYSBkaWZlcmVuY2lhIGVudHJlIGxhIFIyIEFqdXN0YWRhIHkgbGEgbcOpdHJpY2EgUk1TRT8gDQpMYSAqUjIqIChSIGN1YWRyYWRhKSBlcyB1bmEgbWVkaWRhIHF1ZSBub3MgZGljZSBxdcOpIHRhbiBiaWVuIHNlIGFqdXN0YSB1biBtb2RlbG8gYSBsb3MgZGF0b3MsIGVzIGRlY2lyIHF1ZSB0YW50byBwb3JjZW50YWplIGRlIGVsbG9zIHB1ZWRlIGV4cGxpY2FyLCBjdWFudG8gbcOhcyBhbHRvIHNlYSBlbCB2YWxvciBkZSBSMiwgbWVqb3Igc2UgYWp1c3RhIGVsIG1vZGVsbyBhIGxvcyBkYXRvcyB5IG3DoXMgcG9yY2VudGFqZSBkZSBsYSB2YXJpYWJpbGlkYWQgZGUgbGEgdmFyaWFibGUgZGVwZW5kaWVudGUgcHVlZGUgZXhwbGljYXINCkxhIG3DqXRyaWNhICpSTVNFKiBlcyBsYSBtZWRpZGEgcXVlIGV4cGxpY2EgbGEgbWFnbml0dWQgcHJvbWVkaW8gZGUgbG9zIGVycm9yZXMgZW50cmUgbG9zIHZhbG9yZXMgcHJlZGljaG9zIHkgbG9zIHZhbG9yZXMgcmVhbGVzLCBlcyBkZWNpciBxdWUgZW50cmUgbWVub3Igc2VhIGVsIHZhbG9yIGRlbCBSTVNFLCBtZWpvciBzZXLDoSBsYSBjYXBhY2lkYWQgcHJlZGljdGl2YSBkZWwgbW9kZWxvLCB5YSBxdWUgaW5kaWNhIHF1ZSBsYXMgcHJlZGljY2lvbmVzIGRlbCBtb2RlbG8gZXN0w6FuIG3DoXMgY2VyY2EgZGUgbG9zIHZhbG9yZXMgcmVhbGVzDQpMYSBwcmluY2lwYWwgZGlmZXJlbmNpYSBlbnRyZSBlc3RhcyAyIG1lZGlkYXMgZXMgcXVlIGxhIFIyIGV2YWzDumEgbGEgY2FsaWRhZCBnZW5lcmFsIGRlbCBtb2RlbG8sIG1pZW50cmFzIHF1ZSBlbCBSTVNFIGV2YWzDumEgbGEgcHJlY2lzacOzbiBkZSBsYXMgcHJlZGljY2lvbmVzLg0KDQojIDIuIEFuw6FsaXNpcyBleHBsb3JhdG9yaW8gKEVEQSkNCiMjIDIuMSBJbnN0YWxhciBwYXF1ZXRlcyB5IGxsYW1hciBsaWJyZXLDrWFzDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoY29ycnBsb3QpDQpsaWJyYXJ5KGNhcikNCmxpYnJhcnkobG10ZXN0KQ0KbGlicmFyeShsYXR0aWNlKQ0KbGlicmFyeSh4Z2Jvb3N0KQ0KbGlicmFyeShycGFydCkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShuZXVyYWxuZXQpDQoNCmBgYA0KDQojIyAyLjIgSW1wb3J0YWNpw7NuIGJhc2UgZGUgZGF0b3MNCmBgYHtyfQ0KZGY8LSByZWFkLmNzdigiQzpcXFVzZXJzXFxMdWlzRFxcRG9jdW1lbnRzXFxDb25jZW50cmFjacOzblxcTU9EVUxPIDNcXGhlYWx0aF9pbnN1cmFuY2UuY3N2IikNCmRmcmY8LSByZWFkLmNzdigiQzpcXFVzZXJzXFxMdWlzRFxcRG9jdW1lbnRzXFxDb25jZW50cmFjacOzblxcTU9EVUxPIDNcXGhlYWx0aF9pbnN1cmFuY2UuY3N2IikNCg0KI2ZpbGUuY2hvb3NlKCkNCmBgYA0KDQojIyAyLjMgUHJpbWVyIGFjZXJjYW1pZW50bw0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnN1bW1hcnkoZGYpDQpzdHIoZGYpDQojIGRmDQpgYGANCg0KIyMgMi40IENhbWJpYXIgdGlwbyBkZSB2YXJpYWJsZXMNCkVzIG5lY2VzYXJpbyBjYXRlZ29yaXphciBhbGd1bmFzIHZhcmlhYmxlcyBwYXJhIHF1ZSBwdWVkYSByZWFsaXphcnNlIGVsIHByaW1lciBtw6l0b2RvIGRlIFNNTCAoT0xTKQ0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGYkc2V4IDwtIGlmZWxzZShkZiRzZXggPT0gIm1hbGUiLCAxLCAwKQ0KZGYkc21va2VyIDwtIGlmZWxzZShkZiRzbW9rZXIgPT0gInllcyIsIDEsIDApDQpkZiRyZWdpb24gPC0gYXMubnVtZXJpYyhmYWN0b3IoZGYkcmVnaW9uLCBsZXZlbHMgPSBjKCJzb3V0aHdlc3QiLCJzb3V0aGVhc3QiLCJub3J0aGVhc3QiLCAibm9ydGh3ZXN0IiksIGxhYmVscyA9IGMoMSwyLDMsNCkpKQ0Kc3RyKGRmKQ0KYGBgDQoNCiMjIDIuNSBOQSdzIChJZGVudGlmaWNhY2nDs24geSByZWVtcGxhem8pDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbmFfY291bnQgPC0gY29sU3Vtcyhpcy5uYShkZikpDQpuYV9jb3VudA0KI05vIGNvbnRhbW9zIGNvbiBOQSdzIHBvciBsbyBxdWUgbm8gZXMgbmVjZXNhcmlvIHJlbXBsYXphciBuaW5ndW4gcmVnaXN0cm8gY29uIG1lZGlhbmEuDQpgYGANCg0KIyMgMi42IE1lZGlkYXMgZGVzY3JpcHRpdmFzDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KY29sdW1uYXNfbnVtZXJpY2FzIDwtIHNhcHBseShkZiwgaXMubnVtZXJpYykNCg0KbWVkaWEgPC0gY29sTWVhbnMoZGZbLCBjb2x1bW5hc19udW1lcmljYXNdLCBuYS5ybSA9IFRSVUUpDQpjYXQoIk1lZGlhOiIsIG1lZGlhLCAiXG4iKQ0KDQptZWRpYW5hIDwtIHNhcHBseShkZlssIGNvbHVtbmFzX251bWVyaWNhc10sIG1lZGlhbiwgbmEucm0gPSBUUlVFKSANCmNhdCgiTWVkaWFuYToiLCBtZWRpYW5hLCAiXG4iKQ0KDQptb2RhIDwtIGFwcGx5KGRmLCAyLCBmdW5jdGlvbih4KSB7DQogIHVuaXF1ZV94IDwtIHVuaXF1ZSh4WyFpcy5uYSh4KV0pDQogIHVuaXF1ZV94W3doaWNoLm1heCh0YWJ1bGF0ZShtYXRjaCh4LCB1bmlxdWVfeCkpKV19KSAgDQpjYXQoIk1vZGE6IiwgbWVkaWEsICJcbiIpDQoNCmBgYA0KDQojIyAyLjcgTWVkaWRhcyBkZSBkaXNwZXJzacOzbg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmRlc3ZpYWNpb25fZXN0YW5kYXIgPC0gc2FwcGx5KGRmLCBzZCkNCmRlc3ZpYWNpb25fZXN0YW5kYXIgPC0gZm9ybWF0KGRlc3ZpYWNpb25fZXN0YW5kYXIsIHNjaWVudGlmaWMgPSBGQUxTRSkNCmRlc3ZpYWNpb25fZXN0YW5kYXINCg0KDQp2YXJpYW56YSA8LSBzYXBwbHkoZGYsIHZhcikNCnZhcmlhbnphIDwtIGZvcm1hdCh2YXJpYW56YSwgc2NpZW50aWZpYyA9IEZBTFNFKQ0KdmFyaWFuemENCg0KcmFuZ2UgPC0gc2FwcGx5KGRmLCByYW5nZSkNCnJhbmdlIDwtIGZvcm1hdChyYW5nZSwgc2NpZW50aWZpYyA9IEZBTFNFKQ0KcmFuZ2UNCg0KY3VhcnRpbGVzIDwtIGFwcGx5KGRmLCAyLCBxdWFudGlsZSwgcHJvYnMgPSBjKDAsIDAuMjUsIDAuNSwgMC43NSwgMSkpDQpyb3duYW1lcyhjdWFydGlsZXMpIDwtIGMoIjAlIiwgIjI1JSIsICI1MCUiLCAiNzUlIiwgIjEwMCUiKQ0KY29sbmFtZXMoY3VhcnRpbGVzKSA8LSBjb2xuYW1lcyhkZikNCmN1YXJ0aWxlcw0KYGBgDQoNCiMjIDIuOCBHcmFmaWNvcyBwYXJhIGlkZW50aWZpY2FyIHBhdHJvbmVzDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBEaXN0cmlidWNpw7NuIGRlIGfDqW5lcm9zIGVuIGdyw6FmaWNvIGRlIGJhcnJhczoNCiNiYXJwbG90KHRhYmxlKGRmJHNleCksIG1haW49IkRpc3RyaWJ1Y2nDs24gZGUgZ8OpbmVybyIsIHhsYWI9IlNleG8iLCB5bGFiPSJGcmVjdWVuY2lhIix5bGltPWMoMCwgODAwKSkNCg0KIyBEaXN0cmlidWNpw7NuIGRlIGZ1bWFkb3JlcyBlbiBncsOhZmljbyBkZSBiYXJyYXM6DQpiYXJwbG90KHRhYmxlKGRmJHNtb2tlciksIG1haW49IkRpc3RyaWJ1Y2nDs24gZGUgZnVtYWRvcmVzIiwgeGxhYj0iRnVtYWRvciBvIG5vIGZ1bWFkb3IiLCB5bGFiPSJGcmVjdWVuY2lhIix5bGltPWMoMCwgMTMwMCkpDQoNCiMgSGlzdG9ncmFtYSBkZSBsYSB2YXJpYWJsZSBlZGFkDQpoaXN0KGRmJGFnZSwgbWFpbj0iSGlzdG9ncmFtYSBkZSBFZGFkIiwgeGxhYj0iRWRhZCIsIHlsYWI9IkZyZWN1ZW5jaWEiKQ0KDQojIEhpc3RvZ3JhbWEgZGUgbGEgdmFyaWFibGUgYm1pDQpoaXN0KGRmJGJtaSwgbWFpbj0iSGlzdG9ncmFtYSBkZSBCTUkiLCB4bGFiPSJCTUkiLCB5bGFiPSJGcmVjdWVuY2lhIikNCg0KIyBNYXRyaXogZGUgY29ycmVsYWNpw7NuIHBhcmEgdmFyaWFibGVzIG51bcOpcmljYXMNCmNvcnJfbWF0cml4IDwtIGNvcihkZlssIGMoImFnZSIsICJibWkiLCAiY2hpbGRyZW4iLCAiZXhwZW5zZXMiKV0pDQpjb3JycGxvdChjb3JyX21hdHJpeCwgbWV0aG9kPSJjb2xvciIpDQoNCiMgQm94IHBsb3QgcGFyYSAnZXhwZW5zZXMnIHZzLiAnc21va2VyJw0KYm94cGxvdChleHBlbnNlcyB+IHNtb2tlciwgZGF0YT1kZiwgbWFpbj0iQm94IHBsb3QgZGUgR2FzdG9zIHBvciBGdW1hZG9yIiwgeGxhYj0iRnVtYWRvciIsIHlsYWI9Ikdhc3RvcyIpDQoNCiMgQm94IHBsb3QgcGFyYSAnZXhwZW5zZXMnIHZzLiAncmVnaW9uJw0KYm94cGxvdChleHBlbnNlcyB+IHJlZ2lvbiwgZGF0YT1kZiwgbWFpbj0iQm94IHBsb3QgZGUgR2FzdG9zIHBvciBSZWdpw7NuIiwgeGxhYj0iUmVnacOzbiIsIHlsYWI9Ikdhc3RvcyIpDQoNCiNwbG90X25vcm1hbGl0eShkZiwgYWdlLCBibWksIGNoaWxkcmVuKQ0KYGBgDQoNCiMgMy4gSW50ZXJwcmV0YWNpw7NuIEVEQQ0KVG9tYW5kbyBlbiBjdWVudGEgbG9zIGdyw6FmaWNvcyBlbGFib3JhZG9zIGR1cmFudGUgbGEgYW50ZXJpb3IgZXRhcGEgcG9kZW1vcyBsbGVnYXIgYSBsYXMgc2lndWllbnRlcyBjb25jbHVzaW9uZXM6ICANCiogQ29uIHJlc3BlY3RvIGEgbGFzIHByaW1lcmFzIDIgZ3LDoWZpY2FzIHJlbGFjaW9uYWRhcyBhIGxhIGRpc3RyaWJ1Y2nDs24geSBjYW50aWRhZCBkZSBvYnNlcnZhY2lvbmVzIGVuIGxhcyB2YXJpYWJsZXMgKnNleCogeSAqc21va2VyKiBwb2RlbW9zIHZlciBxdWUgZXhpc3RlIHVuYSBjYWl0ZGFkIHNpbWlsYXIgZGUgcmVnaXN0cm9zIGRlIGhvbWJyZXMgeSBtdWplcmVzLCBzaW4gZW1iYXJnbyBkZW50cm8gZGUgbGEgbXVlc3RyYSBjb250YW1vcyBjb24gdW4gbnVtZXJvIHNpZ25pZmljYW50ZSBtYXlvciBkZSBwZXJzb25hcyBmdW1hZG9yYXMgYSBubyBmdW1hZG9yYXMuICANCiogRGUgbGEgbWlzbWEgZm9ybWEgcG9kZW1vcyBvYnNlcnZhciBtZWRpYW50ZSBlbCBoaXN0b2dyYW1hIGRlICphZ2UqIHF1ZSBjb250YW1vcyBjb24gdW5hIGdyYW4gdmFyaWVkYWQgZGUgcmVnaXN0cm9zIGRlIHRvZGFzIGxhcyBlZGFkZXMsIHB1ZXMgbGEgZGlmZXJlbmNpYSBlbnRyZSBsYXMgZnJlY3VlbmNpYXMgZGVsIGdyw6FmaWNvIG5vIGVzIHRhbiBncmFuZGUuICANCiogRW4gY3VhbnRvIGEgbGEgbWF0cml6IGRlIGNvcnJlbGFjacOzbiBlbnRyZSBjYWRhIHZhcmlhYmxlLCBwb2RlbW9zIG9ic2VydmFyIHVuYSAgY29ycmVsYWNpw7NuIG1lZGlhbmEgZW50cmUgbGFzIHZpYXJpYWJsZXMgKmFnZSogeSAqZXhwZW5zZXMqIHkgdW5hIGNvcnJlbGFjacOzbiBsaWdlcmEgZW50cmUgbGFzIHZhcmlhYmxlcyAqYm1pKiB5ICpleHBlbnNlcyosIHNpIHRvbWFtb3MgZW4gY3VlbnRhIHF1ZSAqZXhwZW5zZXMqIGVzIG51ZXN0cmEgdmFyaWFibGUgZGVwZW5kaWVudGUgcHVlZGUgc2VyIHF1ZSBsYSBjb3JyZWxhY2nDs24gcXVlIHByZXNlbnRhIGNvbiBlc3RhcyBkb3MgdmFyaWFibGVzIGluZGVwZW5kaWVudGVzIHNlIHZlYSByZWZsZWphZGEgbWFzIGFkZWxhbnRlIHkgbGFzIHZlYW1vcyBjb21vIGVzdGFkw61zdGljYW1lbnRlIHNpZ25pZmljYW50ZXMgcGFyYSBleHBsaWNhciBlbCBjYW1iaW8gZW4gbGEgdmFyaWFibGUgKlkqLiAgDQoqIEZpbmFsbWVudGUgbWVkaWFudGUgZWwgdXNvIGRlIGJveHBsb3RzIHB1ZGltb3Mgb2JzZXJ2YXIgZWwgY29tcG9ydGFtaWVudG8gZGUgbGEgdmFyaWFibGUgZGVwZW5kaWVudGUgKmV4cGVuc2VzKiBjdWFuZG8gbGEgcmVsYWNpb25hbW9zIGNvbiBsYSB2YXJpYWJsZSAqc21va2VyKiwgZW4gZXN0ZSBwcmltZXIgY2FzbyBwb2RlbW9zIG9ic2VydmFyIHVuYSBtZWRpYW5hIGJhc3RhbnRlIG1heW9yIGRlbnRybyBkZSBsYXMgb2JzZXJ2YWNpb25lcyAqc21va2VyLXllcyogeSB0YW1iacOpbiB1bmEgbWF5b3IgZGlzcGVyc2nDs24gZGUgb2JzZXJ2YWNpb25lcywgcG9yIGVsIGNvbnRyYXJpbyBkZWwgY29tcG9ydGFtaWVudG8gcHJlc2VudGFkbyBlbnRyZSAqZXhwZW5zZXMqIHkgKnJlZ2lvbiogcHVlcyBubyBzZSBvYnNlcnZhIHVuYSBkaWZlcmVuY2lhIHNpZ25pZmljYXRpdmEgZW50cmUgbGFzIG1lZGlhcyBkZSBsYXMgcmVnaW9uZXMsIMO6bmljYW1lbnRlIHBvZGVtb3Mgb2JzZXJ2YXIgdW4gbGlnZXJvIGF1bWVudG8gZW4gY3VhbnRvIGEgbGEgdmFyaWFiaWxpZGFkIGRlIGxhcyAqZXhwZW5zZXMqIHJlbGFjaW9uYWRhIGEgbGFzIHJlZ2lvbmVzLg0KKiBUZW5kcmVtb3MgcXVlIHRyYW5zZm9ybWFyIGxhIHZhcmlhYmxlICpibWkqIHBhcmEgcXVlIG5vIGhheWEgcHJvYmxlbWFzIGRlIGhldGVyb2NlZGFzdGljaWRhZC4NCg0KIyA0LiBNb2RlbG9zIFNNTA0KIyMgNC4wIFBhcnRpY2nDs24gZGUgZGF0b3MgeSBzZW1pbGxhDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShjYXJldCkNCnNldC5zZWVkKDEyMykNCmRhdG9zX3BhcnRpZG9zIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IGRmJGV4cGVuc2VzLCBwPTAuOCwgbGlzdD1GKQ0KdHJhaW4gPSBkZltkYXRvc19wYXJ0aWRvcywgXQ0KdGVzdCA8LSBkZlstZGF0b3NfcGFydGlkb3MsIF0NCmBgYA0KDQojIyA0LjEgT0xTIFJlZ3Jlc2nDs24NCiMjIyA0LjEuMSBDcmVhY2nDs24gZGVsIG1vZGVsbw0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgQ3JlYWNpw7NuIGRlbCBtb2RlbG8NCm9sc19tb2RlbCA8LSBsbShsb2coZXhwZW5zZXMpICB+IGFnZSArIHNleCArIGxvZyhibWkpICsgY2hpbGRyZW4gKyBzbW9rZXIgKyByZWdpb24sIGRhdGEgPSB0cmFpbikNCnN1bW1hcnkob2xzX21vZGVsKQ0KYGBgDQoNCiMjIyA0LjEuMiBQcnVlYmEgZGUgTXVsdGljb2xpbmVhbGlkYWQgDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KdmlmKG9sc19tb2RlbCkNCiMgTG9zIHZhbG9yZXMgZGUgVklGIHNvbiBtZW5vcmVzIGEgNSBvIDEwIHBvciBsbyBxdWUgTk8gZXhpc3RlIG11bHRpY29saW5lYWxpZGFkDQpgYGANClRvbWFuZG8gZW4gY3VlbnRhIGxvcyByZXN1bHRhZG9zIGRlIGxhIHBydWViYSBWSUYgcG9kZW1vcyBhZmlybWFyIHF1ZSAqKk5PIEVYSVNURSBNVUxUSUNPTElORUFMSURBRCoqICANCg0KDQojIyMgNC4xLjMgUHJ1ZWJhIEhldGVyb2NlZGFzdGljaWRhZA0KIyMjIyA0LjEuMy4xIFZpc3VhbGl6YWNpw7NuIGRlIHJlc2lkdWFsZXMNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIENvbW8gcHJpbWVyIGNvbnRhY3RvIGhhY2Vtb3MgdW4gZ3LDoWZpY28gcGFyYSB2ZXIgbG9zIHJlc2lkdWFsZXMgZGVsIG1vZGVsbw0KcmVzPC0gcmVzaWR1YWxzKG9sc19tb2RlbCkNCnBsb3QocmVzLCB0eXBlPSJsIikNCmBgYA0KRWwgZ3LDoWZpY28gYW50ZXJpb3IgcGFyZWNlIHNlciBwcnVlYmEgYWNlcmNhIGRlIGxhIGV4aXN0ZW5jaWEgZGUgSGV0ZXJvY2VkYXN0aWNpZGFkIHBvciBsbyBxdWUgcHJvY2VkZXJlbW9zIGEgcmVhbGl6YXIgZWwgQlAtVGVzdCBwYXJhIGNvbmZpcm1hcmxvDQoNCiMjIyMgNC4xLjMuMiBCUC1UZXN0DQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KYnB0ZXN0KG9sc19tb2RlbCkNCmBgYA0KVG9tYW5kbyBlbiBjdWVudGEgZWwgcmVzdWx0YWRvIGRlbCBCcmV1c2NoLVBhZ2FuIFRlc3QsIG9ic2VydmFtb3MgcXVlIGVzdGUgaW5kaWNhIGZ1ZXJ0ZW1lbnRlIGxhIHByZXNlbmNpYSBkZSBoZXRlcm9jZWRhc3RpY2lkYWQgZW4gZWwgbW9kZWxvIE9MUywgcG9yIGxvIHF1ZSBlcyBuZWNlc2FyaW8gcmVhbGl6YXIgdW5hIGVzdGltYWNpw7NuIGRlIG3DrW5pbW9zIGN1YWRyYWRvcyBwb25kZXJhZG9zIChXTFMpLi4uDQoNCiMjIyMgNC4xLjMuMyBXTFMgDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQp3dCA8LSAxIC8gbG0oYWJzKG9sc19tb2RlbCRyZXNpZHVhbHMpIH4gb2xzX21vZGVsJGZpdHRlZC52YWx1ZXMpJGZpdHRlZC52YWx1ZXNeMg0Kd2xzX21vZGVsPC1sbShsb2coZXhwZW5zZXMpIH4gIGFnZSArIHNleCArIGxvZyhibWkpICsgY2hpbGRyZW4gKyBzbW9rZXIgKyByZWdpb24gLCBkYXRhID0gdHJhaW4sIHdlaWdodHM9d3QpDQpzdW1tYXJ5KHdsc19tb2RlbCkNCnN0cih0cmFpbikNCg0KDQpicHRlc3Qod2xzX21vZGVsKQ0KIyAgUG9kZW1vcyBvYnNlcnZhciBxdWUgc2UgaGEgbG9ncmFkbyBlbGltaW5hciBsYSBoZXRlcm9jZWRhc3RpY2lkYWQgYXVtZW50YW5kbyBlbCBwLXZhbHVlIGEgMQ0KDQp2aWYod2xzX21vZGVsKQ0KIyBQb2RlbW9zIG9ic2VydmFyIHRhbWJpw6luIHF1ZSBhIHBlc2FyIGRlIHF1ZSBsb3MgY29lZmljaWVudGVzIGRlIFZJRiBhdW1lbnRhcm9uLCBOTyB0ZW5lbW9zIG11bHRpY29saW5lYWxpZGFkDQoNCmBgYA0KDQojIyMgNC4xLjQgTm9ybWFsaWRhZCBkZSByZXNpZHVhbGVzIA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgUmVzaWR1YWxlcyBkZWwgV0xTDQpyZXNfd2xzPC0gcmVzaWR1YWxzKHdsc19tb2RlbCkNCnBsb3QocmVzX3dscywgdHlwZT0ibCIpDQoNCg0KIyBHcsOhZmljbyBRLVEgZGUgcmVzaWR1YWxlcw0KcXFub3JtKHJlc193bHMpDQpxcWxpbmUocmVzX3dscykNCg0KIyBDcmVhdGUgcmVzaWR1YWwgdnMuIGZpdHRlZCBwbG90DQoNCnBsb3QoZml0dGVkKHdsc19tb2RlbCksIHJlc2lkKHdsc19tb2RlbCksIG1haW49IkxpbmVhciBSZWdyZXNzaW9uIFJlc2lkdWFsIHZzLiBGaXR0ZWQgVmFsdWVzIiwgeGxhYj0iRml0dGVkIFZhbHVlcyIsIHlsYWI9IlJlc2lkdWFscyIpDQphYmxpbmUoMCwwKQ0KDQpoaXN0KHdsc19tb2RlbCRyZXNpZHVhbHMsIHhsYWI9IkVzdGltYXRlZCBSZWdyZXNzaW9uIFJlc2lkdWFscyIsIG1haW49J0Rpc3RyaWJ1dGlvbiBvZiBPTFMgRXN0aW1hdGVkIFJlZ3Jlc3Npb24gUmVzaWR1YWxzJywgY29sPSdsaWdodGJsdWUnLCBib3JkZXI9IndoaXRlIiApDQoNCiMgU2hhcGhpcm8gVGVzdA0Kc2hhcGhpcm8gPC0gc2hhcGlyby50ZXN0KHJlc193bHMpDQpzaGFwaGlybw0KYGBgICAgDQpBIHBlc2FyIGRlIHF1ZSBhcGFyZW50ZW1lbnRlIG5vIGNvbnRhbW9zIGNvbiBub3JtYWxpZGFkIGRlIHJlc2lkdWFsZXMsIGRlYmVtb3MgcmVjb3JkYXIgcXVlIGVzdGFtb3MgZXZhbHVhbmRvIHVuIG1vZGVsbyBkZSBXTFMgcG9yIGxvIHF1ZSBwb2RlbW9zIGNvbnRpbnVhciBhIGxhIGNvbXBhcmFjacOzbiBkZSBSTVNFIHkgUjIgZGUgbG9zIG1vZGVsb3MgT0xTIHkgV0xTLi4uDQoNCiMjIyA0LjEuNSBDb21wYXJhY2nDs24gZGUgZGVzZW1wZcOxbyBSTVNFDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBQcmVkaWNjaW9uZXMgZGVsIG1vZGVsbyBPTFMNCnByZWRpY2Npb25lc19vbHMgPC0gcHJlZGljdChvbHNfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0KQ0KIyBDb252ZXJ0aXIgbGFzIHByZWRpY2Npb25lcyBkZSBsb2dhcml0bW8gYSBzdSBlc2NhbGEgb3JpZ2luYWwNCnByZWRpY2Npb25lc19vbHMgPC0gZXhwKHByZWRpY2Npb25lc19vbHMpDQoNCiMgUHJlZGljY2lvbmVzIGRlbCBtb2RlbG8gV0xTDQpwcmVkaWNjaW9uZXNfd2xzIDwtIHByZWRpY3Qod2xzX21vZGVsLCBuZXdkYXRhID0gdGVzdCkNCiMgQ29udmVydGlyIGxhcyBwcmVkaWNjaW9uZXMgZGUgbG9nYXJpdG1vIGEgc3UgZXNjYWxhIG9yaWdpbmFsDQpwcmVkaWNjaW9uZXNfd2xzIDwtIGV4cChwcmVkaWNjaW9uZXNfd2xzKQ0KDQojIENhbGN1bGFyIGVsIFJNU0UgcGFyYSBjYWRhIG1vZGVsbw0Kcm1zZV9vbHMgPC0gc3FydChtZWFuKChwcmVkaWNjaW9uZXNfb2xzIC0gdGVzdCRleHBlbnNlcyleMikpDQpybXNlX3dscyA8LSBzcXJ0KG1lYW4oKHByZWRpY2Npb25lc193bHMgLSB0ZXN0JGV4cGVuc2VzKV4yKSkNCg0KIyBJbXByaW1pciBsb3MgUk1TRQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8gT0xTOiIsIHJtc2Vfb2xzLCAiXG4iKQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8gV0xTOiIsIHJtc2Vfd2xzLCAiXG4iKQ0KYGBgDQoNCiMjIDQuMiBYR0Jvb3N0IFJlZ3Jlc2lvbg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCnRyYWluX3ggPC0gZGF0YS5tYXRyaXgodHJhaW5bLCAhY29sbmFtZXModHJhaW4pICVpbiUgImV4cGVuc2VzIl0pDQp0cmFpbl95IDwtIGxvZyh0cmFpbiRleHBlbnNlcykNCg0KdGVzdF94IDwtIGRhdGEubWF0cml4KHRlc3RbLCAhY29sbmFtZXModGVzdCkgJWluJSAiZXhwZW5zZXMiXSkNCnRlc3RfeSA8LSBsb2codGVzdCRleHBlbnNlcykNCg0KeGdiX3RyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSB0cmFpbl94LCBsYWJlbCA9IHRyYWluX3kpDQp4Z2JfdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gdGVzdF94LCBsYWJlbCA9IHRlc3RfeSkNCg0Kd2F0Y2hsaXN0IDwtIGxpc3QodHJhaW4gPSB4Z2JfdHJhaW4sIHRlc3QgPSB4Z2JfdGVzdCkNCm1vZGVsX3hnYiA8LSB4Z2IudHJhaW4oZGF0YSA9IHhnYl90cmFpbiwgbWF4X2RlcHRoID0gMywgd2F0Y2hsaXN0ID0gd2F0Y2hsaXN0LCBucm91bmRzID0gMTAwKQ0KYGBgDQpgYGB7cn0NCnJlZ194Z2IgPC0geGdib29zdChkYXRhID0geGdiX3RyYWluLCBtYXhfZGVwdGggPSAzLCBucm91bmRzID0gNTksIHZlcmJvc2UgPSAwKQ0KcHJlZGljdGlvbl94Z2JfdGVzdCA8LSBwcmVkaWN0KHJlZ194Z2IsIHhnYl90ZXN0KQ0Kcm1zZV9YR0IgPC0gc3FydChtZWFuKChleHAocHJlZGljdGlvbl94Z2JfdGVzdCkgLSBleHAodGVzdF95KSleMikpDQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIHJtc2Vfb2xzLCAiXG4iKQ0KDQpgYGANCg0KDQojIyA0LjMgRGVjaXNpb24gVHJlZSBSZWdyZXNpb24NCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpkdF9yZWdyZXNpb24gPC0gcnBhcnQobG9nKGV4cGVuc2VzKSB+IGFnZSArIHNleCArIGJtaSArIGNoaWxkcmVuICsgc21va2VyICsgcmVnaW9uLCBkYXRhID0gdHJhaW4pDQpwbG90KGR0X3JlZ3Jlc2lvbiwgY29tcHJlc3MgPSBUUlVFKQ0KdGV4dChkdF9yZWdyZXNpb24sIHVzZS5uID0gVFJVRSkNCmxpYnJhcnkocnBhcnQucGxvdCkNCnJwYXJ0LnBsb3QoZHRfcmVncmVzaW9uKQ0KDQpkZWNpc2lvbl90cmVlX3ByZWRpY3Rpb24gPC0gcHJlZGljdChkdF9yZWdyZXNpb24sdGVzdCkNCnJtc2VfZHQgPC0gc3FydChtZWFuKChleHAoZGVjaXNpb25fdHJlZV9wcmVkaWN0aW9uKSAtIGRmJGV4cGVuc2VzKV4yKSkNCmNhdCgiUk1TRSBkZSBEZWNpc2lvbiBUcmVlczoiLCBybXNlX2R0LCAiXG4iKQ0KYGBgDQoNCiMjIDQuNCBSYW5kb20gRm9yZXN0IFJlZ3Jlc2lvbg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgQ3JlYXIgZWwgbW9kZWxvIGRlIFJhbmRvbSBGb3Jlc3QNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KZGZyZiRzZXg8LSBmYWN0b3IoZGZyZiRzZXgsIGxldmVscz1jKCJtYWxlIiwgImZlbWFsZSIpKQ0KZGZyZiRzbW9rZXI8LSBmYWN0b3IoZGZyZiRzbW9rZXIsIGxldmVscz1jKCJ5ZXMiLCAibm8iKSkNCmRmcmYkcmVnaW9uIDwtIGZhY3RvcihkZnJmJHJlZ2lvbiwgbGV2ZWxzID0gYygic291dGh3ZXN0IiwgInNvdXRoZWFzdCIsICJub3J0aGVhc3QiLCAibm9ydGh3ZXN0IikpDQoNCmxpYnJhcnkoY2FyZXQpDQpzZXQuc2VlZCgxMjMpDQpkYXRvc19wYXJ0aWRvc3JmIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oeSA9IGRmcmYkZXhwZW5zZXMsIHA9MC44LCBsaXN0PUYpDQp0cmFpbnJmID0gZGZyZltkYXRvc19wYXJ0aWRvcywgXQ0KdGVzdHJmIDwtIGRmcmZbLWRhdG9zX3BhcnRpZG9zLCBdDQoNCnJhbmRvbV9mb3Jlc3QgPC0gcmFuZG9tRm9yZXN0KGxvZyhleHBlbnNlcykgfiBhZ2UgKyBzZXggKyBibWkgKyBjaGlsZHJlbiArIHNtb2tlciArIHJlZ2lvbiwgZGF0YSA9IHRyYWlucmYsIG5hLmFjdGlvbiA9IG5hLmV4Y2x1ZGUpDQpyYW5kb21fZm9yZXN0DQoNCnByZWRpY3Rpb25zX3JmIDwtIHByZWRpY3QocmFuZG9tX2ZvcmVzdCwgdGVzdHJmKQ0Kcm1zZV9yZiA8LSBzcXJ0KG1lYW4oKGV4cChwcmVkaWN0aW9uc19yZikgLSBkZiRleHBlbnNlcyleMikpDQpjYXQoIlJNU0UgUmFuZG9tIEZvcmVzdDoiLCBybXNlX3JmLCAiXG4iKQ0KDQpgYGANCg0KIyMgNC41IFJlZCBOZXVyb25hbA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkobmV1cmFsbmV0KQ0Kbm5fbW9kZWwgPC0gbmV1cmFsbmV0KGV4cGVuc2VzIH4gYWdlICsgc2V4ICsgYm1pICsgY2hpbGRyZW4gKyBzbW9rZXIgKyByZWdpb24sIGRhdGEgPSB0cmFpbiwgaGlkZGVuID0gYyg1LCAzKSwgbGluZWFyLm91dHB1dCA9IFRSVUUpDQpwbG90KG5uX21vZGVsKQ0KcHJlZGljdGlvbnNfbm5ldCA8LSBwcmVkaWN0KG5uX21vZGVsLCB0ZXN0KQ0Kcm1zZV9ubiA8LSBzcXJ0KG1lYW4oKGV4cChwcmVkaWN0aW9uc19ubmV0KSAtIHRlc3QkZXhwZW5zZXMpXjIpKQ0KY2F0KCJSTVNFIGRlIFJlZGVzIE5ldXJvbmFsZXM6Iiwgcm1zZV9ubiwgIlxuIikNCmBgYA0KDQojIDYuIEV2YWx1YWNpw7NuIHkgc2VsZWNjacOzbiBkZSBtb2RlbG9zDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kcm1zZXN5PC0gYyhybXNlX29scyxybXNlX3dscyxybXNlX1hHQixybXNlX2R0LHJtc2VfcmYpDQoNCnJtc2VzeDwtIGMoIk9MUyIsICJXTFMiLCAiWEdCb29zdCIsICJEVCIsICJSRiIpDQoNCmRmX1JNU0VTPC0gZGF0YS5mcmFtZShybXNlc3g9cm1zZXN4LHJtc2VzeT1ybXNlc3kpDQoNCg0KYmFycGxvdChkZl9STVNFUyRybXNlc3ksIG5hbWVzPSBkZl9STVNFUyRybXNlc3gsIGNvbCA9ICJza3libHVlIiwgDQogICAgICAgIG1haW4gPSAiVmFsb3JlcyBkZSBSTVNFIHBhcmEgZGlmZXJlbnRlcyBtb2RlbG9zIiwgDQogICAgICAgIHhsYWIgPSAiUk1TRSIsIHlsYWIgPSAiVmFsb3IgZGUgUk1TRSIpDQpgYGANClRvbWFuZG8gZW4gY3VlbnRhIGxvcyByZXN1bHRhZG9zIHByZXNlbnRhZG9zIGFudGVyaW9ybWVudGUsIGVuIGRvbmRlIGNvbXBhcmFtb3MgbG9zIFJNU0UgZGUgbG9zIGRpZmVyZW50ZXMgbW9kZWxvcyByZWFsaXphZG9zIChSTVNFIGRlIE5ldXJhbCBOZXR3b3JrcyBubyBmdWUgaW5jbHVpZG8gZGViaWRvIGEgdmFsb3Igc3VtYW1lbnRlIGFsdG8pLiBQb2Vtb3Mgb2JzZXJ2YXIgcXVlIGVsIG1vZGVsbyBjb24gdW4gbWVqb3IgZGVzZW1wZcOxbyBjb21wcm9iYWRvIHBvciBzZXIgZW4gUk1TRSBjb24gZWwgdmFsb3IgbcOhcyBwZXF1ZcOxbyBlcyBlbCAqWEdCb29zdCBtb2RlbCogDQojIDcuIENvbmNsdXNpb25lcw0KIyMgYS4gRURBDQpEdXJhbnRlIGVsIEFuw6FsaXNpcyBFeHBsb3JhdG9yaW8gZGUgRGF0b3MgdHV2ZSBsYSBvcG9ydHVuaWRhZCBkZSBjb21lbnphciBlbCBhbsOhbGlzaXMgcmVhbGl6YW5kbyB1biBhY2VyY2FtaWVudG8gcHJpbmNpcGFsIG1lZGlhbnRlIHN0ciB5IHN1bW1hcnkgcGFyYSBzYWJlciBlbCB0aXBvIGRlIHZhcmlhYmxlcyB5IHVuIHBvY28gYWNlcmNhIGRlIHN1IGNvbXBvcnRhbWllbnRvLCB0YW1iacOpbiBpbmNsdWltb3MgbWVkaWRhcyBkZSBkaXNwZXJzacOzbiB5IGRlc2NyaXB0aXZhcywgYSBsYSBob3JhIGRlIGJ1c2NhciBOQSdzIG5vIGxvZ3JhbW9zIGlkZW50aWZpY2FyIGxhIHByZXNlbmNpYSBkZSBhbGd1bm8sIGZpbmFsbWVudGUgY29uY2x1aW1vcyBlbCBFREEgbWVkaWFudGUgbGEgdmlzdWFsaXphY2nDs24gZGUgaGlzdG9ncmFtYXMgcGFyYSBwb2RlciB2aXN1YWxpemFyIGxhcyBmcmVjdWVuY2lhcyBkZSBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMsIHRhbWJpw6luIHVzYW1vcyBib3hwbG90cyBwYXJhIHBvZGVyIG9ic2VydmFyIHVuIHBvY28gZGVsIGNvbXByb3RhbWllbnRvIGRlIGxhIHZhcmlhYmxlIGRlcGVuZGllbnRlIHkgc3UgcmVsYWNpw7NuIGNvbiBsYXMgdmFyaWFibGVzIGNhdGVnw7NyaWNhcywgZm5hbG1lbnRlIHBvZGVtb3MgdmlzdWFsaXphciB1biBoZWF0bWFwIGVuIGVsIHF1ZSBzZSBtdWVzdHJhbiBsYXMgcmVsYWNpb25lcyBlbnRyZSB2YXJpYWJsZXMuDQoNCiMjIGIuIE1vZGVsbyBzZWxlY2Npb25hZG8NCkNvbW8gY29uY2x1c2nDs24geSBiYXPDoW5kb25vcyBlbiBsYSBjb21wYXJhY2nDs24gZGUgUk1TRSdzIGRlIGxvcyBkaXN0aW50b3MgbW9kZWxvcywgcG9kZW1vcyBsbGVnYXIgYSBsYSBjb25jbHVzacOzbiBkZSBxdWUgZWwgbW9kZWxvIFhHQm9vc3QgZXMgYXF1ZWwgcXVlIG11ZXN0cmEgdW4gbWVqb3IgZGVzZW1wZcOxbywgZW4gY2FzbyBkZSB0ZW5lciBhbGd1bmEgZHVkYSBlbiBjdWFudG8gYSBsYSBjb21wYXJhY2nDs24sIHNlIHN1Z2llcmUgaW1wbGVtZW50YXIgYWxndW5hcyBvdHJhcyB0w6ljbmljYXMgY29tbyBBSUMuDQojIyMgaS4gwr9DdcOhbGVzIHNvbiBsYXMgdmFyaWFibGVzIHF1ZSBjb250cmlidXllbiBhIGV4cGxpY2FyIGxvcyBjYW1iaW9zIGRlIGxhIHByaW5jaXBhbCB2YXJpYWJsZSBkZSBlc3R1ZGlvPyAgDQpMYXMgcHJpbmNpcGFsZXMgdmFyaWFibGVzIHF1ZSBjb250cmlidXllbiBhIGxhIGV4cGxpY2FjacOzbiBkZSBsb3MgY2FtYmlvcyBzb246ICANCiogYWdlICANCiogYm1pICANCiogc21va2VyIHllcy1ubyAgDQoNCiMjIyBpaS4gwr9Dw7NtbyBlcyBlbCBpbXBhY3RvIGRlIGRpY2hhcyB2YXJpYWJsZXMgZXhwbGljYXRpdmFzIHNvYnJlIGxhIHZhcmlhYmxlIGRlcGVuZGllbnRlPw0KRWwgaW1wYWN0byBkZSB0b2RhcyBsYXMgdmFyaWFibGVzIGFudGVyaW9yZXMgZXMgcG9zaXRpdm8sIGVzIGRlY2lyIHF1ZSBzaSBlbCB2YWxvciBkZSBlc3RhcyBhdW1lbnRhLCB0YW1iacOpbiBsbyBoYXLDoSBlbCB2YWxvciBkZSAqZXhwZW5zZXMqLCBlbiBlbCBjYXNvIGRlICpzbW9rZXIqIHNlIGp1c3RpZmljYSBkZWJpZG8gYSBxdWUgMD0ibm8iIHkgMT0ieWVzIg0KDQojIyMgaWlpLiDCv0xvcyByZXN1bHRhZG9zIGVzdGltYWRvcyBkZWwgbW9kZWxvIHNlbGVjY2lvbmFkbyBzb24gc2ltaWxhcmVzIGEgbG9zIG90cm9zIG1vZGVsb3MgZXN0aW1hZG9zPyDCv0N1w6FsZXMgc29uIGxhcyBkaWZlcmVuY2lhcz8gDQpTaSBjb21wYXJhbW9zIGxvcyByYXN1bHRhZG9zIG9idGVuaWRvcyBkZSBYR0Jvb3N0IGNvbiBsb3MgcmVzdWx0YWRvcyBkZSBsb3MgZGVtw6FzIG1vZGVsb3MgcG9kZW1vcyBvYnNlcnZhciBxdWUgbGFzIHZhaWFibGVzIHNpZ25pZmljYXRpdmFzIG8gY29uIG1heW9yIGltcGFjdG8gc29uIGxhcyBtaXNtYXMgZW4gbGEgbWF5b3LDrWEgZGUgbG9zIG1vZGVsb3MsIGFzw60gY29tbyBzdSBjYXBhY2lkYWQgZXhwbGljYXRpdmEgZGUgbG9zIG1vZGVsb3MsIHNpbiBlbWJhcmdvIGxvZ3LDsyBkaWZlcmVuY2lhcnNlIGVuIGVsIFJNU0UgcXVlIGNvbW8gc3Ugbm9tYnJlIGxvIGRpY2UgZXMgbGEgZGlmZXJlbmNpYSBlbnRyZSBsb3MgdmFsb3JlcyBwcmVkaWNob3MgcG9yIHVuIG1vZGVsbyB5IGxvcyB2YWxvcmVzIG9ic2VydmFkb3Mu