Informe Técnicas Estadísticas en Análisis de Mercados

1 Trabajo Técnicas Estadísticas en Análisis de Mercados

1.1 Planteaminto del problema

En el siguiente informe estamos ante un caso práctico en el cual necesitamos analizar y predecir el precio de cualquier automóvil en función de distintas cacterísticas principales que se presentan. Para el caso, hemos supuesto que trabajamos para una gran compraventa de coches que opera en todo el territorio nacional español. Nos han pedido que para facilitar el trabajo a los tasadores y ahorrar una gran coste salarial debemos crear un algoritmo que dadas unas características sobre el coche, lo clasifique en dos grupos según si este se encuentra por debajo o por encima de la media (18337.6€). Para el algoritmo hemos creado una base de datos con 340 coches de la web “Coches.net” gracias a las técnicas de Web-Scraping, la cual contiene la siguiente información:

  • Cambio: Manual, Automático ; Variable Factor
  • Coche: Marcha de Coche (Audi, BMW, Seat, etc) ; Variable Factor
  • Modelo: Modelo de Coche (Qashqai, Q5, GLC, etc) ; Variable Factor
  • Seguridad: 1 (Seguridad máxima) 2 (Seguridad aceptable) 3(Seguridad mala); Variable Factor
  • Caballos: Caballos de Vapor del coche; Variable Numérica
  • Edad: Edad del Coche desde que se matriculó; Variable Numérica
  • Tipo_Fuel: A (Gasolina) B (Diésel) C (Eléctrico) D (Gas); Variable Factor
  • Kilometros: Kilometraje del automóvil en el momento de la venta; Variable Numérica
  • Cuerpo: Tipo de coche que estamos analizando (Suv, Hatchback, Coupe, etc); Variable Factor
  • Precio: Precio por el que se vende el automóvil; Variable Numérica

En función de todas estas varibales que son las originales, hemos configurado una base de datos “nueva” la cuál ofrece los mejores resultados para el análisis que vamos a efectuar. En esta base de datos utilizamos variables de las antes mencionadas, generamos nuevas variables con información de las originales y modificamos dos de ellas para hacer el análisis más cómodo.

  • Tipo_Fuel: 1 (Gasolina) 2 (Diésel) 3 (Eléctrico) 4 (Gas); Variable Factor - Transformada de original
  • Cambio: 1 (Automático), 2 (Manual); Variable Factor - Transformada de original
  • Lujo: 1 (Marcas de coche de Lujo que implican un precio mayor: Audi, BMW, Mercedes, Porsche, Cupra, Jeep y Jaguar) 0 (El resto); Variable Factor - Generada a través de Coche
  • Grupo_Precio: 0 (el precio es menor a la media) 1 (el precio es superior a la media); Variable Factor - Generada a través de Precio

Como hemos comentado, y tras realizar muchas pruebas para ver qué variables son más convenientes para elegir, hemos llegado a la conclusión de que utilizaremos únicamente las siguientes: Cambio, Seguridad, Caballos, Edad, Tipo_Fuel, Kilometros y Lujo. Todas estas variables serán variables “explicativas” para poder predecir Grupo_Precio

library(readr)
datos=read.csv2("Coches.csv",sep=";")
datos$Seguridad=as.factor(datos$Seguridad)
datos$Cambio=ifelse(datos$Cambio=="Manual",2,1)
datos$Cambio=as.factor(datos$Cambio)
datos$Cuerpo=as.factor(datos$Cuerpo)
datos$Tipo_Fuel=as.factor(datos$Tipo_Fuel)
datos$Seguridad=as.factor(datos$Seguridad)
datos$Tipo_Fuel=ifelse(datos$Tipo_Fuel=="A",1,ifelse(datos$Tipo_Fuel=="B",2
,ifelse(datos$Tipo_Fuel=="D",4,3)))
datos$Tipo_Fuel=as.factor(datos$Tipo_Fuel)


datos$Lujo=ifelse(datos$Coche=="Audi"|datos$Coche=="BMW"|
datos$Coche=="Mercedes-Benz"|datos$Coche=="Lexus"|datos$Coche=="Porsche"|
datos$Coche=="Cupra"|datos$Coche=="Jeep"|datos$Coche=="Jaguar",1,0)
datos$Lujo=as.factor(datos$Lujo)
datos2=datos[1:340,]
predecir=datos[341:nrow(datos),]
datos2$grupo_precio=ifelse(datos2$Precio<mean(datos2$Precio),0,1)
datos2$grupo_precio=as.factor(datos2$grupo_precio)
datos2=subset(datos2,select=c(Cambio,Seguridad,Caballos,Edad,Tipo_Fuel,
Kilometros,Lujo,grupo_precio))

1.2 Técnica de validación

En muchas ocasiones, si no utilizamos una técnica de validación, dejamos el análisis en manos del azar, pues existe la posibilidad de que aunque a la hora de comparar una técnica sea objetivamente mejor que la otra, esta obtenga peores resultados por el conjunto de datos obtenidos, o la aleatoriedad del proceso. Por ello, nos decidimos a utilizar la técnica de validación cruzada en 3 iteraciones con conjuntos aleatorios.

Para ello necesitamos saber inicialmente el número de datos que tenemos y partirlo en 3 partes iguales (no para seleccionar los grupos, sino para saber el tamaño). Una vez sabemos que cada parte ha de tener 113 datos, tomamos una muestra aleatoria de la base de datos con 113 individuos que van a hacer de test (un conjunto que vamos a probar y con el que vamos a comprobar resultados para probar el modelo), las 227 restantes serán el train (el conjunto que entrenará al test antes seleccionado).

Lo explicado arriba sirve para el primer conjunto, el segundo seleccionará el test con una muestra aleatoria de 113 individuos dentro del train (para que los conjuntos de test no se repitan). De nuevo el train serán las 227 restantes. Finalmente para la tercera muestra tendremos que coger el conjunto de test que aún no hemos utilizado y de nuevo el train las 227 restantes.

De esta forma hemos probado 3 conjuntos distintos, con tres errores distintos y los podemos unificar calculando la media del error de las 3 iteraciones para un análisis, garantizando de esta forma la certeza de los resultados y evitando la aleatoriedad o casualidad de los eventos.

### Validación cruzada 
library(MASS)


set.seed(3)
test1<-datos2[sample(nrow(datos2), 113), ]
training1<-unlist(row.names(test1))
train1<-datos2[!(row.names(datos2) %in% training1), ]

set.seed(3)
test2<-train1[sample(nrow(train1), 113), ]
training2<-unlist(row.names(test2))
train2<-datos2[!(row.names(datos2) %in% training2), ]

set.seed(3)
test3<-datos2[!(row.names(datos2) %in% training1 | row.names(datos2) %in% 
training2), ]
training3<-unlist(row.names(test3))
train3<-datos2[!(row.names(datos2) %in% training3), ]

1.3 Árbol de clasificación

Como se vió en el planteamiento inicial del problema, estamos trabajando para una gran compraventa de automóviles en España, la cuál opera en todo el territorio. En una empresa de este estilo, no todos los empleados tendrán conocimientos suficientes para saber cómo se está clasificando el automóvil para mandarle la información al tasador, por lo que nos hemos decidido a realizar un Árbol de Clasificación.

Lo que buscamos con el Árbol de Clasificación es hacer de un proceso estadístico algo completamente visual, fácil, rudimentario y sencillo para cualquier persona que entre en el sistema.

El Árbol de Clasificación es un método que busca dividir el conjunto de datos seleccionado en función de atributos que mejor separan las clases objetivo. Vista la base de datos original, el árbol buscará llegar a 0 si está clasificado por debajo de la media de precio o 1 si está por encima en función de las variables utilizadas.

Gracias a la validación cruzada, hemos probado el error de tres árboles distintos y hemos calculado la media como medida de confianza, obteniendo un acierto promedio de 0.8321948, es decir, que en el 83.22% de las veces que sigammos los recorridos del árbol de clasificación obtendremos una respuesta certera del precio del automóvil.

set.seed(3)
library(tree)
#a) Arbol de clasificación---
tree.Coches <- tree (grupo_precio~.,data = train1)
tree.pred1<-predict(tree.Coches,newdata=test1,type="class")
confusiona1=table(tree.pred1,test1$grupo_precio)
acierto=0
for (i in c(1:nrow(confusiona1))){
  acierto=acierto+confusiona1[i,i]
  tasa.acierto=acierto/sum(confusiona1)
  tasa.error=1-tasa.acierto
}
aciertoa1=tasa.acierto
errora1=tasa.error
set.seed(3)
tree.Coches <- tree (grupo_precio~.,data = train2)
tree.pred2<-predict(tree.Coches,newdata=test2,type="class")
confusiona2=table(tree.pred2,test2$grupo_precio)
acierto=0
for (i in c(1:nrow(confusiona2))){
  acierto=acierto+confusiona2[i,i]
  tasa.acierto=acierto/sum(confusiona2)
  tasa.error=1-tasa.acierto
}
aciertoa2=tasa.acierto
errora2=tasa.error
set.seed(3)
tree.Coches <- tree (grupo_precio~.,data = train3)
tree.pred3<-predict(tree.Coches,newdata=test3,type="class")
confusiona3=table(tree.pred3,test3$grupo_precio)
acierto=0
for (i in c(1:nrow(confusiona3))){
  acierto=acierto+confusiona3[i,i]
  tasa.acierto=acierto/sum(confusiona3)
  tasa.error=1-tasa.acierto
}
aciertoa3=tasa.acierto
errora3=tasa.error

cat("El método de Árbol de clasificación ofrece una media de acierto de",sum(aciertoa1,aciertoa2,aciertoa3)/3)
El método de Árbol de clasificación ofrece una media de acierto de 0.8321948
#---------- Ploteamos --------------------
tree.Coches <- tree (grupo_precio~.,data = datos2)
#b) plot----
plot(tree.Coches)
text(tree.Coches,pretty=0,cex=0.6)

En el gráfico se utilizan los atributos: Caballos, Edad, Kilómetros, Tipo_Fuel, Cambio y Lujo, para predecir la variable objetivo Tipo_Precio. Poniendo un ejemplo además, si tenemos un coche con menos de 125.5 Caballos y más de 4.26 años, estaremos ante un coche por debajo de la media.

1.4 Random forest

El segundo método que hemos utilizado para probar la predicción es Random Forest, un algoritmo de aprendizaje automático que combina múltiples árboles de clasifiación utilizando internamente conjuntos de entrenamiento distintos mediante bootstrapping. No debemos confundir esto con la técnica de validación que utilizamos, cada uno de los tres conjuntos de la técnica de validación, utiliza internamente bootstrapping en su algoritmo.

El principal objetivo de esta técnica es debido a su certeza, pues objetivamente funciona mejor que los árboles de clasificación, como hemos comentado son una mejora de los mismos, pues cada bosque es un conjunto de árboles. Además esta técnica es muy sofisticada y no es visualmente tan cómoda como el árbol, por lo que requiere ser tratada por estadísticos o matemáticos.

Una vez realizamos tres Bosques Aleatorios y estimamos el acierto medio, vemos que este es de 0.9087875, es decir, en las predicciones que realicemos con los árboles estaremos acertando en el 90.91% de las mismas.

Finalmente para bosques aleatorios somos capaces de identificar qué variables son más y menos relevantes para nuestro análisis. Si nos fijamos, al bosque le podemos introducir el número de variables que tenga en cuenta para el anális, esto lo podemos gestionar con ayuda de Varimplot(), de esta forma, si vemos que hay x variables menos relevantes seleccionamos un número menor de variables para el bosque para acelerar el proceso y no contar con aquellas variables que no aportan.En nuestro caso, lo general para lo común en los tres árboles es observar Tipo_Fuel y Seguridad como poco relevantes y Caballos y Edad como muy relevantes.

library(randomForest)
# conjunto 1
set.seed(3)
forest1<-randomForest(grupo_precio~.,data=train1,mtry=7, importance=TRUE)
forest.pred1<-predict(forest1,newdata=test1,type="class")
confusionb1=table(forest.pred1,test1$grupo_precio)
acierto=0
for (i in c(1:nrow(confusionb1))){
  acierto=acierto+confusionb1[i,i]
  tasa.acierto=acierto/sum(confusionb1)
  tasa.error=1-tasa.acierto
}
aciertob1=tasa.acierto
errorb1=tasa.error

set.seed(3)
forest2<-randomForest(grupo_precio~.,data=train2,mtry=7, importance=TRUE)
forest.pred2<-predict(forest2,newdata=test2,type="class")
confusionb2=table(forest.pred2,test2$grupo_precio)
acierto=0
for (i in c(1:nrow(confusionb2))){
  acierto=acierto+confusionb2[i,i]
  tasa.acierto=acierto/sum(confusionb2)
  tasa.error=1-tasa.acierto
}
aciertob2=tasa.acierto
errorb2=tasa.error

set.seed(3)
forest3<-randomForest(grupo_precio~.,data=train3,mtry=7, importance=TRUE)
forest.pred3<-predict(forest3,newdata=test3,type="class")
confusionb3=table(forest.pred3,test3$grupo_precio)
acierto=0
for (i in c(1:nrow(confusionb3))){
  acierto=acierto+confusionb3[i,i]
  tasa.acierto=acierto/sum(confusionb3)
  tasa.error=1-tasa.acierto
}
aciertob3=tasa.acierto
errorb3=tasa.error

cat("El método de Bosques Aleatorios tiene una media de acierto de",sum(aciertob1,aciertob2,aciertob3)/3)
El método de Bosques Aleatorios tiene una media de acierto de 0.9087875
varImpPlot(forest1)

varImpPlot(forest2)

varImpPlot(forest3)

1.5 Predicción método Árbol de Clasifiación y Random Forest

Para completar el informe, hemos generado una nueva base de datos donde hemos simulado las características de 12 automóviles que entran al sistema un día dado.Cabe destacar que en esta base de datos se incluye el precio del automóvil, esto no es realista porque si queremos tasar no nos van a dar el precio del automóvil, simplemente lo hacemos para demostrar cómo funciona el análisis. Para realizar las predicciones para Árbol de Clasificación y Random Forest debemos utilizar la base de datos al completo, el train y test era simplemente para probar el modelo, una vez sabemos cómo funcionan utilizamos el 100% de los datos para predecir los nuevos. De esta forma tenemos los siguientes resultados:

  • El método de Árbol de Clasificación se equivoca en 3 de 12 ocasiones, dos de ellas no tienen a penas relevancia, pues se equivoca con una diferencia de precio de tan solo hasta 600€. Es decir, sitúa por debajo de la media un automóvil de 18999€ y otro de 18500€. Sin embargo, si nos fijamos en el precio del Mercedes-Benz Cls, comete un grave error, pues este dice que está por encima de la media cuando realmente vale 12900€, creemos que esto puede deberse por la importancia que da a la marca.

  • El método de Random Forest sin embargo evita el error del Cls, por lo que es mucho más preciso y solamente cae en los dos antes comentados con una difrencia de precio realmente baja.

set.seed(3)
library(tree)
#a) Arbol de clasificación---
tree.Coches <- tree (grupo_precio~.,data = datos2)
predecir$prediccion_arbol<-predict(tree.Coches,newdata=predecir,type="class")

library(randomForest)
# conjunto 1
set.seed(3)
forest1<-randomForest(grupo_precio~.,data=datos2,mtry=7, importance=TRUE)
predecir$prediccion_forest<-predict(forest1,newdata=predecir,type="class")
knitr::kable(predecir)
Cambio Coche Modelo Seguridad Caballos Edad Tipo_Fuel Kilometros Cuerpo Precio Lujo prediccion_arbol prediccion_forest
341 1 Mercedes-Benz Clase GLC 1 204 7 2 198000 SUV 26500 1 1 1
342 2 Nissan Qashqai 2 150 13 2 152003 SUV 12500 0 0 0
343 2 BMW Serie 2 1 150 4 2 43000 Hatchback 26900 1 1 1
344 2 Volkswagen Passat 2 120 5 2 157000 Berlina 15500 0 0 0
345 2 Opel Crossland 2 81 5 1 25218 SUV 14995 0 0 0
346 2 Audi Q5 1 190 8 2 91000 SUV 23990 1 1 1
347 1 Volkswagen Touareg 2 204 11 2 240000 SUV 18999 0 0 0
348 1 Subaru Impreza 2 114 5 1 53000 Coupe 17900 0 0 0
349 1 Volkswagen Troc 2 150 3 1 32169 SUV 22810 0 1 1
350 1 Mercedes-Benz Cls 2 224 16 2 182000 Berllina 12900 1 1 0
351 1 Hyundai Kona 2 204 4 3 31500 SUV 26900 0 1 1
352 2 Mazda Mazda3 1 122 4 4 60000 Hatchback 18500 0 0 0

1.6 Conclusión

Como se ha ido comentado durante el informe, los dos métodos funcionan bastante bien y cada uno tiene un enfoque distinto. Mientra que el árbol de clasificación ofrece una gran comodidad y versatilidad para tomar decisiones en el momento sin tener conocimientos especializados, el método de Random Forest ofrece una mejor predicción con errores menos graves ajustando mucho más los precios que buscamos.

Además cabe destacar en las conclusiones el tamaño de la base de datos. Queremos predecir el precio de un coche en función de las variables antes comentadas, pero tenemos únicamente 340 datos para un sistema tan complejo. Como hemos visto, hemos tenido que realizar apaños “cambiando” la variable Coche y Modelo por Lujo. Por lo que creemos que con una base de datos realmente grande donde pudiéramos tomar las especificaciones mucho más al dedo con más individuos representativos de cada factor se podría realizar un análisis mucho más sofisticado siendo capaces de predecir con mucha más credibilidad y precisión.

LS0tDQp0aXRsZTogIkluZm9ybWUgVMOpY25pY2FzIEVzdGFkw61zdGljYXMgZW4gQW7DoWxpc2lzIGRlIE1lcmNhZG9zIg0KYXV0aG9yOiANCiAgLSAgIEVybmVzdG8gTW9sdMOzIFF1aWxlcw0KICAtICAgUmFmYWVsIEFudMOzbiBNb3lhDQpvdXRwdXQ6DQogIHJtZGZvcm1hdHM6OnJlYWR0aGVkb3duOg0KICAgIHNlbGZfY29udGFpbmVkOiB5ZXMNCiAgICB0aHVtYm5haWxzOiB5ZXMNCiAgICBsaWdodGJveDogeWVzDQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KICAgIGNvZGVfZm9sZGluZzogImhpZGUiDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQ0KICAgICANCiAgICANCi0tLQ0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GLGNvbW1lbnQ9TkEsIGZpZy53aWR0aD02LCBmaWcuYWxpZ249ImNlbnRlciIpDQpgYGANCg0KIyBUcmFiYWpvIFTDqWNuaWNhcyBFc3RhZMOtc3RpY2FzIGVuIEFuw6FsaXNpcyBkZSBNZXJjYWRvcw0KDQojIyBQbGFudGVhbWludG8gZGVsIHByb2JsZW1hDQoNCkVuIGVsIHNpZ3VpZW50ZSBpbmZvcm1lIGVzdGFtb3MgYW50ZSB1biBjYXNvIHByw6FjdGljbyBlbiBlbCBjdWFsIG5lY2VzaXRhbW9zIGFuYWxpemFyIHkgcHJlZGVjaXIgZWwgcHJlY2lvIGRlIGN1YWxxdWllciBhdXRvbcOzdmlsIGVuIGZ1bmNpw7NuIGRlIGRpc3RpbnRhcyBjYWN0ZXLDrXN0aWNhcyBwcmluY2lwYWxlcyBxdWUgc2UgcHJlc2VudGFuLiBQYXJhIGVsIGNhc28sIGhlbW9zIHN1cHVlc3RvIHF1ZSB0cmFiYWphbW9zIHBhcmEgdW5hIGdyYW4gY29tcHJhdmVudGEgZGUgY29jaGVzIHF1ZSBvcGVyYSBlbiB0b2RvIGVsIHRlcnJpdG9yaW8gbmFjaW9uYWwgZXNwYcOxb2wuIE5vcyBoYW4gcGVkaWRvIHF1ZSBwYXJhIGZhY2lsaXRhciBlbCB0cmFiYWpvIGEgbG9zIHRhc2Fkb3JlcyB5IGFob3JyYXIgdW5hIGdyYW4gY29zdGUgc2FsYXJpYWwgZGViZW1vcyBjcmVhciB1biBhbGdvcml0bW8gcXVlIGRhZGFzIHVuYXMgY2FyYWN0ZXLDrXN0aWNhcyBzb2JyZSBlbCBjb2NoZSwgbG8gY2xhc2lmaXF1ZSBlbiBkb3MgZ3J1cG9zIHNlZ8O6biBzaSBlc3RlIHNlIGVuY3VlbnRyYSBwb3IgZGViYWpvIG8gcG9yIGVuY2ltYSBkZSBsYSBtZWRpYSAoMTgzMzcuNuKCrCkuIFBhcmEgZWwgYWxnb3JpdG1vIGhlbW9zIGNyZWFkbyB1bmEgYmFzZSBkZSBkYXRvcyBjb24gMzQwIGNvY2hlcyBkZSBsYSB3ZWIgIkNvY2hlcy5uZXQiIGdyYWNpYXMgYSBsYXMgdMOpY25pY2FzIGRlIFdlYi1TY3JhcGluZywgbGEgY3VhbCBjb250aWVuZSBsYSBzaWd1aWVudGUgaW5mb3JtYWNpw7NuOiANCg0KLSAgICoqQ2FtYmlvKio6IE1hbnVhbCwgQXV0b23DoXRpY28gOyBWYXJpYWJsZSBGYWN0b3INCi0gICAqKkNvY2hlKio6IE1hcmNoYSBkZSBDb2NoZSAoQXVkaSwgQk1XLCBTZWF0LCBldGMpIDsgVmFyaWFibGUgRmFjdG9yDQotICAgKipNb2RlbG8qKjogTW9kZWxvIGRlIENvY2hlIChRYXNocWFpLCBRNSwgR0xDLCBldGMpIDsgVmFyaWFibGUgRmFjdG9yDQotICAgKipTZWd1cmlkYWQqKjogMSAoU2VndXJpZGFkIG3DoXhpbWEpIDIgKFNlZ3VyaWRhZCBhY2VwdGFibGUpIDMoU2VndXJpZGFkIG1hbGEpOyBWYXJpYWJsZSBGYWN0b3INCi0gICAqKkNhYmFsbG9zKio6IENhYmFsbG9zIGRlIFZhcG9yIGRlbCBjb2NoZTsgVmFyaWFibGUgTnVtw6lyaWNhDQotICAgKipFZGFkKio6IEVkYWQgZGVsIENvY2hlIGRlc2RlIHF1ZSBzZSBtYXRyaWN1bMOzOyBWYXJpYWJsZSBOdW3DqXJpY2ENCi0gICAqKlRpcG9fRnVlbCoqOiBBIChHYXNvbGluYSkgQiAoRGnDqXNlbCkgQyAoRWzDqWN0cmljbykgRCAoR2FzKTsgVmFyaWFibGUgRmFjdG9yDQotICAgKipLaWxvbWV0cm9zKio6IEtpbG9tZXRyYWplIGRlbCBhdXRvbcOzdmlsIGVuIGVsIG1vbWVudG8gZGUgbGEgdmVudGE7IFZhcmlhYmxlIE51bcOpcmljYQ0KLSAgICoqQ3VlcnBvKio6IFRpcG8gZGUgY29jaGUgcXVlIGVzdGFtb3MgYW5hbGl6YW5kbyAoU3V2LCBIYXRjaGJhY2ssIENvdXBlLCBldGMpOyBWYXJpYWJsZSBGYWN0b3INCi0gICAqKlByZWNpbyoqOiBQcmVjaW8gcG9yIGVsIHF1ZSBzZSB2ZW5kZSBlbCBhdXRvbcOzdmlsOyBWYXJpYWJsZSBOdW3DqXJpY2ENCg0KRW4gZnVuY2nDs24gZGUgdG9kYXMgZXN0YXMgdmFyaWJhbGVzIHF1ZSBzb24gbGFzIG9yaWdpbmFsZXMsIGhlbW9zIGNvbmZpZ3VyYWRvIHVuYSBiYXNlIGRlIGRhdG9zICJudWV2YSIgbGEgY3XDoWwgb2ZyZWNlIGxvcyBtZWpvcmVzIHJlc3VsdGFkb3MgcGFyYSBlbCBhbsOhbGlzaXMgcXVlIHZhbW9zIGEgZWZlY3R1YXIuIEVuIGVzdGEgYmFzZSBkZSBkYXRvcyB1dGlsaXphbW9zIHZhcmlhYmxlcyBkZSBsYXMgYW50ZXMgbWVuY2lvbmFkYXMsIGdlbmVyYW1vcyBudWV2YXMgdmFyaWFibGVzIGNvbiBpbmZvcm1hY2nDs24gZGUgbGFzIG9yaWdpbmFsZXMgeSBtb2RpZmljYW1vcyBkb3MgZGUgZWxsYXMgcGFyYSBoYWNlciBlbCBhbsOhbGlzaXMgbcOhcyBjw7Ntb2RvLiANCg0KLSAgICoqVGlwb19GdWVsKio6IDEgKEdhc29saW5hKSAyIChEacOpc2VsKSAzIChFbMOpY3RyaWNvKSA0IChHYXMpOyBWYXJpYWJsZSBGYWN0b3IgLSBUcmFuc2Zvcm1hZGEgZGUgb3JpZ2luYWwNCi0gICAqKkNhbWJpbyoqOiAxIChBdXRvbcOhdGljbyksIDIgKE1hbnVhbCk7IFZhcmlhYmxlIEZhY3RvciAtIFRyYW5zZm9ybWFkYSBkZSBvcmlnaW5hbA0KLSAgICoqTHVqbyoqOiAxICAoTWFyY2FzIGRlIGNvY2hlIGRlIEx1am8gcXVlIGltcGxpY2FuIHVuIHByZWNpbyBtYXlvcjogQXVkaSwgQk1XLCBNZXJjZWRlcywgUG9yc2NoZSwgQ3VwcmEsIEplZXAgeSBKYWd1YXIpIDAgKEVsIHJlc3RvKTsgVmFyaWFibGUgRmFjdG9yIC0gR2VuZXJhZGEgYSB0cmF2w6lzIGRlIENvY2hlDQotICAgKipHcnVwb19QcmVjaW8qKjogMCAoZWwgcHJlY2lvIGVzIG1lbm9yIGEgbGEgbWVkaWEpIDEgKGVsIHByZWNpbyBlcyBzdXBlcmlvciBhIGxhIG1lZGlhKTsgVmFyaWFibGUgRmFjdG9yIC0gR2VuZXJhZGEgYSB0cmF2w6lzIGRlIFByZWNpbw0KDQpDb21vIGhlbW9zIGNvbWVudGFkbywgeSB0cmFzIHJlYWxpemFyIG11Y2hhcyBwcnVlYmFzIHBhcmEgdmVyIHF1w6kgdmFyaWFibGVzIHNvbiBtw6FzIGNvbnZlbmllbnRlcyBwYXJhIGVsZWdpciwgaGVtb3MgbGxlZ2FkbyBhIGxhIGNvbmNsdXNpw7NuIGRlIHF1ZSB1dGlsaXphcmVtb3Mgw7puaWNhbWVudGUgbGFzIHNpZ3VpZW50ZXM6IENhbWJpbywgU2VndXJpZGFkLCBDYWJhbGxvcywgRWRhZCwgVGlwb19GdWVsLCBLaWxvbWV0cm9zIHkgTHVqby4gVG9kYXMgZXN0YXMgdmFyaWFibGVzIHNlcsOhbiB2YXJpYWJsZXMgImV4cGxpY2F0aXZhcyIgcGFyYSBwb2RlciBwcmVkZWNpciAqKkdydXBvX1ByZWNpbyoqDQoNCmBgYHtyfQ0KbGlicmFyeShyZWFkcikNCmRhdG9zPXJlYWQuY3N2MigiQ29jaGVzLmNzdiIsc2VwPSI7IikNCmRhdG9zJFNlZ3VyaWRhZD1hcy5mYWN0b3IoZGF0b3MkU2VndXJpZGFkKQ0KZGF0b3MkQ2FtYmlvPWlmZWxzZShkYXRvcyRDYW1iaW89PSJNYW51YWwiLDIsMSkNCmRhdG9zJENhbWJpbz1hcy5mYWN0b3IoZGF0b3MkQ2FtYmlvKQ0KZGF0b3MkQ3VlcnBvPWFzLmZhY3RvcihkYXRvcyRDdWVycG8pDQpkYXRvcyRUaXBvX0Z1ZWw9YXMuZmFjdG9yKGRhdG9zJFRpcG9fRnVlbCkNCmRhdG9zJFNlZ3VyaWRhZD1hcy5mYWN0b3IoZGF0b3MkU2VndXJpZGFkKQ0KZGF0b3MkVGlwb19GdWVsPWlmZWxzZShkYXRvcyRUaXBvX0Z1ZWw9PSJBIiwxLGlmZWxzZShkYXRvcyRUaXBvX0Z1ZWw9PSJCIiwyDQosaWZlbHNlKGRhdG9zJFRpcG9fRnVlbD09IkQiLDQsMykpKQ0KZGF0b3MkVGlwb19GdWVsPWFzLmZhY3RvcihkYXRvcyRUaXBvX0Z1ZWwpDQoNCg0KZGF0b3MkTHVqbz1pZmVsc2UoZGF0b3MkQ29jaGU9PSJBdWRpInxkYXRvcyRDb2NoZT09IkJNVyJ8DQpkYXRvcyRDb2NoZT09Ik1lcmNlZGVzLUJlbnoifGRhdG9zJENvY2hlPT0iTGV4dXMifGRhdG9zJENvY2hlPT0iUG9yc2NoZSJ8DQpkYXRvcyRDb2NoZT09IkN1cHJhInxkYXRvcyRDb2NoZT09IkplZXAifGRhdG9zJENvY2hlPT0iSmFndWFyIiwxLDApDQpkYXRvcyRMdWpvPWFzLmZhY3RvcihkYXRvcyRMdWpvKQ0KZGF0b3MyPWRhdG9zWzE6MzQwLF0NCnByZWRlY2lyPWRhdG9zWzM0MTpucm93KGRhdG9zKSxdDQpkYXRvczIkZ3J1cG9fcHJlY2lvPWlmZWxzZShkYXRvczIkUHJlY2lvPG1lYW4oZGF0b3MyJFByZWNpbyksMCwxKQ0KZGF0b3MyJGdydXBvX3ByZWNpbz1hcy5mYWN0b3IoZGF0b3MyJGdydXBvX3ByZWNpbykNCmRhdG9zMj1zdWJzZXQoZGF0b3MyLHNlbGVjdD1jKENhbWJpbyxTZWd1cmlkYWQsQ2FiYWxsb3MsRWRhZCxUaXBvX0Z1ZWwsDQpLaWxvbWV0cm9zLEx1am8sZ3J1cG9fcHJlY2lvKSkNCmBgYA0KDQojIyBUw6ljbmljYSBkZSB2YWxpZGFjacOzbg0KDQpFbiBtdWNoYXMgb2Nhc2lvbmVzLCBzaSBubyB1dGlsaXphbW9zIHVuYSB0w6ljbmljYSBkZSB2YWxpZGFjacOzbiwgZGVqYW1vcyBlbCBhbsOhbGlzaXMgZW4gbWFub3MgZGVsIGF6YXIsIHB1ZXMgZXhpc3RlIGxhIHBvc2liaWxpZGFkIGRlIHF1ZSBhdW5xdWUgYSBsYSBob3JhIGRlIGNvbXBhcmFyIHVuYSB0w6ljbmljYSBzZWEgb2JqZXRpdmFtZW50ZSBtZWpvciBxdWUgbGEgb3RyYSwgZXN0YSBvYnRlbmdhIHBlb3JlcyByZXN1bHRhZG9zIHBvciBlbCBjb25qdW50byBkZSBkYXRvcyBvYnRlbmlkb3MsIG8gbGEgYWxlYXRvcmllZGFkIGRlbCBwcm9jZXNvLiBQb3IgZWxsbywgbm9zIGRlY2lkaW1vcyBhIHV0aWxpemFyIGxhIHTDqWNuaWNhIGRlIHZhbGlkYWNpw7NuIGNydXphZGEgZW4gMyBpdGVyYWNpb25lcyBjb24gY29uanVudG9zIGFsZWF0b3Jpb3MuIA0KDQpQYXJhIGVsbG8gbmVjZXNpdGFtb3Mgc2FiZXIgaW5pY2lhbG1lbnRlIGVsIG7Dum1lcm8gZGUgZGF0b3MgcXVlIHRlbmVtb3MgeSBwYXJ0aXJsbyBlbiAzIHBhcnRlcyBpZ3VhbGVzIChubyBwYXJhIHNlbGVjY2lvbmFyIGxvcyBncnVwb3MsIHNpbm8gcGFyYSBzYWJlciBlbCB0YW1hw7FvKS4gVW5hIHZleiBzYWJlbW9zIHF1ZSBjYWRhIHBhcnRlIGhhIGRlIHRlbmVyIDExMyBkYXRvcywgdG9tYW1vcyB1bmEgbXVlc3RyYSBhbGVhdG9yaWEgZGUgbGEgYmFzZSBkZSBkYXRvcyBjb24gMTEzIGluZGl2aWR1b3MgcXVlIHZhbiBhIGhhY2VyIGRlIHRlc3QgKHVuIGNvbmp1bnRvIHF1ZSB2YW1vcyBhIHByb2JhciB5IGNvbiBlbCBxdWUgdmFtb3MgYSBjb21wcm9iYXIgcmVzdWx0YWRvcyBwYXJhIHByb2JhciBlbCBtb2RlbG8pLCBsYXMgMjI3IHJlc3RhbnRlcyBzZXLDoW4gZWwgdHJhaW4gKGVsIGNvbmp1bnRvIHF1ZSBlbnRyZW5hcsOhIGFsIHRlc3QgYW50ZXMgc2VsZWNjaW9uYWRvKS4gDQoNCkxvIGV4cGxpY2FkbyBhcnJpYmEgc2lydmUgcGFyYSBlbCBwcmltZXIgY29uanVudG8sIGVsIHNlZ3VuZG8gc2VsZWNjaW9uYXLDoSBlbCB0ZXN0IGNvbiB1bmEgbXVlc3RyYSBhbGVhdG9yaWEgZGUgMTEzIGluZGl2aWR1b3MgZGVudHJvIGRlbCB0cmFpbiAocGFyYSBxdWUgbG9zIGNvbmp1bnRvcyBkZSB0ZXN0IG5vIHNlIHJlcGl0YW4pLiBEZSBudWV2byBlbCB0cmFpbiBzZXLDoW4gbGFzIDIyNyByZXN0YW50ZXMuIEZpbmFsbWVudGUgcGFyYSBsYSB0ZXJjZXJhIG11ZXN0cmEgdGVuZHJlbW9zIHF1ZSBjb2dlciBlbCBjb25qdW50byBkZSB0ZXN0IHF1ZSBhw7puIG5vIGhlbW9zIHV0aWxpemFkbyB5IGRlIG51ZXZvIGVsIHRyYWluIGxhcyAyMjcgcmVzdGFudGVzLg0KDQpEZSBlc3RhIGZvcm1hIGhlbW9zIHByb2JhZG8gMyBjb25qdW50b3MgZGlzdGludG9zLCBjb24gdHJlcyBlcnJvcmVzIGRpc3RpbnRvcyB5IGxvcyBwb2RlbW9zIHVuaWZpY2FyIGNhbGN1bGFuZG8gbGEgbWVkaWEgZGVsIGVycm9yIGRlIGxhcyAzIGl0ZXJhY2lvbmVzIHBhcmEgdW4gYW7DoWxpc2lzLCBnYXJhbnRpemFuZG8gZGUgZXN0YSBmb3JtYSBsYSBjZXJ0ZXphIGRlIGxvcyByZXN1bHRhZG9zIHkgZXZpdGFuZG8gbGEgYWxlYXRvcmllZGFkIG8gY2FzdWFsaWRhZCBkZSBsb3MgZXZlbnRvcy4gDQoNCmBgYHtyfQ0KIyMjIFZhbGlkYWNpw7NuIGNydXphZGEgDQpsaWJyYXJ5KE1BU1MpDQoNCg0Kc2V0LnNlZWQoMykNCnRlc3QxPC1kYXRvczJbc2FtcGxlKG5yb3coZGF0b3MyKSwgMTEzKSwgXQ0KdHJhaW5pbmcxPC11bmxpc3Qocm93Lm5hbWVzKHRlc3QxKSkNCnRyYWluMTwtZGF0b3MyWyEocm93Lm5hbWVzKGRhdG9zMikgJWluJSB0cmFpbmluZzEpLCBdDQoNCnNldC5zZWVkKDMpDQp0ZXN0MjwtdHJhaW4xW3NhbXBsZShucm93KHRyYWluMSksIDExMyksIF0NCnRyYWluaW5nMjwtdW5saXN0KHJvdy5uYW1lcyh0ZXN0MikpDQp0cmFpbjI8LWRhdG9zMlshKHJvdy5uYW1lcyhkYXRvczIpICVpbiUgdHJhaW5pbmcyKSwgXQ0KDQpzZXQuc2VlZCgzKQ0KdGVzdDM8LWRhdG9zMlshKHJvdy5uYW1lcyhkYXRvczIpICVpbiUgdHJhaW5pbmcxIHwgcm93Lm5hbWVzKGRhdG9zMikgJWluJSANCnRyYWluaW5nMiksIF0NCnRyYWluaW5nMzwtdW5saXN0KHJvdy5uYW1lcyh0ZXN0MykpDQp0cmFpbjM8LWRhdG9zMlshKHJvdy5uYW1lcyhkYXRvczIpICVpbiUgdHJhaW5pbmczKSwgXQ0KYGBgDQoNCiMjIMOBcmJvbCBkZSBjbGFzaWZpY2FjacOzbg0KDQpDb21vIHNlIHZpw7MgZW4gZWwgcGxhbnRlYW1pZW50byBpbmljaWFsIGRlbCBwcm9ibGVtYSwgZXN0YW1vcyB0cmFiYWphbmRvIHBhcmEgdW5hIGdyYW4gY29tcHJhdmVudGEgZGUgYXV0b23Ds3ZpbGVzIGVuIEVzcGHDsWEsIGxhIGN1w6FsIG9wZXJhIGVuIHRvZG8gZWwgdGVycml0b3Jpby4gRW4gdW5hIGVtcHJlc2EgZGUgZXN0ZSBlc3RpbG8sIG5vIHRvZG9zIGxvcyBlbXBsZWFkb3MgdGVuZHLDoW4gY29ub2NpbWllbnRvcyBzdWZpY2llbnRlcyBwYXJhIHNhYmVyIGPDs21vIHNlIGVzdMOhIGNsYXNpZmljYW5kbyBlbCBhdXRvbcOzdmlsIHBhcmEgbWFuZGFybGUgbGEgaW5mb3JtYWNpw7NuIGFsIHRhc2Fkb3IsIHBvciBsbyBxdWUgbm9zIGhlbW9zIGRlY2lkaWRvIGEgcmVhbGl6YXIgdW4gw4FyYm9sIGRlIENsYXNpZmljYWNpw7NuLiANCg0KTG8gcXVlIGJ1c2NhbW9zIGNvbiBlbCDDgXJib2wgZGUgQ2xhc2lmaWNhY2nDs24gZXMgaGFjZXIgZGUgdW4gcHJvY2VzbyBlc3RhZMOtc3RpY28gYWxnbyBjb21wbGV0YW1lbnRlIHZpc3VhbCwgZsOhY2lsLCBydWRpbWVudGFyaW8geSBzZW5jaWxsbyBwYXJhIGN1YWxxdWllciBwZXJzb25hIHF1ZSBlbnRyZSBlbiBlbCBzaXN0ZW1hLg0KDQpFbCDDgXJib2wgZGUgQ2xhc2lmaWNhY2nDs24gZXMgdW4gbcOpdG9kbyBxdWUgYnVzY2EgZGl2aWRpciBlbCBjb25qdW50byBkZSBkYXRvcyBzZWxlY2Npb25hZG8gZW4gZnVuY2nDs24gZGUgYXRyaWJ1dG9zIHF1ZSBtZWpvciBzZXBhcmFuIGxhcyBjbGFzZXMgb2JqZXRpdm8uIFZpc3RhIGxhIGJhc2UgZGUgZGF0b3Mgb3JpZ2luYWwsIGVsIMOhcmJvbCBidXNjYXLDoSBsbGVnYXIgYSAwIHNpIGVzdMOhIGNsYXNpZmljYWRvIHBvciBkZWJham8gZGUgbGEgbWVkaWEgZGUgcHJlY2lvIG8gMSBzaSBlc3TDoSBwb3IgZW5jaW1hIGVuIGZ1bmNpw7NuIGRlIGxhcyB2YXJpYWJsZXMgdXRpbGl6YWRhcy4NCg0KR3JhY2lhcyBhIGxhIHZhbGlkYWNpw7NuIGNydXphZGEsIGhlbW9zIHByb2JhZG8gZWwgZXJyb3IgZGUgdHJlcyDDoXJib2xlcyBkaXN0aW50b3MgeSBoZW1vcyBjYWxjdWxhZG8gbGEgbWVkaWEgY29tbyBtZWRpZGEgZGUgY29uZmlhbnphLCBvYnRlbmllbmRvIHVuIGFjaWVydG8gcHJvbWVkaW8gZGUgMC44MzIxOTQ4LCBlcyBkZWNpciwgcXVlIGVuIGVsIDgzLjIyJSBkZSBsYXMgdmVjZXMgcXVlIHNpZ2FtbW9zIGxvcyByZWNvcnJpZG9zIGRlbCDDoXJib2wgZGUgY2xhc2lmaWNhY2nDs24gb2J0ZW5kcmVtb3MgdW5hIHJlc3B1ZXN0YSBjZXJ0ZXJhIGRlbCBwcmVjaW8gZGVsIGF1dG9tw7N2aWwuIA0KDQoNCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0NCnNldC5zZWVkKDMpDQpsaWJyYXJ5KHRyZWUpDQojYSkgQXJib2wgZGUgY2xhc2lmaWNhY2nDs24tLS0NCnRyZWUuQ29jaGVzIDwtIHRyZWUgKGdydXBvX3ByZWNpb34uLGRhdGEgPSB0cmFpbjEpDQp0cmVlLnByZWQxPC1wcmVkaWN0KHRyZWUuQ29jaGVzLG5ld2RhdGE9dGVzdDEsdHlwZT0iY2xhc3MiKQ0KY29uZnVzaW9uYTE9dGFibGUodHJlZS5wcmVkMSx0ZXN0MSRncnVwb19wcmVjaW8pDQphY2llcnRvPTANCmZvciAoaSBpbiBjKDE6bnJvdyhjb25mdXNpb25hMSkpKXsNCiAgYWNpZXJ0bz1hY2llcnRvK2NvbmZ1c2lvbmExW2ksaV0NCiAgdGFzYS5hY2llcnRvPWFjaWVydG8vc3VtKGNvbmZ1c2lvbmExKQ0KICB0YXNhLmVycm9yPTEtdGFzYS5hY2llcnRvDQp9DQphY2llcnRvYTE9dGFzYS5hY2llcnRvDQplcnJvcmExPXRhc2EuZXJyb3INCnNldC5zZWVkKDMpDQp0cmVlLkNvY2hlcyA8LSB0cmVlIChncnVwb19wcmVjaW9+LixkYXRhID0gdHJhaW4yKQ0KdHJlZS5wcmVkMjwtcHJlZGljdCh0cmVlLkNvY2hlcyxuZXdkYXRhPXRlc3QyLHR5cGU9ImNsYXNzIikNCmNvbmZ1c2lvbmEyPXRhYmxlKHRyZWUucHJlZDIsdGVzdDIkZ3J1cG9fcHJlY2lvKQ0KYWNpZXJ0bz0wDQpmb3IgKGkgaW4gYygxOm5yb3coY29uZnVzaW9uYTIpKSl7DQogIGFjaWVydG89YWNpZXJ0bytjb25mdXNpb25hMltpLGldDQogIHRhc2EuYWNpZXJ0bz1hY2llcnRvL3N1bShjb25mdXNpb25hMikNCiAgdGFzYS5lcnJvcj0xLXRhc2EuYWNpZXJ0bw0KfQ0KYWNpZXJ0b2EyPXRhc2EuYWNpZXJ0bw0KZXJyb3JhMj10YXNhLmVycm9yDQpzZXQuc2VlZCgzKQ0KdHJlZS5Db2NoZXMgPC0gdHJlZSAoZ3J1cG9fcHJlY2lvfi4sZGF0YSA9IHRyYWluMykNCnRyZWUucHJlZDM8LXByZWRpY3QodHJlZS5Db2NoZXMsbmV3ZGF0YT10ZXN0Myx0eXBlPSJjbGFzcyIpDQpjb25mdXNpb25hMz10YWJsZSh0cmVlLnByZWQzLHRlc3QzJGdydXBvX3ByZWNpbykNCmFjaWVydG89MA0KZm9yIChpIGluIGMoMTpucm93KGNvbmZ1c2lvbmEzKSkpew0KICBhY2llcnRvPWFjaWVydG8rY29uZnVzaW9uYTNbaSxpXQ0KICB0YXNhLmFjaWVydG89YWNpZXJ0by9zdW0oY29uZnVzaW9uYTMpDQogIHRhc2EuZXJyb3I9MS10YXNhLmFjaWVydG8NCn0NCmFjaWVydG9hMz10YXNhLmFjaWVydG8NCmVycm9yYTM9dGFzYS5lcnJvcg0KDQpjYXQoIkVsIG3DqXRvZG8gZGUgw4FyYm9sIGRlIGNsYXNpZmljYWNpw7NuIG9mcmVjZSB1bmEgbWVkaWEgZGUgYWNpZXJ0byBkZSIsc3VtKGFjaWVydG9hMSxhY2llcnRvYTIsYWNpZXJ0b2EzKS8zKQ0KDQojLS0tLS0tLS0tLSBQbG90ZWFtb3MgLS0tLS0tLS0tLS0tLS0tLS0tLS0NCnRyZWUuQ29jaGVzIDwtIHRyZWUgKGdydXBvX3ByZWNpb34uLGRhdGEgPSBkYXRvczIpDQojYikgcGxvdC0tLS0NCnBsb3QodHJlZS5Db2NoZXMpDQp0ZXh0KHRyZWUuQ29jaGVzLHByZXR0eT0wLGNleD0wLjYpDQoNCmBgYA0KDQpFbiBlbCBncsOhZmljbyBzZSB1dGlsaXphbiBsb3MgYXRyaWJ1dG9zOiBDYWJhbGxvcywgRWRhZCwgS2lsw7NtZXRyb3MsIFRpcG9fRnVlbCwgQ2FtYmlvIHkgTHVqbywgIHBhcmEgcHJlZGVjaXIgbGEgdmFyaWFibGUgb2JqZXRpdm8gVGlwb19QcmVjaW8uIFBvbmllbmRvIHVuIGVqZW1wbG8gYWRlbcOhcywgc2kgdGVuZW1vcyB1biBjb2NoZSBjb24gbWVub3MgZGUgMTI1LjUgQ2FiYWxsb3MgeSBtw6FzIGRlIDQuMjYgYcOxb3MsIGVzdGFyZW1vcyBhbnRlIHVuIGNvY2hlIHBvciBkZWJham8gZGUgbGEgbWVkaWEuIA0KDQojIyBSYW5kb20gZm9yZXN0DQoNCkVsIHNlZ3VuZG8gbcOpdG9kbyBxdWUgaGVtb3MgdXRpbGl6YWRvIHBhcmEgcHJvYmFyIGxhIHByZWRpY2Npw7NuIGVzIFJhbmRvbSBGb3Jlc3QsIHVuIGFsZ29yaXRtbyBkZSBhcHJlbmRpemFqZSBhdXRvbcOhdGljbyBxdWUgY29tYmluYSBtw7psdGlwbGVzIMOhcmJvbGVzIGRlIGNsYXNpZmlhY2nDs24gdXRpbGl6YW5kbyBpbnRlcm5hbWVudGUgY29uanVudG9zIGRlIGVudHJlbmFtaWVudG8gZGlzdGludG9zIG1lZGlhbnRlIGJvb3RzdHJhcHBpbmcuIE5vIGRlYmVtb3MgY29uZnVuZGlyIGVzdG8gY29uIGxhIHTDqWNuaWNhIGRlIHZhbGlkYWNpw7NuIHF1ZSB1dGlsaXphbW9zLCBjYWRhIHVubyBkZSBsb3MgdHJlcyBjb25qdW50b3MgZGUgbGEgdMOpY25pY2EgZGUgdmFsaWRhY2nDs24sIHV0aWxpemEgaW50ZXJuYW1lbnRlIGJvb3RzdHJhcHBpbmcgZW4gc3UgYWxnb3JpdG1vLiANCg0KRWwgcHJpbmNpcGFsIG9iamV0aXZvIGRlIGVzdGEgdMOpY25pY2EgZXMgZGViaWRvIGEgc3UgY2VydGV6YSwgcHVlcyBvYmpldGl2YW1lbnRlIGZ1bmNpb25hIG1lam9yIHF1ZSBsb3Mgw6FyYm9sZXMgZGUgY2xhc2lmaWNhY2nDs24sIGNvbW8gaGVtb3MgY29tZW50YWRvIHNvbiB1bmEgbWVqb3JhIGRlIGxvcyBtaXNtb3MsIHB1ZXMgY2FkYSBib3NxdWUgZXMgdW4gY29uanVudG8gZGUgw6FyYm9sZXMuIEFkZW3DoXMgZXN0YSB0w6ljbmljYSBlcyBtdXkgc29maXN0aWNhZGEgeSBubyBlcyB2aXN1YWxtZW50ZSB0YW4gY8OzbW9kYSBjb21vIGVsIMOhcmJvbCwgcG9yIGxvIHF1ZSByZXF1aWVyZSBzZXIgdHJhdGFkYSBwb3IgZXN0YWTDrXN0aWNvcyBvIG1hdGVtw6F0aWNvcy4gDQoNClVuYSB2ZXogcmVhbGl6YW1vcyB0cmVzIEJvc3F1ZXMgQWxlYXRvcmlvcyB5IGVzdGltYW1vcyBlbCBhY2llcnRvIG1lZGlvLCB2ZW1vcyBxdWUgZXN0ZSBlcyBkZSAwLjkwODc4NzUsIGVzIGRlY2lyLCBlbiBsYXMgcHJlZGljY2lvbmVzIHF1ZSByZWFsaWNlbW9zIGNvbiBsb3Mgw6FyYm9sZXMgZXN0YXJlbW9zIGFjZXJ0YW5kbyBlbiBlbCA5MC45MSUgZGUgbGFzIG1pc21hcy4gDQoNCkZpbmFsbWVudGUgcGFyYSBib3NxdWVzIGFsZWF0b3Jpb3Mgc29tb3MgY2FwYWNlcyBkZSBpZGVudGlmaWNhciBxdcOpIHZhcmlhYmxlcyBzb24gbcOhcyB5IG1lbm9zIHJlbGV2YW50ZXMgcGFyYSBudWVzdHJvIGFuw6FsaXNpcy4gU2kgbm9zIGZpamFtb3MsIGFsIGJvc3F1ZSBsZSBwb2RlbW9zIGludHJvZHVjaXIgZWwgbsO6bWVybyBkZSB2YXJpYWJsZXMgcXVlIHRlbmdhIGVuIGN1ZW50YSBwYXJhIGVsIGFuw6FsaXMsIGVzdG8gbG8gcG9kZW1vcyBnZXN0aW9uYXIgY29uIGF5dWRhIGRlIFZhcmltcGxvdCgpLCBkZSBlc3RhIGZvcm1hLCBzaSB2ZW1vcyBxdWUgaGF5IHggdmFyaWFibGVzIG1lbm9zIHJlbGV2YW50ZXMgc2VsZWNjaW9uYW1vcyB1biBuw7ptZXJvIG1lbm9yIGRlIHZhcmlhYmxlcyBwYXJhIGVsIGJvc3F1ZSBwYXJhIGFjZWxlcmFyIGVsIHByb2Nlc28geSBubyBjb250YXIgY29uIGFxdWVsbGFzIHZhcmlhYmxlcyBxdWUgbm8gYXBvcnRhbi5FbiBudWVzdHJvIGNhc28sIGxvIGdlbmVyYWwgcGFyYSBsbyBjb23Dum4gZW4gbG9zIHRyZXMgw6FyYm9sZXMgZXMgb2JzZXJ2YXIgVGlwb19GdWVsIHkgU2VndXJpZGFkIGNvbW8gcG9jbyByZWxldmFudGVzIHkgQ2FiYWxsb3MgeSBFZGFkIGNvbW8gbXV5IHJlbGV2YW50ZXMuIA0KDQpgYGB7cn0NCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KIyBjb25qdW50byAxDQpzZXQuc2VlZCgzKQ0KZm9yZXN0MTwtcmFuZG9tRm9yZXN0KGdydXBvX3ByZWNpb34uLGRhdGE9dHJhaW4xLG10cnk9NywgaW1wb3J0YW5jZT1UUlVFKQ0KZm9yZXN0LnByZWQxPC1wcmVkaWN0KGZvcmVzdDEsbmV3ZGF0YT10ZXN0MSx0eXBlPSJjbGFzcyIpDQpjb25mdXNpb25iMT10YWJsZShmb3Jlc3QucHJlZDEsdGVzdDEkZ3J1cG9fcHJlY2lvKQ0KYWNpZXJ0bz0wDQpmb3IgKGkgaW4gYygxOm5yb3coY29uZnVzaW9uYjEpKSl7DQogIGFjaWVydG89YWNpZXJ0bytjb25mdXNpb25iMVtpLGldDQogIHRhc2EuYWNpZXJ0bz1hY2llcnRvL3N1bShjb25mdXNpb25iMSkNCiAgdGFzYS5lcnJvcj0xLXRhc2EuYWNpZXJ0bw0KfQ0KYWNpZXJ0b2IxPXRhc2EuYWNpZXJ0bw0KZXJyb3JiMT10YXNhLmVycm9yDQoNCnNldC5zZWVkKDMpDQpmb3Jlc3QyPC1yYW5kb21Gb3Jlc3QoZ3J1cG9fcHJlY2lvfi4sZGF0YT10cmFpbjIsbXRyeT03LCBpbXBvcnRhbmNlPVRSVUUpDQpmb3Jlc3QucHJlZDI8LXByZWRpY3QoZm9yZXN0MixuZXdkYXRhPXRlc3QyLHR5cGU9ImNsYXNzIikNCmNvbmZ1c2lvbmIyPXRhYmxlKGZvcmVzdC5wcmVkMix0ZXN0MiRncnVwb19wcmVjaW8pDQphY2llcnRvPTANCmZvciAoaSBpbiBjKDE6bnJvdyhjb25mdXNpb25iMikpKXsNCiAgYWNpZXJ0bz1hY2llcnRvK2NvbmZ1c2lvbmIyW2ksaV0NCiAgdGFzYS5hY2llcnRvPWFjaWVydG8vc3VtKGNvbmZ1c2lvbmIyKQ0KICB0YXNhLmVycm9yPTEtdGFzYS5hY2llcnRvDQp9DQphY2llcnRvYjI9dGFzYS5hY2llcnRvDQplcnJvcmIyPXRhc2EuZXJyb3INCg0Kc2V0LnNlZWQoMykNCmZvcmVzdDM8LXJhbmRvbUZvcmVzdChncnVwb19wcmVjaW9+LixkYXRhPXRyYWluMyxtdHJ5PTcsIGltcG9ydGFuY2U9VFJVRSkNCmZvcmVzdC5wcmVkMzwtcHJlZGljdChmb3Jlc3QzLG5ld2RhdGE9dGVzdDMsdHlwZT0iY2xhc3MiKQ0KY29uZnVzaW9uYjM9dGFibGUoZm9yZXN0LnByZWQzLHRlc3QzJGdydXBvX3ByZWNpbykNCmFjaWVydG89MA0KZm9yIChpIGluIGMoMTpucm93KGNvbmZ1c2lvbmIzKSkpew0KICBhY2llcnRvPWFjaWVydG8rY29uZnVzaW9uYjNbaSxpXQ0KICB0YXNhLmFjaWVydG89YWNpZXJ0by9zdW0oY29uZnVzaW9uYjMpDQogIHRhc2EuZXJyb3I9MS10YXNhLmFjaWVydG8NCn0NCmFjaWVydG9iMz10YXNhLmFjaWVydG8NCmVycm9yYjM9dGFzYS5lcnJvcg0KDQpjYXQoIkVsIG3DqXRvZG8gZGUgQm9zcXVlcyBBbGVhdG9yaW9zIHRpZW5lIHVuYSBtZWRpYSBkZSBhY2llcnRvIGRlIixzdW0oYWNpZXJ0b2IxLGFjaWVydG9iMixhY2llcnRvYjMpLzMpDQp2YXJJbXBQbG90KGZvcmVzdDEpDQp2YXJJbXBQbG90KGZvcmVzdDIpDQp2YXJJbXBQbG90KGZvcmVzdDMpDQoNCg0KYGBgDQoNCiMjIFByZWRpY2Npw7NuIG3DqXRvZG8gw4FyYm9sIGRlIENsYXNpZmlhY2nDs24geSBSYW5kb20gRm9yZXN0DQoNClBhcmEgY29tcGxldGFyIGVsIGluZm9ybWUsIGhlbW9zIGdlbmVyYWRvIHVuYSBudWV2YSBiYXNlIGRlIGRhdG9zIGRvbmRlIGhlbW9zIHNpbXVsYWRvIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGRlIDEyIGF1dG9tw7N2aWxlcyBxdWUgZW50cmFuIGFsIHNpc3RlbWEgdW4gZMOtYSBkYWRvLkNhYmUgZGVzdGFjYXIgcXVlIGVuIGVzdGEgYmFzZSBkZSBkYXRvcyBzZSBpbmNsdXllIGVsIHByZWNpbyBkZWwgYXV0b23Ds3ZpbCwgZXN0byBubyBlcyByZWFsaXN0YSBwb3JxdWUgc2kgcXVlcmVtb3MgdGFzYXIgbm8gbm9zIHZhbiBhIGRhciBlbCBwcmVjaW8gZGVsIGF1dG9tw7N2aWwsIHNpbXBsZW1lbnRlIGxvIGhhY2Vtb3MgcGFyYSBkZW1vc3RyYXIgY8OzbW8gZnVuY2lvbmEgZWwgYW7DoWxpc2lzLiBQYXJhIHJlYWxpemFyIGxhcyBwcmVkaWNjaW9uZXMgcGFyYSDDgXJib2wgZGUgQ2xhc2lmaWNhY2nDs24geSBSYW5kb20gRm9yZXN0IGRlYmVtb3MgdXRpbGl6YXIgbGEgYmFzZSBkZSBkYXRvcyBhbCBjb21wbGV0bywgZWwgdHJhaW4geSB0ZXN0IGVyYSBzaW1wbGVtZW50ZSBwYXJhIHByb2JhciBlbCBtb2RlbG8sIHVuYSB2ZXogc2FiZW1vcyBjw7NtbyBmdW5jaW9uYW4gdXRpbGl6YW1vcyBlbCAxMDAlIGRlIGxvcyBkYXRvcyBwYXJhIHByZWRlY2lyIGxvcyBudWV2b3MuIERlIGVzdGEgZm9ybWEgdGVuZW1vcyBsb3Mgc2lndWllbnRlcyByZXN1bHRhZG9zOiANCg0KLSAgIEVsIG3DqXRvZG8gZGUgw4FyYm9sIGRlIENsYXNpZmljYWNpw7NuIHNlIGVxdWl2b2NhIGVuIDMgZGUgMTIgb2Nhc2lvbmVzLCBkb3MgZGUgZWxsYXMgbm8gdGllbmVuIGEgcGVuYXMgcmVsZXZhbmNpYSwgcHVlcyBzZSBlcXVpdm9jYSBjb24gdW5hIGRpZmVyZW5jaWEgZGUgcHJlY2lvIGRlIHRhbiBzb2xvIGhhc3RhIDYwMOKCrC4gRXMgZGVjaXIsIHNpdMO6YSBwb3IgZGViYWpvIGRlIGxhIG1lZGlhIHVuIGF1dG9tw7N2aWwgZGUgMTg5OTnigqwgeSBvdHJvIGRlICAxODUwMOKCrC4gU2luIGVtYmFyZ28sIHNpIG5vcyBmaWphbW9zIGVuIGVsIHByZWNpbyBkZWwgTWVyY2VkZXMtQmVueiBDbHMsIGNvbWV0ZSB1biBncmF2ZSBlcnJvciwgcHVlcyBlc3RlIGRpY2UgcXVlIGVzdMOhIHBvciBlbmNpbWEgZGUgbGEgbWVkaWEgY3VhbmRvIHJlYWxtZW50ZSB2YWxlIDEyOTAw4oKsLCBjcmVlbW9zIHF1ZSBlc3RvIHB1ZWRlIGRlYmVyc2UgcG9yIGxhIGltcG9ydGFuY2lhIHF1ZSBkYSBhIGxhIG1hcmNhLg0KDQotICAgRWwgbcOpdG9kbyBkZSBSYW5kb20gRm9yZXN0IHNpbiBlbWJhcmdvIGV2aXRhIGVsIGVycm9yIGRlbCBDbHMsIHBvciBsbyBxdWUgZXMgbXVjaG8gbcOhcyBwcmVjaXNvIHkgc29sYW1lbnRlIGNhZSBlbiBsb3MgZG9zIGFudGVzIGNvbWVudGFkb3MgY29uIHVuYSBkaWZyZW5jaWEgZGUgcHJlY2lvIHJlYWxtZW50ZSBiYWphLiANCg0KYGBge3IsIGZpZy5hbGlnbj0ibGVmdCJ9DQpzZXQuc2VlZCgzKQ0KbGlicmFyeSh0cmVlKQ0KI2EpIEFyYm9sIGRlIGNsYXNpZmljYWNpw7NuLS0tDQp0cmVlLkNvY2hlcyA8LSB0cmVlIChncnVwb19wcmVjaW9+LixkYXRhID0gZGF0b3MyKQ0KcHJlZGVjaXIkcHJlZGljY2lvbl9hcmJvbDwtcHJlZGljdCh0cmVlLkNvY2hlcyxuZXdkYXRhPXByZWRlY2lyLHR5cGU9ImNsYXNzIikNCg0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQojIGNvbmp1bnRvIDENCnNldC5zZWVkKDMpDQpmb3Jlc3QxPC1yYW5kb21Gb3Jlc3QoZ3J1cG9fcHJlY2lvfi4sZGF0YT1kYXRvczIsbXRyeT03LCBpbXBvcnRhbmNlPVRSVUUpDQpwcmVkZWNpciRwcmVkaWNjaW9uX2ZvcmVzdDwtcHJlZGljdChmb3Jlc3QxLG5ld2RhdGE9cHJlZGVjaXIsdHlwZT0iY2xhc3MiKQ0Ka25pdHI6OmthYmxlKHByZWRlY2lyKQ0KYGBgDQoNCiMjIENvbmNsdXNpw7NuDQoNCkNvbW8gc2UgaGEgaWRvIGNvbWVudGFkbyBkdXJhbnRlIGVsIGluZm9ybWUsIGxvcyBkb3MgbcOpdG9kb3MgZnVuY2lvbmFuIGJhc3RhbnRlIGJpZW4geSBjYWRhIHVubyB0aWVuZSB1biBlbmZvcXVlIGRpc3RpbnRvLiBNaWVudHJhIHF1ZSBlbCDDoXJib2wgZGUgY2xhc2lmaWNhY2nDs24gb2ZyZWNlIHVuYSBncmFuIGNvbW9kaWRhZCB5IHZlcnNhdGlsaWRhZCBwYXJhIHRvbWFyIGRlY2lzaW9uZXMgZW4gZWwgbW9tZW50byBzaW4gdGVuZXIgY29ub2NpbWllbnRvcyBlc3BlY2lhbGl6YWRvcywgZWwgbcOpdG9kbyBkZSBSYW5kb20gRm9yZXN0IG9mcmVjZSB1bmEgbWVqb3IgcHJlZGljY2nDs24gY29uIGVycm9yZXMgbWVub3MgZ3JhdmVzIGFqdXN0YW5kbyBtdWNobyBtw6FzIGxvcyBwcmVjaW9zIHF1ZSBidXNjYW1vcy4gDQoNCkFkZW3DoXMgY2FiZSBkZXN0YWNhciBlbiBsYXMgY29uY2x1c2lvbmVzIGVsIHRhbWHDsW8gZGUgbGEgYmFzZSBkZSBkYXRvcy4gUXVlcmVtb3MgcHJlZGVjaXIgZWwgcHJlY2lvIGRlIHVuIGNvY2hlIGVuIGZ1bmNpw7NuIGRlIGxhcyB2YXJpYWJsZXMgYW50ZXMgY29tZW50YWRhcywgcGVybyB0ZW5lbW9zIMO6bmljYW1lbnRlIDM0MCBkYXRvcyBwYXJhIHVuIHNpc3RlbWEgdGFuIGNvbXBsZWpvLiBDb21vIGhlbW9zIHZpc3RvLCBoZW1vcyB0ZW5pZG8gcXVlIHJlYWxpemFyIGFwYcOxb3MgImNhbWJpYW5kbyIgbGEgdmFyaWFibGUgQ29jaGUgeSBNb2RlbG8gcG9yIEx1am8uIFBvciBsbyBxdWUgY3JlZW1vcyBxdWUgY29uIHVuYSBiYXNlIGRlIGRhdG9zIHJlYWxtZW50ZSBncmFuZGUgZG9uZGUgcHVkacOpcmFtb3MgdG9tYXIgbGFzIGVzcGVjaWZpY2FjaW9uZXMgbXVjaG8gbcOhcyBhbCBkZWRvIGNvbiBtw6FzIGluZGl2aWR1b3MgcmVwcmVzZW50YXRpdm9zIGRlIGNhZGEgZmFjdG9yIHNlIHBvZHLDrWEgcmVhbGl6YXIgdW4gYW7DoWxpc2lzIG11Y2hvIG3DoXMgc29maXN0aWNhZG8gc2llbmRvIGNhcGFjZXMgZGUgcHJlZGVjaXIgY29uIG11Y2hhIG3DoXMgY3JlZGliaWxpZGFkIHkgcHJlY2lzacOzbi4gDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0K