Ejercicio 2
El siguiente conjunto de datos
ventascigarrillos.xlsx corresponde a los datos de los
50 estados de EEUU y las variables regitradas son las siguientes:
- Age Edad mediana de las personas que viven en el Estado.
- HS Porcentaje de personas con 25 años o más que tienen su nivel de
educación secundaria concluido.
- Income Ingreso personal per cápita.
- Black Porcentaje de individuos de raza negra.
- Female Porcentaje de mujeres.
- Price Precio promedio del paquete de cigarrillos.
- Sales Número de paquetes promedio vendido per cápita.
Se requiere construir una ecuación de regresión que vincule el
promedio de paquetes vendidos de cigarrillos per cápita (Sales) con las
diversas variables económicas y socio demográficas disponibles en la
basse (en caso que sea útiles para predecir a la respuesta de interés).
Para ello sugerimos tener en consideración los siguientes
lineamientos:
Dataset
library(readxl)
library(ggplot2)
library(plotly)
library(dplyr)
library(tibble)
library(readxl)
library(dplyr)
library(GGally)
library(Hmisc)
library(corrplot)
library(PerformanceAnalytics)
library(robustbase); library(MASS)
library(olsrr); library(car); library(quantreg)
data <- read.csv("C:/Users/ander/Desktop/1. Ciencias de Datos/4. Regresión avanzada/9. Examen 11042023/ventascigarrillos.csv", sep =";")
head(data)
Qué nivel de
asociación lineal presentan estas variables con la variable respuesta
Sales?
Un primer acercamiento para estudiar el nivel de asociación lineal
entre las variables, puede ser a través de la matriz de correlación. En
ella se observa como Sales tiene una correlación
negativa y significativa de -0.30 con Price y positiva
con Income de un 0.33.
Para un posible modelo lineal multiple estas serian las variables que
mejor explicarian a Sales
data <-data[,-c(1)]
chart.Correlation(data, histogram = F)

Utilizando un método
de selección automática elegir el numero de variables y cuáles son para
construir un modelo en el que no se considere interacción.
Se utiliza una validación combinada de modelos, en donde se evaluan
dichos modelos en función de la cantidad de regresores y se elige el
mejor por el criterio de AIC.
library(olsrr)
lm <- lm(Sales~ ., data=data)
k_best <- ols_step_best_subset(lm)
k_best
El mejor modelo resulto ser el tercero ya que cuenta con el mayor R2
adj y el menor AIC. Por lo que queraría:
modelo1 <- lm(Sales~Age + Income + Price, data=data)
summary(modelo1)
##
## Call:
## lm(formula = Sales ~ Age + Income + Price, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -50.490 -14.336 -5.164 7.175 128.880
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 64.521307 62.752062 1.028 0.30923
## Age 4.156318 2.222385 1.870 0.06783 .
## Income 0.019298 0.006963 2.772 0.00802 **
## Price -3.407476 1.008475 -3.379 0.00149 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 27.91 on 46 degrees of freedom
## (1 observation deleted due to missingness)
## Multiple R-squared: 0.3017, Adjusted R-squared: 0.2562
## F-statistic: 6.626 on 3 and 46 DF, p-value: 0.0008171
Si bien resulto el mejor modelo respecto a las variables disponibles,
el R2 solo logra explicar un 30% de la variación de la variable Sales.
Las variables mas significativas dentro del modelo resultan ser Income y
Price cuyo p-value es menor al nivel de confianza del 0.05.
Agregue en caso de
que tenga sentido alguna interacción al modelo.
Al agregar una interacción entre las variables
Female y Price se ve como mejora el R2
adj y esta interaccion resulta significativa para el modelo
modelo2 <- lm(Sales ~ Age + Price*Female + Income, data=data)
summary(modelo2)
##
## Call:
## lm(formula = Sales ~ Age + Price * Female + Income, data = data)
##
## Residuals:
## Min 1Q Median 3Q Max
## -34.127 -12.054 -4.804 4.443 126.868
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -6.501e+03 2.043e+03 -3.183 0.00268 **
## Age 5.427e+00 2.638e+00 2.057 0.04563 *
## Price 1.621e+02 5.196e+01 3.119 0.00320 **
## Female 1.284e+02 3.989e+01 3.219 0.00242 **
## Income 1.253e-02 7.014e-03 1.786 0.08095 .
## Price:Female -3.240e+00 1.018e+00 -3.183 0.00267 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 25.67 on 44 degrees of freedom
## (1 observation deleted due to missingness)
## Multiple R-squared: 0.4351, Adjusted R-squared: 0.3709
## F-statistic: 6.778 on 5 and 44 DF, p-value: 9.186e-05
Analice la presencia
de multicolinealidad. Si la respuesta es afiramtiva, cómo lo
solucionaría?
Análisis de
multicolinialidad a través del VIF
Si se evalua el modelo 2, vemos que el indicador vif es muy alto en
las variables Price y Female, asi como también la interacción entre
ambas. Por lo que, por simplicidad del modelo podemos optar por el
modelo sin interacción (modelo 1).
## Age Price Female Income Price:Female
## 1.860602 3437.138657 146.989956 1.320652 3597.379621
## Age Income Price
## 1.116570 1.100580 1.094865
Analice los residuos
e indique si aprecia la presencia de outliers o/y observaciones
influyentes.
Análisis gráfico de
normalidad
Según el análisis del QQplot a simple vista se observa falta de
normalidad en los residuos por presencia de outliers, especificamente el
registro 29 y 30 se marcan con valores atípicos.

## [1] 30 29
Análisis de Puntos
influyentes
Gráfico de
distancia Cook
Otros puntos
influyentes
## Potentially influential observations of
## lm(formula = Sales ~ Age + Income + Price, data = data) :
##
## dfb.1_ dfb.Age dfb.Incm dfb.Pric dffit cov.r cook.d hat
## 2 0.15 -0.30 0.19 0.12 0.36 1.43_* 0.03 0.26_*
## 10 -0.03 0.03 -0.01 0.01 0.04 1.33_* 0.00 0.18
## 29 -0.32 -0.18 0.48 0.52 0.89_* 0.61_* 0.17 0.09
## 30 0.19 0.49 0.06 -1.03_* 1.42_* 0.08_* 0.27 0.05
## 32 0.07 -0.10 -0.05 0.08 0.14 1.27_* 0.01 0.15
## 38 0.00 -0.01 0.00 0.02 -0.02 1.30_* 0.00 0.16
La observación 9, 29 y 30 poseen un alto leverage y tienen influencia
sobre la recta de regresión, por lo cual el modelo al no cumplir con lo
supuestos no resulta el mas adecuado. Se podría armar un modelo robusto
para restarle peso a estos los outliers.
Se justifica un
método robusto? si la respuesta es afirmativa, constrúyalo.
modelo_robusto <- lmrob(Sales ~ Age + Income + Price, data = data)
resultados_robustos <- summary(modelo_robusto)
resultados_robustos
##
## Call:
## lmrob(formula = Sales ~ Age + Income + Price, data = data)
## \--> method = "MM"
## Residuals:
## Min 1Q Median 3Q Max
## -42.2830 -5.4653 -0.8096 10.3100 142.5234
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 75.256351 30.357912 2.479 0.016904 *
## Age 2.118118 1.913024 1.107 0.273960
## Income 0.014023 0.003899 3.597 0.000783 ***
## Price -1.870741 0.804314 -2.326 0.024487 *
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Robust residual standard error: 10.86
## (1 observation deleted due to missingness)
## Multiple R-squared: 0.4638, Adjusted R-squared: 0.4288
## Convergence in 19 IRWLS iterations
##
## Robustness weights:
## 4 observations c(9,29,30,34) are outliers with |weight| <= 0.00031 ( < 0.002);
## 6 weights are ~= 1. The remaining 40 ones are summarized as
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.09537 0.86780 0.95850 0.85710 0.98800 0.99780
## Algorithmic parameters:
## tuning.chi bb tuning.psi refine.tol
## 1.548e+00 5.000e-01 4.685e+00 1.000e-07
## rel.tol scale.tol solve.tol eps.outlier
## 1.000e-07 1.000e-10 1.000e-07 2.000e-03
## eps.x warn.limit.reject warn.limit.meanrw
## 9.239e-09 5.000e-01 5.000e-01
## nResample max.it best.r.s k.fast.s k.max
## 500 50 2 1 200
## maxit.scale trace.lev mts compute.rd fast.s.large.n
## 200 0 1000 0 2000
## psi subsampling cov
## "bisquare" "nonsingular" ".vcov.avar1"
## compute.outlier.stats
## "SM"
## seed : int(0)
Si comparamos el error estandar del modelo robusto respecto al modelo
1, se nota como el modelo robusto disminuye el error a 10.86 vs 27.91
del mejor modelo lineal. Resulta la mejor alternativa en este caso, el
modelo robusto.
Sin embargo vemos que el modelo no es adecuado ya que no cumple con
los supuesto de normalidad, una alternativa sería intentar con modelos
parametricos tipo GAMLSS.
Test de
Normalidad
##
## Shapiro-Wilk normality test
##
## data: modelo_robusto$residuals
## W = 0.73152, p-value = 3.188e-08
Ejercicio 3.
A partir de los datos de la base insectos.xlsx con
las variables disponibles: * grupo (Especie) * longitud_pata *
circunf_abdomen * long_antena
Se pretende utilizar estas variables predecir la especie de los
insectos.
Dataset
path <- "C:/Users/ander/Desktop/1. Ciencias de Datos/4. Regresión avanzada/9. Examen 11042023/"
insectos <- read_excel(paste(path,"insectos.xlsx", sep=""))
insectos$grupo <- as.factor(insectos$grupo)
head(insectos)
Ajustar un modelo
logístico para predecir la probabilidad de pertenecer al grupo 1.
Se divide el dataset en test y train para validar las predicciones
del modelo, en este caso se opto por incluir a todas las variables del
excel y evaluar distintas metricas.
# Lanzamos una semilla para que salgan siempre los mismos datos
set.seed(12345)
# Creamos los dataframes
# Generamos una variable aleatoria con una distribución 70-30
insectos$random<-sample(0:1,size = nrow(insectos),replace = T,prob = c(0.3,0.7))
train<-filter(insectos,random==1)
test<-filter(insectos,random==0)
#Eliminamos ya la random para que no moleste
insectos$random <- NULL
train$random <- NULL
test$random <- NULL
rl <- glm(grupo ~ ., data = train, family = "binomial")
summary(rl)
##
## Call:
## glm(formula = grupo ~ ., family = "binomial", data = train)
##
## Deviance Residuals:
## Min 1Q Median 3Q Max
## -1.0268 -0.1683 0.0000 0.0049 2.1294
##
## Coefficients:
## Estimate Std. Error z value Pr(>|z|)
## (Intercept) -58.3885 57.8054 -1.010 0.312
## longitud_pata 0.4925 0.4408 1.117 0.264
## circunf_abdomen -1.2211 0.8600 -1.420 0.156
## long_antena 2.3795 1.6893 1.409 0.159
##
## (Dispersion parameter for binomial family taken to be 1)
##
## Null deviance: 30.7891 on 22 degrees of freedom
## Residual deviance: 8.2266 on 19 degrees of freedom
## AIC: 16.227
##
## Number of Fisher Scoring iterations: 10
pr2_rl <- 1 -(rl$deviance / rl$null.deviance)
pr2_rl
## [1] 0.7328072
Evaluar la calidad de
ajuste del modelo con al menos dos criterios distintos.
Para evaluar el ajuste del modelo se opto por evaluar curva ROC,
especificamente el AUC y la cantidad de aciertos del modelo
(Accuracy).
Para ello, lo primero que hacemos es buscar el punto de corte optimo
que maximice el F1 y el Accuracy, para tener la referencia el umbral
para el pronostico del conjunto de test.
## [1] 0.05
Se observa que el umbral 0.05 es el que maximiza el F1 = 2*
(precisión * cobertura)/ (precisión + cobertura) y la cantidad de
aciertos
Matriz de confusión
y curva ROC
confusion(test$grupo,rl_predict,umbral_final_rl2)
##
## real FALSE TRUE
## 1 6 2
## 2 1 2

El modelo resulta poco adecuado para predecir el grupo de insecto
sobre el conjunto de test, ya que solo logro un AUC de 0.58 pese a que
el accuracy resulto ser del 72%
Métricas
## [,1]
## umbral 0.05000
## acierto 72.72727
## precision 50.00000
## cobertura 66.66667
## F1 57.14286
## AUC 58.00000
Interpretar los
coeficientes del modelo elegido.
La variable longitud_pata y 2.3795 cambia el logaritmo de las
probabilidades de pertecer al grupo 1 a 0.492 en el caso de longitud
_pata mientras que la circunf_abdomen disminuye la probabilidad de
pertecer a este grupo de insecto.
exp(cbind(OR = coef(rl), confint(rl)))
## OR 2.5 % 97.5 %
## (Intercept) 4.387301e-26 3.980216e-111 3599.5237087
## longitud_pata 1.636415e+00 1.089527e+00 8.1556031
## circunf_abdomen 2.949046e-01 1.331519e-02 0.7622868
## long_antena 1.079967e+01 1.389075e+00 3397.7809766
En cuanto al ratio odds podemos decir que, al aumentar longitud_pata
en una unidad, las probabilidades de pertenecer al grupo de insectos 1
aumenta un factor de 1.63 y en el caso de circunf_abdomen este ratio es
de 2.94.
Evaluar la calidad de
clasificación y compararlo con otro método de clasificasión
Se prueba con un arbol de clasificasión
library(tidyverse)
library(rpart)
library(rpart.plot)
library(caret)
library(rpart)
arbol <- rpart(grupo ~ ., train,
xval= 0,
cp= 0, #esto significa no limitar la complejidad de los splits
minsplit= 5, #minima cantidad de registros para que se haga el split
minbucket= 5, #tamaño minimo de una hoja
maxdepth= 5 ) #profundidad maxima del arbol
prediccion_1 <- predict(arbol, newdata = test, type = "prob")
prediccion_2 <- predict(arbol, newdata = test, type = "class")
library(tibble)
library(ggplot2)
library(rpart.plot)
# Arbol generado
rpart.plot(arbol,
# show fitted class, probs, percentages
box.palette = "GnBu", # color scheme
branch.lty = 3, # dotted branch lines
shadow.col = "grey", # shadows under the node boxes
nn = TRUE) # display the node numbers

Métricas
Comparando con el modelo de regresión logistica vemos como el AUC
aumenta si usamos el arbol de clasificasión, sin embargo el Accuracy se
mantiene igual.
confusionMatrix(prediccion_2, test[["grupo"]])
## Confusion Matrix and Statistics
##
## Reference
## Prediction 1 2
## 1 6 1
## 2 2 2
##
## Accuracy : 0.7273
## 95% CI : (0.3903, 0.9398)
## No Information Rate : 0.7273
## P-Value [Acc > NIR] : 0.6491
##
## Kappa : 0.3774
##
## Mcnemar's Test P-Value : 1.0000
##
## Sensitivity : 0.7500
## Specificity : 0.6667
## Pos Pred Value : 0.8571
## Neg Pred Value : 0.5000
## Prevalence : 0.7273
## Detection Rate : 0.5455
## Detection Prevalence : 0.6364
## Balanced Accuracy : 0.7083
##
## 'Positive' Class : 1
##
# loading the package
library(ROCR)
prediobj <-prediction(prediccion_1[,2],test$grupo)
perf <- performance(prediobj, "tpr","fpr")
plot(perf,
main = "Curva ROC",
xlab="Tasa de falsos positivos",
ylab="Tasa de verdaderos positivos")
abline(a=0,b=1,col="blue",lty=2)
grid()
auc <- as.numeric(performance(prediobj,"auc")@y.values)
legend("bottomright",legend=paste(" AUC =",round(auc,4)))

LS0tDQp0aXRsZTogIkV4YW1lbiBGaW5hbCBSZWdyZXNpw7NuIEF2YW56YWRhIg0KYXV0aG9yOiAiQW5kZXJzIEdyYWZmZSINCmRhdGU6ICIxMS8wNC8yMDIzIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogICAgY29kZV9mb2xkaW5nOiBzaG93DQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0aGVtZTogam91cm5hbA0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogeWVzDQogIHdvcmRfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KPGJyPg0KPGJyPg0KDQoNCiMgRWplcmNpY2lvIDENCg0KRWwgRGVwYXJ0YW1lbnRvIGRlIE51dHJpY2nDs24gZGUgbGEgQ2l1ZGFkIGRlc2VhIGVzdHVkaWFyIGVsIGVmZWN0byBkZWwgdGlwbyBkZSBoYXJpbmEgeSBlbCBwb3JjZW50YWplIGRlIGF6w7pjYXIgc29icmUgZWwgZW5kdWx6YW1pZW50byBkZSBsYXMgZ2FsbGV0aXRhcyBxdWUgcHJlcGFyYSBwYXJhIGxvcyBjb21lZG9yZXMgZXNjb2xhcmVzLiBMb3Mgc2lndWllbnRlcyBkYXRvcyBjb3JyZXNwb25kZW4gYSBsYSBkZW5zaWRhZCBlc3BlY8OtZmljYSBkZSBtdWVzdHJhcyBkZSBnYWxsZXRpdGFzIHByZXBhcmFkYXMgY29uDQpsYXMgZGlzdGludGFzIGNvbWJpbmFjaW9uZXMgcG9zaWJsZXMuIFBhcmEgY2FkYSBjb21iaW5hY2nDs24gc2UgcmVhbGl6YXJvbiB0cmVzDQplbnNheW9zIGluZGVwZW5kaWVudGVzLg0KDQphKSBQbGFudGVhciBsYXMgaGlww7N0ZXNpcyBkZSBpbnRlcsOpcy4NCmIpIFJlYWxpemFyIGxhcyBwcnVlYmFzIGNvcnJlc3BvbmRpZW50ZXMuDQpjKSBBbmFsaXphciBsYSB2YWxpZGV6IGRlIGxvcyByZXN1bHRhZG9zIGRlIGxvcyBjb250cmFzdGVzLg0KZCkgQ29uY2x1aXIgZW4gZWwgY29udGV4dG8gZGVsIHByb2JsZW1hIHkgZWZlY3R1YXIgcmVjb21lbmRhY2lvbmVzLg0KDQoNCiMgRWplcmNpY2lvIDINCkVsIHNpZ3VpZW50ZSBjb25qdW50byBkZSBkYXRvcyBfX3ZlbnRhc2NpZ2FycmlsbG9zLnhsc3hfXyBjb3JyZXNwb25kZSBhIGxvcyBkYXRvcyBkZSBsb3MgNTAgZXN0YWRvcyBkZSBFRVVVIHkgbGFzIHZhcmlhYmxlcyByZWdpdHJhZGFzIHNvbiBsYXMgc2lndWllbnRlczoNCg0KKiBBZ2UgRWRhZCBtZWRpYW5hIGRlIGxhcyBwZXJzb25hcyBxdWUgdml2ZW4gZW4gZWwgRXN0YWRvLg0KKiBIUyBQb3JjZW50YWplIGRlIHBlcnNvbmFzIGNvbiAyNSBhw7FvcyBvIG3DoXMgcXVlIHRpZW5lbiBzdSBuaXZlbCBkZSBlZHVjYWNpw7NuIHNlY3VuZGFyaWEgY29uY2x1aWRvLg0KKiBJbmNvbWUgSW5ncmVzbyBwZXJzb25hbCBwZXIgY8OhcGl0YS4NCiogQmxhY2sgUG9yY2VudGFqZSBkZSBpbmRpdmlkdW9zIGRlIHJhemEgbmVncmEuDQoqIEZlbWFsZSBQb3JjZW50YWplIGRlIG11amVyZXMuDQoqIFByaWNlIFByZWNpbyBwcm9tZWRpbyBkZWwgcGFxdWV0ZSBkZSBjaWdhcnJpbGxvcy4NCiogU2FsZXMgTsO6bWVybyBkZSBwYXF1ZXRlcyBwcm9tZWRpbyB2ZW5kaWRvIHBlciBjw6FwaXRhLg0KDQpTZSByZXF1aWVyZSBjb25zdHJ1aXIgdW5hIGVjdWFjacOzbiBkZSByZWdyZXNpw7NuIHF1ZSB2aW5jdWxlIGVsIHByb21lZGlvIGRlDQpwYXF1ZXRlcyB2ZW5kaWRvcyBkZSBjaWdhcnJpbGxvcyBwZXIgY8OhcGl0YSAoU2FsZXMpIGNvbiBsYXMgZGl2ZXJzYXMgdmFyaWFibGVzDQplY29uw7NtaWNhcyB5IHNvY2lvIGRlbW9ncsOhZmljYXMgZGlzcG9uaWJsZXMgZW4gbGEgYmFzc2UgKGVuIGNhc28gcXVlIHNlYQ0Kw7p0aWxlcyBwYXJhIHByZWRlY2lyIGEgbGEgcmVzcHVlc3RhIGRlIGludGVyw6lzKS4gUGFyYSBlbGxvIHN1Z2VyaW1vcyB0ZW5lciBlbg0KY29uc2lkZXJhY2nDs24gbG9zIHNpZ3VpZW50ZXMgbGluZWFtaWVudG9zOg0KDQoNCg0KDQojIyBEYXRhc2V0DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQoNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWJibGUpDQpsaWJyYXJ5KHJlYWR4bCkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KEdHYWxseSkNCmxpYnJhcnkoSG1pc2MpDQpsaWJyYXJ5KGNvcnJwbG90KQ0KbGlicmFyeShQZXJmb3JtYW5jZUFuYWx5dGljcykNCmxpYnJhcnkocm9idXN0YmFzZSk7IGxpYnJhcnkoTUFTUykNCmxpYnJhcnkob2xzcnIpOyBsaWJyYXJ5KGNhcik7IGxpYnJhcnkocXVhbnRyZWcpDQoNCmRhdGEgPC0gcmVhZC5jc3YoIkM6L1VzZXJzL2FuZGVyL0Rlc2t0b3AvMS4gQ2llbmNpYXMgZGUgRGF0b3MvNC4gUmVncmVzacOzbiBhdmFuemFkYS85LiBFeGFtZW4gMTEwNDIwMjMvdmVudGFzY2lnYXJyaWxsb3MuY3N2Iiwgc2VwID0iOyIpDQoNCmhlYWQoZGF0YSkNCg0KYGBgDQoNCiMjIFF1w6kgbml2ZWwgZGUgYXNvY2lhY2nDs24gbGluZWFsIHByZXNlbnRhbiBlc3RhcyB2YXJpYWJsZXMgY29uIGxhIHZhcmlhYmxlIHJlc3B1ZXN0YSBTYWxlcz8NCg0KVW4gcHJpbWVyIGFjZXJjYW1pZW50byBwYXJhIGVzdHVkaWFyIGVsIG5pdmVsIGRlIGFzb2NpYWNpw7NuIGxpbmVhbCBlbnRyZSBsYXMgdmFyaWFibGVzLCBwdWVkZSBzZXIgYSB0cmF2w6lzIGRlIGxhIG1hdHJpeiBkZSBjb3JyZWxhY2nDs24uIEVuIGVsbGEgc2Ugb2JzZXJ2YSBjb21vIF9fU2FsZXNfXyB0aWVuZSB1bmEgY29ycmVsYWNpw7NuIG5lZ2F0aXZhIHkgc2lnbmlmaWNhdGl2YSBkZSAtMC4zMCBjb24gX19QcmljZV9fIHkgcG9zaXRpdmEgY29uIF9fSW5jb21lX18gZGUgdW4gMC4zMy4NCg0KUGFyYSB1biBwb3NpYmxlIG1vZGVsbyBsaW5lYWwgbXVsdGlwbGUgZXN0YXMgc2VyaWFuIGxhcyB2YXJpYWJsZXMgcXVlIG1lam9yIGV4cGxpY2FyaWFuIGEgX19TYWxlc19fDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpkYXRhIDwtZGF0YVssLWMoMSldDQpjaGFydC5Db3JyZWxhdGlvbihkYXRhLCBoaXN0b2dyYW0gPSBGKQ0KDQpgYGANCg0KIyMgVXRpbGl6YW5kbyB1biBtw6l0b2RvIGRlIHNlbGVjY2nDs24gYXV0b23DoXRpY2EgZWxlZ2lyIGVsIG51bWVybyBkZSB2YXJpYWJsZXMgeSBjdcOhbGVzIHNvbiBwYXJhIGNvbnN0cnVpciB1biBtb2RlbG8gZW4gZWwgcXVlIG5vIHNlIGNvbnNpZGVyZSBpbnRlcmFjY2nDs24uDQoNClNlIHV0aWxpemEgdW5hIHZhbGlkYWNpw7NuIGNvbWJpbmFkYSBkZSBtb2RlbG9zLCBlbiBkb25kZSBzZSBldmFsdWFuIGRpY2hvcyBtb2RlbG9zIGVuIGZ1bmNpw7NuIGRlIGxhIGNhbnRpZGFkIGRlIHJlZ3Jlc29yZXMgeSBzZSBlbGlnZSBlbCBtZWpvciBwb3IgZWwgY3JpdGVyaW8gZGUgQUlDLg0KDQpgYGB7cn0NCmxpYnJhcnkob2xzcnIpDQpsbSA8LSBsbShTYWxlc34gLiwgZGF0YT1kYXRhKQ0Ka19iZXN0IDwtIG9sc19zdGVwX2Jlc3Rfc3Vic2V0KGxtKQ0Ka19iZXN0DQpgYGANCkVsIG1lam9yIG1vZGVsbyByZXN1bHRvIHNlciBlbCB0ZXJjZXJvIHlhIHF1ZSBjdWVudGEgY29uIGVsIG1heW9yIFIyIGFkaiB5IGVsIG1lbm9yIEFJQy4gUG9yIGxvIHF1ZSBxdWVyYXLDrWE6DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQoNCm1vZGVsbzEgPC0gbG0oU2FsZXN+QWdlICsgSW5jb21lICsgUHJpY2UsIGRhdGE9ZGF0YSkNCnN1bW1hcnkobW9kZWxvMSkNCg0KYGBgDQpTaSBiaWVuIHJlc3VsdG8gZWwgbWVqb3IgbW9kZWxvIHJlc3BlY3RvIGEgbGFzIHZhcmlhYmxlcyBkaXNwb25pYmxlcywgZWwgUjIgc29sbyBsb2dyYSBleHBsaWNhciB1biAzMCUgZGUgbGEgdmFyaWFjacOzbiBkZSBsYSB2YXJpYWJsZSBTYWxlcy4gTGFzIHZhcmlhYmxlcyBtYXMgc2lnbmlmaWNhdGl2YXMgZGVudHJvIGRlbCBtb2RlbG8gcmVzdWx0YW4gc2VyIEluY29tZSB5IFByaWNlIGN1eW8gcC12YWx1ZSBlcyBtZW5vciBhbCBuaXZlbCBkZSBjb25maWFuemEgZGVsIDAuMDUuDQoNCiMjIEFncmVndWUgZW4gY2FzbyBkZSBxdWUgdGVuZ2Egc2VudGlkbyBhbGd1bmEgaW50ZXJhY2Npw7NuIGFsIG1vZGVsby4NCg0KQWwgYWdyZWdhciB1bmEgaW50ZXJhY2Npw7NuIGVudHJlIGxhcyB2YXJpYWJsZXMgX19GZW1hbGVfXyB5IF9fUHJpY2VfXyBzZSB2ZSBjb21vIG1lam9yYSBlbCBSMiBhZGogeSBlc3RhIGludGVyYWNjaW9uIHJlc3VsdGEgc2lnbmlmaWNhdGl2YSBwYXJhIGVsIG1vZGVsbw0KDQpgYGB7cn0NCg0KbW9kZWxvMiA8LSBsbShTYWxlcyB+IEFnZSArIFByaWNlKkZlbWFsZSArIEluY29tZSwgZGF0YT1kYXRhKQ0Kc3VtbWFyeShtb2RlbG8yKQ0KDQpgYGANCiMjIEFuYWxpY2UgbGEgcHJlc2VuY2lhIGRlIG11bHRpY29saW5lYWxpZGFkLiBTaSBsYSByZXNwdWVzdGEgZXMgYWZpcmFtdGl2YSwgY8OzbW8gbG8gc29sdWNpb25hcsOtYT8NCg0KIyMjIEFuw6FsaXNpcyBkZSBtdWx0aWNvbGluaWFsaWRhZCBhIHRyYXbDqXMgZGVsIFZJRg0KDQo8YnI+DQoNClNpIHNlIGV2YWx1YSBlbCBtb2RlbG8gMiwgdmVtb3MgcXVlIGVsIGluZGljYWRvciB2aWYgZXMgbXV5IGFsdG8gZW4gbGFzIHZhcmlhYmxlcyBQcmljZSB5IEZlbWFsZSwgYXNpIGNvbW8gdGFtYmnDqW4gbGEgaW50ZXJhY2Npw7NuIGVudHJlIGFtYmFzLiBQb3IgbG8gcXVlLCBwb3Igc2ltcGxpY2lkYWQgZGVsIG1vZGVsbyBwb2RlbW9zIG9wdGFyIHBvciBlbCBtb2RlbG8gc2luIGludGVyYWNjacOzbiAobW9kZWxvIDEpLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBlY2hvPUZBTFNFfQ0KDQpwYXIobWZyb3c9YygyLDEpKQ0KDQp2aWYobW9kZWxvMikNCnZpZl92YWx1ZXMgPC0gdmlmKG1vZGVsbzIpDQoNCnZpZl9kZj1kYXRhLmZyYW1lKHZhcmlhYmxlcz1jKCJBZ2UiICwgIlByaWNlIiwiRmVtYWxlIiwiSW5jb21lIiwgIlByaWNlOkZlbWFsZSIpLHZpZl92YWx1ZXMpDQpiYXJwbG90KHZpZl92YWx1ZXMsIG1haW4gPSAiVklGIE1vZGVsbyAyIiwgY29sID0gInNreWJsdWUiLHlsaW0gPWMoMC4wLDIwKSkNCmFibGluZShoID0gNSwgbHdkID0gMiwgbHR5ID0iZG90dGVkIixjb2wgPSAiYmx1ZSIpDQoNCg0KdmlmKG1vZGVsbzEpDQp2aWZfdmFsdWVzIDwtIHZpZihtb2RlbG8xKQ0KdmlmX2RmPWRhdGEuZnJhbWUodmFyaWFibGVzPWMoIkFnZSIgLCAiUHJpY2UiLCJJbmNvbWUiKSx2aWZfdmFsdWVzKQ0KDQpiYXJwbG90KHZpZl92YWx1ZXMsIG1haW4gPSAiVklGIE1vZGVsbyAxIiwgY29sID0gInNreWJsdWUiLHlsaW0gPWMoMC4wLDIwKSkNCmFibGluZShoID0gNSwgbHdkID0gMiwgbHR5ID0iZG90dGVkIixjb2wgPSAiYmx1ZSIpDQoNCmBgYA0KPGJyPg0KDQojIyBBbmFsaWNlIGxvcyByZXNpZHVvcyBlIGluZGlxdWUgc2kgYXByZWNpYSBsYSBwcmVzZW5jaWEgZGUgb3V0bGllcnMgby95IG9ic2VydmFjaW9uZXMgaW5mbHV5ZW50ZXMuDQoNCiMjIyBBbsOhbGlzaXMgZ3LDoWZpY28gZGUgbm9ybWFsaWRhZA0KDQpTZWfDum4gZWwgYW7DoWxpc2lzIGRlbCBRUXBsb3QgYSBzaW1wbGUgdmlzdGEgc2Ugb2JzZXJ2YSBmYWx0YSBkZSBub3JtYWxpZGFkIGVuIGxvcyByZXNpZHVvcyBwb3IgcHJlc2VuY2lhIGRlIG91dGxpZXJzLCBlc3BlY2lmaWNhbWVudGUgZWwgcmVnaXN0cm8gMjkgeSAzMCBzZSBtYXJjYW4gY29uIHZhbG9yZXMgYXTDrXBpY29zLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBlY2hvPUZBTFNFfQ0KDQpxcVBsb3QobW9kZWxvMSRyZXNpZHVhbHMsIHBjaD0xOSwNCm1haW49IlFRcGxvdCBwYXJhIGxvcyByZXNpZHVvcyBkZWwgTW9kZWxvIExpbmVhbCIsDQp4bGFiPSJDdWFudGlsZXMgdGVvcmljb3MiLA0KeWxhYj0iQ3VhbnRpbGVzIG11ZXN0cmFsZXMiKQ0KDQpgYGANCg0KPGJyPg0KDQoNCiMjIyBBbsOhbGlzaXMgZGUgUHVudG9zIGluZmx1eWVudGVzDQoNCiMjIyMgR3LDoWZpY28gZGUgZGlzdGFuY2lhIENvb2sNCg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBlY2hvPUZBTFNFfQ0KDQppbmZsdWVuY2VJbmRleFBsb3QobW9kZWxvMSwgdmFycz0iQ29vayIgLCBsYXM9MSxjb2w9ImJsdWUiKQ0KDQpgYGANCjxicj4NCg0KIyMjIyBPdHJvcyBwdW50b3MgaW5mbHV5ZW50ZXMNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1GQUxTRX0NCg0Kc3VtbWFyeShpbmZsdWVuY2UubWVhc3VyZXMobW9kZWwgPSBtb2RlbG8xKSkNCg0KYGBgDQpMYSBvYnNlcnZhY2nDs24gOSwgMjkgeSAzMCBwb3NlZW4gdW4gYWx0byBsZXZlcmFnZSB5IHRpZW5lbiBpbmZsdWVuY2lhIHNvYnJlIGxhIHJlY3RhIGRlIHJlZ3Jlc2nDs24sIHBvciBsbyBjdWFsIGVsIG1vZGVsbyBhbCBubyBjdW1wbGlyIGNvbiBsbyBzdXB1ZXN0b3Mgbm8gcmVzdWx0YSBlbCBtYXMgYWRlY3VhZG8uIFNlIHBvZHLDrWEgYXJtYXIgdW4gbW9kZWxvIHJvYnVzdG8gcGFyYSByZXN0YXJsZSBwZXNvIGEgZXN0b3MgbG9zIG91dGxpZXJzLg0KDQo8YnI+DQoNCiMjIFNlIGp1c3RpZmljYSB1biBtw6l0b2RvIHJvYnVzdG8/IHNpIGxhIHJlc3B1ZXN0YSBlcyBhZmlybWF0aXZhLCBjb25zdHLDunlhbG8uDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQptb2RlbG9fcm9idXN0byA8LSBsbXJvYihTYWxlcyB+IEFnZSArIEluY29tZSArIFByaWNlLCBkYXRhID0gZGF0YSkNCnJlc3VsdGFkb3Nfcm9idXN0b3MgPC0gc3VtbWFyeShtb2RlbG9fcm9idXN0bykNCnJlc3VsdGFkb3Nfcm9idXN0b3MNCg0KYGBgDQpTaSBjb21wYXJhbW9zIGVsIGVycm9yIGVzdGFuZGFyIGRlbCBtb2RlbG8gcm9idXN0byByZXNwZWN0byBhbCBtb2RlbG8gMSwgc2Ugbm90YSBjb21vIGVsIG1vZGVsbyByb2J1c3RvIGRpc21pbnV5ZSBlbCBlcnJvciBhIDEwLjg2IHZzIDI3LjkxIGRlbCBtZWpvciBtb2RlbG8gbGluZWFsLiBSZXN1bHRhIGxhIG1lam9yIGFsdGVybmF0aXZhIGVuIGVzdGUgY2FzbywgZWwgbW9kZWxvIHJvYnVzdG8uDQoNClNpbiBlbWJhcmdvIHZlbW9zIHF1ZSBlbCBtb2RlbG8gbm8gZXMgYWRlY3VhZG8geWEgcXVlIG5vIGN1bXBsZSBjb24gbG9zIHN1cHVlc3RvIGRlIG5vcm1hbGlkYWQsIHVuYSBhbHRlcm5hdGl2YSBzZXLDrWEgaW50ZW50YXIgY29uIG1vZGVsb3MgcGFyYW1ldHJpY29zIHRpcG8gR0FNTFNTLg0KDQo8YnI+DQoNCg0KIyMjIFRlc3QgZGUgTm9ybWFsaWRhZA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBlY2hvPUZBTFNFfQ0KDQpzaGFwaXJvLnRlc3QobW9kZWxvX3JvYnVzdG8kcmVzaWR1YWxzKQ0KDQpgYGANCjxicj4NCg0KDQojIEVqZXJjaWNpbyAzLiANCg0KQSBwYXJ0aXIgZGUgbG9zIGRhdG9zIGRlIGxhIGJhc2UgX19pbnNlY3Rvcy54bHN4X18gY29uIGxhcyB2YXJpYWJsZXMgZGlzcG9uaWJsZXM6DQoqIGdydXBvIChFc3BlY2llKQ0KKiBsb25naXR1ZF9wYXRhDQoqIGNpcmN1bmZfYWJkb21lbg0KKiBsb25nX2FudGVuYQ0KDQpTZSBwcmV0ZW5kZSB1dGlsaXphciBlc3RhcyB2YXJpYWJsZXMgcHJlZGVjaXIgbGEgZXNwZWNpZSBkZSBsb3MgaW5zZWN0b3MuDQoNCiMjIERhdGFzZXQNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KcGF0aCA8LSAiQzovVXNlcnMvYW5kZXIvRGVza3RvcC8xLiBDaWVuY2lhcyBkZSBEYXRvcy80LiBSZWdyZXNpw7NuIGF2YW56YWRhLzkuIEV4YW1lbiAxMTA0MjAyMy8iDQppbnNlY3RvcyA8LSByZWFkX2V4Y2VsKHBhc3RlKHBhdGgsImluc2VjdG9zLnhsc3giLCBzZXA9IiIpKQ0KaW5zZWN0b3MkZ3J1cG8gPC0gYXMuZmFjdG9yKGluc2VjdG9zJGdydXBvKQ0KDQpoZWFkKGluc2VjdG9zKQ0KDQpgYGANCg0KDQojIyBBanVzdGFyIHVuIG1vZGVsbyBsb2fDrXN0aWNvIHBhcmEgcHJlZGVjaXIgbGEgcHJvYmFiaWxpZGFkIGRlIHBlcnRlbmVjZXIgYWwgZ3J1cG8gMS4NCg0KYGBge3Igd2FybmluZz1GQUxTRSwgZWNobz1GQUxTRX0NCg0KbGlicmFyeShST0NSKQ0KDQpjb25mdXNpb248LWZ1bmN0aW9uKHJlYWwsc2NvcmluZyx1bWJyYWwpeyANCiAgY29uZjwtdGFibGUocmVhbCxzY29yaW5nPj11bWJyYWwpDQogIGlmKG5jb2woY29uZik9PTIpIHJldHVybihjb25mKSBlbHNlIHJldHVybihOVUxMKQ0KfQ0KDQoNCg0KbWV0cmljYXM8LWZ1bmN0aW9uKG1hdHJpel9jb25mKXsNCiAgYWNpZXJ0byA8LSAobWF0cml6X2NvbmZbMSwxXSArIG1hdHJpel9jb25mWzIsMl0pIC8gc3VtKG1hdHJpel9jb25mKSAqMTAwDQogIHByZWNpc2lvbiA8LSBtYXRyaXpfY29uZlsyLDJdIC8gKG1hdHJpel9jb25mWzIsMl0gKyBtYXRyaXpfY29uZlsxLDJdKSAqMTAwDQogIGNvYmVydHVyYSA8LSBtYXRyaXpfY29uZlsyLDJdIC8gKG1hdHJpel9jb25mWzIsMl0gKyBtYXRyaXpfY29uZlsyLDFdKSAqMTAwDQogIEYxIDwtIDIqcHJlY2lzaW9uKmNvYmVydHVyYS8ocHJlY2lzaW9uK2NvYmVydHVyYSkNCiAgc2FsaWRhPC1jKGFjaWVydG8scHJlY2lzaW9uLGNvYmVydHVyYSxGMSkNCiAgcmV0dXJuKHNhbGlkYSkNCn0NCg0KdW1icmFsZXM8LWZ1bmN0aW9uKHJlYWwsc2NvcmluZyl7DQogIHVtYnJhbGVzPC1kYXRhLmZyYW1lKHVtYnJhbD1yZXAoMCx0aW1lcz0xOSksYWNpZXJ0bz1yZXAoMCx0aW1lcz0xOSkscHJlY2lzaW9uPXJlcCgwLHRpbWVzPTE5KSxjb2JlcnR1cmE9cmVwKDAsdGltZXM9MTkpLEYxPXJlcCgwLHRpbWVzPTE5KSkNCiAgY29udCA8LSAxDQogIGZvciAoY2FkYSBpbiBzZXEoMC4wNSwwLjk1LGJ5ID0gMC4wNSkpew0KICAgIGRhdG9zPC1tZXRyaWNhcyhjb25mdXNpb24ocmVhbCxzY29yaW5nLGNhZGEpKQ0KICAgIHJlZ2lzdHJvPC1jKGNhZGEsZGF0b3MpDQogICAgdW1icmFsZXNbY29udCxdPC1yZWdpc3Rybw0KICAgIGNvbnQgPC0gY29udCArIDENCiAgfQ0KICByZXR1cm4odW1icmFsZXMpDQp9DQoNCnJvYzwtZnVuY3Rpb24ocHJlZGljdGlvbil7DQogIHI8LXBlcmZvcm1hbmNlKHByZWRpY3Rpb24sJ3RwcicsJ2ZwcicpDQogIHBsb3QocikNCn0NCg0KI0Z1bmNpb25lcyBwYXJhIGNhbGN1bGFyIGxhIGN1cnZhIFJPQyB5IGVsIEFVQw0KYXVjPC1mdW5jdGlvbihwcmVkaWN0aW9uKXsNCiAgYTwtcGVyZm9ybWFuY2UocHJlZGljdGlvbiwnYXVjJykNCiAgcmV0dXJuKGFAeS52YWx1ZXNbWzFdXSkNCn0NCmBgYA0KDQoNClNlIGRpdmlkZSBlbCBkYXRhc2V0IGVuIHRlc3QgeSB0cmFpbiBwYXJhIHZhbGlkYXIgbGFzIHByZWRpY2Npb25lcyBkZWwgbW9kZWxvLCBlbiBlc3RlIGNhc28gc2Ugb3B0byBwb3IgaW5jbHVpciBhIHRvZGFzIGxhcyB2YXJpYWJsZXMgZGVsIGV4Y2VsIHkgZXZhbHVhciBkaXN0aW50YXMgbWV0cmljYXMuDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQoNCiMgTGFuemFtb3MgdW5hIHNlbWlsbGEgcGFyYSBxdWUgc2FsZ2FuIHNpZW1wcmUgbG9zIG1pc21vcyBkYXRvcw0Kc2V0LnNlZWQoMTIzNDUpDQoNCiMgQ3JlYW1vcyBsb3MgZGF0YWZyYW1lcw0KDQojIEdlbmVyYW1vcyB1bmEgdmFyaWFibGUgYWxlYXRvcmlhIGNvbiB1bmEgZGlzdHJpYnVjacOzbiA3MC0zMA0KaW5zZWN0b3MkcmFuZG9tPC1zYW1wbGUoMDoxLHNpemUgPSBucm93KGluc2VjdG9zKSxyZXBsYWNlID0gVCxwcm9iID0gYygwLjMsMC43KSkgDQoNCnRyYWluPC1maWx0ZXIoaW5zZWN0b3MscmFuZG9tPT0xKQ0KdGVzdDwtZmlsdGVyKGluc2VjdG9zLHJhbmRvbT09MCkNCg0KI0VsaW1pbmFtb3MgeWEgbGEgcmFuZG9tIHBhcmEgcXVlIG5vIG1vbGVzdGUNCmluc2VjdG9zJHJhbmRvbSA8LSBOVUxMDQp0cmFpbiRyYW5kb20gPC0gTlVMTA0KdGVzdCRyYW5kb20gPC0gTlVMTA0KDQpybCA8LSBnbG0oZ3J1cG8gfiAuLCBkYXRhID0gdHJhaW4sIGZhbWlseSA9ICJiaW5vbWlhbCIpDQpzdW1tYXJ5KHJsKQ0KDQoNCg0KcHIyX3JsIDwtIDEgLShybCRkZXZpYW5jZSAvIHJsJG51bGwuZGV2aWFuY2UpDQpwcjJfcmwNCg0KYGBgDQoNCiMjIEV2YWx1YXIgbGEgY2FsaWRhZCBkZSBhanVzdGUgZGVsIG1vZGVsbyBjb24gYWwgbWVub3MgZG9zIGNyaXRlcmlvcyBkaXN0aW50b3MuDQoNClBhcmEgZXZhbHVhciBlbCBhanVzdGUgZGVsIG1vZGVsbyBzZSBvcHRvIHBvciBldmFsdWFyIGN1cnZhIFJPQywgZXNwZWNpZmljYW1lbnRlIGVsIEFVQyB5IGxhIGNhbnRpZGFkIGRlIGFjaWVydG9zIGRlbCBtb2RlbG8gKEFjY3VyYWN5KS4NCg0KUGFyYSBlbGxvLCBsbyBwcmltZXJvIHF1ZSBoYWNlbW9zIGVzIGJ1c2NhciBlbCBwdW50byBkZSBjb3J0ZSBvcHRpbW8gcXVlIG1heGltaWNlIGVsIEYxIHkgZWwgQWNjdXJhY3ksIHBhcmEgdGVuZXIgbGEgcmVmZXJlbmNpYSBlbCB1bWJyYWwgcGFyYSBlbCBwcm9ub3N0aWNvIGRlbCBjb25qdW50byBkZSB0ZXN0Lg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBlY2hvPUZBTFNFfQ0KDQpybF9wcmVkaWN0PC1wcmVkaWN0KHJsLHRlc3QsdHlwZSA9ICdyZXNwb25zZScpDQoNCnVtYl9ybDI8LXVtYnJhbGVzKHRlc3QkZ3J1cG8scmxfcHJlZGljdCkNCnVtYl9ybDINCg0KdW1icmFsX2ZpbmFsX3JsMjwtdW1iX3JsMlt3aGljaC5tYXgodW1iX3JsMiRGMSksMV0NCnVtYnJhbF9maW5hbF9ybDINCmBgYA0KU2Ugb2JzZXJ2YSBxdWUgZWwgdW1icmFsIDAuMDUgZXMgZWwgcXVlIG1heGltaXphIGVsIEYxID0gMiogKHByZWNpc2nDs24gKiBjb2JlcnR1cmEpLyAocHJlY2lzacOzbiArIGNvYmVydHVyYSkgeSBsYSBjYW50aWRhZCBkZSBhY2llcnRvcw0KDQo8YnI+DQoNCiMjIyBNYXRyaXogZGUgY29uZnVzacOzbiB5IGN1cnZhIFJPQw0KDQpgYGB7cn0NCmNvbmZ1c2lvbih0ZXN0JGdydXBvLHJsX3ByZWRpY3QsdW1icmFsX2ZpbmFsX3JsMikNCmBgYA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1GQUxTRX0NCiNjcmVhbW9zIGVsIG9iamV0byBwcmVkaWN0aW9uDQpybDJfcHJlZGljdGlvbjwtcHJlZGljdGlvbihybF9wcmVkaWN0LHRlc3QkZ3J1cG8pDQojdmlzdWFsaXphbW9zIGxhIFJPQw0Kcm9jKHJsMl9wcmVkaWN0aW9uKQ0KYGBgDQoNCjxicj4NCg0KRWwgbW9kZWxvIHJlc3VsdGEgcG9jbyBhZGVjdWFkbyBwYXJhIHByZWRlY2lyIGVsIGdydXBvIGRlIGluc2VjdG8gc29icmUgZWwgY29uanVudG8gZGUgdGVzdCwgeWEgcXVlIHNvbG8gbG9ncm8gdW4gQVVDIGRlIDAuNTggcGVzZSBhIHF1ZSBlbCBhY2N1cmFjeSByZXN1bHRvIHNlciBkZWwgNzIlDQoNCjxicj4NCg0KIyMjIE3DqXRyaWNhcw0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZWNobz1GQUxTRX0NCnJsMl9tZXRyaWNhczwtZmlsdGVyKHVtYl9ybDIsdW1icmFsPT11bWJyYWxfZmluYWxfcmwyKQ0KDQpybDJfbWV0cmljYXM8LWNiaW5kKHJsMl9tZXRyaWNhcyxBVUM9cm91bmQoYXVjKHJsMl9wcmVkaWN0aW9uKSwyKSoxMDApDQpwcmludCh0KHJsMl9tZXRyaWNhcykpDQpgYGANCg0KPGJyPg0KDQojIyBJbnRlcnByZXRhciBsb3MgY29lZmljaWVudGVzIGRlbCBtb2RlbG8gZWxlZ2lkby4NCg0KTGEgdmFyaWFibGUgbG9uZ2l0dWRfcGF0YSB5IDIuMzc5NSBjYW1iaWEgZWwgbG9nYXJpdG1vIGRlIGxhcyBwcm9iYWJpbGlkYWRlcyBkZSBwZXJ0ZWNlciBhbCBncnVwbyAxICBhIDAuNDkyIGVuIGVsIGNhc28gZGUgbG9uZ2l0dWQgX3BhdGEgbWllbnRyYXMgcXVlIGxhIGNpcmN1bmZfYWJkb21lbiBkaXNtaW51eWUgbGEgcHJvYmFiaWxpZGFkIGRlIHBlcnRlY2VyIGEgZXN0ZSBncnVwbyBkZSBpbnNlY3RvLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQpleHAoY2JpbmQoT1IgPSBjb2VmKHJsKSwgY29uZmludChybCkpKQ0KDQpgYGANCg0KRW4gY3VhbnRvIGFsIHJhdGlvIG9kZHMgcG9kZW1vcyBkZWNpciBxdWUsIGFsIGF1bWVudGFyIGxvbmdpdHVkX3BhdGEgZW4gdW5hIHVuaWRhZCwgbGFzIHByb2JhYmlsaWRhZGVzIGRlIHBlcnRlbmVjZXIgYWwgZ3J1cG8gZGUgaW5zZWN0b3MgMSBhdW1lbnRhIHVuIGZhY3RvciBkZSAxLjYzIHkgZW4gZWwgY2FzbyBkZSBjaXJjdW5mX2FiZG9tZW4gZXN0ZSByYXRpbyBlcyBkZSAyLjk0Lg0KDQojIyBFdmFsdWFyIGxhIGNhbGlkYWQgZGUgY2xhc2lmaWNhY2nDs24geSBjb21wYXJhcmxvIGNvbiBvdHJvIG3DqXRvZG8gZGUgY2xhc2lmaWNhc2nDs24NCg0KU2UgcHJ1ZWJhIGNvbiB1biBhcmJvbCBkZSBjbGFzaWZpY2FzacOzbg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkocnBhcnQpDQphcmJvbCA8LSBycGFydChncnVwbyB+IC4sIHRyYWluLA0KICAgICAgICAgICAgICAgeHZhbD0gICAgICAwLA0KICAgICAgICAgICAgIGNwPSAgICAgICAgMCwgICAjZXN0byBzaWduaWZpY2Egbm8gbGltaXRhciBsYSBjb21wbGVqaWRhZCBkZSBsb3Mgc3BsaXRzDQogICAgICAgICAgICAgbWluc3BsaXQ9ICA1LCAgICAgI21pbmltYSBjYW50aWRhZCBkZSByZWdpc3Ryb3MgcGFyYSBxdWUgc2UgaGFnYSBlbCBzcGxpdA0KICAgICAgICAgICAgIG1pbmJ1Y2tldD0gNSwgICAgICN0YW1hw7FvIG1pbmltbyBkZSB1bmEgaG9qYQ0KICAgICAgICAgICAgIG1heGRlcHRoPSAgNSApICAgICNwcm9mdW5kaWRhZCBtYXhpbWEgZGVsIGFyYm9sDQoNCg0KcHJlZGljY2lvbl8xIDwtIHByZWRpY3QoYXJib2wsIG5ld2RhdGEgPSB0ZXN0LCB0eXBlID0gInByb2IiKQ0KcHJlZGljY2lvbl8yIDwtIHByZWRpY3QoYXJib2wsIG5ld2RhdGEgPSB0ZXN0LCB0eXBlID0gImNsYXNzIikNCmBgYA0KDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpYmJsZSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkocnBhcnQucGxvdCkNCg0KIyAgQXJib2wgZ2VuZXJhZG8NCg0KcnBhcnQucGxvdChhcmJvbCwgDQogICAgICAgICAgICAgICAgICAgICMgc2hvdyBmaXR0ZWQgY2xhc3MsIHByb2JzLCBwZXJjZW50YWdlcw0KICAgICAgICAgICBib3gucGFsZXR0ZSA9ICJHbkJ1IiwgIyBjb2xvciBzY2hlbWUNCiAgICAgICAgICAgYnJhbmNoLmx0eSA9IDMsICAgICAgICMgZG90dGVkIGJyYW5jaCBsaW5lcw0KICAgICAgICAgICBzaGFkb3cuY29sID0gImdyZXkiLCAgIyBzaGFkb3dzIHVuZGVyIHRoZSBub2RlIGJveGVzDQogICAgICAgICAgIG5uID0gVFJVRSkgICAgICAgICAgICAjIGRpc3BsYXkgdGhlIG5vZGUgbnVtYmVycyANCmBgYA0KDQoNCiMjIyBNw6l0cmljYXMNCg0KQ29tcGFyYW5kbyBjb24gZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbG9naXN0aWNhIHZlbW9zIGNvbW8gZWwgQVVDIGF1bWVudGEgc2kgdXNhbW9zIGVsIGFyYm9sIGRlIGNsYXNpZmljYXNpw7NuLCBzaW4gZW1iYXJnbyBlbCBBY2N1cmFjeSBzZSBtYW50aWVuZSBpZ3VhbC4NCg0KYGBge3J9DQoNCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWNjaW9uXzIsIHRlc3RbWyJncnVwbyJdXSkNCmBgYA0KDQpgYGB7cn0NCiMgbG9hZGluZyB0aGUgcGFja2FnZQ0KbGlicmFyeShST0NSKQ0KDQoNCnByZWRpb2JqIDwtcHJlZGljdGlvbihwcmVkaWNjaW9uXzFbLDJdLHRlc3QkZ3J1cG8pDQpwZXJmIDwtICBwZXJmb3JtYW5jZShwcmVkaW9iaiwgInRwciIsImZwciIpDQoNCnBsb3QocGVyZiwNCiAgICAgbWFpbiA9ICJDdXJ2YSBST0MiLA0KICAgICB4bGFiPSJUYXNhIGRlIGZhbHNvcyBwb3NpdGl2b3MiLCANCiAgICAgeWxhYj0iVGFzYSBkZSB2ZXJkYWRlcm9zIHBvc2l0aXZvcyIpDQphYmxpbmUoYT0wLGI9MSxjb2w9ImJsdWUiLGx0eT0yKQ0KZ3JpZCgpDQphdWMgPC0gYXMubnVtZXJpYyhwZXJmb3JtYW5jZShwcmVkaW9iaiwiYXVjIilAeS52YWx1ZXMpDQpsZWdlbmQoImJvdHRvbXJpZ2h0IixsZWdlbmQ9cGFzdGUoIiBBVUMgPSIscm91bmQoYXVjLDQpKSkNCg0KYGBgDQoNCg==