In questo caso noi stiamo lavorando con dati multivariati… Vuoldire che noi abbiamo una matrice di dati dove sulle righe ci sono le unità che sono le componenti chimiche e sulle colonne ci sono le variabil, Noi vogliamo trovare dei gruppi, delle classi di individui simili fra loro, quindi vogliamo vedere se nei nostri dati sono presentidei gruppi. La cluster analysis viene chiamata anche analisi dei gruppi Stiamo lavorando con variabili quantitative numeriche,fatta eccezione per la variabile “type”(variabile qualitativa). Abbiamo un dataset analitico di 178 vini piemontesi appartenenti a tre diversi vitigni (Barbera, Grignolino, Barolo) questo dataset contiene 28 variabili di cui 27 numeriche che rappresentano la composizione chimica e una variabile categorica “type” che rappresenta i diversi tipi di vino (che sono 3) 1 = Barolo 2 = Grignolino 3 = Barbera Lo scopo principale del nostro lavoro è stato dimostrare che queste 27 variabili numeriche chimiche,sono in grado di distinguere diverse “tipologie” o marche di vino.

#Importiamo le librerie

library(gridExtra)
library(tidytext)
library(tidyverse)
library(psych)
library(corrr)
library(tidymodels)
library(GGally)  
library(janitor)             
library(kohonen)
library(factoextra)
library(cluster)
library(FactoMineR)
library(Factoshiny)
library(readr)
library(stringr)
library(dplyr)
library(tidyr)
library(ggplot2)
library(skimr)
library(gt)
library(VIM)
library(corrplot)
library(NbClust)

#lettura e visualizzazione dati

View(wine)
skim(wine) # Skim è una funziona che si trova all'interno del pacchetto Skimr, molto simile al summary
── Data Summary ────────────────────────
                           Values
Name                       wine  
Number of rows             178   
Number of columns          28    
_______________________          
Column type frequency:           
  numeric                  28    
________________________         
Group variables            None  

creiaamo nuovo dataset e togliamo la prima colonna (type)

wine1 <- wine[2:28] 

#Check dei valori mancanti

Grafico che ci mostra che effettivamente non ci sono valori mancanti (abbiamo usato una funsione che si chiama aggr che si trova nel pacchetto VIM)


aggr(wine1)

#Costruiamo una tabella di frequenza Questa tabella di frequenza è stata presa in considerazione dalla variabile Type del dataset, ci servirà in un secondo momento per confrontare i valori dei cluster (abbiamo usato la funzione tabyl che si trova nel pacchetto janitor e gt che si trova nel pacchetto gt)

tab_freq <- tabyl(wine, Type) %>% 
  adorn_totals() %>% 
  adorn_pct_formatting() %>% 
  mutate(Vini = recode(Type,
                               '1'= 'Barolo',
                               '2' = 'Grignolino',
                               '3'= 'Barbera')) %>% 
  relocate(Vini, .after = Type) %>% 
  gt()
tab_freq
Type Vini n percent
1 Barolo 59 33.1%
2 Grignolino 71 39.9%
3 Barbera 48 27.0%
Total Total 178 100.0%

#Check outlier e visualizzazione dataset

Quando facciamo l’analisi dei gruppi le variabili devono essere misurate sulla stessa scala, quindi dobbiamo standardizzare le variabili se sono molto diverse dalle altre…

notiamo subito che che ci sono dei valori che si discostano molto dagli altri

wine1 %>%
  gather(Attributes, values) %>%
  ggplot(aes(x=reorder(Attributes, values, FUN=median), y=values, fill=Attributes)) +
  geom_boxplot(show.legend=FALSE) +
  labs(title="Wines Attributes - Boxplots") +
  theme_bw() +
  theme(axis.title.y=element_blank(),
        axis.title.x=element_blank()) +
  ylim(0, 800) +
  coord_flip()
Avvertimento: Removed 239 rows containing non-finite values (`stat_boxplot()`).

Con la funzione scale() per ciascun dato viene calcolata la deviata normale standardizzata. In pratica questa funzione prima calcola per i dati di ciascuna colonna/variabile la media e la deviazione standard, poi calcola per ciascuno dato x la corrispondente deviata normale standardizzata

z = (x – media) / deviazione standard

Quando usiamo scale, nel caso in cui volessimo un dataframe dobbiamo specificarlo, altrimenti ritorna una matrice di dati

wine2_s <- as.data.frame(scale(as.data.frame(wine1)))

BoxPlot con dataset standardizzato

wine2_s %>%
  gather(Attributes, values) %>%
  ggplot(aes(x=reorder(Attributes, values, FUN=median), y=values, fill=Attributes)) +
  geom_boxplot(show.legend=FALSE) +
  labs(title="Wines Attributes - Boxplots") +
  theme_bw() +
  theme(axis.title.y=element_blank(),
        axis.title.x=element_blank()) +
  ylim(0, 10) +
  coord_flip()
Avvertimento: Removed 2529 rows containing non-finite values (`stat_boxplot()`).

Vogliamo cercare di fare una selezione di variabili attraverso la visualizzazzione dei grafici

Partendo dal presupposto che abbiamo 27 variabili, facendo un plot di tutte le variabili sarebbe dificile riuscire ad individuare i cluster per le variabili, quindi abbiamo pensato di fare 3 subplot con

** Impostiamo numeri random e utiliazziamo la funzione kmeans** L’algoritmo K-means è un algoritmo di analisi dei gruppi partizionale che permette di suddividere un insieme di oggetti in k gruppi sulla base dei loro attributi

set.seed(123)
wine_dens <- kmeans(wine2_s, centers=3)
summary(wine_dens)
             Length Class  Mode   
cluster      178    -none- numeric
centers       81    -none- numeric
totss          1    -none- numeric
withinss       3    -none- numeric
tot.withinss   1    -none- numeric
betweenss      1    -none- numeric
size           3    -none- numeric
iter           1    -none- numeric
ifault         1    -none- numeric

**Stiamo cercando di fare una selezione delle features attraverso i grafici, e abbiamo provato a farlo anche attraverso la correlazione, siamo certi esistono metodi statistici per la selezione delle features, (abbiamo visto un pacchetto che si chiama caret) ma in fine abbiamo deciso di tenere in considerazione tutte le variabili eccetto type

ggcorr(wine2_s, low = "navy", high = "darkred")

**Con questo istogramma vediamo quante volte si ripetono valori delle variabili

wine2_s %>% 
  gather(key = value_groups, value = value) %>% 
  ggplot(aes(x=value)) +
  geom_histogram() +
  facet_wrap(.~value_groups, scales = "free")
`stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

** Con un qq plot vediamo le distribuzioni delle variabili chimiche e notiamo che sono poche le variabili che sono distribuite come una normale**


wine2_s %>% 
  gather(key = value_groups, value = value) %>% 
  ggplot(aes(sample=value, colour=value_groups)) +
  stat_qq() +
  stat_qq_line() +
  facet_wrap(.~value_groups, scales = "free_y")

dobbiamo definire una matrice di dissimilarità .. andiamo a vedere quanto sono simili o diversi partendo dalla nostra matrice di dati. Per ciascuna coppia di individui, andiamo a vedere per ciascuna variabile quanto questi due individui sono diversi o simili, a seconda del valore R ( il peso che noi diamo alle distanze) otteniamo delle metriche diverse, la più famosa è la norm euclidea dove R=2

Defianiamo le distanze (euclidea,manhattan,minkowski)

mat <- hclust(dist(wine2_s, method = "euclidean"), method="ward.D") # matrice delle distanze euclidee
mat1 <- hclust(dist(wine2_s, method = "manhattan"), method="ward.D")# distanza di manhattan
mat2 <- hclust(dist(wine2_s, method = "minkowski"), method="ward.D")# distanza di minkowski

**Queste funzioni ci consentono di capire la quantità ottimale dei cluster che possiamo fare con il dataset

fviz_nbclust(wine2_s, FUNcluster = kmeans, nstart=50, "silhouette") # Il coefficente di Silhouette misura quanto e' simile un oggetto x con gli altri oggetti nel proprio cluster contro quelli negli altri cluster. I valori piu' vicini a 1 indicano un cluster migliore

fviz_nbclust(wine2_s, FUNcluster = kmeans, nstart=80, "wss") +
  geom_vline(xintercept = 3, linetype=2)

fviz_nbclust(wine2_s, FUNcluster = kmeans, nstart=30, "gap_stat", nboot = 50)
Clustering k = 1,2,..., K.max (= 10): .. done
Bootstrapping, b = 1,2,..., B (= 50)  [one "." per sample]:
.................................................. 50 

** Questa funzione è un test che genera in automatico il numero di cluster che sarebbe meglio tirar fuoriin questo caso il numero delle classsi migliori è 3**

x <- NbClust(wine2_s, distance = "euclidean", min.nc = 2, max.nc = 10, method = "ward.D2")
*** : The Hubert index is a graphical method of determining the number of clusters.
                In the plot of Hubert index, we seek a significant knee that corresponds to a 
                significant increase of the value of the measure i.e the significant peak in Hubert
                index second differences plot. 
 

*** : The D index is a graphical method of determining the number of clusters. 
                In the plot of D index, we seek a significant knee (the significant peak in Dindex
                second differences plot) that corresponds to a significant increase of the value of
                the measure. 
 
******************************************************************* 
* Among all indices:                                                
* 4 proposed 2 as the best number of clusters 
* 16 proposed 3 as the best number of clusters 
* 1 proposed 8 as the best number of clusters 
* 1 proposed 10 as the best number of clusters 

                   ***** Conclusion *****                            
 
* According to the majority rule, the best number of clusters is  3 
 
 
******************************************************************* 

** Visualizziamo tutte le proposte **

x$All.index
       KL      CH Hartigan    CCC     Scott      Marriot   TrCovW   TraceW Friedman  Rubin Cindex     DB Silhouette   Duda Pseudot2  Beale Ratkowsky      Ball Ptbiserial    Frey McClain   Dunn Hubert SDindex Dindex   SDbw
2  1.3943 43.3719  32.9472 0.2449  382.9864 1.970498e+52 39291.48 3834.146  31.5640 1.2464 0.3400 1.7959     0.2039 0.7883  33.3041 5.0571    0.2592 1917.0732     0.4622  0.5073  0.5711 0.2152 0.0008  0.8833 4.5062 0.8437
3  5.6787 41.9793   8.6883 2.9309  696.9339 7.599268e+51 23170.59 3229.570  44.8292 1.4798 0.2916 1.8784     0.1984 0.8841   7.9972 2.4483    0.3079 1076.5235     0.5233  0.5038  1.5205 0.2102 0.0008  0.8438 4.1069 0.7603
4  0.8909 32.0873   8.5378 1.6012  859.8732 5.408798e+51 20509.53 3076.814  49.6488 1.5532 0.3589 2.2321     0.1773 0.8529   8.6209 3.2085    0.2840  769.2036     0.5175  0.4614  1.9870 0.2543 0.0009  0.9683 4.0169 0.7608
5  1.5750 27.2234   5.9738 1.3038 1034.9995 3.159641e+51 18163.48 2932.903  54.1784 1.6294 0.3443 2.3906     0.1486 0.8951   7.1500 2.1889    0.2661  586.5807     0.5085  0.7322  2.4358 0.2543 0.0010  0.9575 3.9167 0.7305
6  0.9464 23.5884   5.8566 0.7741 1144.9602 2.453081e+51 16677.85 2835.009  56.3149 1.6857 0.3336 2.4890     0.1099 0.8650   5.6198 2.8829    0.2520  472.5016     0.4816 -0.0319  3.0509 0.2543 0.0011  1.0231 3.8527 0.8097
7  0.9142 21.1786   6.0199 0.5208 1251.0749 1.839504e+51 15771.56 2741.656  59.3104 1.7431 0.3642 2.3391     0.1116 0.8032   5.6347 4.4563    0.2403  391.6651     0.4956 -0.0124  3.1347 0.2790 0.0011  0.9806 3.8049 0.7880
8  1.0787 19.5372   5.5609 0.5966 1389.9304 1.101278e+51 14749.02 2648.421  61.3904 1.8045 0.3707 2.1743     0.1137 0.8478   5.9227 3.3064    0.2308  331.0526     0.5034  0.2329  3.1914 0.2868 0.0012  0.9765 3.7456 0.7288
9  1.0894 18.2414   5.1187 0.7401 1492.9426 7.813888e+50 13587.34 2564.531  64.0273 1.8635 0.3614 2.1318     0.1118 0.8810   6.6175 2.5121    0.2222  284.9479     0.5024  1.2192  3.4373 0.2868 0.0012  0.9872 3.6895 0.7170
10 0.9953 17.1722   5.0641 0.8378 1584.5417 5.766250e+50 12510.82 2489.140  66.2517 1.9199 0.3558 2.1039     0.1048 0.7946   5.1698 4.6727    0.2145  248.9140     0.4698  0.2131  4.1187 0.2868 0.0012  0.9795 3.6321 0.6917

Sulla base delle distanze che abbiamo andiamo a considerare ● Single linkage —> Nearest Neighbor (il vicino più vicino) cerca di mettere ● Complete linkage —> il vicino più lontano cerca di mettere insieme gruppi tra di loro che sono vicini comparando le distanze fra di loro ● Average linkage —> il linkage medio fa una media dei due ● Ward’s minimum variance linkage —> il word ion genere è quello che funziona meglio…word minimizza la varianza within…perché il metodo di word non si basa sulla distanza ma ha l’idea di minimizzare l’inerzia ovvero la varianza….quindi lui tende a far si che la varianza whitin non sia troppo grande

Definiamo le variabili per la visualizzazione dei dendogrammi

d_eucli <- dist(wine2_s, method = "euclidean") # distanza euclide
wine_hc <- hclust(d_eucli, method = "ward.D2")
fviz_dend(wine_hc, k = 3, show_labels = F, rect = T) #dendogramma migliore

{
vini_complete <- hclust(d_eucli, method = "complete")   ## cluster with "complete" linkage
vini_single <- hclust(d_eucli, method = "single")     ## cluster with "single" linkage
vini_average <- hclust(d_eucli, method = "average")   ## cluster with "average" linkage
vini_ward <- hclust(d_eucli, method = "ward.D2")      ## cluster with "ward" linkage
}

Facciamo una comparazione dei dendogrammi per vedere quale ci sembra il migliore

par(mfrow = c(2, 2))
plot(vini_complete, cex = 0.8, hang = -1, main = "Hierarchical Clustering with Complete Linkage")
plot(vini_single, cex = 0.8, hang = -1, main = "Hierarchical Clustering with Single Linkage")
plot(vini_average, cex = 0.8, hang = -1, main = "Hierarchical Clustering with Average Linkage")
plot(vini_ward, cex = 0.8, hang = -1, main = "Hierarchical Clustering with Ward Linkage")

Abbiamo optato per il dendogramma migliore,abbiamo evidenziato il dendogramma con colori diversi e abbiamo tolto i numeri per una visualizzazione migliore

Il centroide è un punto appartenente allo spazio delle features che media le distanze tra tutti i dati appartenenti al cluster ad esso associato. Rappresenta quindi una sorta di baricentro del cluster ed in generale, proprio per le sue caratteristiche, non è uno dei punti del dataset. Non conoscendo le classi presenti nel dataset di ingresso, la prima cosa da fare è decidere il numero di classi, Si scelgono in modo casuale K centroidi appartenenti allo spazio delle features. Si calcola la distanza di ogni punto del dataset rispetto ad ogni centroide Ogni punto del dataset viene associato al cluster collegato al centroide più vicino Si ricalcola la posizione di ogni centroide facendo la media delle posizioni di tutti i punti del cluster associato Si itera dal punto 3 fino a quando non ci sarà più alcun ingresso che cambia di cluster.

set.seed(123)
vini_kmeans <- kmeans(wine2_s, centers = 3)
vini_kmeans
K-means clustering with 3 clusters of sizes 69, 60, 49

Cluster means:
     Alcohol Sugar-free Extract Fixed Acidity Tartaric Acid Malic Acid Uronic Acids           pH        Ash Alcalinity of Ash  Potassium    Calcium   Magnesium  Phosphate   Chloride Total Phenols  Flavanoids Non-flavanoid Phenols Proanthocyanins
1 -0.8856865         -0.4979609    -0.2282955    -0.1499780 -0.3579000   -0.3954178 -0.006752522 -0.5161014         0.1225921 -0.2533765  0.5580202 -0.41519242 -0.4450871  0.1693383    -0.0804181  0.01146806           -0.01373209      0.05540199
2  0.8666244          0.6638956    -0.5146858    -0.5791356 -0.3253961   -0.3561667  0.240038184  0.3905674        -0.6163311  0.2068074 -0.4749177  0.51870720  0.7143103  0.1655889     0.8975314  0.99353094           -0.56798207      0.54674519
3  0.1860184         -0.1117231     0.9517048     0.9203392  0.9024258    0.9929352 -0.284415653  0.2485092         0.5820616  0.1035619 -0.2042516 -0.05049296 -0.2479104 -0.4412179    -0.9857762 -1.23271740            0.71482528     -0.74749896
  Color Intensity        Hue OD280/OD315 of Diluted Wines OD280/OD315 of Flavanoids    Glycerol 2-3-Butanediol Total Nitrogen    Proline    Methanol
1      -0.8730101  0.4230425                    0.2317587                 0.3918922 -0.59945726     -0.5310122     -0.1339752 -0.7333939 -0.18931901
2       0.1989588  0.4836584                    0.7934116                 0.5529190  0.66943876      0.1927984      0.5168134  1.1528997  0.03215541
3       0.9857177 -1.1879477                   -1.2978785                -1.2288919  0.02441276      0.5116722     -0.4441737 -0.3789756  0.22721810

Clustering vector:
  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64 
  2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   1   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   1   1   1   1   1 
 65  66  67  68  69  70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 
  1   1   1   1   1   1   1   1   1   2   1   1   1   1   1   1   1   1   1   3   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   2   1   1   1   1   1   1 
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 
  1   1   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3 

Within cluster sum of squares by cluster:
[1] 1373.5013  919.6663  893.0953
 (between_SS / total_SS =  33.3 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss" "betweenss"    "size"         "iter"         "ifault"      
table(wine$Type,vini_kmeans$cluster)
   
     1  2  3
  1  1 58  0
  2 68  2  1
  3  0  0 48
fviz_cluster(vini_kmeans, data = wine2_s,
             palette = c("#2E9FDF", "#00AFBB", "#E7B800"), 
             geom = "point",
             ellipse.type = "convex", 
             ggtheme = theme_bw()
)

print(vini_kmeans)
K-means clustering with 3 clusters of sizes 69, 60, 49

Cluster means:
     Alcohol Sugar-free Extract Fixed Acidity Tartaric Acid Malic Acid Uronic Acids           pH        Ash Alcalinity of Ash  Potassium    Calcium   Magnesium  Phosphate   Chloride Total Phenols  Flavanoids Non-flavanoid Phenols Proanthocyanins
1 -0.8856865         -0.4979609    -0.2282955    -0.1499780 -0.3579000   -0.3954178 -0.006752522 -0.5161014         0.1225921 -0.2533765  0.5580202 -0.41519242 -0.4450871  0.1693383    -0.0804181  0.01146806           -0.01373209      0.05540199
2  0.8666244          0.6638956    -0.5146858    -0.5791356 -0.3253961   -0.3561667  0.240038184  0.3905674        -0.6163311  0.2068074 -0.4749177  0.51870720  0.7143103  0.1655889     0.8975314  0.99353094           -0.56798207      0.54674519
3  0.1860184         -0.1117231     0.9517048     0.9203392  0.9024258    0.9929352 -0.284415653  0.2485092         0.5820616  0.1035619 -0.2042516 -0.05049296 -0.2479104 -0.4412179    -0.9857762 -1.23271740            0.71482528     -0.74749896
  Color Intensity        Hue OD280/OD315 of Diluted Wines OD280/OD315 of Flavanoids    Glycerol 2-3-Butanediol Total Nitrogen    Proline    Methanol
1      -0.8730101  0.4230425                    0.2317587                 0.3918922 -0.59945726     -0.5310122     -0.1339752 -0.7333939 -0.18931901
2       0.1989588  0.4836584                    0.7934116                 0.5529190  0.66943876      0.1927984      0.5168134  1.1528997  0.03215541
3       0.9857177 -1.1879477                   -1.2978785                -1.2288919  0.02441276      0.5116722     -0.4441737 -0.3789756  0.22721810

Clustering vector:
  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64 
  2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   1   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   1   1   1   1   1 
 65  66  67  68  69  70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 
  1   1   1   1   1   1   1   1   1   2   1   1   1   1   1   1   1   1   1   3   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   2   1   1   1   1   1   1 
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 
  1   1   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3 

Within cluster sum of squares by cluster:
[1] 1373.5013  919.6663  893.0953
 (between_SS / total_SS =  33.3 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss" "betweenss"    "size"         "iter"         "ifault"      
vini_kmeans
K-means clustering with 3 clusters of sizes 69, 60, 49

Cluster means:
     Alcohol Sugar-free Extract Fixed Acidity Tartaric Acid Malic Acid Uronic Acids           pH        Ash Alcalinity of Ash  Potassium    Calcium   Magnesium  Phosphate   Chloride Total Phenols  Flavanoids Non-flavanoid Phenols Proanthocyanins
1 -0.8856865         -0.4979609    -0.2282955    -0.1499780 -0.3579000   -0.3954178 -0.006752522 -0.5161014         0.1225921 -0.2533765  0.5580202 -0.41519242 -0.4450871  0.1693383    -0.0804181  0.01146806           -0.01373209      0.05540199
2  0.8666244          0.6638956    -0.5146858    -0.5791356 -0.3253961   -0.3561667  0.240038184  0.3905674        -0.6163311  0.2068074 -0.4749177  0.51870720  0.7143103  0.1655889     0.8975314  0.99353094           -0.56798207      0.54674519
3  0.1860184         -0.1117231     0.9517048     0.9203392  0.9024258    0.9929352 -0.284415653  0.2485092         0.5820616  0.1035619 -0.2042516 -0.05049296 -0.2479104 -0.4412179    -0.9857762 -1.23271740            0.71482528     -0.74749896
  Color Intensity        Hue OD280/OD315 of Diluted Wines OD280/OD315 of Flavanoids    Glycerol 2-3-Butanediol Total Nitrogen    Proline    Methanol
1      -0.8730101  0.4230425                    0.2317587                 0.3918922 -0.59945726     -0.5310122     -0.1339752 -0.7333939 -0.18931901
2       0.1989588  0.4836584                    0.7934116                 0.5529190  0.66943876      0.1927984      0.5168134  1.1528997  0.03215541
3       0.9857177 -1.1879477                   -1.2978785                -1.2288919  0.02441276      0.5116722     -0.4441737 -0.3789756  0.22721810

Clustering vector:
  1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  64 
  2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   1   2   2   2   2   2   2   2   2   2   2   2   2   2   2   2   1   1   1   1   1 
 65  66  67  68  69  70  71  72  73  74  75  76  77  78  79  80  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 
  1   1   1   1   1   1   1   1   1   2   1   1   1   1   1   1   1   1   1   3   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   2   1   1   1   1   1   1 
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 
  1   1   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3 

Within cluster sum of squares by cluster:
[1] 1373.5013  919.6663  893.0953
 (between_SS / total_SS =  33.3 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss" "betweenss"    "size"         "iter"         "ifault"      
bar <- subset(wine, Type == "1")
bar2 <- subset(wine, Type == "2")
bar3 <- subset(wine, Type == "3")
mean(bar$Alcohol)
[1] 13.74475
mean(bar2$Alcohol)
[1] 12.27873
mean(bar3$Alcohol)
[1] 13.15375
bar <- subset(wine, Type == "1")
bar2 <- subset(wine, Type == "2")
bar3 <- subset(wine, Type == "3")
mean(bar$Proline)
[1] 1115.712
mean(bar2$Proline)
[1] 519.507
mean(bar3$Proline)
[1] 629.8958

cluster basato su modelli identifichiamo la classe, ovvero il tipo dei vini:

class <- factor(wine$Type, levels = 1:3, labels = c("Barolo", "Grignolino", "Barbera"))
table(class)
class
    Barolo Grignolino    Barbera 
        59         71         48 

Selezione delle variabili con clustvarsel

library(clustvarsel)#La metodologia consente di trovare il sottoinsieme (localmente) ottimale di variabili in un set di dati che hanno informazioni di gruppo/cluster.
out <- clustvarsel(wine1)      # utilizzo la funzione clustvarsel per selezionare le varibili ottimali
iter 1
+ adding step
iter 2
+ adding step
iter 3 
+ adding step
- removing step
iter 4 
+ adding step
- removing step
iter 5 
+ adding step
- removing step
iter 6 
+ adding step
- removing step
iter 7 
+ adding step
- removing step
iter 8 
+ adding step
- removing step
iter 9 
+ adding step
- removing step
final iter
* fitting model on selected subset
out                              # ai fini del clustering, in modo da utilizzarle nei grafici
------------------------------------------------------ 
Variable selection for Gaussian model-based clustering
Stepwise (forward/backward) greedy search
------------------------------------------------------ 

Selected subset: Chloride, Malic Acid, Flavanoids, Color Intensity, Proline, Uronic Acids, OD280/OD315 of Flavanoids
out$subset
                 Chloride                Malic Acid                Flavanoids           Color Intensity                   Proline              Uronic Acids OD280/OD315 of Flavanoids 
                       14                         5                        16                        19                        26                         6                        22 
selected.var <- wine1[out$subset]
selected.var
# Le variabili selezionate sono: Chloride, Malic Acid, Flavanoids, Color Intensity, Proline, Uronic Acids,
#OD280/OD315 of Flavanoids
#la prolina indica la secchezza del vino, l'acido malico è l'acido più diffuso nel regno vegetale
#il gruppo dei flavonoidi ha una concentrazione più elevata nei vini rossi, e svolgono un azione protettiva contro le radiazioni ultraviolette

Analisi esplorativa delle variabili selezionate

selezione delle variabili con la libreria “BORUTA”,Boruta è un algoritmo di classificazione e selezione delle caratteristiche basato su un algoritmo di foreste casuali. vantaggio di Boruta è che decide chiaramente se una variabile è importante o meno e aiuta a selezionare variabili statisticamente significative.

selezione delle variabili con il metodo SelvarClustLasso, questa funzione implementa la selezione delle variabili nel clustering basato su modelli

library(sparcl) 
library(vscc) 
library(SelvarMix) 
require(Rmixmod)
require(glasso)
lasso <- SelvarClustLasso(x=wine1, nbcluster=1:3,criterio="ICL")
variable  ranking
SRUW selection with ICL criterion
model selection  with ICL criterion
summary(lasso)
Criterion: ICL 
Criterion value: -10843.71 
Number of clusters: 3 
Gaussian mixture model: Gaussian_pk_Lk_Ck 
Regression covariance model: LC 
Independent covariance model:  
The SRUW model:
 S: 1 6 9 10 12 14 16 18 19 20 21 22 23 25 26 27 
 R: 9 10 12 16 19 20 23 
 U: 2 3 4 5 7 8 11 13 15 17 24 
 W: 
lasso$S #variabili accettate
 [1]  1  6  9 10 12 14 16 18 19 20 21 22 23 25 26 27
lasso$R #variabili per regressione
[1]  9 10 12 16 19 20 23
lasso$U #variabili ridondanti
 [1]  2  3  4  5  7  8 11 13 15 17 24
lasso$nbcluster
[1] 3
table(lasso$partition,wine[,1])
   
     1  2  3
  1 59 71 48

** MODEL - BASED CLUSTERING applicato a tutto il dataset wine **

Il miglior modello è EVI, con tre cluster, EVI ha distrubuzione diagonale, volume uguale e forma variabile (aggiungere foto)

rappresentazione dei risultati

rappresentazione dei valori Bic utilizzati per la scelta del numero dei cluster

plot(mod, what = "BIC", ylim = range(mod$BIC[,-(1:2)], na.rm = TRUE), legendArgs = list(x = "bottomleft"))

** rappresentazione dei risultati con la libreria factoextraClassificazione: grafico che mostra il raggruppamento**

library(factoextra)
fviz_mclust(mod, "classification", geom = "point", 
            pointsize = 1.5, palette = "jco")

Classificazione dell’incertezza

fviz_mclust(mod, "uncertainty", palette = "jco")

** Mclust con le variabili selezionate con il metodo di clustvarsel**

fviz_mclust(mclust.selected, "classification", geom = "point", 
            pointsize = 1.5, palette = "set2")

fviz_mclust(mclust.selected, "uncertainty", palette = "set2")

** mclust con le variabile selezionate ci dice cge il miglior modello è EVI, ma con 7 cluster**

ora rifacciamo la stessa funzione, ma impostando il numero di cluster a 3

plot(mclust.selected1, what = "classification")

mclustDA Analisi discriminante basata sulla modellazione di miscele finite gaussiane

wineMclustDA <- MclustDA(selected.var, class, modelType = "EDDA", modelNames = "EEE")
fitting ...

  |                                                                                                                                                                                                                                                            
  |                                                                                                                                                                                                                                                      |   0%
  |                                                                                                                                                                                                                                                            
  |======================================================================================================================================================================================================================================================| 100%
summary(wineMclustDA, parameters = TRUE)
------------------------------------------------ 
Gaussian finite mixture model for classification 
------------------------------------------------ 

EDDA model summary: 
            
Classes       n     % Model G
  Barolo     59 33.15   EEE 1
  Grignolino 71 39.89   EEE 1
  Barbera    48 26.97   EEE 1

Class prior probabilities:
    Barolo Grignolino    Barbera 
 0.3314607  0.3988764  0.2696629 

Class = Barolo

Means:
                                  [,1]
Chloride                    71.7118644
Malic Acid                   2.0106780
Flavanoids                   2.9823729
Color Intensity              5.5283051
Proline                   1115.7118644
Uronic Acids                 0.8110169
OD280/OD315 of Flavanoids    3.4484746

Variances:
[,,1]
                              Chloride    Malic Acid   Flavanoids Color Intensity      Proline Uronic Acids OD280/OD315 of Flavanoids
Chloride                  2309.3076627  -5.292799727  2.892527934      4.13800916   420.801184 -0.868100227              -3.584535341
Malic Acid                  -5.2927997   0.872588143 -0.009250663     -0.25433997   -32.501326  0.039696376               0.128537935
Flavanoids                   2.8925279  -0.009250663  0.270077612      0.28184664     3.367962  0.005806350               0.074876906
Color Intensity              4.1380092  -0.254339969  0.281846643      2.24641309    66.946034 -0.010459694              -0.143612129
Proline                    420.8011842 -32.501326194  3.367961843     66.94603423 29206.990603  3.068294871             -18.334531286
Uronic Acids                -0.8681002   0.039696376  0.005806350     -0.01045969     3.068295  0.038401609               0.008848205
OD280/OD315 of Flavanoids   -3.5845353   0.128537935  0.074876906     -0.14361213   -18.334531  0.008848205               0.338611842

Class = Grignolino

Means:
                                 [,1]
Chloride                   71.6901408
Malic Acid                  1.9326761
Flavanoids                  2.0808451
Color Intensity             3.0866197
Proline                   519.5070423
Uronic Acids                0.8253521
OD280/OD315 of Flavanoids   3.3316901

Variances:
[,,1]
                              Chloride    Malic Acid   Flavanoids Color Intensity      Proline Uronic Acids OD280/OD315 of Flavanoids
Chloride                  2309.3076627  -5.292799727  2.892527934      4.13800916   420.801184 -0.868100227              -3.584535341
Malic Acid                  -5.2927997   0.872588143 -0.009250663     -0.25433997   -32.501326  0.039696376               0.128537935
Flavanoids                   2.8925279  -0.009250663  0.270077612      0.28184664     3.367962  0.005806350               0.074876906
Color Intensity              4.1380092  -0.254339969  0.281846643      2.24641309    66.946034 -0.010459694              -0.143612129
Proline                    420.8011842 -32.501326194  3.367961843     66.94603423 29206.990603  3.068294871             -18.334531286
Uronic Acids                -0.8681002   0.039696376  0.005806350     -0.01045969     3.068295  0.038401609               0.008848205
OD280/OD315 of Flavanoids   -3.5845353   0.128537935  0.074876906     -0.14361213   -18.334531  0.008848205               0.338611842

Class = Barbera

Means:
                                 [,1]
Chloride                   41.2708333
Malic Acid                  3.3337500
Flavanoids                  0.7814583
Color Intensity             7.3962500
Proline                   629.8958333
Uronic Acids                1.1743750
OD280/OD315 of Flavanoids   1.8860417

Variances:
[,,1]
                              Chloride    Malic Acid   Flavanoids Color Intensity      Proline Uronic Acids OD280/OD315 of Flavanoids
Chloride                  2309.3076627  -5.292799727  2.892527934      4.13800916   420.801184 -0.868100227              -3.584535341
Malic Acid                  -5.2927997   0.872588143 -0.009250663     -0.25433997   -32.501326  0.039696376               0.128537935
Flavanoids                   2.8925279  -0.009250663  0.270077612      0.28184664     3.367962  0.005806350               0.074876906
Color Intensity              4.1380092  -0.254339969  0.281846643      2.24641309    66.946034 -0.010459694              -0.143612129
Proline                    420.8011842 -32.501326194  3.367961843     66.94603423 29206.990603  3.068294871             -18.334531286
Uronic Acids                -0.8681002   0.039696376  0.005806350     -0.01045969     3.068295  0.038401609               0.008848205
OD280/OD315 of Flavanoids   -3.5845353   0.128537935  0.074876906     -0.14361213   -18.334531  0.008848205               0.338611842

Training confusion matrix:
            Predicted
Class        Barolo Grignolino Barbera
  Barolo         56          3       0
  Grignolino      1         69       1
  Barbera         0          1      47
Classification error = 0.0337 
Brier score          = 0.0279 
summary(wineMclustDA, newdata = selected.var, newclass = class)
------------------------------------------------ 
Gaussian finite mixture model for classification 
------------------------------------------------ 

EDDA model summary: 
            
Classes       n     % Model G
  Barolo     59 33.15   EEE 1
  Grignolino 71 39.89   EEE 1
  Barbera    48 26.97   EEE 1

Training confusion matrix:
            Predicted
Class        Barolo Grignolino Barbera
  Barolo         56          3       0
  Grignolino      1         69       1
  Barbera         0          1      47
Classification error = 0.0337 
Brier score          = 0.0279 

Test confusion matrix:
            Predicted
Class        Barolo Grignolino Barbera
  Barolo         56          3       0
  Grignolino      1         69       1
  Barbera         0          1      47
Classification error = 0.0337 
Brier score          = 0.0279 
plot(wineMclustDA)
Model-based discriminant analysis plots: 

1: scatterplot
2: classification
3: train&test
4: error
1
Model-based discriminant analysis plots: 

1: scatterplot
2: classification
3: train&test
4: error
2

Model-based discriminant analysis plots: 

1: scatterplot
2: classification
3: train&test
4: error
3

Model-based discriminant analysis plots: 

1: scatterplot
2: classification
3: train&test
4: error
4

Model-based discriminant analysis plots: 

1: scatterplot
2: classification
3: train&test
4: error
0

NA
NA

visualizziamo i cluster su un biplot PCA

wine_pca <- prcomp(X, scale. = TRUE) #Esegue un'analisi dei componenti principali sulla matrice di dati data e restituisce i risultati come oggetto di classe prcomp.
library(ggbiplot)
mc_clusters <- factor(mod$classification)
ggbiplot(wine_pca, groups = mc_clusters) +
  labs(color = "mclust clusters") +
  scale_color_brewer(palette = "Set2") +
  theme(aspect.ratio = 0.8, legend.position = "top")

** analisi delle componenti principaòi con il dataset ridotto**


wine_pca1 <- prcomp(selected.var, scale. = TRUE)
mc_clusters1 <- factor(mclust.selected1$classification)
ggbiplot(wine_pca1, groups = mc_clusters1) +
  labs(color = "mclust clusters") +
  scale_color_brewer(palette = "Set2") +
  theme(aspect.ratio = 0.8, legend.position = "top")

LS0tCnRpdGxlOiAiV2luZSBDbHVzdGVyaW5nIgphdXRob3I6IEZlZGVyaWNvIEd1enpvLCBUYW5pYSBQaWEgQWxvZSwgQ2hyaXN0aWFuIEpvbmVzLCBMZW9uYXJkbyBJYWN1emlvCm91dHB1dDoKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAplZGl0b3Jfb3B0aW9uczoKICBtYXJrZG93bjoKICAgIHdyYXA6IDcyCi0tLQoKSW4gcXVlc3RvIGNhc28gbm9pIHN0aWFtbyBsYXZvcmFuZG8gY29uIGRhdGkgbXVsdGl2YXJpYXRpLi4uIFZ1b2xkaXJlCmNoZSBub2kgYWJiaWFtbyB1bmEgbWF0cmljZSBkaSBkYXRpIGRvdmUgc3VsbGUgcmlnaGUgY2kgc29ubyBsZSB1bml0w6AKY2hlIHNvbm8gbGUgY29tcG9uZW50aSBjaGltaWNoZSBlIHN1bGxlIGNvbG9ubmUgY2kgc29ubyBsZSB2YXJpYWJpbCwgTm9pCnZvZ2xpYW1vIHRyb3ZhcmUgZGVpIGdydXBwaSwgZGVsbGUgY2xhc3NpIGRpIGluZGl2aWR1aSBzaW1pbGkgZnJhIGxvcm8sCnF1aW5kaSB2b2dsaWFtbyB2ZWRlcmUgc2UgbmVpIG5vc3RyaSBkYXRpIHNvbm8gcHJlc2VudGlkZWkgZ3J1cHBpLiBMYQpjbHVzdGVyIGFuYWx5c2lzIHZpZW5lIGNoaWFtYXRhIGFuY2hlIGFuYWxpc2kgZGVpIGdydXBwaSBTdGlhbW8KbGF2b3JhbmRvIGNvbiB2YXJpYWJpbGkgcXVhbnRpdGF0aXZlIG51bWVyaWNoZSxmYXR0YSBlY2NlemlvbmUgcGVyIGxhCnZhcmlhYmlsZSAidHlwZSIodmFyaWFiaWxlIHF1YWxpdGF0aXZhKS4gQWJiaWFtbyB1biBkYXRhc2V0IGFuYWxpdGljbyBkaQoxNzggdmluaSBwaWVtb250ZXNpIGFwcGFydGVuZW50aSBhIHRyZSBkaXZlcnNpIHZpdGlnbmkgKEJhcmJlcmEsCkdyaWdub2xpbm8sIEJhcm9sbykgcXVlc3RvIGRhdGFzZXQgY29udGllbmUgMjggdmFyaWFiaWxpIGRpIGN1aSAyNwpudW1lcmljaGUgY2hlIHJhcHByZXNlbnRhbm8gbGEgY29tcG9zaXppb25lIGNoaW1pY2EgZSB1bmEgdmFyaWFiaWxlCmNhdGVnb3JpY2EgInR5cGUiIGNoZSByYXBwcmVzZW50YSBpIGRpdmVyc2kgdGlwaSBkaSB2aW5vIChjaGUgc29ubyAzKSAxCj0gQmFyb2xvIDIgPSBHcmlnbm9saW5vIDMgPSBCYXJiZXJhIExvIHNjb3BvIHByaW5jaXBhbGUgZGVsIG5vc3RybwpsYXZvcm8gw6ggc3RhdG8gZGltb3N0cmFyZSBjaGUgcXVlc3RlIDI3IHZhcmlhYmlsaSBudW1lcmljaGUKY2hpbWljaGUsc29ubyBpbiBncmFkbyBkaSBkaXN0aW5ndWVyZSBkaXZlcnNlICJ0aXBvbG9naWUiIG8gbWFyY2hlIGRpCnZpbm8uCgpgYGB7cn0KCmBgYAoKXCMqKkltcG9ydGlhbW8gbGUgbGlicmVyaWUqKgoKYGBge3IgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkodGlkeXRleHQpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHBzeWNoKQpsaWJyYXJ5KGNvcnJyKQpsaWJyYXJ5KHRpZHltb2RlbHMpCmxpYnJhcnkoR0dhbGx5KSAgCmxpYnJhcnkoamFuaXRvcikgICAgICAgICAgICAgCmxpYnJhcnkoa29ob25lbikKbGlicmFyeShmYWN0b2V4dHJhKQpsaWJyYXJ5KGNsdXN0ZXIpCmxpYnJhcnkoRmFjdG9NaW5lUikKbGlicmFyeShGYWN0b3NoaW55KQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShza2ltcikKbGlicmFyeShndCkKbGlicmFyeShWSU0pCmxpYnJhcnkoY29ycnBsb3QpCmxpYnJhcnkoTmJDbHVzdCkKYGBgCgpcIyoqbGV0dHVyYSBlIHZpc3VhbGl6emF6aW9uZSBkYXRpKioKCmBgYHtyIGluY2x1ZGU9RkFMU0V9CmF0dGFjaCh3aW5lKQpgYGAKCmBgYHtyfQpWaWV3KHdpbmUpCnNraW0od2luZSkgIyBTa2ltIMOoIHVuYSBmdW56aW9uYSBjaGUgc2kgdHJvdmEgYWxsJ2ludGVybm8gZGVsIHBhY2NoZXR0byBTa2ltciwgbW9sdG8gc2ltaWxlIGFsIHN1bW1hcnkKYGBgCgoqKmNyZWlhYW1vIG51b3ZvIGRhdGFzZXQgZSB0b2dsaWFtbyBsYSBwcmltYSBjb2xvbm5hICh0eXBlKSoqCgpgYGB7cn0Kd2luZTEgPC0gd2luZVsyOjI4XSAKYGBgCgpcIyoqQ2hlY2sgZGVpIHZhbG9yaSBtYW5jYW50aSoqCgoqR3JhZmljbyBjaGUgY2kgbW9zdHJhIGNoZSBlZmZldHRpdmFtZW50ZSBub24gY2kgc29ubyB2YWxvcmkgbWFuY2FudGkKKGFiYmlhbW8gdXNhdG8gdW5hIGZ1bnNpb25lIGNoZSBzaSBjaGlhbWEgYWdnciBjaGUgc2kgdHJvdmEgbmVsCnBhY2NoZXR0byBWSU0pKgoKYGBge3J9CgphZ2dyKHdpbmUxKQoKYGBgCgpcIyoqQ29zdHJ1aWFtbyB1bmEgdGFiZWxsYSBkaSBmcmVxdWVuemEqKiAqUXVlc3RhIHRhYmVsbGEgZGkgZnJlcXVlbnphIMOoCnN0YXRhIHByZXNhIGluIGNvbnNpZGVyYXppb25lIGRhbGxhIHZhcmlhYmlsZSBUeXBlIGRlbCBkYXRhc2V0LCBjaQpzZXJ2aXLDoCBpbiB1biBzZWNvbmRvIG1vbWVudG8gcGVyIGNvbmZyb250YXJlIGkgdmFsb3JpIGRlaSBjbHVzdGVyCihhYmJpYW1vIHVzYXRvIGxhIGZ1bnppb25lIHRhYnlsIGNoZSBzaSB0cm92YSBuZWwgcGFjY2hldHRvIGphbml0b3IgZSBndApjaGUgc2kgdHJvdmEgbmVsIHBhY2NoZXR0byBndCkqCgpgYGB7cn0KdGFiX2ZyZXEgPC0gdGFieWwod2luZSwgVHlwZSkgJT4lIAogIGFkb3JuX3RvdGFscygpICU+JSAKICBhZG9ybl9wY3RfZm9ybWF0dGluZygpICU+JSAKICBtdXRhdGUoVmluaSA9IHJlY29kZShUeXBlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJzEnPSAnQmFyb2xvJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcyJyA9ICdHcmlnbm9saW5vJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICczJz0gJ0JhcmJlcmEnKSkgJT4lIAogIHJlbG9jYXRlKFZpbmksIC5hZnRlciA9IFR5cGUpICU+JSAKICBndCgpCnRhYl9mcmVxCmBgYAoKXCMqKkNoZWNrIG91dGxpZXIgZSB2aXN1YWxpenphemlvbmUgZGF0YXNldCoqCgoqUXVhbmRvIGZhY2NpYW1vIGwnYW5hbGlzaSBkZWkgZ3J1cHBpIGxlIHZhcmlhYmlsaSBkZXZvbm8gZXNzZXJlCm1pc3VyYXRlIHN1bGxhIHN0ZXNzYSBzY2FsYSwgcXVpbmRpIGRvYmJpYW1vIHN0YW5kYXJkaXp6YXJlIGxlIHZhcmlhYmlsaQpzZSBzb25vIG1vbHRvIGRpdmVyc2UgZGFsbGUgYWx0cmUuLi4qCgoqbm90aWFtbyBzdWJpdG8gY2hlIGNoZSBjaSBzb25vIGRlaSB2YWxvcmkgY2hlIHNpIGRpc2Nvc3Rhbm8gbW9sdG8gZGFnbGkKYWx0cmkqCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQp3aW5lMSAlPiUKICBnYXRoZXIoQXR0cmlidXRlcywgdmFsdWVzKSAlPiUKICBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihBdHRyaWJ1dGVzLCB2YWx1ZXMsIEZVTj1tZWRpYW4pLCB5PXZhbHVlcywgZmlsbD1BdHRyaWJ1dGVzKSkgKwogIGdlb21fYm94cGxvdChzaG93LmxlZ2VuZD1GQUxTRSkgKwogIGxhYnModGl0bGU9IldpbmVzIEF0dHJpYnV0ZXMgLSBCb3hwbG90cyIpICsKICB0aGVtZV9idygpICsKICB0aGVtZShheGlzLnRpdGxlLnk9ZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCkpICsKICB5bGltKDAsIDgwMCkgKwogIGNvb3JkX2ZsaXAoKQpgYGAKCioqQ29uIGxhIGZ1bnppb25lIHNjYWxlKCkgcGVyIGNpYXNjdW4gZGF0byB2aWVuZSBjYWxjb2xhdGEgbGEgZGV2aWF0YQpub3JtYWxlIHN0YW5kYXJkaXp6YXRhLiBJbiBwcmF0aWNhIHF1ZXN0YSBmdW56aW9uZSBwcmltYSBjYWxjb2xhIHBlciBpCmRhdGkgZGkgY2lhc2N1bmEgY29sb25uYS92YXJpYWJpbGUgbGEgbWVkaWEgZSBsYSBkZXZpYXppb25lIHN0YW5kYXJkLApwb2kgY2FsY29sYSBwZXIgY2lhc2N1bm8gZGF0byB4IGxhIGNvcnJpc3BvbmRlbnRlIGRldmlhdGEgbm9ybWFsZQpzdGFuZGFyZGl6emF0YSoqCgoqKnogPSAoeCAtLSBtZWRpYSkgLyBkZXZpYXppb25lIHN0YW5kYXJkKioKCipRdWFuZG8gdXNpYW1vIHNjYWxlLCBuZWwgY2FzbyBpbiBjdWkgdm9sZXNzaW1vIHVuIGRhdGFmcmFtZSBkb2JiaWFtbwpzcGVjaWZpY2FybG8sIGFsdHJpbWVudGkgcml0b3JuYSB1bmEgbWF0cmljZSBkaSBkYXRpKgoKYGBge3J9CndpbmUyX3MgPC0gYXMuZGF0YS5mcmFtZShzY2FsZShhcy5kYXRhLmZyYW1lKHdpbmUxKSkpCmBgYAoKKipCb3hQbG90IGNvbiBkYXRhc2V0IHN0YW5kYXJkaXp6YXRvKioKCmBgYHtyfQp3aW5lMl9zICU+JQogIGdhdGhlcihBdHRyaWJ1dGVzLCB2YWx1ZXMpICU+JQogIGdncGxvdChhZXMoeD1yZW9yZGVyKEF0dHJpYnV0ZXMsIHZhbHVlcywgRlVOPW1lZGlhbiksIHk9dmFsdWVzLCBmaWxsPUF0dHJpYnV0ZXMpKSArCiAgZ2VvbV9ib3hwbG90KHNob3cubGVnZW5kPUZBTFNFKSArCiAgbGFicyh0aXRsZT0iV2luZXMgQXR0cmlidXRlcyAtIEJveHBsb3RzIikgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKGF4aXMudGl0bGUueT1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSkgKwogIHlsaW0oMCwgMTApICsKICBjb29yZF9mbGlwKCkKYGBgCgoqKlZvZ2xpYW1vIGNlcmNhcmUgZGkgZmFyZSB1bmEgc2VsZXppb25lIGRpIHZhcmlhYmlsaSBhdHRyYXZlcnNvIGxhCnZpc3VhbGl6emF6emlvbmUgZGVpIGdyYWZpY2kqKgoKKipQYXJ0ZW5kbyBkYWwgcHJlc3VwcG9zdG8gY2hlIGFiYmlhbW8gMjcgdmFyaWFiaWxpLCBmYWNlbmRvIHVuIHBsb3QgZGkKdHV0dGUgbGUgdmFyaWFiaWxpIHNhcmViYmUgZGlmaWNpbGUgcml1c2NpcmUgYWQgaW5kaXZpZHVhcmUgaSBjbHVzdGVyCnBlciBsZSB2YXJpYWJpbGksIHF1aW5kaSBhYmJpYW1vIHBlbnNhdG8gZGkgZmFyZSAzIHN1YnBsb3QgY29uKioKClwqXCogSW1wb3N0aWFtbyBudW1lcmkgcmFuZG9tIGUgdXRpbGlhenppYW1vIGxhIGZ1bnppb25lIGttZWFuc1wqXCoKKkwnYWxnb3JpdG1vIEstbWVhbnMgw6ggdW4gYWxnb3JpdG1vIGRpIGFuYWxpc2kgZGVpIGdydXBwaSBwYXJ0aXppb25hbGUKY2hlIHBlcm1ldHRlIGRpIHN1ZGRpdmlkZXJlIHVuIGluc2llbWUgZGkgb2dnZXR0aSBpbiBrIGdydXBwaSBzdWxsYSBiYXNlCmRlaSBsb3JvIGF0dHJpYnV0aSoKCmBgYHtyfQpzZXQuc2VlZCgxMjMpCndpbmVfZGVucyA8LSBrbWVhbnMod2luZTJfcywgY2VudGVycz0zKQpzdW1tYXJ5KHdpbmVfZGVucykKYGBgCgpgYGB7ciBlY2hvPUZBTFNFLCBjYWNoZT1GQUxTRSwgY29tbWVudD1GQUxTRSwgcmVzdWx0cz1GQUxTRX0KZ2dwYWlycyhjYmluZCh3aW5lMSwgQ2x1c3Rlcj1hcy5mYWN0b3Iod2luZV9kZW5zJGNsdXN0ZXIpKSwKICAgICAgICBjb2x1bW5zPTE6OCwgYWVzKGNvbG91cj1DbHVzdGVyLCBhbHBoYT0wLjUpLAogICAgICAgIGxvd2VyPWxpc3QoY29udGludW91cz0icG9pbnRzIiksCiAgICAgICAgdXBwZXI9bGlzdChjb250aW51b3VzPSJibGFuayIpLAogICAgICAgIGF4aXNMYWJlbHM9Im5vbmUiLCBzd2l0Y2g9ImJvdGgiKSArCiAgdGhlbWVfYncoKQoKYGBgCgpgYGB7ciBlY2hvPUZBTFNFLCBjYWNoZT1GQUxTRSwgY29tbWVudD1GQUxTRSwgcmVzdWx0cz1GQUxTRX0KZ2dwYWlycyhjYmluZCh3aW5lMSwgQ2x1c3Rlcj1hcy5mYWN0b3Iod2luZV9kZW5zJGNsdXN0ZXIpKSwKICAgICAgICBjb2x1bW5zPTk6MTgsIGFlcyhjb2xvdXI9Q2x1c3RlciwgYWxwaGE9MC41KSwKICAgICAgICBsb3dlcj1saXN0KGNvbnRpbnVvdXM9InBvaW50cyIpLAogICAgICAgIHVwcGVyPWxpc3QoY29udGludW91cz0iYmxhbmsiKSwKICAgICAgICBheGlzTGFiZWxzPSJub25lIiwgc3dpdGNoPSJib3RoIikgKwogIHRoZW1lX2J3KCkKYGBgCgpgYGB7ciBlY2hvPUZBTFNFLCBjYWNoZT1GQUxTRSwgY29tbWVudD1GQUxTRSwgcmVzdWx0cz1GQUxTRX0KZ2dwYWlycyhjYmluZCh3aW5lMSwgQ2x1c3Rlcj1hcy5mYWN0b3Iod2luZV9kZW5zJGNsdXN0ZXIpKSwKICAgICAgICBjb2x1bW5zPTE5OjI3LCBhZXMoY29sb3VyPUNsdXN0ZXIsIGFscGhhPTAuNSksCiAgICAgICAgbG93ZXI9bGlzdChjb250aW51b3VzPSJwb2ludHMiKSwKICAgICAgICB1cHBlcj1saXN0KGNvbnRpbnVvdXM9ImJsYW5rIiksCiAgICAgICAgYXhpc0xhYmVscz0ibm9uZSIsIHN3aXRjaD0iYm90aCIpICsKICB0aGVtZV9idygpCmBgYAoKXCpcKlN0aWFtbyBjZXJjYW5kbyBkaSBmYXJlIHVuYSBzZWxlemlvbmUgZGVsbGUgZmVhdHVyZXMgYXR0cmF2ZXJzbyBpCmdyYWZpY2ksIGUgYWJiaWFtbyBwcm92YXRvIGEgZmFybG8gYW5jaGUgYXR0cmF2ZXJzbyBsYSBjb3JyZWxhemlvbmUsCnNpYW1vIGNlcnRpIGVzaXN0b25vIG1ldG9kaSBzdGF0aXN0aWNpIHBlciBsYSBzZWxlemlvbmUgZGVsbGUgZmVhdHVyZXMsCihhYmJpYW1vIHZpc3RvIHVuIHBhY2NoZXR0byBjaGUgc2kgY2hpYW1hIGNhcmV0KSBtYSBpbiBmaW5lIGFiYmlhbW8KZGVjaXNvIGRpIHRlbmVyZSBpbiBjb25zaWRlcmF6aW9uZSB0dXR0ZSBsZSB2YXJpYWJpbGkgZWNjZXR0byB0eXBlCgpgYGB7cn0KZ2djb3JyKHdpbmUyX3MsIGxvdyA9ICJuYXZ5IiwgaGlnaCA9ICJkYXJrcmVkIikKYGBgCgpcKlwqQ29uIHF1ZXN0byBpc3RvZ3JhbW1hIHZlZGlhbW8gcXVhbnRlIHZvbHRlIHNpIHJpcGV0b25vIHZhbG9yaSBkZWxsZQp2YXJpYWJpbGkKCmBgYHtyfQp3aW5lMl9zICU+JSAKICBnYXRoZXIoa2V5ID0gdmFsdWVfZ3JvdXBzLCB2YWx1ZSA9IHZhbHVlKSAlPiUgCiAgZ2dwbG90KGFlcyh4PXZhbHVlKSkgKwogIGdlb21faGlzdG9ncmFtKCkgKwogIGZhY2V0X3dyYXAoLn52YWx1ZV9ncm91cHMsIHNjYWxlcyA9ICJmcmVlIikKCmBgYAoKXCpcKiBDb24gdW4gcXEgcGxvdCB2ZWRpYW1vIGxlIGRpc3RyaWJ1emlvbmkgZGVsbGUgdmFyaWFiaWxpIGNoaW1pY2hlIGUKbm90aWFtbyBjaGUgc29ubyBwb2NoZSBsZSB2YXJpYWJpbGkgY2hlIHNvbm8gZGlzdHJpYnVpdGUgY29tZSB1bmEKbm9ybWFsZVwqXCoKCmBgYHtyfQoKd2luZTJfcyAlPiUgCiAgZ2F0aGVyKGtleSA9IHZhbHVlX2dyb3VwcywgdmFsdWUgPSB2YWx1ZSkgJT4lIAogIGdncGxvdChhZXMoc2FtcGxlPXZhbHVlLCBjb2xvdXI9dmFsdWVfZ3JvdXBzKSkgKwogIHN0YXRfcXEoKSArCiAgc3RhdF9xcV9saW5lKCkgKwogIGZhY2V0X3dyYXAoLn52YWx1ZV9ncm91cHMsIHNjYWxlcyA9ICJmcmVlX3kiKQoKYGBgCgoqKmRvYmJpYW1vIGRlZmluaXJlIHVuYSBtYXRyaWNlIGRpIGRpc3NpbWlsYXJpdMOgIC4uIGFuZGlhbW8gYSB2ZWRlcmUKcXVhbnRvIHNvbm8gc2ltaWxpIG8gZGl2ZXJzaSBwYXJ0ZW5kbyBkYWxsYSBub3N0cmEgbWF0cmljZSBkaSBkYXRpLiBQZXIKY2lhc2N1bmEgY29wcGlhIGRpIGluZGl2aWR1aSwgYW5kaWFtbyBhIHZlZGVyZSBwZXIgY2lhc2N1bmEgdmFyaWFiaWxlCnF1YW50byBxdWVzdGkgZHVlIGluZGl2aWR1aSBzb25vIGRpdmVyc2kgbyBzaW1pbGksIGEgc2Vjb25kYSBkZWwgdmFsb3JlClIgKCBpbCBwZXNvIGNoZSBub2kgZGlhbW8gYWxsZSBkaXN0YW56ZSkgb3R0ZW5pYW1vIGRlbGxlIG1ldHJpY2hlCmRpdmVyc2UsIGxhIHBpw7kgZmFtb3NhIMOoIGxhIG5vcm0gZXVjbGlkZWEgZG92ZSBSPTIqKgoKKkRlZmlhbmlhbW8gbGUgZGlzdGFuemUgKGV1Y2xpZGVhLG1hbmhhdHRhbixtaW5rb3dza2kpKgoKYGBge3J9Cm1hdCA8LSBoY2x1c3QoZGlzdCh3aW5lMl9zLCBtZXRob2QgPSAiZXVjbGlkZWFuIiksIG1ldGhvZD0id2FyZC5EIikgIyBtYXRyaWNlIGRlbGxlIGRpc3RhbnplIGV1Y2xpZGVlCm1hdDEgPC0gaGNsdXN0KGRpc3Qod2luZTJfcywgbWV0aG9kID0gIm1hbmhhdHRhbiIpLCBtZXRob2Q9IndhcmQuRCIpIyBkaXN0YW56YSBkaSBtYW5oYXR0YW4KbWF0MiA8LSBoY2x1c3QoZGlzdCh3aW5lMl9zLCBtZXRob2QgPSAibWlua293c2tpIiksIG1ldGhvZD0id2FyZC5EIikjIGRpc3RhbnphIGRpIG1pbmtvd3NraQpgYGAKClwqXCpRdWVzdGUgZnVuemlvbmkgY2kgY29uc2VudG9ubyBkaSBjYXBpcmUgbGEgcXVhbnRpdMOgIG90dGltYWxlIGRlaQpjbHVzdGVyIGNoZSBwb3NzaWFtbyBmYXJlIGNvbiBpbCBkYXRhc2V0CgpgYGB7cn0KZnZpel9uYmNsdXN0KHdpbmUyX3MsIEZVTmNsdXN0ZXIgPSBrbWVhbnMsIG5zdGFydD01MCwgInNpbGhvdWV0dGUiKSAjIElsIGNvZWZmaWNlbnRlIGRpIFNpbGhvdWV0dGUgbWlzdXJhIHF1YW50byBlJyBzaW1pbGUgdW4gb2dnZXR0byB4IGNvbiBnbGkgYWx0cmkgb2dnZXR0aSBuZWwgcHJvcHJpbyBjbHVzdGVyIGNvbnRybyBxdWVsbGkgbmVnbGkgYWx0cmkgY2x1c3Rlci4gSSB2YWxvcmkgcGl1JyB2aWNpbmkgYSAxIGluZGljYW5vIHVuIGNsdXN0ZXIgbWlnbGlvcmUKYGBgCgpgYGB7cn0KZnZpel9uYmNsdXN0KHdpbmUyX3MsIEZVTmNsdXN0ZXIgPSBrbWVhbnMsIG5zdGFydD04MCwgIndzcyIpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAzLCBsaW5ldHlwZT0yKQoKYGBgCgpgYGB7cn0KZnZpel9uYmNsdXN0KHdpbmUyX3MsIEZVTmNsdXN0ZXIgPSBrbWVhbnMsIG5zdGFydD0zMCwgImdhcF9zdGF0IiwgbmJvb3QgPSA1MCkKYGBgCgpcKlwqIFF1ZXN0YSBmdW56aW9uZSDDqCB1biB0ZXN0IGNoZSBnZW5lcmEgaW4gYXV0b21hdGljbyBpbCBudW1lcm8gZGkKY2x1c3RlciBjaGUgc2FyZWJiZSBtZWdsaW8gdGlyYXIgZnVvcmlpbiBxdWVzdG8gY2FzbyBpbCBudW1lcm8gZGVsbGUKY2xhc3NzaSBtaWdsaW9yaSDDqCAzXCpcKgoKYGBge3J9CnggPC0gTmJDbHVzdCh3aW5lMl9zLCBkaXN0YW5jZSA9ICJldWNsaWRlYW4iLCBtaW4ubmMgPSAyLCBtYXgubmMgPSAxMCwgbWV0aG9kID0gIndhcmQuRDIiKQpgYGAKClwqXCogVmlzdWFsaXp6aWFtbyB0dXR0ZSBsZSBwcm9wb3N0ZSBcKlwqCgpgYGB7cn0KeCRBbGwuaW5kZXgKYGBgCgoqKlN1bGxhIGJhc2UgZGVsbGUgZGlzdGFuemUgY2hlIGFiYmlhbW8gYW5kaWFtbyBhIGNvbnNpZGVyYXJlIOKXjyBTaW5nbGUKbGlua2FnZSAtLS1cPiBOZWFyZXN0IE5laWdoYm9yIChpbCB2aWNpbm8gcGnDuSB2aWNpbm8pIGNlcmNhIGRpIG1ldHRlcmUg4pePCkNvbXBsZXRlIGxpbmthZ2UgLS0tXD4gaWwgdmljaW5vIHBpw7kgbG9udGFubyBjZXJjYSBkaSBtZXR0ZXJlIGluc2llbWUKZ3J1cHBpIHRyYSBkaSBsb3JvIGNoZSBzb25vIHZpY2luaSBjb21wYXJhbmRvIGxlIGRpc3RhbnplIGZyYSBkaSBsb3JvIOKXjwpBdmVyYWdlIGxpbmthZ2UgLS0tXD4gaWwgbGlua2FnZSBtZWRpbyBmYSB1bmEgbWVkaWEgZGVpIGR1ZSDil48gV2FyZCdzCm1pbmltdW0gdmFyaWFuY2UgbGlua2FnZSAtLS1cPiBpbCB3b3JkIGlvbiBnZW5lcmUgw6ggcXVlbGxvIGNoZSBmdW56aW9uYQptZWdsaW8uLi53b3JkIG1pbmltaXp6YSBsYSB2YXJpYW56YSB3aXRoaW4uLi5wZXJjaMOpIGlsIG1ldG9kbyBkaSB3b3JkCm5vbiBzaSBiYXNhIHN1bGxhIGRpc3RhbnphIG1hIGhhIGwnaWRlYSBkaSBtaW5pbWl6emFyZSBsJ2luZXJ6aWEgb3Z2ZXJvCmxhIHZhcmlhbnphLi4uLnF1aW5kaSBsdWkgdGVuZGUgYSBmYXIgc2kgY2hlIGxhIHZhcmlhbnphIHdoaXRpbiBub24gc2lhCnRyb3BwbyBncmFuZGUqKgoKKipEZWZpbmlhbW8gbGUgdmFyaWFiaWxpIHBlciBsYSB2aXN1YWxpenphemlvbmUgZGVpIGRlbmRvZ3JhbW1pKioKYGBge3J9CmRfZXVjbGkgPC0gZGlzdCh3aW5lMl9zLCBtZXRob2QgPSAiZXVjbGlkZWFuIikgIyBkaXN0YW56YSBldWNsaWRlCndpbmVfaGMgPC0gaGNsdXN0KGRfZXVjbGksIG1ldGhvZCA9ICJ3YXJkLkQyIikKZnZpel9kZW5kKHdpbmVfaGMsIGsgPSAzLCBzaG93X2xhYmVscyA9IEYsIHJlY3QgPSBUKSAjZGVuZG9ncmFtbWEgbWlnbGlvcmUKYGBgCgpgYGB7cn0Kewp2aW5pX2NvbXBsZXRlIDwtIGhjbHVzdChkX2V1Y2xpLCBtZXRob2QgPSAiY29tcGxldGUiKSAgICMjIGNsdXN0ZXIgd2l0aCAiY29tcGxldGUiIGxpbmthZ2UKdmluaV9zaW5nbGUgPC0gaGNsdXN0KGRfZXVjbGksIG1ldGhvZCA9ICJzaW5nbGUiKSAgICAgIyMgY2x1c3RlciB3aXRoICJzaW5nbGUiIGxpbmthZ2UKdmluaV9hdmVyYWdlIDwtIGhjbHVzdChkX2V1Y2xpLCBtZXRob2QgPSAiYXZlcmFnZSIpICAgIyMgY2x1c3RlciB3aXRoICJhdmVyYWdlIiBsaW5rYWdlCnZpbmlfd2FyZCA8LSBoY2x1c3QoZF9ldWNsaSwgbWV0aG9kID0gIndhcmQuRDIiKSAgICAgICMjIGNsdXN0ZXIgd2l0aCAid2FyZCIgbGlua2FnZQp9CmBgYAoKKipGYWNjaWFtbyB1bmEgY29tcGFyYXppb25lIGRlaSBkZW5kb2dyYW1taSBwZXIgdmVkZXJlIHF1YWxlIGNpIHNlbWJyYQppbCBtaWdsaW9yZSoqCgpgYGB7cn0KcGFyKG1mcm93ID0gYygyLCAyKSkKcGxvdCh2aW5pX2NvbXBsZXRlLCBjZXggPSAwLjgsIGhhbmcgPSAtMSwgbWFpbiA9ICJIaWVyYXJjaGljYWwgQ2x1c3RlcmluZyB3aXRoIENvbXBsZXRlIExpbmthZ2UiKQpwbG90KHZpbmlfc2luZ2xlLCBjZXggPSAwLjgsIGhhbmcgPSAtMSwgbWFpbiA9ICJIaWVyYXJjaGljYWwgQ2x1c3RlcmluZyB3aXRoIFNpbmdsZSBMaW5rYWdlIikKcGxvdCh2aW5pX2F2ZXJhZ2UsIGNleCA9IDAuOCwgaGFuZyA9IC0xLCBtYWluID0gIkhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nIHdpdGggQXZlcmFnZSBMaW5rYWdlIikKcGxvdCh2aW5pX3dhcmQsIGNleCA9IDAuOCwgaGFuZyA9IC0xLCBtYWluID0gIkhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nIHdpdGggV2FyZCBMaW5rYWdlIikKYGBgCgoqKkFiYmlhbW8gb3B0YXRvIHBlciBpbCBkZW5kb2dyYW1tYSBtaWdsaW9yZSxhYmJpYW1vIGV2aWRlbnppYXRvIGlsCmRlbmRvZ3JhbW1hIGNvbiBjb2xvcmkgZGl2ZXJzaSBlIGFiYmlhbW8gdG9sdG8gaSBudW1lcmkgcGVyIHVuYQp2aXN1YWxpenphemlvbmUgbWlnbGlvcmUqKgoKKipJbCBjZW50cm9pZGUgw6ggdW4gcHVudG8gYXBwYXJ0ZW5lbnRlIGFsbG8gc3BhemlvIGRlbGxlIGZlYXR1cmVzIGNoZQptZWRpYSBsZSBkaXN0YW56ZSB0cmEgdHV0dGkgaSBkYXRpIGFwcGFydGVuZW50aSBhbCBjbHVzdGVyIGFkIGVzc28KYXNzb2NpYXRvLiBSYXBwcmVzZW50YSBxdWluZGkgdW5hIHNvcnRhIGRpIGJhcmljZW50cm8gZGVsIGNsdXN0ZXIgZWQgaW4KZ2VuZXJhbGUsIHByb3ByaW8gcGVyIGxlIHN1ZSBjYXJhdHRlcmlzdGljaGUsIG5vbiDDqCB1bm8gZGVpIHB1bnRpIGRlbApkYXRhc2V0LiBOb24gY29ub3NjZW5kbyBsZSBjbGFzc2kgcHJlc2VudGkgbmVsIGRhdGFzZXQgZGkgaW5ncmVzc28sIGxhCnByaW1hIGNvc2EgZGEgZmFyZSDDqCBkZWNpZGVyZSBpbCBudW1lcm8gZGkgY2xhc3NpLCBTaSBzY2VsZ29ubyBpbiBtb2RvCmNhc3VhbGUgSyBjZW50cm9pZGkgYXBwYXJ0ZW5lbnRpIGFsbG8gc3BhemlvIGRlbGxlIGZlYXR1cmVzLiBTaSBjYWxjb2xhCmxhIGRpc3RhbnphIGRpIG9nbmkgcHVudG8gZGVsIGRhdGFzZXQgcmlzcGV0dG8gYWQgb2duaSBjZW50cm9pZGUgT2duaQpwdW50byBkZWwgZGF0YXNldCB2aWVuZSBhc3NvY2lhdG8gYWwgY2x1c3RlciBjb2xsZWdhdG8gYWwgY2VudHJvaWRlIHBpw7kKdmljaW5vIFNpIHJpY2FsY29sYSBsYSBwb3NpemlvbmUgZGkgb2duaSBjZW50cm9pZGUgZmFjZW5kbyBsYSBtZWRpYQpkZWxsZSBwb3NpemlvbmkgZGkgdHV0dGkgaSBwdW50aSBkZWwgY2x1c3RlciBhc3NvY2lhdG8gU2kgaXRlcmEgZGFsCnB1bnRvIDMgZmlubyBhIHF1YW5kbyBub24gY2kgc2Fyw6AgcGnDuSBhbGN1biBpbmdyZXNzbyBjaGUgY2FtYmlhIGRpCmNsdXN0ZXIuKioKCmBgYHtyfQpzZXQuc2VlZCgxMjMpCnZpbmlfa21lYW5zIDwtIGttZWFucyh3aW5lMl9zLCBjZW50ZXJzID0gMykKdmluaV9rbWVhbnMKCnRhYmxlKHdpbmUkVHlwZSx2aW5pX2ttZWFucyRjbHVzdGVyKQpgYGAKCmBgYHtyfQpmdml6X2NsdXN0ZXIodmluaV9rbWVhbnMsIGRhdGEgPSB3aW5lMl9zLAogICAgICAgICAgICAgcGFsZXR0ZSA9IGMoIiMyRTlGREYiLCAiIzAwQUZCQiIsICIjRTdCODAwIiksIAogICAgICAgICAgICAgZ2VvbSA9ICJwb2ludCIsCiAgICAgICAgICAgICBlbGxpcHNlLnR5cGUgPSAiY29udmV4IiwgCiAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfYncoKQopCnByaW50KHZpbmlfa21lYW5zKQpgYGAKCmBgYHtyfQp2aW5pX2ttZWFucwpgYGAKCmBgYHtyfQpiYXIgPC0gc3Vic2V0KHdpbmUsIFR5cGUgPT0gIjEiKQpiYXIyIDwtIHN1YnNldCh3aW5lLCBUeXBlID09ICIyIikKYmFyMyA8LSBzdWJzZXQod2luZSwgVHlwZSA9PSAiMyIpCm1lYW4oYmFyJEFsY29ob2wpCm1lYW4oYmFyMiRBbGNvaG9sKQptZWFuKGJhcjMkQWxjb2hvbCkKCmJhciA8LSBzdWJzZXQod2luZSwgVHlwZSA9PSAiMSIpCmJhcjIgPC0gc3Vic2V0KHdpbmUsIFR5cGUgPT0gIjIiKQpiYXIzIDwtIHN1YnNldCh3aW5lLCBUeXBlID09ICIzIikKbWVhbihiYXIkUHJvbGluZSkKbWVhbihiYXIyJFByb2xpbmUpCm1lYW4oYmFyMyRQcm9saW5lKQpgYGAKCioqY2x1c3RlciBiYXNhdG8gc3UgbW9kZWxsaSBpZGVudGlmaWNoaWFtbyBsYSBjbGFzc2UsIG92dmVybyBpbCB0aXBvIGRlaQp2aW5pOioqCgpgYGB7cn0KY2xhc3MgPC0gZmFjdG9yKHdpbmUkVHlwZSwgbGV2ZWxzID0gMTozLCBsYWJlbHMgPSBjKCJCYXJvbG8iLCAiR3JpZ25vbGlubyIsICJCYXJiZXJhIikpCnRhYmxlKGNsYXNzKQpgYGAKCioqU2VsZXppb25lIGRlbGxlIHZhcmlhYmlsaSBjb24gY2x1c3R2YXJzZWwqKgoKYGBge3J9CmxpYnJhcnkoY2x1c3R2YXJzZWwpI0xhIG1ldG9kb2xvZ2lhIGNvbnNlbnRlIGRpIHRyb3ZhcmUgaWwgc290dG9pbnNpZW1lIChsb2NhbG1lbnRlKSBvdHRpbWFsZSBkaSB2YXJpYWJpbGkgaW4gdW4gc2V0IGRpIGRhdGkgY2hlIGhhbm5vIGluZm9ybWF6aW9uaSBkaSBncnVwcG8vY2x1c3Rlci4Kb3V0IDwtIGNsdXN0dmFyc2VsKHdpbmUxKSAgICAgICMgdXRpbGl6em8gbGEgZnVuemlvbmUgY2x1c3R2YXJzZWwgcGVyIHNlbGV6aW9uYXJlIGxlIHZhcmliaWxpIG90dGltYWxpCm91dCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgYWkgZmluaSBkZWwgY2x1c3RlcmluZywgaW4gbW9kbyBkYSB1dGlsaXp6YXJsZSBuZWkgZ3JhZmljaQpvdXQkc3Vic2V0CnNlbGVjdGVkLnZhciA8LSB3aW5lMVtvdXQkc3Vic2V0XQpzZWxlY3RlZC52YXIKIyBMZSB2YXJpYWJpbGkgc2VsZXppb25hdGUgc29ubzogQ2hsb3JpZGUsIE1hbGljIEFjaWQsIEZsYXZhbm9pZHMsIENvbG9yIEludGVuc2l0eSwgUHJvbGluZSwgVXJvbmljIEFjaWRzLAojT0QyODAvT0QzMTUgb2YgRmxhdmFub2lkcwojbGEgcHJvbGluYSBpbmRpY2EgbGEgc2VjY2hlenphIGRlbCB2aW5vLCBsJ2FjaWRvIG1hbGljbyDDqCBsJ2FjaWRvIHBpw7kgZGlmZnVzbyBuZWwgcmVnbm8gdmVnZXRhbGUKI2lsIGdydXBwbyBkZWkgZmxhdm9ub2lkaSBoYSB1bmEgY29uY2VudHJhemlvbmUgcGnDuSBlbGV2YXRhIG5laSB2aW5pIHJvc3NpLCBlIHN2b2xnb25vIHVuIGF6aW9uZSBwcm90ZXR0aXZhIGNvbnRybyBsZSByYWRpYXppb25pIHVsdHJhdmlvbGV0dGUKCmBgYAoKKipBbmFsaXNpIGVzcGxvcmF0aXZhIGRlbGxlIHZhcmlhYmlsaSBzZWxlemlvbmF0ZSoqCgpgYGB7ciBlY2hvPUZBTFNFLCBjYWNoZT1GQUxTRSwgY29tbWVudD1GQUxTRSwgcmVzdWx0cz1GQUxTRX0KbGlicmFyeShHR2FsbHkpCmdncGFpcnMoc2VsZWN0ZWQudmFyLCB0aXRsZT0iQW5hbGlzaSBlc3Bsb3JhdGl2YSBkZWxsZSB2YXJpYWJpbGkgc2VsZXppb25hdGUiKSsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQyIikKCmBgYAoKKipzZWxlemlvbmUgZGVsbGUgdmFyaWFiaWxpIGNvbiBsYSBsaWJyZXJpYSAiQk9SVVRBIixCb3J1dGEgw6ggdW4KYWxnb3JpdG1vIGRpIGNsYXNzaWZpY2F6aW9uZSBlIHNlbGV6aW9uZSBkZWxsZSBjYXJhdHRlcmlzdGljaGUgYmFzYXRvIHN1CnVuIGFsZ29yaXRtbyBkaSBmb3Jlc3RlIGNhc3VhbGkuIHZhbnRhZ2dpbyBkaSBCb3J1dGEgw6ggY2hlIGRlY2lkZQpjaGlhcmFtZW50ZSBzZSB1bmEgdmFyaWFiaWxlIMOoIGltcG9ydGFudGUgbyBtZW5vIGUgYWl1dGEgYSBzZWxlemlvbmFyZQp2YXJpYWJpbGkgc3RhdGlzdGljYW1lbnRlIHNpZ25pZmljYXRpdmUuKioKCmBgYHtyIGVjaG89RkFMU0UsIGNhY2hlPUZBTFNFLCBjb21tZW50PUZBTFNFLCByZXN1bHRzPUZBTFNFfQpsaWJyYXJ5KEJvcnV0YSkKc2V0LnNlZWQoMTEwKQpib3J1dGFfb3V0cHV0IDwtIEJvcnV0YShUeXBlIH4gLiwgZGF0YSA9IHdpbmUsIGRvVHJhY2U9MiwgbWF4UnVucz0xMDApCm5hbWVzKGJvcnV0YV9vdXRwdXQpCnByaW50KGJvcnV0YV9vdXRwdXQpCnBsb3QoYm9ydXRhX291dHB1dCxjZXguYXhpcz0uNywgbGFzPTIpCnBsb3RJbXBIaXN0b3J5KGJvcnV0YV9vdXRwdXQpCmBgYAoKKipzZWxlemlvbmUgZGVsbGUgdmFyaWFiaWxpIGNvbiBpbCBtZXRvZG8gU2VsdmFyQ2x1c3RMYXNzbywgcXVlc3RhCmZ1bnppb25lIGltcGxlbWVudGEgbGEgc2VsZXppb25lIGRlbGxlIHZhcmlhYmlsaSBuZWwgY2x1c3RlcmluZyBiYXNhdG8Kc3UgbW9kZWxsaSoqCgpgYGB7cn0KbGlicmFyeShzcGFyY2wpIApsaWJyYXJ5KHZzY2MpIApsaWJyYXJ5KFNlbHZhck1peCkgCnJlcXVpcmUoUm1peG1vZCkKcmVxdWlyZShnbGFzc28pCmxhc3NvIDwtIFNlbHZhckNsdXN0TGFzc28oeD13aW5lMSwgbmJjbHVzdGVyPTE6Myxjcml0ZXJpbz0iSUNMIikKc3VtbWFyeShsYXNzbykKbGFzc28kUyAjdmFyaWFiaWxpIGFjY2V0dGF0ZQpsYXNzbyRSICN2YXJpYWJpbGkgcGVyIHJlZ3Jlc3Npb25lCmxhc3NvJFUgI3ZhcmlhYmlsaSByaWRvbmRhbnRpCmxhc3NvJG5iY2x1c3Rlcgp0YWJsZShsYXNzbyRwYXJ0aXRpb24sd2luZVssMV0pCmBgYAoKXCpcKiBNT0RFTCAtIEJBU0VEIENMVVNURVJJTkcgYXBwbGljYXRvIGEgdHV0dG8gaWwgZGF0YXNldCB3aW5lIFwqXCoKCmBgYHtyIGVjaG89RkFMU0UsIGNhY2hlPUZBTFNFLCBjb21tZW50PUZBTFNFLCByZXN1bHRzPUZBTFNFfQpsaWJyYXJ5KG1jbHVzdCkKWCA8LSBkYXRhLm1hdHJpeCh3aW5lWywtMV0pCm1vZCA8LSBNY2x1c3QoWCkgCnN1bW1hcnkobW9kJEJJQykKc3VtbWFyeShtb2QpCnRhYmxlKGNsYXNzLCBtb2QkY2xhc3NpZmljYXRpb24pIAphZGp1c3RlZFJhbmRJbmRleChjbGFzcywgbW9kJGNsYXNzaWZpY2F0aW9uKQoKYGBgCgoqKklsIG1pZ2xpb3IgbW9kZWxsbyDDqCBFVkksIGNvbiB0cmUgY2x1c3RlciwgRVZJIGhhIGRpc3RydWJ1emlvbmUKZGlhZ29uYWxlLCB2b2x1bWUgdWd1YWxlIGUgZm9ybWEgdmFyaWFiaWxlIChhZ2dpdW5nZXJlIGZvdG8pKioKCioqcmFwcHJlc2VudGF6aW9uZSBkZWkgcmlzdWx0YXRpKioKCioqcmFwcHJlc2VudGF6aW9uZSBkZWkgdmFsb3JpIEJpYyB1dGlsaXp6YXRpIHBlciBsYSBzY2VsdGEgZGVsIG51bWVybwpkZWkgY2x1c3RlcioqCgpgYGB7cn0KcGxvdChtb2QsIHdoYXQgPSAiQklDIiwgeWxpbSA9IHJhbmdlKG1vZCRCSUNbLC0oMToyKV0sIG5hLnJtID0gVFJVRSksIGxlZ2VuZEFyZ3MgPSBsaXN0KHggPSAiYm90dG9tbGVmdCIpKQoKYGBgCgpcKlwqIHJhcHByZXNlbnRhemlvbmUgZGVpIHJpc3VsdGF0aSBjb24gbGEgbGlicmVyaWEKZmFjdG9leHRyYUNsYXNzaWZpY2F6aW9uZTogZ3JhZmljbyBjaGUgbW9zdHJhIGlsIHJhZ2dydXBwYW1lbnRvXCpcKgoKYGBge3J9CmxpYnJhcnkoZmFjdG9leHRyYSkKZnZpel9tY2x1c3QobW9kLCAiY2xhc3NpZmljYXRpb24iLCBnZW9tID0gInBvaW50IiwgCiAgICAgICAgICAgIHBvaW50c2l6ZSA9IDEuNSwgcGFsZXR0ZSA9ICJqY28iKQpgYGAKCioqQ2xhc3NpZmljYXppb25lIGRlbGwnaW5jZXJ0ZXp6YSoqCgpgYGB7cn0KZnZpel9tY2x1c3QobW9kLCAidW5jZXJ0YWludHkiLCBwYWxldHRlID0gImpjbyIpCgpgYGAKClwqXCogTWNsdXN0IGNvbiBsZSB2YXJpYWJpbGkgc2VsZXppb25hdGUgY29uIGlsIG1ldG9kbyBkaQpjbHVzdHZhcnNlbFwqXCoKCmBgYHtyIGVjaG89RkFMU0UsIGNhY2hlPUZBTFNFLCBjb21tZW50PUZBTFNFLCByZXN1bHRzPUZBTFNFfQptY2x1c3Quc2VsZWN0ZWQgPC0gTWNsdXN0KHNlbGVjdGVkLnZhcikKc3VtbWFyeShtY2x1c3Quc2VsZWN0ZWQpCm1jbHVzdC5zZWxlY3RlZAp0YWJsZShjbGFzcywgbWNsdXN0LnNlbGVjdGVkJGNsYXNzaWZpY2F0aW9uKQpgYGAKCmBgYHtyfQpmdml6X21jbHVzdChtY2x1c3Quc2VsZWN0ZWQsICJjbGFzc2lmaWNhdGlvbiIsIGdlb20gPSAicG9pbnQiLCAKICAgICAgICAgICAgcG9pbnRzaXplID0gMS41LCBwYWxldHRlID0gInNldDIiKQoKYGBgCgpgYGB7cn0KZnZpel9tY2x1c3QobWNsdXN0LnNlbGVjdGVkLCAidW5jZXJ0YWludHkiLCBwYWxldHRlID0gInNldDIiKQpgYGAKClwqXCogbWNsdXN0IGNvbiBsZSB2YXJpYWJpbGUgc2VsZXppb25hdGUgY2kgZGljZSBjZ2UgaWwgbWlnbGlvciBtb2RlbGxvCsOoIEVWSSwgbWEgY29uIDcgY2x1c3RlclwqXCoKCioqb3JhIHJpZmFjY2lhbW8gbGEgc3Rlc3NhIGZ1bnppb25lLCBtYSBpbXBvc3RhbmRvIGlsIG51bWVybyBkaSBjbHVzdGVyCmEgMyoqCgpgYGB7ciBlY2hvPUZBTFNFLCBjYWNoZT1GQUxTRSwgY29tbWVudD1GQUxTRSwgcmVzdWx0cz1GQUxTRX0KbWNsdXN0LnNlbGVjdGVkMSA8LSBNY2x1c3Qoc2VsZWN0ZWQudmFyLCBHPTMpCnN1bW1hcnkobWNsdXN0LnNlbGVjdGVkMSkKbWNsdXN0LnNlbGVjdGVkMQp0YWJsZShjbGFzcywgbWNsdXN0LnNlbGVjdGVkMSRjbGFzc2lmaWNhdGlvbikKYGBgCgpgYGB7cn0KcGxvdChtY2x1c3Quc2VsZWN0ZWQxLCB3aGF0ID0gImNsYXNzaWZpY2F0aW9uIikKYGBgCgoqKm1jbHVzdERBIEFuYWxpc2kgZGlzY3JpbWluYW50ZSBiYXNhdGEgc3VsbGEgbW9kZWxsYXppb25lIGRpIG1pc2NlbGUKZmluaXRlIGdhdXNzaWFuZSoqCgpgYGB7cn0Kd2luZU1jbHVzdERBIDwtIE1jbHVzdERBKHNlbGVjdGVkLnZhciwgY2xhc3MsIG1vZGVsVHlwZSA9ICJFRERBIiwgbW9kZWxOYW1lcyA9ICJFRUUiKQpzdW1tYXJ5KHdpbmVNY2x1c3REQSwgcGFyYW1ldGVycyA9IFRSVUUpCnN1bW1hcnkod2luZU1jbHVzdERBLCBuZXdkYXRhID0gc2VsZWN0ZWQudmFyLCBuZXdjbGFzcyA9IGNsYXNzKQoKcGxvdCh3aW5lTWNsdXN0REEpCgoKYGBgCgpgYGB7ciBlY2hvPUZBTFNFLCBjYWNoZT1GQUxTRSwgY29tbWVudD1GQUxTRSwgcmVzdWx0cz1GQUxTRX0KZGVuc2l0eSA8LWRlbnNpdHlNY2x1c3Qoc2VsZWN0ZWQudmFyKQpzdW1tYXJ5KGRlbnNpdHkpCgpgYGAKCioqdmlzdWFsaXp6aWFtbyBpIGNsdXN0ZXIgc3UgdW4gYmlwbG90IFBDQSoqCgpgYGB7cn0Kd2luZV9wY2EgPC0gcHJjb21wKFgsIHNjYWxlLiA9IFRSVUUpICNFc2VndWUgdW4nYW5hbGlzaSBkZWkgY29tcG9uZW50aSBwcmluY2lwYWxpIHN1bGxhIG1hdHJpY2UgZGkgZGF0aSBkYXRhIGUgcmVzdGl0dWlzY2UgaSByaXN1bHRhdGkgY29tZSBvZ2dldHRvIGRpIGNsYXNzZSBwcmNvbXAuCgpgYGAKCmBgYHtyfQpsaWJyYXJ5KGdnYmlwbG90KQptY19jbHVzdGVycyA8LSBmYWN0b3IobW9kJGNsYXNzaWZpY2F0aW9uKQpnZ2JpcGxvdCh3aW5lX3BjYSwgZ3JvdXBzID0gbWNfY2x1c3RlcnMpICsKICBsYWJzKGNvbG9yID0gIm1jbHVzdCBjbHVzdGVycyIpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJTZXQyIikgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDAuOCwgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCgpgYGAKClwqXCogYW5hbGlzaSBkZWxsZSBjb21wb25lbnRpIHByaW5jaXBhw7JpIGNvbiBpbCBkYXRhc2V0IHJpZG90dG9cKlwqCgpgYGB7cn0KCndpbmVfcGNhMSA8LSBwcmNvbXAoc2VsZWN0ZWQudmFyLCBzY2FsZS4gPSBUUlVFKQptY19jbHVzdGVyczEgPC0gZmFjdG9yKG1jbHVzdC5zZWxlY3RlZDEkY2xhc3NpZmljYXRpb24pCmdnYmlwbG90KHdpbmVfcGNhMSwgZ3JvdXBzID0gbWNfY2x1c3RlcnMxKSArCiAgbGFicyhjb2xvciA9ICJtY2x1c3QgY2x1c3RlcnMiKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiU2V0MiIpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAwLjgsIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQoKYGBgCg==