Introduction

L’objectif de ce projet est de pratiquer les médthodes de segmentation, explorer les données avec des transformations nécessaires, et faire les cartes avec les données spatiales.

Le data set: les données publice (donnesouvertes_logsoc_20181231.csv) de la ville de Montréal obtenue du leur portaile de données public.

Ce rapport est aussi mis en ligne, voir http://rpubs.com/jz8/LogsocCluster.

Chager les librairies

library(stats)
library(cluster)
library(ggplot2)
library(ggthemes)
library(dplyr)
library(factoextra)
library(viridis)
library(tidyr)
library(ggmap)
library(maps)

Chager les données

Liste des projets et/ou bâtiments associés aux logements sociaux. Données utilisées pour mieux connaître la desserte en logements sociaux et communautaires sur le territoire de Montréal.

logsoc_mtl <- read.csv('donnesouvertes_logsoc_20181231.csv')
dim(logsoc_mtl)
[1] 2741   20

2741 projets(ou bâtiments) 20 variables.

Préparation des données

Nom des variables dans le data set.

names(logsoc_mtl)
 [1] "numero"       "nom_projet"   "nom_rue"      "nb_log"       "hlm_familles"
 [6] "hlm_pa"       "hlm_autres"   "projettype"   "type_prog"    "an_orig"     
[11] "an_effect"    "arrond"       "villelie"     "qr"           "localisation"
[16] "aired_id"     "longitude"    "latitude"     "xnad83"       "ynad83"      

Les détails des prémieres 5 projets.

head(logsoc_mtl,5)

Dans ce data set, on intéressé notamment aux variables:projettype, arrond.

unique(logsoc_mtl$projettype)
[1] HLM  OMHM SHDM OBNL Coop
Levels: Coop HLM OBNL OMHM SHDM
unique(logsoc_mtl$arrond)
 [1] Ahuntsic-Cartierville                      
 [2] Lachine                                    
 [3] Le Sud-Ouest                               
 [4] Montréal-Nord                             
 [5] Rosemont–La Petite-Patrie                
 [6] Saint-Laurent                              
 [7] Villeray–Saint-Michel–Parc-Extension   
 [8] Ville-Marie                                
 [9] Mercier–Hochelaga-Maisonneuve            
[10]                                            
[11] Le Plateau-Mont-Royal                      
[12] LaSalle                                    
[13] Rivière-des-Prairies–Pointe-aux-Trembles
[14] Verdun                                     
[15] Côte-des-Neiges–Notre-Dame-de-Grâce    
[16] Outremont                                  
[17] Anjou                                      
[18] Saint-Léonard                             
[19] L'Île-Bizard–Sainte-Geneviève          
[20] Pierrefonds-Roxboro                        
20 Levels:  Ahuntsic-Cartierville ... Villerayâ\200“Saint-Michelâ\200“Parc-Extension
summary(logsoc_mtl)
     numero     
 Min.   :    3  
 1st Qu.:  888  
 Median : 1646  
 Mean   :18475  
 3rd Qu.:64121  
 Max.   :64913  
                
                                                                              nom_projet  
 Projet impliquant des groupes vulnérables de la population - Données obfusquées: 233  
 C.h. Village Cloverdale                                                           :  46  
 Inter-loge Centre-Sud                                                             :  38  
 Hab. de la SHAPEM                                                                 :  31  
 Hab. com. SOCAM                                                                   :  20  
 Hab. Loge accès                                                                  :  15  
 (Other)                                                                           :2358  
            nom_rue         nb_log        hlm_familles         hlm_pa       
                : 233   Min.   :  0.00   Min.   :  0.000   Min.   :  0.000  
 Barclay        :  33   1st Qu.:  6.00   1st Qu.:  0.000   1st Qu.:  0.000  
 Jeanne-Mance   :  32   Median : 10.00   Median :  0.000   Median :  0.000  
 Notre-Dame     :  29   Mean   : 22.68   Mean   :  3.598   Mean   :  3.935  
 Saint-Dominique:  28   3rd Qu.: 24.00   3rd Qu.:  0.000   3rd Qu.:  0.000  
 La Fontaine    :  26   Max.   :782.00   Max.   :210.000   Max.   :398.000  
 (Other)        :2360                                                       
   hlm_autres      projettype   type_prog       an_orig       an_effect   
 Min.   : 0.0000   Coop:1092         : 652   Min.   :1959   Min.   :1979  
 1st Qu.: 0.0000   HLM : 645   ACL   : 546   1st Qu.:1982   1st Qu.:1988  
 Median : 0.0000   OBNL: 624   Aucun :  79   Median :1988   Median :2002  
 Mean   : 0.3462   OMHM:  39   Autres:1393   Mean   :1990   Mean   :1999  
 3rd Qu.: 0.0000   SHDM: 341   LAQ   :  71   3rd Qu.:1996   3rd Qu.:2007  
 Max.   :58.0000                             Max.   :2018   Max.   :2017  
                                             NA's   :2      NA's   :2479  
                                     arrond                   villelie   
 Le Sud-Ouest                           :593                      :2699  
 Ville-Marie                            :420   Montréal-Est      :  10  
 Mercier–Hochelaga-Maisonneuve        :399   Côte-Saint-Luc    :   9  
 Le Plateau-Mont-Royal                  :208   Dollard-Des-Ormeaux:   5  
 Côte-des-Neiges–Notre-Dame-de-Grâce:194   Westmount          :   5  
 Rosemont–La Petite-Patrie            :194   Dorval             :   3  
 (Other)                                :733   (Other)            :  10  
       qr                             localisation     aired_id       
 30     : 275   Aire de diffusion           :2508   Min.   :24660011  
 47     : 238   Arrondissement / Ville liée: 233   1st Qu.:24661130  
        : 234                                       Median :24661781  
 50     : 161                                       Mean   :24661753  
 51     : 119                                       3rd Qu.:24662131  
 28     : 111                                       Max.   :24663453  
 (Other):1603                                       NA's   :233       
   longitude         latitude         xnad83           ynad83       
 Min.   :-73.95   Min.   :45.41   Min.   :269256   Min.   :5029845  
 1st Qu.:-73.62   1st Qu.:45.48   1st Qu.:295764   1st Qu.:5038128  
 Median :-73.58   Median :45.52   Median :298894   Median :5042504  
 Mean   :-73.59   Mean   :45.52   Mean   :297725   Mean   :5042540  
 3rd Qu.:-73.56   3rd Qu.:45.55   3rd Qu.:300341   3rd Qu.:5045396  
 Max.   :-73.49   Max.   :45.69   Max.   :305647   Max.   :5061670  
                                                                    

Exploration des données:

Logments Sociaux et Communautaires depuis 1960`s

library(dplyr) library(ggplot2)

year_group <- group_by(logsoc_mtl, an_orig)
logsoc_mtl_by_year <- summarise(year_group,
                          n = n())
logsoc_mtl_by_year <- logsoc_mtl_by_year[complete.cases(logsoc_mtl_by_year), ]

ggplot(data = logsoc_mtl_by_year, aes(x = an_orig, y = n))+
  geom_line(color = "#00AFBB", size = 2)+
  ylab('Nombre de logment') +
  xlab("Année ")+
  ggtitle('Logments Sociaux et Communautaires depuis 1960`s') +
  theme_bw()+ 
  geom_point(size = 1)+
  theme(plot.title = element_text(size = 14),
        axis.title = element_text(size = 12, face = "bold"))

Logments Sociaux et Communautaires selon les types

type_group <- group_by(logsoc_mtl, projettype)
logsoc_mtl_by_type <- summarise(type_group,
                          n = n())
logsoc_mtl_by_type <- logsoc_mtl_by_type[complete.cases(logsoc_mtl_by_type), ]
logsoc_mtl_by_type <- logsoc_mtl_by_type[order(logsoc_mtl_by_type$n, decreasing = TRUE), ]
logsoc_mtl_by_type
ggplot(aes(x = reorder(projettype, n), y = n), data = logsoc_mtl_by_type) +
  geom_bar(stat = 'identity', width = 0.5, fill = "#00AFBB") +
  geom_text(aes(label = n), stat = 'identity', data = logsoc_mtl_by_type, hjust = +0.5, size = 3.5) +
  xlab('Types de Projet') +
  ylab("Nombre de logments ") +
  ggtitle('Projet ou bâtiment selon le type de propriétaire et dencadrement général') +
  theme_bw() +
  theme(plot.title = element_text(size = 14),
        axis.title = element_text(size = 12, face = "bold"))

year_type_group <- group_by(logsoc_mtl, an_orig, projettype)
year_type <- summarise(year_type_group, n=n())
year_type <- year_type[complete.cases(year_type), ]

ggplot(aes(x=an_orig, y=n, color=projettype), data = year_type) + 
  geom_line(size=1.2) + 
  ggtitle("Logments Sociaux et Communautaires selon les types depuis 1960's") +
  ylab("Nombre d'occurrences ") +
  xlab('Année') +
  theme_bw() +
  theme(plot.title = element_text(size = 14),
        axis.title = element_text(size = 12, face = "bold"))

Logments Sociaux et Communautaires selon les Arrondissements

arrond_group <- group_by(logsoc_mtl, arrond)
logsoc_mtl_by_arrond <- summarise(arrond_group,
                          n = n())
logsoc_mtl_by_arrond <- logsoc_mtl_by_arrond[complete.cases(logsoc_mtl_by_arrond), ]
logsoc_mtl_by_arrond <- logsoc_mtl_by_arrond[order(logsoc_mtl_by_arrond$n, decreasing = TRUE), ]
logsoc_mtl_by_arrond
NA
ggplot(aes(x = reorder(arrond, n), y = n), data = logsoc_mtl_by_arrond) +
  geom_bar(stat = 'identity', width = 0.5, fill = "#FF6666") +
  geom_text(aes(label = n), stat = 'identity', data = logsoc_mtl_by_arrond, hjust = -0.1, size = 3.5) +
  coord_flip() +
  xlab('Arrondissements') +
  ylab("Nombre de logments ") +
  ggtitle('Logments Sociaux et Communautaires selon les Arrondissements') +
  theme_bw() +
  theme(plot.title = element_text(size = 14),
        axis.title = element_text(size = 12, face = "bold"))

K-Mean Clustering

Principe de segmentation: Maximiser l’homogénéité à l’intérieur de chaque groupe, maximiser l’hétérogénéité entre les groupes.

On va rechercher une meilleure façon de segmenter les arrondissements en utilsant la variable projettype.

library(tidyr)


by_groups <- group_by(logsoc_mtl, projettype, arrond)
groups <- summarise(by_groups, n=n())
groups <- groups[c("arrond", "projettype", "n")]
groups_wide <- spread(groups, key = projettype, value = n)
groups

groups_wide[is.na(groups_wide)] = 0
groups_wide
z <- groups_wide[, -c(1,1)]
z

Standardiser les donées à l’échelle pour la comparaison.

m <- apply(z, 2, mean)
s <- apply(z, 2, sd)
z <- scale(z, m, s)
z
             Coop         HLM        OBNL        OMHM        SHDM
 [1,] -0.74477086 -0.52222997 -0.25118096  0.02069108 -0.55534916
 [2,] -0.41619548 -0.02933876  0.08576911 -0.39313051 -0.03420039
 [3,] -0.79171019 -0.66305603 -0.92508110 -0.80695210 -0.55534916
 [4,] -0.16585233  0.08801629  0.72903742  0.02069108  1.30124335
 [5,] -0.85429598 -0.73346906 -0.95571293 -0.80695210 -0.55534916
 [6,] -0.52572060 -0.40487492 -0.61876286  0.02069108 -0.55534916
 [7,] -0.79171019 -0.52222997 -0.86381745 -0.80695210 -0.55534916
 [8,]  0.66340933  0.48702346  0.20829641 -0.39313051  0.06351501
 [9,]  2.35322556  3.13924758  2.26062865  2.50362063  2.99497687
[10,]  2.46275068  1.09726971  1.24977844  2.50362063  0.35666119
[11,] -0.38490258 -0.68652704  0.36145553  0.84833426 -0.55534916
[12,] -0.83864953 -0.70999805 -0.95571293 -0.80695210 -0.55534916
[13,] -0.02503431 -0.73346906 -0.25118096 -0.80695210 -0.55534916
[14,] -0.24408457 -0.38140391 -0.52686738  0.43451267 -0.55534916
[15,]  0.50694487  0.20537134  0.57587830  0.84833426 -0.16448758
[16,] -0.82300309 -0.66305603 -0.92508110 -0.80695210 -0.55534916
[17,] -0.77606375 -0.63958502 -0.71065833 -0.39313051 -0.55534916
[18,]  0.02190503 -0.63958502 -0.77192198 -0.39313051 -0.55534916
[19,]  1.21103496  1.59016092  2.26062865 -0.39313051  2.11553831
[20,]  0.16272304  0.72173356  0.02450546 -0.39313051  0.03094321
attr(,"scaled:center")
         Coop           HLM          OBNL          OMHM          SHDM 
 5.906869e-18  3.979022e-18  8.322353e-18 -1.459607e-17  6.422797e-18 
attr(,"scaled:scale")
Coop  HLM OBNL OMHM SHDM 
   1    1    1    1    1 

Déterminer le nombre de clusters k

Visualisation la matrice de distance

# 
fviz_dist(get_dist(z), gradient = list(low = "#00AFBB", mid = "#FFFFFF", high = "#FF6666"))

Calculer K en utilisant le sommes squares dans les groupes

wss <- (nrow(z)-1) * sum(apply(z, 2, var))
for (i in 2:10) wss[i] <- sum(kmeans(z, centers=i)$withiness)
plot(1:10, wss, type='b', xlab='Nombre de Clusters', ylab='Within groups sum of squares')

Le graphique indique le meilleur clustering: k=2.

Calculer K en utilisant le méthode Elbow

# 
fviz_nbclust(z, kmeans, method = "wss") +
    geom_vline(xintercept = 4, linetype = 2)+
  labs(subtitle = "Elbow method")

Le graphique indique le meilleur clustering: k=2,3,4.

Calculer K en utilisant le méthode Silhouette


fviz_nbclust(z, kmeans, method = "silhouette")+
  labs(subtitle = "Silhouette method")

Le graphique indique le meilleur clustering: k=2.

Calculer K en utilisant le méthode Gap statistic

# nboot = 50 to keep the function speedy. 
# recommended value: nboot= 500 for your analysis.
# Use verbose = FALSE to hide computing progression.
set.seed(123)
fviz_nbclust(z, kmeans, nstart = 25,  method = "gap_stat", nboot = 50)+
  labs(subtitle = "Gap statistic method")
Clustering k = 1,2,..., K.max (= 10): .. done
Bootstrapping, b = 1,2,..., B (= 50)  [one "." per sample]:
.................................................. 50 

Le graphique indique le meilleur clustering: k=1. On n’a pas besoin de segmentation.

Donc, selon les graphiques au-dessus, On choit k=2.

Calculer K-means clustering

set.seed(123)
km.res <- kmeans(z, 2, iter.max = 100, nstart = 25)
print(km.res)
K-means clustering with 2 clusters of sizes 17, 3

Cluster means:
        Coop        HLM       OBNL       OMHM       SHDM
1 -0.3545301 -0.3427458 -0.3394727 -0.2714183 -0.3215986
2  2.0090037  1.9422261  1.9236786  1.5380369  1.8223921

Clustering vector:
 [1] 1 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 1 1 2 1

Within cluster sum of squares by cluster:
[1] 21.19336 13.11647
 (between_SS / total_SS =  63.9 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
[6] "betweenss"    "size"         "iter"         "ifault"      

Le deuxième groupe comprend 3 arrodissements:Le Sud-Ouest,Mercier–Hochelaga-Maisonneuve,Ville-Marie. Le premier groupe compte les autres 16 arrodissements et une groupe de projets qui n’indque pas l’arrondissement.

Le graphique suivant indique les 2 clusters en utilisant le libraire(factoextra).

head(km.res$cluster,10)
 [1] 1 1 1 1 1 1 1 1 2 2
fviz_cluster(km.res, data = z, geom = "point",
             stand = FALSE) + theme_bw()

Le graphique suivant indique les 2 clusters en utilisant le libraire(cluster).

z1 <- data.frame(z, km.res$cluster)
clusplot(z1, km.res$cluster, color=TRUE, shade=F, labels=0, lines=0, main='Analyse de cluster k-Means')

PAM (k-methoid) Clustering avec le libaire(stat)

PAM(Partitioning Around Medoids).

pam.res <- pam(z,2)
print(pam.res)
Medoids:
     ID       Coop        HLM       OBNL       OMHM       SHDM
[1,] 17 -0.7760637 -0.6395850 -0.7106583 -0.3931305 -0.5553492
[2,] 15  0.5069449  0.2053713  0.5758783  0.8483343 -0.1644876
Clustering vector:
 [1] 1 1 1 2 1 1 1 2 2 2 2 1 1 1 2 1 1 1 2 2
Objective function:
   build     swap 
1.269655 1.256545 

Available components:
 [1] "medoids"    "id.med"     "clustering" "objective"  "isolation"  "clusinfo"  
 [7] "silinfo"    "diss"       "call"       "data"      

Le deuxième groupe comprend 8 arrodissements: Côte-des-Neiges–Notre-Dame-de-Grâce, Le Plateau-Mont-Royal, Le Sud-Ouest, Mercier–Hochelaga-Maisonneuve, Montréal-Nord, Rosemont–La Petite-Patrie, Ville-Marie, Villeray–Saint-Michel–Parc-Extensio; Le premier groupe compte les autres 11 arrodissements et une groupe de projets qui n’indque pas l’arrondissement.

Le résultat est différent que le sien du K-means parce que PAM utilise un élément (medoid) pour pésenter son groupe, pas le vraie means du groupe.

clusplot(z, pam.res$cluster, color=TRUE, shade=F, labels=0, lines=0, main='Analyse de cluster PAM')

Clara Clustering avec le libaire(stat)

CLARA (Clustering LARge Applications):Tire des échantillons successifs de la base et applique PAM à chaque échantillon, retourne le meilleur partitionnement obtenu.

clara.res <- clara(z, 2, metric = "euclidean", samples=50)
print(clara.res)
Call:    clara(x = z, k = 2, metric = "euclidean", samples = 50) 
Medoids:
           Coop        HLM       OBNL       OMHM       SHDM
[1,] -0.7760637 -0.6395850 -0.7106583 -0.3931305 -0.5553492
[2,]  0.5069449  0.2053713  0.5758783  0.8483343 -0.1644876
Objective function:  1.256545
Clustering vector:   int [1:20] 1 1 1 2 1 1 1 2 2 2 2 1 1 1 2 1 1 1 ...
Cluster sizes:           12 8 
Best sample:
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20

Available components:
 [1] "sample"     "medoids"    "i.med"      "clustering" "objective"  "clusinfo"  
 [7] "diss"       "call"       "silinfo"    "data"      
clusplot(z, clara.res$cluster, color=TRUE, shade=F, labels=0, lines=0, main='Analyse de cluster Clara')

Le résultat du Clara est même que le sien du PAM parce que Clara utilise PAM pour trouver le meilleur clustering.

Hierarchical Clustering avec le libaire(stat)

z_df <- data.frame(z)

hc <- hclust(dist(z_df))
hc

Call:
hclust(d = dist(z_df))

Cluster method   : complete 
Distance         : euclidean 
Number of objects: 20 
plot(hc, labels = groups_wide$arrond, main='Cluster Dendrogram', cex=0.65)

counts = sapply(2:5,function(ncl)table(cutree(hc,ncl)))
names(counts) = 2:5
counts
$`2`

 1  2 
17  3 

$`3`

 1  2  3 
17  2  1 

$`4`

 1  2  3  4 
17  1  1  1 

$`5`

 1  2  3  4  5 
12  5  1  1  1 

Le counts indique le meilleur clustering, 2 groupes, le résultat est le même que le sien du K-means.

member <- cutree(hc, 2)
aggregate(z, list(member), mean)

Dans le groupe 1 (16 arrondissements + un groupe sans nom), tous les type de projet sont négatifs; dans le groupe 2 (3 arrondissements), tous les types de projet sont positifs.

plot(silhouette(cutree(hc, 2), dist(z_df)))

Le graphique de la silhouette indique que nous n’avons vraiment pas besoin du troisième groupe.

Faire une carte des Logments Sociaux et Communautaires de Montréal

On va utiliser ggmap pour produire une carte qui contient les logments sociaux et communautaires de Montréal.

lat <- logsoc_mtl$latitude
lon <- logsoc_mtl$longitude
projet_type <- logsoc_mtl$projettype
to_map <- data.frame(projet_type, lat, lon)
colnames(to_map) <- c('projet_type', 'lat', 'lon')
sbbox <- make_bbox(lon = logsoc_mtl$longitude, lat = logsoc_mtl$latitude, f = 0.01)
my_map <- get_map(location = sbbox, maptype = "roadmap", scale = 2, color="bw", zoom = 10)
ggmap(my_map) +
  geom_point(data=to_map, aes(x = lon, y = lat, color = "#FF6666"), 
             size = 1, alpha = 0.5) +
  xlab('Longitude') +
  ylab('Latitude') +
  ggtitle('Logments Sociaux et Communautaires de Montréal ') +
  guides(color=FALSE)

Dans cette carte on voit une grande concentration dans les arrondissements: ville-marie, Sud-Ouest et Mercier- Hochelata-Maisonneuve.

ggmap(my_map) +
  geom_point(data=to_map, aes(x = lon, y = lat, color = "#FF6666"), 
             size = 1, alpha = 0.5) +
  xlab('Longitude') +
  ylab('Latitude') +
  ggtitle('Type de projet des Logments Sociaux et Communautaires de Montréal ') +
  guides(color=FALSE) +
  facet_wrap(~ projet_type, nrow = 2)

Dans ces cartes on voit les concentrationa dans les arrondissements: ville-marie, Sud-Ouest et Mercier- Hochelata-Maisonneuve. Le nombre de OMHM est le minimum et le sien de Coop est le maximum.

Conclusion

Dans ce projet on a analysé les données des logments sociaux et communitaires de Montréal et on a segementé les arrondissements de Montréal selon les types de projets. On a calculé le meuiller K = 2 en utilisant des méthodes Elbow, Silhouette, Gap statistic etc. Et puis, on a utilisé les algorithems K-means, PAM, Clara, et CHA pour segmenter les arrodissements dans 2 groups: Groupe 2(ville-marie, Sud-Ouest et Mercier- Hochelata-Maisonneuve) et Groupe 1(les autres arrondissements). En fin, on a fait des cartes pour montrer les logements sociaux et communitaires de Montréal en utilisant les données spatiales dans le data set.

LS0tDQp0aXRsZTogIklORDYyMTIgUHJvamV0OiBBbmFseXNlIGV0IFNlZ21lbnRhdGlvbiBkZXMgTG9nZW1lbnRzIHNvY2lhdXggZXQgY29tbXVuYXV0YWlyZXMgZGFucyBsJ2FnZ2xvbcOpcmF0aW9uIGRlIE1vbnRyw6lhbCAiDQphdXRob3I6ICJZaWZlaSBaaGFuZyINCmRhdGU6ICIxNy8wNC8yMDIwIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogeWVzDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgaGlnaGxpZ2h0OiB0ZXh0bWF0ZQ0KICAgIHRoZW1lOiBzcGFjZWxhYg0KICAgIHRvYzogeWVzDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICB3b3JkX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQphbHdheXNfYWxsb3dfaHRtbDogeWVzDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KDQojIEludHJvZHVjdGlvbg0KDQpMJ29iamVjdGlmIGRlIGNlIHByb2pldCBlc3QgZGUgcHJhdGlxdWVyIGxlcyBtw6lkdGhvZGVzIGRlIHNlZ21lbnRhdGlvbiwgZXhwbG9yZXIgbGVzIGRvbm7DqWVzIGF2ZWMgZGVzIHRyYW5zZm9ybWF0aW9ucyBuw6ljZXNzYWlyZXMsIGV0IGZhaXJlIGxlcyBjYXJ0ZXMgYXZlYyBsZXMgZG9ubsOpZXMgc3BhdGlhbGVzLg0KDQpMZSBkYXRhIHNldDogbGVzIGRvbm7DqWVzIHB1YmxpY2UgKFtkb25uZXNvdXZlcnRlc19sb2dzb2NfMjAxODEyMzEuY3N2XShodHRwOi8vZG9ubmVlcy52aWxsZS5tb250cmVhbC5xYy5jYS9kYXRhc2V0L2QyNmZhZDBmLTJlYWUtNDRkNS04OGEwLTJiYzY5OWZkMjU5Mi9yZXNvdXJjZS9iYjM4MGZhYS0xYmE1LTQ1OGItYjUyMC05ZTIyODdiY2MwN2YvZG93bmxvYWQvZG9ubmVzb3V2ZXJ0ZXNfbG9nc29jXzIwMTgxMjMxLmNzdikpIGRlIGxhIHZpbGxlIGRlIE1vbnRyw6lhbCBvYnRlbnVlIGR1IGxldXIgcG9ydGFpbGUgZGUgZG9ubsOpZXMgcHVibGljLg0KDQpDZSByYXBwb3J0IGVzdCBhdXNzaSBtaXMgZW4gbGlnbmUsIHZvaXIgPGh0dHA6Ly9ycHVicy5jb20vano4L0xvZ3NvY0NsdXN0ZXI+Lg0KDQojIENoYWdlciBsZXMgbGlicmFpcmllcw0KYGBge3J9DQpsaWJyYXJ5KHN0YXRzKQ0KbGlicmFyeShjbHVzdGVyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShnZ3RoZW1lcykNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGZhY3RvZXh0cmEpDQpsaWJyYXJ5KHZpcmlkaXMpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShnZ21hcCkNCmxpYnJhcnkobWFwcykNCmBgYA0KDQojIENoYWdlciBsZXMgZG9ubsOpZXMNCkxpc3RlIGRlcyBwcm9qZXRzIGV0L291IGLDonRpbWVudHMgYXNzb2Npw6lzIGF1eCBsb2dlbWVudHMgc29jaWF1eC4gRG9ubsOpZXMgdXRpbGlzw6llcyBwb3VyIG1pZXV4IGNvbm5hw650cmUgbGEgZGVzc2VydGUgZW4gbG9nZW1lbnRzIHNvY2lhdXggZXQgY29tbXVuYXV0YWlyZXMgc3VyIGxlIHRlcnJpdG9pcmUgZGUgTW9udHLDqWFsLg0KYGBge3J9DQpsb2dzb2NfbXRsIDwtIHJlYWQuY3N2KCdkb25uZXNvdXZlcnRlc19sb2dzb2NfMjAxODEyMzEuY3N2JykNCmRpbShsb2dzb2NfbXRsKQ0KYGBgDQoyNzQxIHByb2pldHMob3UgYsOidGltZW50cykgMjAgdmFyaWFibGVzLg0KDQoNCiMgUHLDqXBhcmF0aW9uIGRlcyBkb25uw6llcyANCk5vbSBkZXMgdmFyaWFibGVzIGRhbnMgbGUgZGF0YSBzZXQuDQpgYGB7cn0NCm5hbWVzKGxvZ3NvY19tdGwpDQoNCmBgYA0KTGVzIGTDqXRhaWxzIGRlcyBwcsOpbWllcmVzIDUgcHJvamV0cy4NCmBgYHtyfQ0KaGVhZChsb2dzb2NfbXRsLDUpDQpgYGANCg0KRGFucyBjZSBkYXRhIHNldCwgb24gaW50w6lyZXNzw6kgbm90YW1tZW50IGF1eCB2YXJpYWJsZXM6cHJvamV0dHlwZSwgYXJyb25kLg0KYGBge3J9DQp1bmlxdWUobG9nc29jX210bCRwcm9qZXR0eXBlKQ0KYGBgDQpgYGB7cn0NCnVuaXF1ZShsb2dzb2NfbXRsJGFycm9uZCkNCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnkobG9nc29jX210bCkNCmBgYA0KDQoNCiMgRXhwbG9yYXRpb24gZGVzIGRvbm7DqWVzOiANCg0KIyMgTG9nbWVudHMgU29jaWF1eCBldCBDb21tdW5hdXRhaXJlcyBkZXB1aXMgMTk2MGBzDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KYGBge3J9DQp5ZWFyX2dyb3VwIDwtIGdyb3VwX2J5KGxvZ3NvY19tdGwsIGFuX29yaWcpDQpsb2dzb2NfbXRsX2J5X3llYXIgPC0gc3VtbWFyaXNlKHllYXJfZ3JvdXAsDQogICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSBuKCkpDQpsb2dzb2NfbXRsX2J5X3llYXIgPC0gbG9nc29jX210bF9ieV95ZWFyW2NvbXBsZXRlLmNhc2VzKGxvZ3NvY19tdGxfYnlfeWVhciksIF0NCg0KZ2dwbG90KGRhdGEgPSBsb2dzb2NfbXRsX2J5X3llYXIsIGFlcyh4ID0gYW5fb3JpZywgeSA9IG4pKSsNCiAgZ2VvbV9saW5lKGNvbG9yID0gIiMwMEFGQkIiLCBzaXplID0gMikrDQogIHlsYWIoJ05vbWJyZSBkZSBsb2dtZW50JykgKw0KICB4bGFiKCJBbm7DqWUgIikrDQogIGdndGl0bGUoJ0xvZ21lbnRzIFNvY2lhdXggZXQgQ29tbXVuYXV0YWlyZXMgZGVwdWlzIDE5NjBgcycpICsNCiAgdGhlbWVfYncoKSsgDQogIGdlb21fcG9pbnQoc2l6ZSA9IDEpKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksDQogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSkNCmBgYA0KDQojIyBMb2dtZW50cyBTb2NpYXV4IGV0IENvbW11bmF1dGFpcmVzIHNlbG9uIGxlcyB0eXBlcw0KYGBge3J9DQp0eXBlX2dyb3VwIDwtIGdyb3VwX2J5KGxvZ3NvY19tdGwsIHByb2pldHR5cGUpDQpsb2dzb2NfbXRsX2J5X3R5cGUgPC0gc3VtbWFyaXNlKHR5cGVfZ3JvdXAsDQogICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSBuKCkpDQpsb2dzb2NfbXRsX2J5X3R5cGUgPC0gbG9nc29jX210bF9ieV90eXBlW2NvbXBsZXRlLmNhc2VzKGxvZ3NvY19tdGxfYnlfdHlwZSksIF0NCmxvZ3NvY19tdGxfYnlfdHlwZSA8LSBsb2dzb2NfbXRsX2J5X3R5cGVbb3JkZXIobG9nc29jX210bF9ieV90eXBlJG4sIGRlY3JlYXNpbmcgPSBUUlVFKSwgXQ0KbG9nc29jX210bF9ieV90eXBlDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoYWVzKHggPSByZW9yZGVyKHByb2pldHR5cGUsIG4pLCB5ID0gbiksIGRhdGEgPSBsb2dzb2NfbXRsX2J5X3R5cGUpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsIHdpZHRoID0gMC41LCBmaWxsID0gIiMwMEFGQkIiKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBuKSwgc3RhdCA9ICdpZGVudGl0eScsIGRhdGEgPSBsb2dzb2NfbXRsX2J5X3R5cGUsIGhqdXN0ID0gKzAuNSwgc2l6ZSA9IDMuNSkgKw0KICB4bGFiKCdUeXBlcyBkZSBQcm9qZXQnKSArDQogIHlsYWIoIk5vbWJyZSBkZSBsb2dtZW50cyAiKSArDQogIGdndGl0bGUoJ1Byb2pldCBvdSBiw6J0aW1lbnQgc2Vsb24gbGUgdHlwZSBkZSBwcm9wcmnDqXRhaXJlIGV0IGRlbmNhZHJlbWVudCBnw6luw6lyYWwnKSArDQogIHRoZW1lX2J3KCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksDQogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSkNCmBgYA0KDQoNCmBgYHtyfQ0KeWVhcl90eXBlX2dyb3VwIDwtIGdyb3VwX2J5KGxvZ3NvY19tdGwsIGFuX29yaWcsIHByb2pldHR5cGUpDQp5ZWFyX3R5cGUgPC0gc3VtbWFyaXNlKHllYXJfdHlwZV9ncm91cCwgbj1uKCkpDQp5ZWFyX3R5cGUgPC0geWVhcl90eXBlW2NvbXBsZXRlLmNhc2VzKHllYXJfdHlwZSksIF0NCg0KZ2dwbG90KGFlcyh4PWFuX29yaWcsIHk9biwgY29sb3I9cHJvamV0dHlwZSksIGRhdGEgPSB5ZWFyX3R5cGUpICsgDQogIGdlb21fbGluZShzaXplPTEuMikgKyANCiAgZ2d0aXRsZSgiTG9nbWVudHMgU29jaWF1eCBldCBDb21tdW5hdXRhaXJlcyBzZWxvbiBsZXMgdHlwZXMgZGVwdWlzIDE5NjAncyIpICsNCiAgeWxhYigiTm9tYnJlIGQnb2NjdXJyZW5jZXMgIikgKw0KICB4bGFiKCdBbm7DqWUnKSArDQogIHRoZW1lX2J3KCkgKw0KICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxNCksDQogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSkNCmBgYA0KDQoNCiMjIExvZ21lbnRzIFNvY2lhdXggZXQgQ29tbXVuYXV0YWlyZXMgc2Vsb24gbGVzIEFycm9uZGlzc2VtZW50cw0KDQpgYGB7cn0NCmFycm9uZF9ncm91cCA8LSBncm91cF9ieShsb2dzb2NfbXRsLCBhcnJvbmQpDQpsb2dzb2NfbXRsX2J5X2Fycm9uZCA8LSBzdW1tYXJpc2UoYXJyb25kX2dyb3VwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gbigpKQ0KbG9nc29jX210bF9ieV9hcnJvbmQgPC0gbG9nc29jX210bF9ieV9hcnJvbmRbY29tcGxldGUuY2FzZXMobG9nc29jX210bF9ieV9hcnJvbmQpLCBdDQpsb2dzb2NfbXRsX2J5X2Fycm9uZCA8LSBsb2dzb2NfbXRsX2J5X2Fycm9uZFtvcmRlcihsb2dzb2NfbXRsX2J5X2Fycm9uZCRuLCBkZWNyZWFzaW5nID0gVFJVRSksIF0NCmxvZ3NvY19tdGxfYnlfYXJyb25kDQoNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChhZXMoeCA9IHJlb3JkZXIoYXJyb25kLCBuKSwgeSA9IG4pLCBkYXRhID0gbG9nc29jX210bF9ieV9hcnJvbmQpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsIHdpZHRoID0gMC41LCBmaWxsID0gIiNGRjY2NjYiKSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSBuKSwgc3RhdCA9ICdpZGVudGl0eScsIGRhdGEgPSBsb2dzb2NfbXRsX2J5X2Fycm9uZCwgaGp1c3QgPSAtMC4xLCBzaXplID0gMy41KSArDQogIGNvb3JkX2ZsaXAoKSArDQogIHhsYWIoJ0Fycm9uZGlzc2VtZW50cycpICsNCiAgeWxhYigiTm9tYnJlIGRlIGxvZ21lbnRzICIpICsNCiAgZ2d0aXRsZSgnTG9nbWVudHMgU29jaWF1eCBldCBDb21tdW5hdXRhaXJlcyBzZWxvbiBsZXMgQXJyb25kaXNzZW1lbnRzJykgKw0KICB0aGVtZV9idygpICsNCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTQpLA0KICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIikpDQpgYGANCg0KIyBLLU1lYW4gQ2x1c3RlcmluZw0KUHJpbmNpcGUgZGUgc2VnbWVudGF0aW9uOiBNYXhpbWlzZXIgbOKAmWhvbW9nw6luw6lpdMOpIMOgIGzigJlpbnTDqXJpZXVyIGRlIGNoYXF1ZQ0KZ3JvdXBlLCBtYXhpbWlzZXIgbOKAmWjDqXTDqXJvZ8OpbsOpaXTDqSBlbnRyZSBsZXMgZ3JvdXBlcy4NCg0KT24gdmEgcmVjaGVyY2hlciB1bmUgbWVpbGxldXJlIGZhw6dvbiBkZSBzZWdtZW50ZXIgbGVzIGFycm9uZGlzc2VtZW50cyBlbiB1dGlsc2FudCBsYSB2YXJpYWJsZSBwcm9qZXR0eXBlLg0KDQpsaWJyYXJ5KHRpZHlyKQ0KYGBge3J9DQoNCmJ5X2dyb3VwcyA8LSBncm91cF9ieShsb2dzb2NfbXRsLCBwcm9qZXR0eXBlLCBhcnJvbmQpDQpncm91cHMgPC0gc3VtbWFyaXNlKGJ5X2dyb3Vwcywgbj1uKCkpDQpncm91cHMgPC0gZ3JvdXBzW2MoImFycm9uZCIsICJwcm9qZXR0eXBlIiwgIm4iKV0NCmdyb3Vwc193aWRlIDwtIHNwcmVhZChncm91cHMsIGtleSA9IHByb2pldHR5cGUsIHZhbHVlID0gbikNCmdyb3Vwcw0KDQpncm91cHNfd2lkZVtpcy5uYShncm91cHNfd2lkZSldID0gMA0KZ3JvdXBzX3dpZGUNCmBgYA0KDQpgYGB7cn0NCnogPC0gZ3JvdXBzX3dpZGVbLCAtYygxLDEpXQ0Keg0KYGBgDQpTdGFuZGFyZGlzZXIgbGVzIGRvbsOpZXMgw6AgbOKAmcOpY2hlbGxlIHBvdXIgbGEgY29tcGFyYWlzb24uDQpgYGB7cn0NCm0gPC0gYXBwbHkoeiwgMiwgbWVhbikNCnMgPC0gYXBwbHkoeiwgMiwgc2QpDQp6IDwtIHNjYWxlKHosIG0sIHMpDQp6DQpgYGANCg0KIyMgRMOpdGVybWluZXIgbGUgbm9tYnJlIGRlIGNsdXN0ZXJzIGsNCg0KIyMjIFZpc3VhbGlzYXRpb24gbGEgbWF0cmljZSBkZSBkaXN0YW5jZQ0KYGBge3J9DQojIA0KZnZpel9kaXN0KGdldF9kaXN0KHopLCBncmFkaWVudCA9IGxpc3QobG93ID0gIiMwMEFGQkIiLCBtaWQgPSAiI0ZGRkZGRiIsIGhpZ2ggPSAiI0ZGNjY2NiIpKQ0KYGBgDQojIyMgQ2FsY3VsZXIgSyBlbiB1dGlsaXNhbnQgbGUgc29tbWVzIHNxdWFyZXMgZGFucyBsZXMgZ3JvdXBlcw0KYGBge3J9DQp3c3MgPC0gKG5yb3coeiktMSkgKiBzdW0oYXBwbHkoeiwgMiwgdmFyKSkNCmZvciAoaSBpbiAyOjEwKSB3c3NbaV0gPC0gc3VtKGttZWFucyh6LCBjZW50ZXJzPWkpJHdpdGhpbmVzcykNCnBsb3QoMToxMCwgd3NzLCB0eXBlPSdiJywgeGxhYj0nTm9tYnJlIGRlIENsdXN0ZXJzJywgeWxhYj0nV2l0aGluIGdyb3VwcyBzdW0gb2Ygc3F1YXJlcycpDQpgYGANCkxlIGdyYXBoaXF1ZSBpbmRpcXVlIGxlIG1laWxsZXVyIGNsdXN0ZXJpbmc6IGs9Mi4NCg0KDQoNCiMjIyBDYWxjdWxlciBLIGVuIHV0aWxpc2FudCBsZSBtw6l0aG9kZSBFbGJvdyANCmBgYHtyfQ0KIyANCmZ2aXpfbmJjbHVzdCh6LCBrbWVhbnMsIG1ldGhvZCA9ICJ3c3MiKSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNCwgbGluZXR5cGUgPSAyKSsNCiAgbGFicyhzdWJ0aXRsZSA9ICJFbGJvdyBtZXRob2QiKQ0KDQpgYGANCkxlIGdyYXBoaXF1ZSBpbmRpcXVlIGxlIG1laWxsZXVyIGNsdXN0ZXJpbmc6IGs9MiwzLDQuDQoNCiMjIyBDYWxjdWxlciBLIGVuIHV0aWxpc2FudCBsZSBtw6l0aG9kZSBTaWxob3VldHRlDQoNCmBgYHtyfQ0KDQpmdml6X25iY2x1c3Qoeiwga21lYW5zLCBtZXRob2QgPSAic2lsaG91ZXR0ZSIpKw0KICBsYWJzKHN1YnRpdGxlID0gIlNpbGhvdWV0dGUgbWV0aG9kIikNCg0KYGBgDQpMZSBncmFwaGlxdWUgaW5kaXF1ZSBsZSBtZWlsbGV1ciBjbHVzdGVyaW5nOiBrPTIuDQoNCiMjIyBDYWxjdWxlciBLIGVuIHV0aWxpc2FudCBsZSBtw6l0aG9kZSBHYXAgc3RhdGlzdGljDQpgYGB7cn0NCiMgbmJvb3QgPSA1MCB0byBrZWVwIHRoZSBmdW5jdGlvbiBzcGVlZHkuIA0KIyByZWNvbW1lbmRlZCB2YWx1ZTogbmJvb3Q9IDUwMCBmb3IgeW91ciBhbmFseXNpcy4NCiMgVXNlIHZlcmJvc2UgPSBGQUxTRSB0byBoaWRlIGNvbXB1dGluZyBwcm9ncmVzc2lvbi4NCnNldC5zZWVkKDEyMykNCmZ2aXpfbmJjbHVzdCh6LCBrbWVhbnMsIG5zdGFydCA9IDI1LCAgbWV0aG9kID0gImdhcF9zdGF0IiwgbmJvb3QgPSA1MCkrDQogIGxhYnMoc3VidGl0bGUgPSAiR2FwIHN0YXRpc3RpYyBtZXRob2QiKQ0KYGBgDQpMZSBncmFwaGlxdWUgaW5kaXF1ZSBsZSBtZWlsbGV1ciBjbHVzdGVyaW5nOiBrPTEuIE9uIG4nYSBwYXMgYmVzb2luIGRlIHNlZ21lbnRhdGlvbi4NCg0KDQpEb25jLCBzZWxvbiBsZXMgZ3JhcGhpcXVlcyBhdS1kZXNzdXMsIE9uIGNob2l0IGs9Mi4NCg0KDQojIyBDYWxjdWxlciBLLW1lYW5zIGNsdXN0ZXJpbmcNCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQprbS5yZXMgPC0ga21lYW5zKHosIDIsIGl0ZXIubWF4ID0gMTAwLCBuc3RhcnQgPSAyNSkNCnByaW50KGttLnJlcykNCmBgYA0KDQpMZSBkZXV4acOobWUgZ3JvdXBlIGNvbXByZW5kIDMgYXJyb2Rpc3NlbWVudHM6TGUgU3VkLU91ZXN0LE1lcmNpZXLigJNIb2NoZWxhZ2EtTWFpc29ubmV1dmUsVmlsbGUtTWFyaWUuDQpMZSBwcmVtaWVyIGdyb3VwZSBjb21wdGUgbGVzIGF1dHJlcyAxNiBhcnJvZGlzc2VtZW50cyBldCB1bmUgZ3JvdXBlIGRlIHByb2pldHMgcXVpIG4naW5kcXVlIHBhcyBsJ2Fycm9uZGlzc2VtZW50Lg0KDQpMZSBncmFwaGlxdWUgc3VpdmFudCBpbmRpcXVlIGxlcyAyIGNsdXN0ZXJzIGVuIHV0aWxpc2FudCBsZSBsaWJyYWlyZShmYWN0b2V4dHJhKS4NCmBgYHtyfQ0KaGVhZChrbS5yZXMkY2x1c3RlciwxMCkNCmZ2aXpfY2x1c3RlcihrbS5yZXMsIGRhdGEgPSB6LCBnZW9tID0gInBvaW50IiwNCiAgICAgICAgICAgICBzdGFuZCA9IEZBTFNFKSArIHRoZW1lX2J3KCkNCg0KYGBgDQoNCkxlIGdyYXBoaXF1ZSBzdWl2YW50IGluZGlxdWUgbGVzIDIgY2x1c3RlcnMgZW4gdXRpbGlzYW50IGxlIGxpYnJhaXJlKGNsdXN0ZXIpLg0KYGBge3J9DQp6MSA8LSBkYXRhLmZyYW1lKHosIGttLnJlcyRjbHVzdGVyKQ0KY2x1c3Bsb3QoejEsIGttLnJlcyRjbHVzdGVyLCBjb2xvcj1UUlVFLCBzaGFkZT1GLCBsYWJlbHM9MCwgbGluZXM9MCwgbWFpbj0nQW5hbHlzZSBkZSBjbHVzdGVyIGstTWVhbnMnKQ0KYGBgDQoNCg0KIyBQQU0gKGstbWV0aG9pZCkgIENsdXN0ZXJpbmcgYXZlYyBsZSBsaWJhaXJlKHN0YXQpDQpQQU0oUGFydGl0aW9uaW5nIEFyb3VuZCBNZWRvaWRzKS4NCmBgYHtyfQ0KcGFtLnJlcyA8LSBwYW0oeiwyKQ0KcHJpbnQocGFtLnJlcykNCmBgYA0KTGUgZGV1eGnDqG1lIGdyb3VwZSBjb21wcmVuZCA4IGFycm9kaXNzZW1lbnRzOg0KQ8O0dGUtZGVzLU5laWdlc+KAk05vdHJlLURhbWUtZGUtR3LDomNlLA0KTGUgUGxhdGVhdS1Nb250LVJveWFsLA0KTGUgU3VkLU91ZXN0LA0KTWVyY2llcuKAk0hvY2hlbGFnYS1NYWlzb25uZXV2ZSwNCk1vbnRyw6lhbC1Ob3JkLA0KUm9zZW1vbnTigJNMYSBQZXRpdGUtUGF0cmllLA0KVmlsbGUtTWFyaWUsDQpWaWxsZXJheeKAk1NhaW50LU1pY2hlbOKAk1BhcmMtRXh0ZW5zaW87DQpMZSBwcmVtaWVyIGdyb3VwZSBjb21wdGUgbGVzIGF1dHJlcyAxMSBhcnJvZGlzc2VtZW50cyBldCB1bmUgZ3JvdXBlIGRlIHByb2pldHMgcXVpIG4naW5kcXVlIHBhcyBsJ2Fycm9uZGlzc2VtZW50Lg0KDQpMZSByw6lzdWx0YXQgZXN0IGRpZmbDqXJlbnQgcXVlIGxlIHNpZW4gZHUgSy1tZWFucyBwYXJjZSBxdWUgUEFNIHV0aWxpc2UgdW4gw6lsw6ltZW50IChtZWRvaWQpIHBvdXIgcMOpc2VudGVyIHNvbiBncm91cGUsIHBhcyBsZSB2cmFpZSBtZWFucyBkdSBncm91cGUuIA0KDQpgYGB7cn0NCmNsdXNwbG90KHosIHBhbS5yZXMkY2x1c3RlciwgY29sb3I9VFJVRSwgc2hhZGU9RiwgbGFiZWxzPTAsIGxpbmVzPTAsIG1haW49J0FuYWx5c2UgZGUgY2x1c3RlciBQQU0nKQ0KYGBgDQoNCiMgQ2xhcmEgQ2x1c3RlcmluZyAgYXZlYyBsZSBsaWJhaXJlKHN0YXQpDQpDTEFSQSAoQ2x1c3RlcmluZyBMQVJnZSBBcHBsaWNhdGlvbnMpOlRpcmUgZGVzIMOpY2hhbnRpbGxvbnMgc3VjY2Vzc2lmcyBkZSBsYSBiYXNlIGV0DQphcHBsaXF1ZSBQQU0gw6AgY2hhcXVlIMOpY2hhbnRpbGxvbiwgcmV0b3VybmUgbGUgbWVpbGxldXIgcGFydGl0aW9ubmVtZW50IG9idGVudS4NCmBgYHtyfQ0KY2xhcmEucmVzIDwtIGNsYXJhKHosIDIsIG1ldHJpYyA9ICJldWNsaWRlYW4iLCBzYW1wbGVzPTUwKQ0KcHJpbnQoY2xhcmEucmVzKQ0KDQpgYGANCg0KYGBge3J9DQpjbHVzcGxvdCh6LCBjbGFyYS5yZXMkY2x1c3RlciwgY29sb3I9VFJVRSwgc2hhZGU9RiwgbGFiZWxzPTAsIGxpbmVzPTAsIG1haW49J0FuYWx5c2UgZGUgY2x1c3RlciBDbGFyYScpDQpgYGANCkxlIHLDqXN1bHRhdCBkdSBDbGFyYSBlc3QgbcOqbWUgcXVlIGxlIHNpZW4gZHUgUEFNIHBhcmNlIHF1ZSBDbGFyYSB1dGlsaXNlIFBBTSBwb3VyIHRyb3V2ZXIgbGUgbWVpbGxldXIgY2x1c3RlcmluZy4NCg0KDQojIEhpZXJhcmNoaWNhbCAgQ2x1c3RlcmluZyBhdmVjIGxlIGxpYmFpcmUoc3RhdCkNCg0KYGBge3J9DQp6X2RmIDwtIGRhdGEuZnJhbWUoeikNCg0KaGMgPC0gaGNsdXN0KGRpc3Qoel9kZikpDQpoYw0KYGBgDQoNCg0KDQoNCmBgYHtyfQ0KcGxvdChoYywgbGFiZWxzID0gZ3JvdXBzX3dpZGUkYXJyb25kLCBtYWluPSdDbHVzdGVyIERlbmRyb2dyYW0nLCBjZXg9MC42NSkNCg0KYGBgDQoNCg0KYGBge3J9DQpjb3VudHMgPSBzYXBwbHkoMjo1LGZ1bmN0aW9uKG5jbCl0YWJsZShjdXRyZWUoaGMsbmNsKSkpDQpuYW1lcyhjb3VudHMpID0gMjo1DQpjb3VudHMNCmBgYA0KTGUgY291bnRzIGluZGlxdWUgbGUgbWVpbGxldXIgY2x1c3RlcmluZywgMiBncm91cGVzLCBsZSByw6lzdWx0YXQgZXN0IGxlIG3Dqm1lIHF1ZSBsZSBzaWVuIGR1IEstbWVhbnMuDQoNCmBgYHtyfQ0KbWVtYmVyIDwtIGN1dHJlZShoYywgMikNCmFnZ3JlZ2F0ZSh6LCBsaXN0KG1lbWJlciksIG1lYW4pDQpgYGANCkRhbnMgbGUgZ3JvdXBlIDEgKDE2IGFycm9uZGlzc2VtZW50cyArIHVuIGdyb3VwZSBzYW5zIG5vbSksIHRvdXMgbGVzIHR5cGUgZGUgcHJvamV0IHNvbnQgbsOpZ2F0aWZzOyBkYW5zIGxlIGdyb3VwZSAyICgzIGFycm9uZGlzc2VtZW50cyksIHRvdXMgbGVzIHR5cGVzIGRlIHByb2pldCBzb250IHBvc2l0aWZzLg0KDQoNCmBgYHtyfQ0KcGxvdChzaWxob3VldHRlKGN1dHJlZShoYywgMiksIGRpc3Qoel9kZikpKQ0KYGBgDQpMZSBncmFwaGlxdWUgZGUgbGEgc2lsaG91ZXR0ZSBpbmRpcXVlIHF1ZSBub3VzIG7igJlhdm9ucyB2cmFpbWVudCBwYXMgYmVzb2luIGR1IHRyb2lzacOobWUgZ3JvdXBlLg0KDQojIEZhaXJlIHVuZSBjYXJ0ZSBkZXMgTG9nbWVudHMgU29jaWF1eCBldCBDb21tdW5hdXRhaXJlcyBkZSBNb250csOpYWwNCk9uIHZhIHV0aWxpc2VyIGdnbWFwIHBvdXIgcHJvZHVpcmUgdW5lIGNhcnRlIHF1aSBjb250aWVudCBsZXMgbG9nbWVudHMgc29jaWF1eCBldCBjb21tdW5hdXRhaXJlcyBkZSBNb250csOpYWwuDQoNCmBgYHtyfQ0KbGF0IDwtIGxvZ3NvY19tdGwkbGF0aXR1ZGUNCmxvbiA8LSBsb2dzb2NfbXRsJGxvbmdpdHVkZQ0KcHJvamV0X3R5cGUgPC0gbG9nc29jX210bCRwcm9qZXR0eXBlDQp0b19tYXAgPC0gZGF0YS5mcmFtZShwcm9qZXRfdHlwZSwgbGF0LCBsb24pDQpjb2xuYW1lcyh0b19tYXApIDwtIGMoJ3Byb2pldF90eXBlJywgJ2xhdCcsICdsb24nKQ0Kc2Jib3ggPC0gbWFrZV9iYm94KGxvbiA9IGxvZ3NvY19tdGwkbG9uZ2l0dWRlLCBsYXQgPSBsb2dzb2NfbXRsJGxhdGl0dWRlLCBmID0gMC4wMSkNCm15X21hcCA8LSBnZXRfbWFwKGxvY2F0aW9uID0gc2Jib3gsIG1hcHR5cGUgPSAicm9hZG1hcCIsIHNjYWxlID0gMiwgY29sb3I9ImJ3Iiwgem9vbSA9IDEwKQ0KYGBgDQoNCmBgYHtyfQ0KZ2dtYXAobXlfbWFwKSArDQogIGdlb21fcG9pbnQoZGF0YT10b19tYXAsIGFlcyh4ID0gbG9uLCB5ID0gbGF0LCBjb2xvciA9ICIjRkY2NjY2IiksIA0KICAgICAgICAgICAgIHNpemUgPSAxLCBhbHBoYSA9IDAuNSkgKw0KICB4bGFiKCdMb25naXR1ZGUnKSArDQogIHlsYWIoJ0xhdGl0dWRlJykgKw0KICBnZ3RpdGxlKCdMb2dtZW50cyBTb2NpYXV4IGV0IENvbW11bmF1dGFpcmVzIGRlIE1vbnRyw6lhbCAnKSArDQogIGd1aWRlcyhjb2xvcj1GQUxTRSkNCmBgYA0KRGFucyBjZXR0ZSBjYXJ0ZSBvbiB2b2l0IHVuZSBncmFuZGUgY29uY2VudHJhdGlvbiBkYW5zIGxlcyBhcnJvbmRpc3NlbWVudHM6IHZpbGxlLW1hcmllLCBTdWQtT3Vlc3QgZXQgTWVyY2llci0gSG9jaGVsYXRhLU1haXNvbm5ldXZlLg0KYGBge3J9DQpnZ21hcChteV9tYXApICsNCiAgZ2VvbV9wb2ludChkYXRhPXRvX21hcCwgYWVzKHggPSBsb24sIHkgPSBsYXQsIGNvbG9yID0gIiNGRjY2NjYiKSwgDQogICAgICAgICAgICAgc2l6ZSA9IDEsIGFscGhhID0gMC41KSArDQogIHhsYWIoJ0xvbmdpdHVkZScpICsNCiAgeWxhYignTGF0aXR1ZGUnKSArDQogIGdndGl0bGUoJ1R5cGUgZGUgcHJvamV0IGRlcyBMb2dtZW50cyBTb2NpYXV4IGV0IENvbW11bmF1dGFpcmVzIGRlIE1vbnRyw6lhbCAnKSArDQogIGd1aWRlcyhjb2xvcj1GQUxTRSkgKw0KICBmYWNldF93cmFwKH4gcHJvamV0X3R5cGUsIG5yb3cgPSAyKQ0KYGBgDQpEYW5zIGNlcyBjYXJ0ZXMgb24gdm9pdCBsZXMgY29uY2VudHJhdGlvbmEgZGFucyBsZXMgYXJyb25kaXNzZW1lbnRzOiB2aWxsZS1tYXJpZSwgU3VkLU91ZXN0IGV0IE1lcmNpZXItIEhvY2hlbGF0YS1NYWlzb25uZXV2ZS4gTGUgbm9tYnJlIGRlIE9NSE0gZXN0IGxlIG1pbmltdW0gZXQgbGUgc2llbiBkZSBDb29wIGVzdCBsZSBtYXhpbXVtLiANCg0KIyBDb25jbHVzaW9uDQoNCkRhbnMgY2UgcHJvamV0IG9uIGEgYW5hbHlzw6kgbGVzIGRvbm7DqWVzIGRlcyBsb2dtZW50cyBzb2NpYXV4IGV0IGNvbW11bml0YWlyZXMgZGUgTW9udHLDqWFsIGV0IG9uIGEgc2VnZW1lbnTDqSBsZXMgYXJyb25kaXNzZW1lbnRzIGRlIE1vbnRyw6lhbCBzZWxvbiBsZXMgdHlwZXMgZGUgcHJvamV0cy4gT24gYSBjYWxjdWzDqSBsZSBtZXVpbGxlciBLID0gMiBlbiB1dGlsaXNhbnQgZGVzIG3DqXRob2RlcyAqKkVsYm93KiosICoqU2lsaG91ZXR0ZSoqLCAqKkdhcCBzdGF0aXN0aWMqKiBldGMuIEV0IHB1aXMsIG9uIGEgdXRpbGlzw6kgbGVzIGFsZ29yaXRoZW1zICoqSy1tZWFucyoqLCAqKlBBTSoqLCAqKkNsYXJhKiosIGV0ICoqQ0hBKiogcG91ciBzZWdtZW50ZXIgbGVzIGFycm9kaXNzZW1lbnRzIGRhbnMgMiBncm91cHM6IEdyb3VwZSAyKHZpbGxlLW1hcmllLCBTdWQtT3Vlc3QgZXQgTWVyY2llci0gSG9jaGVsYXRhLU1haXNvbm5ldXZlKSBldCBHcm91cGUgMShsZXMgYXV0cmVzIGFycm9uZGlzc2VtZW50cykuIEVuIGZpbiwgb24gYSBmYWl0IGRlcyBjYXJ0ZXMgcG91ciBtb250cmVyIGxlcyBsb2dlbWVudHMgc29jaWF1eCBldCBjb21tdW5pdGFpcmVzIGRlIE1vbnRyw6lhbCBlbiB1dGlsaXNhbnQgbGVzIGRvbm7DqWVzIHNwYXRpYWxlcyBkYW5zIGxlIGRhdGEgc2V0Lg0K