Librerías

library(dplyr)
library(tidymodels)
library(GGally)
library(purrr)
library(kableExtra)

Funciones Auxiliares

# Dado un valor, devuelve un char si se lo considera alto, medio o bajo de acuerdo a los límites preestablecidos.
setClasificacion <-function(valor) {
  ifelse(valor>=3316,'alto',ifelse(valor<=2379,'bajo','medio'))
}
# Dado un DataFrame, ejecuta regresión lineal múltiple de acuerdo al modelo4.
runModel<-function(datos){
  tidy(lm(price ~ rooms + bathrooms + surface_covered + barrios + surface_patio, data=datos))
}

Preparación de datos

Se elimina la variable id y se transforman las variables categóricas a factor.

propiedades<-readRDS(file="ar_properties.rds")
propiedades<- propiedades %>% select(-id)
propiedades$l3<-as.factor(propiedades$l3)
propiedades$property_type<-as.factor(propiedades$property_type)

Regresión Lineal Múltiple

Modelo1

Se propone un modelo aditivo de regresión lineal múltiple para explicar el precio de las propiedades en función de 6 variables (surface_covered, surface_total, rooms, bathrooms, l3, property_type), de las cuales 4 son continuas y 2 son categóricas de 57 y 3 niveles respectivamente.

modelo1<-lm(price ~ ., data=propiedades)
modelo1_tidy<-tidy(modelo1)
modelo1_tidy

Interpretación de los coeficientes

Al tener dos variables categóricas con más de dos categorías se hace más complejo el análisis de los coeficientes. El valor intercept(\(\beta_0\)) es de -109406.61 y sería el valor promedio de los precios de una propiedad que no tiene ninguna habitación, ningún baño, no tiene superficie cubierta ni superficie total y que pertenece al grupo de referencia, es decir cuando el Barrio es Abasto y el tipo de propiedad es una Casa. Por lo tanto éste \(\beta_0\) no es relevante si se lo separa del término del modelo de regresión lineal.

  • \(\beta_{l3Barrio_i}\) es el cambio en el valor esperado de los precios del \({Barrio_i}\) respecto del barrio de Abasto cuando se controla por tipo de propiedad y se mantienen constantes las otras variables del modelo.
  • \(\beta_{property_i}\) es el cambio en el valor esperado de los precios del \({PropertyType_i}\) respecto de una propiedad del tipo Casa cuando se controla por barrio y se mantienen constantes las otras variables del modelo.
  • \(\beta_{rooms}\) es el valor promedio de una propiedad cuando aumentamos en un ambiente y se mantienen las demás variables constantes. En este caso el valor(-3961.27) es negativo; es decir el valor de la propiedad disminuye en promedio al agregar un ambiente.
  • \(\beta_{bathrooms}\) es el valor promedio de una propiedad cuando agregamos un baño a la misma y se mantienen las demás variables constantes. En este caso el valor de la propiedad aumenta en promedio en USD 34040.98.
  • \(\beta_{surfaceTotal}\) es el valor promedio de una propiedad cuando aumentamos un metro cuadrado en la superficie total y se mantienen las demás variables constantes.En este caso el valor de la propiedad aumenta en promedio en USD 919.
  • \(\beta_{surfaceCovered}\) es el valor promedio de una propiedad cuando aumentamos un metro cuadrado en la superficie cubierta y se mantienen las demás variables constantes.En este caso el valor de la propiedad aumenta en promedio en USD 1457.

Significatividad de las variables

Todos los p-valores de las variables continuas y de la categórica property_type en este modelo son estadísticamente significativas (<0.05). Se observa a través de las variables dummies de l3 que no todos los barrios influyen en la misma proporción en el precio medio de las propiedades. Se encuentran 13 barrios que no contribuyen de forma significativa al modelo.

modelo1_tidy[modelo1_tidy$p.value >0.05,]$term
 [1] "l3Agronomía"        "l3Almagro"          "l3Barracas"         "l3Caballito"       
 [5] "l3Monte Castro"     "l3Parque Chas"      "l3San Telmo"        "l3Villa Crespo"    
 [9] "l3Villa del Parque" "l3Villa Luro"       "l3Villa Real"       "l3Villa Riachuelo" 
[13] "l3Villa Santa Rita"

Medidas de Evaluación

El coeficiente de determinación múltiple ajustado \(R^2_a\) es 77,61% que representa el porcentaje de variación de la variable precio que puede ser explicada por este modelo. Se utiliza \(R^2_a\) en lugar de \(R^2\) ya que penaliza los modelos con muchas variables explciativas que no contribuyen al modelo.
Usamos el Test F para evaluar si existe o no relación entre las variables predictorias y la respuesta. Se plantea como \(H_0\) que no existe relación entre las variables (l3, rooms, bathrooms, surface_total, surface_covered y property_type) y precio; \(H_1\) que existe una relación lineal entre ellas. En nuestro modelo; en este caso se obtiene un valor alto igual a 2567,58 con un p-value < 2.2e-16 por lo tanto se rechaza \(H_0\) y se puede indicar que existe relación entre la respuesta y al menos una de las variables predictorias. El modelo planteado es correcto.

glance(modelo1)

¿Qué es preferible tener para vender?

En base al modelo obtenido se estima el precio de cada propiedad de acuerdo a sus características utilizando la función predict. En la salida se obtienen tres valores; fit seria la predicción del valor de la propiedad con esos datos especifico dentro de los límites inferior(lwr) y superior(upr) con una confianza del 95%.

  • Un departamento de 120 mts cuadrados cubiertos en Abasto, con 3 dormitorios y 2 baños:
    \[Y=-109406,61 + 3*(-3961.27) + 2*34040.98 + 120*1457.17 + 100*919,08 + 92653.31\] \[Y= 324596.4\]
  • Un PH en Balvanera, con 80 mts cuadrados cubiertos, 20 mts cuadrados no cubiertos, 2 dormitorios y 3 baños. \[Y=-109406,61 +(-24788.27) + 2*(-3961.27) + 3*34040.98 + 80*1457.17 + 100*919,08 + 46779.37\] \[Y= 215267,6\] Es preferible vender el Departamento en el Abasto.
newdata <- data.frame(l3="Abasto", rooms=3, bathrooms=2, surface_total=120, surface_covered=120, property_type="Departamento")

predict(modelo1, newdata, interval="predict", level = 0.95)
       fit      lwr      upr
1 324596.4 193837.5 455355.3
newdata <- data.frame(l3="Balvanera", rooms=2, bathrooms=3, surface_total=100, surface_covered=80, property_type="PH")

predict(modelo1, newdata, interval="predict", level = 0.95)
       fit      lwr      upr
1 215267.6 84678.03 345857.2

Modelo2

Se elimina la variable l3 del modelo y se observa a partir de los resultados que todas las variables explicativas son estadísticamente significativas(p-valor<0.05). Se observa que los precios promedios de los departamentos se ubica por encima del de las casas(\(\beta_0 + \beta_{propertyDepto}\) =-131096+135177=4081) mientras que el precio promedio de los PHs con respecto a las casas se ubica por debajo (\(\beta_0 + \beta_{propertyPH}\) =-131096+68598=-62498). Se explica en este modelo un 68,32% de la variabilidad de los precios y el Test F siendo de 16490 con un p-value < 2.2e-16 indica que este modelo también es correcto.

modelo2<-lm(price ~rooms + bathrooms + surface_total + surface_covered + property_type , data=propiedades)
tidy(modelo2)
glance(modelo2)

Comparar Modelo1 y Modelo2

Comparo por el coeficiente de determinación múltiple ajustado \(R^2\) ya que tiende a aumentar cuando se agregan más covariables aunque éstas sean o no apropiadas para el modelo y teniendo estos modelos diferente número de covariables, se observa que el \(R^2_a\) es mayor en el modelo1 en un 9% (77.6%-68.3%). Es decir, que la covariable l3 es importante para predecir la variabilidad de los precios de las propiedades. Se concluye que el modelo1 es el que mejor explica la variable respuesta.

Creación de Variables

Barrios: Nueva Variable

Se debe calcular el precio por metro cuadrado promedio de las propiedades en cada barrio y en base a sus valores definir los puntos de corte para clasificarlos en tres grupos: alto, medio y bajo. Considerando los cuartiles de esta nueva variable defino; alto cuando esta nueva variable es mayor o igual a 3316; bajo cuando es menor o igual a 2379 y medio entre (2379,3316).
Se crearon dos variables auxiliares para definir la nueva, una para obtener el precio por metro cuadrado de cada propiedad y otra pra calcular el promedio de ésta última para cada barrio.

propiedades<-propiedades %>%
  mutate(pricexmts=price/surface_total) %>%
  group_by(l3) %>%
  nest() 

propiedades<-propiedades %>%
  mutate(meanpricexmts=map_dbl(data,function(x) mean(x$pricexmts))) %>%
  unnest(data)

propiedades$meanpricexmts<-round(propiedades$meanpricexmts, digits = 2)

summary(propiedades$meanpricexmts)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1093    2379    2610    2768    3316    5472 
propiedades<-propiedades %>% mutate(barrios=map_chr(meanpricexmts,setClasificacion)) %>% ungroup

propiedades$barrios<-as.factor(propiedades$barrios)

table(propiedades$barrios)

 alto  bajo medio 
11816 11519 22569 

Modelo3 y Comparación con Modelo1

Al agregar la nueva variable barrios y construir un modelo con la misma sin considerar l3 se observa que el \(R^2_a\) disminuyó aunque no en un porcentaje considerablemente alto. En el Modelo3 tenemos explicado un 74% de variabilidad de los precios mientras que en el Modelo1 habíamos alcanzado un 77%. Se destaca que todas las variables de este nuevo modelo son estadísticamente significativas y se observa que los coeficientes de los parámetros aumentaron a excepción de intercept y de surface_total que disminuyeron. La clasificación de los barrios de acuerdo al precio x metros cuadrados promedio de cada uno ayuda a explicar el modelo y se observa que el precio promedio de los barrios clasificados tanto como ‘bajo’ y ‘medio’ están por debajo del de los clasificados como ‘alto’ para las propiedades de tipo Casa.
Este modelo lo considero mejor que el Modelo1 porque no se tienen variables que no ayudan al modelo y es más fácil de interpretar aún perdiendo un 3% de variabilidad.

modelo3<-lm(price ~ rooms + bathrooms + surface_total + surface_covered + property_type + barrios, data=propiedades)
summary(modelo3)

Call:
lm(formula = price ~ rooms + bathrooms + surface_total + surface_covered + 
    property_type + barrios, data = propiedades)

Residuals:
    Min      1Q  Median      3Q     Max 
-445722  -36280   -6020   26105  677385 

Coefficients:
                           Estimate Std. Error t value Pr(>|t|)    
(Intercept)               -45273.19    2645.14  -17.12   <2e-16 ***
rooms                      -7831.99     474.32  -16.51   <2e-16 ***
bathrooms                  35582.69     690.40   51.54   <2e-16 ***
surface_total                895.82      25.04   35.78   <2e-16 ***
surface_covered             1636.40      30.46   53.72   <2e-16 ***
property_typeDepartamento 101782.63    2309.46   44.07   <2e-16 ***
property_typePH            51162.33    2436.69   21.00   <2e-16 ***
barriosbajo               -96229.28     976.41  -98.55   <2e-16 ***
barriosmedio              -54270.27     823.12  -65.93   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 71880 on 45895 degrees of freedom
Multiple R-squared:  0.7391,    Adjusted R-squared:  0.739 
F-statistic: 1.625e+04 on 8 and 45895 DF,  p-value: < 2.2e-16

Superficie_patio: Nueva Variable

Se observa una correlación muy fuerte(0.95) entre surface_total y surface_covered. Además en el 28% de los casos se registra el mismo valor; es decir cuando las propiedades no tienen una superficie descubierta. En el caso de que exista una incompatibilidad en los datos, por ejemplo \({surface\_covered} > {surface\_total}\) implica que tengamos la totalidad de la superficie en la variable surface_covered; en estos casos se suma la superficie descubierta surface_patio (dado que patio es negativo) y se ajusta el signo de esta última. Sin embargo no se han encontrado registros con estas características en el dataset.

propiedades %>%
  keep(is.numeric) %>%
  ggcorr(low = "darkred", mid = "white", high = "steelblue", label = T, label_round =
           2, size = 4,  color = "grey50",  angle = -45)


propiedades<-propiedades %>% 
  mutate(surface_patio=surface_total-surface_covered)

#Calcular el % de las propiedades sin sup descubierta.
nrow(propiedades[propiedades$surface_patio==0,])/nrow(propiedades)
[1] 0.2886459
#En caso de incompatibilidad, se ajustan los datos.
if (nrow(propiedades[propiedades$surface_patio < 0,]) > 0){
  propiedades<-propiedades %>% 
  mutate(surface_covered=ifelse(surface_patio<0, surface_covered+surface_patio, surface_covered), 
         surface_patio=ifelse(surface_patio<0,(-1)*surface_patio,surface_patio))
}

Modelo4

Agregar la variable surface_patio no mejoró el \(R^2_a\). El coeficiente de surface_covered se incrementó y ahora se tiene que cada metro cuadrado cubierto tiene un incremento del 2532.22 en el precio promedio de las propiedades.
En cuanto al parámetro de surface_patio se observa el mismo coeficiente que se tenia en surface_total en el modelo anterior.

modelo4<-lm(price ~ rooms + bathrooms + surface_covered + property_type + barrios + surface_patio, data=propiedades)
summary(modelo4)

Call:
lm(formula = price ~ rooms + bathrooms + surface_covered + property_type + 
    barrios + surface_patio, data = propiedades)

Residuals:
    Min      1Q  Median      3Q     Max 
-445722  -36280   -6020   26105  677385 

Coefficients:
                           Estimate Std. Error t value Pr(>|t|)    
(Intercept)               -45273.19    2645.14  -17.12   <2e-16 ***
rooms                      -7831.99     474.32  -16.51   <2e-16 ***
bathrooms                  35582.69     690.40   51.54   <2e-16 ***
surface_covered             2532.22      16.06  157.71   <2e-16 ***
property_typeDepartamento 101782.63    2309.46   44.07   <2e-16 ***
property_typePH            51162.33    2436.69   21.00   <2e-16 ***
barriosbajo               -96229.28     976.41  -98.55   <2e-16 ***
barriosmedio              -54270.27     823.12  -65.93   <2e-16 ***
surface_patio                895.82      25.04   35.78   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 71880 on 45895 degrees of freedom
Multiple R-squared:  0.7391,    Adjusted R-squared:  0.739 
F-statistic: 1.625e+04 on 8 and 45895 DF,  p-value: < 2.2e-16

Evaluación del Modelo4

Analizar Residuos

Para aplicar el modelo de regresión lineal los residuos deben tener una distribución normal. En los gráficos qqnorm, los quantiles tiende a seguir los quantiles de una distribución normal aunque hay varios puntos alejados de esa tendencia. Por el teorema central del limite se puede decir que se cumple este supuesto. Otro que debiese cumplirse es el de la homocedasticidad y vemos a través de los gráficos que los residuos tienen cierta estructura.


residuos <-modelo4$residuals
qqnorm(residuos)
qqline(residuos)


plot(modelo4$fitted, modelo4$residuals, xlab = "Fitted Values", ylab = "Residuals")
abline(0,0)

Comparar con Modelo Propuesto

Aplicando logaritmo a las variables propuestas se observa una mejoría al realizar los gráficos para analizar los supuestos. Los quantiles se acercan más a los quantiles de una distribución normal aunque se sigue visualizando cierto patrón en los residuos pero con una distribución más uniforme. El \(R^2_a\) mejoró de 74% a 81% aunque en este modelo explica la variación en log(price) y no en price como el modelo anteior.
Analizando los coeficientes se observa que:

  • por cada habitación que se agrega a una propiedad hay una disminución del 0.04% en el precio de las propiedades.
  • por cada baño que se agrega a una propiedad hay una aumento del 0.17% en el precio de las propiedades.
  • por cada metro cuadrado que se agrega en la superficie cubierta de una propiedad hay una aumento del 0.82% en el precio de las propiedades.
  • el precio de las propiedades aumenta un 23% si la propiedad es un Departamento, 6% si es PH, 0.39% por cada metro cuadrado que se agrega al patio.
  • el precio de las propiedades disminuye un 42% si se encuentra en un barrio clasificado como bajo y un 20% en uno como medio.
modelo5<-lm(log(price) ~ log(rooms) + log(bathrooms) + log(surface_covered) + property_type + barrios + surface_patio, data=propiedades)
summary(modelo5)

Call:
lm(formula = log(price) ~ log(rooms) + log(bathrooms) + log(surface_covered) + 
    property_type + barrios + surface_patio, data = propiedades)

Residuals:
     Min       1Q   Median       3Q      Max 
-1.37399 -0.15780 -0.01026  0.14704  1.30806 

Coefficients:
                            Estimate Std. Error  t value Pr(>|t|)    
(Intercept)                8.653e+00  1.941e-02  445.708  < 2e-16 ***
log(rooms)                -4.228e-02  3.919e-03  -10.790  < 2e-16 ***
log(bathrooms)             1.787e-01  3.953e-03   45.216  < 2e-16 ***
log(surface_covered)       8.274e-01  4.592e-03  180.168  < 2e-16 ***
property_typeDepartamento  2.328e-01  7.481e-03   31.119  < 2e-16 ***
property_typePH            6.004e-02  7.925e-03    7.575 3.66e-14 ***
barriosbajo               -4.283e-01  3.207e-03 -133.560  < 2e-16 ***
barriosmedio              -1.996e-01  2.704e-03  -73.809  < 2e-16 ***
surface_patio              3.942e-03  8.246e-05   47.810  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.2362 on 45895 degrees of freedom
Multiple R-squared:  0.8079,    Adjusted R-squared:  0.8079 
F-statistic: 2.413e+04 on 8 and 45895 DF,  p-value: < 2.2e-16
residuos <-modelo5$residuals
qqnorm(residuos)
qqline(residuos)


plot(modelo5$fitted, modelo5$residuals, xlab = "Fitted Values", ylab = "Residuals")
abline(0,0)

Dataframes Anidados

Anidar por property_type

propiedades<-propiedades %>%
  group_by(property_type) %>%
  nest()

Construir varios modelos

La variable rooms se comporta diferente en los tres modelos; en el caso del PH no es significativa, en los departamentos agregar un ambiente implica una disminución en el promedio de los precios mientras que en las Casas implica un aumento considerando las demás variables constantes.
En los tres modelos sin importar el tipo de propiedad se encuentra que las propiedades ubicadas en los barrios que tienen precios por superficie muy altos incrementan el precio promedio de las mismas; mientras que en los otros dos casos(bajo y medio) disminuyen el precio en promedio.
Se observa que el desvío estándar en el modelo de Departamento es el menor de todos para cada variable, esto indicaría que se ajusta mejor para las propiedades de tipo Departamento que para las Casas o PH.

propiedades<-propiedades %>%
  mutate(lm=map(data, runModel)) %>%
  unnest(lm)

propiedades %>%
  select(-data) %>%
  kable() %>% 
  kable_styling(bootstrap_options = c("striped", "hover", "condensed", "responsive"))
property_type term estimate std.error statistic p.value
Casa (Intercept) 206894.3067 13908.12317 14.875789 0.0000000
Casa rooms 6910.6527 2473.10121 2.794327 0.0052854
Casa bathrooms 29754.1323 3656.77542 8.136713 0.0000000
Casa surface_covered 1124.0869 63.60352 17.673344 0.0000000
Casa barriosbajo -203342.1874 11563.64468 -17.584611 0.0000000
Casa barriosmedio -128606.3944 11868.46071 -10.835979 0.0000000
Casa surface_patio 510.5541 97.02695 5.261983 0.0000002
Departamento (Intercept) 46101.9295 1106.49784 41.664726 0.0000000
Departamento rooms -13214.0639 505.05795 -26.163461 0.0000000
Departamento bathrooms 31267.3508 738.26585 42.352427 0.0000000
Departamento surface_covered 2951.0291 17.83451 165.467388 0.0000000
Departamento barriosbajo -90899.0536 1005.52440 -90.399650 0.0000000
Departamento barriosmedio -53103.0289 824.11241 -64.436633 0.0000000
Departamento surface_patio 1012.8329 30.96091 32.713276 0.0000000
PH (Intercept) 103139.9790 3447.71818 29.915432 0.0000000
PH rooms 1262.3017 1068.25328 1.181650 0.2374089
PH bathrooms 21595.6038 1531.44268 14.101477 0.0000000
PH surface_covered 1351.1598 31.62260 42.727660 0.0000000
PH barriosbajo -99229.2215 2626.54732 -37.779339 0.0000000
PH barriosmedio -48061.4893 2600.57910 -18.481072 0.0000000
PH surface_patio 648.5723 35.45137 18.294703 0.0000000
LS0tDQp0aXRsZTogIlRQMDIgLSBFc3RlZmFuaWEgRGUgTWFyemlvIg0KYXV0aG9yOiAiQ29taXNpw7NuIERpZWdvIEtvemxvd3NraSINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6IA0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGRlcHRoOiAyDQotLS0NCg0KIyBMaWJyZXLDrWFzDQpgYGB7ciBsaWJyZXJpYXMsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5bW9kZWxzKQ0KbGlicmFyeShHR2FsbHkpDQpsaWJyYXJ5KHB1cnJyKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KYGBgDQojIEZ1bmNpb25lcyBBdXhpbGlhcmVzDQpgYGB7ciBmdW5jaW9uZXN9DQojIERhZG8gdW4gdmFsb3IsIGRldnVlbHZlIHVuIGNoYXIgc2kgc2UgbG8gY29uc2lkZXJhIGFsdG8sIG1lZGlvIG8gYmFqbyBkZSBhY3VlcmRvIGEgbG9zIGzDrW1pdGVzIHByZWVzdGFibGVjaWRvcy4NCnNldENsYXNpZmljYWNpb24gPC1mdW5jdGlvbih2YWxvcikgew0KICBpZmVsc2UodmFsb3I+PTMzMTYsJ2FsdG8nLGlmZWxzZSh2YWxvcjw9MjM3OSwnYmFqbycsJ21lZGlvJykpDQp9DQojIERhZG8gdW4gRGF0YUZyYW1lLCBlamVjdXRhIHJlZ3Jlc2nDs24gbGluZWFsIG3Dumx0aXBsZSBkZSBhY3VlcmRvIGFsIG1vZGVsbzQuDQpydW5Nb2RlbDwtZnVuY3Rpb24oZGF0b3Mpew0KICB0aWR5KGxtKHByaWNlIH4gcm9vbXMgKyBiYXRocm9vbXMgKyBzdXJmYWNlX2NvdmVyZWQgKyBiYXJyaW9zICsgc3VyZmFjZV9wYXRpbywgZGF0YT1kYXRvcykpDQp9DQpgYGANCg0KIyBQcmVwYXJhY2nDs24gZGUgZGF0b3MNCjxkaXYgc3R5bGU9InRleHQtYWxpZ246IGp1c3RpZnkiPg0KU2UgZWxpbWluYSBsYSB2YXJpYWJsZSBpZCB5IHNlIHRyYW5zZm9ybWFuIGxhcyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzIGEgZmFjdG9yLg0KPC9kaXY+DQpgYGB7ciBwcmVwYXJhY2lvbkRhdG9zfQ0KcHJvcGllZGFkZXM8LXJlYWRSRFMoZmlsZT0iYXJfcHJvcGVydGllcy5yZHMiKQ0KcHJvcGllZGFkZXM8LSBwcm9waWVkYWRlcyAlPiUgc2VsZWN0KC1pZCkNCnByb3BpZWRhZGVzJGwzPC1hcy5mYWN0b3IocHJvcGllZGFkZXMkbDMpDQpwcm9waWVkYWRlcyRwcm9wZXJ0eV90eXBlPC1hcy5mYWN0b3IocHJvcGllZGFkZXMkcHJvcGVydHlfdHlwZSkNCmBgYA0KDQojIFJlZ3Jlc2nDs24gTGluZWFsIE3Dumx0aXBsZQ0KIyMjIE1vZGVsbzENCjxkaXYgc3R5bGU9InRleHQtYWxpZ246IGp1c3RpZnkiPg0KU2UgcHJvcG9uZSB1biBtb2RlbG8gYWRpdGl2byBkZSByZWdyZXNpw7NuIGxpbmVhbCBtw7psdGlwbGUgcGFyYSBleHBsaWNhciBlbCBwcmVjaW8gZGUgbGFzIHByb3BpZWRhZGVzIGVuIGZ1bmNpw7NuIGRlIDYgdmFyaWFibGVzICgqc3VyZmFjZV9jb3ZlcmVkKiwgKnN1cmZhY2VfdG90YWwqLCAqcm9vbXMqLCAqYmF0aHJvb21zKiwgKmwzKiwgKnByb3BlcnR5X3R5cGUqKSwgZGUgbGFzIGN1YWxlcyA0IHNvbiBjb250aW51YXMgeSAyIHNvbiBjYXRlZ8OzcmljYXMgZGUgNTcgeSAzIG5pdmVsZXMgcmVzcGVjdGl2YW1lbnRlLiA8YnI+DQo8L2Rpdj4NCmBgYHtyIG1vZGVsbzF9DQptb2RlbG8xPC1sbShwcmljZSB+IC4sIGRhdGE9cHJvcGllZGFkZXMpDQptb2RlbG8xX3RpZHk8LXRpZHkobW9kZWxvMSkNCm1vZGVsbzFfdGlkeQ0KYGBgDQojIyMgSW50ZXJwcmV0YWNpw7NuIGRlIGxvcyBjb2VmaWNpZW50ZXMNCjxkaXYgc3R5bGU9InRleHQtYWxpZ246IGp1c3RpZnkiPg0KQWwgdGVuZXIgZG9zIHZhcmlhYmxlcyBjYXRlZ8OzcmljYXMgY29uIG3DoXMgZGUgZG9zIGNhdGVnb3LDrWFzIHNlIGhhY2UgbcOhcyBjb21wbGVqbyBlbCBhbsOhbGlzaXMgZGUgbG9zIGNvZWZpY2llbnRlcy4gRWwgdmFsb3IgaW50ZXJjZXB0KCRcYmV0YV8wJCkgZXMgZGUgLTEwOTQwNi42MSB5IHNlcsOtYSBlbCB2YWxvciBwcm9tZWRpbyBkZSBsb3MgcHJlY2lvcyBkZSB1bmEgcHJvcGllZGFkIHF1ZSBubyB0aWVuZSBuaW5ndW5hIGhhYml0YWNpw7NuLCBuaW5nw7puIGJhw7FvLCBubyB0aWVuZSBzdXBlcmZpY2llIGN1YmllcnRhIG5pIHN1cGVyZmljaWUgdG90YWwgeSBxdWUgcGVydGVuZWNlIGFsIGdydXBvIGRlIHJlZmVyZW5jaWEsIGVzIGRlY2lyIGN1YW5kbyBlbCBCYXJyaW8gZXMgQWJhc3RvIHkgZWwgdGlwbyBkZSBwcm9waWVkYWQgZXMgdW5hIENhc2EuIFBvciBsbyB0YW50byDDqXN0ZSAkXGJldGFfMCQgbm8gZXMgcmVsZXZhbnRlIHNpIHNlIGxvIHNlcGFyYSBkZWwgdMOpcm1pbm8gZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbC4gPGJyPg0KDQoqICRcYmV0YV97bDNCYXJyaW9faX0kIGVzIGVsIGNhbWJpbyBlbiBlbCB2YWxvciBlc3BlcmFkbyBkZSBsb3MgcHJlY2lvcyBkZWwgJHtCYXJyaW9faX0kIHJlc3BlY3RvIGRlbCBiYXJyaW8gZGUgQWJhc3RvIGN1YW5kbyBzZSBjb250cm9sYSBwb3IgdGlwbyBkZSBwcm9waWVkYWQgeSBzZSBtYW50aWVuZW4gY29uc3RhbnRlcyBsYXMgb3RyYXMgdmFyaWFibGVzIGRlbCBtb2RlbG8uIDxicj4NCiogJFxiZXRhX3twcm9wZXJ0eV9pfSQgZXMgZWwgY2FtYmlvIGVuIGVsIHZhbG9yIGVzcGVyYWRvIGRlIGxvcyBwcmVjaW9zIGRlbCAke1Byb3BlcnR5VHlwZV9pfSQgcmVzcGVjdG8gZGUgdW5hIHByb3BpZWRhZCBkZWwgdGlwbyBDYXNhIGN1YW5kbyBzZSBjb250cm9sYSBwb3IgYmFycmlvIHkgc2UgbWFudGllbmVuIGNvbnN0YW50ZXMgbGFzIG90cmFzIHZhcmlhYmxlcyBkZWwgbW9kZWxvLiAgPGJyPg0KKiAkXGJldGFfe3Jvb21zfSQgZXMgZWwgdmFsb3IgcHJvbWVkaW8gZGUgdW5hIHByb3BpZWRhZCBjdWFuZG8gYXVtZW50YW1vcyBlbiB1biBhbWJpZW50ZSB5IHNlIG1hbnRpZW5lbiBsYXMgZGVtw6FzIHZhcmlhYmxlcyBjb25zdGFudGVzLiBFbiBlc3RlIGNhc28gZWwgdmFsb3IoLTM5NjEuMjcpIGVzIG5lZ2F0aXZvOyBlcyBkZWNpciBlbCB2YWxvciBkZSBsYSBwcm9waWVkYWQgZGlzbWludXllIGVuIHByb21lZGlvIGFsIGFncmVnYXIgdW4gYW1iaWVudGUuIDxicj4NCiogJFxiZXRhX3tiYXRocm9vbXN9JCBlcyBlbCB2YWxvciBwcm9tZWRpbyBkZSB1bmEgcHJvcGllZGFkIGN1YW5kbyBhZ3JlZ2Ftb3MgdW4gYmHDsW8gYSBsYSBtaXNtYSB5IHNlIG1hbnRpZW5lbiBsYXMgZGVtw6FzIHZhcmlhYmxlcyBjb25zdGFudGVzLiBFbiBlc3RlIGNhc28gZWwgdmFsb3IgZGUgbGEgcHJvcGllZGFkIGF1bWVudGEgZW4gcHJvbWVkaW8gZW4gVVNEIDM0MDQwLjk4LiA8YnI+DQoqICRcYmV0YV97c3VyZmFjZVRvdGFsfSQgZXMgZWwgdmFsb3IgcHJvbWVkaW8gZGUgdW5hIHByb3BpZWRhZCBjdWFuZG8gYXVtZW50YW1vcyB1biBtZXRybyBjdWFkcmFkbyBlbiBsYSBzdXBlcmZpY2llIHRvdGFsIHkgc2UgbWFudGllbmVuIGxhcyBkZW3DoXMgdmFyaWFibGVzIGNvbnN0YW50ZXMuRW4gZXN0ZSBjYXNvIGVsIHZhbG9yIGRlIGxhIHByb3BpZWRhZCBhdW1lbnRhIGVuIHByb21lZGlvIGVuIFVTRCA5MTkuIDxicj4NCiogJFxiZXRhX3tzdXJmYWNlQ292ZXJlZH0kIGVzIGVsIHZhbG9yIHByb21lZGlvIGRlIHVuYSBwcm9waWVkYWQgY3VhbmRvIGF1bWVudGFtb3MgdW4gbWV0cm8gY3VhZHJhZG8gZW4gbGEgc3VwZXJmaWNpZSBjdWJpZXJ0YSB5IHNlIG1hbnRpZW5lbiBsYXMgZGVtw6FzIHZhcmlhYmxlcyBjb25zdGFudGVzLkVuIGVzdGUgY2FzbyBlbCB2YWxvciBkZSBsYSBwcm9waWVkYWQgYXVtZW50YSBlbiBwcm9tZWRpbyBlbiBVU0QgMTQ1Ny4NCjwvZGl2Pg0KIyMjIFNpZ25pZmljYXRpdmlkYWQgZGUgbGFzIHZhcmlhYmxlcw0KPGRpdiBzdHlsZT0idGV4dC1hbGlnbjoganVzdGlmeSI+DQpUb2RvcyBsb3MgcC12YWxvcmVzIGRlIGxhcyB2YXJpYWJsZXMgY29udGludWFzIHkgZGUgbGEgY2F0ZWfDs3JpY2EgKnByb3BlcnR5X3R5cGUqIGVuIGVzdGUgbW9kZWxvIHNvbiBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhcyAoPDAuMDUpLiBTZSBvYnNlcnZhIGEgdHJhdsOpcyBkZSBsYXMgdmFyaWFibGVzIGR1bW1pZXMgZGUgKmwzKiBxdWUgbm8gdG9kb3MgbG9zIGJhcnJpb3MgaW5mbHV5ZW4gZW4gbGEgbWlzbWEgcHJvcG9yY2nDs24gZW4gZWwgcHJlY2lvIG1lZGlvIGRlIGxhcyBwcm9waWVkYWRlcy4gU2UgZW5jdWVudHJhbiAxMyBiYXJyaW9zIHF1ZSBubyBjb250cmlidXllbiBkZSBmb3JtYSBzaWduaWZpY2F0aXZhIGFsIG1vZGVsby4gPGJyPg0KPC9kaXY+DQpgYGB7ciBtb2RlbG8xX3B2YWx1ZX0NCm1vZGVsbzFfdGlkeVttb2RlbG8xX3RpZHkkcC52YWx1ZSA+MC4wNSxdJHRlcm0NCmBgYA0KIyMjIE1lZGlkYXMgZGUgRXZhbHVhY2nDs24NCjxkaXYgc3R5bGU9InRleHQtYWxpZ246IGp1c3RpZnkiPg0KRWwgY29lZmljaWVudGUgZGUgZGV0ZXJtaW5hY2nDs24gbcO6bHRpcGxlIGFqdXN0YWRvICRSXjJfYSQgZXMgNzcsNjElIHF1ZSByZXByZXNlbnRhIGVsIHBvcmNlbnRhamUgZGUgdmFyaWFjacOzbiBkZSBsYSB2YXJpYWJsZSBwcmVjaW8gcXVlIHB1ZWRlIHNlciBleHBsaWNhZGEgcG9yIGVzdGUgbW9kZWxvLiBTZSB1dGlsaXphICAkUl4yX2EkIGVuIGx1Z2FyIGRlICAkUl4yJCB5YSBxdWUgcGVuYWxpemEgbG9zIG1vZGVsb3MgY29uIG11Y2hhcyB2YXJpYWJsZXMgZXhwbGNpYXRpdmFzIHF1ZSBubyBjb250cmlidXllbiBhbCBtb2RlbG8uIDxicj4NClVzYW1vcyBlbCBUZXN0IEYgcGFyYSBldmFsdWFyIHNpIGV4aXN0ZSBvIG5vIHJlbGFjacOzbiBlbnRyZSBsYXMgdmFyaWFibGVzIHByZWRpY3RvcmlhcyB5IGxhIHJlc3B1ZXN0YS4gU2UgcGxhbnRlYSBjb21vICRIXzAkIHF1ZSBubyBleGlzdGUgcmVsYWNpw7NuIGVudHJlIGxhcyB2YXJpYWJsZXMgKGwzLCByb29tcywgYmF0aHJvb21zLCBzdXJmYWNlX3RvdGFsLCBzdXJmYWNlX2NvdmVyZWQgeSBwcm9wZXJ0eV90eXBlKSB5IHByZWNpbzsgJEhfMSQgcXVlIGV4aXN0ZSB1bmEgcmVsYWNpw7NuIGxpbmVhbCBlbnRyZSBlbGxhcy4gRW4gbnVlc3RybyBtb2RlbG87IGVuIGVzdGUgY2FzbyBzZSBvYnRpZW5lIHVuIHZhbG9yIGFsdG8gaWd1YWwgYSAyNTY3LDU4IGNvbiB1biBwLXZhbHVlIDwgMi4yZS0xNiBwb3IgbG8gdGFudG8gc2UgcmVjaGF6YSAkSF8wJCB5IHNlIHB1ZWRlIGluZGljYXIgcXVlIGV4aXN0ZSByZWxhY2nDs24gZW50cmUgbGEgcmVzcHVlc3RhIHkgYWwgbWVub3MgdW5hIGRlIGxhcyB2YXJpYWJsZXMgcHJlZGljdG9yaWFzLiBFbCBtb2RlbG8gcGxhbnRlYWRvIGVzIGNvcnJlY3RvLiA8YnI+DQo8L2Rpdj4NCmBgYHtyIG1vZGVsbzFfZ2xhbmNlfQ0KZ2xhbmNlKG1vZGVsbzEpDQpgYGANCg0KIyMjIMK/UXXDqSBlcyBwcmVmZXJpYmxlIHRlbmVyIHBhcmEgdmVuZGVyPw0KPGRpdiBzdHlsZT0idGV4dC1hbGlnbjoganVzdGlmeSI+DQpFbiBiYXNlIGFsIG1vZGVsbyBvYnRlbmlkbyBzZSBlc3RpbWEgZWwgcHJlY2lvIGRlIGNhZGEgcHJvcGllZGFkIGRlIGFjdWVyZG8gYSBzdXMgY2FyYWN0ZXLDrXN0aWNhcyB1dGlsaXphbmRvIGxhIGZ1bmNpw7NuIHByZWRpY3QuIEVuIGxhIHNhbGlkYSBzZSBvYnRpZW5lbiB0cmVzIHZhbG9yZXM7IGZpdCAgc2VyaWEgbGEgcHJlZGljY2nDs24gZGVsIHZhbG9yIGRlIGxhIHByb3BpZWRhZCBjb24gZXNvcyBkYXRvcyBlc3BlY2lmaWNvIGRlbnRybyBkZSBsb3MgbMOtbWl0ZXMgaW5mZXJpb3IobHdyKSB5IHN1cGVyaW9yKHVwcikgY29uIHVuYSBjb25maWFuemEgZGVsIDk1JS4NCg0KDQoqIFVuIGRlcGFydGFtZW50byBkZSAxMjAgbXRzIGN1YWRyYWRvcyBjdWJpZXJ0b3MgZW4gQWJhc3RvLCBjb24gMyBkb3JtaXRvcmlvcyB5IDIgYmHDsW9zOiA8YnI+DQokJFk9LTEwOTQwNiw2MSArIDMqKC0zOTYxLjI3KSArIDIqMzQwNDAuOTggKyAxMjAqMTQ1Ny4xNyArIDEwMCo5MTksMDggKyA5MjY1My4zMSQkDQokJFk9IDMyNDU5Ni40JCQNCiogVW4gUEggZW4gQmFsdmFuZXJhLCBjb24gODAgbXRzIGN1YWRyYWRvcyBjdWJpZXJ0b3MsIDIwIG10cyBjdWFkcmFkb3Mgbm8gY3ViaWVydG9zLCAyIGRvcm1pdG9yaW9zIHkgMyBiYcOxb3MuDQokJFk9LTEwOTQwNiw2MSArKC0yNDc4OC4yNykgKyAyKigtMzk2MS4yNykgKyAzKjM0MDQwLjk4ICsgODAqMTQ1Ny4xNyArIDEwMCo5MTksMDggKyA0Njc3OS4zNyQkDQokJFk9IDIxNTI2Nyw2JCQNCkVzIHByZWZlcmlibGUgdmVuZGVyIGVsIERlcGFydGFtZW50byBlbiBlbCBBYmFzdG8uDQo8L2Rpdj4NCmBgYHtyIHByZWRpY2Npb259DQpuZXdkYXRhIDwtIGRhdGEuZnJhbWUobDM9IkFiYXN0byIsIHJvb21zPTMsIGJhdGhyb29tcz0yLCBzdXJmYWNlX3RvdGFsPTEyMCwgc3VyZmFjZV9jb3ZlcmVkPTEyMCwgcHJvcGVydHlfdHlwZT0iRGVwYXJ0YW1lbnRvIikNCg0KcHJlZGljdChtb2RlbG8xLCBuZXdkYXRhLCBpbnRlcnZhbD0icHJlZGljdCIsIGxldmVsID0gMC45NSkNCg0KbmV3ZGF0YSA8LSBkYXRhLmZyYW1lKGwzPSJCYWx2YW5lcmEiLCByb29tcz0yLCBiYXRocm9vbXM9Mywgc3VyZmFjZV90b3RhbD0xMDAsIHN1cmZhY2VfY292ZXJlZD04MCwgcHJvcGVydHlfdHlwZT0iUEgiKQ0KDQpwcmVkaWN0KG1vZGVsbzEsIG5ld2RhdGEsIGludGVydmFsPSJwcmVkaWN0IiwgbGV2ZWwgPSAwLjk1KQ0KYGBgDQojIyMgTW9kZWxvMg0KPGRpdiBzdHlsZT0idGV4dC1hbGlnbjoganVzdGlmeSI+DQpTZSBlbGltaW5hIGxhIHZhcmlhYmxlICpsMyogZGVsIG1vZGVsbyB5IHNlIG9ic2VydmEgYSBwYXJ0aXIgZGUgbG9zIHJlc3VsdGFkb3MgcXVlIHRvZGFzIGxhcyB2YXJpYWJsZXMgZXhwbGljYXRpdmFzIHNvbiBlc3RhZMOtc3RpY2FtZW50ZSBzaWduaWZpY2F0aXZhcyhwLXZhbG9yPDAuMDUpLiBTZSBvYnNlcnZhIHF1ZSBsb3MgcHJlY2lvcyBwcm9tZWRpb3MgZGUgbG9zIGRlcGFydGFtZW50b3Mgc2UgdWJpY2EgcG9yIGVuY2ltYSBkZWwgZGUgbGFzIGNhc2FzKCRcYmV0YV8wICsgXGJldGFfe3Byb3BlcnR5RGVwdG99JCA9LTEzMTA5NisxMzUxNzc9NDA4MSkgbWllbnRyYXMgcXVlIGVsIHByZWNpbyBwcm9tZWRpbyBkZSBsb3MgUEhzIGNvbiByZXNwZWN0byBhIGxhcyBjYXNhcyBzZSB1YmljYSBwb3IgZGViYWpvICgkXGJldGFfMCArIFxiZXRhX3twcm9wZXJ0eVBIfSQgPS0xMzEwOTYrNjg1OTg9LTYyNDk4KS4NClNlIGV4cGxpY2EgZW4gZXN0ZSBtb2RlbG8gdW4gNjgsMzIlIGRlIGxhIHZhcmlhYmlsaWRhZCBkZSBsb3MgcHJlY2lvcyB5IGVsIFRlc3QgRiBzaWVuZG8gZGUgMTY0OTAgY29uIHVuIHAtdmFsdWUgIDwgMi4yZS0xNiBpbmRpY2EgcXVlIGVzdGUgbW9kZWxvIHRhbWJpw6luIGVzIGNvcnJlY3RvLg0KPC9kaXY+DQpgYGB7ciBtb2RlbG8yfQ0KbW9kZWxvMjwtbG0ocHJpY2UgfnJvb21zICsgYmF0aHJvb21zICsgc3VyZmFjZV90b3RhbCArIHN1cmZhY2VfY292ZXJlZCArIHByb3BlcnR5X3R5cGUgLCBkYXRhPXByb3BpZWRhZGVzKQ0KdGlkeShtb2RlbG8yKQ0KZ2xhbmNlKG1vZGVsbzIpDQpgYGANCiMjIyBDb21wYXJhciBNb2RlbG8xIHkgTW9kZWxvMg0KPGRpdiBzdHlsZT0idGV4dC1hbGlnbjoganVzdGlmeSI+DQpDb21wYXJvIHBvciBlbCBjb2VmaWNpZW50ZSBkZSBkZXRlcm1pbmFjacOzbiBtw7psdGlwbGUgYWp1c3RhZG8gJFJeMiQgeWEgcXVlIHRpZW5kZSBhIGF1bWVudGFyIGN1YW5kbyBzZSBhZ3JlZ2FuIG3DoXMgY292YXJpYWJsZXMgYXVucXVlIMOpc3RhcyBzZWFuIG8gbm8gYXByb3BpYWRhcyBwYXJhIGVsIG1vZGVsbyB5IHRlbmllbmRvIGVzdG9zIG1vZGVsb3MgZGlmZXJlbnRlIG7Dum1lcm8gZGUgY292YXJpYWJsZXMsIHNlIG9ic2VydmEgcXVlIGVsICRSXjJfYSQgZXMgbWF5b3IgZW4gZWwgbW9kZWxvMSBlbiB1biA5JSAoNzcuNiUtNjguMyUpLiBFcyBkZWNpciwgcXVlIGxhIGNvdmFyaWFibGUgKmwzKiBlcyBpbXBvcnRhbnRlIHBhcmEgcHJlZGVjaXIgbGEgdmFyaWFiaWxpZGFkIGRlIGxvcyBwcmVjaW9zIGRlIGxhcyBwcm9waWVkYWRlcy4gU2UgY29uY2x1eWUgcXVlIGVsIG1vZGVsbzEgZXMgZWwgcXVlIG1lam9yIGV4cGxpY2EgbGEgdmFyaWFibGUgcmVzcHVlc3RhLiAgDQo8L2Rpdj4NCiMgQ3JlYWNpw7NuIGRlIFZhcmlhYmxlcw0KIyMjIEJhcnJpb3M6IE51ZXZhIFZhcmlhYmxlDQo8ZGl2IHN0eWxlPSJ0ZXh0LWFsaWduOiBqdXN0aWZ5Ij4NClNlIGRlYmUgY2FsY3VsYXIgZWwgcHJlY2lvIHBvciBtZXRybyBjdWFkcmFkbyBwcm9tZWRpbyBkZSBsYXMgcHJvcGllZGFkZXMgZW4gY2FkYSBiYXJyaW8geSBlbiBiYXNlIGEgc3VzIHZhbG9yZXMgZGVmaW5pciBsb3MgcHVudG9zIGRlIGNvcnRlIHBhcmEgY2xhc2lmaWNhcmxvcyBlbiB0cmVzIGdydXBvczogYWx0bywgbWVkaW8geSBiYWpvLiBDb25zaWRlcmFuZG8gbG9zIGN1YXJ0aWxlcyBkZSBlc3RhIG51ZXZhIHZhcmlhYmxlIGRlZmlubzsgYWx0byBjdWFuZG8gZXN0YSBudWV2YSB2YXJpYWJsZSBlcyBtYXlvciBvIGlndWFsIGEgMzMxNjsgYmFqbyBjdWFuZG8gZXMgbWVub3IgbyBpZ3VhbCBhIDIzNzkgeSBtZWRpbyBlbnRyZSAoMjM3OSwzMzE2KS4gPGJyPg0KU2UgY3JlYXJvbiBkb3MgdmFyaWFibGVzIGF1eGlsaWFyZXMgcGFyYSBkZWZpbmlyIGxhIG51ZXZhLCB1bmEgcGFyYSBvYnRlbmVyIGVsIHByZWNpbyBwb3IgbWV0cm8gY3VhZHJhZG8gZGUgY2FkYSBwcm9waWVkYWQgeSBvdHJhIHByYSBjYWxjdWxhciBlbCBwcm9tZWRpbyBkZSDDqXN0YSDDumx0aW1hIHBhcmEgY2FkYSBiYXJyaW8uDQo8L2Rpdj4NCmBgYHtyIGJhcnJpb30NCnByb3BpZWRhZGVzPC1wcm9waWVkYWRlcyAlPiUNCiAgbXV0YXRlKHByaWNleG10cz1wcmljZS9zdXJmYWNlX3RvdGFsKSAlPiUNCiAgZ3JvdXBfYnkobDMpICU+JQ0KICBuZXN0KCkgDQoNCnByb3BpZWRhZGVzPC1wcm9waWVkYWRlcyAlPiUNCiAgbXV0YXRlKG1lYW5wcmljZXhtdHM9bWFwX2RibChkYXRhLGZ1bmN0aW9uKHgpIG1lYW4oeCRwcmljZXhtdHMpKSkgJT4lDQogIHVubmVzdChkYXRhKQ0KDQpwcm9waWVkYWRlcyRtZWFucHJpY2V4bXRzPC1yb3VuZChwcm9waWVkYWRlcyRtZWFucHJpY2V4bXRzLCBkaWdpdHMgPSAyKQ0KDQpzdW1tYXJ5KHByb3BpZWRhZGVzJG1lYW5wcmljZXhtdHMpDQoNCnByb3BpZWRhZGVzPC1wcm9waWVkYWRlcyAlPiUgbXV0YXRlKGJhcnJpb3M9bWFwX2NocihtZWFucHJpY2V4bXRzLHNldENsYXNpZmljYWNpb24pKSAlPiUgdW5ncm91cA0KDQpwcm9waWVkYWRlcyRiYXJyaW9zPC1hcy5mYWN0b3IocHJvcGllZGFkZXMkYmFycmlvcykNCg0KdGFibGUocHJvcGllZGFkZXMkYmFycmlvcykNCmBgYA0KIyMjIE1vZGVsbzMgeSBDb21wYXJhY2nDs24gY29uIE1vZGVsbzENCjxkaXYgc3R5bGU9InRleHQtYWxpZ246IGp1c3RpZnkiPg0KQWwgYWdyZWdhciBsYSBudWV2YSB2YXJpYWJsZSAqYmFycmlvcyogeSBjb25zdHJ1aXIgdW4gbW9kZWxvIGNvbiBsYSBtaXNtYSBzaW4gY29uc2lkZXJhciAqbDMqIHNlIG9ic2VydmEgcXVlIGVsICRSXjJfYSQgZGlzbWludXnDsyBhdW5xdWUgbm8gZW4gdW4gcG9yY2VudGFqZSBjb25zaWRlcmFibGVtZW50ZSBhbHRvLiBFbiBlbCBNb2RlbG8zIHRlbmVtb3MgZXhwbGljYWRvIHVuIDc0JSBkZSB2YXJpYWJpbGlkYWQgZGUgbG9zIHByZWNpb3MgbWllbnRyYXMgcXVlIGVuIGVsIE1vZGVsbzEgaGFiw61hbW9zIGFsY2FuemFkbyB1biA3NyUuIFNlIGRlc3RhY2EgcXVlIHRvZGFzIGxhcyB2YXJpYWJsZXMgZGUgZXN0ZSBudWV2byBtb2RlbG8gc29uIGVzdGFkw61zdGljYW1lbnRlIHNpZ25pZmljYXRpdmFzIHkgc2Ugb2JzZXJ2YSBxdWUgbG9zIGNvZWZpY2llbnRlcyBkZSBsb3MgcGFyw6FtZXRyb3MgYXVtZW50YXJvbiBhIGV4Y2VwY2nDs24gZGUgaW50ZXJjZXB0IHkgZGUgc3VyZmFjZV90b3RhbCBxdWUgZGlzbWludXllcm9uLiBMYSBjbGFzaWZpY2FjacOzbiBkZSBsb3MgYmFycmlvcyBkZSBhY3VlcmRvIGFsIHByZWNpbyB4IG1ldHJvcyBjdWFkcmFkb3MgcHJvbWVkaW8gZGUgY2FkYSB1bm8gYXl1ZGEgYSBleHBsaWNhciBlbCBtb2RlbG8geSBzZSBvYnNlcnZhIHF1ZSBlbCBwcmVjaW8gcHJvbWVkaW8gZGUgbG9zIGJhcnJpb3MgY2xhc2lmaWNhZG9zIHRhbnRvIGNvbW8gJ2Jham8nIHkgJ21lZGlvJyBlc3TDoW4gcG9yIGRlYmFqbyBkZWwgZGUgbG9zIGNsYXNpZmljYWRvcyBjb21vICdhbHRvJyBwYXJhIGxhcyBwcm9waWVkYWRlcyBkZSB0aXBvIENhc2EuPGJyPg0KRXN0ZSBtb2RlbG8gbG8gY29uc2lkZXJvIG1lam9yIHF1ZSBlbCBNb2RlbG8xIHBvcnF1ZSBubyBzZSB0aWVuZW4gdmFyaWFibGVzIHF1ZSBubyBheXVkYW4gYWwgbW9kZWxvIHkgZXMgbcOhcyBmw6FjaWwgZGUgaW50ZXJwcmV0YXIgYcO6biBwZXJkaWVuZG8gdW4gMyUgZGUgdmFyaWFiaWxpZGFkLg0KPC9kaXY+DQpgYGB7ciBtb2RlbG8zfQ0KbW9kZWxvMzwtbG0ocHJpY2UgfiByb29tcyArIGJhdGhyb29tcyArIHN1cmZhY2VfdG90YWwgKyBzdXJmYWNlX2NvdmVyZWQgKyBwcm9wZXJ0eV90eXBlICsgYmFycmlvcywgZGF0YT1wcm9waWVkYWRlcykNCnN1bW1hcnkobW9kZWxvMykNCmBgYA0KIyMjIFN1cGVyZmljaWVfcGF0aW86IE51ZXZhIFZhcmlhYmxlDQo8ZGl2IHN0eWxlPSJ0ZXh0LWFsaWduOiBqdXN0aWZ5Ij4NClNlIG9ic2VydmEgdW5hIGNvcnJlbGFjacOzbiBtdXkgZnVlcnRlKDAuOTUpIGVudHJlICpzdXJmYWNlX3RvdGFsKiB5ICpzdXJmYWNlX2NvdmVyZWQqLiBBZGVtw6FzIGVuIGVsIDI4JSBkZSBsb3MgY2Fzb3Mgc2UgcmVnaXN0cmEgZWwgbWlzbW8gdmFsb3I7IGVzIGRlY2lyIGN1YW5kbyBsYXMgcHJvcGllZGFkZXMgbm8gdGllbmVuIHVuYSBzdXBlcmZpY2llIGRlc2N1YmllcnRhLiBFbiBlbCBjYXNvIGRlIHF1ZSBleGlzdGEgdW5hIGluY29tcGF0aWJpbGlkYWQgZW4gbG9zIGRhdG9zLCBwb3IgZWplbXBsbyAke3N1cmZhY2VcX2NvdmVyZWR9ID4ge3N1cmZhY2VcX3RvdGFsfSQgaW1wbGljYSBxdWUgdGVuZ2Ftb3MgbGEgdG90YWxpZGFkIGRlIGxhIHN1cGVyZmljaWUgZW4gbGEgdmFyaWFibGUgKnN1cmZhY2VfY292ZXJlZCo7IGVuIGVzdG9zIGNhc29zIHNlIHN1bWEgbGEgc3VwZXJmaWNpZSBkZXNjdWJpZXJ0YSAqc3VyZmFjZV9wYXRpbyogKGRhZG8gcXVlIHBhdGlvIGVzIG5lZ2F0aXZvKSAgeSBzZSBhanVzdGEgZWwgc2lnbm8gZGUgZXN0YSDDumx0aW1hLiBTaW4gZW1iYXJnbyBubyBzZSBoYW4gZW5jb250cmFkbyByZWdpc3Ryb3MgY29uIGVzdGFzIGNhcmFjdGVyw61zdGljYXMgZW4gZWwgZGF0YXNldC4gDQo8L2Rpdj4NCmBgYHtyIHBhdGlvfQ0KcHJvcGllZGFkZXMgJT4lDQogIGtlZXAoaXMubnVtZXJpYykgJT4lDQogIGdnY29ycihsb3cgPSAiZGFya3JlZCIsIG1pZCA9ICJ3aGl0ZSIsIGhpZ2ggPSAic3RlZWxibHVlIiwgbGFiZWwgPSBULCBsYWJlbF9yb3VuZCA9DQogICAgICAgICAgIDIsIHNpemUgPSA0LCAgY29sb3IgPSAiZ3JleTUwIiwgIGFuZ2xlID0gLTQ1KQ0KDQpwcm9waWVkYWRlczwtcHJvcGllZGFkZXMgJT4lIA0KICBtdXRhdGUoc3VyZmFjZV9wYXRpbz1zdXJmYWNlX3RvdGFsLXN1cmZhY2VfY292ZXJlZCkNCg0KI0NhbGN1bGFyIGVsICUgZGUgbGFzIHByb3BpZWRhZGVzIHNpbiBzdXAgZGVzY3ViaWVydGEuDQpucm93KHByb3BpZWRhZGVzW3Byb3BpZWRhZGVzJHN1cmZhY2VfcGF0aW89PTAsXSkvbnJvdyhwcm9waWVkYWRlcykNCg0KI0VuIGNhc28gZGUgaW5jb21wYXRpYmlsaWRhZCwgc2UgYWp1c3RhbiBsb3MgZGF0b3MuDQppZiAobnJvdyhwcm9waWVkYWRlc1twcm9waWVkYWRlcyRzdXJmYWNlX3BhdGlvIDwgMCxdKSA+IDApew0KICBwcm9waWVkYWRlczwtcHJvcGllZGFkZXMgJT4lIA0KICBtdXRhdGUoc3VyZmFjZV9jb3ZlcmVkPWlmZWxzZShzdXJmYWNlX3BhdGlvPDAsIHN1cmZhY2VfY292ZXJlZCtzdXJmYWNlX3BhdGlvLCBzdXJmYWNlX2NvdmVyZWQpLCANCiAgICAgICAgIHN1cmZhY2VfcGF0aW89aWZlbHNlKHN1cmZhY2VfcGF0aW88MCwoLTEpKnN1cmZhY2VfcGF0aW8sc3VyZmFjZV9wYXRpbykpDQp9DQpgYGANCiMjIyBNb2RlbG80DQo8ZGl2IHN0eWxlPSJ0ZXh0LWFsaWduOiBqdXN0aWZ5Ij4NCkFncmVnYXIgbGEgdmFyaWFibGUgKnN1cmZhY2VfcGF0aW8qIG5vIG1lam9yw7MgZWwgJFJeMl9hJC4gRWwgY29lZmljaWVudGUgZGUgc3VyZmFjZV9jb3ZlcmVkIHNlIGluY3JlbWVudMOzIHkgYWhvcmEgc2UgdGllbmUgcXVlIGNhZGEgbWV0cm8gY3VhZHJhZG8gY3ViaWVydG8gdGllbmUgdW4gaW5jcmVtZW50byBkZWwgMjUzMi4yMiBlbiBlbCBwcmVjaW8gcHJvbWVkaW8gZGUgbGFzIHByb3BpZWRhZGVzLiA8YnI+DQpFbiBjdWFudG8gYWwgcGFyw6FtZXRybyBkZSAqc3VyZmFjZV9wYXRpbyogc2Ugb2JzZXJ2YSBlbCBtaXNtbyBjb2VmaWNpZW50ZSBxdWUgc2UgdGVuaWEgZW4gKnN1cmZhY2VfdG90YWwqIGVuIGVsIG1vZGVsbyBhbnRlcmlvci4NCjwvZGl2Pg0KYGBge3IgbW9kZWxvNH0NCm1vZGVsbzQ8LWxtKHByaWNlIH4gcm9vbXMgKyBiYXRocm9vbXMgKyBzdXJmYWNlX2NvdmVyZWQgKyBwcm9wZXJ0eV90eXBlICsgYmFycmlvcyArIHN1cmZhY2VfcGF0aW8sIGRhdGE9cHJvcGllZGFkZXMpDQpzdW1tYXJ5KG1vZGVsbzQpDQpgYGANCiMgRXZhbHVhY2nDs24gZGVsIE1vZGVsbzQNCiMjIyBBbmFsaXphciBSZXNpZHVvcw0KPGRpdiBzdHlsZT0idGV4dC1hbGlnbjoganVzdGlmeSI+DQpQYXJhIGFwbGljYXIgZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIGxvcyByZXNpZHVvcyBkZWJlbiB0ZW5lciB1bmEgZGlzdHJpYnVjacOzbiBub3JtYWwuIEVuIGxvcyBncsOhZmljb3MgcXFub3JtLCBsb3MgcXVhbnRpbGVzIHRpZW5kZSBhIHNlZ3VpciBsb3MgcXVhbnRpbGVzIGRlIHVuYSBkaXN0cmlidWNpw7NuIG5vcm1hbCBhdW5xdWUgaGF5IHZhcmlvcyBwdW50b3MgYWxlamFkb3MgZGUgZXNhIHRlbmRlbmNpYS4gUG9yIGVsIHRlb3JlbWEgY2VudHJhbCBkZWwgbGltaXRlIHNlIHB1ZWRlIGRlY2lyIHF1ZSBzZSBjdW1wbGUgZXN0ZSBzdXB1ZXN0by4gT3RybyBxdWUgZGViaWVzZSBjdW1wbGlyc2UgZXMgZWwgZGUgbGEgaG9tb2NlZGFzdGljaWRhZCB5IHZlbW9zIGEgdHJhdsOpcyBkZSBsb3MgZ3LDoWZpY29zIHF1ZSBsb3MgcmVzaWR1b3MgdGllbmVuIGNpZXJ0YSBlc3RydWN0dXJhLg0KPC9kaXY+DQpgYGB7ciByZXNpZHVhbHN9DQoNCnJlc2lkdW9zIDwtbW9kZWxvNCRyZXNpZHVhbHMNCnFxbm9ybShyZXNpZHVvcykNCnFxbGluZShyZXNpZHVvcykNCg0KcGxvdChtb2RlbG80JGZpdHRlZCwgbW9kZWxvNCRyZXNpZHVhbHMsIHhsYWIgPSAiRml0dGVkIFZhbHVlcyIsIHlsYWIgPSAiUmVzaWR1YWxzIikNCmFibGluZSgwLDApDQpgYGANCiMjIyBDb21wYXJhciBjb24gTW9kZWxvIFByb3B1ZXN0bw0KPGRpdiBzdHlsZT0idGV4dC1hbGlnbjoganVzdGlmeSI+DQpBcGxpY2FuZG8gbG9nYXJpdG1vIGEgbGFzIHZhcmlhYmxlcyBwcm9wdWVzdGFzIHNlIG9ic2VydmEgdW5hIG1lam9yw61hIGFsIHJlYWxpemFyIGxvcyBncsOhZmljb3MgcGFyYSBhbmFsaXphciBsb3Mgc3VwdWVzdG9zLiBMb3MgcXVhbnRpbGVzIHNlIGFjZXJjYW4gbcOhcyBhIGxvcyBxdWFudGlsZXMgZGUgdW5hIGRpc3RyaWJ1Y2nDs24gbm9ybWFsIGF1bnF1ZSBzZSBzaWd1ZSB2aXN1YWxpemFuZG8gY2llcnRvIHBhdHLDs24gZW4gbG9zIHJlc2lkdW9zIHBlcm8gY29uIHVuYSBkaXN0cmlidWNpw7NuIG3DoXMgdW5pZm9ybWUuDQpFbCAkUl4yX2EkIG1lam9yw7MgZGUgNzQlIGEgODElIGF1bnF1ZSBlbiBlc3RlIG1vZGVsbyBleHBsaWNhIGxhIHZhcmlhY2nDs24gZW4gbG9nKHByaWNlKSB5IG5vIGVuIHByaWNlIGNvbW8gZWwgbW9kZWxvIGFudGVpb3IuIDxicj4NCkFuYWxpemFuZG8gbG9zIGNvZWZpY2llbnRlcyBzZSBvYnNlcnZhIHF1ZToNCg0KKiBwb3IgY2FkYSBoYWJpdGFjacOzbiBxdWUgc2UgYWdyZWdhIGEgdW5hIHByb3BpZWRhZCBoYXkgdW5hIGRpc21pbnVjacOzbiBkZWwgMC4wNCUgZW4gZWwgcHJlY2lvIGRlIGxhcyBwcm9waWVkYWRlcy4gDQoqIHBvciBjYWRhIGJhw7FvIHF1ZSBzZSBhZ3JlZ2EgYSB1bmEgcHJvcGllZGFkIGhheSB1bmEgYXVtZW50byBkZWwgMC4xNyUgZW4gZWwgcHJlY2lvIGRlIGxhcyBwcm9waWVkYWRlcy4NCiogcG9yIGNhZGEgbWV0cm8gY3VhZHJhZG8gcXVlIHNlIGFncmVnYSBlbiBsYSBzdXBlcmZpY2llIGN1YmllcnRhIGRlIHVuYSBwcm9waWVkYWQgaGF5IHVuYSBhdW1lbnRvIGRlbCAwLjgyJSBlbiBlbCBwcmVjaW8gZGUgbGFzIHByb3BpZWRhZGVzLg0KKiBlbCBwcmVjaW8gZGUgbGFzIHByb3BpZWRhZGVzIGF1bWVudGEgdW4gMjMlIHNpIGxhIHByb3BpZWRhZCBlcyB1biBEZXBhcnRhbWVudG8sIDYlIHNpIGVzIFBILCAwLjM5JSBwb3IgY2FkYSBtZXRybyBjdWFkcmFkbyBxdWUgc2UgYWdyZWdhIGFsIHBhdGlvLg0KKiBlbCBwcmVjaW8gZGUgbGFzIHByb3BpZWRhZGVzIGRpc21pbnV5ZSB1biA0MiUgc2kgc2UgZW5jdWVudHJhIGVuIHVuIGJhcnJpbyBjbGFzaWZpY2FkbyBjb21vIGJham8geSB1biAgMjAlIGVuIHVubyBjb21vIG1lZGlvLg0KDQo8L2Rpdj4NCmBgYHtyIH0NCm1vZGVsbzU8LWxtKGxvZyhwcmljZSkgfiBsb2cocm9vbXMpICsgbG9nKGJhdGhyb29tcykgKyBsb2coc3VyZmFjZV9jb3ZlcmVkKSArIHByb3BlcnR5X3R5cGUgKyBiYXJyaW9zICsgc3VyZmFjZV9wYXRpbywgZGF0YT1wcm9waWVkYWRlcykNCnN1bW1hcnkobW9kZWxvNSkNCg0KcmVzaWR1b3MgPC1tb2RlbG81JHJlc2lkdWFscw0KcXFub3JtKHJlc2lkdW9zKQ0KcXFsaW5lKHJlc2lkdW9zKQ0KDQpwbG90KG1vZGVsbzUkZml0dGVkLCBtb2RlbG81JHJlc2lkdWFscywgeGxhYiA9ICJGaXR0ZWQgVmFsdWVzIiwgeWxhYiA9ICJSZXNpZHVhbHMiKQ0KYWJsaW5lKDAsMCkNCmBgYA0KIyBEYXRhZnJhbWVzIEFuaWRhZG9zDQojIyMgQW5pZGFyIHBvciAqcHJvcGVydHlfdHlwZSoNCmBgYHtyIHByb3BlcnR5X2FuaWRhZG99DQpwcm9waWVkYWRlczwtcHJvcGllZGFkZXMgJT4lDQogIGdyb3VwX2J5KHByb3BlcnR5X3R5cGUpICU+JQ0KICBuZXN0KCkNCmBgYA0KIyMjIENvbnN0cnVpciB2YXJpb3MgbW9kZWxvcw0KPGRpdiBzdHlsZT0idGV4dC1hbGlnbjoganVzdGlmeSI+DQpMYSB2YXJpYWJsZSAqcm9vbXMqIHNlIGNvbXBvcnRhIGRpZmVyZW50ZSBlbiBsb3MgdHJlcyBtb2RlbG9zOyBlbiBlbCBjYXNvIGRlbCBQSCBubyBlcyBzaWduaWZpY2F0aXZhLCBlbiBsb3MgZGVwYXJ0YW1lbnRvcyBhZ3JlZ2FyIHVuIGFtYmllbnRlIGltcGxpY2EgdW5hIGRpc21pbnVjacOzbiBlbiBlbCBwcm9tZWRpbyBkZSBsb3MgcHJlY2lvcyBtaWVudHJhcyBxdWUgZW4gbGFzIENhc2FzIGltcGxpY2EgdW4gYXVtZW50byBjb25zaWRlcmFuZG8gbGFzIGRlbcOhcyB2YXJpYWJsZXMgY29uc3RhbnRlcy48YnI+DQpFbiBsb3MgdHJlcyBtb2RlbG9zIHNpbiBpbXBvcnRhciBlbCB0aXBvIGRlIHByb3BpZWRhZCBzZSBlbmN1ZW50cmEgcXVlIGxhcyBwcm9waWVkYWRlcyB1YmljYWRhcyBlbiBsb3MgYmFycmlvcyBxdWUgdGllbmVuIHByZWNpb3MgcG9yIHN1cGVyZmljaWUgbXV5IGFsdG9zIGluY3JlbWVudGFuIGVsIHByZWNpbyBwcm9tZWRpbyBkZSBsYXMgbWlzbWFzOyBtaWVudHJhcyBxdWUgZW4gbG9zIG90cm9zIGRvcyBjYXNvcyhiYWpvIHkgbWVkaW8pIGRpc21pbnV5ZW4gZWwgcHJlY2lvIGVuIHByb21lZGlvLjxicj4NClNlIG9ic2VydmEgcXVlIGVsIGRlc3bDrW8gZXN0w6FuZGFyIGVuIGVsIG1vZGVsbyBkZSBEZXBhcnRhbWVudG8gZXMgZWwgbWVub3IgZGUgdG9kb3MgcGFyYSBjYWRhIHZhcmlhYmxlLCBlc3RvIGluZGljYXLDrWEgcXVlIHNlIGFqdXN0YSBtZWpvciBwYXJhIGxhcyBwcm9waWVkYWRlcyBkZSB0aXBvIERlcGFydGFtZW50byBxdWUgcGFyYSBsYXMgQ2FzYXMgbyBQSC4NCjwvZGl2Pg0KYGBge3IgdmFyaW9zTW9kZWxvc30NCnByb3BpZWRhZGVzPC1wcm9waWVkYWRlcyAlPiUNCiAgbXV0YXRlKGxtPW1hcChkYXRhLCBydW5Nb2RlbCkpICU+JQ0KICB1bm5lc3QobG0pDQoNCnByb3BpZWRhZGVzICU+JQ0KICBzZWxlY3QoLWRhdGEpICU+JQ0KICBrYWJsZSgpICU+JSANCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIiwgInJlc3BvbnNpdmUiKSkNCmBgYA0KDQoNCg==