Introduction

Dans ce projet, nous cherchons à prédire la consommation électrique de groupes de clients du \(1^{\text{er}}\) janvier \(2014\) au \(27\) février \(2014\). Pour cela, nous avons à notre disposition \(30\) échantillons de taille \(17520\) qui correspondent à différentes tailles de groupe :

Ces échantillons nous donnent la consommation électrique de ces différents groupes tout au long de l’année \(2013\) par intervalles de \(30\) minutes.

Dans un premier temps, nous allons faire une rapide analyse de nos données. Puis nous testerons trois méthodes de prédiction. Enfin, nous pourrons les comparer.

Compte-rendu

Prétraitement et mises-en-forme des données

Une grosse partie de ce projet consiste à bien sélectionner les variables que nous utilisons dans les prédictions. Pour cela, il faut les avoir sous le bon format et créer les data-frames adéquats.

Tout d’abord nous importons nos données :

data0 <- read.csv('Data/Data0.csv', header = TRUE)
data1 <- read.csv('Data/Data1.csv', header = TRUE)

La première variable data0 correspond aux données de consommation d’électricité de l’année \(2013\). Elle contient \(17520\) lignes et \(32\) colonnes dont les \(30\) échantillons, la température, la date et l’heure.

La deuxième variable data1 correspond aux données connues afin de réaliser nos prévisions. Elle contient \(83520\) lignes et \(4\) colonnes dont les températures connues du début de l’année \(2014\), la date, l’heure et l’indice de l’échantillon correspondant.

Par la suite nous créons de nombreuses variables contenant les différentes valeurs qui nous intéressent :

  • date : pour avoir la date et l’heure à chaque instant sous le bon format;
date.debut <- strptime(c("01/01/2013 00:00:00"), '%d/%m/%Y %H:%M:%S')
date.fin <- strptime(c("31/12/2013 23:30:00"), "%d/%m/%Y %H:%M:%S")
date <- seq(date.debut, date.fin, by = '30 min')
rm(date.debut, date.fin)
  • Temperature et Temperature.lag : il faut faire attention car la colonne des températures contient des NA, nous créons alors la fonction my.na.approx qui remplace les NA par des valeurs en faisant la moyenne (voir exemple plus loin), la deuxième variable contient les températures décalées de \(1\);
my.na.approx <- function(x) {
  if (sum(is.finite(x)) == 0L) return(x)
  if (sum(is.finite(x)) == 1L) return(na.approx(x, rule = 2, method = "constant"))
  na.approx(x, rule = 2)
}
Temperature <- data0$Temperature
Temperature <- my.na.approx(Temperature)
Temperature.lag <- as.numeric(sapply(1:length(Temperature), function(x) Temperature[x - 1]))
Temperature.lag[1] <- Temperature.lag[2]
  • mois et jour : nous gardons uniquement le numero du mois correspondant et uniquement la date en retirant l’heure;
mois <- format(date, "%m")
jour <- format(date, "%d/%m/%Y")
  • heure et heure.num : dans la premiere variable nous gardons uniquement l’heure correspondante et dans la deuxième nous la transformons en valeur numérique de telle sorte que \(12\)h\(30\) soit égal à \(12.5\);
heure <- format(date, "%H:%M:%S")
essai.heure <- as.POSIXlt(heure, format = "%H:%M:%S")
heure.num <- as.numeric(essai.heure$hour) + (as.numeric(essai.heure$min)/60)
rm(essai.heure)
  • weekend : nous créons une variable qui vaut \(0\) si nous sommes en semaine et \(1\) si nous sommes en week-end;
day <- weekdays(as.Date(format(date, "%d/%m/%Y")))
weekend <- (day == "Sunday") + (day == "Saturday")
rm(day)
  • week.num : cette variable correspond au numéro de la semaine à laquelle nous sommes.
week.num <- strftime(date, format = "%V")

Donnons un exemple d’utilisation de la fonction my.na.approx :

my.na.approx(c(NA, 1, NA, NA, 2, NA))
[1] 1.000000 1.000000 1.333333 1.666667 2.000000 2.000000

Ainsi, il a divisé \(2 - 1\) par \(3\) pour obtenir les deux valeurs du milieu et il a mis les valeurs \(1\) et \(2\) sur les bords.

Ensuite nous stockons toutes ces variables dans un data-frame nommé consom.csv :

consom.csv <- data.frame(date, mois, heure, weekend, week.num, data0[, c(2:31)], Temperature, Temperature.lag)

Nous créons les même variables suivies de l’extensions .pred qui correspondent à ces même valeurs mais associées aux données que nous souhaitons prédire. Nous les stockons dans un data-frame nommé consom.pred.csv.

Pour les prédictions, nous allons avoir besoin de variables croisées. En effet, elles permettent d’avoir un modèle plus proche de la réalité. Par exemple, la température en janvier ne va pas influer comme la température en mars, c’est pour cela que nous introduisons une variable Temperature fois mois. Nous les créons :

temp.heure <- Temperature*heure.num
temp.weekend <- Temperature*weekend
temp.mois <- Temperature*as.numeric(mois)
mois.heure <- as.numeric(mois)*heure.num
weekend.heure <- weekend*heure.num

Nous faisons de même pour les variables .pred.

Enfin, pour tester nos prédictions, nous allons réaliser des prédictions à partir de nos données connues data0. Pour cela, nous prenons \(75 \%\) de notre échantillon de façon aléatoire afin de ne pas avoir de biais à la construction de nos modèles. Puis, nous prédisons les \(25 \%\) restants que nous connaissons. Ainsi, nous pourrons comparer la prédiction aux vraies valeurs. Pour cela, nous calculons la rmse, si elle diminue c’est que les changements réalisés sont bons et vice-versa. Pour plus de précision, nous pouvons faire ces tests sur plusieurs échantillons Train et Test afin de prendre le paramètre qui correspond le mieux au plus grand nombre.

Nous implémentons donc la fonction qui permet de calculer le score :

rmse <- function(actual, predicted) {
  sqrt(mean((actual - predicted) ^ 2))
}

Cette fonction est basée sur la formule : \[ RMSE(Y, \widehat{Y}) = \sqrt{\dfrac{1}{n} \sum\limits_{i = 1}^n \left( y_i - \widehat{y}_i \right)^2} \] qui est donnée dans le sujet du projet.

Puis, nous créons les deux variables Train et Test :

smp_size <- floor(0.75 * nrow(consom.csv))
train_ind <- sample(seq_len(nrow(consom.csv)), size = smp_size)
Train <- consom.csv[train_ind, ]
Test <- consom.csv[-train_ind, ]
rm(smp_size)

Train correspond donc au \(75 \%\) de notre échantillon sur lequel nous réaliserons les prévisions des \(25 \%\) restants dont les vraies valeurs sont contenues dans Test. Ainsi, il nous restera à calculer le score en faisant rmse(Test, pred).

Analyse descriptive des données

Représentation graphique

Nous représentons graphiquement trois échantillons de chaque taille : X2, X12 et X22.

Nous pouvons remarquer que X2 semble stationnaire. Tandis que X12 et X22 ne semblent pas stationnaires. Pour les trois échantillons, leur variance varie dans le temps.

Ces trois graphiques restent peu lisibles, nous allons donc représenter graphiquement la moyenne par jour :

X2.jour <- tapply(consom.csv$X2, as.factor(jour), mean)
X12.jour <- tapply(consom.csv$X12, as.factor(jour), mean)
X22.jour <- tapply(consom.csv$X22, as.factor(jour), mean)
temp.jour <- tapply(consom.csv$Temperature, as.factor(jour), mean)

Ces graphiques sont déjà moins saturés mais restent difficilement lisibles. La variance semble varier fortement pour X2, un peu moins pour X12 et encore moins pour X22. Cette fois-ci, ils semblent tous quasiment stationnaires.

Maintenant, nous allons représenter graphiquement la moyenne par mois :

X2.mois <- tapply(consom.csv$X2, as.factor(mois), mean)
X12.mois <- tapply(consom.csv$X12, as.factor(mois), mean)
X22.mois <- tapply(consom.csv$X22, as.factor(mois), mean)
temp.mean.mois <- tapply(consom.csv$Temperature, as.factor(mois), mean)

Pour les trois graphiques, nous remarquons que le minimum est atteint le \(8^{\text{ième}}\) mois de l’année donc en août, ce qui paraît cohérent. En observant en plus la courbe de la moyenne par mois de la température, nous remarquons que son maximum est atteint en juillet, il y aurait donc une influence de la température du mois précédent sur le mois suivant. Nous pouvons alors supposer que la température des \(30\) dernières minutes va influer sur notre consommation électrique. C’est pour cette raison que nous avons introduit la variable Temperature.lag qui nous donne la température \(30\) minutes avant le relevé de la consommation électrique.

Statistiques de base

Nous représentons l’histogramme de nos trois échantillons de référence :

Nous remarquons qu’il est rare que la consommation électrique soit supérieure à \(0.4\). En effet, la majorité des données se répartissent dans l’intervalle \([0.1, 0.4]\) pour X2 et \([0.1, 0.3]\) pour X12 et X22.

Nous appelons la fonction summary sur notre data-frame consom.csv.

Pour les échantillons de taille \(10\), nous obtenons :

       X1               X2               X3               X4               X5        
 Min.   :0.0464   Min.   :0.0387   Min.   :0.0483   Min.   :0.0281   Min.   :0.0295  
 1st Qu.:0.1150   1st Qu.:0.1086   1st Qu.:0.1121   1st Qu.:0.0836   1st Qu.:0.0790  
 Median :0.1681   Median :0.1870   Median :0.1636   Median :0.1308   Median :0.1151  
 Mean   :0.1923   Mean   :0.2185   Mean   :0.1826   Mean   :0.1558   Mean   :0.1296  
 3rd Qu.:0.2416   3rd Qu.:0.3024   3rd Qu.:0.2325   3rd Qu.:0.2008   3rd Qu.:0.1636  
 Max.   :0.7993   Max.   :0.8645   Max.   :0.6966   Max.   :0.7811   Max.   :0.5634  
       X6               X7               X8               X9               X10        
 Min.   :0.0356   Min.   :0.0389   Min.   :0.0670   Min.   :0.06389   Min.   :0.0610  
 1st Qu.:0.0935   1st Qu.:0.1113   1st Qu.:0.1370   1st Qu.:0.12089   1st Qu.:0.1500  
 Median :0.1588   Median :0.1610   Median :0.2120   Median :0.16463   Median :0.2192  
 Mean   :0.1978   Mean   :0.1965   Mean   :0.2387   Mean   :0.18094   Mean   :0.2475  
 3rd Qu.:0.2586   3rd Qu.:0.2563   3rd Qu.:0.3100   3rd Qu.:0.22478   3rd Qu.:0.3096  
 Max.   :1.0005   Max.   :0.8715   Max.   :0.9404   Max.   :0.62978   Max.   :1.2252  

Nous remarquons que la moyenne varie beaucoup d’un échantillon à l’autre, en effet elle varie de \(0.1179\), ce qui confirme notre remarque sur la variance qui change au cours du temps.

Pour les échantillons de taille \(100\), nous obtenons :

      X11               X12               X13               X14               X15         
 Min.   :0.07404   Min.   :0.08239   Min.   :0.07937   Min.   :0.08299   Min.   :0.07637  
 1st Qu.:0.15252   1st Qu.:0.14930   1st Qu.:0.16067   1st Qu.:0.16284   1st Qu.:0.14715  
 Median :0.19958   Median :0.19838   Median :0.20673   Median :0.20896   Median :0.19836  
 Mean   :0.20684   Mean   :0.21067   Mean   :0.22000   Mean   :0.22257   Mean   :0.20652  
 3rd Qu.:0.25186   3rd Qu.:0.25489   3rd Qu.:0.26754   3rd Qu.:0.26763   3rd Qu.:0.25615  
 Max.   :0.47418   Max.   :0.53622   Max.   :0.56355   Max.   :0.54974   Max.   :0.50513  
      X16               X17               X18               X19               X20         
 Min.   :0.08057   Min.   :0.07538   Min.   :0.07226   Min.   :0.08219   Min.   :0.07013  
 1st Qu.:0.16341   1st Qu.:0.16036   1st Qu.:0.13704   1st Qu.:0.14839   1st Qu.:0.14158  
 Median :0.22237   Median :0.21009   Median :0.17704   Median :0.19689   Median :0.17463  
 Mean   :0.24079   Mean   :0.22543   Mean   :0.18785   Mean   :0.20321   Mean   :0.18552  
 3rd Qu.:0.30082   3rd Qu.:0.27603   3rd Qu.:0.22638   3rd Qu.:0.24586   3rd Qu.:0.21774  
 Max.   :0.62920   Max.   :0.54530   Max.   :0.43365   Max.   :0.47149   Max.   :0.43013  

Cette fois-ci, la moyenne varie de \(0.05527\), ce qui réduit de moitié la variabilité observée pour les échantillons de taille \(10\).

Pour les échantillons de taille \(1000\), nous obtenons :

      X21               X22               X23               X24               X25         
 Min.   :0.09255   Min.   :0.08978   Min.   :0.09128   Min.   :0.09042   Min.   :0.09152  
 1st Qu.:0.16373   1st Qu.:0.16381   1st Qu.:0.15974   1st Qu.:0.15589   1st Qu.:0.15544  
 Median :0.20969   Median :0.20793   Median :0.20564   Median :0.19834   Median :0.20100  
 Mean   :0.21856   Mean   :0.21811   Mean   :0.21478   Mean   :0.20883   Mean   :0.21075  
 3rd Qu.:0.26222   3rd Qu.:0.26100   3rd Qu.:0.25784   3rd Qu.:0.25006   3rd Qu.:0.25350  
 Max.   :0.49736   Max.   :0.49901   Max.   :0.49160   Max.   :0.50714   Max.   :0.47620  
      X26               X27              X28               X29               X30         
 Min.   :0.09081   Min.   :0.0924   Min.   :0.09053   Min.   :0.09133   Min.   :0.08895  
 1st Qu.:0.16072   1st Qu.:0.1633   1st Qu.:0.15928   1st Qu.:0.16276   1st Qu.:0.15788  
 Median :0.20350   Median :0.2063   Median :0.20487   Median :0.20536   Median :0.20089  
 Mean   :0.21259   Mean   :0.2155   Mean   :0.21381   Mean   :0.21560   Mean   :0.21040  
 3rd Qu.:0.25500   3rd Qu.:0.2569   3rd Qu.:0.25728   3rd Qu.:0.25777   3rd Qu.:0.25276  
 Max.   :0.46788   Max.   :0.4878   Max.   :0.47927   Max.   :0.48296   Max.   :0.48571  

Enfin, cette fois-ci la moyenne varie de \(0.00973\). La variabilité est donc minime.

Pour la température, nous obtenons :

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  -3.33    6.11   10.84   11.16   15.84   32.78 

Corrélations

Nous allons maintenant étudier la corrélation entre nos échantillons de référence et les différentes variables que nous avons créées pour réaliser nos prédictions.

X2 <- data0[, 3]
summary(lm(X2 ~ mois + heure + weekend + Temperature + I(Temperature^2) + temp.heure + temp.weekend + temp.mois + mois.heure + weekend.heure + Temperature.lag, data = consom.csv))

Call:
lm(formula = X2 ~ mois + heure + weekend + Temperature + I(Temperature^2) + 
    temp.heure + temp.weekend + temp.mois + mois.heure + weekend.heure + 
    Temperature.lag, data = consom.csv)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.28276 -0.04242 -0.00395  0.03277  0.45221 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)       2.728e-01  4.772e-03  57.161  < 2e-16 ***
mois02           -2.204e-02  2.710e-03  -8.133 4.46e-16 ***
mois03           -5.069e-02  2.709e-03 -18.713  < 2e-16 ***
mois04           -5.917e-02  2.919e-03 -20.267  < 2e-16 ***
mois05           -6.088e-02  3.281e-03 -18.555  < 2e-16 ***
mois06           -6.722e-02  3.819e-03 -17.600  < 2e-16 ***
mois07           -5.458e-02  4.680e-03 -11.662  < 2e-16 ***
mois08           -9.009e-02  5.148e-03 -17.500  < 2e-16 ***
mois09           -6.595e-02  5.367e-03 -12.289  < 2e-16 ***
mois10           -6.195e-02  5.765e-03 -10.745  < 2e-16 ***
mois11           -5.793e-02  5.045e-03 -11.482  < 2e-16 ***
mois12           -1.248e-02  5.322e-03  -2.345 0.019044 *  
heure00:30:00    -4.057e-02  5.251e-03  -7.726 1.17e-14 ***
heure01:00:00    -7.817e-02  5.256e-03 -14.875  < 2e-16 ***
heure01:30:00    -1.079e-01  5.262e-03 -20.511  < 2e-16 ***
heure02:00:00    -1.314e-01  5.271e-03 -24.921  < 2e-16 ***
heure02:30:00    -1.448e-01  5.282e-03 -27.421  < 2e-16 ***
heure03:00:00    -1.509e-01  5.295e-03 -28.501  < 2e-16 ***
heure03:30:00    -1.557e-01  5.310e-03 -29.327  < 2e-16 ***
heure04:00:00    -1.561e-01  5.325e-03 -29.323  < 2e-16 ***
heure04:30:00    -1.555e-01  5.342e-03 -29.102  < 2e-16 ***
heure05:00:00    -1.553e-01  5.360e-03 -28.977  < 2e-16 ***
heure05:30:00    -1.532e-01  5.383e-03 -28.463  < 2e-16 ***
heure06:00:00    -1.469e-01  5.403e-03 -27.193  < 2e-16 ***
heure06:30:00    -1.296e-01  5.434e-03 -23.844  < 2e-16 ***
heure07:00:00    -1.291e-01  5.454e-03 -23.666  < 2e-16 ***
heure07:30:00    -1.141e-01  5.503e-03 -20.730  < 2e-16 ***
heure08:00:00    -7.971e-02  5.522e-03 -14.434  < 2e-16 ***
heure08:30:00    -8.027e-02  5.585e-03 -14.374  < 2e-16 ***
heure09:00:00    -6.882e-02  5.605e-03 -12.278  < 2e-16 ***
heure09:30:00    -4.401e-02  5.642e-03  -7.800 6.54e-15 ***
heure10:00:00    -4.030e-02  5.667e-03  -7.110 1.20e-12 ***
heure10:30:00    -2.507e-02  5.703e-03  -4.396 1.11e-05 ***
heure11:00:00    -1.992e-02  5.734e-03  -3.474 0.000513 ***
heure11:30:00    -1.188e-02  5.734e-03  -2.072 0.038292 *  
heure12:00:00     6.260e-03  5.771e-03   1.085 0.278069    
heure12:30:00     2.014e-02  5.779e-03   3.485 0.000493 ***
heure13:00:00     3.348e-02  5.820e-03   5.752 8.95e-09 ***
heure13:30:00     4.015e-02  5.849e-03   6.864 6.91e-12 ***
heure14:00:00     4.198e-02  5.894e-03   7.122 1.11e-12 ***
heure14:30:00     4.926e-02  5.925e-03   8.313  < 2e-16 ***
heure15:00:00     5.611e-02  5.972e-03   9.394  < 2e-16 ***
heure15:30:00     6.875e-02  6.012e-03  11.435  < 2e-16 ***
heure16:00:00     8.905e-02  6.062e-03  14.689  < 2e-16 ***
heure16:30:00     1.013e-01  6.106e-03  16.594  < 2e-16 ***
heure17:00:00     1.204e-01  6.157e-03  19.552  < 2e-16 ***
heure17:30:00     1.379e-01  6.204e-03  22.224  < 2e-16 ***
heure18:00:00     1.622e-01  6.255e-03  25.934  < 2e-16 ***
heure18:30:00     1.986e-01  6.303e-03  31.512  < 2e-16 ***
heure19:00:00     2.292e-01  6.353e-03  36.071  < 2e-16 ***
heure19:30:00     2.371e-01  6.406e-03  37.021  < 2e-16 ***
heure20:00:00     2.220e-01  6.457e-03  34.375  < 2e-16 ***
heure20:30:00     2.159e-01  6.509e-03  33.173  < 2e-16 ***
heure21:00:00     1.980e-01  6.560e-03  30.180  < 2e-16 ***
heure21:30:00     1.916e-01  6.614e-03  28.967  < 2e-16 ***
heure22:00:00     1.802e-01  6.666e-03  27.025  < 2e-16 ***
heure22:30:00     1.826e-01  6.718e-03  27.185  < 2e-16 ***
heure23:00:00     1.610e-01  6.767e-03  23.797  < 2e-16 ***
heure23:30:00     1.238e-01  6.820e-03  18.147  < 2e-16 ***
weekend          -2.956e-03  2.918e-03  -1.013 0.310997    
Temperature       3.476e-03  1.615e-03   2.152 0.031434 *  
I(Temperature^2) -2.872e-05  1.514e-05  -1.897 0.057863 .  
temp.heure       -3.828e-04  1.543e-05 -24.808  < 2e-16 ***
temp.weekend      1.927e-04  1.895e-04   1.017 0.309189    
temp.mois         2.023e-04  5.406e-05   3.741 0.000184 ***
mois.heure        2.943e-05  2.467e-05   1.193 0.232955    
weekend.heure     1.801e-04  1.736e-04   1.037 0.299672    
Temperature.lag  -1.737e-03  1.572e-03  -1.105 0.269176    
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.07092 on 17452 degrees of freedom
Multiple R-squared:  0.7058,    Adjusted R-squared:  0.7047 
F-statistic:   625 on 67 and 17452 DF,  p-value: < 2.2e-16

Les variables significatives correspondent à celles qui ont la valeur Pr(>|t|) inférieure à \(\alpha = 0.05\). Donc, dans ce modèle, les variables Temperature, temp.mois, temp.heure, heure et mois sont significatives. Cependant, nous ajoutons tout de même les autres variables car ça ne peut pas fausser nos prédictions, dans le pire des cas elles ne changent rien et elles peuvent même, éventuellement, améliorer notre prédiction.

En testant nos prédictions sur Train et Test avec différentes variables, nous avons remarqué que nos scores étaient meilleurs en prenant les variables mois et heure au format chr et non au format numérique. Pour toutes les variables croisées, nous avons fait les calculs au format numérique.

Méthodes de prédiction

Pour chaque prédiction, nous implémentons graphiquement les \(2520\) dernières lignes de l’échantillon puis, à la suite, la prédiction réalisée. Nous allons représenter ces courbes pour nos trois échantillons de référence : X2, X12 et X22. De plus, nous avons choisi de ne pas montrer l’échantillon en entier afin de gagner en lisibilité et de nous permettre d’analyser graphiquement la prédiction.

Pour chaque méthode, nous avons créé une fonction qui dépend d’un paramètre \(k\) et qui réalise la prédiction de l’échantillon Xk. Pour construire la prédiction finale, nous avons tout simplément fait une boucle sur \(k\) allant de \(1\) à \(30\) et nous avons concaténer tous les vecteurs en un.

Régression linéaire

Nous avons d’abord réalisé des prédictions par régression linéaire à l’aide de la fonction lm. Pour ce modèle, ce qui est important est de bien choisir les variables par rapport auxquelles nous réalisons la régression.

Dans un premier temps, nous avons réalisé une régression avec les variables de base mois, heure et Temperature.

reglin1 <- function(k) {
  # modèle
  Xk <- data0[, k + 1]
  model <- lm(Xk ~ mois + heure + Temperature, data = consom.csv)
  
  # prédiction
  data.pred <- data1[c((2784*(k - 1) + 1):(2784*k)), ]
  temp.Xk <- my.na.approx(data.pred$Temperature)
  Xk.pred <- data.frame(mois.pred, heure.pred, temp.Xk)
  names(Xk.pred) <- c("mois", "heure", "Temperature")
  pred <- unname(predict(model, newdata = Xk.pred))
}

Dans un second temps, nous avons ajouté la variable weekend ainsi que les variables croisées à la régression.

reglin2 <- function(k) {
  # modèle
  Xk <- data0[, k + 1]
  model <- lm(Xk ~ mois + heure + weekend + Temperature + temp.heure + temp.weekend + temp.mois + mois.heure + weekend.heure, data = consom.csv)
  
  # prédiction
  data.pred <- data1[c((2784*(k - 1) + 1):(2784*k)), ]
  temp.Xk <- my.na.approx(data.pred$Temperature)
  temp.heure.pred <- temp.Xk*heure.num.pred
  temp.mois.pred <- temp.Xk*as.numeric(mois.pred)
  temp.weekend.pred <- temp.Xk*weekend.pred
  
  Xk.pred <- data.frame(mois.pred, heure.pred, weekend.pred, temp.Xk, temp.heure.pred, temp.mois.pred, temp.weekend.pred, mois.heure.pred, weekend.heure.pred)
  names(Xk.pred) <- c("mois", "heure", "weekend", "Temperature", "temp.heure", "temp.mois", "temp.weekend", "mois.heure", "weekend.heure")
  pred <- unname(predict(model, newdata = Xk.pred))
}

Finalement, nous avons ajouté la variable Temperature.lag ainsi que la Temperature au carré.

reglin3 <- function(k) {
  # modèle
  Xk <- data0[, k + 1]
  model <- lm(Xk ~ mois + heure + weekend + Temperature + I(Temperature^2) + Temperature.lag + temp.heure + temp.weekend + temp.mois + mois.heure + weekend.heure, data = consom.csv)
  
  # prédiction
  data.pred <- data1[c((2784*(k - 1) + 1):(2784*k)), ]
  temp.Xk <- my.na.approx(data.pred$Temperature)
  temp.Xk.lag <- sapply(1:length(temp.Xk), function(x) temp.Xk[x - 1])
  temp.Xk.lag[1] <- temp.Xk.lag[2]
  
  temp.heure.pred <- temp.Xk*heure.num.pred
  temp.mois.pred <- temp.Xk*as.numeric(mois.pred)
  temp.weekend.pred <- temp.Xk*weekend.pred
  
  Xk.pred <- data.frame(mois.pred, heure.pred, weekend.pred, temp.Xk, temp.Xk^2, temp.Xk.lag, temp.heure.pred, temp.mois.pred, temp.weekend.pred, mois.heure.pred, weekend.heure.pred)
  names(Xk.pred) <- c("mois", "heure", "weekend", "Temperature", "I(Temperature^2)", "Temperature.lag", "temp.heure", "temp.mois", "temp.weekend", "mois.heure", "weekend.heure")
  pred <- unname(predict(model, newdata = Xk.pred))
}

Comparons ces trois régressions linéaires pour X2, X12 et X22.

Les graphiques de l’échantillon X2 sont :

Nous remarquons que pour les trois prédictions, la variation de la partie de droite est trop petite par rapport a la partie de gauche. En effet, l’échantillon de base varie entre \(0\) et \(0.75\), tandis que la prédiction varie entre \(0.1\) et \(0.5\). Cependant, la troisième prédiction semble plus large que les deux autres. Pour les groupes de taille \(10\), le troisième modèle de régression linéaire semble mieux correspondre.

Les graphiques de l’échantillon X12 sont :

Pour les échantillons de taille \(100\), nous pouvons faire la même remarque que pour les échantillons de taille \(10\). De plus, le troisième modèle de régression linéaire réalise une prédiction qui semble assez stationnaire et périodique. Cependant, notre échantillon de base ne l’est pas. Bien que la largeur de la prédiction réalisée avec le deuxième modèle est plus petite, elle est plus semblable à l’échantillon dans son attitude. Pour les groupes de taille \(100\), le deuxième modèle de régression linéaire semble mieux correspondre.

Les graphiques de l’échantillon X22 sont :

Nous pouvons faire les mêmes remarques que pour les échantillons de taille \(100\). Cependant, comme la variance des échantillons est plus petite dans ce cas-là, la différence est moins flagrante. À nouveau, le comportement de la troisième prédiction semble trop régulier. Pour les groupes de taille \(1000\), le deuxième modèle de régression linéaire semble mieux correspondre.

Modèle additif généralisé

Ensuite, nous avons réalisé des prédictions avec un modèle additif généralisé à l’aide de la fonction gam. Pour ce modèle, ce qui est important est de bien choisir les variables ainsi que les paramètres qui lui sont associées.

Nous avons pris les variables :

  • Temperature avec comme paramètre \(k = 54\);
  • heure en numérique avec comme paramètre \(k = 48\);
  • mois en numérique avec comme paramètre \(k = 12\);
  • mois.heure avec comme paramètre \(k = 191\);
  • weekend.heure avec comme paramètre \(k = 10\);
  • temp.weekend avec comme paramètre \(k = 40\);
  • temp.mois avec comme paramètre \(k = 20\);
  • temp.heure avec comme paramètre \(k = 18\);
  • Temperature.lag avec comme paramètre \(k = 10\).

Pour sélectionner ces paramètres, à nouveau, nous avons fait des prédictions tests sur Train et Test. Pour cela, nous avons testé plusieurs valeurs de \(k\) pour chaque variable et comparé les scores à l’aide de la fonction rmse. Nous avons gardé les paramètres qui nous permettaient d’obtenir le plus petit score.

Nous avons donc implémenté notre fonction de prédiction avec les paramètres trouvés.

gam.pred <- function (k) {
  # modèle
  Xk <- data0[, k + 1]
  model <- gam(Xk ~ s(Temperature, k = 54) + s(heure.num, k = 48) + s(as.numeric(mois), k = 12) + s(mois.heure, k = 191) + s(weekend.heure, k = 10) + s(temp.weekend, k = 40) + s(temp.mois, k = 20) + s(Temperature.lag, k = 10) + s(temp.heure, k = 18), data = consom.csv)
  
  # prédiction
  data.pred <- data1[c((2784*(k - 1) + 1):(2784*k)), ]
  temp.Xk <- my.na.approx(data.pred$Temperature)
  temp.Xk.lag <- sapply(1:length(temp.Xk), function(x) temp.Xk[x - 1])
  temp.Xk.lag[1] <- temp.Xk.lag[2]
  
  Xk.pred <- data.frame(temp.Xk, heure.num.pred, as.numeric(mois.pred), mois.heure.pred,
                        weekend.heure.pred, temp.Xk*weekend.pred, temp.Xk*as.numeric(mois.pred),
                        temp.Xk.lag, temp.Xk*heure.num.pred)
  names(Xk.pred) <- c("Temperature", "heure.num", "mois", "mois.heure", "weekend.heure", "temp.weekend", "temp.mois", "Temperature.lag", "temp.heure")
  pred <- predict(model, newdata = Xk.pred)
}

Le graphique de l’échantillon X2 est :

Tout comme pour les prédictions par régression linéaire, la variance de la partie de droite est plus petite que la partie de gauche. Cependant, cette prédiction équivaut à la troisième prédiction par lm.

Le graphique de l’échantillon X12 est :

À nouveau, cette prédiction équivaut à la troisième prédiction par lm. La prédiction est plutôt stationnaire et périodique.

Le graphique de l’échantillon X22 est :

Enfin, même pour les groupes de taille \(1000\), nous faisons les mêmes observations que pour les autres groupes de tailles différentes.

Les plus proches voisins

Enfin, nous avons réalisé des prédictions avec la méthode des plus proches voisins. Si nous souhaitons classifier l’ensemble \(\{ x_i \text{ pour } i \in 1, \cdots, N \}\), cette méthode associe à chaque élément une classe \(c(x_i)\) qui correspond à la classe des \(k\) voisins les plus proches de cet élément. Pour ce modèle, ce qui est important est de bien choisir les variables ainsi que les paramètres qui sont associés à chaque échantillon. Les paramètres sont les différents \(k\) qui définissent le nombre de plus proches voisins auxquels nous nous intéressons. Donnons un exemple pour illustrer cette classification :

  • Si nous prenons en compte les trois plus proches voisins, le point vert sera de classe bleue :

  • Si nous prenons en compte les sept plus proches voisins, le point vert sera de classe orange :

À nouveau, pour déterminer les meilleurs paramètres, nous allons prédire nos données Test à partir des données Train. Pour chaque colonne d’échantillon X nous aurons un paramètre \(k\) différent. Nous enregistrons ces paramètres dans une variable nommée best_k.

test.knn <- function (k = 1, i = 1) {
  # variables pour Train
  mois.tra <- as.numeric(Train$mois)
  heure.tra <- heure.num[train_ind]
  we.tra <- Train$weekend
  we.num.tra <- as.numeric(Train$week.num)
  temp.tra <- Train$Temperature
  temp.lag.tra <- Train$Temperature.lag
  
  # variables pour Test
  mois.tes <- as.numeric(Test$mois)
  heure.tes <- heure.num[-train_ind]
  we.tes <- Test$weekend
  we.num.tes <- as.numeric(Test$week.num)
  temp.tes <- Test$Temperature
  temp.lag.tes <- Test$Temperature.lag
  
  tra <- data.frame(mois.tra, heure.tra, we.tra, we.num.tra, temp.tra, temp.lag.tra, temp.tra*heure.tra, temp.tra*we.tra, temp.tra*mois.tra, mois.tra*heure.tra, we.tra*heure.tra)
  
  tes <- data.frame(mois.tes, heure.tes, we.tes, we.num.tes, temp.tes, temp.lag.tes, temp.tes*heure.tes, temp.tes*we.tes, temp.tes*mois.tes, mois.tes*heure.tes, we.tes*heure.tes)
  
  conso <- Train[, i + 5]
  pred <- knn.reg(train = tra, test = tes, y = conso, k = k)$pred
  act <- Test[, i + 5]
  rmse(pred, act)
}
best_k <- vector()
for (I in 1:30) {
  ListK <- c(1:10)
  List <- sapply(ListK, test.knn, i = I)
  best_k <- c(best_k, which.min(List))
}

La liste best_k vaut :

print(best_k)
 [1] 9 8 7 6 9 6 5 6 9 5 4 4 4 4 5 4 4 4 4 4 3 4 4 3 3 3 3 3 3 3

Une fois cette liste créée, nous réalisons notre prédiction en prenant en compte les variables mois en numérique, heure en numérique, weekend, week.num, Temperature et Temperature.lag.

tra <- data.frame(as.numeric(consom.csv$mois), heure.num, consom.csv$weekend, as.numeric(consom.csv$week.num), consom.csv$Temperature, consom.csv$Temperature.lag)
knn.pred <- function (i) {
  tes <- data.frame(as.numeric(mois.pred), heure.num.pred, weekend.pred, as.numeric(week.num.pred), Temperature.pred, Temperature.lag.pred)
  tes <- tes[c((2784*(i - 1) + 1):(2784*i)), ]
  conso <- consom.csv[, i + 5]
  pred <- knn.reg(train = tra, test = tes, y = conso, k = best_k[i])$pred
}

Le graphique de l’échantillon X2 est :

Cette fois-ci, contrairement aux deux méthodes précédentes, la variance de la prédiction est bien plus proche de la variance de l’échantillon. De plus, la prédiction ne semble ni particulièrement stationnaire ni particulièrement périodique. L’allure de la courbe est plutôt cohérente.

Le graphique de l’échantillon X12 est :

À nouveau, nous pouvons faire les mêmes commentaires que pour les échantillons de taille \(10\). En effet, l’allure de la courbe est cohérente et les deux parties semblent se comporter de la même façon. Les variances sont encore plus semblables.

Le graphique de l’échantillon X22 est :

Pour les échantillons de taille \(1000\), il se passe la même chose que précédemment avec une différence de variance très faible.

Graphiquement, les prédictions faites par la méthode des plus proches voisins semblent les meilleures.

Conclusion

De façon générale, nous remarquons que plus la taille de l’échantillon est grande, plus la variance est petite. La difficulté vient donc du fait qu’il faut prédire correctement cette variance qui change pour chaque échantillon.

La régression linéaire nous a permis d’avoir nos meilleurs scores. Cependant, à la représentation graphique, nous avons remarqué que pour les échantillons de taille \(10\), la prédiction n’est pas cohérente, en effet, le modèle ne retrouve pas la bonne variance. Par contre, plus la taille du groupe est grande, plus la variance diminue et plus la prédiction est cohérente.

Le modèle additif généralisé n’a pas amélioré nos scores. Cependant, à la représentation graphique, la variance est plus conservée pour toutes les tailles d’échantillon. En effet, pour les groupes de taille \(10\), la variance de la prédiction est bien plus grande que pour la régression linéaire. Dès les groupes de taille \(100\), les variances sont quasiment égales.

Le modèle des plus proches voisins n’a pas amélioré nos scores. Cependant, les représentations graphiques des prédictions semblent tout à fait cohérentes. En effet, la prédiction de l’échantillon X2 est bien plus large que dans les deux autres méthodes. De plus, les courbes des prédictions semblent se comporter de la même façon que les courbes des échantillons.

Pour tenter d’améliorer nos scores, nous pourrions utiliser des modèles de séries temporelles comme ARIMA par exemple. Il faudrait, pour ce faire, trouver les paramètres \(p\) et \(q\) associés à chaque échantillon Xk.

LS0tCnRpdGxlOiAiUHLDqXZpc2lvbiBkZSBjb25zb21tYXRpb24gw6lsZWN0cmlxdWUgw6AgcGx1c2lldXJzIG5pdmVhdXgiCmF1dGhvcjogIkx1bmEgTUFUU1VPIGV0IENsYXJhIENBUkxJRVIiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCgoKIyBJbnRyb2R1Y3Rpb24KCmBgYHtyIGluY2x1ZGUgPSBGQUxTRX0KbGlicmFyeSh6b28pCmxpYnJhcnkocGx5cikKbGlicmFyeSh0aW1lRGF0ZSkKbGlicmFyeSh4dHMpCmxpYnJhcnkoZHlncmFwaHMpCmxpYnJhcnkobWdjdikKbGlicmFyeShmaWVsZHMpCmxpYnJhcnkobWFncml0dHIpCmxpYnJhcnkoTUFTUykKbGlicmFyeShjbGFzcykKbGlicmFyeShGTk4pCmBgYAoKRGFucyBjZSBwcm9qZXQsIG5vdXMgY2hlcmNob25zIMOgIHByw6lkaXJlIGxhIGNvbnNvbW1hdGlvbiDDqWxlY3RyaXF1ZSBkZQpncm91cGVzIGRlIGNsaWVudHMgZHUgJDFee1x0ZXh0e2VyfX0kIGphbnZpZXIgJDIwMTQkIGF1ICQyNyQKZsOpdnJpZXIgJDIwMTQkLiBQb3VyIGNlbGEsIG5vdXMgYXZvbnMgw6Agbm90cmUgZGlzcG9zaXRpb24gJDMwJArDqWNoYW50aWxsb25zIGRlIHRhaWxsZSAkMTc1MjAkIHF1aSBjb3JyZXNwb25kZW50IMOgIGRpZmbDqXJlbnRlcyB0YWlsbGVzCmRlIGdyb3VwZSA6CgoqIGRlIFgxIMOgIFgxMCwgaWwgcydhZ2l0IGRlIGdyb3VwZXMgZGUgJDEwJCBwZXJzb25uZXM7CiogZGUgWDExIMOgIFgyMCwgaWwgcydhZ2l0IGRlIGdyb3VwZXMgZGUgJDEwMCQgcGVyc29ubmVzOwoqIGRlIFgyMSDDoCBYMzAsIGlsIHMnYWdpdCBkZSBncm91cGVzIGRlICQxMDAwJCBwZXJzb25uZXMuCgpDZXMgw6ljaGFudGlsbG9ucyBub3VzIGRvbm5lbnQgbGEgY29uc29tbWF0aW9uIMOpbGVjdHJpcXVlIGRlIGNlcyBkaWZmw6lyZW50cyBncm91cGVzIHRvdXQgYXUgbG9uZyBkZSBsJ2FubsOpZSAkMjAxMyQgcGFyIGludGVydmFsbGVzIGRlICQzMCQgbWludXRlcy4KCkRhbnMgdW4gcHJlbWllciB0ZW1wcywgbm91cyBhbGxvbnMgZmFpcmUgdW5lIHJhcGlkZSBhbmFseXNlIGRlIG5vcyBkb25uw6llcy4gUHVpcyBub3VzIHRlc3Rlcm9ucyB0cm9pcyBtw6l0aG9kZXMgZGUgcHLDqWRpY3Rpb24uIEVuZmluLCBub3VzIHBvdXJyb25zIGxlcyBjb21wYXJlci4KCgoKIyBDb21wdGUtcmVuZHUKCgojIyBQcsOpdHJhaXRlbWVudCBldCBtaXNlcy1lbi1mb3JtZSBkZXMgZG9ubsOpZXMKClVuZSBncm9zc2UgcGFydGllIGRlIGNlIHByb2pldCBjb25zaXN0ZSDDoCBiaWVuIHPDqWxlY3Rpb25uZXIgbGVzIHZhcmlhYmxlcyBxdWUgbm91cyB1dGlsaXNvbnMgZGFucyBsZXMgcHLDqWRpY3Rpb25zLiBQb3VyIGNlbGEsIGlsIGZhdXQgbGVzIGF2b2lyIHNvdXMgbGUgYm9uIGZvcm1hdCBldCBjcsOpZXIgbGVzIGRhdGEtZnJhbWVzIGFkw6lxdWF0cy4gCgpUb3V0IGQnYWJvcmQgbm91cyBpbXBvcnRvbnMgbm9zIGRvbm7DqWVzIDoKYGBge3J9CmRhdGEwIDwtIHJlYWQuY3N2KCdEYXRhL0RhdGEwLmNzdicsIGhlYWRlciA9IFRSVUUpCmRhdGExIDwtIHJlYWQuY3N2KCdEYXRhL0RhdGExLmNzdicsIGhlYWRlciA9IFRSVUUpCmBgYApMYSBwcmVtacOocmUgdmFyaWFibGUgYGRhdGEwYCBjb3JyZXNwb25kIGF1eCBkb25uw6llcyBkZSBjb25zb21tYXRpb24gZCfDqWxlY3RyaWNpdMOpIGRlIGwnYW5uw6llICQyMDEzJC4gRWxsZSBjb250aWVudCAkMTc1MjAkIGxpZ25lcyBldCAkMzIkIGNvbG9ubmVzIGRvbnQgbGVzICQzMCQgw6ljaGFudGlsbG9ucywgbGEgdGVtcMOpcmF0dXJlLCBsYSBkYXRlIGV0IGwnaGV1cmUuCgpMYSBkZXV4acOobWUgdmFyaWFibGUgYGRhdGExYCBjb3JyZXNwb25kIGF1eCBkb25uw6llcyBjb25udWVzIGFmaW4gZGUgcsOpYWxpc2VyIG5vcyBwcsOpdmlzaW9ucy4gRWxsZSBjb250aWVudCAkODM1MjAkIGxpZ25lcyBldCAkNCQgY29sb25uZXMgZG9udCBsZXMgdGVtcMOpcmF0dXJlcyBjb25udWVzIGR1IGTDqWJ1dCBkZSBsJ2FubsOpZSAkMjAxNCQsIGxhIGRhdGUsIGwnaGV1cmUgZXQgbCdpbmRpY2UgZGUgbCfDqWNoYW50aWxsb24gY29ycmVzcG9uZGFudC4KClBhciBsYSBzdWl0ZSBub3VzIGNyw6lvbnMgZGUgbm9tYnJldXNlcyB2YXJpYWJsZXMgY29udGVuYW50IGxlcyBkaWZmw6lyZW50ZXMgdmFsZXVycyBxdWkgbm91cyBpbnTDqXJlc3NlbnQgOgoKKiBgZGF0ZWAgOiBwb3VyIGF2b2lyIGxhIGRhdGUgZXQgbCdoZXVyZSDDoCBjaGFxdWUgaW5zdGFudCBzb3VzIGxlIGJvbiBmb3JtYXQ7CmBgYHtyfQpkYXRlLmRlYnV0IDwtIHN0cnB0aW1lKGMoIjAxLzAxLzIwMTMgMDA6MDA6MDAiKSwgJyVkLyVtLyVZICVIOiVNOiVTJykKZGF0ZS5maW4gPC0gc3RycHRpbWUoYygiMzEvMTIvMjAxMyAyMzozMDowMCIpLCAiJWQvJW0vJVkgJUg6JU06JVMiKQpkYXRlIDwtIHNlcShkYXRlLmRlYnV0LCBkYXRlLmZpbiwgYnkgPSAnMzAgbWluJykKcm0oZGF0ZS5kZWJ1dCwgZGF0ZS5maW4pCmBgYAoqIGBUZW1wZXJhdHVyZWAgZXQgYFRlbXBlcmF0dXJlLmxhZ2AgOiBpbCBmYXV0IGZhaXJlIGF0dGVudGlvbiBjYXIgbGEgY29sb25uZSBkZXMgdGVtcMOpcmF0dXJlcyBjb250aWVudCBkZXMgYE5BYCwgbm91cyBjcsOpb25zIGFsb3JzIGxhIGZvbmN0aW9uIGBteS5uYS5hcHByb3hgIHF1aSByZW1wbGFjZSBsZXMgYE5BYCBwYXIgZGVzIHZhbGV1cnMgZW4gZmFpc2FudCBsYSBtb3llbm5lICh2b2lyIGV4ZW1wbGUgcGx1cyBsb2luKSwgbGEgZGV1eGnDqG1lIHZhcmlhYmxlIGNvbnRpZW50IGxlcyB0ZW1ww6lyYXR1cmVzIGTDqWNhbMOpZXMgZGUgJDEkOwpgYGB7cn0KbXkubmEuYXBwcm94IDwtIGZ1bmN0aW9uKHgpIHsKICBpZiAoc3VtKGlzLmZpbml0ZSh4KSkgPT0gMEwpIHJldHVybih4KQogIGlmIChzdW0oaXMuZmluaXRlKHgpKSA9PSAxTCkgcmV0dXJuKG5hLmFwcHJveCh4LCBydWxlID0gMiwgbWV0aG9kID0gImNvbnN0YW50IikpCiAgbmEuYXBwcm94KHgsIHJ1bGUgPSAyKQp9ClRlbXBlcmF0dXJlIDwtIGRhdGEwJFRlbXBlcmF0dXJlClRlbXBlcmF0dXJlIDwtIG15Lm5hLmFwcHJveChUZW1wZXJhdHVyZSkKVGVtcGVyYXR1cmUubGFnIDwtIGFzLm51bWVyaWMoc2FwcGx5KDE6bGVuZ3RoKFRlbXBlcmF0dXJlKSwgZnVuY3Rpb24oeCkgVGVtcGVyYXR1cmVbeCAtIDFdKSkKVGVtcGVyYXR1cmUubGFnWzFdIDwtIFRlbXBlcmF0dXJlLmxhZ1syXQpgYGAKKiBgbW9pc2AgZXQgYGpvdXJgIDogbm91cyBnYXJkb25zIHVuaXF1ZW1lbnQgbGUgbnVtZXJvIGR1IG1vaXMgY29ycmVzcG9uZGFudCBldCB1bmlxdWVtZW50IGxhIGRhdGUgZW4gcmV0aXJhbnQgbCdoZXVyZTsKYGBge3J9Cm1vaXMgPC0gZm9ybWF0KGRhdGUsICIlbSIpCmpvdXIgPC0gZm9ybWF0KGRhdGUsICIlZC8lbS8lWSIpCmBgYAoqIGBoZXVyZWAgZXQgYGhldXJlLm51bWAgOiBkYW5zIGxhIHByZW1pZXJlIHZhcmlhYmxlIG5vdXMgZ2FyZG9ucyB1bmlxdWVtZW50IGwnaGV1cmUgY29ycmVzcG9uZGFudGUgZXQgZGFucyBsYSBkZXV4acOobWUgbm91cyBsYSB0cmFuc2Zvcm1vbnMgZW4gdmFsZXVyIG51bcOpcmlxdWUgZGUgdGVsbGUgc29ydGUgcXVlICQxMiRoJDMwJCBzb2l0IMOpZ2FsIMOgICQxMi41JDsKYGBge3J9CmhldXJlIDwtIGZvcm1hdChkYXRlLCAiJUg6JU06JVMiKQplc3NhaS5oZXVyZSA8LSBhcy5QT1NJWGx0KGhldXJlLCBmb3JtYXQgPSAiJUg6JU06JVMiKQpoZXVyZS5udW0gPC0gYXMubnVtZXJpYyhlc3NhaS5oZXVyZSRob3VyKSArIChhcy5udW1lcmljKGVzc2FpLmhldXJlJG1pbikvNjApCnJtKGVzc2FpLmhldXJlKQpgYGAKKiBgd2Vla2VuZGAgOiBub3VzIGNyw6lvbnMgdW5lIHZhcmlhYmxlIHF1aSB2YXV0ICQwJCBzaSBub3VzIHNvbW1lcyBlbiBzZW1haW5lIGV0ICQxJCBzaSBub3VzIHNvbW1lcyBlbiB3ZWVrLWVuZDsKYGBge3J9CmRheSA8LSB3ZWVrZGF5cyhhcy5EYXRlKGZvcm1hdChkYXRlLCAiJWQvJW0vJVkiKSkpCndlZWtlbmQgPC0gKGRheSA9PSAiU3VuZGF5IikgKyAoZGF5ID09ICJTYXR1cmRheSIpCnJtKGRheSkKYGBgCiogYHdlZWsubnVtYCA6IGNldHRlIHZhcmlhYmxlIGNvcnJlc3BvbmQgYXUgbnVtw6lybyBkZSBsYSBzZW1haW5lIMOgIGxhcXVlbGxlIG5vdXMgc29tbWVzLgpgYGB7cn0Kd2Vlay5udW0gPC0gc3RyZnRpbWUoZGF0ZSwgZm9ybWF0ID0gIiVWIikKYGBgCgpEb25ub25zIHVuIGV4ZW1wbGUgZCd1dGlsaXNhdGlvbiBkZSBsYSBmb25jdGlvbiBgbXkubmEuYXBwcm94YCA6CmBgYHtyfQpteS5uYS5hcHByb3goYyhOQSwgMSwgTkEsIE5BLCAyLCBOQSkpCmBgYApBaW5zaSwgaWwgYSBkaXZpc8OpICQyIC0gMSQgcGFyICQzJCBwb3VyIG9idGVuaXIgbGVzIGRldXggdmFsZXVycyBkdSBtaWxpZXUgZXQgaWwgYSBtaXMgbGVzIHZhbGV1cnMgJDEkIGV0ICQyJCBzdXIgbGVzIGJvcmRzLgoKRW5zdWl0ZSBub3VzIHN0b2Nrb25zIHRvdXRlcyBjZXMgdmFyaWFibGVzIGRhbnMgdW4gZGF0YS1mcmFtZSBub21tw6kgYGNvbnNvbS5jc3ZgIDoKYGBge3J9CmNvbnNvbS5jc3YgPC0gZGF0YS5mcmFtZShkYXRlLCBtb2lzLCBoZXVyZSwgd2Vla2VuZCwgd2Vlay5udW0sIGRhdGEwWywgYygyOjMxKV0sIFRlbXBlcmF0dXJlLCBUZW1wZXJhdHVyZS5sYWcpCmBgYAoKTm91cyBjcsOpb25zIGxlcyBtw6ptZSB2YXJpYWJsZXMgc3VpdmllcyBkZSBsJ2V4dGVuc2lvbnMgYC5wcmVkYCBxdWkgY29ycmVzcG9uZGVudCDDoCBjZXMgbcOqbWUgdmFsZXVycyBtYWlzIGFzc29jacOpZXMgYXV4IGRvbm7DqWVzIHF1ZSBub3VzIHNvdWhhaXRvbnMgcHLDqWRpcmUuIE5vdXMgbGVzIHN0b2Nrb25zIGRhbnMgdW4gZGF0YS1mcmFtZSBub21tw6kgYGNvbnNvbS5wcmVkLmNzdmAuCmBgYHtyIGluY2x1ZGUgPSBGQUxTRX0KZGF0ZS5kZWJ1dC5wcmVkIDwtIHN0cnB0aW1lKGMoIjAxLzAxLzIwMTQgMDA6MDA6MDAiKSwgJyVkLyVtLyVZICVIOiVNOiVTJykKZGF0ZS5maW4ucHJlZCA8LSBzdHJwdGltZShjKCIyNy8wMi8yMDE0IDIzOjMwOjAwIiksICIlZC8lbS8lWSAlSDolTTolUyIpCmRhdGUucHJlZCA8LSBzZXEoZGF0ZS5kZWJ1dC5wcmVkLCBkYXRlLmZpbi5wcmVkLCBieSA9ICczMCBtaW4nKQpybShkYXRlLmRlYnV0LnByZWQsIGRhdGUuZmluLnByZWQpCgpUZW1wZXJhdHVyZS5wcmVkIDwtIGRhdGExJFRlbXBlcmF0dXJlClRlbXBlcmF0dXJlLnByZWQgPC0gbXkubmEuYXBwcm94KFRlbXBlcmF0dXJlLnByZWQpClRlbXBlcmF0dXJlLmxhZy5wcmVkIDwtIGFzLm51bWVyaWMoc2FwcGx5KDE6bGVuZ3RoKFRlbXBlcmF0dXJlLnByZWQpLCBmdW5jdGlvbih4KSBUZW1wZXJhdHVyZS5wcmVkW3ggLSAxXSkpClRlbXBlcmF0dXJlLmxhZy5wcmVkWzFdIDwtIFRlbXBlcmF0dXJlLmxhZy5wcmVkWzJdCgptb2lzLnByZWQgPC0gZm9ybWF0KGRhdGUucHJlZCwgIiVtIikKCmhldXJlLnByZWQgPC0gZm9ybWF0KGRhdGUucHJlZCwgIiVIOiVNOiVTIikKZXNzYWkuaGV1cmUgPC0gYXMuUE9TSVhsdChoZXVyZS5wcmVkLCBmb3JtYXQgPSAiJUg6JU06JVMiKQpoZXVyZS5udW0ucHJlZCA8LSBhcy5udW1lcmljKGVzc2FpLmhldXJlJGhvdXIpICsgKGFzLm51bWVyaWMoZXNzYWkuaGV1cmUkbWluKS82MCkKcm0oZXNzYWkuaGV1cmUpCgpkYXkgPC0gd2Vla2RheXMoYXMuRGF0ZShmb3JtYXQoZGF0ZS5wcmVkLCAiJWQvJW0vJVkiKSkpCndlZWtlbmQucHJlZCA8LSAoZGF5ID09ICJTdW5kYXkiKSArIChkYXkgPT0gIlNhdHVyZGF5IikgIyBuZSBzZXJ0IMOgIHJpZW4gcG91ciBsbQpybShkYXkpCgp3ZWVrLm51bS5wcmVkIDwtIHN0cmZ0aW1lKGRhdGUucHJlZCwgZm9ybWF0ID0gIiVWIikKCmNvbnNvbS5wcmVkLmNzdiA8LSBkYXRhLmZyYW1lKGRhdGUucHJlZCwgbW9pcy5wcmVkLCBoZXVyZS5wcmVkLCB3ZWVrZW5kLnByZWQsIHdlZWsubnVtLnByZWQsIGRhdGExJEluZCwgVGVtcGVyYXR1cmUucHJlZCkKbmFtZXMoY29uc29tLnByZWQuY3N2KSA8LSBjKCJkYXRlIiwgIm1vaXMiLCAiaGV1cmUiLCAid2Vla2VuZCIsICJ3ZWVrLm51bSIsICJJbmQiLCAiVGVtcGVyYXR1cmUiKQpgYGAKClBvdXIgbGVzIHByw6lkaWN0aW9ucywgbm91cyBhbGxvbnMgYXZvaXIgYmVzb2luIGRlIHZhcmlhYmxlcyBjcm9pc8OpZXMuIEVuIGVmZmV0LCBlbGxlcyBwZXJtZXR0ZW50IGQnYXZvaXIgdW4gbW9kw6hsZSBwbHVzIHByb2NoZSBkZSBsYSByw6lhbGl0w6kuIFBhciBleGVtcGxlLCBsYSB0ZW1ww6lyYXR1cmUgZW4gamFudmllciBuZSB2YSBwYXMgaW5mbHVlciBjb21tZSBsYSB0ZW1ww6lyYXR1cmUgZW4gbWFycywgYydlc3QgcG91ciBjZWxhIHF1ZSBub3VzIGludHJvZHVpc29ucyB1bmUgdmFyaWFibGUgYFRlbXBlcmF0dXJlYCBmb2lzIGBtb2lzYC4gTm91cyBsZXMgY3LDqW9ucyA6CmBgYHtyfQp0ZW1wLmhldXJlIDwtIFRlbXBlcmF0dXJlKmhldXJlLm51bQp0ZW1wLndlZWtlbmQgPC0gVGVtcGVyYXR1cmUqd2Vla2VuZAp0ZW1wLm1vaXMgPC0gVGVtcGVyYXR1cmUqYXMubnVtZXJpYyhtb2lzKQptb2lzLmhldXJlIDwtIGFzLm51bWVyaWMobW9pcykqaGV1cmUubnVtCndlZWtlbmQuaGV1cmUgPC0gd2Vla2VuZCpoZXVyZS5udW0KYGBgCgpOb3VzIGZhaXNvbnMgZGUgbcOqbWUgcG91ciBsZXMgdmFyaWFibGVzIGAucHJlZGAuCmBgYHtyIGluY2x1ZGUgPSBGQUxTRX0KbW9pcy5oZXVyZS5wcmVkIDwtIGFzLm51bWVyaWMobW9pcy5wcmVkKSpoZXVyZS5udW0ucHJlZAp3ZWVrZW5kLmhldXJlLnByZWQgPC0gd2Vla2VuZC5wcmVkKmhldXJlLm51bS5wcmVkCmBgYAoKRW5maW4sIHBvdXIgdGVzdGVyIG5vcyBwcsOpZGljdGlvbnMsIG5vdXMgYWxsb25zIHLDqWFsaXNlciBkZXMgcHLDqWRpY3Rpb25zIMOgIHBhcnRpciBkZSBub3MgZG9ubsOpZXMgY29ubnVlcyBgZGF0YTBgLiBQb3VyIGNlbGEsIG5vdXMgcHJlbm9ucyAkNzUgXCUkIGRlIG5vdHJlIMOpY2hhbnRpbGxvbiBkZSBmYcOnb24gYWzDqWF0b2lyZSBhZmluIGRlIG5lIHBhcyBhdm9pciBkZSBiaWFpcyDDoCBsYSBjb25zdHJ1Y3Rpb24gZGUgbm9zIG1vZMOobGVzLiBQdWlzLCBub3VzIHByw6lkaXNvbnMgbGVzICQyNSBcJSQgcmVzdGFudHMgcXVlIG5vdXMgY29ubmFpc3NvbnMuIEFpbnNpLCBub3VzIHBvdXJyb25zIGNvbXBhcmVyIGxhIHByw6lkaWN0aW9uIGF1eCB2cmFpZXMgdmFsZXVycy4gUG91ciBjZWxhLCBub3VzIGNhbGN1bG9ucyBsYSBgcm1zZWAsIHNpIGVsbGUgZGltaW51ZSBjJ2VzdCBxdWUgbGVzIGNoYW5nZW1lbnRzIHLDqWFsaXPDqXMgc29udCBib25zIGV0IHZpY2UtdmVyc2EuIFBvdXIgcGx1cyBkZSBwcsOpY2lzaW9uLCBub3VzIHBvdXZvbnMgZmFpcmUgY2VzIHRlc3RzIHN1ciBwbHVzaWV1cnMgw6ljaGFudGlsbG9ucyBgVHJhaW5gIGV0IGBUZXN0YCBhZmluIGRlIHByZW5kcmUgbGUgcGFyYW3DqHRyZSBxdWkgY29ycmVzcG9uZCBsZSBtaWV1eCBhdSBwbHVzIGdyYW5kIG5vbWJyZS4gCgpOb3VzIGltcGzDqW1lbnRvbnMgZG9uYyBsYSBmb25jdGlvbiBxdWkgcGVybWV0IGRlIGNhbGN1bGVyIGxlIHNjb3JlIDoKYGBge3J9CnJtc2UgPC0gZnVuY3Rpb24oYWN0dWFsLCBwcmVkaWN0ZWQpIHsKICBzcXJ0KG1lYW4oKGFjdHVhbCAtIHByZWRpY3RlZCkgXiAyKSkKfQpgYGAKQ2V0dGUgZm9uY3Rpb24gZXN0IGJhc8OpZSBzdXIgbGEgZm9ybXVsZSA6CiQkClJNU0UoWSwgXHdpZGVoYXR7WX0pID0gXHNxcnR7XGRmcmFjezF9e259IFxzdW1cbGltaXRzX3tpID0gMX1ebiBcbGVmdCggeV9pIC0gXHdpZGVoYXR7eX1faSBccmlnaHQpXjJ9CiQkCnF1aSBlc3QgZG9ubsOpZSBkYW5zIGxlIHN1amV0IGR1IHByb2pldC4KClB1aXMsIG5vdXMgY3LDqW9ucyBsZXMgZGV1eCB2YXJpYWJsZXMgYFRyYWluYCBldCBgVGVzdGAgOgpgYGB7cn0Kc21wX3NpemUgPC0gZmxvb3IoMC43NSAqIG5yb3coY29uc29tLmNzdikpCnRyYWluX2luZCA8LSBzYW1wbGUoc2VxX2xlbihucm93KGNvbnNvbS5jc3YpKSwgc2l6ZSA9IHNtcF9zaXplKQoKVHJhaW4gPC0gY29uc29tLmNzdlt0cmFpbl9pbmQsIF0KVGVzdCA8LSBjb25zb20uY3N2Wy10cmFpbl9pbmQsIF0Kcm0oc21wX3NpemUpCmBgYApvw7kgYFRyYWluYCBjb3JyZXNwb25kIGRvbmMgYXUgJDc1IFwlJCBkZSBub3RyZSDDqWNoYW50aWxsb24gc3VyIGxlcXVlbCBub3VzIHLDqWFsaXNlcm9ucyBsZXMgcHLDqXZpc2lvbnMgZGVzICQyNSBcJSQgcmVzdGFudHMgZG9udCBsZXMgdnJhaWVzIHZhbGV1cnMgc29udCBjb250ZW51ZXMgZGFucyBgVGVzdGAuIEFpbnNpLCBpbCBub3VzIHJlc3RlcmEgw6AgY2FsY3VsZXIgbGUgc2NvcmUgZW4gZmFpc2FudCBgcm1zZShUZXN0LCBwcmVkKWAuCgoKIyMgQW5hbHlzZSBkZXNjcmlwdGl2ZSBkZXMgZG9ubsOpZXMgCgojIyMgUmVwcsOpc2VudGF0aW9uIGdyYXBoaXF1ZSAKCk5vdXMgcmVwcsOpc2VudG9ucyBncmFwaGlxdWVtZW50IHRyb2lzIMOpY2hhbnRpbGxvbnMgZGUgY2hhcXVlIHRhaWxsZSA6IFgyLCBYMTIgZXQgWDIyLgpgYGB7ciBlY2hvID0gRkFMU0V9ClgyIDwtIGFzLm1hdHJpeCh0KGNvbnNvbS5jc3ZbJ1gyJ10pKQpYMi54dHMgPC0geHRzKGMoWDIpLCBvcmRlci5ieSA9IGRhdGUpCnBsb3QoWDIueHRzLCBjb2wgPSAnZG9kZ2VyYmx1ZScsIG1haW4gPSBwYXN0ZSgiXFUwMEU5IiwiY2hhbnRpbGxvbiBYMiIsIHNlcCA9ICIiKSkKClgxMiA8LSBhcy5tYXRyaXgodChjb25zb20uY3N2WydYMTInXSkpClgxMi54dHMgPC0geHRzKGMoWDEyKSwgb3JkZXIuYnkgPSBkYXRlKQpwbG90KFgxMi54dHMsIGNvbCA9ICdjb3JhbCcsIG1haW4gPSBwYXN0ZSgiXFUwMEU5IiwiY2hhbnRpbGxvbiBYMTIiLCBzZXAgPSAiIikpCgpYMjIgPC0gYXMubWF0cml4KHQoY29uc29tLmNzdlsnWDIyJ10pKQpYMjIueHRzIDwtIHh0cyhjKFgyMiksIG9yZGVyLmJ5ID0gZGF0ZSkKcGxvdChYMjIueHRzLCBjb2wgPSAnZm9yZXN0Z3JlZW4nLCBtYWluID0gcGFzdGUoIlxVMDBFOSIsImNoYW50aWxsb24gWDIyIiwgc2VwID0gIiIpKQpgYGAKTm91cyBwb3V2b25zIHJlbWFycXVlciBxdWUgWDIgc2VtYmxlIHN0YXRpb25uYWlyZS4gVGFuZGlzIHF1ZSBYMTIgZXQgWDIyIG5lIHNlbWJsZW50IHBhcyBzdGF0aW9ubmFpcmVzLiBQb3VyIGxlcyB0cm9pcyDDqWNoYW50aWxsb25zLCBsZXVyIHZhcmlhbmNlIHZhcmllIGRhbnMgbGUgdGVtcHMuCgpDZXMgdHJvaXMgZ3JhcGhpcXVlcyByZXN0ZW50IHBldSBsaXNpYmxlcywgbm91cyBhbGxvbnMgZG9uYyByZXByw6lzZW50ZXIgZ3JhcGhpcXVlbWVudCBsYSBtb3llbm5lIHBhciBqb3VyIDoKYGBge3J9ClgyLmpvdXIgPC0gdGFwcGx5KGNvbnNvbS5jc3YkWDIsIGFzLmZhY3Rvcihqb3VyKSwgbWVhbikKWDEyLmpvdXIgPC0gdGFwcGx5KGNvbnNvbS5jc3YkWDEyLCBhcy5mYWN0b3Ioam91ciksIG1lYW4pClgyMi5qb3VyIDwtIHRhcHBseShjb25zb20uY3N2JFgyMiwgYXMuZmFjdG9yKGpvdXIpLCBtZWFuKQp0ZW1wLmpvdXIgPC0gdGFwcGx5KGNvbnNvbS5jc3YkVGVtcGVyYXR1cmUsIGFzLmZhY3Rvcihqb3VyKSwgbWVhbikKYGBgCmBgYHtyIGVjaG8gPSBGQUxTRX0KcGxvdChYMi5qb3VyLCB0eXBlID0gJ2wnLCBheGVzID0gRiwgeGxhYiA9ICdqb3VyJywgeWxhYiA9ICdtb3llbm5lJywgY29sID0gJ2RvZGdlcmJsdWUnKQp0aXRsZSgnTW95ZW5uZSBwYXIgam91ciBkZSBYMicpCmF4aXMoMSwgYygxOjM2NSkpCmF4aXMoMikKZ3JpZChueCA9IE5VTEwsIG55ID0gTlVMTCwgY29sID0gImdyYXkiLCBsdHkgPSAiZG90dGVkIiwgbHdkID0gcGFyKCJsd2QiKSwgZXF1aWxvZ3MgPSBUUlVFKQoKcGxvdChYMTIuam91ciwgdHlwZSA9ICdsJywgYXhlcyA9IEYsIHhsYWIgPSAnam91cicsIHlsYWIgPSAnbW95ZW5uZScsIGNvbCA9ICdjb3JhbCcpCnRpdGxlKCdNb3llbm5lIHBhciBqb3VyIGRlIFgxMicpCmF4aXMoMSwgYygxOjM2NSkpCmF4aXMoMikKZ3JpZChueCA9IE5VTEwsIG55ID0gTlVMTCwgY29sID0gImdyYXkiLCBsdHkgPSAiZG90dGVkIiwgbHdkID0gcGFyKCJsd2QiKSwgZXF1aWxvZ3MgPSBUUlVFKQoKcGxvdChYMjIuam91ciwgdHlwZSA9ICdsJywgYXhlcyA9IEYsIHhsYWIgPSAnam91cicsIHlsYWIgPSAnbW95ZW5uZScsIGNvbCA9ICdmb3Jlc3RncmVlbicpCnRpdGxlKCdNb3llbm5lIHBhciBqb3VyIGRlIFgyMicpCmF4aXMoMSwgYygxOjM2NSkpCmF4aXMoMikKZ3JpZChueCA9IE5VTEwsIG55ID0gTlVMTCwgY29sID0gImdyYXkiLCBsdHkgPSAiZG90dGVkIiwgbHdkID0gcGFyKCJsd2QiKSwgZXF1aWxvZ3MgPSBUUlVFKQpgYGAKQ2VzIGdyYXBoaXF1ZXMgc29udCBkw6lqw6AgbW9pbnMgc2F0dXLDqXMgbWFpcyByZXN0ZW50IGRpZmZpY2lsZW1lbnQgbGlzaWJsZXMuIExhIHZhcmlhbmNlIHNlbWJsZSB2YXJpZXIgZm9ydGVtZW50IHBvdXIgWDIsIHVuIHBldSBtb2lucyBwb3VyIFgxMiBldCBlbmNvcmUgbW9pbnMgcG91ciBYMjIuIENldHRlIGZvaXMtY2ksIGlscyBzZW1ibGVudCB0b3VzIHF1YXNpbWVudCBzdGF0aW9ubmFpcmVzLgoKTWFpbnRlbmFudCwgbm91cyBhbGxvbnMgcmVwcsOpc2VudGVyIGdyYXBoaXF1ZW1lbnQgbGEgbW95ZW5uZSBwYXIgbW9pcyA6CmBgYHtyfQpYMi5tb2lzIDwtIHRhcHBseShjb25zb20uY3N2JFgyLCBhcy5mYWN0b3IobW9pcyksIG1lYW4pClgxMi5tb2lzIDwtIHRhcHBseShjb25zb20uY3N2JFgxMiwgYXMuZmFjdG9yKG1vaXMpLCBtZWFuKQpYMjIubW9pcyA8LSB0YXBwbHkoY29uc29tLmNzdiRYMjIsIGFzLmZhY3Rvcihtb2lzKSwgbWVhbikKdGVtcC5tZWFuLm1vaXMgPC0gdGFwcGx5KGNvbnNvbS5jc3YkVGVtcGVyYXR1cmUsIGFzLmZhY3Rvcihtb2lzKSwgbWVhbikKYGBgCmBgYHtyIGVjaG8gPSBGQUxTRX0KcGxvdChYMi5tb2lzLCB0eXBlID0gJ2wnLCBheGVzID0gRiwgeGxhYiA9ICdtb2lzJywgeWxhYiA9ICdtb3llbm5lJywgY29sID0gJ2RvZGdlcmJsdWUnLCBsd2QgPSAzKQp0aXRsZSgnTW95ZW5uZSBwYXIgbW9pcyBkZSBYMicpCmF4aXMoMSwgYygxOjEyKSkKYXhpcygyKQpncmlkKG54ID0gTlVMTCAsIG55ID0gTlVMTCwgY29sID0gImdyYXkiLCBsdHkgPSAiZG90dGVkIixsd2QgPSBwYXIoImx3ZCIpLCBlcXVpbG9ncyA9IFRSVUUpCnBsb3QoWDEyLm1vaXMsIHR5cGUgPSAnbCcsIGF4ZXMgPSBGLCB4bGFiID0gJ21vaXMnLCB5bGFiID0gJ21veWVubmUnLCBjb2wgPSAnY29yYWwnLCBsd2QgPSAzKQp0aXRsZSgnTW95ZW5uZSBwYXIgbW9pcyBkZSBYMTInKQpheGlzKDEsIGMoMToxMikpCmF4aXMoMikKZ3JpZChueCA9IE5VTEwgLCBueSA9IE5VTEwsIGNvbCA9ICJncmF5IiwgbHR5ID0gImRvdHRlZCIsbHdkID0gcGFyKCJsd2QiKSwgZXF1aWxvZ3MgPSBUUlVFKQpwbG90KFgyMi5tb2lzLCB0eXBlID0gJ2wnLCBheGVzID0gRiwgeGxhYiA9ICdtb2lzJywgeWxhYiA9ICdtb3llbm5lJywgY29sID0gJ2ZvcmVzdGdyZWVuJywgbHdkID0gMykKdGl0bGUoJ01veWVubmUgcGFyIG1vaXMgZGUgWDIyJykKYXhpcygxLCBjKDE6MTIpKQpheGlzKDIpCmdyaWQobnggPSBOVUxMICwgbnkgPSBOVUxMLCBjb2wgPSAiZ3JheSIsIGx0eSA9ICJkb3R0ZWQiLGx3ZCA9IHBhcigibHdkIiksIGVxdWlsb2dzID0gVFJVRSkKcGxvdCh0ZW1wLm1lYW4ubW9pcywgdHlwZSA9ICdsJywgYXhlcyA9IEYsIHhsYWIgPSAnbW9pcycsIHlsYWIgPSAnbW95ZW5uZScsIGx3ZCA9IDMpCnRpdGxlKCdNb3llbm5lIHBhciBtb2lzIGRlIGxhIHRlbXBcVTAwRTlyYXR1cmUnKQpheGlzKDEsIGMoMToxMikpCmF4aXMoMikKZ3JpZChueCA9IE5VTEwgLCBueSA9IE5VTEwsIGNvbCA9ICJncmF5IiwgbHR5ID0gImRvdHRlZCIsbHdkID0gcGFyKCJsd2QiKSwgZXF1aWxvZ3MgPSBUUlVFKQpgYGAKUG91ciBsZXMgdHJvaXMgZ3JhcGhpcXVlcywgbm91cyByZW1hcnF1b25zIHF1ZSBsZSBtaW5pbXVtIGVzdCBhdHRlaW50IGxlICQ4XntcdGV4dHtpw6htZX19JCBtb2lzIGRlIGwnYW5uw6llIGRvbmMgZW4gYW/Du3QsIGNlIHF1aSBwYXJhw650IGNvaMOpcmVudC4gRW4gb2JzZXJ2YW50IGVuIHBsdXMgbGEgY291cmJlIGRlIGxhIG1veWVubmUgcGFyIG1vaXMgZGUgbGEgdGVtcMOpcmF0dXJlLCBub3VzIHJlbWFycXVvbnMgcXVlIHNvbiBtYXhpbXVtIGVzdCBhdHRlaW50IGVuIGp1aWxsZXQsIGlsIHkgYXVyYWl0IGRvbmMgdW5lIGluZmx1ZW5jZSBkZSBsYSB0ZW1ww6lyYXR1cmUgZHUgbW9pcyBwcsOpY8OpZGVudCBzdXIgbGUgbW9pcyBzdWl2YW50LiBOb3VzIHBvdXZvbnMgYWxvcnMgc3VwcG9zZXIgcXVlIGxhIHRlbXDDqXJhdHVyZSBkZXMgJDMwJCBkZXJuacOocmVzIG1pbnV0ZXMgdmEgaW5mbHVlciBzdXIgbm90cmUgY29uc29tbWF0aW9uIMOpbGVjdHJpcXVlLiBDJ2VzdCBwb3VyIGNldHRlIHJhaXNvbiBxdWUgbm91cyBhdm9ucyBpbnRyb2R1aXQgbGEgdmFyaWFibGUgYFRlbXBlcmF0dXJlLmxhZ2AgcXVpIG5vdXMgZG9ubmUgbGEgdGVtcMOpcmF0dXJlICQzMCQgbWludXRlcyBhdmFudCBsZSByZWxldsOpIGRlIGxhIGNvbnNvbW1hdGlvbiDDqWxlY3RyaXF1ZS4KCiMjIyBTdGF0aXN0aXF1ZXMgZGUgYmFzZQoKTm91cyByZXByw6lzZW50b25zIGwnaGlzdG9ncmFtbWUgZGUgbm9zIHRyb2lzIMOpY2hhbnRpbGxvbnMgZGUgcsOpZsOpcmVuY2UgOgpgYGB7ciBlY2hvID0gRkFMU0V9Cmhpc3QoY29uc29tLmNzdiRYMiwgY29sID0gJ2RvZGdlcmJsdWUnLCBtYWluID0gJ0hpc3RvZ3JhbW1lIGRlIFgyJywgYnJlYWtzID0gMjAsIHhsYWIgPSAnY29uc29tbWF0aW9uIGVsZWN0cmlxdWUnKQphYmxpbmUodiA9IG1lYW4oY29uc29tLmNzdiRYMiksIGNvbCA9ICdibHVlJywgbHdkID0gNSkKbGVnZW5kKCd0b3ByaWdodCcsICdtb3llbm5lJywgY29sID0gJ2JsdWUnLCBsdHkgPSAxLCBsd2QgPSA1KQpoaXN0KGNvbnNvbS5jc3YkWDEyLCBjb2wgPSAnY29yYWwnLCBtYWluID0gJ0hpc3RvZ3JhbW1lIGRlIFgxMicsIGJyZWFrcyA9IDIwLCB4bGFiID0gJ2NvbnNvbW1hdGlvbiBlbGVjdHJpcXVlJykKYWJsaW5lKHYgPSBtZWFuKGNvbnNvbS5jc3YkWDEyKSwgY29sID0gJ3JlZCcsIGx3ZCA9IDUpCmxlZ2VuZCgndG9wcmlnaHQnLCAnbW95ZW5uZScsIGNvbCA9ICdyZWQnLCBsdHkgPSAxLCBsd2QgPSA1KQpoaXN0KGNvbnNvbS5jc3YkWDIyLCBjb2wgPSAnZm9yZXN0Z3JlZW4nLCBtYWluID0gJ0hpc3RvZ3JhbW1lIGRlIFgyMicsIGJyZWFrcyA9IDIwLCB4bGFiID0gJ2NvbnNvbW1hdGlvbiBlbGVjdHJpcXVlJykKYWJsaW5lKHYgPSBtZWFuKGNvbnNvbS5jc3YkWDIyKSwgY29sID0gJ2dyZWVuJywgbHdkID0gNSkKbGVnZW5kKCd0b3ByaWdodCcsICdtb3llbm5lJywgY29sID0gJ2dyZWVuJywgbHR5ID0gMSwgbHdkID0gNSkKYGBgCk5vdXMgcmVtYXJxdW9ucyBxdSdpbCBlc3QgcmFyZSBxdWUgbGEgY29uc29tbWF0aW9uIMOpbGVjdHJpcXVlIHNvaXQgc3Vww6lyaWV1cmUgw6AgJDAuNCQuIEVuIGVmZmV0LCBsYSBtYWpvcml0w6kgZGVzIGRvbm7DqWVzIHNlIHLDqXBhcnRpc3NlbnQgZGFucyBsJ2ludGVydmFsbGUgJFswLjEsIDAuNF0kIHBvdXIgWDIgZXQgJFswLjEsIDAuM10kIHBvdXIgWDEyIGV0IFgyMi4KCk5vdXMgYXBwZWxvbnMgbGEgZm9uY3Rpb24gYHN1bW1hcnlgIHN1ciBub3RyZSBkYXRhLWZyYW1lIGBjb25zb20uY3N2YC4gCgpQb3VyIGxlcyDDqWNoYW50aWxsb25zIGRlIHRhaWxsZSAkMTAkLCBub3VzIG9idGVub25zIDoKYGBge3IgZWNobyA9IEZBTFNFfQpzdW1tYXJ5KGNvbnNvbS5jc3ZbICwgNjoxNV0pCmBgYApOb3VzIHJlbWFycXVvbnMgcXVlIGxhIG1veWVubmUgdmFyaWUgYmVhdWNvdXAgZCd1biDDqWNoYW50aWxsb24gw6AgbCdhdXRyZSwgZW4gZWZmZXQgZWxsZSB2YXJpZSBkZSAkMC4xMTc5JCwgY2UgcXVpIGNvbmZpcm1lIG5vdHJlIHJlbWFycXVlIHN1ciBsYSB2YXJpYW5jZSBxdWkgY2hhbmdlIGF1IGNvdXJzIGR1IHRlbXBzLgoKUG91ciBsZXMgw6ljaGFudGlsbG9ucyBkZSB0YWlsbGUgJDEwMCQsIG5vdXMgb2J0ZW5vbnMgOgpgYGB7ciBlY2hvID0gRkFMU0V9CnN1bW1hcnkoY29uc29tLmNzdlsgLCAxNjoyNV0pCmBgYApDZXR0ZSBmb2lzLWNpLCBsYSBtb3llbm5lIHZhcmllIGRlICQwLjA1NTI3JCwgY2UgcXVpIHLDqWR1aXQgZGUgbW9pdGnDqSBsYSB2YXJpYWJpbGl0w6kgb2JzZXJ2w6llIHBvdXIgbGVzIMOpY2hhbnRpbGxvbnMgZGUgdGFpbGxlICQxMCQuCgpQb3VyIGxlcyDDqWNoYW50aWxsb25zIGRlIHRhaWxsZSAkMTAwMCQsIG5vdXMgb2J0ZW5vbnMgOgpgYGB7ciBlY2hvID0gRkFMU0V9CnN1bW1hcnkoY29uc29tLmNzdlsgLCAyNjozNV0pCmBgYApFbmZpbiwgY2V0dGUgZm9pcy1jaSBsYSBtb3llbm5lIHZhcmllIGRlICQwLjAwOTczJC4gTGEgdmFyaWFiaWxpdMOpIGVzdCBkb25jIG1pbmltZS4KClBvdXIgbGEgdGVtcMOpcmF0dXJlLCBub3VzIG9idGVub25zIDoKYGBge3IgZWNobyA9IEZBTFNFfQpzdW1tYXJ5KGNvbnNvbS5jc3YkVGVtcGVyYXR1cmUpCmBgYAoKIyMjIENvcnLDqWxhdGlvbnMKCk5vdXMgYWxsb25zIG1haW50ZW5hbnQgw6l0dWRpZXIgbGEgY29ycsOpbGF0aW9uIGVudHJlIG5vcyDDqWNoYW50aWxsb25zIGRlIHLDqWbDqXJlbmNlIGV0IGxlcyBkaWZmw6lyZW50ZXMgdmFyaWFibGVzIHF1ZSBub3VzIGF2b25zIGNyw6nDqWVzIHBvdXIgcsOpYWxpc2VyIG5vcyBwcsOpZGljdGlvbnMuCmBgYHtyfQpYMiA8LSBkYXRhMFssIDNdCnN1bW1hcnkobG0oWDIgfiBtb2lzICsgaGV1cmUgKyB3ZWVrZW5kICsgVGVtcGVyYXR1cmUgKyBJKFRlbXBlcmF0dXJlXjIpICsgdGVtcC5oZXVyZSArIHRlbXAud2Vla2VuZCArIHRlbXAubW9pcyArIG1vaXMuaGV1cmUgKyB3ZWVrZW5kLmhldXJlICsgVGVtcGVyYXR1cmUubGFnLCBkYXRhID0gY29uc29tLmNzdikpCmBgYApMZXMgdmFyaWFibGVzIHNpZ25pZmljYXRpdmVzIGNvcnJlc3BvbmRlbnQgw6AgY2VsbGVzIHF1aSBvbnQgbGEgdmFsZXVyIGBQcig+fHR8KWAgaW5mw6lyaWV1cmUgw6AgJFxhbHBoYSA9IDAuMDUkLiBEb25jLCBkYW5zIGNlIG1vZMOobGUsIGxlcyB2YXJpYWJsZXMgYFRlbXBlcmF0dXJlYCwgYHRlbXAubW9pc2AsIGB0ZW1wLmhldXJlYCwgYGhldXJlYCBldCBgbW9pc2Agc29udCBzaWduaWZpY2F0aXZlcy4gQ2VwZW5kYW50LCBub3VzIGFqb3V0b25zIHRvdXQgZGUgbcOqbWUgbGVzIGF1dHJlcyB2YXJpYWJsZXMgY2FyIMOnYSBuZSBwZXV0IHBhcyBmYXVzc2VyIG5vcyBwcsOpZGljdGlvbnMsIGRhbnMgbGUgcGlyZSBkZXMgY2FzIGVsbGVzIG5lIGNoYW5nZW50IHJpZW4gZXQgZWxsZXMgcGV1dmVudCBtw6ptZSwgw6l2ZW50dWVsbGVtZW50LCBhbcOpbGlvcmVyIG5vdHJlIHByw6lkaWN0aW9uLgoKRW4gdGVzdGFudCBub3MgcHLDqWRpY3Rpb25zIHN1ciBgVHJhaW5gIGV0IGBUZXN0YCBhdmVjIGRpZmbDqXJlbnRlcyB2YXJpYWJsZXMsIG5vdXMgYXZvbnMgcmVtYXJxdcOpIHF1ZSBub3Mgc2NvcmVzIMOpdGFpZW50IG1laWxsZXVycyBlbiBwcmVuYW50IGxlcyB2YXJpYWJsZXMgYG1vaXNgIGV0IGBoZXVyZWAgYXUgZm9ybWF0IGBjaHJgIGV0IG5vbiBhdSBmb3JtYXQgbnVtw6lyaXF1ZS4gUG91ciB0b3V0ZXMgbGVzIHZhcmlhYmxlcyBjcm9pc8OpZXMsIG5vdXMgYXZvbnMgZmFpdCBsZXMgY2FsY3VscyBhdSBmb3JtYXQgbnVtw6lyaXF1ZS4KCgojIyBNw6l0aG9kZXMgZGUgcHLDqWRpY3Rpb24KClBvdXIgY2hhcXVlIHByw6lkaWN0aW9uLCBub3VzIGltcGzDqW1lbnRvbnMgZ3JhcGhpcXVlbWVudCBsZXMgJDI1MjAkIGRlcm5pw6hyZXMgbGlnbmVzIGRlIGwnw6ljaGFudGlsbG9uIHB1aXMsIMOgIGxhIHN1aXRlLCBsYSBwcsOpZGljdGlvbiByw6lhbGlzw6llLiBOb3VzIGFsbG9ucyByZXByw6lzZW50ZXIgY2VzIGNvdXJiZXMgcG91ciBub3MgdHJvaXMgw6ljaGFudGlsbG9ucyBkZSByw6lmw6lyZW5jZSA6IFgyLCBYMTIgZXQgWDIyLiBEZSBwbHVzLCBub3VzIGF2b25zIGNob2lzaSBkZSBuZSBwYXMgbW9udHJlciBsJ8OpY2hhbnRpbGxvbiBlbiBlbnRpZXIgYWZpbiBkZSBnYWduZXIgZW4gbGlzaWJpbGl0w6kgZXQgZGUgbm91cyBwZXJtZXR0cmUgZCdhbmFseXNlciBncmFwaGlxdWVtZW50IGxhIHByw6lkaWN0aW9uLgoKUG91ciBjaGFxdWUgbcOpdGhvZGUsIG5vdXMgYXZvbnMgY3LDqcOpIHVuZSBmb25jdGlvbiBxdWkgZMOpcGVuZCBkJ3VuIHBhcmFtw6h0cmUgJGskIGV0IHF1aSByw6lhbGlzZSBsYSBwcsOpZGljdGlvbiBkZSBsJ8OpY2hhbnRpbGxvbiBgWGtgLiBQb3VyIGNvbnN0cnVpcmUgbGEgcHLDqWRpY3Rpb24gZmluYWxlLCBub3VzIGF2b25zIHRvdXQgc2ltcGzDqW1lbnQgZmFpdCB1bmUgYm91Y2xlIHN1ciAkayQgYWxsYW50IGRlICQxJCDDoCAkMzAkIGV0IG5vdXMgYXZvbnMgY29uY2F0w6luZXIgdG91cyBsZXMgdmVjdGV1cnMgZW4gdW4uCgojIyMgUsOpZ3Jlc3Npb24gbGluw6lhaXJlCgpOb3VzIGF2b25zIGQnYWJvcmQgcsOpYWxpc8OpIGRlcyBwcsOpZGljdGlvbnMgcGFyIHLDqWdyZXNzaW9uIGxpbsOpYWlyZSDDoCBsJ2FpZGUgZGUgbGEgZm9uY3Rpb24gYGxtYC4gUG91ciBjZSBtb2TDqGxlLCBjZSBxdWkgZXN0IGltcG9ydGFudCBlc3QgZGUgYmllbiBjaG9pc2lyIGxlcyB2YXJpYWJsZXMgcGFyIHJhcHBvcnQgYXV4cXVlbGxlcyBub3VzIHLDqWFsaXNvbnMgbGEgcsOpZ3Jlc3Npb24uCgpEYW5zIHVuIHByZW1pZXIgdGVtcHMsIG5vdXMgYXZvbnMgcsOpYWxpc8OpIHVuZSByw6lncmVzc2lvbiBhdmVjIGxlcyB2YXJpYWJsZXMgZGUgYmFzZSBgbW9pc2AsIGBoZXVyZWAgZXQgYFRlbXBlcmF0dXJlYC4KYGBge3J9CnJlZ2xpbjEgPC0gZnVuY3Rpb24oaykgewogICMgbW9kw6hsZQogIFhrIDwtIGRhdGEwWywgayArIDFdCiAgbW9kZWwgPC0gbG0oWGsgfiBtb2lzICsgaGV1cmUgKyBUZW1wZXJhdHVyZSwgZGF0YSA9IGNvbnNvbS5jc3YpCiAgCiAgIyBwcsOpZGljdGlvbgogIGRhdGEucHJlZCA8LSBkYXRhMVtjKCgyNzg0KihrIC0gMSkgKyAxKTooMjc4NCprKSksIF0KICB0ZW1wLlhrIDwtIG15Lm5hLmFwcHJveChkYXRhLnByZWQkVGVtcGVyYXR1cmUpCiAgWGsucHJlZCA8LSBkYXRhLmZyYW1lKG1vaXMucHJlZCwgaGV1cmUucHJlZCwgdGVtcC5YaykKICBuYW1lcyhYay5wcmVkKSA8LSBjKCJtb2lzIiwgImhldXJlIiwgIlRlbXBlcmF0dXJlIikKICBwcmVkIDwtIHVubmFtZShwcmVkaWN0KG1vZGVsLCBuZXdkYXRhID0gWGsucHJlZCkpCn0KYGBgCgpEYW5zIHVuIHNlY29uZCB0ZW1wcywgbm91cyBhdm9ucyBham91dMOpIGxhIHZhcmlhYmxlIGB3ZWVrZW5kYCBhaW5zaSBxdWUgbGVzIHZhcmlhYmxlcyBjcm9pc8OpZXMgw6AgbGEgcsOpZ3Jlc3Npb24uIApgYGB7cn0KcmVnbGluMiA8LSBmdW5jdGlvbihrKSB7CiAgIyBtb2TDqGxlCiAgWGsgPC0gZGF0YTBbLCBrICsgMV0KICBtb2RlbCA8LSBsbShYayB+IG1vaXMgKyBoZXVyZSArIHdlZWtlbmQgKyBUZW1wZXJhdHVyZSArIHRlbXAuaGV1cmUgKyB0ZW1wLndlZWtlbmQgKyB0ZW1wLm1vaXMgKyBtb2lzLmhldXJlICsgd2Vla2VuZC5oZXVyZSwgZGF0YSA9IGNvbnNvbS5jc3YpCiAgCiAgIyBwcsOpZGljdGlvbgogIGRhdGEucHJlZCA8LSBkYXRhMVtjKCgyNzg0KihrIC0gMSkgKyAxKTooMjc4NCprKSksIF0KICB0ZW1wLlhrIDwtIG15Lm5hLmFwcHJveChkYXRhLnByZWQkVGVtcGVyYXR1cmUpCiAgdGVtcC5oZXVyZS5wcmVkIDwtIHRlbXAuWGsqaGV1cmUubnVtLnByZWQKICB0ZW1wLm1vaXMucHJlZCA8LSB0ZW1wLlhrKmFzLm51bWVyaWMobW9pcy5wcmVkKQogIHRlbXAud2Vla2VuZC5wcmVkIDwtIHRlbXAuWGsqd2Vla2VuZC5wcmVkCiAgCiAgWGsucHJlZCA8LSBkYXRhLmZyYW1lKG1vaXMucHJlZCwgaGV1cmUucHJlZCwgd2Vla2VuZC5wcmVkLCB0ZW1wLlhrLCB0ZW1wLmhldXJlLnByZWQsIHRlbXAubW9pcy5wcmVkLCB0ZW1wLndlZWtlbmQucHJlZCwgbW9pcy5oZXVyZS5wcmVkLCB3ZWVrZW5kLmhldXJlLnByZWQpCiAgbmFtZXMoWGsucHJlZCkgPC0gYygibW9pcyIsICJoZXVyZSIsICJ3ZWVrZW5kIiwgIlRlbXBlcmF0dXJlIiwgInRlbXAuaGV1cmUiLCAidGVtcC5tb2lzIiwgInRlbXAud2Vla2VuZCIsICJtb2lzLmhldXJlIiwgIndlZWtlbmQuaGV1cmUiKQogIHByZWQgPC0gdW5uYW1lKHByZWRpY3QobW9kZWwsIG5ld2RhdGEgPSBYay5wcmVkKSkKfQpgYGAKCkZpbmFsZW1lbnQsIG5vdXMgYXZvbnMgYWpvdXTDqSBsYSB2YXJpYWJsZSBgVGVtcGVyYXR1cmUubGFnYCBhaW5zaSBxdWUgbGEgYFRlbXBlcmF0dXJlYCBhdSBjYXJyw6kuCmBgYHtyfQpyZWdsaW4zIDwtIGZ1bmN0aW9uKGspIHsKICAjIG1vZMOobGUKICBYayA8LSBkYXRhMFssIGsgKyAxXQogIG1vZGVsIDwtIGxtKFhrIH4gbW9pcyArIGhldXJlICsgd2Vla2VuZCArIFRlbXBlcmF0dXJlICsgSShUZW1wZXJhdHVyZV4yKSArIFRlbXBlcmF0dXJlLmxhZyArIHRlbXAuaGV1cmUgKyB0ZW1wLndlZWtlbmQgKyB0ZW1wLm1vaXMgKyBtb2lzLmhldXJlICsgd2Vla2VuZC5oZXVyZSwgZGF0YSA9IGNvbnNvbS5jc3YpCiAgCiAgIyBwcsOpZGljdGlvbgogIGRhdGEucHJlZCA8LSBkYXRhMVtjKCgyNzg0KihrIC0gMSkgKyAxKTooMjc4NCprKSksIF0KICB0ZW1wLlhrIDwtIG15Lm5hLmFwcHJveChkYXRhLnByZWQkVGVtcGVyYXR1cmUpCiAgdGVtcC5Yay5sYWcgPC0gc2FwcGx5KDE6bGVuZ3RoKHRlbXAuWGspLCBmdW5jdGlvbih4KSB0ZW1wLlhrW3ggLSAxXSkKICB0ZW1wLlhrLmxhZ1sxXSA8LSB0ZW1wLlhrLmxhZ1syXQogIAogIHRlbXAuaGV1cmUucHJlZCA8LSB0ZW1wLlhrKmhldXJlLm51bS5wcmVkCiAgdGVtcC5tb2lzLnByZWQgPC0gdGVtcC5Yayphcy5udW1lcmljKG1vaXMucHJlZCkKICB0ZW1wLndlZWtlbmQucHJlZCA8LSB0ZW1wLlhrKndlZWtlbmQucHJlZAogIAogIFhrLnByZWQgPC0gZGF0YS5mcmFtZShtb2lzLnByZWQsIGhldXJlLnByZWQsIHdlZWtlbmQucHJlZCwgdGVtcC5YaywgdGVtcC5Ya14yLCB0ZW1wLlhrLmxhZywgdGVtcC5oZXVyZS5wcmVkLCB0ZW1wLm1vaXMucHJlZCwgdGVtcC53ZWVrZW5kLnByZWQsIG1vaXMuaGV1cmUucHJlZCwgd2Vla2VuZC5oZXVyZS5wcmVkKQogIG5hbWVzKFhrLnByZWQpIDwtIGMoIm1vaXMiLCAiaGV1cmUiLCAid2Vla2VuZCIsICJUZW1wZXJhdHVyZSIsICJJKFRlbXBlcmF0dXJlXjIpIiwgIlRlbXBlcmF0dXJlLmxhZyIsICJ0ZW1wLmhldXJlIiwgInRlbXAubW9pcyIsICJ0ZW1wLndlZWtlbmQiLCAibW9pcy5oZXVyZSIsICJ3ZWVrZW5kLmhldXJlIikKICBwcmVkIDwtIHVubmFtZShwcmVkaWN0KG1vZGVsLCBuZXdkYXRhID0gWGsucHJlZCkpCn0KYGBgCgpDb21wYXJvbnMgY2VzIHRyb2lzIHLDqWdyZXNzaW9ucyBsaW7DqWFpcmVzIHBvdXIgWDIsIFgxMiBldCBYMjIuCmBgYHtyIGluY2x1ZGUgPSBGQUxTRX0KdGVtcHMgPC0gYygxNTAwMDoxNzUyMCkKYWJzeCA8LSBjKDE3NTIxOigxNzUyMCArIDI3ODQpKQpsaW14IDwtIGMoMTUwMDAsIDE3NTIwICsgMjc4NCkKbGlteSA8LSBjKDAuMDUsIG1heChjb25zb20uY3N2JFgyW2MoMTUwMDA6MTc1MjApXSkpCmBgYAoKTGVzIGdyYXBoaXF1ZXMgZGUgbCfDqWNoYW50aWxsb24gWDIgc29udCA6CmBgYHtyIGVjaG8gPSBGQUxTRX0KY29uc29tbWF0aW9uIDwtIGNvbnNvbS5jc3YkWDJbYygxNTAwMDoxNzUyMCldCgpwcmVkWDIgPC0gcmVnbGluMSgyKQpwbG90KHRlbXBzLCBjb25zb21tYXRpb24sIHR5cGUgPSAnbCcsIHhsaW0gPSBsaW14LCB5bGltID0gbGlteSwgbWFpbiA9IHBhc3RlKCJwcmVtaVxVMDBFOHJlIHByXFUwMEU5IiwiZGljdGlvbiBwYXIgbG0gcG91ciBYMiIsIHNlcCA9ICIiKSwgY29sID0gJ2RvZGdlcmJsdWUnKQpsaW5lcyhhYnN4LCBwcmVkWDIsIGNvbCA9ICdibHVlJywgdHlwZSA9ICdsJykKCnByZWRYMiA8LSByZWdsaW4yKDIpCnBsb3QodGVtcHMsIGNvbnNvbW1hdGlvbiwgdHlwZSA9ICdsJywgeGxpbSA9IGxpbXgsIHlsaW0gPSBsaW15LCBtYWluID0gcGFzdGUoImRldXhpXFUwMEU4bWUgcHJcVTAwRTkiLCJkaWN0aW9uIHBhciBsbSBwb3VyIFgyIiwgc2VwID0gIiIpLCBjb2wgPSAnZG9kZ2VyYmx1ZScpCmxpbmVzKGFic3gsIHByZWRYMiwgY29sID0gJ2JsdWUnLCB0eXBlID0gJ2wnKQoKcHJlZFgyIDwtIHJlZ2xpbjMoMikKcGxvdCh0ZW1wcywgY29uc29tbWF0aW9uLCB0eXBlID0gJ2wnLCB4bGltID0gbGlteCwgeWxpbSA9IGxpbXksIG1haW4gPSBwYXN0ZSgidHJvaXNpXFUwMEU4bWUgcHJcVTAwRTkiLCJkaWN0aW9uIHBhciBsbSBwb3VyIFgyIiwgc2VwID0gIiIpLCBjb2wgPSAnZG9kZ2VyYmx1ZScpCmxpbmVzKGFic3gsIHByZWRYMiwgY29sID0gJ2JsdWUnLCB0eXBlID0gJ2wnKQpgYGAKTm91cyByZW1hcnF1b25zIHF1ZSBwb3VyIGxlcyB0cm9pcyBwcsOpZGljdGlvbnMsIGxhIHZhcmlhdGlvbiBkZSBsYSBwYXJ0aWUgZGUgZHJvaXRlIGVzdCB0cm9wIHBldGl0ZSBwYXIgcmFwcG9ydCBhIGxhIHBhcnRpZSBkZSBnYXVjaGUuIEVuIGVmZmV0LCBsJ8OpY2hhbnRpbGxvbiBkZSBiYXNlIHZhcmllIGVudHJlICQwJCBldCAkMC43NSQsIHRhbmRpcyBxdWUgbGEgcHLDqWRpY3Rpb24gdmFyaWUgZW50cmUgJDAuMSQgZXQgJDAuNSQuIENlcGVuZGFudCwgbGEgdHJvaXNpw6htZSBwcsOpZGljdGlvbiBzZW1ibGUgcGx1cyBsYXJnZSBxdWUgbGVzIGRldXggYXV0cmVzLiBQb3VyIGxlcyBncm91cGVzIGRlIHRhaWxsZSAkMTAkLCBsZSB0cm9pc2nDqG1lIG1vZMOobGUgZGUgcsOpZ3Jlc3Npb24gbGluw6lhaXJlIHNlbWJsZSBtaWV1eCBjb3JyZXNwb25kcmUuCgpMZXMgZ3JhcGhpcXVlcyBkZSBsJ8OpY2hhbnRpbGxvbiBYMTIgc29udCA6CmBgYHtyIGVjaG8gPSBGQUxTRX0KY29uc29tbWF0aW9uIDwtIGNvbnNvbS5jc3YkWDEyW2MoMTUwMDA6MTc1MjApXQoKcHJlZFgxMiA8LSByZWdsaW4xKDEyKQpwbG90KHRlbXBzLCBjb25zb21tYXRpb24sIHR5cGUgPSAnbCcsIHhsaW0gPSBsaW14LCB5bGltID0gbGlteSwgbWFpbiA9IHBhc3RlKCJwcmVtaVxVMDBFOHJlIHByXFUwMEU5IiwiZGljdGlvbiBwYXIgbG0gcG91ciBYMTIiLCBzZXAgPSAiIiksIGNvbCA9ICdjb3JhbCcpCmxpbmVzKGFic3gsIHByZWRYMTIsIGNvbCA9ICdyZWQnLCB0eXBlID0gJ2wnKQoKcHJlZFgxMiA8LSByZWdsaW4yKDEyKQpwbG90KHRlbXBzLCBjb25zb21tYXRpb24sIHR5cGUgPSAnbCcsIHhsaW0gPSBsaW14LCB5bGltID0gbGlteSwgbWFpbiA9IHBhc3RlKCJkZXV4aVxVMDBFOG1lIHByXFUwMEU5IiwiZGljdGlvbiBwYXIgbG0gcG91ciBYMTIiLCBzZXAgPSAiIiksIGNvbCA9ICdjb3JhbCcpCmxpbmVzKGFic3gsIHByZWRYMTIsIGNvbCA9ICdyZWQnLCB0eXBlID0gJ2wnKQoKcHJlZFgxMiA8LSByZWdsaW4zKDEyKQpwbG90KHRlbXBzLCBjb25zb21tYXRpb24sIHR5cGUgPSAnbCcsIHhsaW0gPSBsaW14LCB5bGltID0gbGlteSwgbWFpbiA9IHBhc3RlKCJ0cm9pc2lcVTAwRThtZSBwclxVMDBFOSIsImRpY3Rpb24gcGFyIGxtIHBvdXIgWDEyIiwgc2VwID0gIiIpLCBjb2wgPSAnY29yYWwnKQpsaW5lcyhhYnN4LCBwcmVkWDEyLCBjb2wgPSAncmVkJywgdHlwZSA9ICdsJykKYGBgClBvdXIgbGVzIMOpY2hhbnRpbGxvbnMgZGUgdGFpbGxlICQxMDAkLCBub3VzIHBvdXZvbnMgZmFpcmUgbGEgbcOqbWUgcmVtYXJxdWUgcXVlIHBvdXIgbGVzIMOpY2hhbnRpbGxvbnMgZGUgdGFpbGxlICQxMCQuIERlIHBsdXMsIGxlIHRyb2lzacOobWUgbW9kw6hsZSBkZSByw6lncmVzc2lvbiBsaW7DqWFpcmUgcsOpYWxpc2UgdW5lIHByw6lkaWN0aW9uIHF1aSBzZW1ibGUgYXNzZXogc3RhdGlvbm5haXJlIGV0IHDDqXJpb2RpcXVlLiBDZXBlbmRhbnQsIG5vdHJlIMOpY2hhbnRpbGxvbiBkZSBiYXNlIG5lIGwnZXN0IHBhcy4gQmllbiBxdWUgbGEgbGFyZ2V1ciBkZSBsYSBwcsOpZGljdGlvbiByw6lhbGlzw6llIGF2ZWMgbGUgZGV1eGnDqG1lIG1vZMOobGUgZXN0IHBsdXMgcGV0aXRlLCBlbGxlIGVzdCBwbHVzIHNlbWJsYWJsZSDDoCBsJ8OpY2hhbnRpbGxvbiBkYW5zIHNvbiBhdHRpdHVkZS4gUG91ciBsZXMgZ3JvdXBlcyBkZSB0YWlsbGUgJDEwMCQsIGxlIGRldXhpw6htZSBtb2TDqGxlIGRlIHLDqWdyZXNzaW9uIGxpbsOpYWlyZSBzZW1ibGUgbWlldXggY29ycmVzcG9uZHJlLgoKTGVzIGdyYXBoaXF1ZXMgZGUgbCfDqWNoYW50aWxsb24gWDIyIHNvbnQgOgpgYGB7ciBlY2hvID0gRkFMU0V9CmNvbnNvbW1hdGlvbiA8LSBjb25zb20uY3N2JFgyMltjKDE1MDAwOjE3NTIwKV0KCnByZWRYMjIgPC0gcmVnbGluMSgyMikKcGxvdCh0ZW1wcywgY29uc29tbWF0aW9uLCB0eXBlID0gJ2wnLCB4bGltID0gbGlteCwgeWxpbSA9IGxpbXksIG1haW4gPSBwYXN0ZSgicHJlbWlcVTAwRThyZSBwclxVMDBFOSIsImRpY3Rpb24gcGFyIGxtIHBvdXIgWDIyIiwgc2VwID0gIiIpLCBjb2wgPSAnZm9yZXN0Z3JlZW4nKQpsaW5lcyhhYnN4LCBwcmVkWDIyLCBjb2wgPSAnZ3JlZW4nLCB0eXBlID0gJ2wnKQoKcHJlZFgyMiA8LSByZWdsaW4yKDIyKQpwbG90KHRlbXBzLCBjb25zb21tYXRpb24sIHR5cGUgPSAnbCcsIHhsaW0gPSBsaW14LCB5bGltID0gbGlteSwgbWFpbiA9IHBhc3RlKCJkZXV4aVxVMDBFOG1lIHByXFUwMEU5IiwiZGljdGlvbiBwYXIgbG0gcG91ciBYMjIiLCBzZXAgPSAiIiksIGNvbCA9ICdmb3Jlc3RncmVlbicpCmxpbmVzKGFic3gsIHByZWRYMjIsIGNvbCA9ICdncmVlbicsIHR5cGUgPSAnbCcpCgpwcmVkWDIyIDwtIHJlZ2xpbjMoMjIpCnBsb3QodGVtcHMsIGNvbnNvbW1hdGlvbiwgdHlwZSA9ICdsJywgeGxpbSA9IGxpbXgsIHlsaW0gPSBsaW15LCBtYWluID0gcGFzdGUoInRyb2lzaVxVMDBFOG1lIHByXFUwMEU5IiwiZGljdGlvbiBwYXIgbG0gcG91ciBYMjIiLCBzZXAgPSAiIiksIGNvbCA9ICdmb3Jlc3RncmVlbicpCmxpbmVzKGFic3gsIHByZWRYMjIsIGNvbCA9ICdncmVlbicsIHR5cGUgPSAnbCcpCmBgYApOb3VzIHBvdXZvbnMgZmFpcmUgbGVzIG3Dqm1lcyByZW1hcnF1ZXMgcXVlIHBvdXIgbGVzIMOpY2hhbnRpbGxvbnMgZGUgdGFpbGxlICQxMDAkLiBDZXBlbmRhbnQsIGNvbW1lIGxhIHZhcmlhbmNlIGRlcyDDqWNoYW50aWxsb25zIGVzdCBwbHVzIHBldGl0ZSBkYW5zIGNlIGNhcy1sw6AsIGxhIGRpZmbDqXJlbmNlIGVzdCBtb2lucyBmbGFncmFudGUuIMOAIG5vdXZlYXUsIGxlIGNvbXBvcnRlbWVudCBkZSBsYSB0cm9pc2nDqG1lIHByw6lkaWN0aW9uIHNlbWJsZSB0cm9wIHLDqWd1bGllci4gUG91ciBsZXMgZ3JvdXBlcyBkZSB0YWlsbGUgJDEwMDAkLCBsZSBkZXV4acOobWUgbW9kw6hsZSBkZSByw6lncmVzc2lvbiBsaW7DqWFpcmUgc2VtYmxlIG1pZXV4IGNvcnJlc3BvbmRyZS4KCiMjIyBNb2TDqGxlIGFkZGl0aWYgZ8OpbsOpcmFsaXPDqQoKRW5zdWl0ZSwgbm91cyBhdm9ucyByw6lhbGlzw6kgZGVzIHByw6lkaWN0aW9ucyBhdmVjIHVuIG1vZMOobGUgYWRkaXRpZiBnw6luw6lyYWxpc8OpIMOgIGwnYWlkZSBkZSBsYSBmb25jdGlvbiBgZ2FtYC4gUG91ciBjZSBtb2TDqGxlLCBjZSBxdWkgZXN0IGltcG9ydGFudCBlc3QgZGUgYmllbiBjaG9pc2lyIGxlcyB2YXJpYWJsZXMgYWluc2kgcXVlIGxlcyBwYXJhbcOodHJlcyBxdWkgbHVpIHNvbnQgYXNzb2Npw6llcy4KCk5vdXMgYXZvbnMgcHJpcyBsZXMgdmFyaWFibGVzIDogCgoqIGBUZW1wZXJhdHVyZWAgYXZlYyBjb21tZSBwYXJhbcOodHJlICRrID0gNTQkOwoqIGBoZXVyZWAgZW4gbnVtw6lyaXF1ZSBhdmVjIGNvbW1lIHBhcmFtw6h0cmUgJGsgPSA0OCQ7CiogYG1vaXNgIGVuIG51bcOpcmlxdWUgYXZlYyBjb21tZSBwYXJhbcOodHJlICRrID0gMTIkOwoqIGBtb2lzLmhldXJlYCBhdmVjIGNvbW1lIHBhcmFtw6h0cmUgJGsgPSAxOTEkOwoqIGB3ZWVrZW5kLmhldXJlYCBhdmVjIGNvbW1lIHBhcmFtw6h0cmUgJGsgPSAxMCQ7CiogYHRlbXAud2Vla2VuZGAgYXZlYyBjb21tZSBwYXJhbcOodHJlICRrID0gNDAkOwoqIGB0ZW1wLm1vaXNgIGF2ZWMgY29tbWUgcGFyYW3DqHRyZSAkayA9IDIwJDsKKiBgdGVtcC5oZXVyZWAgYXZlYyBjb21tZSBwYXJhbcOodHJlICRrID0gMTgkOwoqIGBUZW1wZXJhdHVyZS5sYWdgIGF2ZWMgY29tbWUgcGFyYW3DqHRyZSAkayA9IDEwJC4KClBvdXIgc8OpbGVjdGlvbm5lciBjZXMgcGFyYW3DqHRyZXMsIMOgIG5vdXZlYXUsIG5vdXMgYXZvbnMgZmFpdCBkZXMgcHLDqWRpY3Rpb25zIHRlc3RzIHN1ciBgVHJhaW5gIGV0IGBUZXN0YC4gUG91ciBjZWxhLCBub3VzIGF2b25zIHRlc3TDqSBwbHVzaWV1cnMgdmFsZXVycyBkZSAkayQgcG91ciBjaGFxdWUgdmFyaWFibGUgZXQgY29tcGFyw6kgbGVzIHNjb3JlcyDDoCBsJ2FpZGUgZGUgbGEgZm9uY3Rpb24gYHJtc2VgLiBOb3VzIGF2b25zIGdhcmTDqSBsZXMgcGFyYW3DqHRyZXMgcXVpIG5vdXMgcGVybWV0dGFpZW50IGQnb2J0ZW5pciBsZSBwbHVzIHBldGl0IHNjb3JlLgoKTm91cyBhdm9ucyBkb25jIGltcGzDqW1lbnTDqSBub3RyZSBmb25jdGlvbiBkZSBwcsOpZGljdGlvbiBhdmVjIGxlcyBwYXJhbcOodHJlcyB0cm91dsOpcy4KYGBge3J9CmdhbS5wcmVkIDwtIGZ1bmN0aW9uIChrKSB7CiAgIyBtb2TDqGxlCiAgWGsgPC0gZGF0YTBbLCBrICsgMV0KICBtb2RlbCA8LSBnYW0oWGsgfiBzKFRlbXBlcmF0dXJlLCBrID0gNTQpICsgcyhoZXVyZS5udW0sIGsgPSA0OCkgKyBzKGFzLm51bWVyaWMobW9pcyksIGsgPSAxMikgKyBzKG1vaXMuaGV1cmUsIGsgPSAxOTEpICsgcyh3ZWVrZW5kLmhldXJlLCBrID0gMTApICsgcyh0ZW1wLndlZWtlbmQsIGsgPSA0MCkgKyBzKHRlbXAubW9pcywgayA9IDIwKSArIHMoVGVtcGVyYXR1cmUubGFnLCBrID0gMTApICsgcyh0ZW1wLmhldXJlLCBrID0gMTgpLCBkYXRhID0gY29uc29tLmNzdikKICAKICAjIHByw6lkaWN0aW9uCiAgZGF0YS5wcmVkIDwtIGRhdGExW2MoKDI3ODQqKGsgLSAxKSArIDEpOigyNzg0KmspKSwgXQogIHRlbXAuWGsgPC0gbXkubmEuYXBwcm94KGRhdGEucHJlZCRUZW1wZXJhdHVyZSkKICB0ZW1wLlhrLmxhZyA8LSBzYXBwbHkoMTpsZW5ndGgodGVtcC5YayksIGZ1bmN0aW9uKHgpIHRlbXAuWGtbeCAtIDFdKQogIHRlbXAuWGsubGFnWzFdIDwtIHRlbXAuWGsubGFnWzJdCiAgCiAgWGsucHJlZCA8LSBkYXRhLmZyYW1lKHRlbXAuWGssIGhldXJlLm51bS5wcmVkLCBhcy5udW1lcmljKG1vaXMucHJlZCksIG1vaXMuaGV1cmUucHJlZCwKICAgICAgICAgICAgICAgICAgICAgICAgd2Vla2VuZC5oZXVyZS5wcmVkLCB0ZW1wLlhrKndlZWtlbmQucHJlZCwgdGVtcC5Yayphcy5udW1lcmljKG1vaXMucHJlZCksCiAgICAgICAgICAgICAgICAgICAgICAgIHRlbXAuWGsubGFnLCB0ZW1wLlhrKmhldXJlLm51bS5wcmVkKQogIG5hbWVzKFhrLnByZWQpIDwtIGMoIlRlbXBlcmF0dXJlIiwgImhldXJlLm51bSIsICJtb2lzIiwgIm1vaXMuaGV1cmUiLCAid2Vla2VuZC5oZXVyZSIsICJ0ZW1wLndlZWtlbmQiLCAidGVtcC5tb2lzIiwgIlRlbXBlcmF0dXJlLmxhZyIsICJ0ZW1wLmhldXJlIikKICBwcmVkIDwtIHByZWRpY3QobW9kZWwsIG5ld2RhdGEgPSBYay5wcmVkKQp9CmBgYAoKYGBge3IgaW5jbHVkZSA9IEZBTFNFfQpwcmVkLmdhbSA8LSByZWFkLmNzdigiZ2FtL2dhbV8wLjA1ODUyLmNzdiIsIGhlYWRlciA9IFRSVUUpCmBgYAoKTGUgZ3JhcGhpcXVlIGRlIGwnw6ljaGFudGlsbG9uIFgyIGVzdCA6CmBgYHtyIGVjaG8gPSBGQUxTRX0KayA8LSAyCnByZWRYMiA8LSBwcmVkLmdhbSRQcmVkaWN0aW9uWygyNzg0KihrIC0gMSkgKyAxKTooMjc4NCprKV0KY29uc29tbWF0aW9uIDwtIGNvbnNvbS5jc3YkWDJbYygxNTAwMDoxNzUyMCldCgpwbG90KHRlbXBzLCBjb25zb21tYXRpb24sIHR5cGUgPSAnbCcsIHhsaW0gPSBsaW14LCB5bGltID0gbGlteSwgbWFpbiA9IHBhc3RlKCJwclxVMDBFOSIsImRpY3Rpb24gcGFyIGdhbSBwb3VyIFgyIiwgc2VwID0gIiIpLCBjb2wgPSAnZG9kZ2VyYmx1ZScpCmxpbmVzKGFic3gsIHByZWRYMiwgY29sID0gJ2JsdWUnLCB0eXBlID0gJ2wnKQpgYGAKVG91dCBjb21tZSBwb3VyIGxlcyBwcsOpZGljdGlvbnMgcGFyIHLDqWdyZXNzaW9uIGxpbsOpYWlyZSwgbGEgdmFyaWFuY2UgZGUgbGEgcGFydGllIGRlIGRyb2l0ZSBlc3QgcGx1cyBwZXRpdGUgcXVlIGxhIHBhcnRpZSBkZSBnYXVjaGUuIENlcGVuZGFudCwgY2V0dGUgcHLDqWRpY3Rpb24gw6lxdWl2YXV0IMOgIGxhIHRyb2lzacOobWUgcHLDqWRpY3Rpb24gcGFyIGBsbWAuCgpMZSBncmFwaGlxdWUgZGUgbCfDqWNoYW50aWxsb24gWDEyIGVzdCA6CmBgYHtyIGVjaG8gPSBGQUxTRX0KayA8LSAxMgpwcmVkWDEyIDwtIHByZWQuZ2FtJFByZWRpY3Rpb25bKDI3ODQqKGsgLSAxKSArIDEpOigyNzg0KmspXQpjb25zb21tYXRpb24gPC0gY29uc29tLmNzdiRYMTJbYygxNTAwMDoxNzUyMCldCgpwbG90KHRlbXBzLCBjb25zb21tYXRpb24sIHR5cGUgPSAnbCcsIHhsaW0gPSBsaW14LCB5bGltID0gbGlteSwgbWFpbiA9IHBhc3RlKCJwclxVMDBFOSIsImRpY3Rpb24gcGFyIGdhbSBwb3VyIFgxMiIsIHNlcCA9ICIiKSwgY29sID0gJ2NvcmFsJykKbGluZXMoYWJzeCwgcHJlZFgxMiwgY29sID0gJ3JlZCcsIHR5cGUgPSAnbCcpCmBgYArDgCBub3V2ZWF1LCBjZXR0ZSBwcsOpZGljdGlvbiDDqXF1aXZhdXQgw6AgbGEgdHJvaXNpw6htZSBwcsOpZGljdGlvbiBwYXIgYGxtYC4gTGEgcHLDqWRpY3Rpb24gZXN0IHBsdXTDtHQgc3RhdGlvbm5haXJlIGV0IHDDqXJpb2RpcXVlLgoKTGUgZ3JhcGhpcXVlIGRlIGwnw6ljaGFudGlsbG9uIFgyMiBlc3QgOgpgYGB7ciBlY2hvID0gRkFMU0V9CmsgPC0gMjIKcHJlZFgyMiA8LSBwcmVkLmdhbSRQcmVkaWN0aW9uWygyNzg0KihrIC0gMSkgKyAxKTooMjc4NCprKV0KY29uc29tbWF0aW9uIDwtIGNvbnNvbS5jc3YkWDIyW2MoMTUwMDA6MTc1MjApXQoKcGxvdCh0ZW1wcywgY29uc29tbWF0aW9uLCB0eXBlID0gJ2wnLCB4bGltID0gbGlteCwgeWxpbSA9IGxpbXksIG1haW4gPSBwYXN0ZSgicHJcVTAwRTkiLCJkaWN0aW9uIHBhciBnYW0gcG91ciBYMjIiLCBzZXAgPSAiIiksIGNvbCA9ICdmb3Jlc3RncmVlbicpCmxpbmVzKGFic3gsIHByZWRYMjIsIGNvbCA9ICdncmVlbicsIHR5cGUgPSAnbCcpCmBgYApFbmZpbiwgbcOqbWUgcG91ciBsZXMgZ3JvdXBlcyBkZSB0YWlsbGUgJDEwMDAkLCBub3VzIGZhaXNvbnMgbGVzIG3Dqm1lcyBvYnNlcnZhdGlvbnMgcXVlIHBvdXIgbGVzIGF1dHJlcyBncm91cGVzIGRlIHRhaWxsZXMgZGlmZsOpcmVudGVzLgoKIyMjIExlcyBwbHVzIHByb2NoZXMgdm9pc2lucwoKRW5maW4sIG5vdXMgYXZvbnMgcsOpYWxpc8OpIGRlcyBwcsOpZGljdGlvbnMgYXZlYyBsYSBtw6l0aG9kZSBkZXMgcGx1cyBwcm9jaGVzIHZvaXNpbnMuIFNpIG5vdXMgc291aGFpdG9ucyBjbGFzc2lmaWVyIGwnZW5zZW1ibGUgJFx7IHhfaSBcdGV4dHsgcG91ciB9IGkgXGluIDEsIFxjZG90cywgTiBcfSQsIGNldHRlIG3DqXRob2RlIGFzc29jaWUgw6AgY2hhcXVlIMOpbMOpbWVudCB1bmUgY2xhc3NlICRjKHhfaSkkIHF1aSBjb3JyZXNwb25kIMOgIGxhIGNsYXNzZSBkZXMgJGskIHZvaXNpbnMgbGVzIHBsdXMgcHJvY2hlcyBkZSBjZXQgw6lsw6ltZW50LiBQb3VyIGNlIG1vZMOobGUsIGNlIHF1aSBlc3QgaW1wb3J0YW50IGVzdCBkZSBiaWVuIGNob2lzaXIgbGVzIHZhcmlhYmxlcyBhaW5zaSBxdWUgbGVzIHBhcmFtw6h0cmVzIHF1aSBzb250IGFzc29jacOpcyDDoCBjaGFxdWUgw6ljaGFudGlsbG9uLiBMZXMgcGFyYW3DqHRyZXMgc29udCBsZXMgZGlmZsOpcmVudHMgJGskIHF1aSBkw6lmaW5pc3NlbnQgbGUgbm9tYnJlIGRlIHBsdXMgcHJvY2hlcyB2b2lzaW5zIGF1eHF1ZWxzIG5vdXMgbm91cyBpbnTDqXJlc3NvbnMuIERvbm5vbnMgdW4gZXhlbXBsZSBwb3VyIGlsbHVzdHJlciBjZXR0ZSBjbGFzc2lmaWNhdGlvbiA6CgoqIFNpIG5vdXMgcHJlbm9ucyBlbiBjb21wdGUgbGVzIHRyb2lzIHBsdXMgcHJvY2hlcyB2b2lzaW5zLCBsZSBwb2ludCB2ZXJ0IHNlcmEgZGUgY2xhc3NlIGJsZXVlIDoKYGBge3IgZWNobyA9IEZBTFNFfQpwbG90KGMoMSwgcmVwKDIsIDIpLCByZXAoMywgNCksIDQpLCBjKDMsIDMsIDIsIDIsIDMsIDQsIDUsIDMpLCB4bGltID0gYygxLCA4KSwgeWxpbSA9IGMoMiwgNSksIHhsYWIgPSAiIiwgeWxhYiA9ICcnLCB0eXBlID0gJ3AnLCBjb2wgPSAnY29yYWwnLCBwY2ggPSAxNiwgY2V4ID0gMikKcG9pbnRzKDUsIDMsIGNvbCA9ICdmb3Jlc3RncmVlbicsIHBjaCA9IDE2LCBjZXggPSAzKQpwb2ludHMoYyhyZXAoNiwgMiksIHJlcCg3LCAyKSwgOCksIGMoMywgNCwgMiwgNSwgMyksIGNvbCA9ICdkb2RnZXJibHVlJywgcGNoID0gMTYsIGNleCA9IDIpCnN5bWJvbHMoNSwgMywgY2lyY2xlcyA9IDEsIGluY2hlcyA9IDEuMjUsIGx3ZCA9IDIsIGFkZCA9IFRSVUUsIGZnID0gJ2ZvcmVzdGdyZWVuJykKYGBgCiogU2kgbm91cyBwcmVub25zIGVuIGNvbXB0ZSBsZXMgc2VwdCBwbHVzIHByb2NoZXMgdm9pc2lucywgbGUgcG9pbnQgdmVydCBzZXJhIGRlIGNsYXNzZSBvcmFuZ2UgOgpgYGB7ciBlY2hvID0gRkFMU0V9CnBsb3QoYygxLCByZXAoMiwgMiksIHJlcCgzLCA0KSwgNCksIGMoMywgMywgMiwgMiwgMywgNCwgNSwgMyksIHhsaW0gPSBjKDEsIDgpLCB5bGltID0gYygyLCA1KSwgeGxhYiA9ICIiLCB5bGFiID0gJycsIHR5cGUgPSAncCcsIGNvbCA9ICdjb3JhbCcsIHBjaCA9IDE2LCBjZXggPSAyKQpwb2ludHMoNSwgMywgY29sID0gJ2ZvcmVzdGdyZWVuJywgcGNoID0gMTYsIGNleCA9IDMpCnBvaW50cyhjKHJlcCg2LCAyKSwgcmVwKDcsIDIpLCA4KSwgYygzLCA0LCAyLCA1LCAzKSwgY29sID0gJ2RvZGdlcmJsdWUnLCBwY2ggPSAxNiwgY2V4ID0gMikKc3ltYm9scyg1LCAzLCBjaXJjbGVzID0gMSwgaW5jaGVzID0gMS44NSwgbHdkID0gMiwgYWRkID0gVFJVRSwgZmcgPSAnZm9yZXN0Z3JlZW4nKQpgYGAKCsOAIG5vdXZlYXUsIHBvdXIgZMOpdGVybWluZXIgbGVzIG1laWxsZXVycyBwYXJhbcOodHJlcywgbm91cyBhbGxvbnMgcHLDqWRpcmUgbm9zIGRvbm7DqWVzIGBUZXN0YCDDoCBwYXJ0aXIgZGVzIGRvbm7DqWVzIGBUcmFpbmAuIFBvdXIgY2hhcXVlIGNvbG9ubmUgZCfDqWNoYW50aWxsb24gYFhgIG5vdXMgYXVyb25zIHVuIHBhcmFtw6h0cmUgJGskIGRpZmbDqXJlbnQuIE5vdXMgZW5yZWdpc3Ryb25zIGNlcyBwYXJhbcOodHJlcyBkYW5zIHVuZSB2YXJpYWJsZSBub21tw6llIGBiZXN0X2tgLgpgYGB7cn0KdGVzdC5rbm4gPC0gZnVuY3Rpb24gKGsgPSAxLCBpID0gMSkgewogICMgdmFyaWFibGVzIHBvdXIgVHJhaW4KICBtb2lzLnRyYSA8LSBhcy5udW1lcmljKFRyYWluJG1vaXMpCiAgaGV1cmUudHJhIDwtIGhldXJlLm51bVt0cmFpbl9pbmRdCiAgd2UudHJhIDwtIFRyYWluJHdlZWtlbmQKICB3ZS5udW0udHJhIDwtIGFzLm51bWVyaWMoVHJhaW4kd2Vlay5udW0pCiAgdGVtcC50cmEgPC0gVHJhaW4kVGVtcGVyYXR1cmUKICB0ZW1wLmxhZy50cmEgPC0gVHJhaW4kVGVtcGVyYXR1cmUubGFnCiAgCiAgIyB2YXJpYWJsZXMgcG91ciBUZXN0CiAgbW9pcy50ZXMgPC0gYXMubnVtZXJpYyhUZXN0JG1vaXMpCiAgaGV1cmUudGVzIDwtIGhldXJlLm51bVstdHJhaW5faW5kXQogIHdlLnRlcyA8LSBUZXN0JHdlZWtlbmQKICB3ZS5udW0udGVzIDwtIGFzLm51bWVyaWMoVGVzdCR3ZWVrLm51bSkKICB0ZW1wLnRlcyA8LSBUZXN0JFRlbXBlcmF0dXJlCiAgdGVtcC5sYWcudGVzIDwtIFRlc3QkVGVtcGVyYXR1cmUubGFnCiAgCiAgdHJhIDwtIGRhdGEuZnJhbWUobW9pcy50cmEsIGhldXJlLnRyYSwgd2UudHJhLCB3ZS5udW0udHJhLCB0ZW1wLnRyYSwgdGVtcC5sYWcudHJhLCB0ZW1wLnRyYSpoZXVyZS50cmEsIHRlbXAudHJhKndlLnRyYSwgdGVtcC50cmEqbW9pcy50cmEsIG1vaXMudHJhKmhldXJlLnRyYSwgd2UudHJhKmhldXJlLnRyYSkKICAKICB0ZXMgPC0gZGF0YS5mcmFtZShtb2lzLnRlcywgaGV1cmUudGVzLCB3ZS50ZXMsIHdlLm51bS50ZXMsIHRlbXAudGVzLCB0ZW1wLmxhZy50ZXMsIHRlbXAudGVzKmhldXJlLnRlcywgdGVtcC50ZXMqd2UudGVzLCB0ZW1wLnRlcyptb2lzLnRlcywgbW9pcy50ZXMqaGV1cmUudGVzLCB3ZS50ZXMqaGV1cmUudGVzKQogIAogIGNvbnNvIDwtIFRyYWluWywgaSArIDVdCiAgcHJlZCA8LSBrbm4ucmVnKHRyYWluID0gdHJhLCB0ZXN0ID0gdGVzLCB5ID0gY29uc28sIGsgPSBrKSRwcmVkCiAgYWN0IDwtIFRlc3RbLCBpICsgNV0KICBybXNlKHByZWQsIGFjdCkKfQoKYmVzdF9rIDwtIHZlY3RvcigpCmZvciAoSSBpbiAxOjMwKSB7CiAgTGlzdEsgPC0gYygxOjEwKQogIExpc3QgPC0gc2FwcGx5KExpc3RLLCB0ZXN0LmtubiwgaSA9IEkpCiAgYmVzdF9rIDwtIGMoYmVzdF9rLCB3aGljaC5taW4oTGlzdCkpCn0KYGBgCgpMYSBsaXN0ZSBgYmVzdF9rYCB2YXV0IDoKYGBge3J9CnByaW50KGJlc3RfaykKYGBgCgpVbmUgZm9pcyBjZXR0ZSBsaXN0ZSBjcsOpw6llLCBub3VzIHLDqWFsaXNvbnMgbm90cmUgcHLDqWRpY3Rpb24gZW4gcHJlbmFudCBlbiBjb21wdGUgbGVzIHZhcmlhYmxlcyBgbW9pc2AgZW4gbnVtw6lyaXF1ZSwgYGhldXJlYCBlbiBudW3DqXJpcXVlLCBgd2Vla2VuZGAsIGB3ZWVrLm51bWAsIGBUZW1wZXJhdHVyZWAgZXQgYFRlbXBlcmF0dXJlLmxhZ2AuCmBgYHtyfQp0cmEgPC0gZGF0YS5mcmFtZShhcy5udW1lcmljKGNvbnNvbS5jc3YkbW9pcyksIGhldXJlLm51bSwgY29uc29tLmNzdiR3ZWVrZW5kLCBhcy5udW1lcmljKGNvbnNvbS5jc3Ykd2Vlay5udW0pLCBjb25zb20uY3N2JFRlbXBlcmF0dXJlLCBjb25zb20uY3N2JFRlbXBlcmF0dXJlLmxhZykKCmtubi5wcmVkIDwtIGZ1bmN0aW9uIChpKSB7CiAgdGVzIDwtIGRhdGEuZnJhbWUoYXMubnVtZXJpYyhtb2lzLnByZWQpLCBoZXVyZS5udW0ucHJlZCwgd2Vla2VuZC5wcmVkLCBhcy5udW1lcmljKHdlZWsubnVtLnByZWQpLCBUZW1wZXJhdHVyZS5wcmVkLCBUZW1wZXJhdHVyZS5sYWcucHJlZCkKICB0ZXMgPC0gdGVzW2MoKDI3ODQqKGkgLSAxKSArIDEpOigyNzg0KmkpKSwgXQogIGNvbnNvIDwtIGNvbnNvbS5jc3ZbLCBpICsgNV0KICBwcmVkIDwtIGtubi5yZWcodHJhaW4gPSB0cmEsIHRlc3QgPSB0ZXMsIHkgPSBjb25zbywgayA9IGJlc3Rfa1tpXSkkcHJlZAp9CmBgYAoKYGBge3IgaW5jbHVkZSA9IEZBTFNFfQpwcmVkLmtubiA8LSByZWFkLmNzdigiS05OL2tubl8wLjA2NTEzLmNzdiIsIGhlYWRlciA9IFRSVUUpCmBgYAoKTGUgZ3JhcGhpcXVlIGRlIGwnw6ljaGFudGlsbG9uIFgyIGVzdCA6CmBgYHtyIGVjaG8gPSBGQUxTRX0KY29uc29tbWF0aW9uIDwtIGNvbnNvbS5jc3YkWDJbYygxNTAwMDoxNzUyMCldCgpwcmVkWDIgPC0ga25uLnByZWQoMikKcGxvdCh0ZW1wcywgY29uc29tbWF0aW9uLCB0eXBlID0gJ2wnLCB4bGltID0gbGlteCwgeWxpbSA9IGxpbXksIG1haW4gPSBwYXN0ZSgicHJcVTAwRTkiLCJkaWN0aW9uIHBhciBrbm4gcG91ciBYMiIsIHNlcCA9ICIiKSwgY29sID0gJ2RvZGdlcmJsdWUnKQpsaW5lcyhhYnN4LCBwcmVkWDIsIGNvbCA9ICdibHVlJywgdHlwZSA9ICdsJykKYGBgCkNldHRlIGZvaXMtY2ksIGNvbnRyYWlyZW1lbnQgYXV4IGRldXggbcOpdGhvZGVzIHByw6ljw6lkZW50ZXMsIGxhIHZhcmlhbmNlIGRlIGxhIHByw6lkaWN0aW9uIGVzdCBiaWVuIHBsdXMgcHJvY2hlIGRlIGxhIHZhcmlhbmNlIGRlIGwnw6ljaGFudGlsbG9uLiBEZSBwbHVzLCBsYSBwcsOpZGljdGlvbiBuZSBzZW1ibGUgbmkgcGFydGljdWxpw6hyZW1lbnQgc3RhdGlvbm5haXJlIG5pIHBhcnRpY3VsacOocmVtZW50IHDDqXJpb2RpcXVlLiBMJ2FsbHVyZSBkZSBsYSBjb3VyYmUgZXN0IHBsdXTDtHQgY29ow6lyZW50ZS4KCkxlIGdyYXBoaXF1ZSBkZSBsJ8OpY2hhbnRpbGxvbiBYMTIgZXN0IDoKYGBge3IgZWNobyA9IEZBTFNFfQpjb25zb21tYXRpb24gPC0gY29uc29tLmNzdiRYMTJbYygxNTAwMDoxNzUyMCldCgpwcmVkWDEyIDwtIGtubi5wcmVkKDEyKQpwbG90KHRlbXBzLCBjb25zb21tYXRpb24sIHR5cGUgPSAnbCcsIHhsaW0gPSBsaW14LCB5bGltID0gbGlteSwgbWFpbiA9IHBhc3RlKCJwclxVMDBFOSIsImRpY3Rpb24gcGFyIGtubiBwb3VyIFgxMiIsIHNlcCA9ICIiKSwgY29sID0gJ2NvcmFsJykKbGluZXMoYWJzeCwgcHJlZFgxMiwgY29sID0gJ3JlZCcsIHR5cGUgPSAnbCcpCmBgYArDgCBub3V2ZWF1LCBub3VzIHBvdXZvbnMgZmFpcmUgbGVzIG3Dqm1lcyBjb21tZW50YWlyZXMgcXVlIHBvdXIgbGVzIMOpY2hhbnRpbGxvbnMgZGUgdGFpbGxlICQxMCQuIEVuIGVmZmV0LCBsJ2FsbHVyZSBkZSBsYSBjb3VyYmUgZXN0IGNvaMOpcmVudGUgZXQgbGVzIGRldXggcGFydGllcyBzZW1ibGVudCBzZSBjb21wb3J0ZXIgZGUgbGEgbcOqbWUgZmHDp29uLiBMZXMgdmFyaWFuY2VzIHNvbnQgZW5jb3JlIHBsdXMgc2VtYmxhYmxlcy4KCkxlIGdyYXBoaXF1ZSBkZSBsJ8OpY2hhbnRpbGxvbiBYMjIgZXN0IDoKYGBge3IgZWNobyA9IEZBTFNFfQpjb25zb21tYXRpb24gPC0gY29uc29tLmNzdiRYMjJbYygxNTAwMDoxNzUyMCldCgpwcmVkWDIyIDwtIGtubi5wcmVkKDIyKQpwbG90KHRlbXBzLCBjb25zb21tYXRpb24sIHR5cGUgPSAnbCcsIHhsaW0gPSBsaW14LCB5bGltID0gbGlteSwgbWFpbiA9IHBhc3RlKCJwclxVMDBFOSIsImRpY3Rpb24gcGFyIGtubiBwb3VyIFgyMiIsIHNlcCA9ICIiKSwgY29sID0gJ2ZvcmVzdGdyZWVuJykKbGluZXMoYWJzeCwgcHJlZFgyMiwgY29sID0gJ2dyZWVuJywgdHlwZSA9ICdsJykKYGBgClBvdXIgbGVzIMOpY2hhbnRpbGxvbnMgZGUgdGFpbGxlICQxMDAwJCwgaWwgc2UgcGFzc2UgbGEgbcOqbWUgY2hvc2UgcXVlIHByw6ljw6lkZW1tZW50IGF2ZWMgdW5lIGRpZmbDqXJlbmNlIGRlIHZhcmlhbmNlIHRyw6hzIGZhaWJsZS4KCkdyYXBoaXF1ZW1lbnQsIGxlcyBwcsOpZGljdGlvbnMgZmFpdGVzIHBhciBsYSBtw6l0aG9kZSBkZXMgcGx1cyBwcm9jaGVzIHZvaXNpbnMgc2VtYmxlbnQgbGVzIG1laWxsZXVyZXMuCgoKCiMgQ29uY2x1c2lvbgoKRGUgZmHDp29uIGfDqW7DqXJhbGUsIG5vdXMgcmVtYXJxdW9ucyBxdWUgcGx1cyBsYSB0YWlsbGUgZGUgbCfDqWNoYW50aWxsb24gZXN0IGdyYW5kZSwgcGx1cyBsYSB2YXJpYW5jZSBlc3QgcGV0aXRlLiBMYSBkaWZmaWN1bHTDqSB2aWVudCBkb25jIGR1IGZhaXQgcXUnaWwgZmF1dCBwcsOpZGlyZSBjb3JyZWN0ZW1lbnQgY2V0dGUgdmFyaWFuY2UgcXVpIGNoYW5nZSBwb3VyIGNoYXF1ZSDDqWNoYW50aWxsb24uCgpMYSByw6lncmVzc2lvbiBsaW7DqWFpcmUgbm91cyBhIHBlcm1pcyBkJ2F2b2lyIG5vcyBtZWlsbGV1cnMgc2NvcmVzLiBDZXBlbmRhbnQsIMOgIGxhIHJlcHLDqXNlbnRhdGlvbiBncmFwaGlxdWUsIG5vdXMgYXZvbnMgcmVtYXJxdcOpIHF1ZSBwb3VyIGxlcyDDqWNoYW50aWxsb25zIGRlIHRhaWxsZSAkMTAkLCBsYSBwcsOpZGljdGlvbiBuJ2VzdCBwYXMgY29ow6lyZW50ZSwgZW4gZWZmZXQsIGxlIG1vZMOobGUgbmUgcmV0cm91dmUgcGFzIGxhIGJvbm5lIHZhcmlhbmNlLiBQYXIgY29udHJlLCBwbHVzIGxhIHRhaWxsZSBkdSBncm91cGUgZXN0IGdyYW5kZSwgcGx1cyBsYSB2YXJpYW5jZSBkaW1pbnVlIGV0IHBsdXMgbGEgcHLDqWRpY3Rpb24gZXN0IGNvaMOpcmVudGUuCgpMZSBtb2TDqGxlIGFkZGl0aWYgZ8OpbsOpcmFsaXPDqSBuJ2EgcGFzIGFtw6lsaW9yw6kgbm9zIHNjb3Jlcy4gQ2VwZW5kYW50LCDDoCBsYSByZXByw6lzZW50YXRpb24gZ3JhcGhpcXVlLCBsYSB2YXJpYW5jZSBlc3QgcGx1cyBjb25zZXJ2w6llIHBvdXIgdG91dGVzIGxlcyB0YWlsbGVzIGQnw6ljaGFudGlsbG9uLiBFbiBlZmZldCwgcG91ciBsZXMgZ3JvdXBlcyBkZSB0YWlsbGUgJDEwJCwgbGEgdmFyaWFuY2UgZGUgbGEgcHLDqWRpY3Rpb24gZXN0IGJpZW4gcGx1cyBncmFuZGUgcXVlIHBvdXIgbGEgcsOpZ3Jlc3Npb24gbGluw6lhaXJlLiBEw6hzIGxlcyBncm91cGVzIGRlIHRhaWxsZSAkMTAwJCwgbGVzIHZhcmlhbmNlcyBzb250IHF1YXNpbWVudCDDqWdhbGVzLgoKTGUgbW9kw6hsZSBkZXMgcGx1cyBwcm9jaGVzIHZvaXNpbnMgbidhIHBhcyBhbcOpbGlvcsOpIG5vcyBzY29yZXMuIENlcGVuZGFudCwgbGVzIHJlcHLDqXNlbnRhdGlvbnMgZ3JhcGhpcXVlcyBkZXMgcHLDqWRpY3Rpb25zIHNlbWJsZW50IHRvdXQgw6AgZmFpdCBjb2jDqXJlbnRlcy4gRW4gZWZmZXQsIGxhIHByw6lkaWN0aW9uIGRlIGwnw6ljaGFudGlsbG9uIFgyIGVzdCBiaWVuIHBsdXMgbGFyZ2UgcXVlIGRhbnMgbGVzIGRldXggYXV0cmVzIG3DqXRob2Rlcy4gRGUgcGx1cywgbGVzIGNvdXJiZXMgZGVzIHByw6lkaWN0aW9ucyBzZW1ibGVudCBzZSBjb21wb3J0ZXIgZGUgbGEgbcOqbWUgZmHDp29uIHF1ZSBsZXMgY291cmJlcyBkZXMgw6ljaGFudGlsbG9ucy4KClBvdXIgdGVudGVyIGQnYW3DqWxpb3JlciBub3Mgc2NvcmVzLCBub3VzIHBvdXJyaW9ucyB1dGlsaXNlciBkZXMgbW9kw6hsZXMgZGUgc8OpcmllcyB0ZW1wb3JlbGxlcyBjb21tZSBgQVJJTUFgIHBhciBleGVtcGxlLiBJbCBmYXVkcmFpdCwgcG91ciBjZSBmYWlyZSwgdHJvdXZlciBsZXMgcGFyYW3DqHRyZXMgJHAkIGV0ICRxJCBhc3NvY2nDqXMgw6AgY2hhcXVlIMOpY2hhbnRpbGxvbiBgWGtgLgoK