Introduzione

In questo notebook, esploreremo sia l’Analisi delle Componenti Principali (PCA) che l’Analisi Fattoriale Esplorativa (EFA) su diversi dataset: alcuni generati, altri pre-caricati di R (come iris e mtcars), e un dataset reale (ANSES-CIQUAL food composition table 2020).

# Alcuni pacchetti che useremo: se non presenti installatele con il comando install.packages()
library(readxl)
library(tidyr, stringr)
library(ggplot2, ggcorrplot)
library(corrplot)
library(ggfortify)

Esempi con dataset generati

Come nel notebook precedente, generiamo dei dati partendo da campioni gaussiani di medie e deviazioni standard specificate.

for( i in 2:d ){
  data_gen <- cbind( data_gen,  rnorm(n, mean=0, sd=1))
  names_data_gen <- c(names_data_gen, print(paste0("feature_", as.character(i))))
}
[1] "feature_2"
[1] "feature_3"
[1] "feature_4"
[1] "feature_5"

In questo dataset la varianza campionaria dovrebbe essere all’incirca diagonale con degli \(1\) sulla diagonale – perché le colonne sono state generate in modo indipendente e abbiamo specificato la deviazione standard teorica sd=1.

I comandi per la varianza, covarianza e correlazione sono rispettivamente var(), cov() e cor().

var_data_gen
            feature_1   feature_2   feature_3  feature_4   feature_5
feature_1  0.90319778 -0.04827194  0.10746503 0.06946613 -0.21129440
feature_2 -0.04827194  1.03936852 -0.06219997 0.14722352 -0.13245614
feature_3  0.10746503 -0.06219997  1.01835020 0.03320205 -0.25230152
feature_4  0.06946613  0.14722352  0.03320205 0.83073817  0.05959285
feature_5 -0.21129440 -0.13245614 -0.25230152 0.05959285  1.15805656
cor_data_gen
            feature_1   feature_2   feature_3  feature_4   feature_5
feature_1  1.00000000 -0.04982170  0.11205394 0.08019543 -0.20660064
feature_2 -0.04982170  1.00000000 -0.06045843 0.15843836 -0.12073201
feature_3  0.11205394 -0.06045843  1.00000000 0.03609810 -0.23233060
feature_4  0.08019543  0.15843836  0.03609810 1.00000000  0.06075712
feature_5 -0.20660064 -0.12073201 -0.23233060 0.06075712  1.00000000

Con cov() e cor() è possibile anche calcolare covarianze tra coppie di data frames.

cor(data_gen[-3], data_gen[3])
            feature_3
feature_1  0.11205394
feature_2 -0.06045843
feature_4  0.03609810
feature_5 -0.23233060

Per visualizzare le correlazioni mediante una mappa di calore possiamo usare il pacchetto corrplot oppure ggcorrplot (che estende ggplot2).

# installare corrplot se non è già presente
library(corrplot)

# plot di base
corrplot(cor_data_gen)

Ci sono moltissime opzioni per migliorare il grafico (leggere l’help per informazioni).

corrplot(cor_data_gen, method="pie", type="lower", diag=FALSE, number.cex=1, number.digits = 2, title="Dataset generato (features indipendenti)")

Similmente, usando ggcorrplot si producono grafici con ggplot.

Dataset con correlazioni

Le matrici trovate di correlazione sono poco interessanti, perché il dataset non presenta associazioni interessanti tra le varie features. Per introdurre artificialmente queste correlazioni, trasformiamo le features con una mappa opportuna.

In questo caso il correlogramma è più interessante

Dalla teoria sappiamo che \(\operatorname{var}(xA) = A^T \operatorname{var}(x) A \approx A^T A\) essendo \(\operatorname{var}(x)\approx I_{d\times d}\). Verifichiamolo numericamente. Calcoliamo la varianza campionaria.

var(data_gen_A)
           feature_1  feature_2  feature_3  feature_4 feature_5
feature_1  2.3673428  0.7354934  1.6560901 -1.0357747 -1.973163
feature_2  0.7354934  5.3050634 -0.3901245 -1.5062156  1.309246
feature_3  1.6560901 -0.3901245  2.7275217 -0.8481627 -1.888022
feature_4 -1.0357747 -1.5062156 -0.8481627  1.9154925  1.637760
feature_5 -1.9731634  1.3092457 -1.8880223  1.6377603  3.822365

Per calcolare la trasposta di una matrice usiamo la funzione t().

cov_A_teorica <- t(A) %*% A
cov_A_teorica
     [,1] [,2] [,3] [,4] [,5]
[1,]    3    1    2   -1   -2
[2,]    1    4    0   -1    1
[3,]    2    0    3   -1   -2
[4,]   -1   -1   -1    2    2
[5,]   -2    1   -2    2    4

PCA sul Dataset Generato

Per eseguire l’analisi delle componenti principali, possiamo usare il comando prcomp() (consigliato) oppure il comando princomp(). Si veda https://cran.r-project.org/web/packages/LearnPCA/vignettes/Vig_07_Functions_PCA.pdf per una discussione delle differenze.

È fortemente consigliato per la PCA di standardizzare i dati. Possiamo farlo prima con il comando scale()


# usiamo direttamente il dataset generato con correlazioni indotte da A
data_gen <- data_gen_A

# standardizziamo
data_gen_standard <- scale(data_gen, center=TRUE, scale=TRUE)

# Notiamo che la media è nulla per ciascuna feature
summary(data_gen_standard)
   feature_1          feature_2         feature_3      
 Min.   :-2.19265   Min.   :-2.4748   Min.   :-2.6179  
 1st Qu.:-0.74504   1st Qu.:-0.6652   1st Qu.:-0.8311  
 Median : 0.02771   Median : 0.0973   Median : 0.1511  
 Mean   : 0.00000   Mean   : 0.0000   Mean   : 0.0000  
 3rd Qu.: 0.71495   3rd Qu.: 0.6608   3rd Qu.: 0.7401  
 Max.   : 2.48814   Max.   : 2.1326   Max.   : 2.2966  
   feature_4         feature_5       
 Min.   :-2.1960   Min.   :-2.24490  
 1st Qu.:-0.7081   1st Qu.:-0.69485  
 Median :-0.1085   Median :-0.05175  
 Mean   : 0.0000   Mean   : 0.00000  
 3rd Qu.: 0.6783   3rd Qu.: 0.64878  
 Max.   : 2.5069   Max.   : 2.19352  
# Notiamo che la varianza coincide con la correlazione
var(data_gen_standard)
           feature_1  feature_2  feature_3  feature_4  feature_5
feature_1  1.0000000  0.2075405  0.6517324 -0.4864011 -0.6559434
feature_2  0.2075405  1.0000000 -0.1025590 -0.4724998  0.2907433
feature_3  0.6517324 -0.1025590  1.0000000 -0.3710691 -0.5847321
feature_4 -0.4864011 -0.4724998 -0.3710691  1.0000000  0.6052628
feature_5 -0.6559434  0.2907433 -0.5847321  0.6052628  1.0000000
cor(data_gen_standard)
           feature_1  feature_2  feature_3  feature_4  feature_5
feature_1  1.0000000  0.2075405  0.6517324 -0.4864011 -0.6559434
feature_2  0.2075405  1.0000000 -0.1025590 -0.4724998  0.2907433
feature_3  0.6517324 -0.1025590  1.0000000 -0.3710691 -0.5847321
feature_4 -0.4864011 -0.4724998 -0.3710691  1.0000000  0.6052628
feature_5 -0.6559434  0.2907433 -0.5847321  0.6052628  1.0000000

A questo punto possiamo usare il comando prcomp().

pca_gen <- prcomp(data_gen_standard)
summary(pca_gen)
Importance of components:
                          PC1    PC2     PC3     PC4
Standard deviation     1.6071 1.0881 0.35344 0.32929
Proportion of Variance 0.6457 0.2960 0.03123 0.02711
Cumulative Proportion  0.6457 0.9417 0.97289 1.00000

Oppure è equivalente usare l’opzione scale.TRUE sul data frame originale.

summary(pca_gen)
Importance of components:
                          PC1    PC2    PC3     PC4     PC5
Standard deviation     1.6421 1.1700 0.7642 0.57752 0.13055
Proportion of Variance 0.5393 0.2738 0.1168 0.06671 0.00341
Cumulative Proportion  0.5393 0.8131 0.9299 0.99659 1.00000

Leggiamo dall’output di summary() la deviazione standard (\(\sqrt{\lambda_j}\)) per ciascuna componente principale, oltre alla proporzione di varianza spiegata e la proporzione cumulativa. Possiamo visualizzarli tramite dei plot. Cominciamo con lo scree plot.


variances_pca_gen <- (pca_gen$sdev)**2

# uno scree plot con il comando plot
plot(variances_pca_gen, type='l')
points(variances_pca_gen)


# uno scree plot con ggplot2

scree_data <- data.frame(PC = 1:d, Variance = variances_pca_gen)
ggplot(scree_data, aes(x = PC, y = Variance)) +
  geom_line() +
  geom_point() +
  labs(title = "Scree Plot: dataset generato", x = "Componenti Principali", y = "Autovalori")+
  scale_x_continuous(label = ~ scales::comma(.x, accuracy = 1))

Presentiamo il plot della varianza cumulata.


# calcoliamo la percentuale di varianza cumulativa

cum_var <- data.frame(PC = 1:d, Variance = cumsum(variances_pca_gen)/sum(variances_pca_gen))

# plot di base
plot(cum_var, type='l')
points(cum_var)
abline(h=0.95, col="red", lty=3)


# stesso plot ma con ggplot2

ggplot(cum_var, aes(x = PC, y = Variance)) +
  geom_line() +
  geom_point() +
  labs(title = "Varianza cumulata: dataset generato (indipendenti)", x = "Componenti Principali", y = "percentuale di varianza")+
  scale_x_continuous(label = ~ scales::comma(.x, accuracy = 1)) +
  geom_hline(yintercept = 0.95, linetype="dashed",  color = "red", size=1)

Presentiamo infine il biplot in cui sia gli scores che i loadings vengono rappresentati sullo stesso piano. Il comando di base è biplot(). Per grafici più personalizzabili e chiari consigliamo di usare autoplot() dal pacchetto ggfortify.

Osserviamo la matrice delle rotazioni (detta anche dei loadings).

pca_gen$rotation
                  PC1         PC2        PC3          PC4         PC5
feature_1 -0.52764223 -0.04329013 -0.4143550 -0.658173590  0.33887623
feature_2 -0.07517918 -0.83412884 -0.2190672 -0.004680497 -0.50056451
feature_3 -0.47915887  0.23391487 -0.5286863  0.653921203 -0.09256595
feature_4  0.46305473  0.39627489 -0.5409181 -0.309175053 -0.49027027
feature_5  0.52146798 -0.30100678 -0.4563090  0.208766530  0.62101906

EFA sul Dataset Generato

Passiamo ora all’Analisi Fattoriale Esplorativa (EFA). Anche qui abbiamo diversi comandi: factanal() del pacchetto stats (di base) e fa() del pacchetto psych, più avanzato.

# cerchiamo un solo fattore (che potrebbe essere vicino alla PC1)

efa_gen <- factanal(data_gen, factors=2)

# library(psych)
# efa_gen<- fa(data_gen, fators=3)

summary(efa_gen)
             Length Class    Mode     
converged     1     -none-   logical  
loadings     10     loadings numeric  
uniquenesses  5     -none-   numeric  
correlation  25     -none-   numeric  
criteria      3     -none-   numeric  
factors       1     -none-   numeric  
dof           1     -none-   numeric  
method        1     -none-   character
rotmat        4     -none-   numeric  
STATISTIC     1     -none-   numeric  
PVAL          1     -none-   numeric  
n.obs         1     -none-   numeric  
call          3     -none-   call     

Controlliamo in che misura l’equazione matriciale della EFA: \(\operatorname{var}(x) = L^T L + \Psi\) è numericamente valida.

cor(data_gen)
           feature_1  feature_2  feature_3  feature_4  feature_5
feature_1  1.0000000  0.2075405  0.6517324 -0.4864011 -0.6559434
feature_2  0.2075405  1.0000000 -0.1025590 -0.4724998  0.2907433
feature_3  0.6517324 -0.1025590  1.0000000 -0.3710691 -0.5847321
feature_4 -0.4864011 -0.4724998 -0.3710691  1.0000000  0.6052628
feature_5 -0.6559434  0.2907433 -0.5847321  0.6052628  1.0000000
 efa_gen$loadings[1:d] %*% t(efa_gen$loadings[1:d]) +diag(efa_gen$uniquenesses, nrow=d, ncol=d)
            [,1]        [,2]        [,3]        [,4]        [,5]
[1,]  1.00000022  0.01177468  0.59398615 -0.51862035 -0.67765244
[2,]  0.01177468  0.99999347  0.01046037 -0.00913314 -0.01193377
[3,]  0.59398615  0.01046037  0.99999970 -0.46073068 -0.60201122
[4,] -0.51862035 -0.00913314 -0.46073068  0.99999751  0.52562719
[5,] -0.67765244 -0.01193377 -0.60201122  0.52562719  0.99999987

Confrontiamo il vettore dei loadings con la prima componente principale riscalata per la sua deviazione standard.

loading_PCA <- pca_gen$sdev[1]* pca_gen$rotation[,1]

print("Loadings PCA: ")
[1] "Loadings PCA: "
loading_PCA
 feature_1  feature_2  feature_3  feature_4  feature_5 
-0.8664501 -0.1234530 -0.7868348  0.7603899  0.8563113 
print("Loadings EFA: ")
[1] "Loadings EFA: "
efa_gen$loadings[1:d]
[1]  0.81769128  0.01439991  0.72641859 -0.63424957 -0.82873873

Esercizio: effettuare una PCA su un dataset non standardizzato, verificare che le PC tendono a catturare le componenti che hanno varianza maggiore (invece di rilevare le correlazioni).

Esempi con dataset precaricati

Cominciamo con il dataset iris.


head(iris)

summary(iris)
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
 Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
 1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
 Median :5.800   Median :3.000   Median :4.350   Median :1.300  
 Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
 3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
 Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
       Species  
 setosa    :50  
 versicolor:50  
 virginica :50  
                
                
                
pairs(iris[-5], col=iris[,5])

Studiamo la correlazione.

ggcorrplot(cor(iris[-5]), type="lower")

Vediamo una forte correlazione positiva tra le lunghezze, larghezze dei petali e lunghezza del sepalo, mentre una correlazione debole negativa tra queste e la larghezza del sepalo.

Suggerimento: alcuni grafici comuni (scatterplot, heatmap della correlazione) si possono condensare in uno solo, ed eseguire con comandi diretti del pacchetto GGally.

library(GGally)

ggpairs(iris[-5])

PCA sul dataset Iris


pca_iris <- prcomp(iris[-5], scale. = TRUE)
summary(pca_iris)
Importance of components:
                          PC1    PC2     PC3     PC4
Standard deviation     1.7084 0.9560 0.38309 0.14393
Proportion of Variance 0.7296 0.2285 0.03669 0.00518
Cumulative Proportion  0.7296 0.9581 0.99482 1.00000

Notiamo che le prime due PC spiegano il \(95\%\) della varianza.


scree_data <- data.frame(PC = 1:4, Variance =pca_iris$sdev**2)
ggplot(scree_data, aes(x = PC, y = Variance)) +
  geom_line() +
  geom_point() +
  labs(title = "Scree Plot: dataset iris", x = "Componenti Principali", y = "Autovalori")+
  scale_x_continuous(label = ~ scales::comma(.x, accuracy = 1))

Mostriamo il biplot ed intepretiamo il risultato della PCA.


autoplot(pca_iris, data = iris, colour="Species", x=1, y=2, # selezionare eventualmente altre componenti principali ad esempio x=2, y=3
         label=FALSE, loadings = TRUE, loadings.label = TRUE, loadings.label.colour="blue", loadings.label.repel=TRUE,
         loadings.label.size = 3, 
         main = "Biplot PCA dataset generato")

Il biplot mostra che la PC1 è associata alle tre variabili petali e lunghezza dei sepali, mentre la PC2 è associata alla larghezza dei sepali. Vediamolo attraverso la matrice dei loadings.

round(pca_iris$rotation[, 1:2], 2)
               PC1   PC2
Sepal.Length  0.52 -0.38
Sepal.Width  -0.27 -0.92
Petal.Length  0.58 -0.02
Petal.Width   0.56 -0.07

EFA sul Dataset Iris

Possiamo eseguire la EFA sullo stesso dataset.

library(psych)
efa_iris <- fa(iris[-5], nfactors = 2)

# Riassunto dei risultati: loadings
efa_iris$loadings

Loadings:
             MR1    MR2   
Sepal.Length  0.940  0.184
Sepal.Width          0.978
Petal.Length  0.962 -0.121
Petal.Width   0.925       

                 MR1   MR2
SS loadings    2.664 1.010
Proportion Var 0.666 0.253
Cumulative Var 0.666 0.919
efa_iris$communalities
Sepal.Length  Sepal.Width Petal.Length  Petal.Width 
   0.8099449    0.9682729    0.9950000    0.9022485 

L’analisi rivela la stessa situazione: tre variabili possono essere espresse in termini del primo fattore, la rimanente tramite il secondo fattore.

PCA sul dataset mtcars

Riprendiamo il dataset mtcars e studiamolo mediante PCA e poi EFA.


head(mtcars)

# togliamo le features 8 e 9 perché non numeriche (sono indicatrici)

mtcars_num <- mtcars[-(8:9)]

cor_mtcars <- cor(mtcars_num)

ggcorrplot(cor_mtcars, method="square", type="upper", title="Dataset generato (features dipendenti)", hc.order = TRUE, lab=FALSE)

Riconosciamo due blocchi di variabili correlate in senso opposto: il primo è carb, wt, hp, cyl, disp, qsec, mpg, drat, gear.

Vediamo cosa evidenza la PCA.

pca_mtcars <- prcomp(mtcars_num, scale. = TRUE)

# Riassunto dei risultati
summary(pca_mtcars)
Importance of components:
                          PC1    PC2     PC3     PC4     PC5     PC6
Standard deviation     2.3782 1.4429 0.71008 0.51481 0.42797 0.35184
Proportion of Variance 0.6284 0.2313 0.05602 0.02945 0.02035 0.01375
Cumulative Proportion  0.6284 0.8598 0.91581 0.94525 0.96560 0.97936
                           PC7    PC8     PC9
Standard deviation     0.32413 0.2419 0.14896
Proportion of Variance 0.01167 0.0065 0.00247
Cumulative Proportion  0.99103 0.9975 1.00000
# Scree plot

scree_mtcars <- data.frame(PC = 1:ncol(mtcars_num), Variance =pca_mtcars$sdev**2)

ggplot(scree_mtcars, aes(x = PC, y = Variance)) +
  geom_line() +
  geom_point() +
  labs(title = "Scree Plot: dataset mtcars", x = "Componenti Principali", y = "Autovalori")+
  scale_x_continuous(label = ~ scales::comma(.x, accuracy = 1))

NA
NA

Riconosciamo un gomito a \(k=3\), mentre il criterio di Kaiser darebbe \(k=2\). Il grafico della varianza spiegata cumulata darebbe \(k=4\) (quasi \(k=3\)) per arrivare al \(95\%\).



cum_var <- data.frame(PC = 1:length(pca_mtcars$sdev), Variance = cumsum(pca_mtcars$sdev^2)/sum(pca_mtcars$sdev^2))

ggplot(cum_var, aes(x = PC, y = Variance)) +
  geom_line() +
  geom_point() +
  labs(title = "Varianza cumulata: dataset generato (indipendenti)", x = "Componenti Principali", y = "percentuale di varianza")+
  scale_x_continuous(label = ~ scales::comma(.x, accuracy = 1)) +
  geom_hline(yintercept = 0.95, linetype="dashed",  color = "red", size=1)

Visualizziamo i biplot per le PC1, PC2 e pure PC3.



autoplot(pca_mtcars, data = mtcars, colour="cyl", x=1, y=2, # selezionare eventualmente altre componenti principali ad esempio x=2, y=3
         label=FALSE, loadings = TRUE, loadings.label = TRUE, label.size=3, shape=20,  loadings.label.colour="red", loadings.label.repel=TRUE, loadings.label.size = 4, 
         main = "Biplot PCA dataset mtcars")

NA
NA

Abbiamo quindi una PC 1 in cui i due gruppi di variabili sono ben separati. Visto l’allineamento di mpg e cyl sembrerebbe una feature legata al consumo. La PC 2 invece è più allineata con gear e qsec, quindi più legata alle prestazioni (accelerazione da ferma ecc.).

Possiamo confermare la nostra ipotesi visualizzando il plot degli scores relativi a PC1 e PC2 con i nomi dei modelli.


autoplot(pca_mtcars, data = mtcars, colour="mpg", size="qsec", x=1, y=2,
         label=TRUE, label.repel=TRUE, loadings = FALSE, loadings.label = FALSE, label.size=3, shape=20,  loadings.label.colour="red", loadings.label.repel=TRUE, loadings.label.size = 4, 
         main = "Biplot PCA dataset mtcars")

NA
NA

Consideriamo anche la terza componente principale (plottiamo solo PC2 e PC3). Questa sembra essere anche legata al peso wt e al numero di carburatori carb.


autoplot(pca_mtcars, data = mtcars, colour="cyl", size="wt", x=2, y=3,
         label=FALSE, loadings = TRUE, loadings.label = TRUE, label.size=3, shape=20,  loadings.label.colour="red", loadings.label.repel=TRUE, loadings.label.size = 4, 
         main = "Biplot PCA dataset mtcars")

NA
NA

EFA sul Dataset mtcars

Eseguiamo ora una EFA sullo stesso dataset.

# Eseguiamo l'EFA
efa_mtcars <- factanal(mtcars_num, factors = 2)

# Riassunto dei risultati
print(efa_mtcars)

Call:
factanal(x = mtcars_num, factors = 2)

Uniquenesses:
  mpg   cyl  disp    hp  drat    wt  qsec  gear  carb 
0.169 0.097 0.086 0.079 0.294 0.189 0.338 0.144 0.242 

Loadings:
     Factor1 Factor2
mpg  -0.757  -0.509 
cyl   0.764   0.566 
disp  0.839   0.458 
hp    0.474   0.835 
drat -0.838         
wt    0.837   0.332 
qsec         -0.811 
gear -0.861   0.339 
carb          0.869 

               Factor1 Factor2
SS loadings      4.235   3.127
Proportion Var   0.471   0.347
Cumulative Var   0.471   0.818

Test of the hypothesis that 2 factors are sufficient.
The chi square statistic is 52.32 on 19 degrees of freedom.
The p-value is 5.92e-05 

Un dataset reale

La tabella di composizione alimentare ANSES-CIQUAL, disponibile in formato Excel, è gestito dal CIQUAL all’interno dell’Osservatorio dell’Alimentazione, un’unità di ANSES (l’agenzia francese per la sicurezza alimentare, ambientale e della salute occupazionale). La sua missione è raccogliere, valutare e rendere disponibili i dati sulla composizione nutrizionale degli alimenti consumati in Francia. Questo file contiene la composizione di 3185 alimenti per 67 componenti, come carboidrati, zuccheri, proteine, grassi, vitamine e minerali, fornendo informazioni dettagliate per ogni 100 g della parte commestibile degli alimenti.

È importante notare che, in caso di valori mancanti, viene utilizzato un trattino al posto del numero, e tali valori non dovrebbero essere considerati come zero (cosa che invece faremo per semplicità). Inoltre, il termine traccia viene utilizzato quando un componente è rilevato ma non può essere quantificato con precisione, oppure quando si stima che il contenuto di un alimento sia molto basso, ma non nullo. In questi casi, il contenuto medio viene pubblicato come traccia.

Con queste premesse, carichiamo il file dal formato Excel.


library(readxl)

ciqual <- read_excel("datasets/Table Ciqual 2020_ENG_2020 07 07.xls", 
     col_types = c("skip", "skip", "skip", 
         "text", "text", "skip", "skip", "text", 
         "skip", "text", "text", "text", "text", 
         "text", "text", "text", "text", "text", 
         "text", "text", "text", "text", "text", 
         "text", "text", "text", "text", "text", 
         "text", "text", "text", "text", "text", 
         "text", "skip", "skip", "skip", "skip", 
         "skip", "skip", "skip", "skip", "skip", 
         "skip", "skip", "skip", "skip", "skip",
         "text", "text", "text", "text", "text", 
         "text", "text", "text", "text", "text", 
         "text", "text", "text", "text", "text", 
         "text", "text", "text", "text", "text", 
         "text", "text", "text", "text", "text", 
         "text", "text", "text"))

head(ciqual)
NA

Ripuliamo il dataset rimuovendo caratteri non standard come righe, e i dati mancanti ecc.



ciqual_tidy <- ciqual

ciqual_tidy[ciqual=="-"]<- "0"
ciqual_tidy[] <- lapply(ciqual_tidy, function(x) gsub("< ", "", x))
ciqual_tidy[] <- lapply(ciqual_tidy, function(x) gsub(",", ".", x))
ciqual_tidy[] <- lapply(ciqual_tidy, function(x) gsub("traces", "0", x))
ciqual_tidy$alim_nom_eng <- ciqual$alim_nom_eng

ciqual_tidy[-(1:3)] <- lapply(ciqual_tidy[-(1:3)], function(x) as.numeric(x))
ciqual_tidy <- drop_na(ciqual_tidy)
names_ciqual_short <- substr(colnames(ciqual_tidy), 1, 10)

head(ciqual_tidy)
NA

Selezioniamo alcune colonne per effettuare PCA e EFA.


c_select <- c(1, 7:9, 11:14, 17, 30, 40, 41)

# oppure selezioniamo tutte le colonne eccetto le prime 3

#c_select <- c(-(2:3))
 
ciqual_reduced <- ciqual_tidy[c_select]


colnames(ciqual_reduced)<- names_ciqual_short[c_select]

cor_ciqual <- cor(ciqual_reduced[-1])

ggcorrplot(cor_ciqual, type="upper", hc.order=TRUE, lab=FALSE)

Notiamo una forte correlazione positiva tra carboidrati e zuccheri, tra grassi ed energia, tra sale e sodio, e una correlazione negativa (più o meno forte) tra l’acqua e tutto il resto.

PCA su dataset reale

Passiamo alla PCA.


pca_result <- prcomp(ciqual_reduced[-1], center=TRUE, scale.=TRUE)


# Riassunto dei risultati
summary(pca_result)
Importance of components:
                          PC1    PC2    PC3     PC4     PC5     PC6
Standard deviation     1.5497 1.4509 1.3704 1.04099 1.00218 0.92773
Proportion of Variance 0.2183 0.1914 0.1707 0.09852 0.09131 0.07824
Cumulative Proportion  0.2183 0.4097 0.5804 0.67895 0.77026 0.84850
                           PC7     PC8     PC9    PC10    PC11
Standard deviation     0.85564 0.63403 0.58519 0.38596 0.20239
Proportion of Variance 0.06656 0.03654 0.03113 0.01354 0.00372
Cumulative Proportion  0.91506 0.95160 0.98273 0.99628 1.00000
# Scree plot

scree_ciqual <- data.frame(PC = 1:ncol(ciqual_reduced[-1]), Variance =pca_result$sdev**2)

ggplot(scree_ciqual, aes(x = PC, y = Variance)) +
  geom_line() +
  geom_point() +
  labs(title = "Scree Plot: dataset CIQUAL", x = "Componenti Principali", y = "Autovalori")+
  scale_x_continuous(label = ~ scales::comma(.x, accuracy = 1))

NA
NA
NA
NA

Possiamo selezionare le prime tre componenti principali basandoci sul criterio di Kaiser e scree plot.

Osserviamo ora il biplot.

autoplot(pca_result, data = ciqual_reduced, col="alim_grp_n",
         loadings = TRUE, loadings.label = TRUE, loadings.label.colour="black", loadings.label.repel=TRUE,
         loadings.label.size = 3, 
         main = "Biplot del Dataset ciqual reduced PC1-PC2")

La PC1 sembra essere legata alla presenza o meno di acqua e ai grassi e carboidrati. La PC2 invece distingue piuttosto se l’alimento è proteico/grasso/salato oppure ricco di zuccheri (dolce?). Visualizziamo solamente il plot degli scores senza i loadings.

autoplot(pca_result, data = ciqual_reduced, col="alim_grp_n",
         loadings = FALSE, loadings.label = FALSE, 
         main = "Biplot del Dataset ciqual reduced PC1-PC2")

Quali sono gli alimenti classificati come miscellaneous?


head(ciqual_tidy[ciqual_tidy$alim_grp_nom_eng =="miscellaneous", ])
NA

Sono le salse: quindi PC2 potrebbe indicare un’asse dolce vs salato. Vediamo ora la PC3.

autoplot(pca_result, data = ciqual_reduced, col="alim_grp_n", x=2, y=3,
         loadings = TRUE, loadings.label = TRUE, loadings.label.colour="black", loadings.label.repel=TRUE,
         loadings.label.size = 3, 
         main = "Biplot del Dataset ciqual reduced PC2-PC3")

La PC3 sembra distinguere tra contenuto di sale e grassi/proteine.

Esercizo: In effetti le salse sono dei veri e propri outliers, anche perché stiamo confrontando gli indicatori nutrizionali per 100 g di prodotto. Ripetere tutta l’analisi delle componenti principali rimuovendo le righe corrispondenti al gruppo miscellaneous. I risultati si prestano a interepretazoni diverse?

EFA su dataset reale

Eseguiamo infine l’analisi fattoriale esplorativa.

# Eseguiamo l'EFA
efa_ciqual <- factanal(ciqual_reduced[-1], factors = 3)

# Riassunto dei risultati
print(efa_ciqual)

Call:
factanal(x = ciqual_reduced[-1], factors = 3)

Uniquenesses:
Energy, N  Water (g/1 Protein (g Carbohydra Fat (g/100 Sugars (g/ 
     0.324      0.427      0.880      0.005      0.336      0.509 
fructose ( lactose (g Salt (g/10 Selenium ( Sodium (mg 
     0.921      0.984      0.076      0.996      0.005 

Loadings:
           Factor1 Factor2 Factor3
Energy, N           0.139   0.810 
Water (g/1 -0.147  -0.531  -0.519 
Protein (g         -0.193   0.287 
Carbohydra          0.997         
Fat (g/100                  0.812 
Sugars (g/          0.697         
fructose (          0.250  -0.127 
lactose (g          0.123         
Salt (g/10  0.960                 
Selenium (                        
Sodium (mg  0.997                 

               Factor1 Factor2 Factor3
SS loadings      1.939   1.906   1.690
Proportion Var   0.176   0.173   0.154
Cumulative Var   0.176   0.350   0.503

Test of the hypothesis that 3 factors are sufficient.
The chi square statistic is 1392.81 on 25 degrees of freedom.
The p-value is 1.31e-278 

Troviamo 3 fattori: uno legato al sale/sodio, uno legato ai carboidrati/acqua, e un terzo legato all’energia/grassi/proteine. Ovviamente la varianza cumulata è ancora insufficiente (il test rifiuta l’ipotesi che 3 fattori siano sufficienti). Cosa accade se aumentiamo il numero di fattori latenti?

Esercizi

  1. Generare una matrice \(A\) simmetrica e semidefinita positiva con 3 righe e 3 colonne. Implementare il calcolo della radice quarta di \(A\), ovvero di una matrice \(Q\) tale che \(Q^4=A\).

2.Generare 100 coppie di dati Gaussiani che abbiano la bisettrice del primo quadrante come asse principale e varianza 9 lungo tale direzione. Dire quale deve essere l’altro asse principale.

  1. Si generi una tabella di 3 colonne e 25 righe. Le prime due colonne siano Gaussiane (media e varianza a piacere), la terza colonna sia la somma delle prime due colonne più un piccolo rumore. Ottenere le varianze spiegate delle componenti principali della tabella, rappresentarle in un grafico illustrativo e visualizzare il grafico delle prime due componenti principali.

  2. Generare un vettore Gaussiano di 5000 punti con componenti principali di varianze 9, 4 e 1 ed in modo che i suoi assi principali siano le bisettrici dei quadranti del piano xy e l’asse delle quote. Ottenere il grafico della proiezione dei punti sul piano xz.

  3. Generare un campione Gaussiano quadri-dimensionale di numerosità 100, in modo che questo risulti approssimativamente bidimensionale e che la prima componente principale sia la somma delle prime due componenti.

  4. Creare una tabella con 5 colonne e 350 righe, e popolarla di valori in modo che il biplot dell’analisi delle componenti principali per la tabella creata presenti alcune frecce molto corte.

  5. Generare una tabella di dati in modo che, analizzata attraverso l’analisi delle componenti principali, riveli un accumulo di dati sul piano principale che però si rivela ingannevole se visto attraverso gli altri piani.

  6. Considerare la tabella di dati iris. Studiare la corrispondente tabella ottenuta attraverso i logaritmi dei dati originari (ovvero, indagare rispetto a una dipendenza di tipo potenza tra i fattori).

  • Valutare l’opportunità di standardizzare la tabella ottenuta.

  • Ricavare l’analisi delle componenti principali per la nuova tabella di dati.

  • Verificare, colorando i punti rappresentati sui piani principali in accordo con la corrispondente specie, la collocazione dei dati in dipendenza dalla specie.

LS0tCnRpdGxlOiAiUENBIGUgRUZBIChub3RlYm9vayAzKSIKYXV0aG9yOiAiRGFyaW8gVHJldmlzYW4iCmRhdGU6ICIwOC8xMC8yMDI1IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIHRoZW1lOiByZWFkYWJsZQogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICBkb3dubG9hZF9oYW5kbGVyOiB0cnVlCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAnMycKICAgIGRmX3ByaW50OiBwYWdlZApzdWJ0aXRsZTogIlN0YXRpc3RpY2EgSUkgLSA3NTBBQSIKLS0tCgoKCgojIyBJbnRyb2R1emlvbmUKSW4gcXVlc3RvIG5vdGVib29rLCBlc3Bsb3JlcmVtbyBzaWEgbCdBbmFsaXNpIGRlbGxlIENvbXBvbmVudGkgUHJpbmNpcGFsaSAoUENBKSBjaGUgbCdBbmFsaXNpIEZhdHRvcmlhbGUgRXNwbG9yYXRpdmEgKEVGQSkgc3UgZGl2ZXJzaSBkYXRhc2V0OiBhbGN1bmkgZ2VuZXJhdGksIGFsdHJpIHByZS1jYXJpY2F0aSBkaSBSIChjb21lIGBgaXJpc2BgIGUgYGBtdGNhcnNgYCksIGUgdW4gZGF0YXNldCByZWFsZSAoQU5TRVMtQ0lRVUFMIGZvb2QgY29tcG9zaXRpb24gdGFibGUKMjAyMCkuCgpgYGB7cn0KIyBBbGN1bmkgcGFjY2hldHRpIGNoZSB1c2VyZW1vOiBzZSBub24gcHJlc2VudGkgaW5zdGFsbGF0ZWxlIGNvbiBpbCBjb21hbmRvIGluc3RhbGwucGFja2FnZXMoKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeSh0aWR5ciwgc3RyaW5ncikKbGlicmFyeShnZ3Bsb3QyLCBnZ2NvcnJwbG90KQpsaWJyYXJ5KGNvcnJwbG90KQpsaWJyYXJ5KGdnZm9ydGlmeSkKYGBgCgojIyBFc2VtcGkgY29uIGRhdGFzZXQgZ2VuZXJhdGkKCkNvbWUgbmVsIG5vdGVib29rIHByZWNlZGVudGUsIGdlbmVyaWFtbyBkZWkgZGF0aSBwYXJ0ZW5kbyBkYSBjYW1waW9uaSBnYXVzc2lhbmkgZGkgbWVkaWUgZSBkZXZpYXppb25pIHN0YW5kYXJkIHNwZWNpZmljYXRlLgoKYGBge3J9CiMgSW1wb3N0YXJlIGlsIHNlZWQgcGVyIGwnZXNhdHRhIHJpcHJvZHV6aW9uZQojc2V0LnNlZWQoMTIzKQoKIyBTcGVjaWZpY2hpYW1vIGxhIHRhZ2xpYSBuIGRlbCBjYW1waW9uZSBnZW5lcmF0byBlIGlsIG51bWVybyBkaSBmZWF0dXJlcyBkCgpuIDwtIDEwMApkIDwtIDUKCmRhdGFfZ2VuIDwtIGRhdGEuZnJhbWUocm5vcm0obikpCgojIEV0aWNoZXR0ZSBwZXIgbGUgY29sb25uZQpuYW1lc19kYXRhX2dlbiA8LSBwcmludChwYXN0ZTAoImZlYXR1cmVfIiwgYXMuY2hhcmFjdGVyKDEpKSkKCmZvciggaSBpbiAyOmQgKXsKICBkYXRhX2dlbiA8LSBjYmluZCggZGF0YV9nZW4sICBybm9ybShuLCBtZWFuPTAsIHNkPTEpKQogIG5hbWVzX2RhdGFfZ2VuIDwtIGMobmFtZXNfZGF0YV9nZW4sIHByaW50KHBhc3RlMCgiZmVhdHVyZV8iLCBhcy5jaGFyYWN0ZXIoaSkpKSkKfQoKY29sbmFtZXMoZGF0YV9nZW4pIDwtIG5hbWVzX2RhdGFfZ2VuCgpoZWFkKGRhdGFfZ2VuKQoKcGFpcnMoZGF0YV9nZW4pCgpgYGAKCkluIHF1ZXN0byBkYXRhc2V0IGxhIHZhcmlhbnphIGNhbXBpb25hcmlhIGRvdnJlYmJlIGVzc2VyZSBhbGwnaW5jaXJjYSBkaWFnb25hbGUgY29uIGRlZ2xpICQxJCBzdWxsYSBkaWFnb25hbGUgLS0gcGVyY2jDqSBsZSBjb2xvbm5lIHNvbm8gc3RhdGUgZ2VuZXJhdGUgaW4gbW9kbyAqaW5kaXBlbmRlbnRlKiBlIGFiYmlhbW8gc3BlY2lmaWNhdG8gbGEgZGV2aWF6aW9uZSBzdGFuZGFyZCB0ZW9yaWNhIGBgc2Q9MWBgLgoKSSBjb21hbmRpIHBlciBsYSB2YXJpYW56YSwgY292YXJpYW56YSBlIGNvcnJlbGF6aW9uZSBzb25vIHJpc3BldHRpdmFtZW50ZSBgYHZhcigpYGAsIGBgY292KClgYCBlICBgYGNvcigpYGAuCgpgYGB7cn0KIyB2YXIoKSBjYWxjb2xhIGxhIG1hdHJpY2UgZGVsbGUgY292YXJpYW56ZSBjb21wbGV0YQoKdmFyX2RhdGFfZ2VuIDwtIHZhcihkYXRhX2dlbikKdmFyX2RhdGFfZ2VuCmBgYAoKYGBge3J9CiMgY29yKCkgY2FsY29sYSBsYSBtYXRyaWNlIGRlbGxlIGNvcnJlbGF6aW9uaQoKY29yX2RhdGFfZ2VuIDwtIGNvcihkYXRhX2dlbikKY29yX2RhdGFfZ2VuCmBgYAoKQ29uIGBgY292KClgYCBlIGBgY29yKClgYCDDqCBwb3NzaWJpbGUgYW5jaGUgY2FsY29sYXJlIGNvdmFyaWFuemUgdHJhIGNvcHBpZSBkaSBkYXRhIGZyYW1lcy4KCmBgYHtyfQoKY292KGRhdGFfZ2VuWzE6Ml0sIGRhdGFfZ2VuWzM6NF0pCmNvcihkYXRhX2dlblstM10sIGRhdGFfZ2VuWzNdKQpgYGAKClBlciB2aXN1YWxpenphcmUgbGUgY29ycmVsYXppb25pIG1lZGlhbnRlIHVuYSBtYXBwYSBkaSBjYWxvcmUgcG9zc2lhbW8gdXNhcmUgaWwgcGFjY2hldHRvIGBgY29ycnBsb3RgYCBvcHB1cmUgYGBnZ2NvcnJwbG90YGAgKGNoZSBlc3RlbmRlIGBgZ2dwbG90MmBgKS4KCmBgYHtyfQojIGluc3RhbGxhcmUgY29ycnBsb3Qgc2Ugbm9uIMOoIGdpw6AgcHJlc2VudGUKbGlicmFyeShjb3JycGxvdCkKCiMgcGxvdCBkaSBiYXNlCmNvcnJwbG90KGNvcl9kYXRhX2dlbikKYGBgCgpDaSBzb25vIG1vbHRpc3NpbWUgb3B6aW9uaSBwZXIgbWlnbGlvcmFyZSBpbCBncmFmaWNvIChsZWdnZXJlIGwnaGVscCBwZXIgaW5mb3JtYXppb25pKS4KCmBgYHtyfQpjb3JycGxvdChjb3JfZGF0YV9nZW4sIG1ldGhvZD0icGllIiwgdHlwZT0ibG93ZXIiLCBkaWFnPUZBTFNFLCBudW1iZXIuY2V4PTEsIG51bWJlci5kaWdpdHMgPSAyLCB0aXRsZT0iRGF0YXNldCBnZW5lcmF0byAoZmVhdHVyZXMgaW5kaXBlbmRlbnRpKSIpCmBgYAoKU2ltaWxtZW50ZSwgdXNhbmRvIGBgZ2djb3JycGxvdGBgIHNpIHByb2R1Y29ubyBncmFmaWNpIGNvbiBnZ3Bsb3QuCgpgYGB7cn0KIyBpbnN0YWxsYXJlIGlsIHBhY2NoZXR0byBzZSBub24gcHJlc2VudGUKbGlicmFyeShnZ2NvcnJwbG90KQoKZ2djb3JycGxvdChjb3JfZGF0YV9nZW4sICB0eXBlPSJ1cHBlciIsIHRpdGxlPSJEYXRhc2V0IGdlbmVyYXRvIChmZWF0dXJlcyBpbmRpcGVuZGVudGkpIiwgaGMub3JkZXIgPSBUUlVFKQpgYGAKCgojIyMgRGF0YXNldCBjb24gY29ycmVsYXppb25pCgpMZSBtYXRyaWNpIHRyb3ZhdGUgZGkgY29ycmVsYXppb25lIHNvbm8gcG9jbyBpbnRlcmVzc2FudGksIHBlcmNow6kgaWwgZGF0YXNldCBub24gcHJlc2VudGEgYXNzb2NpYXppb25pIGludGVyZXNzYW50aSB0cmEgbGUgdmFyaWUgZmVhdHVyZXMuIFBlciBpbnRyb2R1cnJlIGFydGlmaWNpYWxtZW50ZSBxdWVzdGUgY29ycmVsYXppb25pLCB0cmFzZm9ybWlhbW8gbGUgZmVhdHVyZXMgY29uIHVuYSBtYXBwYSBvcHBvcnR1bmEuCgpgYGB7cn0KIyBBZ2dpdW5naWFtbyB1bmEgY29ycmVsYXppb25lIHRyYSBsZSB2YXJpYWJpbGkuCiMgQ29zdHJ1aWFtbyB1bmEgbWF0cmljZSBjYXN1YWxlIGNvbiB2YWxvcmkgLTEsIDAgbyAxCgplbnRyYXRlX21hdHJpY2UgPC0gc2FtcGxlKGMoLTEsMCwxKSwgZCpkLCByZXBsYWNlPVRSVUUpCgpBIDwtIG1hdHJpeChlbnRyYXRlX21hdHJpY2UsIG5yb3c9ZCwgbmNvbD1kKQoKIyB2aXN1YWxpenppYW1vIGxhIG1hdHJpY2UKCkEKCiMgTW9sdGlwbGljaGlhbW8gaWwgZGF0YSBmcmFtZSAodHJhc2Zvcm1hdG8gaW4gbWF0cmljZSkgcGVyIGxhIG1hdHJpY2UKCmRhdGFfZ2VuX0EgPC0gZGF0YS5mcmFtZSggYXMubWF0cml4LmRhdGEuZnJhbWUoZGF0YV9nZW4pICUqJSBBKQoKY29sbmFtZXMoZGF0YV9nZW5fQSkgPC0gbmFtZXNfZGF0YV9nZW4KCmhlYWQoZGF0YV9nZW5fQSkKCnBsb3QoZGF0YV9nZW5fQSkKCgpgYGAKSW4gcXVlc3RvIGNhc28gaWwgY29ycmVsb2dyYW1tYSDDqCBwacO5IGludGVyZXNzYW50ZQpgYGB7cn0KY29yX2RhdGFfZ2VuX0EgPC0gY29yKGRhdGFfZ2VuX0EpCgpnZ2NvcnJwbG90KGNvcl9kYXRhX2dlbl9BLCBtZXRob2Q9InNxdWFyZSIsIHR5cGU9InVwcGVyIiwgdGl0bGU9IkRhdGFzZXQgZ2VuZXJhdG8gKGZlYXR1cmVzIGRpcGVuZGVudGkpIiwgaGMub3JkZXIgPSBGQUxTRSwgbGFiPVRSVUUpCmBgYAoKRGFsbGEgdGVvcmlhIHNhcHBpYW1vIGNoZSAkXG9wZXJhdG9ybmFtZXt2YXJ9KHhBKSA9IEFeVCBcb3BlcmF0b3JuYW1le3Zhcn0oeCkgQSBcYXBwcm94IEFeVCBBJCBlc3NlbmRvICRcb3BlcmF0b3JuYW1le3Zhcn0oeClcYXBwcm94IElfe2RcdGltZXMgZH0kLiBWZXJpZmljaGlhbW9sbyBudW1lcmljYW1lbnRlLiBDYWxjb2xpYW1vIGxhIHZhcmlhbnphIGNhbXBpb25hcmlhLgoKYGBge3J9Cgp2YXIoZGF0YV9nZW5fQSkKYGBgCiAKUGVyIGNhbGNvbGFyZSBsYSB0cmFzcG9zdGEgZGkgdW5hIG1hdHJpY2UgdXNpYW1vIGxhIGZ1bnppb25lIGBgdCgpYGAuCmBgYHtyfQpjb3ZfQV90ZW9yaWNhIDwtIHQoQSkgJSolIEEKY292X0FfdGVvcmljYQpgYGAKCiMjIyBQQ0Egc3VsIERhdGFzZXQgR2VuZXJhdG8KClBlciBlc2VndWlyZSBsJ2FuYWxpc2kgZGVsbGUgY29tcG9uZW50aSBwcmluY2lwYWxpLCBwb3NzaWFtbyB1c2FyZSBpbCBjb21hbmRvIGBgcHJjb21wKClgYCAoY29uc2lnbGlhdG8pIG9wcHVyZSBpbCBjb21hbmRvICBgYHByaW5jb21wKClgYC4gU2kgdmVkYSA8aHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL0xlYXJuUENBL3ZpZ25ldHRlcy9WaWdfMDdfRnVuY3Rpb25zX1BDQS5wZGY+IHBlciB1bmEgZGlzY3Vzc2lvbmUgZGVsbGUgZGlmZmVyZW56ZS4gCgrDiCBmb3J0ZW1lbnRlIGNvbnNpZ2xpYXRvIHBlciBsYSBQQ0EgZGkgc3RhbmRhcmRpenphcmUgaSBkYXRpLiBQb3NzaWFtbyBmYXJsbyBwcmltYSBjb24gaWwgY29tYW5kbyBgYHNjYWxlKClgYAoKYGBge3J9CgojIHVzaWFtbyBkaXJldHRhbWVudGUgaWwgZGF0YXNldCBnZW5lcmF0byBjb24gY29ycmVsYXppb25pIGluZG90dGUgZGEgQQpkYXRhX2dlbiA8LSBkYXRhX2dlbl9BCgojIHN0YW5kYXJkaXp6aWFtbwpkYXRhX2dlbl9zdGFuZGFyZCA8LSBzY2FsZShkYXRhX2dlbiwgY2VudGVyPVRSVUUsIHNjYWxlPVRSVUUpCgojIE5vdGlhbW8gY2hlIGxhIG1lZGlhIMOoIG51bGxhIHBlciBjaWFzY3VuYSBmZWF0dXJlCnN1bW1hcnkoZGF0YV9nZW5fc3RhbmRhcmQpCgojIE5vdGlhbW8gY2hlIGxhIHZhcmlhbnphIGNvaW5jaWRlIGNvbiBsYSBjb3JyZWxhemlvbmUKdmFyKGRhdGFfZ2VuX3N0YW5kYXJkKQpjb3IoZGF0YV9nZW5fc3RhbmRhcmQpCgpgYGAKCkEgcXVlc3RvIHB1bnRvIHBvc3NpYW1vIHVzYXJlIGlsIGNvbWFuZG8gYGBwcmNvbXAoKWBgLgoKCmBgYHtyfQpwY2FfZ2VuIDwtIHByY29tcChkYXRhX2dlbl9zdGFuZGFyZCkKc3VtbWFyeShwY2FfZ2VuKQoKYGBgCgpPcHB1cmUgw6ggZXF1aXZhbGVudGUgdXNhcmUgbCdvcHppb25lIGBgc2NhbGUuVFJVRWBgIHN1bCBkYXRhIGZyYW1lIG9yaWdpbmFsZS4KCmBgYHtyfQoKcGNhX2dlbiA8LSBwcmNvbXAoZGF0YV9nZW4sIHNjYWxlLiA9IFRSVUUpCnN1bW1hcnkocGNhX2dlbikKYGBgCgpMZWdnaWFtbyBkYWxsJ291dHB1dCBkaSBgYHN1bW1hcnkoKWBgIGxhIGRldmlhemlvbmUgc3RhbmRhcmQgKCRcc3FydHtcbGFtYmRhX2p9JCkgcGVyIGNpYXNjdW5hIGNvbXBvbmVudGUgcHJpbmNpcGFsZSwgb2x0cmUgYWxsYSBwcm9wb3J6aW9uZSBkaSB2YXJpYW56YSBfc3BpZWdhdGFfIGUgbGEgcHJvcG9yemlvbmUgY3VtdWxhdGl2YS4gUG9zc2lhbW8gdmlzdWFsaXp6YXJsaSB0cmFtaXRlIGRlaSBwbG90LiBDb21pbmNpYW1vIGNvbiBsbyBfc2NyZWUgcGxvdF8uCgoKYGBge3J9Cgp2YXJpYW5jZXNfcGNhX2dlbiA8LSAocGNhX2dlbiRzZGV2KSoqMgoKIyB1bm8gc2NyZWUgcGxvdCBjb24gaWwgY29tYW5kbyBwbG90CnBsb3QodmFyaWFuY2VzX3BjYV9nZW4sIHR5cGU9J2wnKQpwb2ludHModmFyaWFuY2VzX3BjYV9nZW4pCgojIHVubyBzY3JlZSBwbG90IGNvbiBnZ3Bsb3QyCgpzY3JlZV9kYXRhIDwtIGRhdGEuZnJhbWUoUEMgPSAxOmQsIFZhcmlhbmNlID0gdmFyaWFuY2VzX3BjYV9nZW4pCmdncGxvdChzY3JlZV9kYXRhLCBhZXMoeCA9IFBDLCB5ID0gVmFyaWFuY2UpKSArCiAgZ2VvbV9saW5lKCkgKwogIGdlb21fcG9pbnQoKSArCiAgbGFicyh0aXRsZSA9ICJTY3JlZSBQbG90OiBkYXRhc2V0IGdlbmVyYXRvIiwgeCA9ICJDb21wb25lbnRpIFByaW5jaXBhbGkiLCB5ID0gIkF1dG92YWxvcmkiKSsKICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWwgPSB+IHNjYWxlczo6Y29tbWEoLngsIGFjY3VyYWN5ID0gMSkpCgpgYGAKClByZXNlbnRpYW1vIGlsIHBsb3QgZGVsbGEgdmFyaWFuemEgY3VtdWxhdGEuCgoKYGBge3J9CgojIGNhbGNvbGlhbW8gbGEgcGVyY2VudHVhbGUgZGkgdmFyaWFuemEgY3VtdWxhdGl2YQoKY3VtX3ZhciA8LSBkYXRhLmZyYW1lKFBDID0gMTpkLCBWYXJpYW5jZSA9IGN1bXN1bSh2YXJpYW5jZXNfcGNhX2dlbikvc3VtKHZhcmlhbmNlc19wY2FfZ2VuKSkKCiMgcGxvdCBkaSBiYXNlCnBsb3QoY3VtX3ZhciwgdHlwZT0nbCcpCnBvaW50cyhjdW1fdmFyKQphYmxpbmUoaD0wLjk1LCBjb2w9InJlZCIsIGx0eT0zKQoKIyBzdGVzc28gcGxvdCBtYSBjb24gZ2dwbG90MgoKZ2dwbG90KGN1bV92YXIsIGFlcyh4ID0gUEMsIHkgPSBWYXJpYW5jZSkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHRpdGxlID0gIlZhcmlhbnphIGN1bXVsYXRhOiBkYXRhc2V0IGdlbmVyYXRvIChpbmRpcGVuZGVudGkpIiwgeCA9ICJDb21wb25lbnRpIFByaW5jaXBhbGkiLCB5ID0gInBlcmNlbnR1YWxlIGRpIHZhcmlhbnphIikrCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVsID0gfiBzY2FsZXM6OmNvbW1hKC54LCBhY2N1cmFjeSA9IDEpKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC45NSwgbGluZXR5cGU9ImRhc2hlZCIsICBjb2xvciA9ICJyZWQiLCBzaXplPTEpCgpgYGAKClByZXNlbnRpYW1vIGluZmluZSBpbCBfYmlwbG90XyBpbiBjdWkgc2lhIGdsaSBzY29yZXMgY2hlIGkgbG9hZGluZ3MgdmVuZ29ubyByYXBwcmVzZW50YXRpIHN1bGxvIHN0ZXNzbyBwaWFuby4gSWwgY29tYW5kbyBkaSBiYXNlIMOoIGBgYmlwbG90KClgYC4gUGVyIGdyYWZpY2kgcGnDuSBwZXJzb25hbGl6emFiaWxpIGUgY2hpYXJpIGNvbnNpZ2xpYW1vIGRpIHVzYXJlIGBgYXV0b3Bsb3QoKWBgIGRhbCBwYWNjaGV0dG8gYGBnZ2ZvcnRpZnlgYC4KCgpgYGB7cn0KIyBCaXBsb3QgZGkgYmFzZSBzdSBlc2l0byBkaSBwcmNvbXAKYmlwbG90KHBjYV9nZW4sIG1haW4gPSAiQmlwbG90IFBDQSBkYXRhc2V0IGdlbmVyYXRvIikKCiMgaW5zdGFsbGFyZSBnZ2ZvcnRpZnkgc2Ugbm9uIHByZXNlbnRlCmxpYnJhcnkoZ2dmb3J0aWZ5KQoKIyBpbnN0YWxsYXJlIGFuY2hlIGdncmVwZWwgcGVyIGV2aXRhcmUgc292cmFwcG9zaXppb25pIHRyYSBsZSBldGljaGV0dGUKbGlicmFyeShnZ3JlcGVsKQoKIyBhdXRvcGxvdCBwcm92dmVkZSBpbiBhdXRvbWF0aWNvIGEgcHJlc2VudGFyZSBpbCBiaXBsb3QgaW4gbW9kbyBlc3RldGljYW1lbnRlIGVxdWlsaWJyYXRvCgphdXRvcGxvdChwY2FfZ2VuLCBkYXRhID0gZGF0YV9nZW4sIHg9MiwgeT0zLCAjIHNlbGV6aW9uYXJlIGV2ZW50dWFsbWVudGUgYWx0cmUgY29tcG9uZW50aSBwcmluY2lwYWxpIGFkIGVzZW1waW8geD0yLCB5PTMKICAgICAgICAgbGFiZWw9RkFMU0UsIGxvYWRpbmdzID0gVFJVRSwgbG9hZGluZ3MubGFiZWwgPSBUUlVFLCBsb2FkaW5ncy5sYWJlbC5jb2xvdXI9ImJsdWUiLCBsb2FkaW5ncy5sYWJlbC5zaXplID0gMywgbG9hZGluZ3MubGFiZWwucmVwZWwgPSBUUlVFLCBtYWluID0gIkJpcGxvdCBQQ0EgZGF0YXNldCBnZW5lcmF0byIpCmBgYApPc3NlcnZpYW1vIGxhIG1hdHJpY2UgZGVsbGUgcm90YXppb25pIChkZXR0YSBhbmNoZSBkZWkgX2xvYWRpbmdzXykuCgpgYGB7cn0KcGNhX2dlbiRyb3RhdGlvbgpgYGAKCiMjIyBFRkEgc3VsIERhdGFzZXQgR2VuZXJhdG8KClBhc3NpYW1vIG9yYSBhbGwnQW5hbGlzaSBGYXR0b3JpYWxlIEVzcGxvcmF0aXZhIChFRkEpLiBBbmNoZSBxdWkgYWJiaWFtbyBkaXZlcnNpIGNvbWFuZGk6IGBgZmFjdGFuYWwoKWBgIGRlbCBwYWNjaGV0dG8gYGBzdGF0c2BgIChkaSBiYXNlKSBlIGBgZmEoKWBgIGRlbCBwYWNjaGV0dG8gYGBwc3ljaGBgLCBwacO5IGF2YW56YXRvLgoKYGBge3J9CiMgY2VyY2hpYW1vIHVuIHNvbG8gZmF0dG9yZSAoY2hlIHBvdHJlYmJlIGVzc2VyZSB2aWNpbm8gYWxsYSBQQzEpCgplZmFfZ2VuIDwtIGZhY3RhbmFsKGRhdGFfZ2VuLCBmYWN0b3JzPTIpCgojIGxpYnJhcnkocHN5Y2gpCiMgZWZhX2dlbjwtIGZhKGRhdGFfZ2VuLCBmYXRvcnM9MykKCnN1bW1hcnkoZWZhX2dlbikKCmBgYAoKQ29udHJvbGxpYW1vIGluIGNoZSBtaXN1cmEgbCdlcXVhemlvbmUgbWF0cmljaWFsZSBkZWxsYSBFRkE6ICRcb3BlcmF0b3JuYW1le3Zhcn0oeCkgPSBMXlQgTCArIFxQc2kkIMOoIG51bWVyaWNhbWVudGUgdmFsaWRhLgoKYGBge3J9CmNvcihkYXRhX2dlbikKCiBlZmFfZ2VuJGxvYWRpbmdzWzE6ZF0gJSolIHQoZWZhX2dlbiRsb2FkaW5nc1sxOmRdKSArZGlhZyhlZmFfZ2VuJHVuaXF1ZW5lc3NlcywgbnJvdz1kLCBuY29sPWQpCgpgYGAKCkNvbmZyb250aWFtbyBpbCB2ZXR0b3JlIGRlaSBsb2FkaW5ncyBjb24gbGEgcHJpbWEgY29tcG9uZW50ZSBwcmluY2lwYWxlIHJpc2NhbGF0YSBwZXIgbGEgc3VhIGRldmlhemlvbmUgc3RhbmRhcmQuCgpgYGB7cn0KbG9hZGluZ19QQ0EgPC0gcGNhX2dlbiRzZGV2WzFdKiBwY2FfZ2VuJHJvdGF0aW9uWywxXQoKcHJpbnQoIkxvYWRpbmdzIFBDQTogIikKbG9hZGluZ19QQ0EKcHJpbnQoIkxvYWRpbmdzIEVGQTogIikKZWZhX2dlbiRsb2FkaW5nc1sxOmRdCmBgYAoKKipFc2VyY2l6aW8qKjogZWZmZXR0dWFyZSB1bmEgUENBIHN1IHVuIGRhdGFzZXQgbm9uIHN0YW5kYXJkaXp6YXRvLCB2ZXJpZmljYXJlIGNoZSBsZSBQQyB0ZW5kb25vIGEgKmNhdHR1cmFyZSogbGUgY29tcG9uZW50aSBjaGUgaGFubm8gdmFyaWFuemEgbWFnZ2lvcmUgKGludmVjZSBkaSByaWxldmFyZSBsZSBjb3JyZWxhemlvbmkpLgoKIyMgRXNlbXBpIGNvbiBkYXRhc2V0IHByZWNhcmljYXRpCgpDb21pbmNpYW1vIGNvbiBpbCBkYXRhc2V0IGBgaXJpc2BgLgoKYGBge3J9CgpoZWFkKGlyaXMpCgpzdW1tYXJ5KGlyaXMpCgpwYWlycyhpcmlzWy01XSwgY29sPWlyaXNbLDVdKQpgYGAKClN0dWRpYW1vIGxhIGNvcnJlbGF6aW9uZS4KCmBgYHtyfQpnZ2NvcnJwbG90KGNvcihpcmlzWy01XSksIHR5cGU9Imxvd2VyIikKYGBgClZlZGlhbW8gdW5hIGZvcnRlIGNvcnJlbGF6aW9uZSBwb3NpdGl2YSB0cmEgbGUgbHVuZ2hlenplLCBsYXJnaGV6emUgZGVpIHBldGFsaSBlIGx1bmdoZXp6YSBkZWwgc2VwYWxvLCBtZW50cmUgdW5hIGNvcnJlbGF6aW9uZSBkZWJvbGUgbmVnYXRpdmEgdHJhIHF1ZXN0ZSBlIGxhIGxhcmdoZXp6YSBkZWwgc2VwYWxvLgoKKipTdWdnZXJpbWVudG8qKjogYWxjdW5pIGdyYWZpY2kgY29tdW5pIChzY2F0dGVycGxvdCwgaGVhdG1hcCBkZWxsYSBjb3JyZWxhemlvbmUpIHNpIHBvc3Nvbm8gY29uZGVuc2FyZSBpbiB1bm8gc29sbywgZWQgZXNlZ3VpcmUgY29uIGNvbWFuZGkgZGlyZXR0aSBkZWwgcGFjY2hldHRvIGBgR0dhbGx5YGAuCgpgYGB7cn0KbGlicmFyeShHR2FsbHkpCgpnZ3BhaXJzKGlyaXNbLTVdKQoKYGBgCgoKIyMjIFBDQSBzdWwgZGF0YXNldCBJcmlzCgpgYGB7cn0KCnBjYV9pcmlzIDwtIHByY29tcChpcmlzWy01XSwgc2NhbGUuID0gVFJVRSkKc3VtbWFyeShwY2FfaXJpcykKCmBgYApOb3RpYW1vIGNoZSBsZSBwcmltZSBkdWUgUEMgc3BpZWdhbm8gaWwgJDk1XCUkIGRlbGxhIHZhcmlhbnphLgoKYGBge3J9CgpzY3JlZV9kYXRhIDwtIGRhdGEuZnJhbWUoUEMgPSAxOjQsIFZhcmlhbmNlID1wY2FfaXJpcyRzZGV2KioyKQpnZ3Bsb3Qoc2NyZWVfZGF0YSwgYWVzKHggPSBQQywgeSA9IFZhcmlhbmNlKSkgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KCkgKwogIGxhYnModGl0bGUgPSAiU2NyZWUgUGxvdDogZGF0YXNldCBpcmlzIiwgeCA9ICJDb21wb25lbnRpIFByaW5jaXBhbGkiLCB5ID0gIkF1dG92YWxvcmkiKSsKICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWwgPSB+IHNjYWxlczo6Y29tbWEoLngsIGFjY3VyYWN5ID0gMSkpCmBgYAoKTW9zdHJpYW1vIGlsIGJpcGxvdCBlZCBpbnRlcHJldGlhbW8gaWwgcmlzdWx0YXRvIGRlbGxhIFBDQS4KYGBge3J9CgphdXRvcGxvdChwY2FfaXJpcywgZGF0YSA9IGlyaXMsIGNvbG91cj0iU3BlY2llcyIsIHg9MSwgeT0yLCAjIHNlbGV6aW9uYXJlIGV2ZW50dWFsbWVudGUgYWx0cmUgY29tcG9uZW50aSBwcmluY2lwYWxpIGFkIGVzZW1waW8geD0yLCB5PTMKICAgICAgICAgbGFiZWw9RkFMU0UsIGxvYWRpbmdzID0gVFJVRSwgbG9hZGluZ3MubGFiZWwgPSBUUlVFLCBsb2FkaW5ncy5sYWJlbC5jb2xvdXI9ImJsdWUiLCBsb2FkaW5ncy5sYWJlbC5yZXBlbD1UUlVFLAogICAgICAgICBsb2FkaW5ncy5sYWJlbC5zaXplID0gMywgCiAgICAgICAgIG1haW4gPSAiQmlwbG90IFBDQSBkYXRhc2V0IGdlbmVyYXRvIikKCmBgYApJbCBiaXBsb3QgbW9zdHJhIGNoZSBsYSBQQzEgw6ggYXNzb2NpYXRhIGFsbGUgdHJlIHZhcmlhYmlsaSBfcGV0YWxpXyBlIF9sdW5naGV6emEgZGVpIHNlcGFsaV8sIG1lbnRyZSBsYSBQQzIgw6ggYXNzb2NpYXRhIGFsbGEgX2xhcmdoZXp6YSBkZWkgc2VwYWxpXy4gVmVkaWFtb2xvIGF0dHJhdmVyc28gbGEgbWF0cmljZSBkZWkgbG9hZGluZ3MuCgoKYGBge3J9CnJvdW5kKHBjYV9pcmlzJHJvdGF0aW9uWywgMToyXSwgMikKYGBgCgojIyMgRUZBIHN1bCBEYXRhc2V0IElyaXMKClBvc3NpYW1vIGVzZWd1aXJlIGxhIEVGQSBzdWxsbyBzdGVzc28gZGF0YXNldC4KCmBgYHtyfQpsaWJyYXJ5KHBzeWNoKQplZmFfaXJpcyA8LSBmYShpcmlzWy01XSwgbmZhY3RvcnMgPSAyKQoKIyBSaWFzc3VudG8gZGVpIHJpc3VsdGF0aTogbG9hZGluZ3MKZWZhX2lyaXMkbG9hZGluZ3MKCmVmYV9pcmlzJGNvbW11bmFsaXRpZXMKYGBgCkwnYW5hbGlzaSByaXZlbGEgbGEgc3Rlc3NhIHNpdHVhemlvbmU6IHRyZSB2YXJpYWJpbGkgcG9zc29ubyBlc3NlcmUgZXNwcmVzc2UgaW4gdGVybWluaSBkZWwgcHJpbW8gZmF0dG9yZSwgbGEgcmltYW5lbnRlIHRyYW1pdGUgaWwgc2Vjb25kbyBmYXR0b3JlLgoKIyMjIFBDQSBzdWwgZGF0YXNldCBtdGNhcnMKClJpcHJlbmRpYW1vIGlsIGRhdGFzZXQgYGBtdGNhcnNgYCBlIHN0dWRpYW1vbG8gbWVkaWFudGUgUENBIGUgcG9pIEVGQS4KCmBgYHtyfQoKaGVhZChtdGNhcnMpCgojIHRvZ2xpYW1vIGxlIGZlYXR1cmVzIDggZSA5IHBlcmNow6kgbm9uIG51bWVyaWNoZSAoc29ubyBpbmRpY2F0cmljaSkKCm10Y2Fyc19udW0gPC0gbXRjYXJzWy0oODo5KV0KCmNvcl9tdGNhcnMgPC0gY29yKG10Y2Fyc19udW0pCgpnZ2NvcnJwbG90KGNvcl9tdGNhcnMsIG1ldGhvZD0ic3F1YXJlIiwgdHlwZT0idXBwZXIiLCB0aXRsZT0iRGF0YXNldCBnZW5lcmF0byAoZmVhdHVyZXMgZGlwZW5kZW50aSkiLCBoYy5vcmRlciA9IFRSVUUsIGxhYj1GQUxTRSkKCmBgYApSaWNvbm9zY2lhbW8gZHVlIF9ibG9jY2hpXyBkaSB2YXJpYWJpbGkgY29ycmVsYXRlIGluIHNlbnNvIG9wcG9zdG86IGlsIHByaW1vIMOoIF9jYXJiLCB3dCwgaHAsIGN5bCwgZGlzcF8sIF9xc2VjLCAgbXBnLCBkcmF0LCBnZWFyXy4KClZlZGlhbW8gY29zYSBldmlkZW56YSBsYSBQQ0EuCgoKCmBgYHtyfQpwY2FfbXRjYXJzIDwtIHByY29tcChtdGNhcnNfbnVtLCBzY2FsZS4gPSBUUlVFKQoKIyBSaWFzc3VudG8gZGVpIHJpc3VsdGF0aQpzdW1tYXJ5KHBjYV9tdGNhcnMpCgojIFNjcmVlIHBsb3QKCnNjcmVlX210Y2FycyA8LSBkYXRhLmZyYW1lKFBDID0gMTpuY29sKG10Y2Fyc19udW0pLCBWYXJpYW5jZSA9cGNhX210Y2FycyRzZGV2KioyKQoKZ2dwbG90KHNjcmVlX210Y2FycywgYWVzKHggPSBQQywgeSA9IFZhcmlhbmNlKSkgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KCkgKwogIGxhYnModGl0bGUgPSAiU2NyZWUgUGxvdDogZGF0YXNldCBtdGNhcnMiLCB4ID0gIkNvbXBvbmVudGkgUHJpbmNpcGFsaSIsIHkgPSAiQXV0b3ZhbG9yaSIpKwogIHNjYWxlX3hfY29udGludW91cyhsYWJlbCA9IH4gc2NhbGVzOjpjb21tYSgueCwgYWNjdXJhY3kgPSAxKSkKCgpgYGAKUmljb25vc2NpYW1vIHVuICpnb21pdG8qIGEgJGs9MyQsIG1lbnRyZSBpbCBjcml0ZXJpbyBkaSBLYWlzZXIgCmRhcmViYmUgJGs9MiQuIElsIGdyYWZpY28gZGVsbGEgdmFyaWFuemEgc3BpZWdhdGEgY3VtdWxhdGEgZGFyZWJiZSAkaz00JCAocXVhc2kgJGs9MyQpIHBlciBhcnJpdmFyZSBhbCAkOTVcJSQuCgpgYGB7cn0KCgpjdW1fdmFyIDwtIGRhdGEuZnJhbWUoUEMgPSAxOmxlbmd0aChwY2FfbXRjYXJzJHNkZXYpLCBWYXJpYW5jZSA9IGN1bXN1bShwY2FfbXRjYXJzJHNkZXZeMikvc3VtKHBjYV9tdGNhcnMkc2Rldl4yKSkKCmdncGxvdChjdW1fdmFyLCBhZXMoeCA9IFBDLCB5ID0gVmFyaWFuY2UpKSArCiAgZ2VvbV9saW5lKCkgKwogIGdlb21fcG9pbnQoKSArCiAgbGFicyh0aXRsZSA9ICJWYXJpYW56YSBjdW11bGF0YTogZGF0YXNldCBnZW5lcmF0byAoaW5kaXBlbmRlbnRpKSIsIHggPSAiQ29tcG9uZW50aSBQcmluY2lwYWxpIiwgeSA9ICJwZXJjZW50dWFsZSBkaSB2YXJpYW56YSIpKwogIHNjYWxlX3hfY29udGludW91cyhsYWJlbCA9IH4gc2NhbGVzOjpjb21tYSgueCwgYWNjdXJhY3kgPSAxKSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAuOTUsIGxpbmV0eXBlPSJkYXNoZWQiLCAgY29sb3IgPSAicmVkIiwgc2l6ZT0xKQpgYGAKClZpc3VhbGl6emlhbW8gaSBiaXBsb3QgcGVyIGxlIFBDMSwgUEMyIGUgcHVyZSBQQzMuCgpgYGB7cn0KCgphdXRvcGxvdChwY2FfbXRjYXJzLCBkYXRhID0gbXRjYXJzLCBjb2xvdXI9ImN5bCIsIHg9MSwgeT0yLCAjIHNlbGV6aW9uYXJlIGV2ZW50dWFsbWVudGUgYWx0cmUgY29tcG9uZW50aSBwcmluY2lwYWxpIGFkIGVzZW1waW8geD0yLCB5PTMKICAgICAgICAgbGFiZWw9RkFMU0UsIGxvYWRpbmdzID0gVFJVRSwgbG9hZGluZ3MubGFiZWwgPSBUUlVFLCBsYWJlbC5zaXplPTMsIHNoYXBlPTIwLCAgbG9hZGluZ3MubGFiZWwuY29sb3VyPSJyZWQiLCBsb2FkaW5ncy5sYWJlbC5yZXBlbD1UUlVFLCBsb2FkaW5ncy5sYWJlbC5zaXplID0gNCwgCiAgICAgICAgIG1haW4gPSAiQmlwbG90IFBDQSBkYXRhc2V0IG10Y2FycyIpCgoKYGBgCgpBYmJpYW1vIHF1aW5kaSB1bmEgUEMgMSBpbiBjdWkgaSBkdWUgZ3J1cHBpIGRpIHZhcmlhYmlsaSBzb25vIGJlbiBzZXBhcmF0aS4gVmlzdG8gbCdhbGxpbmVhbWVudG8gZGkgX21wZ18gZSBfY3lsXyBzZW1icmVyZWJiZSB1bmEgZmVhdHVyZSBsZWdhdGEgYWwgX2NvbnN1bW9fLiBMYSBQQyAyIGludmVjZSDDqCBwacO5IGFsbGluZWF0YSBjb24gX2dlYXJfIGUgX3FzZWNfLCBxdWluZGkgcGnDuSBsZWdhdGEgYWxsZSBfcHJlc3RhemlvbmlfIChhY2NlbGVyYXppb25lIGRhIGZlcm1hIGVjYy4pLgoKUG9zc2lhbW8gY29uZmVybWFyZSBsYSBub3N0cmEgaXBvdGVzaSB2aXN1YWxpenphbmRvIGlsIHBsb3QgZGVnbGkgc2NvcmVzIHJlbGF0aXZpIGEgUEMxIGUgUEMyIGNvbiBpIG5vbWkgZGVpIG1vZGVsbGkuCgpgYGB7cn0KCmF1dG9wbG90KHBjYV9tdGNhcnMsIGRhdGEgPSBtdGNhcnMsIGNvbG91cj0ibXBnIiwgc2l6ZT0icXNlYyIsIHg9MSwgeT0yLAogICAgICAgICBsYWJlbD1UUlVFLCBsYWJlbC5yZXBlbD1UUlVFLCBsb2FkaW5ncyA9IEZBTFNFLCBsb2FkaW5ncy5sYWJlbCA9IEZBTFNFLCBsYWJlbC5zaXplPTMsIHNoYXBlPTIwLCAgbG9hZGluZ3MubGFiZWwuY29sb3VyPSJyZWQiLCBsb2FkaW5ncy5sYWJlbC5yZXBlbD1UUlVFLCBsb2FkaW5ncy5sYWJlbC5zaXplID0gNCwgCiAgICAgICAgIG1haW4gPSAiQmlwbG90IFBDQSBkYXRhc2V0IG10Y2FycyIpCgoKYGBgCgpDb25zaWRlcmlhbW8gYW5jaGUgbGEgdGVyemEgY29tcG9uZW50ZSBwcmluY2lwYWxlIChwbG90dGlhbW8gc29sbyBQQzIgZSBQQzMpLiBRdWVzdGEgc2VtYnJhIGVzc2VyZSBhbmNoZSBsZWdhdGEgYWwgcGVzbyBfd3RfIGUgYWwgbnVtZXJvIGRpIGNhcmJ1cmF0b3JpIF9jYXJiXy4KCmBgYHtyfQoKYXV0b3Bsb3QocGNhX210Y2FycywgZGF0YSA9IG10Y2FycywgY29sb3VyPSJjeWwiLCBzaXplPSJ3dCIsIHg9MiwgeT0zLAogICAgICAgICBsYWJlbD1GQUxTRSwgbG9hZGluZ3MgPSBUUlVFLCBsb2FkaW5ncy5sYWJlbCA9IFRSVUUsIGxhYmVsLnNpemU9Mywgc2hhcGU9MjAsICBsb2FkaW5ncy5sYWJlbC5jb2xvdXI9InJlZCIsIGxvYWRpbmdzLmxhYmVsLnJlcGVsPVRSVUUsIGxvYWRpbmdzLmxhYmVsLnNpemUgPSA0LCAKICAgICAgICAgbWFpbiA9ICJCaXBsb3QgUENBIGRhdGFzZXQgbXRjYXJzIikKCgpgYGAKCgojIyMgRUZBIHN1bCBEYXRhc2V0IG10Y2FycwoKRXNlZ3VpYW1vIG9yYSB1bmEgRUZBIHN1bGxvIHN0ZXNzbyBkYXRhc2V0LgoKYGBge3J9CiMgRXNlZ3VpYW1vIGwnRUZBCmVmYV9tdGNhcnMgPC0gZmFjdGFuYWwobXRjYXJzX251bSwgZmFjdG9ycyA9IDIpCgojIFJpYXNzdW50byBkZWkgcmlzdWx0YXRpCnByaW50KGVmYV9tdGNhcnMpCmBgYAoKCiMjICBVbiBkYXRhc2V0IHJlYWxlCgoKCkxhIHRhYmVsbGEgZGkgY29tcG9zaXppb25lIGFsaW1lbnRhcmUgQU5TRVMtQ0lRVUFMLCBbZGlzcG9uaWJpbGUgaW4gZm9ybWF0byBFeGNlbF0oaHR0cHM6Ly9jaXF1YWwuYW5zZXMuZnIvY21zL3NpdGVzL2RlZmF1bHQvZmlsZXMvaW5saW5lLWZpbGVzL1RhYmxlJTIwQ2lxdWFsJTIwMjAyMF9FTkdfMjAyMCUyMDA3JTIwMDcueGxzKSwgw6ggZ2VzdGl0byBkYWwgQ0lRVUFMIGFsbCdpbnRlcm5vIGRlbGwnT3NzZXJ2YXRvcmlvIGRlbGwnQWxpbWVudGF6aW9uZSwgdW4ndW5pdMOgIGRpIEFOU0VTIChsJ2FnZW56aWEgZnJhbmNlc2UgcGVyIGxhIHNpY3VyZXp6YSBhbGltZW50YXJlLCBhbWJpZW50YWxlIGUgZGVsbGEgc2FsdXRlIG9jY3VwYXppb25hbGUpLiBMYSBzdWEgbWlzc2lvbmUgw6ggcmFjY29nbGllcmUsIHZhbHV0YXJlIGUgcmVuZGVyZSBkaXNwb25pYmlsaSBpIGRhdGkgc3VsbGEgY29tcG9zaXppb25lIG51dHJpemlvbmFsZSBkZWdsaSBhbGltZW50aSBjb25zdW1hdGkgaW4gRnJhbmNpYS4gUXVlc3RvIGZpbGUgY29udGllbmUgbGEgY29tcG9zaXppb25lIGRpIDMxODUgYWxpbWVudGkgcGVyIDY3IGNvbXBvbmVudGksIGNvbWUgY2FyYm9pZHJhdGksIHp1Y2NoZXJpLCBwcm90ZWluZSwgZ3Jhc3NpLCB2aXRhbWluZSBlIG1pbmVyYWxpLCBmb3JuZW5kbyBpbmZvcm1hemlvbmkgZGV0dGFnbGlhdGUgcGVyIG9nbmkgMTAwIGcgZGVsbGEgcGFydGUgY29tbWVzdGliaWxlIGRlZ2xpIGFsaW1lbnRpLgoKw4ggaW1wb3J0YW50ZSBub3RhcmUgY2hlLCBpbiBjYXNvIGRpIHZhbG9yaSBtYW5jYW50aSwgdmllbmUgdXRpbGl6emF0byB1biB0cmF0dGlubyBhbCBwb3N0byBkZWwgbnVtZXJvLCBlIHRhbGkgdmFsb3JpIG5vbiBkb3ZyZWJiZXJvIGVzc2VyZSBjb25zaWRlcmF0aSBjb21lIHplcm8gKCpjb3NhIGNoZSBpbnZlY2UgZmFyZW1vIHBlciBzZW1wbGljaXTDoCopLiBJbm9sdHJlLCBpbCB0ZXJtaW5lIF90cmFjY2lhXyB2aWVuZSB1dGlsaXp6YXRvIHF1YW5kbyB1biBjb21wb25lbnRlIMOoIHJpbGV2YXRvIG1hIG5vbiBwdcOyIGVzc2VyZSBxdWFudGlmaWNhdG8gY29uIHByZWNpc2lvbmUsIG9wcHVyZSBxdWFuZG8gc2kgc3RpbWEgY2hlIGlsIGNvbnRlbnV0byBkaSB1biBhbGltZW50byBzaWEgbW9sdG8gYmFzc28sIG1hIG5vbiBudWxsby4gSW4gcXVlc3RpIGNhc2ksIGlsIGNvbnRlbnV0byBtZWRpbyB2aWVuZSBwdWJibGljYXRvIGNvbWUgX3RyYWNjaWFfLgoKQ29uIHF1ZXN0ZSBwcmVtZXNzZSwgY2FyaWNoaWFtbyBpbCBmaWxlIGRhbCBmb3JtYXRvIEV4Y2VsLgoKYGBge3J9CgpsaWJyYXJ5KHJlYWR4bCkKCmNpcXVhbCA8LSByZWFkX2V4Y2VsKCJkYXRhc2V0cy9UYWJsZSBDaXF1YWwgMjAyMF9FTkdfMjAyMCAwNyAwNy54bHMiLCAKICAgICBjb2xfdHlwZXMgPSBjKCJza2lwIiwgInNraXAiLCAic2tpcCIsIAogICAgICAgICAidGV4dCIsICJ0ZXh0IiwgInNraXAiLCAic2tpcCIsICJ0ZXh0IiwgCiAgICAgICAgICJza2lwIiwgInRleHQiLCAidGV4dCIsICJ0ZXh0IiwgInRleHQiLCAKICAgICAgICAgInRleHQiLCAidGV4dCIsICJ0ZXh0IiwgInRleHQiLCAidGV4dCIsIAogICAgICAgICAidGV4dCIsICJ0ZXh0IiwgInRleHQiLCAidGV4dCIsICJ0ZXh0IiwgCiAgICAgICAgICJ0ZXh0IiwgInRleHQiLCAidGV4dCIsICJ0ZXh0IiwgInRleHQiLCAKICAgICAgICAgInRleHQiLCAidGV4dCIsICJ0ZXh0IiwgInRleHQiLCAidGV4dCIsIAogICAgICAgICAidGV4dCIsICJza2lwIiwgInNraXAiLCAic2tpcCIsICJza2lwIiwgCiAgICAgICAgICJza2lwIiwgInNraXAiLCAic2tpcCIsICJza2lwIiwgInNraXAiLCAKICAgICAgICAgInNraXAiLCAic2tpcCIsICJza2lwIiwgInNraXAiLCAic2tpcCIsCiAgICAgICAgICJ0ZXh0IiwgInRleHQiLCAidGV4dCIsICJ0ZXh0IiwgInRleHQiLCAKICAgICAgICAgInRleHQiLCAidGV4dCIsICJ0ZXh0IiwgInRleHQiLCAidGV4dCIsIAogICAgICAgICAidGV4dCIsICJ0ZXh0IiwgInRleHQiLCAidGV4dCIsICJ0ZXh0IiwgCiAgICAgICAgICJ0ZXh0IiwgInRleHQiLCAidGV4dCIsICJ0ZXh0IiwgInRleHQiLCAKICAgICAgICAgInRleHQiLCAidGV4dCIsICJ0ZXh0IiwgInRleHQiLCAidGV4dCIsIAogICAgICAgICAidGV4dCIsICJ0ZXh0IiwgInRleHQiKSkKCmhlYWQoY2lxdWFsKQoKYGBgCgoKUmlwdWxpYW1vIGlsIGRhdGFzZXQgcmltdW92ZW5kbyBjYXJhdHRlcmkgbm9uIHN0YW5kYXJkIGNvbWUgcmlnaGUsIGUgaSBkYXRpIG1hbmNhbnRpIGVjYy4KCmBgYHtyfQoKCmNpcXVhbF90aWR5IDwtIGNpcXVhbAoKY2lxdWFsX3RpZHlbY2lxdWFsPT0iLSJdPC0gIjAiCmNpcXVhbF90aWR5W10gPC0gbGFwcGx5KGNpcXVhbF90aWR5LCBmdW5jdGlvbih4KSBnc3ViKCI8ICIsICIiLCB4KSkKY2lxdWFsX3RpZHlbXSA8LSBsYXBwbHkoY2lxdWFsX3RpZHksIGZ1bmN0aW9uKHgpIGdzdWIoIiwiLCAiLiIsIHgpKQpjaXF1YWxfdGlkeVtdIDwtIGxhcHBseShjaXF1YWxfdGlkeSwgZnVuY3Rpb24oeCkgZ3N1YigidHJhY2VzIiwgIjAiLCB4KSkKY2lxdWFsX3RpZHkkYWxpbV9ub21fZW5nIDwtIGNpcXVhbCRhbGltX25vbV9lbmcKCmNpcXVhbF90aWR5Wy0oMTozKV0gPC0gbGFwcGx5KGNpcXVhbF90aWR5Wy0oMTozKV0sIGZ1bmN0aW9uKHgpIGFzLm51bWVyaWMoeCkpCmNpcXVhbF90aWR5IDwtIGRyb3BfbmEoY2lxdWFsX3RpZHkpCm5hbWVzX2NpcXVhbF9zaG9ydCA8LSBzdWJzdHIoY29sbmFtZXMoY2lxdWFsX3RpZHkpLCAxLCAxMCkKCmhlYWQoY2lxdWFsX3RpZHkpCgpgYGAKClNlbGV6aW9uaWFtbyBhbGN1bmUgY29sb25uZSBwZXIgZWZmZXR0dWFyZSBQQ0EgZSBFRkEuCgpgYGB7cn0KCmNfc2VsZWN0IDwtIGMoMSwgNzo5LCAxMToxNCwgMTcsIDMwLCA0MCwgNDEpCgojIG9wcHVyZSBzZWxlemlvbmlhbW8gdHV0dGUgbGUgY29sb25uZSBlY2NldHRvIGxlIHByaW1lIDMKCiNjX3NlbGVjdCA8LSBjKC0oMjozKSkKIApjaXF1YWxfcmVkdWNlZCA8LSBjaXF1YWxfdGlkeVtjX3NlbGVjdF0KCgpjb2xuYW1lcyhjaXF1YWxfcmVkdWNlZCk8LSBuYW1lc19jaXF1YWxfc2hvcnRbY19zZWxlY3RdCgpjb3JfY2lxdWFsIDwtIGNvcihjaXF1YWxfcmVkdWNlZFstMV0pCgpnZ2NvcnJwbG90KGNvcl9jaXF1YWwsIHR5cGU9InVwcGVyIiwgaGMub3JkZXI9VFJVRSwgbGFiPUZBTFNFKQoKYGBgCgpOb3RpYW1vIHVuYSBmb3J0ZSBjb3JyZWxhemlvbmUgcG9zaXRpdmEgdHJhIGNhcmJvaWRyYXRpIGUgenVjY2hlcmksIHRyYSBncmFzc2kgZWQgZW5lcmdpYSwgdHJhIHNhbGUgZSBzb2RpbywgZSB1bmEgY29ycmVsYXppb25lIG5lZ2F0aXZhIChwacO5IG8gbWVubyBmb3J0ZSkgdHJhIGwnYWNxdWEgZSB0dXR0byBpbCByZXN0by4KCgojIyMgUENBIHN1IGRhdGFzZXQgcmVhbGUKClBhc3NpYW1vIGFsbGEgUENBLgoKYGBge3J9CgpwY2FfcmVzdWx0IDwtIHByY29tcChjaXF1YWxfcmVkdWNlZFstMV0sIGNlbnRlcj1UUlVFLCBzY2FsZS49VFJVRSkKCgojIFJpYXNzdW50byBkZWkgcmlzdWx0YXRpCnN1bW1hcnkocGNhX3Jlc3VsdCkKCiMgU2NyZWUgcGxvdAoKc2NyZWVfY2lxdWFsIDwtIGRhdGEuZnJhbWUoUEMgPSAxOm5jb2woY2lxdWFsX3JlZHVjZWRbLTFdKSwgVmFyaWFuY2UgPXBjYV9yZXN1bHQkc2RldioqMikKCmdncGxvdChzY3JlZV9jaXF1YWwsIGFlcyh4ID0gUEMsIHkgPSBWYXJpYW5jZSkpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHRpdGxlID0gIlNjcmVlIFBsb3Q6IGRhdGFzZXQgQ0lRVUFMIiwgeCA9ICJDb21wb25lbnRpIFByaW5jaXBhbGkiLCB5ID0gIkF1dG92YWxvcmkiKSsKICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWwgPSB+IHNjYWxlczo6Y29tbWEoLngsIGFjY3VyYWN5ID0gMSkpCgoKCgpgYGAKUG9zc2lhbW8gc2VsZXppb25hcmUgbGUgcHJpbWUgdHJlIGNvbXBvbmVudGkgcHJpbmNpcGFsaSBiYXNhbmRvY2kgc3VsIGNyaXRlcmlvIGRpIEthaXNlciBlIHNjcmVlIHBsb3QuCgoKT3NzZXJ2aWFtbyBvcmEgaWwgYmlwbG90LgoKYGBge3J9CmF1dG9wbG90KHBjYV9yZXN1bHQsIGRhdGEgPSBjaXF1YWxfcmVkdWNlZCwgY29sPSJhbGltX2dycF9uIiwKICAgICAgICAgbG9hZGluZ3MgPSBUUlVFLCBsb2FkaW5ncy5sYWJlbCA9IFRSVUUsIGxvYWRpbmdzLmxhYmVsLmNvbG91cj0iYmxhY2siLCBsb2FkaW5ncy5sYWJlbC5yZXBlbD1UUlVFLAogICAgICAgICBsb2FkaW5ncy5sYWJlbC5zaXplID0gMywgCiAgICAgICAgIG1haW4gPSAiQmlwbG90IGRlbCBEYXRhc2V0IGNpcXVhbCByZWR1Y2VkIFBDMS1QQzIiKQpgYGAKTGEgUEMxIHNlbWJyYSBlc3NlcmUgbGVnYXRhIGFsbGEgcHJlc2VuemEgbyBtZW5vIGRpIGFjcXVhIGUgYWkgZ3Jhc3NpIGUgY2FyYm9pZHJhdGkuIExhIFBDMiBpbnZlY2UgZGlzdGluZ3VlIHBpdXR0b3N0byBzZSBsJ2FsaW1lbnRvIMOoIHByb3RlaWNvL2dyYXNzby9zYWxhdG8gb3BwdXJlIHJpY2NvIGRpIHp1Y2NoZXJpIChkb2xjZT8pLiBWaXN1YWxpenppYW1vIHNvbGFtZW50ZSBpbCBwbG90IGRlZ2xpIHNjb3JlcyBzZW56YSBpIGxvYWRpbmdzLgoKYGBge3J9CmF1dG9wbG90KHBjYV9yZXN1bHQsIGRhdGEgPSBjaXF1YWxfcmVkdWNlZCwgY29sPSJhbGltX2dycF9uIiwKICAgICAgICAgbG9hZGluZ3MgPSBGQUxTRSwgbG9hZGluZ3MubGFiZWwgPSBGQUxTRSwgCiAgICAgICAgIG1haW4gPSAiQmlwbG90IGRlbCBEYXRhc2V0IGNpcXVhbCByZWR1Y2VkIFBDMS1QQzIiKQoKYGBgCgpRdWFsaSBzb25vIGdsaSBhbGltZW50aSBjbGFzc2lmaWNhdGkgY29tZSBfbWlzY2VsbGFuZW91c18/CgpgYGB7cn0KCmhlYWQoY2lxdWFsX3RpZHlbY2lxdWFsX3RpZHkkYWxpbV9ncnBfbm9tX2VuZyA9PSJtaXNjZWxsYW5lb3VzIiwgXSkKCmBgYApTb25vIGxlIHNhbHNlOiBxdWluZGkgUEMyIHBvdHJlYmJlIGluZGljYXJlIHVuJ2Fzc2UgX2RvbGNlIHZzIHNhbGF0b18uIFZlZGlhbW8gb3JhIGxhIFBDMy4KCgpgYGB7cn0KYXV0b3Bsb3QocGNhX3Jlc3VsdCwgZGF0YSA9IGNpcXVhbF9yZWR1Y2VkLCBjb2w9ImFsaW1fZ3JwX24iLCB4PTIsIHk9MywKICAgICAgICAgbG9hZGluZ3MgPSBUUlVFLCBsb2FkaW5ncy5sYWJlbCA9IFRSVUUsIGxvYWRpbmdzLmxhYmVsLmNvbG91cj0iYmxhY2siLCBsb2FkaW5ncy5sYWJlbC5yZXBlbD1UUlVFLAogICAgICAgICBsb2FkaW5ncy5sYWJlbC5zaXplID0gMywgCiAgICAgICAgIG1haW4gPSAiQmlwbG90IGRlbCBzRGF0YXNldCBjaXF1YWwgcmVkdWNlZCBQQzItUEMzIikKYGBgCgpMYSBQQzMgc2VtYnJhIGRpc3Rpbmd1ZXJlIHRyYSBjb250ZW51dG8gZGkgc2FsZSBlIGdyYXNzaS9wcm90ZWluZS4KCioqRXNlcmNpem86KiogIEluIGVmZmV0dGkgbGUgc2Fsc2Ugc29ubyBkZWkgdmVyaSBlIHByb3ByaSBfb3V0bGllcnNfLCBhbmNoZSBwZXJjaMOpIHN0aWFtbyBjb25mcm9udGFuZG8gZ2xpIGluZGljYXRvcmkgbnV0cml6aW9uYWxpIHBlciAxMDAgZyBkaSBwcm9kb3R0by4gUmlwZXRlcmUgdHV0dGEgbCdhbmFsaXNpIGRlbGxlIGNvbXBvbmVudGkgcHJpbmNpcGFsaSByaW11b3ZlbmRvIGxlIHJpZ2hlIGNvcnJpc3BvbmRlbnRpIGFsIGdydXBwbyBfbWlzY2VsbGFuZW91c18uIEkgcmlzdWx0YXRpIHNpIHByZXN0YW5vIGEgaW50ZXJlcHJldGF6b25pIGRpdmVyc2U/CgoKCgojIyMgRUZBIHN1IGRhdGFzZXQgcmVhbGUKCgpFc2VndWlhbW8gaW5maW5lIGwnYW5hbGlzaSBmYXR0b3JpYWxlIGVzcGxvcmF0aXZhLgoKCmBgYHtyfQojIEVzZWd1aWFtbyBsJ0VGQQplZmFfY2lxdWFsIDwtIGZhY3RhbmFsKGNpcXVhbF9yZWR1Y2VkWy0xXSwgZmFjdG9ycyA9IDMpCgojIFJpYXNzdW50byBkZWkgcmlzdWx0YXRpCnByaW50KGVmYV9jaXF1YWwpCmBgYApUcm92aWFtbyAzIGZhdHRvcmk6IHVubyBsZWdhdG8gYWwgc2FsZS9zb2RpbywgdW5vIGxlZ2F0byBhaSBjYXJib2lkcmF0aS9hY3F1YSwgZSB1biB0ZXJ6byBsZWdhdG8gYWxsJ2VuZXJnaWEvZ3Jhc3NpL3Byb3RlaW5lLiBPdnZpYW1lbnRlIGxhIHZhcmlhbnphIGN1bXVsYXRhIMOoIGFuY29yYSBpbnN1ZmZpY2llbnRlIChpbCB0ZXN0IHJpZml1dGEgbCdpcG90ZXNpIGNoZSAzIGZhdHRvcmkgc2lhbm8gc3VmZmljaWVudGkpLiBDb3NhIGFjY2FkZSBzZSBhdW1lbnRpYW1vIGlsIG51bWVybyBkaSBmYXR0b3JpIGxhdGVudGk/CgojIyBFc2VyY2l6aQoKCjEuIEdlbmVyYXJlIHVuYSBtYXRyaWNlICRBJCBzaW1tZXRyaWNhIGUgc2VtaWRlZmluaXRhIHBvc2l0aXZhIGNvbiAzIHJpZ2hlIGUgMyBjb2xvbm5lLiBJbXBsZW1lbnRhcmUgaWwgY2FsY29sbyBkZWxsYSByYWRpY2UgcXVhcnRhIGRpICRBJCwgb3Z2ZXJvIGRpIHVuYSBtYXRyaWNlICRRJCB0YWxlIGNoZSAkUV40PUEkLgoKMi5HZW5lcmFyZSAxMDAgY29wcGllIGRpIGRhdGkgR2F1c3NpYW5pIGNoZSBhYmJpYW5vIGxhIGJpc2V0dHJpY2UgZGVsIHByaW1vIHF1YWRyYW50ZSBjb21lIGFzc2UgcHJpbmNpcGFsZSBlIHZhcmlhbnphIDkgbHVuZ28gdGFsZSBkaXJlemlvbmUuIERpcmUgcXVhbGUgZGV2ZSBlc3NlcmUgbOKAmWFsdHJvIGFzc2UgcHJpbmNpcGFsZS4KCjMuIFNpIGdlbmVyaSB1bmEgdGFiZWxsYSBkaSAzIGNvbG9ubmUgZSAyNSByaWdoZS4gTGUgcHJpbWUgZHVlIGNvbG9ubmUgc2lhbm8gR2F1c3NpYW5lIChtZWRpYSBlIHZhcmlhbnphIGEgcGlhY2VyZSksIGxhIHRlcnphIGNvbG9ubmEgc2lhIGxhIHNvbW1hIGRlbGxlIHByaW1lIGR1ZSBjb2xvbm5lIHBpw7kgdW4gcGljY29sbyBydW1vcmUuIE90dGVuZXJlIGxlIHZhcmlhbnplIHNwaWVnYXRlIGRlbGxlIGNvbXBvbmVudGkgcHJpbmNpcGFsaSBkZWxsYSB0YWJlbGxhLCByYXBwcmVzZW50YXJsZSBpbiB1biBncmFmaWNvIGlsbHVzdHJhdGl2byBlIHZpc3VhbGl6emFyZSBpbCBncmFmaWNvIGRlbGxlIHByaW1lIGR1ZSBjb21wb25lbnRpIHByaW5jaXBhbGkuCgo0LiBHZW5lcmFyZSB1biB2ZXR0b3JlIEdhdXNzaWFubyBkaSA1MDAwIHB1bnRpIGNvbiBjb21wb25lbnRpIHByaW5jaXBhbGkgZGkgdmFyaWFuemUgOSwgNCBlIDEgZWQgaW4gbW9kbyBjaGUgaSBzdW9pIGFzc2kgcHJpbmNpcGFsaSBzaWFubyBsZSBiaXNldHRyaWNpIGRlaSBxdWFkcmFudGkgZGVsIHBpYW5vIHh5IGUgbOKAmWFzc2UgZGVsbGUgcXVvdGUuIE90dGVuZXJlIGlsIGdyYWZpY28gZGVsbGEgcHJvaWV6aW9uZSBkZWkgcHVudGkgc3VsIHBpYW5vIHh6LgoKNS4gR2VuZXJhcmUgdW4gY2FtcGlvbmUgR2F1c3NpYW5vIHF1YWRyaS1kaW1lbnNpb25hbGUgZGkgbnVtZXJvc2l0w6AgMTAwLCBpbiBtb2RvIGNoZSBxdWVzdG8gcmlzdWx0aSBhcHByb3NzaW1hdGl2YW1lbnRlIGJpZGltZW5zaW9uYWxlIGUgY2hlIGxhIHByaW1hIGNvbXBvbmVudGUgcHJpbmNpcGFsZSBzaWEgbGEgc29tbWEgZGVsbGUgcHJpbWUgZHVlIGNvbXBvbmVudGkuCgo2LiBDcmVhcmUgdW5hIHRhYmVsbGEgY29uIDUgY29sb25uZSBlIDM1MCByaWdoZSwgZSBwb3BvbGFybGEgZGkgdmFsb3JpIGluIG1vZG8gY2hlIGlsIGJpcGxvdCBkZWxs4oCZYW5hbGlzaSBkZWxsZSBjb21wb25lbnRpIHByaW5jaXBhbGkgcGVyIGxhIHRhYmVsbGEgY3JlYXRhIHByZXNlbnRpIGFsY3VuZSBmcmVjY2UgbW9sdG8gY29ydGUuCgo3LiBHZW5lcmFyZSB1bmEgdGFiZWxsYSBkaSBkYXRpIGluIG1vZG8gY2hlLCBhbmFsaXp6YXRhIGF0dHJhdmVyc28gbOKAmWFuYWxpc2kgZGVsbGUgY29tcG9uZW50aSBwcmluY2lwYWxpLCByaXZlbGkgdW4gYWNjdW11bG8gZGkgZGF0aSBzdWwgcGlhbm8gcHJpbmNpcGFsZSBjaGUgcGVyw7Igc2kgcml2ZWxhIGluZ2FubmV2b2xlIHNlIHZpc3RvIGF0dHJhdmVyc28gZ2xpIGFsdHJpIHBpYW5pLgoKOC4gQ29uc2lkZXJhcmUgbGEgdGFiZWxsYSBkaSBkYXRpIGlyaXMuIFN0dWRpYXJlIGxhIGNvcnJpc3BvbmRlbnRlIHRhYmVsbGEgb3R0ZW51dGEgYXR0cmF2ZXJzbyBpIGxvZ2FyaXRtaSBkZWkgZGF0aSBvcmlnaW5hcmkgKG92dmVybywgaW5kYWdhcmUgcmlzcGV0dG8gYSB1bmEgZGlwZW5kZW56YSBkaSB0aXBvIHBvdGVuemEgdHJhIGkgZmF0dG9yaSkuCiAgCiAgLSBWYWx1dGFyZSBs4oCZb3Bwb3J0dW5pdMOgIGRpIHN0YW5kYXJkaXp6YXJlIGxhIHRhYmVsbGEgb3R0ZW51dGEuCiAgCiAgLSBSaWNhdmFyZSBs4oCZYW5hbGlzaSBkZWxsZSBjb21wb25lbnRpIHByaW5jaXBhbGkgcGVyIGxhIG51b3ZhIHRhYmVsbGEgZGkgZGF0aS4KICAKICAtIFZlcmlmaWNhcmUsIGNvbG9yYW5kbyBpIHB1bnRpIHJhcHByZXNlbnRhdGkgc3VpIHBpYW5pIHByaW5jaXBhbGkgaW4gYWNjb3JkbyBjb24gbGEgY29ycmlzcG9uZGVudGUgc3BlY2llLCBsYSBjb2xsb2NhemlvbmUgZGVpIGRhdGkgaW4gZGlwZW5kZW56YSBkYWxsYSBzcGVjaWUuCgo=