#El mercado de bienes raíces en la CDMX

La Ciudad de México, como metrópolis en constante crecimiento y cambio, presenta un mercado inmobiliario dinámico y complejo. La comprensión profunda de este mercado no solo es crucial para inversionistas y desarrolladores, sino también para individuos y familias en busca de su hogar ideal. En este contexto, el análisis de datos inmobiliarios mediante técnicas avanzadas de minería de datos y aprendizaje automático, como se demostró en el código proporcionado, es fundamental. Este análisis abarcó desde la limpieza y preparación de datos hasta la aplicación de algoritmos sofisticados como K-Means para segmentación y modelos predictivos como árboles de decisión. Al investigar y aplicar estas técnicas, se ha realizado un esfuerzo significativo para extraer insights valiosos sobre precios, tendencias y patrones del mercado inmobiliario de la CDMX, con el objetivo de proporcionar una base sólida para la toma de decisiones informadas en este sector vital.

Análisis Exploratorio de los datos

En primer lugar, se cargan las librerías necesarias para el funcionamiento del código.

library(caret)
library(rpart)
library(rpart.plot)
library(party)
library(gmodels)
library(knitr)
library(readxl)
library(readr)
library(dplyr)
library(cluster)
library(ggplot2)
library(corrplot)

Posteriormente, se carga la base de datos de los bienes y raíces de la Ciudad de México, a la cual se le denomina “df”. Así mismo, se comienza a hacer un análisis exploratorio de los datos para conocer el tipo de variables con el que se está trabajando y la estructura general de la base de datos.

#Carga
#file.choose()
df <- read.csv("/Users/gabrielmedina/Downloads/Datos bienes y raices 22CDMXF.csv")
dim(df)
## [1] 658  23
summary(df)
##    Alcaldia           Colonia                X1              X2       
##  Length:658         Length:658         Min.   :0.350   Min.   :3.810  
##  Class :character   Class :character   1st Qu.:0.960   1st Qu.:4.980  
##  Mode  :character   Mode  :character   Median :1.400   Median :5.620  
##                                        Mean   :1.356   Mean   :5.371  
##                                        3rd Qu.:1.550   3rd Qu.:5.680  
##                                        Max.   :2.800   Max.   :6.780  
##        X3              X4              X5              X6       
##  Min.   :31.70   Min.   : 6.07   Min.   :18.46   Min.   :0.480  
##  1st Qu.:40.21   1st Qu.:15.50   1st Qu.:23.74   1st Qu.:1.830  
##  Median :42.85   Median :18.21   Median :25.42   Median :3.190  
##  Mean   :42.34   Mean   :17.24   Mean   :26.25   Mean   :3.395  
##  3rd Qu.:46.56   3rd Qu.:20.05   3rd Qu.:28.25   3rd Qu.:4.540  
##  Max.   :51.23   Max.   :23.46   Max.   :32.20   Max.   :8.530  
##        X7               X8                X9              X10       
##  Min.   : 0.030   Min.   :0.02000   Min.   : 3.170   Min.   :15.15  
##  1st Qu.: 0.110   1st Qu.:0.03000   1st Qu.: 6.335   1st Qu.:35.22  
##  Median : 0.300   Median :0.05000   Median : 8.150   Median :39.65  
##  Mean   : 1.022   Mean   :0.07801   Mean   : 7.825   Mean   :40.16  
##  3rd Qu.: 0.710   3rd Qu.:0.10000   3rd Qu.: 9.560   3rd Qu.:50.08  
##  Max.   :10.210   Max.   :0.23000   Max.   :13.060   Max.   :63.97  
##  Cocina_equip         Gimnasio          Amueblado           Alberca         
##  Length:658         Length:658         Length:658         Length:658        
##  Class :character   Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character   Mode  :character  
##                                                                             
##                                                                             
##                                                                             
##    Terraza            Elevador         m2_construido       Banos      
##  Length:658         Length:658         Min.   : 34.0   Min.   :1.000  
##  Class :character   Class :character   1st Qu.: 58.0   1st Qu.:1.000  
##  Mode  :character   Mode  :character   Median : 72.5   Median :1.000  
##                                        Mean   :103.3   Mean   :1.685  
##                                        3rd Qu.:120.0   3rd Qu.:2.000  
##                                        Max.   :500.0   Max.   :5.000  
##    Recamaras     Estacionamiento        Precio        
##  Min.   :1.000   Length:658         Min.   :     1.0  
##  1st Qu.:2.000   Class :character   1st Qu.:   861.8  
##  Median :2.000   Mode  :character   Median :  1934.0  
##  Mean   :2.318                      Mean   :  4283.8  
##  3rd Qu.:3.000                      3rd Qu.:  5542.0  
##  Max.   :5.000                      Max.   :128524.0

La base de datos contiene información sobre 658 viviendas en Ciudad de México, divididas en dos conjuntos de variables. El primero se enfoca en aspectos geográficos, como la ubicación por Alcaldía y Colonia, así como 10 variables que miden el nivel de marginación de la zona. El segundo conjunto se centra en las características de las viviendas, como instalaciones, tamaño y el precio (en miles de MXN) como variable a predecir.

Tras el análisis exploratorio de los datos, se detectaron algunas incidencias significativas. Se observaron errores ortográficos en palabras como “Si,” “Sí,” o “si,” que afectaban la coherencia de los registros. Además, se identificaron dos variables que habían sido mal interpretadas como otro tipo de variables y se corrigieron para que coincidieran con su naturaleza real. También se detectaron valores atípicos en la variable de precio, duplicados y datos faltantes, los cuales se resolvieron utilizando la mediana para no alterar la integridad de los datos.

# Limpieza de datos
df$Alberca[df$Alberca == "si"] = "Si"
df$Elevador[df$Elevador == "si"] = "Si"


# Conversión de Variables Categóricas a Factores
df$Gimnasio <- as.factor(df$Gimnasio)
df$Cocina_equip <- as.factor(df$Cocina_equip)
df$Gimnasio <- as.factor(df$Gimnasio)
df$Amueblado <- as.factor(df$Amueblado)
df$Alberca <- as.factor(df$Alberca)
df$Terraza <- as.factor(df$Terraza)
df$Elevador <- as.factor(df$Elevador)
df$Alcaldia <- as.factor(df$Alcaldia)
df$Colonia <- as.factor(df$Colonia)

# Eliminación de registros duplicados
df <- unique(df)
dim(df)
## [1] 653  23
# Conversión Variable Estacionamiento Numérica e Imputación de Mediana a Nulos
df$Estacionamiento <- as.numeric(df$Estacionamiento)
## Warning: NAs introduced by coercion
mediana_estacionamiento <- median(df$Estacionamiento, na.rm = TRUE)
df$Estacionamiento[is.na(df$Estacionamiento)] <- mediana_estacionamiento

#Eliminar valores extremos adicionales a 0 usando el rango intercuartil
q1 <- quantile(df$Precio, 0.25, na.rm=TRUE)
q3 <- quantile(df$Precio, 0.75, na.rm=TRUE)
rangointq <- q3 - q1

limite_inferior <- q1 - 0.17 * rangointq
limite_superior <- q3 + 1.5 * rangointq

df <- subset(df, Precio >= limite_inferior & Precio <= limite_superior)



#Revisión de Media de Variables relacionadas a la Marginación por Alcaldía
Exploración <- df %>%
  select(Alcaldia,X1, X2, X3, X4, X5, X6, X7, X8, X9, X10)%>%
  group_by(Alcaldia) %>%
  summarise(X1mean = mean(X1), X2mean = mean(X2), X3mean = mean(X3), X4mean = mean(X4), X5mean = mean(X5), X6mean = mean(X6), X7mean = mean(X7), X8mean = mean(X8), X9mean = mean(X9), X10mean = mean(X10))
Exploración
## # A tibble: 16 × 11
##    Alcaldia       X1mean X2mean X3mean X4mean X5mean X6mean X7mean X8mean X9mean
##    <fct>           <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>  <dbl>
##  1 Alvaro Obregon   1.34   5.68   42.8  17.8    23.7   3.19   0.28   0.03   6.98
##  2 Azcapotzalco     0.96   5.97   39.0  15.5    20.6   1.83   0.46   0.03   7.57
##  3 Benito Juárez    0.35   6.23   31.7   6.07   20.1   0.48   0.03   0.02   3.17
##  4 Coyoacan         0.77   4.98   32.9  11.0    24.6   1.7    0.05   0.03   5.47
##  5 Cuahtemoc        0.9    6.78   42.1  12.2    26.8   1.7    0.11   0.05   6.21
##  6 Cuajimalpa       1.55   5.76   44.4  18.9    24.4   4.17   0.43   0.06   6.68
##  7 Gustavo A. Ma…   1.4    4.99   40.2  18.2    25.4   2.92   0.3    0.05   9.53
##  8 Iztacalco        1.13   4.47   41.4  17.0    27.0   2.92   0.07   0.05   7.68
##  9 Iztapalapa       1.86   5.62   47.8  21.7    32.2   4.54   0.71   0.17   9.56
## 10 La Magdalena …   1.86   5.62   47.8  21.7    32.2   4.54   0.71   0.17   9.56
## 11 Miguel Hidalgo   0.61   5.57   35.5  10.5    18.5   1.06   0.07   0.03   4.5 
## 12 Milpa alta       2.8    3.81   51.2  23.5    30.3   8.53   6.47   0.23  13.1 
## 13 Tlahuac          1.54   4.5    46.6  20.0    26.8   5.04   0.96   0.1    9.61
## 14 Tlalpan          1.39   4.6    40.3  16.5    27.9   3.71   3.91   0.08   6.22
## 15 Venustiano Ca…   1.02   6.37   43.6  16.4    28.2   2.28   0.06   0.04   8.15
## 16 Xochimilco       2.3    4.57   46.7  21.3    32.1   6.81  10.2    0.17   8.25
## # … with 1 more variable: X10mean <dbl>

Dentro del análisis exploratorio de los datos, es importante analizar la correlación lineal entre las variables con el fin de identificar relaciones entre las distintas variables y nuestra variable de interés, mejorar la interpretación de resultados y detectar ciertos patrones o relaciones inesperadas a considerar para la mejor construcción del modelo predictivo.

numeric_cols <- df %>%
 select_if(is.numeric)

corrplot(cor(numeric_cols), type = "upper", order="hclust", addCoef.col =
"black", method = "circle", number.cex=0.5)

Algoritmo K-Means (Variables Marginación X1-X10)

Se implementará el algoritmo K-Means, un método de clusterización reconocido por su eficacia y simplicidad. Este algoritmo es ideal para segmentar el conjunto de datos en grupos o ‘clusters’ basados en similitudes internas, lo que facilita la identificación de patrones y tendencias subyacentes. En particular, se aplicará K-Means para agrupar las propiedades inmobiliarias en la CDMX según variables de marginación (X1-X10).

Para lograr una clusterización efectiva de los datos de viviendas, es esencial omitir variables no relacionadas con la marginación y la variable de precio, que podría sesgar los resultados. Además, se debe normalizar las variables numéricas para una comparación equitativa utilizando la función “scales”.

#Únicamente seleccionar variables numéricas para el algoritmo
numeric_cols <- df %>%
 select_if(is.numeric)

# Eliminar variable dependiente "Precio" y variables no de marginación
numeric_cols$Cluster <- NULL
numeric_cols$Precio <- NULL
numeric_cols$Estacionamiento <- NULL
numeric_cols$Recamaras <- NULL
numeric_cols$Banos <- NULL
numeric_cols$m2_construido <- NULL

# Escalar las columnas numéricas para garantizar que las variables tengan el mismo peso

df_scaled <- scale(numeric_cols)

# Semilla de Reproductibilidad, para garantizar que los resultados de un proceso aleatorio sean reproducibles.

set.seed(123)
Elección de número óptimo de clúster

Para elegir la cantidad óptima de clusters se utiliza el método del CODO.

wss <- numeric(15)
for (i in 2:15) {
  wss[i] <- sum(kmeans(numeric_cols, centers = i, nstart = 25)$withinss)
}
plot(1:15, wss, type = "b", xlab = "Número de Clusters", ylab = "Suma de Cuadrados Internos")

Para elegir el número óptimo de clústers se utilizó el método del Codo.

Como se observa en la gráfica generada por el código, el punto de inflexión, o “codo”, se encuentra en el número de cluster 4. Esto se fundamenta en el método del codo, que sugiere elegir el número de clusters en el punto donde la disminución de la suma de cuadrados internos (WSS) comienza a ralentizarse significativamente. En otras palabras, añadir más clusters más allá de este punto no mejora sustancialmente la compactación de los datos dentro de los clusters. Por lo tanto, 4 es el número óptimo de clusters para este análisis.

# Klusters = 4
kmeans_result <- kmeans(df_scaled, centers = 4)
kmeans_result$centers
##           X1          X2          X3         X4         X5         X6
## 1 -0.4409913  0.98017618 -0.03902648 -0.2497081 -0.5532810 -0.5050034
## 2 -1.6019942  0.14680557 -1.97461591 -1.9804829 -1.1519415 -1.3915230
## 3  1.3032991 -0.07587472  1.09034222  1.0511366  1.4544313  1.0872793
## 4  0.1345192 -0.94534143  0.06880320  0.3025321 -0.0642621  0.2672229
##             X7          X8         X9        X10
## 1 -0.378419468 -0.74895788 -0.4347685 -0.4569176
## 2 -0.475160252 -0.92504655 -1.7452433 -1.8336460
## 3  0.769839217  1.62130221  0.8332463  0.9571546
## 4 -0.009806831 -0.09912622  0.5419809  0.5083863
centros <- kmeans_result$centers

# Transponer la matriz para que las filas representen variables y las columnas representen centros de clústeres
centros_transpuestos <- t(centros)
centros_transpuestos
##               1          2           3            4
## X1  -0.44099134 -1.6019942  1.30329909  0.134519202
## X2   0.98017618  0.1468056 -0.07587472 -0.945341430
## X3  -0.03902648 -1.9746159  1.09034222  0.068803201
## X4  -0.24970812 -1.9804829  1.05113665  0.302532147
## X5  -0.55328095 -1.1519415  1.45443130 -0.064262103
## X6  -0.50500344 -1.3915230  1.08727927  0.267222919
## X7  -0.37841947 -0.4751603  0.76983922 -0.009806831
## X8  -0.74895788 -0.9250465  1.62130221 -0.099126218
## X9  -0.43476845 -1.7452433  0.83324633  0.541980889
## X10 -0.45691758 -1.8336460  0.95715465  0.508386259
# Barplot para cada centro de clúster
colores <- c("red", "blue", "green")
barplot(centros_transpuestos, beside = TRUE, col = colores, 
        legend.text = rownames(centros), ylim = c(min(centros_transpuestos), max(centros_transpuestos)),
        main = "Centros de Clústeres de K-means", 
        ylab = "Valor", 
        xlab = "Variables")
legend("topright", legend = paste("Clúster", 1:nrow(centros)), fill = colores)

# Añadir la columna de clusters al DataFrame original
df$Cluster <- kmeans_result$cluster
head(df)
##                 Alcaldia               Colonia   X1   X2    X3    X4    X5   X6
## 1 La Magdalena Contreras  San Jerónimo Líndice 1.86 5.62 47.82 21.69 32.20 4.54
## 2                Tlahuac           Xochicalli  1.54 4.50 46.56 20.05 26.78 5.04
## 4                Tlahuac              La Turba 1.54 4.50 46.56 20.05 26.78 5.04
## 5                Tlahuac       Miguel Hidalgo  1.54 4.50 46.56 20.05 26.78 5.04
## 6                Tlahuac           Los Olivos  1.54 4.50 46.56 20.05 26.78 5.04
## 8         Alvaro Obregon Jardines del Pedregal 1.34 5.68 42.85 17.83 23.74 3.19
##     X7   X8   X9   X10 Cocina_equip Gimnasio Amueblado Alberca Terraza Elevador
## 1 0.71 0.17 9.56 50.08           Si       Si        No      No      Si       Si
## 2 0.96 0.10 9.61 52.75           Si       No        No      No      No       No
## 4 0.96 0.10 9.61 52.75           No       No        No      No      No       No
## 5 0.96 0.10 9.61 52.75           No       No        No      No      No       No
## 6 0.96 0.10 9.61 52.75           No       No        No      No      No       No
## 8 0.28 0.03 6.98 36.60           Si       Si        No      No      Si       Si
##   m2_construido Banos Recamaras Estacionamiento Precio Cluster
## 1           150   2.0         3               2   6500       3
## 2            51   1.0         2               1   1200       4
## 4            42   1.0         2               1   1046       4
## 5            50   1.0         2               1   1195       4
## 6            80   1.0         2               1    388       4
## 8           144   2.5         3               2   7150       1
# Conteo de Registros en Cada Cluster
dft <- df
dft$Cluster<- as.factor(df$Cluster)
summary(dft$Cluster)
##   1   2   3   4 
## 183  82 143 191

Una vez analizados los resultados de los clusters, se pueden etiquetar de la siguiente manera:

Clúster 1, denominado “Viviendas Media Residencial,” se caracteriza por exhibir en su mayoría valores bajos en las métricas de marginación. Esto sugiere que este grupo puede comprender propiedades residenciales con atributos moderadamente marginados, lo que implica un nivel intermedio de desarrollo y servicios en su ubicación.

Por otro lado, el Clúster 2, llamado “Viviendas Premium,” se distingue por mostrar los valores más bajos en todas las variables, lo que apunta a propiedades exclusivas y de alto valor. Estas viviendas probablemente se encuentran en áreas con un bajo grado de marginación social y económica, lo que las convierte en propiedades de lujo.

En contraste, el Clúster 3, conocido como “Viviendas Económicas,” exhibe valores significativamente altos en todas las métricas de marginación. Esto sugiere que estas propiedades se encuentran en áreas con altos niveles de marginación, caracterizadas por limitaciones socioeconómicas. Estas viviendas son más asequibles y dirigidas a personas con ingresos más bajos.

Finalmente, el Clúster 4, denominado “Viviendas Popular,” presenta una mezcla de valores altos y bajos en diversas métricas. Este grupo podría contener una variedad de propiedades populares en ubicaciones diversas, que abarcan desde áreas marginadas hasta zonas más desarrolladas, lo que indica una diversidad en su perfil socioeconómico y de servicios.

Separación DataFrame en Clusters

# Copiar el dataset para segmentar por clases
dfc <- df
dfc$Cluster <- as.character(dfc$Cluster)
summary(dfc$Cluster)
##    Length     Class      Mode 
##       599 character character
# Dar nombes a los clusters de acuerdo al análisis de sus características
dfc$Cluster[dfc$Cluster == 1] = "Viviendas Media Residencial"
dfc$Cluster[dfc$Cluster == 2] = "Viviendas Premium"
dfc$Cluster[dfc$Cluster == 3] = "Viviendas Económicas"
dfc$Cluster[dfc$Cluster == 4] = "Viviendas Popular"


# Crear una tabla de contingencia para la variable 'EMPRESA' por cluster
contingency <- table(dfc$Cluster, dfc$Alcaldia)
contingency <- prop.table(contingency, margin = 1) * 100
print(contingency)
##                              
##                               Alvaro Obregon Azcapotzalco Benito Juárez
##   Viviendas Económicas              0.000000     0.000000      0.000000
##   Viviendas Media Residencial      43.169399    16.393443      0.000000
##   Viviendas Popular                 0.000000     0.000000      0.000000
##   Viviendas Premium                 0.000000     0.000000     31.707317
##                              
##                                Coyoacan Cuahtemoc Cuajimalpa Gustavo A. Madero
##   Viviendas Económicas         0.000000  0.000000   0.000000          0.000000
##   Viviendas Media Residencial  0.000000 14.754098   7.650273          0.000000
##   Viviendas Popular            0.000000  0.000000   0.000000         41.361257
##   Viviendas Premium           54.878049  0.000000   0.000000          0.000000
##                              
##                               Iztacalco Iztapalapa La Magdalena Contreras
##   Viviendas Económicas         0.000000  64.335664              11.188811
##   Viviendas Media Residencial  0.000000   0.000000               0.000000
##   Viviendas Popular            3.664921   0.000000               0.000000
##   Viviendas Premium            0.000000   0.000000               0.000000
##                              
##                               Miguel Hidalgo Milpa alta   Tlahuac   Tlalpan
##   Viviendas Económicas              0.000000   7.692308  0.000000  0.000000
##   Viviendas Media Residencial       0.000000   0.000000  0.000000  0.000000
##   Viviendas Popular                 0.000000   0.000000 41.361257 13.612565
##   Viviendas Premium                13.414634   0.000000  0.000000  0.000000
##                              
##                               Venustiano Carranza Xochimilco
##   Viviendas Económicas                   0.000000  16.783217
##   Viviendas Media Residencial           18.032787   0.000000
##   Viviendas Popular                      0.000000   0.000000
##   Viviendas Premium                      0.000000   0.000000
# Generación de Subconjuntos
VMediaResidencial <- subset(dfc, Cluster == "Viviendas Media Residencial")

VPopular <- subset(dfc, Cluster == "Viviendas Popular")

VPremium <- subset(dfc, Cluster == "Viviendas Premium")

VEcon <- subset(dfc, Cluster == "Viviendas Económicas")

La tabla de contingencia sugiere que ciertas alcaldías están altamente especializadas en una categoría específica de vivienda. Esto podría deberse a varios factores socioeconómicos y de desarrollo urbano que han llevado a una segregación marcada del tipo de vivienda disponible en cada alcaldía. Reflejando valores donde los clusters son más representativos y significativos.

Álvaro Obregón: Una proporción significativa corresponde a Viviendas Media Residencial (43.17%), indicando una tendencia hacia viviendas con características medianas en términos de marginación.

Benito Juárez: Se destaca por tener un porcentaje considerable de Viviendas Premium (31.71%), lo cual sugiere que esta alcaldía alberga una cantidad significativa de propiedades de alto valor.

Iztapalapa: Predominan las Viviendas Económicas (64.34%), lo que refleja una mayor presencia de viviendas asequibles o con mayor marginación en esta zona.

Gustavo A. Madero: Presenta un porcentaje notable de Viviendas Popular (41.36%), lo que puede señalar una inclinación hacia viviendas más accesibles o populares.

Coyoacán y Miguel Hidalgo: Tienen una concentración de Viviendas Premium (54.88% y 13.41% respectivamente), sugiriendo un mercado inmobiliario con propiedades de gama alta.

Modelos CART para cada tipo de viviendas

Para profundizar en la comprensión del mercado inmobiliario de la CDMX y ofrecer recomendaciones personalizadas para cada segmento, se desarrollaron modelos predictivos específicos para cada tipo de vivienda identificado mediante clustering. Al construir un modelo separado para “Viviendas Media Residencial”, “Viviendas Premium”, “Viviendas Económicas” y “Viviendas Popular”, se busca capturar las dinámicas únicas y los factores influyentes que determinan el valor de las propiedades en cada cluster. Esta metodología permite una aproximación más ajustada a la realidad de cada segmento del mercado, proporcionando así insights más precisos para estrategias de inversión, desarrollo urbanístico y políticas de vivienda.

Este proceso involucró pasos clave para cada árbol, incluyendo la partición de datos en conjuntos de entrenamiento, validación y prueba (50,25,25), la construcción del árbol de decisión utilizando las características de infraestructura como predictores, la validación cruzada para ajustar la complejidad del árbol, la interpretación de variables críticas en el proceso de toma de decisiones, y finalmente, la evaluación del modelo mediante la predicción y comparación de los precios reales con las predicciones.

Viviendas económicas

Sets de Entrenamiento, Validación y Prueba - Viviendas Económicas

summary(VEcon)
##                    Alcaldia                   Colonia          X1       
##  Iztapalapa            :92   San Jerónimo Líndice : 11   Min.   :1.860  
##  Xochimilco            :24   Lomas Estrella       : 10   1st Qu.:1.860  
##  La Magdalena Contreras:16   Tepalcates           :  9   Median :1.860  
##  Milpa alta            :11   Santa Martha Acatitla:  5   Mean   :2.006  
##  Alvaro Obregon        : 0   La Noria             :  4   3rd Qu.:1.860  
##  Azcapotzalco          : 0   San Lorenzo La Cebada:  4   Max.   :2.800  
##  (Other)               : 0   (Other)              :100                  
##        X2              X3              X4              X5       
##  Min.   :3.810   Min.   :46.68   Min.   :21.30   Min.   :30.32  
##  1st Qu.:5.620   1st Qu.:47.82   1st Qu.:21.69   1st Qu.:32.20  
##  Median :5.620   Median :47.82   Median :21.69   Median :32.20  
##  Mean   :5.305   Mean   :47.89   Mean   :21.76   Mean   :32.05  
##  3rd Qu.:5.620   3rd Qu.:47.82   3rd Qu.:21.69   3rd Qu.:32.20  
##  Max.   :5.620   Max.   :51.23   Max.   :23.46   Max.   :32.20  
##                                                                 
##        X6              X7               X8               X9        
##  Min.   :4.540   Min.   : 0.710   Min.   :0.1700   Min.   : 8.250  
##  1st Qu.:4.540   1st Qu.: 0.710   1st Qu.:0.1700   1st Qu.: 9.560  
##  Median :4.540   Median : 0.710   Median :0.1700   Median : 9.560  
##  Mean   :5.228   Mean   : 2.747   Mean   :0.1746   Mean   : 9.609  
##  3rd Qu.:4.540   3rd Qu.: 0.710   3rd Qu.:0.1700   3rd Qu.: 9.560  
##  Max.   :8.530   Max.   :10.210   Max.   :0.2300   Max.   :13.060  
##                                                                    
##       X10        Cocina_equip Gimnasio  Amueblado Alberca   Terraza   Elevador 
##  Min.   :50.06   No : 14      No :123   No :136   No :140   No :106   No :113  
##  1st Qu.:50.08   Si :129      No :  0   No :  0   No :  0   No :  0   Si : 30  
##  Median :50.08   Si :  0      Si : 20   Si :  7   Si :  3   Si : 37   Si :  0  
##  Mean   :51.15                Si :  0   Si :  0   si :  0   Si :  0            
##  3rd Qu.:50.08                                    Si :  0                      
##  Max.   :63.97                                                                 
##                                                                                
##  m2_construido        Banos         Recamaras     Estacionamiento
##  Min.   : 34.00   Min.   :1.000   Min.   :1.000   Min.   :0.000  
##  1st Qu.: 54.00   1st Qu.:1.000   1st Qu.:2.000   1st Qu.:1.000  
##  Median : 65.00   Median :1.000   Median :2.000   Median :1.000  
##  Mean   : 80.39   Mean   :1.294   Mean   :2.343   Mean   :1.147  
##  3rd Qu.: 86.00   3rd Qu.:1.500   3rd Qu.:3.000   3rd Qu.:1.000  
##  Max.   :450.00   Max.   :4.000   Max.   :5.000   Max.   :3.000  
##                                                                  
##      Precio        Cluster         
##  Min.   :  150   Length:143        
##  1st Qu.:  785   Class :character  
##  Median : 1280   Mode  :character  
##  Mean   : 1861                     
##  3rd Qu.: 1938                     
##  Max.   :10800                     
## 
VEcon$Cluster <- NULL

# Establecer la semilla para reproducibilidad 
set.seed(123)

# Paso 1: Dividir el conjunto de datos en entrenamiento (50%) y temporal (50%)
trainIndex1 <- createDataPartition(VEcon$Precio, p = 0.5, list = FALSE, times = 1)
train <- VEcon[trainIndex1, ]
temp <- VEcon[-trainIndex1, ]

# Paso 2: Dividir el conjunto temporal en validación (50% de temp) y prueba (50% de temp)
trainIndex2 <- createDataPartition(temp$Precio, p = 0.5, list = FALSE, times = 1)
validation <- temp[trainIndex2, ]
testEcon <- temp[-trainIndex2, ]

Árbol de Decisión - Viviendas Económicas

# Construir el árbol de decisión
tree <- rpart(Precio ~ m2_construido + Banos + Recamaras + Estacionamiento + Gimnasio + Cocina_equip + Gimnasio + Amueblado + Alberca + Terraza + Elevador, data = train, method = "anova", control = rpart.control(cp = 0))
rpart.plot(tree)

# Visualizar la curva de complejidad de costo
plotcp(tree)

Se construyó un árbol de decisión para predecir el precio de las viviendas en la CDMX. Se eligieron variables como metros cuadrados construidos, número de baños, recámaras, estacionamiento, y otras características relevantes, con un enfoque particular en los metros cuadrados y la cantidad de baños, dado que estas variables suelen tener una influencia significativa en el valor de una propiedad.

El uso de la técnica ANOVA en el modelo sugiere un intento de capturar las variaciones de precios a través de estas características. Posteriormente, la curva de complejidad se utilizó para evaluar el equilibrio entre la precisión del modelo y su complejidad. Esta curva ayuda a identificar el tamaño óptimo del árbol, minimizando el riesgo de sobreajuste y asegurando que el modelo sea lo suficientemente complejo para capturar las relaciones importantes en los datos.

Validación Cruzada - Viviendas Económicas

set.seed(123)
preproc <- preProcess(validation, method = "medianImpute")
validation_clean <- predict(preproc, VEcon)


# Definir el método de control de entrenamiento para la validación cruzada k-fold 10 pliegues o subconjuntos. El modelo se entrena 10 veces
ctrl <- trainControl(method = "cv", number = 10)

# Entrenar el modelo con validación cruzada
tree_model_cv_economic <- train(Precio ~ ., data = validation_clean, 
                       method = "rpart", 
                       trControl = ctrl,
                       tuneLength = 10)
## Warning in nominalTrainWorkflow(x = x, y = y, wts = weights, info = trainInfo,
## : There were missing values in resampled performance measures.
# Ver los resultados
print(tree_model_cv_economic)
## CART 
## 
## 143 samples
##  22 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold) 
## Summary of sample sizes: 127, 128, 128, 129, 127, 130, ... 
## Resampling results across tuning parameters:
## 
##   cp            RMSE       Rsquared   MAE      
##   0.0002265654   962.5014  0.6939193   658.0168
##   0.0004665129   959.7447  0.6960668   653.5596
##   0.0008052244   954.7189  0.7018464   642.6778
##   0.0024056419   946.7250  0.7097076   631.2843
##   0.0037821298   957.5687  0.7037688   646.6300
##   0.0075885014   982.7915  0.6736595   667.6045
##   0.0093075366   989.7040  0.6699152   674.5190
##   0.0163959588  1011.9723  0.6513945   691.3735
##   0.0776976294  1053.4816  0.6668810   736.2038
##   0.6850076345  1620.4865  0.7034096  1102.0062
## 
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was cp = 0.002405642.
# Elegir un valor de cp basado en la gráfica y podar el árbol
pruned_tree <- prune(tree, cp = 0.0008)

# Visualizar el árbol podado
rpart.plot(pruned_tree)

rpart.rules(pruned_tree,style = 'tall',cover = T)
## Precio is  889 with cover 24% when
##     m2_construido < 55
##     Banos < 2
## 
## Precio is 1127 with cover 42% when
##     m2_construido is 55 to 83
##     Banos < 2
## 
## Precio is 1842 with cover 11% when
##     m2_construido is 83 to 111
##     Banos < 2
## 
## Precio is 2494 with cover 14% when
##     m2_construido < 111
##     Banos >= 2
## 
## Precio is 5268 with cover 10% when
##     m2_construido >= 111

Datos: 143 muestras con 22 predictores

Validación Cruzada: Se usó un enfoque de 10 pliegues.

Resultados de Rendimiento: Varias configuraciones del parámetro de complejidad (cp) fueron evaluadas. El modelo con cp (Complexity Parameter) = 0.002405642 tuvo el menor error cuadrático medio (RMSE), indicando el mejor equilibrio entre ajuste y complejidad, pues el proceso de poda del árbol, basado en este parámetro, elimina las divisiones del árbol que no aportan significativamente a su capacidad predictiva, según un cálculo de costo-complejidad.

Reglas del Árbol Podado: Se describen reglas específicas para predecir precios basados en metros cuadrados construidos y número de baños. Por ejemplo, para viviendas menores de 55 m² con menos de 2 baños, el precio estimado es 889.

Validación Cruzada

set.seed(123)
preproc <- preProcess(validation, method = "medianImpute")
validation_clean <- predict(preproc, VPopular)


# Definir el método de control de entrenamiento para la validación cruzada k-fold 10 pliegues o subconjuntos. El modelo se entrena 10 veces
ctrl <- trainControl(method = "cv", number = 10)

# Entrenar el modelo con validación cruzada
tree_model_cv_popular <- train(Precio ~ ., data = validation_clean, 
                       method = "rpart", 
                       trControl = ctrl,
                       tuneLength = 10)
## Warning in nominalTrainWorkflow(x = x, y = y, wts = weights, info = trainInfo,
## : There were missing values in resampled performance measures.
# Ver los resultados
print(tree_model_cv_popular)
## CART 
## 
## 191 samples
##  22 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold) 
## Summary of sample sizes: 172, 172, 171, 171, 173, 172, ... 
## Resampling results across tuning parameters:
## 
##   cp          RMSE      Rsquared   MAE     
##   0.00000000  1025.197  0.4966662  540.2378
##   0.04894713  1077.829  0.4329132  577.7041
##   0.09789426  1107.154  0.4025771  591.7837
##   0.14684138  1080.299  0.4564816  590.9161
##   0.19578851  1080.299  0.4564816  590.9161
##   0.24473564  1080.299  0.4564816  590.9161
##   0.29368277  1080.299  0.4564816  590.9161
##   0.34262990  1080.299  0.4564816  590.9161
##   0.39157702  1080.299  0.4564816  590.9161
##   0.44052415  1346.006  0.2732899  777.7428
## 
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was cp = 0.
# Elegir un valor de cp basado en la gráfica y podar el árbol
pruned_tree <- prune(tree, cp = 0.0)

# Visualizar el árbol podado
rpart.plot(pruned_tree)

rpart.rules(pruned_tree,style = 'tall',cover = T)
## Precio is  553 with cover 20% when
##     Gimnasio is No
##     Banos < 1.3
##     Elevador is Si
## 
## Precio is  786 with cover 10% when
##     Gimnasio is No
##     Banos < 1.3
##     Elevador is No
##     Cocina_equip is Si
##     m2_construido < 52
## 
## Precio is  791 with cover 7% when
##     Gimnasio is No
##     Banos < 1.3
##     Elevador is No
##     Cocina_equip is Si
##     m2_construido >= 68
## 
## Precio is  924 with cover 14% when
##     Gimnasio is No
##     Banos < 1.3
##     Elevador is No
##     Cocina_equip is Si
##     m2_construido is 52 to 68
## 
## Precio is 1022 with cover 18% when
##     Gimnasio is No
##     Banos < 1.3
##     Elevador is No
##     Cocina_equip is No
## 
## Precio is 1169 with cover 14% when
##     Gimnasio is No
##     Banos >= 1.3
## 
## Precio is 2945 with cover 18% when
##     Gimnasio is Si

Precio 924: Para viviendas sin gimnasio, con menos de 1.3 baños, sin elevador, con cocina equipada y con un tamaño de 52 a 68 m², el precio estimado es de 924. Este grupo representa el 14% del total.

Precio 1022: Viviendas sin gimnasio, con menos de 1.3 baños, sin elevador y sin cocina equipada, tienen un precio estimado de 1022. Cubren el 18% del total.

Precio 1169: Viviendas sin gimnasio pero con 1.3 baños o más tienen un precio estimado de 1169, representando el 14%.

Precio 2945: Las viviendas con gimnasio tienen un precio significativamente mayor, estimado en 2945, y constituyen el 18% del total.

Estos resultados resaltan cómo características específicas como la presencia de gimnasio, número de baños, y las comodidades impactan en el precio de las viviendas populares.

Modelo para Viviendas Media Residencial

Se realiza el mismo procedimiento para viviendas residenciales

Sets de Entrenamiento, Validación y Prueba - Viviendas Media Residencial

summary(VMediaResidencial)
##                 Alcaldia                   Colonia          X1       
##  Alvaro Obregon     :79   Santa Fe             : 22   Min.   :0.900  
##  Venustiano Carranza:33   San Angel            : 13   1st Qu.:0.960  
##  Azcapotzalco       :30   Jardines del Pedregal:  6   Median :1.340  
##  Cuahtemoc          :27   Nicolás Bravo        :  6   Mean   :1.171  
##  Cuajimalpa         :14   Condesa              :  5   3rd Qu.:1.340  
##  Benito Juárez      : 0   Juárez               :  5   Max.   :1.550  
##  (Other)            : 0   (Other)              :126                  
##        X2             X3              X4              X5              X6       
##  Min.   :5.68   Min.   :38.99   Min.   :12.23   Min.   :20.61   Min.   :1.700  
##  1st Qu.:5.68   1st Qu.:42.09   1st Qu.:15.50   1st Qu.:23.74   1st Qu.:1.830  
##  Median :5.76   Median :42.85   Median :17.83   Median :23.74   Median :3.190  
##  Mean   :6.02   Mean   :42.36   Mean   :16.44   Mean   :24.55   Mean   :2.658  
##  3rd Qu.:6.37   3rd Qu.:43.62   3rd Qu.:17.83   3rd Qu.:26.83   3rd Qu.:3.190  
##  Max.   :6.78   Max.   :44.36   Max.   :18.90   Max.   :28.25   Max.   :4.170  
##                                                                                
##        X7               X8                X9             X10       
##  Min.   :0.0600   Min.   :0.03000   Min.   :6.210   Min.   :30.68  
##  1st Qu.:0.1100   1st Qu.:0.03000   1st Qu.:6.980   1st Qu.:35.22  
##  Median :0.2800   Median :0.03000   Median :6.980   Median :36.60  
##  Mean   :0.2562   Mean   :0.03705   Mean   :7.151   Mean   :36.18  
##  3rd Qu.:0.2800   3rd Qu.:0.04000   3rd Qu.:7.570   3rd Qu.:39.08  
##  Max.   :0.4600   Max.   :0.06000   Max.   :8.150   Max.   :39.65  
##                                                                    
##  Cocina_equip Gimnasio Amueblado Alberca   Terraza   Elevador  m2_construido  
##  No :  2      No :80   No :167   No :119   No : 56   No : 35   Min.   : 41.0  
##  Si :141      No :23   No : 12   No :  4   No :  4   Si :115   1st Qu.: 63.5  
##  Si : 40      Si :66   Si :  2   Si : 51   Si :103   Si : 33   Median : 90.0  
##               Si :14   Si :  2   si :  1   Si : 20             Mean   :108.6  
##                                  Si :  8                       3rd Qu.:141.0  
##                                                                Max.   :300.0  
##                                                                               
##      Banos         Recamaras     Estacionamiento     Precio     
##  Min.   :1.000   Min.   :1.000   Min.   :0.000   Min.   :  247  
##  1st Qu.:1.000   1st Qu.:2.000   1st Qu.:1.000   1st Qu.: 1990  
##  Median :2.000   Median :2.000   Median :2.000   Median : 4800  
##  Mean   :1.915   Mean   :2.246   Mean   :1.574   Mean   : 4989  
##  3rd Qu.:2.000   3rd Qu.:3.000   3rd Qu.:2.000   3rd Qu.: 7450  
##  Max.   :4.000   Max.   :3.000   Max.   :4.000   Max.   :12500  
##                                                                 
##    Cluster         
##  Length:183        
##  Class :character  
##  Mode  :character  
##                    
##                    
##                    
## 
VMediaResidencial$Cluster <- NULL

# Establecer la semilla para reproducibilidad 
set.seed(123)

# Paso 1: Dividir el conjunto de datos en entrenamiento (50%) y temporal (50%)
trainIndex1 <- createDataPartition(VMediaResidencial$Precio, p = 0.5, list = FALSE, times = 1)
train <- VMediaResidencial[trainIndex1, ]
temp <- VMediaResidencial[-trainIndex1, ]

# Paso 2: Dividir el conjunto temporal en validación (50% de temp) y prueba (50% de temp)
trainIndex2 <- createDataPartition(temp$Precio, p = 0.5, list = FALSE, times = 1)
validation <- temp[trainIndex2, ]
testResi <- temp[-trainIndex2, ]

Árbol de Decisión - Viviendas Media Residencial

# Construir el árbol de decisión
tree <- rpart(Precio ~ m2_construido + Banos + Recamaras + Estacionamiento + Gimnasio + Cocina_equip + Gimnasio + Amueblado + Alberca + Terraza + Elevador, data = train, method = "anova", control = rpart.control(cp = 0))
rpart.plot(tree)

# Visualizar la curva de complejidad de costo
plotcp(tree)

Validación Cruzada

set.seed(123)
preproc <- preProcess(validation, method = "medianImpute")
validation_clean <- predict(preproc, VMediaResidencial)


# Definir el método de control de entrenamiento para la validación cruzada k-fold 10 pliegues o subconjuntos. El modelo se entrena 10 veces
ctrl <- trainControl(method = "cv", number = 10)

# Entrenar el modelo con validación cruzada
tree_model_cv_residencial <- train(Precio ~ ., data = validation_clean, 
                       method = "rpart", 
                       trControl = ctrl,
                       tuneLength = 10)
## Warning in nominalTrainWorkflow(x = x, y = y, wts = weights, info = trainInfo,
## : There were missing values in resampled performance measures.
# Ver los resultados
print(tree_model_cv_residencial)
## CART 
## 
## 183 samples
##  22 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold) 
## Summary of sample sizes: 165, 163, 165, 167, 164, 164, ... 
## Resampling results across tuning parameters:
## 
##   cp           RMSE      Rsquared   MAE      
##   0.003495378  1412.697  0.8171329   979.8117
##   0.004463342  1434.230  0.8112463  1000.4597
##   0.006091701  1438.484  0.8082939  1007.2666
##   0.006989497  1410.232  0.8124515   991.0224
##   0.009008367  1386.033  0.8278197   963.2951
##   0.017344947  1441.532  0.8162223  1052.4314
##   0.024773861  1546.643  0.7906413  1180.9564
##   0.083200306  1762.704  0.7179394  1361.8338
##   0.098483168  1925.277  0.6620508  1532.4232
##   0.636359286  3005.807  0.4681920  2545.2027
## 
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was cp = 0.009008367.
# Elegir un valor de cp basado en el menor RMSE
pruned_tree <- prune(tree, cp = 0.017)

# Visualizar el árbol podado
rpart.plot(pruned_tree)

rpart.rules(pruned_tree,style = 'tall',cover = T)
## Precio is  697 with cover 17% when
##     m2_construido < 88
##     Terraza is No or No
##     Cocina_equip is Si
## 
## Precio is 2411 with cover 12% when
##     m2_construido < 88
##     Terraza is No or No
##     Cocina_equip is No or Si
## 
## Precio is 3927 with cover 21% when
##     m2_construido < 88
##     Terraza is Si or Si
## 
## Precio is 6052 with cover 28% when
##     m2_construido is 88 to 139
## 
## Precio is 8877 with cover 22% when
##     m2_construido >= 139

Desempeño del Modelo: Con un rango de valores de cp, el modelo con cp = 0.009008367 obtuvo el menor RMSE, indicando ser el más óptimo en términos de precisión y complejidad.

Predicciones de Precio:

Precio 697: Para viviendas menores de 88 m² sin terraza y con cocina equipada. Precio 2411: Para viviendas menores de 88 m², independientemente del estado de la terraza o cocina equipada. Precio 3927: Para viviendas menores de 88 m² con terraza. Precio 6052: Para viviendas de tamaño entre 88 y 139 m². Precio 8877: Para viviendas de más de 139 m².

Estos resultados reflejan cómo las características como el tamaño, la presencia de terraza y la cocina equipada influyen significativamente en el precio de las viviendas residenciales.

Modelo para Viviendas Premium

Sets de Entrenamiento, Validación y Prueba - Viviendas Premium

summary(VPremium)
##            Alcaldia                       Colonia         X1        
##  Coyoacan      :45   Narvarte                 : 7   Min.   :0.3500  
##  Benito Juárez :26   Pedregal de Santo Domingo: 7   1st Qu.:0.3500  
##  Miguel Hidalgo:11   Culhuacán                : 6   Median :0.7700  
##  Alvaro Obregon: 0   Polanco                  : 6   Mean   :0.6154  
##  Azcapotzalco  : 0   Pedregal de Carrasco     : 4   3rd Qu.:0.7700  
##  Cuahtemoc     : 0   Del Valle                : 3   Max.   :0.7700  
##  (Other)       : 0   (Other)                  :49                   
##        X2              X3              X4               X5       
##  Min.   :4.980   Min.   :31.70   Min.   : 6.070   Min.   :18.46  
##  1st Qu.:4.980   1st Qu.:31.70   1st Qu.: 6.070   1st Qu.:20.07  
##  Median :4.980   Median :32.91   Median :10.970   Median :24.55  
##  Mean   :5.455   Mean   :32.88   Mean   : 9.353   Mean   :22.31  
##  3rd Qu.:6.230   3rd Qu.:32.91   3rd Qu.:10.970   3rd Qu.:24.55  
##  Max.   :6.230   Max.   :35.54   Max.   :10.970   Max.   :24.55  
##                                                                  
##        X6              X7                X8                X9       
##  Min.   :0.480   Min.   :0.03000   Min.   :0.02000   Min.   :3.170  
##  1st Qu.:0.480   1st Qu.:0.03000   1st Qu.:0.02000   1st Qu.:3.170  
##  Median :1.700   Median :0.05000   Median :0.03000   Median :5.470  
##  Mean   :1.227   Mean   :0.04634   Mean   :0.02683   Mean   :4.611  
##  3rd Qu.:1.700   3rd Qu.:0.05000   3rd Qu.:0.03000   3rd Qu.:5.470  
##  Max.   :1.700   Max.   :0.07000   Max.   :0.03000   Max.   :5.470  
##                                                                     
##       X10        Cocina_equip Gimnasio Amueblado Alberca  Terraza  Elevador
##  Min.   :15.15   No : 0       No :63   No :81    No :66   No :23   No :21  
##  1st Qu.:15.15   Si :82       No : 0   No : 0    No : 0   No : 0   Si :61  
##  Median :25.71   Si : 0       Si :19   Si : 1    Si :16   Si :59   Si : 0  
##  Mean   :21.61                Si : 0   Si : 0    si : 0   Si : 0           
##  3rd Qu.:25.71                                   Si : 0                    
##  Max.   :25.71                                                             
##                                                                            
##  m2_construido        Banos         Recamaras     Estacionamiento
##  Min.   : 35.00   Min.   :1.000   Min.   :1.000   Min.   :0.000  
##  1st Qu.: 65.25   1st Qu.:1.000   1st Qu.:2.000   1st Qu.:1.000  
##  Median : 84.00   Median :2.000   Median :2.000   Median :1.000  
##  Mean   : 97.30   Mean   :1.768   Mean   :2.256   Mean   :1.415  
##  3rd Qu.:111.00   3rd Qu.:2.000   3rd Qu.:3.000   3rd Qu.:2.000  
##  Max.   :238.00   Max.   :3.000   Max.   :3.000   Max.   :3.000  
##                                                                  
##      Precio        Cluster         
##  Min.   : 1080   Length:82         
##  1st Qu.: 2600   Class :character  
##  Median : 3350   Mode  :character  
##  Mean   : 4453                     
##  3rd Qu.: 5446                     
##  Max.   :12500                     
## 
VPremium$Cluster<- NULL

# Establecer la semilla para reproducibilidad 
set.seed(123)

# Paso 1: Dividir el conjunto de datos en entrenamiento (50%) y temporal (50%)
trainIndex1 <- createDataPartition(VPremium$Precio, p = 0.5, list = FALSE, times = 1)
train <- VPremium[trainIndex1, ]
temp <- VPremium[-trainIndex1, ]

# Paso 2: Dividir el conjunto temporal en validación (50% de temp) y prueba (50% de temp)
trainIndex2 <- createDataPartition(temp$Precio, p = 0.5, list = FALSE, times = 1)
validation <- temp[trainIndex2, ]
testPrem <- temp[-trainIndex2, ]

Árbol de Decisión - Viviendas Media Residencial

# Construir el árbol de decisión
tree <- rpart(Precio ~ m2_construido + Banos + Recamaras + Estacionamiento + Gimnasio + Cocina_equip + Gimnasio + Amueblado + Alberca + Terraza + Elevador, data = train, method = "anova", control = rpart.control(cp = 0))
rpart.plot(tree)

# Visualizar la curva de complejidad de costo
plotcp(tree)

Validación Cruzada

set.seed(123)
preproc <- preProcess(validation, method = "medianImpute")
validation_clean <- predict(preproc, VPremium)


# Definir el método de control de entrenamiento para la validación cruzada k-fold 10 pliegues o subconjuntos. El modelo se entrena 10 veces
ctrl <- trainControl(method = "cv", number = 10)

# Entrenar el modelo con validación cruzada
tree_model_cv_premium <- train(Precio ~ ., data = validation_clean, 
                       method = "rpart", 
                       trControl = ctrl,
                       tuneLength = 10)
## Warning in nominalTrainWorkflow(x = x, y = y, wts = weights, info = trainInfo,
## : There were missing values in resampled performance measures.
# Ver los resultados
print(tree_model_cv_premium)
## CART 
## 
## 82 samples
## 22 predictors
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold) 
## Summary of sample sizes: 75, 74, 73, 74, 73, 74, ... 
## Resampling results across tuning parameters:
## 
##   cp          RMSE      Rsquared   MAE     
##   0.00000000  1363.814  0.7293967  1112.320
##   0.07884047  1545.491  0.7440184  1251.368
##   0.15768093  1591.768  0.8024555  1296.617
##   0.23652140  1591.768  0.8024555  1296.617
##   0.31536186  1591.768  0.8024555  1296.617
##   0.39420233  1591.768  0.8024555  1296.617
##   0.47304280  1591.768  0.8024555  1296.617
##   0.55188326  1591.768  0.8024555  1296.617
##   0.63072373  1591.768  0.8024555  1296.617
##   0.70956419  2706.089  0.5564334  2065.383
## 
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was cp = 0.
# Elegir un valor de cp basado en la gráfica y podar el árbol
pruned_tree <- prune(tree, cp = 0.0)

# Visualizar el árbol podado
rpart.plot(pruned_tree)

rpart.rules(pruned_tree,style = 'tall',cover = T)
## Precio is 1947 with cover 21% when
##     m2_construido < 94
##     Elevador is No
## 
## Precio is 3316 with cover 36% when
##     m2_construido < 94
##     Elevador is Si
## 
## Precio is 4974 with cover 24% when
##     m2_construido is 94 to 132
## 
## Precio is 9287 with cover 19% when
##     m2_construido >= 132

Desempeño del Modelo: Con varios valores de cp, el modelo con cp = 0.07 tuvo el menor RMSE, indicando ser el más preciso para estas viviendas.

Predicciones de Precio:

Precio 1947: Para viviendas menores de 94 m² sin elevador. Precio 3316: Para viviendas menores de 94 m² con elevador. Precio 4974: Para viviendas entre 94 y 132 m². Precio 9287: Para viviendas de más de 132 m².

Estos resultados reflejan cómo el tamaño y la presencia de un elevador influyen significativamente en el precio de las viviendas premium. Las viviendas más grandes y aquellas con elevador tienden a tener precios más altos.

Predicciones y Evaluación del Modelo (MAE y RMSE)

calculate_metrics <- function(predictions, actual) {
  mae <- mean(abs(predictions - actual))
  rmse <- sqrt(mean((predictions - actual)^2))
  mape <- mean(abs((predictions - actual) / actual)) * 100
  return(c(MAE = mae, RMSE = rmse, MAPE = mape))
}

## Evaluación para Viviendas Económicas
pred_VEcon <- predict(tree_model_cv_economic, newdata = testEcon)
metrics_VEcon <- calculate_metrics(pred_VEcon, testEcon$Precio)
cat("Métricas para Viviendas Económicas:\n")
## Métricas para Viviendas Económicas:
cat("MAE:", metrics_VEcon["MAE"], "\n")
## MAE: 488.0508
cat("RMSE:", metrics_VEcon["RMSE"], "\n")
## RMSE: 926.7467
cat("MAPE:", metrics_VEcon["MAPE"], "%\n")
## MAPE: 43.43769 %
## Evaluación para Viviendas Popular
pred_VPopular <- predict(tree_model_cv_popular, newdata = testPop)
metrics_VPopular <- calculate_metrics(pred_VPopular, testPop$Precio)
cat("\nMétricas para Viviendas Popular:\n")
## 
## Métricas para Viviendas Popular:
cat("MAE:", metrics_VPopular["MAE"], "\n")
## MAE: 480.5607
cat("RMSE:", metrics_VPopular["RMSE"], "\n")
## RMSE: 1106.681
cat("MAPE:", metrics_VPopular["MAPE"], "%\n")
## MAPE: 40.97677 %
## Evaluación para Viviendas Media Residencial
pred_VMediaResidencial <- predict(tree_model_cv_residencial, newdata = testResi)
metrics_VMediaResidencial <- calculate_metrics(pred_VMediaResidencial, testResi$Precio)
cat("\nMétricas para Viviendas Media Residencial:\n")
## 
## Métricas para Viviendas Media Residencial:
cat("MAE:", metrics_VMediaResidencial["MAE"], "\n")
## MAE: 965.5537
cat("RMSE:", metrics_VMediaResidencial["RMSE"], "\n")
## RMSE: 1500.721
cat("MAPE:", metrics_VMediaResidencial["MAPE"], "%\n")
## MAPE: 37.66764 %
## Evaluación para Viviendas Premium
pred_VPremium <- predict(tree_model_cv_premium, newdata = testPrem)
metrics_VPremium <- calculate_metrics(pred_VPremium, testPrem$Precio)
cat("\nMétricas para Viviendas Premium:\n")
## 
## Métricas para Viviendas Premium:
cat("MAE:", metrics_VPremium["MAE"], "\n")
## MAE: 619.3407
cat("RMSE:", metrics_VPremium["RMSE"], "\n")
## RMSE: 726.5856
cat("MAPE:", metrics_VPremium["MAPE"], "%\n")
## MAPE: 17.95077 %

Modelo Económico (Viviendas Económicas): El modelo económico muestra un MAE (Error Absoluto Medio) relativamente bajo y un RMSE (Error Cuadrático Medio de la Raíz) moderado. Esto sugiere que las predicciones tienden a estar cerca de los valores reales en promedio, pero hay algunas variaciones significativas en los errores de predicción. Además, el MAPE (Error Porcentual Absoluto Medio) del 43.44% indica que las predicciones pueden variar en un 43.44% en promedio en relación con los precios reales. En general, este modelo proporciona resultados aceptables para la predicción de precios de viviendas económicas, pero es importante considerar que aún existe un margen de mejora para reducir las variaciones en las predicciones y que el margen de error es demasiado alto para considerar al modelo como fiable y certero en sus predicciones.

Modelo Popular (Viviendas Popular): El modelo popular muestra un MAE similar al modelo económico, lo que sugiere una precisión similar en las predicciones en términos de valor promedio absoluto. Sin embargo, el RMSE más alto indica una mayor variabilidad en los errores de predicción, lo que podría ser atribuible a valores atípicos o predicciones menos precisas. El MAPE del 40.98% indica que las predicciones pueden variar en un 40.98% en promedio en relación con los precios reales. Esto señala que el modelo puede tener dificultades para predecir de manera consistente los precios de las viviendas populares, y podría requerir mejoras para reducir las variaciones en las predicciones.

Modelo Medio Residencial (Viviendas Media Residencial): El modelo de viviendas de nivel medio residencial muestra un MAE y un RMSE significativamente más altos en comparación con los modelos económico y popular. Esto indica una precisión de predicción general más baja y una mayor inconsistencia en las predicciones. El MAPE del 37.67% señala que las predicciones pueden variar en un 37.67% en promedio en relación con los precios reales. Estas métricas indican que el modelo tiene dificultades para predecir con precisión los precios de las viviendas de nivel medio residencial, y se recomienda una revisión exhaustiva y posibles mejoras en el modelo.

Modelo Premium (Viviendas Premium): El modelo premium muestra un MAE moderado y el RMSE más bajo de los cuatro modelos. Esto sugiere que, aunque existen errores en las predicciones, tienden a ser menos extremos en comparación con los otros modelos. El MAPE del 17.95% indica que las predicciones pueden variar en un 17.95% en promedio en relación con los precios reales, lo que es relativamente bajo en comparación con los otros modelos. En resumen, este modelo parece tener el mejor rendimiento en términos de consistencia y precisión en la predicción de precios para viviendas premium.

En resumen, es esencial considerar estas métricas al evaluar y comparar modelos predictivos. La efectividad de cada modelo dependerá del contexto del mercado y de las expectativas de precisión. Si se requiere una mayor precisión, es posible que se deban realizar mejoras en los modelos, especialmente en el caso del modelo de viviendas de nivel medio residencial. Estas métricas son fundamentales para respaldar la toma de decisiones basada en datos y mejorar la calidad de las predicciones en el mercado de viviendas.

Conclusiones

Como reflexión final, los modelos predictivos elaborados reconocen que diferentes variables pueden influir en el precio de las viviendas en cada categoría, por tanto, Las reglas de decisión generadas por los árboles de decisión proporcionan recomendaciones valiosas para inversores y desarrolladores. Para obtener resultados aún más sólidos, se recomienda realizar una validación exhaustiva de los modelos y considerar la inclusión de más características relevantes, como ubicación geográfica, tendencias macroeconómicas y datos demográficos, así como ampliar el tamaño de la muestra de los datos.

En conclusión, Los insights obtenidos pueden aplicarse en estrategias de inversión, desarrollo de políticas de vivienda, y en la planificación urbana. Al entender las preferencias de los compradores y las tendencias del mercado, los desarrolladores y agentes inmobiliarios pueden adaptar sus proyectos y estrategias de marketing para satisfacer mejor las necesidades del mercado en la CDMX.

Referencias

Gutiérrez, F. (2023). Situación de la vivienda en la CDMX: Precios más altos, pero con oferta más asequible ¿Cuál es la respuesta a esto? El Economista. https://www.eleconomista.com.mx/econohabitat/Situacion-de-la-vivienda-en-la-CDMX-Precios-mas-altos-pero-con-oferta-mas-asequible-Cual-es-la-respuesta-a-esto-20230529-0016.html

Real Estate Analytics. (2023). CDMX, Estudio de mercado inmobiliario. Real Estate Analytics. https://propiedades.com/blog/informacion-inmobiliaria/estudio-mercado-inmobiliario-cdmx

LS0tCnRpdGxlOiAiRXZpZGVuY2UgMSIKYXV0aG9yOiAiR2FicmllbCBNZWRpbmEiCmRhdGU6ICIyMDIzLTExLTA2IgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMKICAgIHRoZW1lOiB1bml0ZWQKICAgIGhpZ2hsaWdodDogdGFuZ28KICBwZGZfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKI0VsIG1lcmNhZG8gZGUgYmllbmVzIHJhw61jZXMgZW4gbGEgQ0RNWAoKICBMYSBDaXVkYWQgZGUgTcOpeGljbywgY29tbyBtZXRyw7Nwb2xpcyBlbiBjb25zdGFudGUgY3JlY2ltaWVudG8geSBjYW1iaW8sIHByZXNlbnRhIHVuIG1lcmNhZG8gaW5tb2JpbGlhcmlvIGRpbsOhbWljbyB5IGNvbXBsZWpvLiBMYSBjb21wcmVuc2nDs24gcHJvZnVuZGEgZGUgZXN0ZSBtZXJjYWRvIG5vIHNvbG8gZXMgY3J1Y2lhbCBwYXJhIGludmVyc2lvbmlzdGFzIHkgZGVzYXJyb2xsYWRvcmVzLCBzaW5vIHRhbWJpw6luIHBhcmEgaW5kaXZpZHVvcyB5IGZhbWlsaWFzIGVuIGJ1c2NhIGRlIHN1IGhvZ2FyIGlkZWFsLiBFbiBlc3RlIGNvbnRleHRvLCBlbCBhbsOhbGlzaXMgZGUgZGF0b3MgaW5tb2JpbGlhcmlvcyBtZWRpYW50ZSB0w6ljbmljYXMgYXZhbnphZGFzIGRlIG1pbmVyw61hIGRlIGRhdG9zIHkgYXByZW5kaXphamUgYXV0b23DoXRpY28sIGNvbW8gc2UgZGVtb3N0csOzIGVuIGVsIGPDs2RpZ28gcHJvcG9yY2lvbmFkbywgZXMgZnVuZGFtZW50YWwuIEVzdGUgYW7DoWxpc2lzIGFiYXJjw7MgZGVzZGUgbGEgbGltcGllemEgeSBwcmVwYXJhY2nDs24gZGUgZGF0b3MgaGFzdGEgbGEgYXBsaWNhY2nDs24gZGUgYWxnb3JpdG1vcyBzb2Zpc3RpY2Fkb3MgY29tbyBLLU1lYW5zIHBhcmEgc2VnbWVudGFjacOzbiB5IG1vZGVsb3MgcHJlZGljdGl2b3MgY29tbyDDoXJib2xlcyBkZSBkZWNpc2nDs24uIEFsIGludmVzdGlnYXIgeSBhcGxpY2FyIGVzdGFzIHTDqWNuaWNhcywgc2UgaGEgcmVhbGl6YWRvIHVuIGVzZnVlcnpvIHNpZ25pZmljYXRpdm8gcGFyYSBleHRyYWVyIGluc2lnaHRzIHZhbGlvc29zIHNvYnJlIHByZWNpb3MsIHRlbmRlbmNpYXMgeSBwYXRyb25lcyBkZWwgbWVyY2FkbyBpbm1vYmlsaWFyaW8gZGUgbGEgQ0RNWCwgY29uIGVsIG9iamV0aXZvIGRlIHByb3BvcmNpb25hciB1bmEgYmFzZSBzw7NsaWRhIHBhcmEgbGEgdG9tYSBkZSBkZWNpc2lvbmVzIGluZm9ybWFkYXMgZW4gZXN0ZSBzZWN0b3Igdml0YWwuCgojIEFuw6FsaXNpcyBFeHBsb3JhdG9yaW8gZGUgbG9zIGRhdG9zCgpFbiBwcmltZXIgbHVnYXIsIHNlIGNhcmdhbiBsYXMgbGlicmVyw61hcyBuZWNlc2FyaWFzIHBhcmEgZWwgZnVuY2lvbmFtaWVudG8gZGVsIGPDs2RpZ28uCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KHJwYXJ0KQpsaWJyYXJ5KHJwYXJ0LnBsb3QpCmxpYnJhcnkocGFydHkpCmxpYnJhcnkoZ21vZGVscykKbGlicmFyeShrbml0cikKbGlicmFyeShyZWFkeGwpCmxpYnJhcnkocmVhZHIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoY2x1c3RlcikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGNvcnJwbG90KQpgYGAKClBvc3Rlcmlvcm1lbnRlLCBzZSBjYXJnYSBsYSBiYXNlIGRlIGRhdG9zIGRlIGxvcyBiaWVuZXMgeSByYcOtY2VzIGRlIGxhIENpdWRhZCBkZSBNw6l4aWNvLCBhIGxhIGN1YWwgc2UgbGUgZGVub21pbmEgImRmIi4gQXPDrSBtaXNtbywgc2UgY29taWVuemEgYSBoYWNlciB1biBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIGRlIGxvcyBkYXRvcyBwYXJhIGNvbm9jZXIgZWwgdGlwbyBkZSB2YXJpYWJsZXMgY29uIGVsIHF1ZSBzZSBlc3TDoSB0cmFiYWphbmRvIHkgbGEgZXN0cnVjdHVyYSBnZW5lcmFsIGRlIGxhIGJhc2UgZGUgZGF0b3MuCgpgYGB7ciB9CiNDYXJnYQojZmlsZS5jaG9vc2UoKQpkZiA8LSByZWFkLmNzdigiL1VzZXJzL2dhYnJpZWxtZWRpbmEvRG93bmxvYWRzL0RhdG9zIGJpZW5lcyB5IHJhaWNlcyAyMkNETVhGLmNzdiIpCmRpbShkZikKc3VtbWFyeShkZikKCmBgYAoKTGEgYmFzZSBkZSBkYXRvcyBjb250aWVuZSBpbmZvcm1hY2nDs24gc29icmUgNjU4IHZpdmllbmRhcyBlbiBDaXVkYWQgZGUgTcOpeGljbywgZGl2aWRpZGFzIGVuIGRvcyBjb25qdW50b3MgZGUgdmFyaWFibGVzLiBFbCBwcmltZXJvIHNlIGVuZm9jYSBlbiBhc3BlY3RvcyBnZW9ncsOhZmljb3MsIGNvbW8gbGEgdWJpY2FjacOzbiBwb3IgQWxjYWxkw61hIHkgQ29sb25pYSwgYXPDrSBjb21vIDEwIHZhcmlhYmxlcyBxdWUgbWlkZW4gZWwgbml2ZWwgZGUgbWFyZ2luYWNpw7NuIGRlIGxhIHpvbmEuIEVsIHNlZ3VuZG8gY29uanVudG8gc2UgY2VudHJhIGVuIGxhcyBjYXJhY3RlcsOtc3RpY2FzIGRlIGxhcyB2aXZpZW5kYXMsIGNvbW8gaW5zdGFsYWNpb25lcywgdGFtYcOxbyB5IGVsIHByZWNpbyAoZW4gbWlsZXMgZGUgTVhOKSBjb21vIHZhcmlhYmxlIGEgcHJlZGVjaXIuCgoKVHJhcyBlbCBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIGRlIGxvcyBkYXRvcywgc2UgZGV0ZWN0YXJvbiBhbGd1bmFzIGluY2lkZW5jaWFzIHNpZ25pZmljYXRpdmFzLiBTZSBvYnNlcnZhcm9uIGVycm9yZXMgb3J0b2dyw6FmaWNvcyBlbiBwYWxhYnJhcyBjb21vICJTaSwiICJTw60sIiBvICJzaSwiIHF1ZSBhZmVjdGFiYW4gbGEgY29oZXJlbmNpYSBkZSBsb3MgcmVnaXN0cm9zLiBBZGVtw6FzLCBzZSBpZGVudGlmaWNhcm9uIGRvcyB2YXJpYWJsZXMgcXVlIGhhYsOtYW4gc2lkbyBtYWwgaW50ZXJwcmV0YWRhcyBjb21vIG90cm8gdGlwbyBkZSB2YXJpYWJsZXMgeSBzZSBjb3JyaWdpZXJvbiBwYXJhIHF1ZSBjb2luY2lkaWVyYW4gY29uIHN1IG5hdHVyYWxlemEgcmVhbC4gVGFtYmnDqW4gc2UgZGV0ZWN0YXJvbiB2YWxvcmVzIGF0w61waWNvcyBlbiBsYSB2YXJpYWJsZSBkZSBwcmVjaW8sIGR1cGxpY2Fkb3MgeSBkYXRvcyBmYWx0YW50ZXMsIGxvcyBjdWFsZXMgc2UgcmVzb2x2aWVyb24gdXRpbGl6YW5kbyBsYSBtZWRpYW5hIHBhcmEgbm8gYWx0ZXJhciBsYSBpbnRlZ3JpZGFkIGRlIGxvcyBkYXRvcy4KCmBgYHtyIH0KCiMgTGltcGllemEgZGUgZGF0b3MKZGYkQWxiZXJjYVtkZiRBbGJlcmNhID09ICJzaSJdID0gIlNpIgpkZiRFbGV2YWRvcltkZiRFbGV2YWRvciA9PSAic2kiXSA9ICJTaSIKCgojIENvbnZlcnNpw7NuIGRlIFZhcmlhYmxlcyBDYXRlZ8OzcmljYXMgYSBGYWN0b3JlcwpkZiRHaW1uYXNpbyA8LSBhcy5mYWN0b3IoZGYkR2ltbmFzaW8pCmRmJENvY2luYV9lcXVpcCA8LSBhcy5mYWN0b3IoZGYkQ29jaW5hX2VxdWlwKQpkZiRHaW1uYXNpbyA8LSBhcy5mYWN0b3IoZGYkR2ltbmFzaW8pCmRmJEFtdWVibGFkbyA8LSBhcy5mYWN0b3IoZGYkQW11ZWJsYWRvKQpkZiRBbGJlcmNhIDwtIGFzLmZhY3RvcihkZiRBbGJlcmNhKQpkZiRUZXJyYXphIDwtIGFzLmZhY3RvcihkZiRUZXJyYXphKQpkZiRFbGV2YWRvciA8LSBhcy5mYWN0b3IoZGYkRWxldmFkb3IpCmRmJEFsY2FsZGlhIDwtIGFzLmZhY3RvcihkZiRBbGNhbGRpYSkKZGYkQ29sb25pYSA8LSBhcy5mYWN0b3IoZGYkQ29sb25pYSkKCiMgRWxpbWluYWNpw7NuIGRlIHJlZ2lzdHJvcyBkdXBsaWNhZG9zCmRmIDwtIHVuaXF1ZShkZikKZGltKGRmKQoKIyBDb252ZXJzacOzbiBWYXJpYWJsZSBFc3RhY2lvbmFtaWVudG8gTnVtw6lyaWNhIGUgSW1wdXRhY2nDs24gZGUgTWVkaWFuYSBhIE51bG9zCmRmJEVzdGFjaW9uYW1pZW50byA8LSBhcy5udW1lcmljKGRmJEVzdGFjaW9uYW1pZW50bykKbWVkaWFuYV9lc3RhY2lvbmFtaWVudG8gPC0gbWVkaWFuKGRmJEVzdGFjaW9uYW1pZW50bywgbmEucm0gPSBUUlVFKQpkZiRFc3RhY2lvbmFtaWVudG9baXMubmEoZGYkRXN0YWNpb25hbWllbnRvKV0gPC0gbWVkaWFuYV9lc3RhY2lvbmFtaWVudG8KCiNFbGltaW5hciB2YWxvcmVzIGV4dHJlbW9zIGFkaWNpb25hbGVzIGEgMCB1c2FuZG8gZWwgcmFuZ28gaW50ZXJjdWFydGlsCnExIDwtIHF1YW50aWxlKGRmJFByZWNpbywgMC4yNSwgbmEucm09VFJVRSkKcTMgPC0gcXVhbnRpbGUoZGYkUHJlY2lvLCAwLjc1LCBuYS5ybT1UUlVFKQpyYW5nb2ludHEgPC0gcTMgLSBxMQoKbGltaXRlX2luZmVyaW9yIDwtIHExIC0gMC4xNyAqIHJhbmdvaW50cQpsaW1pdGVfc3VwZXJpb3IgPC0gcTMgKyAxLjUgKiByYW5nb2ludHEKCmRmIDwtIHN1YnNldChkZiwgUHJlY2lvID49IGxpbWl0ZV9pbmZlcmlvciAmIFByZWNpbyA8PSBsaW1pdGVfc3VwZXJpb3IpCgoKCiNSZXZpc2nDs24gZGUgTWVkaWEgZGUgVmFyaWFibGVzIHJlbGFjaW9uYWRhcyBhIGxhIE1hcmdpbmFjacOzbiBwb3IgQWxjYWxkw61hCkV4cGxvcmFjacOzbiA8LSBkZiAlPiUKICBzZWxlY3QoQWxjYWxkaWEsWDEsIFgyLCBYMywgWDQsIFg1LCBYNiwgWDcsIFg4LCBYOSwgWDEwKSU+JQogIGdyb3VwX2J5KEFsY2FsZGlhKSAlPiUKICBzdW1tYXJpc2UoWDFtZWFuID0gbWVhbihYMSksIFgybWVhbiA9IG1lYW4oWDIpLCBYM21lYW4gPSBtZWFuKFgzKSwgWDRtZWFuID0gbWVhbihYNCksIFg1bWVhbiA9IG1lYW4oWDUpLCBYNm1lYW4gPSBtZWFuKFg2KSwgWDdtZWFuID0gbWVhbihYNyksIFg4bWVhbiA9IG1lYW4oWDgpLCBYOW1lYW4gPSBtZWFuKFg5KSwgWDEwbWVhbiA9IG1lYW4oWDEwKSkKRXhwbG9yYWNpw7NuCgpgYGAKCkRlbnRybyBkZWwgYW7DoWxpc2lzIGV4cGxvcmF0b3JpbyBkZSBsb3MgZGF0b3MsIGVzIGltcG9ydGFudGUgYW5hbGl6YXIgbGEgY29ycmVsYWNpw7NuIGxpbmVhbCBlbnRyZSBsYXMgdmFyaWFibGVzIGNvbiBlbCBmaW4gZGUgaWRlbnRpZmljYXIgcmVsYWNpb25lcyBlbnRyZSBsYXMgZGlzdGludGFzIHZhcmlhYmxlcyB5IG51ZXN0cmEgdmFyaWFibGUgZGUgaW50ZXLDqXMsIG1lam9yYXIgbGEgaW50ZXJwcmV0YWNpw7NuIGRlIHJlc3VsdGFkb3MgeSBkZXRlY3RhciBjaWVydG9zIHBhdHJvbmVzIG8gcmVsYWNpb25lcyBpbmVzcGVyYWRhcyBhIGNvbnNpZGVyYXIgcGFyYSBsYSBtZWpvciBjb25zdHJ1Y2Npw7NuIGRlbCBtb2RlbG8gcHJlZGljdGl2by4KCmBgYHtyIH0KbnVtZXJpY19jb2xzIDwtIGRmICU+JQogc2VsZWN0X2lmKGlzLm51bWVyaWMpCgpjb3JycGxvdChjb3IobnVtZXJpY19jb2xzKSwgdHlwZSA9ICJ1cHBlciIsIG9yZGVyPSJoY2x1c3QiLCBhZGRDb2VmLmNvbCA9CiJibGFjayIsIG1ldGhvZCA9ICJjaXJjbGUiLCBudW1iZXIuY2V4PTAuNSkKCmBgYAoKCiMgQWxnb3JpdG1vIEstTWVhbnMgKFZhcmlhYmxlcyBNYXJnaW5hY2nDs24gWDEtWDEwKQoKU2UgaW1wbGVtZW50YXLDoSBlbCBhbGdvcml0bW8gSy1NZWFucywgdW4gbcOpdG9kbyBkZSBjbHVzdGVyaXphY2nDs24gcmVjb25vY2lkbyBwb3Igc3UgZWZpY2FjaWEgeSBzaW1wbGljaWRhZC4gRXN0ZSBhbGdvcml0bW8gZXMgaWRlYWwgcGFyYSBzZWdtZW50YXIgZWwgY29uanVudG8gZGUgZGF0b3MgZW4gZ3J1cG9zIG8gJ2NsdXN0ZXJzJyBiYXNhZG9zIGVuIHNpbWlsaXR1ZGVzIGludGVybmFzLCBsbyBxdWUgZmFjaWxpdGEgbGEgaWRlbnRpZmljYWNpw7NuIGRlIHBhdHJvbmVzIHkgdGVuZGVuY2lhcyBzdWJ5YWNlbnRlcy4gRW4gcGFydGljdWxhciwgc2UgYXBsaWNhcsOhIEstTWVhbnMgcGFyYSBhZ3J1cGFyIGxhcyBwcm9waWVkYWRlcyBpbm1vYmlsaWFyaWFzIGVuIGxhIENETVggc2Vnw7puIHZhcmlhYmxlcyBkZSBtYXJnaW5hY2nDs24gKFgxLVgxMCkuCgpQYXJhIGxvZ3JhciB1bmEgY2x1c3Rlcml6YWNpw7NuIGVmZWN0aXZhIGRlIGxvcyBkYXRvcyBkZSB2aXZpZW5kYXMsIGVzIGVzZW5jaWFsIG9taXRpciB2YXJpYWJsZXMgbm8gcmVsYWNpb25hZGFzIGNvbiBsYSBtYXJnaW5hY2nDs24geSBsYSB2YXJpYWJsZSBkZSBwcmVjaW8sIHF1ZSBwb2Ryw61hIHNlc2dhciBsb3MgcmVzdWx0YWRvcy4gQWRlbcOhcywgc2UgZGViZSBub3JtYWxpemFyIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcyBwYXJhIHVuYSBjb21wYXJhY2nDs24gZXF1aXRhdGl2YSB1dGlsaXphbmRvIGxhIGZ1bmNpw7NuICJzY2FsZXMiLiAKCgpgYGB7ciB9CiPDmm5pY2FtZW50ZSBzZWxlY2Npb25hciB2YXJpYWJsZXMgbnVtw6lyaWNhcyBwYXJhIGVsIGFsZ29yaXRtbwpudW1lcmljX2NvbHMgPC0gZGYgJT4lCiBzZWxlY3RfaWYoaXMubnVtZXJpYykKCiMgRWxpbWluYXIgdmFyaWFibGUgZGVwZW5kaWVudGUgIlByZWNpbyIgeSB2YXJpYWJsZXMgbm8gZGUgbWFyZ2luYWNpw7NuCm51bWVyaWNfY29scyRDbHVzdGVyIDwtIE5VTEwKbnVtZXJpY19jb2xzJFByZWNpbyA8LSBOVUxMCm51bWVyaWNfY29scyRFc3RhY2lvbmFtaWVudG8gPC0gTlVMTApudW1lcmljX2NvbHMkUmVjYW1hcmFzIDwtIE5VTEwKbnVtZXJpY19jb2xzJEJhbm9zIDwtIE5VTEwKbnVtZXJpY19jb2xzJG0yX2NvbnN0cnVpZG8gPC0gTlVMTAoKIyBFc2NhbGFyIGxhcyBjb2x1bW5hcyBudW3DqXJpY2FzIHBhcmEgZ2FyYW50aXphciBxdWUgbGFzIHZhcmlhYmxlcyB0ZW5nYW4gZWwgbWlzbW8gcGVzbwoKZGZfc2NhbGVkIDwtIHNjYWxlKG51bWVyaWNfY29scykKCiMgU2VtaWxsYSBkZSBSZXByb2R1Y3RpYmlsaWRhZCwgcGFyYSBnYXJhbnRpemFyIHF1ZSBsb3MgcmVzdWx0YWRvcyBkZSB1biBwcm9jZXNvIGFsZWF0b3JpbyBzZWFuIHJlcHJvZHVjaWJsZXMuCgpzZXQuc2VlZCgxMjMpCgpgYGAKCgojIyMjIyBFbGVjY2nDs24gZGUgbsO6bWVybyDDs3B0aW1vIGRlIGNsw7pzdGVyCgpQYXJhIGVsZWdpciBsYSBjYW50aWRhZCDDs3B0aW1hIGRlIGNsdXN0ZXJzIHNlIHV0aWxpemEgZWwgbcOpdG9kbyBkZWwgQ09ETy4KCmBgYHtyIH0KCndzcyA8LSBudW1lcmljKDE1KQpmb3IgKGkgaW4gMjoxNSkgewogIHdzc1tpXSA8LSBzdW0oa21lYW5zKG51bWVyaWNfY29scywgY2VudGVycyA9IGksIG5zdGFydCA9IDI1KSR3aXRoaW5zcykKfQpwbG90KDE6MTUsIHdzcywgdHlwZSA9ICJiIiwgeGxhYiA9ICJOw7ptZXJvIGRlIENsdXN0ZXJzIiwgeWxhYiA9ICJTdW1hIGRlIEN1YWRyYWRvcyBJbnRlcm5vcyIpCgpgYGAKUGFyYSBlbGVnaXIgZWwgbsO6bWVybyDDs3B0aW1vIGRlIGNsw7pzdGVycyBzZSB1dGlsaXrDsyBlbCBtw6l0b2RvIGRlbCBDb2RvLgoKQ29tbyBzZSBvYnNlcnZhIGVuIGxhIGdyw6FmaWNhIGdlbmVyYWRhIHBvciBlbCBjw7NkaWdvLCBlbCBwdW50byBkZSBpbmZsZXhpw7NuLCBvICJjb2RvIiwgc2UgZW5jdWVudHJhIGVuIGVsIG7Dum1lcm8gZGUgY2x1c3RlciA0LiBFc3RvIHNlIGZ1bmRhbWVudGEgZW4gZWwgbcOpdG9kbyBkZWwgY29kbywgcXVlIHN1Z2llcmUgZWxlZ2lyIGVsIG7Dum1lcm8gZGUgY2x1c3RlcnMgZW4gZWwgcHVudG8gZG9uZGUgbGEgZGlzbWludWNpw7NuIGRlIGxhIHN1bWEgZGUgY3VhZHJhZG9zIGludGVybm9zIChXU1MpIGNvbWllbnphIGEgcmFsZW50aXphcnNlIHNpZ25pZmljYXRpdmFtZW50ZS4gRW4gb3RyYXMgcGFsYWJyYXMsIGHDsWFkaXIgbcOhcyBjbHVzdGVycyBtw6FzIGFsbMOhIGRlIGVzdGUgcHVudG8gbm8gbWVqb3JhIHN1c3RhbmNpYWxtZW50ZSBsYSBjb21wYWN0YWNpw7NuIGRlIGxvcyBkYXRvcyBkZW50cm8gZGUgbG9zIGNsdXN0ZXJzLiBQb3IgbG8gdGFudG8sIDQgZXMgZWwgbsO6bWVybyDDs3B0aW1vIGRlIGNsdXN0ZXJzIHBhcmEgZXN0ZSBhbsOhbGlzaXMuCgoKYGBge3IgfQoKIyBLbHVzdGVycyA9IDQKa21lYW5zX3Jlc3VsdCA8LSBrbWVhbnMoZGZfc2NhbGVkLCBjZW50ZXJzID0gNCkKa21lYW5zX3Jlc3VsdCRjZW50ZXJzCmNlbnRyb3MgPC0ga21lYW5zX3Jlc3VsdCRjZW50ZXJzCgojIFRyYW5zcG9uZXIgbGEgbWF0cml6IHBhcmEgcXVlIGxhcyBmaWxhcyByZXByZXNlbnRlbiB2YXJpYWJsZXMgeSBsYXMgY29sdW1uYXMgcmVwcmVzZW50ZW4gY2VudHJvcyBkZSBjbMO6c3RlcmVzCmNlbnRyb3NfdHJhbnNwdWVzdG9zIDwtIHQoY2VudHJvcykKY2VudHJvc190cmFuc3B1ZXN0b3MKCgojIEJhcnBsb3QgcGFyYSBjYWRhIGNlbnRybyBkZSBjbMO6c3Rlcgpjb2xvcmVzIDwtIGMoInJlZCIsICJibHVlIiwgImdyZWVuIikKYmFycGxvdChjZW50cm9zX3RyYW5zcHVlc3RvcywgYmVzaWRlID0gVFJVRSwgY29sID0gY29sb3JlcywgCiAgICAgICAgbGVnZW5kLnRleHQgPSByb3duYW1lcyhjZW50cm9zKSwgeWxpbSA9IGMobWluKGNlbnRyb3NfdHJhbnNwdWVzdG9zKSwgbWF4KGNlbnRyb3NfdHJhbnNwdWVzdG9zKSksCiAgICAgICAgbWFpbiA9ICJDZW50cm9zIGRlIENsw7pzdGVyZXMgZGUgSy1tZWFucyIsIAogICAgICAgIHlsYWIgPSAiVmFsb3IiLCAKICAgICAgICB4bGFiID0gIlZhcmlhYmxlcyIpCmxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQgPSBwYXN0ZSgiQ2zDunN0ZXIiLCAxOm5yb3coY2VudHJvcykpLCBmaWxsID0gY29sb3JlcykKCiMgQcOxYWRpciBsYSBjb2x1bW5hIGRlIGNsdXN0ZXJzIGFsIERhdGFGcmFtZSBvcmlnaW5hbApkZiRDbHVzdGVyIDwtIGttZWFuc19yZXN1bHQkY2x1c3RlcgpoZWFkKGRmKQoKIyBDb250ZW8gZGUgUmVnaXN0cm9zIGVuIENhZGEgQ2x1c3RlcgpkZnQgPC0gZGYKZGZ0JENsdXN0ZXI8LSBhcy5mYWN0b3IoZGYkQ2x1c3RlcikKc3VtbWFyeShkZnQkQ2x1c3RlcikKCmBgYAoKVW5hIHZleiBhbmFsaXphZG9zIGxvcyByZXN1bHRhZG9zIGRlIGxvcyBjbHVzdGVycywgc2UgcHVlZGVuIGV0aXF1ZXRhciBkZSBsYSBzaWd1aWVudGUgbWFuZXJhOiAKCkNsw7pzdGVyIDEsIGRlbm9taW5hZG8gIlZpdmllbmRhcyBNZWRpYSBSZXNpZGVuY2lhbCwiIHNlIGNhcmFjdGVyaXphIHBvciBleGhpYmlyIGVuIHN1IG1heW9yw61hIHZhbG9yZXMgYmFqb3MgZW4gbGFzIG3DqXRyaWNhcyBkZSBtYXJnaW5hY2nDs24uIEVzdG8gc3VnaWVyZSBxdWUgZXN0ZSBncnVwbyBwdWVkZSBjb21wcmVuZGVyIHByb3BpZWRhZGVzIHJlc2lkZW5jaWFsZXMgY29uIGF0cmlidXRvcyBtb2RlcmFkYW1lbnRlIG1hcmdpbmFkb3MsIGxvIHF1ZSBpbXBsaWNhIHVuIG5pdmVsIGludGVybWVkaW8gZGUgZGVzYXJyb2xsbyB5IHNlcnZpY2lvcyBlbiBzdSB1YmljYWNpw7NuLgoKUG9yIG90cm8gbGFkbywgZWwgQ2zDunN0ZXIgMiwgbGxhbWFkbyAiVml2aWVuZGFzIFByZW1pdW0sIiBzZSBkaXN0aW5ndWUgcG9yIG1vc3RyYXIgbG9zIHZhbG9yZXMgbcOhcyBiYWpvcyBlbiB0b2RhcyBsYXMgdmFyaWFibGVzLCBsbyBxdWUgYXB1bnRhIGEgcHJvcGllZGFkZXMgZXhjbHVzaXZhcyB5IGRlIGFsdG8gdmFsb3IuIEVzdGFzIHZpdmllbmRhcyBwcm9iYWJsZW1lbnRlIHNlIGVuY3VlbnRyYW4gZW4gw6FyZWFzIGNvbiB1biBiYWpvIGdyYWRvIGRlIG1hcmdpbmFjacOzbiBzb2NpYWwgeSBlY29uw7NtaWNhLCBsbyBxdWUgbGFzIGNvbnZpZXJ0ZSBlbiBwcm9waWVkYWRlcyBkZSBsdWpvLgoKRW4gY29udHJhc3RlLCBlbCBDbMO6c3RlciAzLCBjb25vY2lkbyBjb21vICJWaXZpZW5kYXMgRWNvbsOzbWljYXMsIiBleGhpYmUgdmFsb3JlcyBzaWduaWZpY2F0aXZhbWVudGUgYWx0b3MgZW4gdG9kYXMgbGFzIG3DqXRyaWNhcyBkZSBtYXJnaW5hY2nDs24uIEVzdG8gc3VnaWVyZSBxdWUgZXN0YXMgcHJvcGllZGFkZXMgc2UgZW5jdWVudHJhbiBlbiDDoXJlYXMgY29uIGFsdG9zIG5pdmVsZXMgZGUgbWFyZ2luYWNpw7NuLCBjYXJhY3Rlcml6YWRhcyBwb3IgbGltaXRhY2lvbmVzIHNvY2lvZWNvbsOzbWljYXMuIEVzdGFzIHZpdmllbmRhcyBzb24gbcOhcyBhc2VxdWlibGVzIHkgZGlyaWdpZGFzIGEgcGVyc29uYXMgY29uIGluZ3Jlc29zIG3DoXMgYmFqb3MuCgpGaW5hbG1lbnRlLCBlbCBDbMO6c3RlciA0LCBkZW5vbWluYWRvICJWaXZpZW5kYXMgUG9wdWxhciwiIHByZXNlbnRhIHVuYSBtZXpjbGEgZGUgdmFsb3JlcyBhbHRvcyB5IGJham9zIGVuIGRpdmVyc2FzIG3DqXRyaWNhcy4gRXN0ZSBncnVwbyBwb2Ryw61hIGNvbnRlbmVyIHVuYSB2YXJpZWRhZCBkZSBwcm9waWVkYWRlcyBwb3B1bGFyZXMgZW4gdWJpY2FjaW9uZXMgZGl2ZXJzYXMsIHF1ZSBhYmFyY2FuIGRlc2RlIMOhcmVhcyBtYXJnaW5hZGFzIGhhc3RhIHpvbmFzIG3DoXMgZGVzYXJyb2xsYWRhcywgbG8gcXVlIGluZGljYSB1bmEgZGl2ZXJzaWRhZCBlbiBzdSBwZXJmaWwgc29jaW9lY29uw7NtaWNvIHkgZGUgc2VydmljaW9zLgoKCiMgU2VwYXJhY2nDs24gRGF0YUZyYW1lIGVuIENsdXN0ZXJzCmBgYHtyIH0KIyBDb3BpYXIgZWwgZGF0YXNldCBwYXJhIHNlZ21lbnRhciBwb3IgY2xhc2VzCmRmYyA8LSBkZgpkZmMkQ2x1c3RlciA8LSBhcy5jaGFyYWN0ZXIoZGZjJENsdXN0ZXIpCnN1bW1hcnkoZGZjJENsdXN0ZXIpCgojIERhciBub21iZXMgYSBsb3MgY2x1c3RlcnMgZGUgYWN1ZXJkbyBhbCBhbsOhbGlzaXMgZGUgc3VzIGNhcmFjdGVyw61zdGljYXMKZGZjJENsdXN0ZXJbZGZjJENsdXN0ZXIgPT0gMV0gPSAiVml2aWVuZGFzIE1lZGlhIFJlc2lkZW5jaWFsIgpkZmMkQ2x1c3RlcltkZmMkQ2x1c3RlciA9PSAyXSA9ICJWaXZpZW5kYXMgUHJlbWl1bSIKZGZjJENsdXN0ZXJbZGZjJENsdXN0ZXIgPT0gM10gPSAiVml2aWVuZGFzIEVjb27Ds21pY2FzIgpkZmMkQ2x1c3RlcltkZmMkQ2x1c3RlciA9PSA0XSA9ICJWaXZpZW5kYXMgUG9wdWxhciIKCgojIENyZWFyIHVuYSB0YWJsYSBkZSBjb250aW5nZW5jaWEgcGFyYSBsYSB2YXJpYWJsZSAnRU1QUkVTQScgcG9yIGNsdXN0ZXIKY29udGluZ2VuY3kgPC0gdGFibGUoZGZjJENsdXN0ZXIsIGRmYyRBbGNhbGRpYSkKY29udGluZ2VuY3kgPC0gcHJvcC50YWJsZShjb250aW5nZW5jeSwgbWFyZ2luID0gMSkgKiAxMDAKcHJpbnQoY29udGluZ2VuY3kpCgoKIyBHZW5lcmFjacOzbiBkZSBTdWJjb25qdW50b3MKVk1lZGlhUmVzaWRlbmNpYWwgPC0gc3Vic2V0KGRmYywgQ2x1c3RlciA9PSAiVml2aWVuZGFzIE1lZGlhIFJlc2lkZW5jaWFsIikKClZQb3B1bGFyIDwtIHN1YnNldChkZmMsIENsdXN0ZXIgPT0gIlZpdmllbmRhcyBQb3B1bGFyIikKClZQcmVtaXVtIDwtIHN1YnNldChkZmMsIENsdXN0ZXIgPT0gIlZpdmllbmRhcyBQcmVtaXVtIikKClZFY29uIDwtIHN1YnNldChkZmMsIENsdXN0ZXIgPT0gIlZpdmllbmRhcyBFY29uw7NtaWNhcyIpCgoKYGBgCgpMYSB0YWJsYSBkZSBjb250aW5nZW5jaWEgc3VnaWVyZSBxdWUgY2llcnRhcyBhbGNhbGTDrWFzIGVzdMOhbiBhbHRhbWVudGUgZXNwZWNpYWxpemFkYXMgZW4gdW5hIGNhdGVnb3LDrWEgZXNwZWPDrWZpY2EgZGUgdml2aWVuZGEuIEVzdG8gcG9kcsOtYSBkZWJlcnNlIGEgdmFyaW9zIGZhY3RvcmVzIHNvY2lvZWNvbsOzbWljb3MgeSBkZSBkZXNhcnJvbGxvIHVyYmFubyBxdWUgaGFuIGxsZXZhZG8gYSB1bmEgc2VncmVnYWNpw7NuIG1hcmNhZGEgZGVsIHRpcG8gZGUgdml2aWVuZGEgZGlzcG9uaWJsZSBlbiBjYWRhIGFsY2FsZMOtYS4gUmVmbGVqYW5kbyB2YWxvcmVzIGRvbmRlIGxvcyBjbHVzdGVycyBzb24gbcOhcyByZXByZXNlbnRhdGl2b3MgeSBzaWduaWZpY2F0aXZvcy4KCsOBbHZhcm8gT2JyZWfDs246IFVuYSBwcm9wb3JjacOzbiBzaWduaWZpY2F0aXZhIGNvcnJlc3BvbmRlIGEgVml2aWVuZGFzIE1lZGlhIFJlc2lkZW5jaWFsICg0My4xNyUpLCBpbmRpY2FuZG8gdW5hIHRlbmRlbmNpYSBoYWNpYSB2aXZpZW5kYXMgY29uIGNhcmFjdGVyw61zdGljYXMgbWVkaWFuYXMgZW4gdMOpcm1pbm9zIGRlIG1hcmdpbmFjacOzbi4KCkJlbml0byBKdcOhcmV6OiBTZSBkZXN0YWNhIHBvciB0ZW5lciB1biBwb3JjZW50YWplIGNvbnNpZGVyYWJsZSBkZSBWaXZpZW5kYXMgUHJlbWl1bSAoMzEuNzElKSwgbG8gY3VhbCBzdWdpZXJlIHF1ZSBlc3RhIGFsY2FsZMOtYSBhbGJlcmdhIHVuYSBjYW50aWRhZCBzaWduaWZpY2F0aXZhIGRlIHByb3BpZWRhZGVzIGRlIGFsdG8gdmFsb3IuCgpJenRhcGFsYXBhOiBQcmVkb21pbmFuIGxhcyBWaXZpZW5kYXMgRWNvbsOzbWljYXMgKDY0LjM0JSksIGxvIHF1ZSByZWZsZWphIHVuYSBtYXlvciBwcmVzZW5jaWEgZGUgdml2aWVuZGFzIGFzZXF1aWJsZXMgbyBjb24gbWF5b3IgbWFyZ2luYWNpw7NuIGVuIGVzdGEgem9uYS4KCkd1c3Rhdm8gQS4gTWFkZXJvOiBQcmVzZW50YSB1biBwb3JjZW50YWplIG5vdGFibGUgZGUgVml2aWVuZGFzIFBvcHVsYXIgKDQxLjM2JSksIGxvIHF1ZSBwdWVkZSBzZcOxYWxhciB1bmEgaW5jbGluYWNpw7NuIGhhY2lhIHZpdmllbmRhcyBtw6FzIGFjY2VzaWJsZXMgbyBwb3B1bGFyZXMuCgpDb3lvYWPDoW4geSBNaWd1ZWwgSGlkYWxnbzogVGllbmVuIHVuYSBjb25jZW50cmFjacOzbiBkZSBWaXZpZW5kYXMgUHJlbWl1bSAoNTQuODglIHkgMTMuNDElIHJlc3BlY3RpdmFtZW50ZSksIHN1Z2lyaWVuZG8gdW4gbWVyY2FkbyBpbm1vYmlsaWFyaW8gY29uIHByb3BpZWRhZGVzIGRlIGdhbWEgYWx0YS4KCgojIE1vZGVsb3MgQ0FSVCBwYXJhIGNhZGEgdGlwbyBkZSB2aXZpZW5kYXMKClBhcmEgcHJvZnVuZGl6YXIgZW4gbGEgY29tcHJlbnNpw7NuIGRlbCBtZXJjYWRvIGlubW9iaWxpYXJpbyBkZSBsYSBDRE1YIHkgb2ZyZWNlciByZWNvbWVuZGFjaW9uZXMgcGVyc29uYWxpemFkYXMgcGFyYSBjYWRhIHNlZ21lbnRvLCBzZSBkZXNhcnJvbGxhcm9uIG1vZGVsb3MgcHJlZGljdGl2b3MgZXNwZWPDrWZpY29zIHBhcmEgY2FkYSB0aXBvIGRlIHZpdmllbmRhIGlkZW50aWZpY2FkbyBtZWRpYW50ZSBjbHVzdGVyaW5nLiBBbCBjb25zdHJ1aXIgdW4gbW9kZWxvIHNlcGFyYWRvIHBhcmEgIlZpdmllbmRhcyBNZWRpYSBSZXNpZGVuY2lhbCIsICJWaXZpZW5kYXMgUHJlbWl1bSIsICJWaXZpZW5kYXMgRWNvbsOzbWljYXMiIHkgIlZpdmllbmRhcyBQb3B1bGFyIiwgc2UgYnVzY2EgY2FwdHVyYXIgbGFzIGRpbsOhbWljYXMgw7puaWNhcyB5IGxvcyBmYWN0b3JlcyBpbmZsdXllbnRlcyBxdWUgZGV0ZXJtaW5hbiBlbCB2YWxvciBkZSBsYXMgcHJvcGllZGFkZXMgZW4gY2FkYSBjbHVzdGVyLiBFc3RhIG1ldG9kb2xvZ8OtYSBwZXJtaXRlIHVuYSBhcHJveGltYWNpw7NuIG3DoXMgYWp1c3RhZGEgYSBsYSByZWFsaWRhZCBkZSBjYWRhIHNlZ21lbnRvIGRlbCBtZXJjYWRvLCBwcm9wb3JjaW9uYW5kbyBhc8OtIGluc2lnaHRzIG3DoXMgcHJlY2lzb3MgcGFyYSBlc3RyYXRlZ2lhcyBkZSBpbnZlcnNpw7NuLCBkZXNhcnJvbGxvIHVyYmFuw61zdGljbyB5IHBvbMOtdGljYXMgZGUgdml2aWVuZGEuCgogRXN0ZSBwcm9jZXNvIGludm9sdWNyw7MgcGFzb3MgY2xhdmUgcGFyYSBjYWRhIMOhcmJvbCwgaW5jbHV5ZW5kbyBsYSBwYXJ0aWNpw7NuIGRlIGRhdG9zIGVuIGNvbmp1bnRvcyBkZSBlbnRyZW5hbWllbnRvLCB2YWxpZGFjacOzbiB5IHBydWViYSAoNTAsMjUsMjUpLCBsYSBjb25zdHJ1Y2Npw7NuIGRlbCDDoXJib2wgZGUgZGVjaXNpw7NuIHV0aWxpemFuZG8gbGFzIGNhcmFjdGVyw61zdGljYXMgZGUgaW5mcmFlc3RydWN0dXJhIGNvbW8gcHJlZGljdG9yZXMsIGxhIHZhbGlkYWNpw7NuIGNydXphZGEgcGFyYSBhanVzdGFyIGxhIGNvbXBsZWppZGFkIGRlbCDDoXJib2wsIGxhIGludGVycHJldGFjacOzbiBkZSB2YXJpYWJsZXMgY3LDrXRpY2FzIGVuIGVsIHByb2Nlc28gZGUgdG9tYSBkZSBkZWNpc2lvbmVzLCB5IGZpbmFsbWVudGUsIGxhIGV2YWx1YWNpw7NuIGRlbCBtb2RlbG8gbWVkaWFudGUgbGEgcHJlZGljY2nDs24geSBjb21wYXJhY2nDs24gZGUgbG9zIHByZWNpb3MgcmVhbGVzIGNvbiBsYXMgcHJlZGljY2lvbmVzLgoKIyMjIyMgVml2aWVuZGFzIGVjb27Ds21pY2FzCgojIyBTZXRzIGRlIEVudHJlbmFtaWVudG8sIFZhbGlkYWNpw7NuIHkgUHJ1ZWJhICAtIFZpdmllbmRhcyBFY29uw7NtaWNhcwpgYGB7ciB9CnN1bW1hcnkoVkVjb24pClZFY29uJENsdXN0ZXIgPC0gTlVMTAoKIyBFc3RhYmxlY2VyIGxhIHNlbWlsbGEgcGFyYSByZXByb2R1Y2liaWxpZGFkIApzZXQuc2VlZCgxMjMpCgojIFBhc28gMTogRGl2aWRpciBlbCBjb25qdW50byBkZSBkYXRvcyBlbiBlbnRyZW5hbWllbnRvICg1MCUpIHkgdGVtcG9yYWwgKDUwJSkKdHJhaW5JbmRleDEgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihWRWNvbiRQcmVjaW8sIHAgPSAwLjUsIGxpc3QgPSBGQUxTRSwgdGltZXMgPSAxKQp0cmFpbiA8LSBWRWNvblt0cmFpbkluZGV4MSwgXQp0ZW1wIDwtIFZFY29uWy10cmFpbkluZGV4MSwgXQoKIyBQYXNvIDI6IERpdmlkaXIgZWwgY29uanVudG8gdGVtcG9yYWwgZW4gdmFsaWRhY2nDs24gKDUwJSBkZSB0ZW1wKSB5IHBydWViYSAoNTAlIGRlIHRlbXApCnRyYWluSW5kZXgyIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24odGVtcCRQcmVjaW8sIHAgPSAwLjUsIGxpc3QgPSBGQUxTRSwgdGltZXMgPSAxKQp2YWxpZGF0aW9uIDwtIHRlbXBbdHJhaW5JbmRleDIsIF0KdGVzdEVjb24gPC0gdGVtcFstdHJhaW5JbmRleDIsIF0KYGBgCgojIyDDgXJib2wgZGUgRGVjaXNpw7NuIC0gVml2aWVuZGFzIEVjb27Ds21pY2FzCmBgYHtyIH0KIyBDb25zdHJ1aXIgZWwgw6FyYm9sIGRlIGRlY2lzacOzbgp0cmVlIDwtIHJwYXJ0KFByZWNpbyB+IG0yX2NvbnN0cnVpZG8gKyBCYW5vcyArIFJlY2FtYXJhcyArIEVzdGFjaW9uYW1pZW50byArIEdpbW5hc2lvICsgQ29jaW5hX2VxdWlwICsgR2ltbmFzaW8gKyBBbXVlYmxhZG8gKyBBbGJlcmNhICsgVGVycmF6YSArIEVsZXZhZG9yLCBkYXRhID0gdHJhaW4sIG1ldGhvZCA9ICJhbm92YSIsIGNvbnRyb2wgPSBycGFydC5jb250cm9sKGNwID0gMCkpCnJwYXJ0LnBsb3QodHJlZSkKCgojIFZpc3VhbGl6YXIgbGEgY3VydmEgZGUgY29tcGxlamlkYWQgZGUgY29zdG8KcGxvdGNwKHRyZWUpCgpgYGAKClNlIGNvbnN0cnV5w7MgdW4gw6FyYm9sIGRlIGRlY2lzacOzbiBwYXJhIHByZWRlY2lyIGVsIHByZWNpbyBkZSBsYXMgdml2aWVuZGFzIGVuIGxhIENETVguIFNlIGVsaWdpZXJvbiB2YXJpYWJsZXMgY29tbyBtZXRyb3MgY3VhZHJhZG9zIGNvbnN0cnVpZG9zLCBuw7ptZXJvIGRlIGJhw7FvcywgcmVjw6FtYXJhcywgZXN0YWNpb25hbWllbnRvLCB5IG90cmFzIGNhcmFjdGVyw61zdGljYXMgcmVsZXZhbnRlcywgY29uIHVuIGVuZm9xdWUgcGFydGljdWxhciBlbiBsb3MgbWV0cm9zIGN1YWRyYWRvcyB5IGxhIGNhbnRpZGFkIGRlIGJhw7FvcywgZGFkbyBxdWUgZXN0YXMgdmFyaWFibGVzIHN1ZWxlbiB0ZW5lciB1bmEgaW5mbHVlbmNpYSBzaWduaWZpY2F0aXZhIGVuIGVsIHZhbG9yIGRlIHVuYSBwcm9waWVkYWQuCgpFbCB1c28gZGUgbGEgdMOpY25pY2EgQU5PVkEgZW4gZWwgbW9kZWxvIHN1Z2llcmUgdW4gaW50ZW50byBkZSBjYXB0dXJhciBsYXMgdmFyaWFjaW9uZXMgZGUgcHJlY2lvcyBhIHRyYXbDqXMgZGUgZXN0YXMgY2FyYWN0ZXLDrXN0aWNhcy4gUG9zdGVyaW9ybWVudGUsIGxhIGN1cnZhIGRlIGNvbXBsZWppZGFkIHNlIHV0aWxpesOzIHBhcmEgZXZhbHVhciBlbCBlcXVpbGlicmlvIGVudHJlIGxhIHByZWNpc2nDs24gZGVsIG1vZGVsbyB5IHN1IGNvbXBsZWppZGFkLiBFc3RhIGN1cnZhIGF5dWRhIGEgaWRlbnRpZmljYXIgZWwgdGFtYcOxbyDDs3B0aW1vIGRlbCDDoXJib2wsIG1pbmltaXphbmRvIGVsIHJpZXNnbyBkZSBzb2JyZWFqdXN0ZSB5IGFzZWd1cmFuZG8gcXVlIGVsIG1vZGVsbyBzZWEgbG8gc3VmaWNpZW50ZW1lbnRlIGNvbXBsZWpvIHBhcmEgY2FwdHVyYXIgbGFzIHJlbGFjaW9uZXMgaW1wb3J0YW50ZXMgZW4gbG9zIGRhdG9zLgoKIyMgVmFsaWRhY2nDs24gQ3J1emFkYSAtIFZpdmllbmRhcyBFY29uw7NtaWNhcwpgYGB7ciB9CnNldC5zZWVkKDEyMykKcHJlcHJvYyA8LSBwcmVQcm9jZXNzKHZhbGlkYXRpb24sIG1ldGhvZCA9ICJtZWRpYW5JbXB1dGUiKQp2YWxpZGF0aW9uX2NsZWFuIDwtIHByZWRpY3QocHJlcHJvYywgVkVjb24pCgoKIyBEZWZpbmlyIGVsIG3DqXRvZG8gZGUgY29udHJvbCBkZSBlbnRyZW5hbWllbnRvIHBhcmEgbGEgdmFsaWRhY2nDs24gY3J1emFkYSBrLWZvbGQgMTAgcGxpZWd1ZXMgbyBzdWJjb25qdW50b3MuIEVsIG1vZGVsbyBzZSBlbnRyZW5hIDEwIHZlY2VzCmN0cmwgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQoKIyBFbnRyZW5hciBlbCBtb2RlbG8gY29uIHZhbGlkYWNpw7NuIGNydXphZGEKdHJlZV9tb2RlbF9jdl9lY29ub21pYyA8LSB0cmFpbihQcmVjaW8gfiAuLCBkYXRhID0gdmFsaWRhdGlvbl9jbGVhbiwgCiAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY3RybCwKICAgICAgICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTApCgoKCgoKCiMgVmVyIGxvcyByZXN1bHRhZG9zCnByaW50KHRyZWVfbW9kZWxfY3ZfZWNvbm9taWMpCgojIEVsZWdpciB1biB2YWxvciBkZSBjcCBiYXNhZG8gZW4gbGEgZ3LDoWZpY2EgeSBwb2RhciBlbCDDoXJib2wKcHJ1bmVkX3RyZWUgPC0gcHJ1bmUodHJlZSwgY3AgPSAwLjAwMDgpCgojIFZpc3VhbGl6YXIgZWwgw6FyYm9sIHBvZGFkbwpycGFydC5wbG90KHBydW5lZF90cmVlKQoKcnBhcnQucnVsZXMocHJ1bmVkX3RyZWUsc3R5bGUgPSAndGFsbCcsY292ZXIgPSBUKQoKYGBgCgpEYXRvczogMTQzIG11ZXN0cmFzIGNvbiAyMiBwcmVkaWN0b3JlcwoKVmFsaWRhY2nDs24gQ3J1emFkYTogU2UgdXPDsyB1biBlbmZvcXVlIGRlIDEwIHBsaWVndWVzLgoKUmVzdWx0YWRvcyBkZSBSZW5kaW1pZW50bzogVmFyaWFzIGNvbmZpZ3VyYWNpb25lcyBkZWwgcGFyw6FtZXRybyBkZSBjb21wbGVqaWRhZCAoY3ApIGZ1ZXJvbiBldmFsdWFkYXMuIEVsIG1vZGVsbyBjb24gY3AgKENvbXBsZXhpdHkgUGFyYW1ldGVyKSA9IDAuMDAyNDA1NjQyIHR1dm8gZWwgbWVub3IgZXJyb3IgY3VhZHLDoXRpY28gbWVkaW8gKFJNU0UpLCBpbmRpY2FuZG8gZWwgbWVqb3IgZXF1aWxpYnJpbyBlbnRyZSBhanVzdGUgeSBjb21wbGVqaWRhZCwgcHVlcyBlbCBwcm9jZXNvIGRlIHBvZGEgZGVsIMOhcmJvbCwgYmFzYWRvIGVuIGVzdGUgcGFyw6FtZXRybywgZWxpbWluYSBsYXMgZGl2aXNpb25lcyBkZWwgw6FyYm9sIHF1ZSBubyBhcG9ydGFuIHNpZ25pZmljYXRpdmFtZW50ZSBhIHN1IGNhcGFjaWRhZCBwcmVkaWN0aXZhLCBzZWfDum4gdW4gY8OhbGN1bG8gZGUgY29zdG8tY29tcGxlamlkYWQuCgpSZWdsYXMgZGVsIMOBcmJvbCBQb2RhZG86IFNlIGRlc2NyaWJlbiByZWdsYXMgZXNwZWPDrWZpY2FzIHBhcmEgcHJlZGVjaXIgcHJlY2lvcyBiYXNhZG9zIGVuIG1ldHJvcyBjdWFkcmFkb3MgY29uc3RydWlkb3MgeSBuw7ptZXJvIGRlIGJhw7Fvcy4gUG9yIGVqZW1wbG8sIHBhcmEgdml2aWVuZGFzIG1lbm9yZXMgZGUgNTUgbcKyIGNvbiBtZW5vcyBkZSAyIGJhw7FvcywgZWwgcHJlY2lvIGVzdGltYWRvIGVzIDg4OS4KCgojIyBNb2RlbG8gcGFyYSBWaXZpZW5kYXMgUG9wdWxhcgoKRWwgbWlzbW8gcHJvY2VkaW1pZW50byBzZSByZWFsaXphIHBhcmEgbGFzIHZpdmllbmRhcyBwb3B1bGFyZXMuLi4KCiMjIyBTZXRzIGRlIEVudHJlbmFtaWVudG8sIFZhbGlkYWNpw7NuIHkgUHJ1ZWJhICAtIFZpdmllbmRhcyBQb3B1bGFyCmBgYHtyIH0Kc3VtbWFyeShWUG9wdWxhcikKVlBvcHVsYXIkQ2x1c3RlciA8LSBOVUxMCgojIEVzdGFibGVjZXIgbGEgc2VtaWxsYSBwYXJhIHJlcHJvZHVjaWJpbGlkYWQgCnNldC5zZWVkKDEyMykKCiMgUGFzbyAxOiBEaXZpZGlyIGVsIGNvbmp1bnRvIGRlIGRhdG9zIGVuIGVudHJlbmFtaWVudG8gKDUwJSkgeSB0ZW1wb3JhbCAoNTAlKQp0cmFpbkluZGV4MSA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKFZQb3B1bGFyJFByZWNpbywgcCA9IDAuNSwgbGlzdCA9IEZBTFNFLCB0aW1lcyA9IDEpCnRyYWluIDwtIFZQb3B1bGFyW3RyYWluSW5kZXgxLCBdCnRlbXAgPC0gVlBvcHVsYXJbLXRyYWluSW5kZXgxLCBdCgojIFBhc28gMjogRGl2aWRpciBlbCBjb25qdW50byB0ZW1wb3JhbCBlbiB2YWxpZGFjacOzbiAoNTAlIGRlIHRlbXApIHkgcHJ1ZWJhICg1MCUgZGUgdGVtcCkKdHJhaW5JbmRleDIgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih0ZW1wJFByZWNpbywgcCA9IDAuNSwgbGlzdCA9IEZBTFNFLCB0aW1lcyA9IDEpCnZhbGlkYXRpb24gPC0gdGVtcFt0cmFpbkluZGV4MiwgXQp0ZXN0UG9wIDwtIHRlbXBbLXRyYWluSW5kZXgyLCBdCmBgYAoKIyMgw4FyYm9sIGRlIERlY2lzacOzbiAtIFZpdmllbmRhcyBQb3B1bGFyCmBgYHtyIH0KIyBDb25zdHJ1aXIgZWwgw6FyYm9sIGRlIGRlY2lzacOzbgp0cmVlIDwtIHJwYXJ0KFByZWNpbyB+IG0yX2NvbnN0cnVpZG8gKyBCYW5vcyArIFJlY2FtYXJhcyArIEVzdGFjaW9uYW1pZW50byArIEdpbW5hc2lvICsgQ29jaW5hX2VxdWlwICsgR2ltbmFzaW8gKyBBbXVlYmxhZG8gKyBBbGJlcmNhICsgVGVycmF6YSArIEVsZXZhZG9yLCBkYXRhID0gdHJhaW4sIG1ldGhvZCA9ICJhbm92YSIsIGNvbnRyb2wgPSBycGFydC5jb250cm9sKGNwID0gMCkpCnJwYXJ0LnBsb3QodHJlZSkKCgojIFZpc3VhbGl6YXIgbGEgY3VydmEgZGUgY29tcGxlamlkYWQgZGUgY29zdG8KcGxvdGNwKHRyZWUpCgpgYGAKCgojIyBWYWxpZGFjacOzbiBDcnV6YWRhCmBgYHtyIH0Kc2V0LnNlZWQoMTIzKQpwcmVwcm9jIDwtIHByZVByb2Nlc3ModmFsaWRhdGlvbiwgbWV0aG9kID0gIm1lZGlhbkltcHV0ZSIpCnZhbGlkYXRpb25fY2xlYW4gPC0gcHJlZGljdChwcmVwcm9jLCBWUG9wdWxhcikKCgojIERlZmluaXIgZWwgbcOpdG9kbyBkZSBjb250cm9sIGRlIGVudHJlbmFtaWVudG8gcGFyYSBsYSB2YWxpZGFjacOzbiBjcnV6YWRhIGstZm9sZCAxMCBwbGllZ3VlcyBvIHN1YmNvbmp1bnRvcy4gRWwgbW9kZWxvIHNlIGVudHJlbmEgMTAgdmVjZXMKY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kID0gImN2IiwgbnVtYmVyID0gMTApCgojIEVudHJlbmFyIGVsIG1vZGVsbyBjb24gdmFsaWRhY2nDs24gY3J1emFkYQp0cmVlX21vZGVsX2N2X3BvcHVsYXIgPC0gdHJhaW4oUHJlY2lvIH4gLiwgZGF0YSA9IHZhbGlkYXRpb25fY2xlYW4sIAogICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJycGFydCIsIAogICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGN0cmwsCiAgICAgICAgICAgICAgICAgICAgICAgdHVuZUxlbmd0aCA9IDEwKQoKCiMgVmVyIGxvcyByZXN1bHRhZG9zCnByaW50KHRyZWVfbW9kZWxfY3ZfcG9wdWxhcikKCiMgRWxlZ2lyIHVuIHZhbG9yIGRlIGNwIGJhc2FkbyBlbiBsYSBncsOhZmljYSB5IHBvZGFyIGVsIMOhcmJvbApwcnVuZWRfdHJlZSA8LSBwcnVuZSh0cmVlLCBjcCA9IDAuMCkKCiMgVmlzdWFsaXphciBlbCDDoXJib2wgcG9kYWRvCnJwYXJ0LnBsb3QocHJ1bmVkX3RyZWUpCgpycGFydC5ydWxlcyhwcnVuZWRfdHJlZSxzdHlsZSA9ICd0YWxsJyxjb3ZlciA9IFQpCgpgYGAKClByZWNpbyA5MjQ6IFBhcmEgdml2aWVuZGFzIHNpbiBnaW1uYXNpbywgY29uIG1lbm9zIGRlIDEuMyBiYcOxb3MsIHNpbiBlbGV2YWRvciwgY29uIGNvY2luYSBlcXVpcGFkYSB5IGNvbiB1biB0YW1hw7FvIGRlIDUyIGEgNjggbcKyLCBlbCBwcmVjaW8gZXN0aW1hZG8gZXMgZGUgOTI0LiBFc3RlIGdydXBvIHJlcHJlc2VudGEgZWwgMTQlIGRlbCB0b3RhbC4KClByZWNpbyAxMDIyOiBWaXZpZW5kYXMgc2luIGdpbW5hc2lvLCBjb24gbWVub3MgZGUgMS4zIGJhw7Fvcywgc2luIGVsZXZhZG9yIHkgc2luIGNvY2luYSBlcXVpcGFkYSwgdGllbmVuIHVuIHByZWNpbyBlc3RpbWFkbyBkZSAxMDIyLiBDdWJyZW4gZWwgMTglIGRlbCB0b3RhbC4KClByZWNpbyAxMTY5OiBWaXZpZW5kYXMgc2luIGdpbW5hc2lvIHBlcm8gY29uIDEuMyBiYcOxb3MgbyBtw6FzIHRpZW5lbiB1biBwcmVjaW8gZXN0aW1hZG8gZGUgMTE2OSwgcmVwcmVzZW50YW5kbyBlbCAxNCUuCgpQcmVjaW8gMjk0NTogTGFzIHZpdmllbmRhcyBjb24gZ2ltbmFzaW8gdGllbmVuIHVuIHByZWNpbyBzaWduaWZpY2F0aXZhbWVudGUgbWF5b3IsIGVzdGltYWRvIGVuIDI5NDUsIHkgY29uc3RpdHV5ZW4gZWwgMTglIGRlbCB0b3RhbC4KCkVzdG9zIHJlc3VsdGFkb3MgcmVzYWx0YW4gY8OzbW8gY2FyYWN0ZXLDrXN0aWNhcyBlc3BlY8OtZmljYXMgY29tbyBsYSBwcmVzZW5jaWEgZGUgZ2ltbmFzaW8sIG7Dum1lcm8gZGUgYmHDsW9zLCB5IGxhcyBjb21vZGlkYWRlcyBpbXBhY3RhbiBlbiBlbCBwcmVjaW8gZGUgbGFzIHZpdmllbmRhcyBwb3B1bGFyZXMuCgoKIyMgTW9kZWxvIHBhcmEgVml2aWVuZGFzIE1lZGlhIFJlc2lkZW5jaWFsCgpTZSByZWFsaXphIGVsIG1pc21vIHByb2NlZGltaWVudG8gcGFyYSB2aXZpZW5kYXMgcmVzaWRlbmNpYWxlcwoKIyMgU2V0cyBkZSBFbnRyZW5hbWllbnRvLCBWYWxpZGFjacOzbiB5IFBydWViYSAgLSBWaXZpZW5kYXMgTWVkaWEgUmVzaWRlbmNpYWwKYGBge3IgfQpzdW1tYXJ5KFZNZWRpYVJlc2lkZW5jaWFsKQpWTWVkaWFSZXNpZGVuY2lhbCRDbHVzdGVyIDwtIE5VTEwKCiMgRXN0YWJsZWNlciBsYSBzZW1pbGxhIHBhcmEgcmVwcm9kdWNpYmlsaWRhZCAKc2V0LnNlZWQoMTIzKQoKIyBQYXNvIDE6IERpdmlkaXIgZWwgY29uanVudG8gZGUgZGF0b3MgZW4gZW50cmVuYW1pZW50byAoNTAlKSB5IHRlbXBvcmFsICg1MCUpCnRyYWluSW5kZXgxIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oVk1lZGlhUmVzaWRlbmNpYWwkUHJlY2lvLCBwID0gMC41LCBsaXN0ID0gRkFMU0UsIHRpbWVzID0gMSkKdHJhaW4gPC0gVk1lZGlhUmVzaWRlbmNpYWxbdHJhaW5JbmRleDEsIF0KdGVtcCA8LSBWTWVkaWFSZXNpZGVuY2lhbFstdHJhaW5JbmRleDEsIF0KCiMgUGFzbyAyOiBEaXZpZGlyIGVsIGNvbmp1bnRvIHRlbXBvcmFsIGVuIHZhbGlkYWNpw7NuICg1MCUgZGUgdGVtcCkgeSBwcnVlYmEgKDUwJSBkZSB0ZW1wKQp0cmFpbkluZGV4MiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHRlbXAkUHJlY2lvLCBwID0gMC41LCBsaXN0ID0gRkFMU0UsIHRpbWVzID0gMSkKdmFsaWRhdGlvbiA8LSB0ZW1wW3RyYWluSW5kZXgyLCBdCnRlc3RSZXNpIDwtIHRlbXBbLXRyYWluSW5kZXgyLCBdCmBgYAoKIyMgw4FyYm9sIGRlIERlY2lzacOzbiAtIFZpdmllbmRhcyBNZWRpYSBSZXNpZGVuY2lhbApgYGB7ciB9CiMgQ29uc3RydWlyIGVsIMOhcmJvbCBkZSBkZWNpc2nDs24KdHJlZSA8LSBycGFydChQcmVjaW8gfiBtMl9jb25zdHJ1aWRvICsgQmFub3MgKyBSZWNhbWFyYXMgKyBFc3RhY2lvbmFtaWVudG8gKyBHaW1uYXNpbyArIENvY2luYV9lcXVpcCArIEdpbW5hc2lvICsgQW11ZWJsYWRvICsgQWxiZXJjYSArIFRlcnJhemEgKyBFbGV2YWRvciwgZGF0YSA9IHRyYWluLCBtZXRob2QgPSAiYW5vdmEiLCBjb250cm9sID0gcnBhcnQuY29udHJvbChjcCA9IDApKQpycGFydC5wbG90KHRyZWUpCgoKIyBWaXN1YWxpemFyIGxhIGN1cnZhIGRlIGNvbXBsZWppZGFkIGRlIGNvc3RvCnBsb3RjcCh0cmVlKQoKYGBgCgoKIyMgVmFsaWRhY2nDs24gQ3J1emFkYQpgYGB7ciB9CnNldC5zZWVkKDEyMykKcHJlcHJvYyA8LSBwcmVQcm9jZXNzKHZhbGlkYXRpb24sIG1ldGhvZCA9ICJtZWRpYW5JbXB1dGUiKQp2YWxpZGF0aW9uX2NsZWFuIDwtIHByZWRpY3QocHJlcHJvYywgVk1lZGlhUmVzaWRlbmNpYWwpCgoKIyBEZWZpbmlyIGVsIG3DqXRvZG8gZGUgY29udHJvbCBkZSBlbnRyZW5hbWllbnRvIHBhcmEgbGEgdmFsaWRhY2nDs24gY3J1emFkYSBrLWZvbGQgMTAgcGxpZWd1ZXMgbyBzdWJjb25qdW50b3MuIEVsIG1vZGVsbyBzZSBlbnRyZW5hIDEwIHZlY2VzCmN0cmwgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQoKIyBFbnRyZW5hciBlbCBtb2RlbG8gY29uIHZhbGlkYWNpw7NuIGNydXphZGEKdHJlZV9tb2RlbF9jdl9yZXNpZGVuY2lhbCA8LSB0cmFpbihQcmVjaW8gfiAuLCBkYXRhID0gdmFsaWRhdGlvbl9jbGVhbiwgCiAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY3RybCwKICAgICAgICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTApCgoKCgoKCiMgVmVyIGxvcyByZXN1bHRhZG9zCnByaW50KHRyZWVfbW9kZWxfY3ZfcmVzaWRlbmNpYWwpCgojIEVsZWdpciB1biB2YWxvciBkZSBjcCBiYXNhZG8gZW4gZWwgbWVub3IgUk1TRQpwcnVuZWRfdHJlZSA8LSBwcnVuZSh0cmVlLCBjcCA9IDAuMDE3KQoKIyBWaXN1YWxpemFyIGVsIMOhcmJvbCBwb2RhZG8KcnBhcnQucGxvdChwcnVuZWRfdHJlZSkKCnJwYXJ0LnJ1bGVzKHBydW5lZF90cmVlLHN0eWxlID0gJ3RhbGwnLGNvdmVyID0gVCkKCmBgYAoKRGVzZW1wZcOxbyBkZWwgTW9kZWxvOiBDb24gdW4gcmFuZ28gZGUgdmFsb3JlcyBkZSBjcCwgZWwgbW9kZWxvIGNvbiBjcCA9IDAuMDA5MDA4MzY3IG9idHV2byBlbCBtZW5vciBSTVNFLCBpbmRpY2FuZG8gc2VyIGVsIG3DoXMgw7NwdGltbyBlbiB0w6lybWlub3MgZGUgcHJlY2lzacOzbiB5IGNvbXBsZWppZGFkLgoKUHJlZGljY2lvbmVzIGRlIFByZWNpbzoKClByZWNpbyA2OTc6IFBhcmEgdml2aWVuZGFzIG1lbm9yZXMgZGUgODggbcKyIHNpbiB0ZXJyYXphIHkgY29uIGNvY2luYSBlcXVpcGFkYS4KUHJlY2lvIDI0MTE6IFBhcmEgdml2aWVuZGFzIG1lbm9yZXMgZGUgODggbcKyLCBpbmRlcGVuZGllbnRlbWVudGUgZGVsIGVzdGFkbyBkZSBsYSB0ZXJyYXphIG8gY29jaW5hIGVxdWlwYWRhLgpQcmVjaW8gMzkyNzogUGFyYSB2aXZpZW5kYXMgbWVub3JlcyBkZSA4OCBtwrIgY29uIHRlcnJhemEuClByZWNpbyA2MDUyOiBQYXJhIHZpdmllbmRhcyBkZSB0YW1hw7FvIGVudHJlIDg4IHkgMTM5IG3Csi4KUHJlY2lvIDg4Nzc6IFBhcmEgdml2aWVuZGFzIGRlIG3DoXMgZGUgMTM5IG3Csi4KCkVzdG9zIHJlc3VsdGFkb3MgcmVmbGVqYW4gY8OzbW8gbGFzIGNhcmFjdGVyw61zdGljYXMgY29tbyBlbCB0YW1hw7FvLCBsYSBwcmVzZW5jaWEgZGUgdGVycmF6YSB5IGxhIGNvY2luYSBlcXVpcGFkYSBpbmZsdXllbiBzaWduaWZpY2F0aXZhbWVudGUgZW4gZWwgcHJlY2lvIGRlIGxhcyB2aXZpZW5kYXMgcmVzaWRlbmNpYWxlcy4KCiMgTW9kZWxvIHBhcmEgVml2aWVuZGFzIFByZW1pdW0KIyMgU2V0cyBkZSBFbnRyZW5hbWllbnRvLCBWYWxpZGFjacOzbiB5IFBydWViYSAgLSBWaXZpZW5kYXMgUHJlbWl1bQpgYGB7ciB9CnN1bW1hcnkoVlByZW1pdW0pClZQcmVtaXVtJENsdXN0ZXI8LSBOVUxMCgojIEVzdGFibGVjZXIgbGEgc2VtaWxsYSBwYXJhIHJlcHJvZHVjaWJpbGlkYWQgCnNldC5zZWVkKDEyMykKCiMgUGFzbyAxOiBEaXZpZGlyIGVsIGNvbmp1bnRvIGRlIGRhdG9zIGVuIGVudHJlbmFtaWVudG8gKDUwJSkgeSB0ZW1wb3JhbCAoNTAlKQp0cmFpbkluZGV4MSA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKFZQcmVtaXVtJFByZWNpbywgcCA9IDAuNSwgbGlzdCA9IEZBTFNFLCB0aW1lcyA9IDEpCnRyYWluIDwtIFZQcmVtaXVtW3RyYWluSW5kZXgxLCBdCnRlbXAgPC0gVlByZW1pdW1bLXRyYWluSW5kZXgxLCBdCgojIFBhc28gMjogRGl2aWRpciBlbCBjb25qdW50byB0ZW1wb3JhbCBlbiB2YWxpZGFjacOzbiAoNTAlIGRlIHRlbXApIHkgcHJ1ZWJhICg1MCUgZGUgdGVtcCkKdHJhaW5JbmRleDIgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbih0ZW1wJFByZWNpbywgcCA9IDAuNSwgbGlzdCA9IEZBTFNFLCB0aW1lcyA9IDEpCnZhbGlkYXRpb24gPC0gdGVtcFt0cmFpbkluZGV4MiwgXQp0ZXN0UHJlbSA8LSB0ZW1wWy10cmFpbkluZGV4MiwgXQpgYGAKCiMjIMOBcmJvbCBkZSBEZWNpc2nDs24gLSBWaXZpZW5kYXMgTWVkaWEgUmVzaWRlbmNpYWwKYGBge3IgfQojIENvbnN0cnVpciBlbCDDoXJib2wgZGUgZGVjaXNpw7NuCnRyZWUgPC0gcnBhcnQoUHJlY2lvIH4gbTJfY29uc3RydWlkbyArIEJhbm9zICsgUmVjYW1hcmFzICsgRXN0YWNpb25hbWllbnRvICsgR2ltbmFzaW8gKyBDb2NpbmFfZXF1aXAgKyBHaW1uYXNpbyArIEFtdWVibGFkbyArIEFsYmVyY2EgKyBUZXJyYXphICsgRWxldmFkb3IsIGRhdGEgPSB0cmFpbiwgbWV0aG9kID0gImFub3ZhIiwgY29udHJvbCA9IHJwYXJ0LmNvbnRyb2woY3AgPSAwKSkKcnBhcnQucGxvdCh0cmVlKQoKCiMgVmlzdWFsaXphciBsYSBjdXJ2YSBkZSBjb21wbGVqaWRhZCBkZSBjb3N0bwpwbG90Y3AodHJlZSkKCmBgYAoKCiMjIFZhbGlkYWNpw7NuIENydXphZGEKYGBge3IgfQpzZXQuc2VlZCgxMjMpCnByZXByb2MgPC0gcHJlUHJvY2Vzcyh2YWxpZGF0aW9uLCBtZXRob2QgPSAibWVkaWFuSW1wdXRlIikKdmFsaWRhdGlvbl9jbGVhbiA8LSBwcmVkaWN0KHByZXByb2MsIFZQcmVtaXVtKQoKCiMgRGVmaW5pciBlbCBtw6l0b2RvIGRlIGNvbnRyb2wgZGUgZW50cmVuYW1pZW50byBwYXJhIGxhIHZhbGlkYWNpw7NuIGNydXphZGEgay1mb2xkIDEwIHBsaWVndWVzIG8gc3ViY29uanVudG9zLiBFbCBtb2RlbG8gc2UgZW50cmVuYSAxMCB2ZWNlcwpjdHJsIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCkKCiMgRW50cmVuYXIgZWwgbW9kZWxvIGNvbiB2YWxpZGFjacOzbiBjcnV6YWRhCnRyZWVfbW9kZWxfY3ZfcHJlbWl1bSA8LSB0cmFpbihQcmVjaW8gfiAuLCBkYXRhID0gdmFsaWRhdGlvbl9jbGVhbiwgCiAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJwYXJ0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gY3RybCwKICAgICAgICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTApCgoKCiMgVmVyIGxvcyByZXN1bHRhZG9zCnByaW50KHRyZWVfbW9kZWxfY3ZfcHJlbWl1bSkKCiMgRWxlZ2lyIHVuIHZhbG9yIGRlIGNwIGJhc2FkbyBlbiBsYSBncsOhZmljYSB5IHBvZGFyIGVsIMOhcmJvbApwcnVuZWRfdHJlZSA8LSBwcnVuZSh0cmVlLCBjcCA9IDAuMCkKCiMgVmlzdWFsaXphciBlbCDDoXJib2wgcG9kYWRvCnJwYXJ0LnBsb3QocHJ1bmVkX3RyZWUpCgpycGFydC5ydWxlcyhwcnVuZWRfdHJlZSxzdHlsZSA9ICd0YWxsJyxjb3ZlciA9IFQpCgpgYGAKCkRlc2VtcGXDsW8gZGVsIE1vZGVsbzogQ29uIHZhcmlvcyB2YWxvcmVzIGRlIGNwLCBlbCBtb2RlbG8gY29uIGNwID0gMC4wNyB0dXZvIGVsIG1lbm9yIFJNU0UsIGluZGljYW5kbyBzZXIgZWwgbcOhcyBwcmVjaXNvIHBhcmEgZXN0YXMgdml2aWVuZGFzLgoKUHJlZGljY2lvbmVzIGRlIFByZWNpbzoKClByZWNpbyAxOTQ3OiBQYXJhIHZpdmllbmRhcyBtZW5vcmVzIGRlIDk0IG3CsiBzaW4gZWxldmFkb3IuClByZWNpbyAzMzE2OiBQYXJhIHZpdmllbmRhcyBtZW5vcmVzIGRlIDk0IG3CsiBjb24gZWxldmFkb3IuClByZWNpbyA0OTc0OiBQYXJhIHZpdmllbmRhcyBlbnRyZSA5NCB5IDEzMiBtwrIuClByZWNpbyA5Mjg3OiBQYXJhIHZpdmllbmRhcyBkZSBtw6FzIGRlIDEzMiBtwrIuCgpFc3RvcyByZXN1bHRhZG9zIHJlZmxlamFuIGPDs21vIGVsIHRhbWHDsW8geSBsYSBwcmVzZW5jaWEgZGUgdW4gZWxldmFkb3IgaW5mbHV5ZW4gc2lnbmlmaWNhdGl2YW1lbnRlIGVuIGVsIHByZWNpbyBkZSBsYXMgdml2aWVuZGFzIHByZW1pdW0uIExhcyB2aXZpZW5kYXMgbcOhcyBncmFuZGVzIHkgYXF1ZWxsYXMgY29uIGVsZXZhZG9yIHRpZW5kZW4gYSB0ZW5lciBwcmVjaW9zIG3DoXMgYWx0b3MuCgojIFByZWRpY2Npb25lcyB5IEV2YWx1YWNpw7NuIGRlbCBNb2RlbG8gKE1BRSB5IFJNU0UpCmBgYHtyIH0KCmNhbGN1bGF0ZV9tZXRyaWNzIDwtIGZ1bmN0aW9uKHByZWRpY3Rpb25zLCBhY3R1YWwpIHsKICBtYWUgPC0gbWVhbihhYnMocHJlZGljdGlvbnMgLSBhY3R1YWwpKQogIHJtc2UgPC0gc3FydChtZWFuKChwcmVkaWN0aW9ucyAtIGFjdHVhbCleMikpCiAgbWFwZSA8LSBtZWFuKGFicygocHJlZGljdGlvbnMgLSBhY3R1YWwpIC8gYWN0dWFsKSkgKiAxMDAKICByZXR1cm4oYyhNQUUgPSBtYWUsIFJNU0UgPSBybXNlLCBNQVBFID0gbWFwZSkpCn0KCiMjIEV2YWx1YWNpw7NuIHBhcmEgVml2aWVuZGFzIEVjb27Ds21pY2FzCnByZWRfVkVjb24gPC0gcHJlZGljdCh0cmVlX21vZGVsX2N2X2Vjb25vbWljLCBuZXdkYXRhID0gdGVzdEVjb24pCm1ldHJpY3NfVkVjb24gPC0gY2FsY3VsYXRlX21ldHJpY3MocHJlZF9WRWNvbiwgdGVzdEVjb24kUHJlY2lvKQpjYXQoIk3DqXRyaWNhcyBwYXJhIFZpdmllbmRhcyBFY29uw7NtaWNhczpcbiIpCmNhdCgiTUFFOiIsIG1ldHJpY3NfVkVjb25bIk1BRSJdLCAiXG4iKQpjYXQoIlJNU0U6IiwgbWV0cmljc19WRWNvblsiUk1TRSJdLCAiXG4iKQpjYXQoIk1BUEU6IiwgbWV0cmljc19WRWNvblsiTUFQRSJdLCAiJVxuIikKCiMjIEV2YWx1YWNpw7NuIHBhcmEgVml2aWVuZGFzIFBvcHVsYXIKcHJlZF9WUG9wdWxhciA8LSBwcmVkaWN0KHRyZWVfbW9kZWxfY3ZfcG9wdWxhciwgbmV3ZGF0YSA9IHRlc3RQb3ApCm1ldHJpY3NfVlBvcHVsYXIgPC0gY2FsY3VsYXRlX21ldHJpY3MocHJlZF9WUG9wdWxhciwgdGVzdFBvcCRQcmVjaW8pCmNhdCgiXG5Nw6l0cmljYXMgcGFyYSBWaXZpZW5kYXMgUG9wdWxhcjpcbiIpCmNhdCgiTUFFOiIsIG1ldHJpY3NfVlBvcHVsYXJbIk1BRSJdLCAiXG4iKQpjYXQoIlJNU0U6IiwgbWV0cmljc19WUG9wdWxhclsiUk1TRSJdLCAiXG4iKQpjYXQoIk1BUEU6IiwgbWV0cmljc19WUG9wdWxhclsiTUFQRSJdLCAiJVxuIikKCiMjIEV2YWx1YWNpw7NuIHBhcmEgVml2aWVuZGFzIE1lZGlhIFJlc2lkZW5jaWFsCnByZWRfVk1lZGlhUmVzaWRlbmNpYWwgPC0gcHJlZGljdCh0cmVlX21vZGVsX2N2X3Jlc2lkZW5jaWFsLCBuZXdkYXRhID0gdGVzdFJlc2kpCm1ldHJpY3NfVk1lZGlhUmVzaWRlbmNpYWwgPC0gY2FsY3VsYXRlX21ldHJpY3MocHJlZF9WTWVkaWFSZXNpZGVuY2lhbCwgdGVzdFJlc2kkUHJlY2lvKQpjYXQoIlxuTcOpdHJpY2FzIHBhcmEgVml2aWVuZGFzIE1lZGlhIFJlc2lkZW5jaWFsOlxuIikKY2F0KCJNQUU6IiwgbWV0cmljc19WTWVkaWFSZXNpZGVuY2lhbFsiTUFFIl0sICJcbiIpCmNhdCgiUk1TRToiLCBtZXRyaWNzX1ZNZWRpYVJlc2lkZW5jaWFsWyJSTVNFIl0sICJcbiIpCmNhdCgiTUFQRToiLCBtZXRyaWNzX1ZNZWRpYVJlc2lkZW5jaWFsWyJNQVBFIl0sICIlXG4iKQoKIyMgRXZhbHVhY2nDs24gcGFyYSBWaXZpZW5kYXMgUHJlbWl1bQpwcmVkX1ZQcmVtaXVtIDwtIHByZWRpY3QodHJlZV9tb2RlbF9jdl9wcmVtaXVtLCBuZXdkYXRhID0gdGVzdFByZW0pCm1ldHJpY3NfVlByZW1pdW0gPC0gY2FsY3VsYXRlX21ldHJpY3MocHJlZF9WUHJlbWl1bSwgdGVzdFByZW0kUHJlY2lvKQpjYXQoIlxuTcOpdHJpY2FzIHBhcmEgVml2aWVuZGFzIFByZW1pdW06XG4iKQpjYXQoIk1BRToiLCBtZXRyaWNzX1ZQcmVtaXVtWyJNQUUiXSwgIlxuIikKY2F0KCJSTVNFOiIsIG1ldHJpY3NfVlByZW1pdW1bIlJNU0UiXSwgIlxuIikKY2F0KCJNQVBFOiIsIG1ldHJpY3NfVlByZW1pdW1bIk1BUEUiXSwgIiVcbiIpCgpgYGAKCk1vZGVsbyBFY29uw7NtaWNvIChWaXZpZW5kYXMgRWNvbsOzbWljYXMpOgpFbCBtb2RlbG8gZWNvbsOzbWljbyBtdWVzdHJhIHVuIE1BRSAoRXJyb3IgQWJzb2x1dG8gTWVkaW8pIHJlbGF0aXZhbWVudGUgYmFqbyB5IHVuIFJNU0UgKEVycm9yIEN1YWRyw6F0aWNvIE1lZGlvIGRlIGxhIFJhw616KSBtb2RlcmFkby4gRXN0byBzdWdpZXJlIHF1ZSBsYXMgcHJlZGljY2lvbmVzIHRpZW5kZW4gYSBlc3RhciBjZXJjYSBkZSBsb3MgdmFsb3JlcyByZWFsZXMgZW4gcHJvbWVkaW8sIHBlcm8gaGF5IGFsZ3VuYXMgdmFyaWFjaW9uZXMgc2lnbmlmaWNhdGl2YXMgZW4gbG9zIGVycm9yZXMgZGUgcHJlZGljY2nDs24uIEFkZW3DoXMsIGVsIE1BUEUgKEVycm9yIFBvcmNlbnR1YWwgQWJzb2x1dG8gTWVkaW8pIGRlbCA0My40NCUgaW5kaWNhIHF1ZSBsYXMgcHJlZGljY2lvbmVzIHB1ZWRlbiB2YXJpYXIgZW4gdW4gNDMuNDQlIGVuIHByb21lZGlvIGVuIHJlbGFjacOzbiBjb24gbG9zIHByZWNpb3MgcmVhbGVzLiBFbiBnZW5lcmFsLCBlc3RlIG1vZGVsbyBwcm9wb3JjaW9uYSByZXN1bHRhZG9zIGFjZXB0YWJsZXMgcGFyYSBsYSBwcmVkaWNjacOzbiBkZSBwcmVjaW9zIGRlIHZpdmllbmRhcyBlY29uw7NtaWNhcywgcGVybyBlcyBpbXBvcnRhbnRlIGNvbnNpZGVyYXIgcXVlIGHDum4gZXhpc3RlIHVuIG1hcmdlbiBkZSBtZWpvcmEgcGFyYSByZWR1Y2lyIGxhcyB2YXJpYWNpb25lcyBlbiBsYXMgcHJlZGljY2lvbmVzIHkgcXVlIGVsIG1hcmdlbiBkZSBlcnJvciBlcyBkZW1hc2lhZG8gYWx0byBwYXJhIGNvbnNpZGVyYXIgYWwgbW9kZWxvIGNvbW8gZmlhYmxlIHkgY2VydGVybyBlbiBzdXMgcHJlZGljY2lvbmVzLgoKTW9kZWxvIFBvcHVsYXIgKFZpdmllbmRhcyBQb3B1bGFyKToKRWwgbW9kZWxvIHBvcHVsYXIgbXVlc3RyYSB1biBNQUUgc2ltaWxhciBhbCBtb2RlbG8gZWNvbsOzbWljbywgbG8gcXVlIHN1Z2llcmUgdW5hIHByZWNpc2nDs24gc2ltaWxhciBlbiBsYXMgcHJlZGljY2lvbmVzIGVuIHTDqXJtaW5vcyBkZSB2YWxvciBwcm9tZWRpbyBhYnNvbHV0by4gU2luIGVtYmFyZ28sIGVsIFJNU0UgbcOhcyBhbHRvIGluZGljYSB1bmEgbWF5b3IgdmFyaWFiaWxpZGFkIGVuIGxvcyBlcnJvcmVzIGRlIHByZWRpY2Npw7NuLCBsbyBxdWUgcG9kcsOtYSBzZXIgYXRyaWJ1aWJsZSBhIHZhbG9yZXMgYXTDrXBpY29zIG8gcHJlZGljY2lvbmVzIG1lbm9zIHByZWNpc2FzLiBFbCBNQVBFIGRlbCA0MC45OCUgaW5kaWNhIHF1ZSBsYXMgcHJlZGljY2lvbmVzIHB1ZWRlbiB2YXJpYXIgZW4gdW4gNDAuOTglIGVuIHByb21lZGlvIGVuIHJlbGFjacOzbiBjb24gbG9zIHByZWNpb3MgcmVhbGVzLiBFc3RvIHNlw7FhbGEgcXVlIGVsIG1vZGVsbyBwdWVkZSB0ZW5lciBkaWZpY3VsdGFkZXMgcGFyYSBwcmVkZWNpciBkZSBtYW5lcmEgY29uc2lzdGVudGUgbG9zIHByZWNpb3MgZGUgbGFzIHZpdmllbmRhcyBwb3B1bGFyZXMsIHkgcG9kcsOtYSByZXF1ZXJpciBtZWpvcmFzIHBhcmEgcmVkdWNpciBsYXMgdmFyaWFjaW9uZXMgZW4gbGFzIHByZWRpY2Npb25lcy4KCk1vZGVsbyBNZWRpbyBSZXNpZGVuY2lhbCAoVml2aWVuZGFzIE1lZGlhIFJlc2lkZW5jaWFsKToKRWwgbW9kZWxvIGRlIHZpdmllbmRhcyBkZSBuaXZlbCBtZWRpbyByZXNpZGVuY2lhbCBtdWVzdHJhIHVuIE1BRSB5IHVuIFJNU0Ugc2lnbmlmaWNhdGl2YW1lbnRlIG3DoXMgYWx0b3MgZW4gY29tcGFyYWNpw7NuIGNvbiBsb3MgbW9kZWxvcyBlY29uw7NtaWNvIHkgcG9wdWxhci4gRXN0byBpbmRpY2EgdW5hIHByZWNpc2nDs24gZGUgcHJlZGljY2nDs24gZ2VuZXJhbCBtw6FzIGJhamEgeSB1bmEgbWF5b3IgaW5jb25zaXN0ZW5jaWEgZW4gbGFzIHByZWRpY2Npb25lcy4gRWwgTUFQRSBkZWwgMzcuNjclIHNlw7FhbGEgcXVlIGxhcyBwcmVkaWNjaW9uZXMgcHVlZGVuIHZhcmlhciBlbiB1biAzNy42NyUgZW4gcHJvbWVkaW8gZW4gcmVsYWNpw7NuIGNvbiBsb3MgcHJlY2lvcyByZWFsZXMuIEVzdGFzIG3DqXRyaWNhcyBpbmRpY2FuIHF1ZSBlbCBtb2RlbG8gdGllbmUgZGlmaWN1bHRhZGVzIHBhcmEgcHJlZGVjaXIgY29uIHByZWNpc2nDs24gbG9zIHByZWNpb3MgZGUgbGFzIHZpdmllbmRhcyBkZSBuaXZlbCBtZWRpbyByZXNpZGVuY2lhbCwgeSBzZSByZWNvbWllbmRhIHVuYSByZXZpc2nDs24gZXhoYXVzdGl2YSB5IHBvc2libGVzIG1lam9yYXMgZW4gZWwgbW9kZWxvLgoKTW9kZWxvIFByZW1pdW0gKFZpdmllbmRhcyBQcmVtaXVtKToKRWwgbW9kZWxvIHByZW1pdW0gbXVlc3RyYSB1biBNQUUgbW9kZXJhZG8geSBlbCBSTVNFIG3DoXMgYmFqbyBkZSBsb3MgY3VhdHJvIG1vZGVsb3MuIEVzdG8gc3VnaWVyZSBxdWUsIGF1bnF1ZSBleGlzdGVuIGVycm9yZXMgZW4gbGFzIHByZWRpY2Npb25lcywgdGllbmRlbiBhIHNlciBtZW5vcyBleHRyZW1vcyBlbiBjb21wYXJhY2nDs24gY29uIGxvcyBvdHJvcyBtb2RlbG9zLiBFbCBNQVBFIGRlbCAxNy45NSUgaW5kaWNhIHF1ZSBsYXMgcHJlZGljY2lvbmVzIHB1ZWRlbiB2YXJpYXIgZW4gdW4gMTcuOTUlIGVuIHByb21lZGlvIGVuIHJlbGFjacOzbiBjb24gbG9zIHByZWNpb3MgcmVhbGVzLCBsbyBxdWUgZXMgcmVsYXRpdmFtZW50ZSBiYWpvIGVuIGNvbXBhcmFjacOzbiBjb24gbG9zIG90cm9zIG1vZGVsb3MuIEVuIHJlc3VtZW4sIGVzdGUgbW9kZWxvIHBhcmVjZSB0ZW5lciBlbCBtZWpvciByZW5kaW1pZW50byBlbiB0w6lybWlub3MgZGUgY29uc2lzdGVuY2lhIHkgcHJlY2lzacOzbiBlbiBsYSBwcmVkaWNjacOzbiBkZSBwcmVjaW9zIHBhcmEgdml2aWVuZGFzIHByZW1pdW0uCgpFbiByZXN1bWVuLCBlcyBlc2VuY2lhbCBjb25zaWRlcmFyIGVzdGFzIG3DqXRyaWNhcyBhbCBldmFsdWFyIHkgY29tcGFyYXIgbW9kZWxvcyBwcmVkaWN0aXZvcy4gTGEgZWZlY3RpdmlkYWQgZGUgY2FkYSBtb2RlbG8gZGVwZW5kZXLDoSBkZWwgY29udGV4dG8gZGVsIG1lcmNhZG8geSBkZSBsYXMgZXhwZWN0YXRpdmFzIGRlIHByZWNpc2nDs24uIFNpIHNlIHJlcXVpZXJlIHVuYSBtYXlvciBwcmVjaXNpw7NuLCBlcyBwb3NpYmxlIHF1ZSBzZSBkZWJhbiByZWFsaXphciBtZWpvcmFzIGVuIGxvcyBtb2RlbG9zLCBlc3BlY2lhbG1lbnRlIGVuIGVsIGNhc28gZGVsIG1vZGVsbyBkZSB2aXZpZW5kYXMgZGUgbml2ZWwgbWVkaW8gcmVzaWRlbmNpYWwuIEVzdGFzIG3DqXRyaWNhcyBzb24gZnVuZGFtZW50YWxlcyBwYXJhIHJlc3BhbGRhciBsYSB0b21hIGRlIGRlY2lzaW9uZXMgYmFzYWRhIGVuIGRhdG9zIHkgbWVqb3JhciBsYSBjYWxpZGFkIGRlIGxhcyBwcmVkaWNjaW9uZXMgZW4gZWwgbWVyY2FkbyBkZSB2aXZpZW5kYXMuCgojIENvbmNsdXNpb25lcwoKQ29tbyByZWZsZXhpw7NuIGZpbmFsLCBsb3MgbW9kZWxvcyBwcmVkaWN0aXZvcyBlbGFib3JhZG9zIHJlY29ub2NlbiBxdWUgZGlmZXJlbnRlcyB2YXJpYWJsZXMgcHVlZGVuIGluZmx1aXIgZW4gZWwgcHJlY2lvIGRlIGxhcyB2aXZpZW5kYXMgZW4gY2FkYSBjYXRlZ29yw61hLCBwb3IgdGFudG8sIExhcyByZWdsYXMgZGUgZGVjaXNpw7NuIGdlbmVyYWRhcyBwb3IgbG9zIMOhcmJvbGVzIGRlIGRlY2lzacOzbiBwcm9wb3JjaW9uYW4gcmVjb21lbmRhY2lvbmVzIHZhbGlvc2FzIHBhcmEgaW52ZXJzb3JlcyB5IGRlc2Fycm9sbGFkb3Jlcy4gUGFyYSBvYnRlbmVyIHJlc3VsdGFkb3MgYcO6biBtw6FzIHPDs2xpZG9zLCBzZSByZWNvbWllbmRhIHJlYWxpemFyIHVuYSB2YWxpZGFjacOzbiBleGhhdXN0aXZhIGRlIGxvcyBtb2RlbG9zIHkgY29uc2lkZXJhciBsYSBpbmNsdXNpw7NuIGRlIG3DoXMgY2FyYWN0ZXLDrXN0aWNhcyByZWxldmFudGVzLCBjb21vIHViaWNhY2nDs24gZ2VvZ3LDoWZpY2EsIHRlbmRlbmNpYXMgbWFjcm9lY29uw7NtaWNhcyB5IGRhdG9zIGRlbW9ncsOhZmljb3MsIGFzw60gY29tbyBhbXBsaWFyIGVsIHRhbWHDsW8gZGUgbGEgbXVlc3RyYSBkZSBsb3MgZGF0b3MuCgpFbiBjb25jbHVzacOzbiwgTG9zIGluc2lnaHRzIG9idGVuaWRvcyBwdWVkZW4gYXBsaWNhcnNlIGVuIGVzdHJhdGVnaWFzIGRlIGludmVyc2nDs24sIGRlc2Fycm9sbG8gZGUgcG9sw610aWNhcyBkZSB2aXZpZW5kYSwgeSBlbiBsYSBwbGFuaWZpY2FjacOzbiB1cmJhbmEuIEFsIGVudGVuZGVyIGxhcyBwcmVmZXJlbmNpYXMgZGUgbG9zIGNvbXByYWRvcmVzIHkgbGFzIHRlbmRlbmNpYXMgZGVsIG1lcmNhZG8sIGxvcyBkZXNhcnJvbGxhZG9yZXMgeSBhZ2VudGVzIGlubW9iaWxpYXJpb3MgcHVlZGVuIGFkYXB0YXIgc3VzIHByb3llY3RvcyB5IGVzdHJhdGVnaWFzIGRlIG1hcmtldGluZyBwYXJhIHNhdGlzZmFjZXIgbWVqb3IgbGFzIG5lY2VzaWRhZGVzIGRlbCBtZXJjYWRvIGVuIGxhIENETVguCgojIFJlZmVyZW5jaWFzCgpHdXRpw6lycmV6LCBGLiAoMjAyMykuIFNpdHVhY2nDs24gZGUgbGEgdml2aWVuZGEgZW4gbGEgQ0RNWDogUHJlY2lvcyBtw6FzIGFsdG9zLCBwZXJvIGNvbiBvZmVydGEgbcOhcyBhc2VxdWlibGUgwr9DdcOhbCBlcyBsYSByZXNwdWVzdGEgYSBlc3RvPyBFbCBFY29ub21pc3RhLiBodHRwczovL3d3dy5lbGVjb25vbWlzdGEuY29tLm14L2Vjb25vaGFiaXRhdC9TaXR1YWNpb24tZGUtbGEtdml2aWVuZGEtZW4tbGEtQ0RNWC1QcmVjaW9zLW1hcy1hbHRvcy1wZXJvLWNvbi1vZmVydGEtbWFzLWFzZXF1aWJsZS1DdWFsLWVzLWxhLXJlc3B1ZXN0YS1hLWVzdG8tMjAyMzA1MjktMDAxNi5odG1sCgpSZWFsIEVzdGF0ZSBBbmFseXRpY3MuICgyMDIzKS4gQ0RNWCwgRXN0dWRpbyBkZSBtZXJjYWRvIGlubW9iaWxpYXJpby4gUmVhbCBFc3RhdGUgQW5hbHl0aWNzLiBodHRwczovL3Byb3BpZWRhZGVzLmNvbS9ibG9nL2luZm9ybWFjaW9uLWlubW9iaWxpYXJpYS9lc3R1ZGlvLW1lcmNhZG8taW5tb2JpbGlhcmlvLWNkbXggCg==