Les analyses factorielles contraintes


L’exécution de ce tutoriel nécessite au préalable l’installation (ou la mise à jour) des packages suivants : GDAtools et factoextra (analyses factorielles), pls, morepls et plsVarSel (régressions PLS), les habituels ggplot2 et dplyr, ainsi que soc.ca et questionr dont on va utiliser des jeux de données.


Premiers pas

Tout d’abord, on charge les packages nécessaires en mémoire.

library(GDAtools)
library(ggplot2)
library(dplyr)
library(pls)
library(morepls)

Variables supplémentaires

Pour ce premier exemple, nous allons utiliser l’un des jeux de données fournis avec le package GDAtools. Il s’agit d’informations sur les goûts et les pratiques culturelles de 2000 individus : écoute de genres musicaux (variété française, rap, rock, jazz et classique) et goût pour des genres de films (comédie, film policier, animation, science-fiction, film d’amour, comédie musicale). Ces 11 variables serviront de variables “actives” dans une ACM et seront complétées par 3 variables “supplémentaires” : le sexe, l’âge et le niveau d’éducation.

data(Taste)
str(Taste)
## 'data.frame':    2000 obs. of  14 variables:
##  $ FrenchPop: Factor w/ 3 levels "No","Yes","NA": 2 1 2 1 2 1 1 1 1 2 ...
##  $ Rap      : Factor w/ 3 levels "No","Yes","NA": 1 1 1 1 1 1 1 1 1 1 ...
##  $ Rock     : Factor w/ 3 levels "No","Yes","NA": 1 1 2 1 1 2 1 1 2 1 ...
##  $ Jazz     : Factor w/ 3 levels "No","Yes","NA": 1 2 1 1 1 1 1 1 1 1 ...
##  $ Classical: Factor w/ 3 levels "No","Yes","NA": 1 2 1 2 1 1 1 1 1 1 ...
##  $ Comedy   : Factor w/ 3 levels "No","Yes","NA": 1 2 1 1 1 1 2 2 2 2 ...
##  $ Crime    : Factor w/ 3 levels "No","Yes","NA": 1 1 1 1 2 1 1 1 1 1 ...
##  $ Animation: Factor w/ 3 levels "No","Yes","NA": 1 1 1 1 1 1 1 1 1 1 ...
##  $ SciFi    : Factor w/ 3 levels "No","Yes","NA": 2 1 1 1 1 2 1 1 1 1 ...
##  $ Love     : Factor w/ 3 levels "No","Yes","NA": 1 1 2 1 1 1 1 1 1 1 ...
##  $ Musical  : Factor w/ 3 levels "No","Yes","NA": 1 1 1 1 1 1 1 1 1 1 ...
##  $ Gender   : Factor w/ 2 levels "Men","Women": 1 1 2 1 2 2 2 2 1 1 ...
##  $ Age      : Factor w/ 3 levels "15-24","25-49",..: 2 3 2 3 2 2 2 2 1 3 ...
##  $ Educ     : Factor w/ 4 levels "None","Low","Medium",..: 3 4 3 4 2 1 3 2 2 2 ...

Les variables actives ont toutes une modalité “non-réponse” (“NA”), qui concerne quelques individus. L’ACM spécifique permet de neutraliser ces modalités dans la construction de l’espace factoriel, tout en conservant l’ensemble des individus.

junk <- c("FrenchPop.NA", "Rap.NA", "Rock.NA", "Jazz.NA", "Classical.NA",
          "Comedy.NA", "Crime.NA", "Animation.NA", "SciFi.NA", "Love.NA", 
          "Musical.NA")
mca <- speMCA(Taste[,1:11], excl = junk)

On représente ensuite le nuage des modalités. Sur ce nuage :

  • l’écoute de jazz et de musique classique et le goût des comédies musicales semblent s’opposer à l’écoute de rap et au goût pour les comédies sur l’axe 1 ;
  • le goût pour l’animation et la science-fiction à celui pour les films d’amour et les comédies musicales sur l’axe 2.
ggcloud_variables(mca, shapes = FALSE, legend = "none")

On représente traditionnellement les résultats dans un plan, c’est-à-dire en deux dimensions (ici les dimensions 1 et 2). Mais il est parfois plus simple de concentrer l’interprétation sur un seul axe à la fois, ce qui est possible avec la fonctionggaxis_variables() (paramétrée ici pour afficher les noms des modalités avec une taille proportionnelle à leur contribution à la construction de l’axe).

ggaxis_variables(mca, axis = 1, prop = "ctr")

ggaxis_variables(mca, axis = 2, prop = "ctr")

Toutefois, l’interprétation du plan factoriel, pour être robuste, ne peut s’arrêter à un examen visuel du nuage des modalités. Celui-ci doit être complété par l’analyse attentive d’indicateurs statistiques, en particulier des contributions des modalités à la construction des axes et de leur qualité de représentation.

Les variables d’écoute de musique classique et de jazz contribuent à elles seules pour plus de 60 % à la construction de l’axe 1. L’écoute de classique et de jazz s’oppose donc à leur non-écoute, et secondairement au goût pour les comédies.

tabcontrib(mca, dim = 1)
Variable Category Weight Quality of representation Contribution (left) Contribution (right) Total contribution Cumulated contribution Contribution of deviation Proportion to variable
Classical Yes 552 0.478 23.08 31.88 31.88 31.88 100
No 1443 0.474 8.8
Jazz Yes 364 0.467 25.49 31.15 63.04 31.15 100
No 1621 0.448 5.66
Comedy Yes 856 0.253 9.66 16.88 79.92 16.88 100
No 1141 0.252 7.22

Sur l’axe 2, l’écoute de rock et de rap et le goût pour les films de science-fiction s’opposent au goût pour les films d’amour et les comédies musicale et à l’écoute de variété française.

tabcontrib(mca, dim = 2)
Variable Category Weight Quality of representation Contribution (left) Contribution (right) Total contribution Cumulated contribution Contribution of deviation Proportion to variable
Rock Yes 535 0.229 12.99 17.77 17.77 17.77 100
No 1455 0.226 4.78
Love Yes 225 0.250 17.24 17.24 35 17.24 88.79
FrenchPop No 741 0.199 9.69 15.39 50.39 15.39 100
Yes 1249 0.196 5.69
Musical Yes 66 0.191 14.33 14.33 64.72 14.33 96.61
SciFi Yes 143 0.160 11.49 11.49 76.21 11.49 92.72
Rap Yes 261 0.164 11.05 11.05 87.27 11.05 86.56

On peut aller plus loin en étudiant la relation entre l’espace factoriel et les variables supplémentaires, en l’occurrence le sexe, l’âge et le niveau d’éducation. Une première étape consiste à projeter les variables supplémentaires sur le nuage des variables.

p <- ggcloud_variables(mca, shapes=FALSE, col="lightgray")
ggadd_supvars(p, mca, Taste[,c("Gender","Age","Educ")])

Le niveau d’éducation semble avant tout associé à l’axe 1, les plus diplômés étant du côté de l’écoute de jazz et de classique. Le sexe ne semble lié qu’à l’axe 2, avec les femmes dans le bas du plan et les hommes en haut. Quant à l’âge, il est associé aux deux axes : les individus se déplacent du quadrant nord-est au quadrant sud-ouest à mesure que leur âge augmente.

On peut confirmer statistiquement ces premières observations en mesurant le degré d’association entre les variables supplémentaires et les axes à l’aide du rapport de corrélation (eta²).

dimeta2(mca, Taste[,c("Gender","Age","Educ")])
##        dim.1 dim.2
## Gender   0.0   6.0
## Age      3.8  14.2
## Educ     5.9   3.3

Le niveau d’éducation est la variable supplémentaire la plus associée à l’axe 1 : il “explique” 5,9 % de la variance des coordonnées individuelles sur cet axe. L’âge est également associé au premier axe, mais de manière moins marquée, et le sexe pas du tout.

Sur l’axe 2, l’âge est la variable la plus structurante, devant le sexe et le niveau d’éducation. On voit en outre que l’âge est nettement plus lié à l’axe 2 qu’à l’axe 1.

Au niveau des modalités, on peut caractériser l’association d’une modalité de variable supplémentaire avec un axe à partir des coefficients de corrélation.

Sur l’axe 1, les non et peu diplômés et les 15-24 ans s’opposent aux plus diplômés et aux 50 ans et plus. Les autres modalités apparaissent peu liées à l’axe (leurs coefficients de corrélation, dans la dernière colonne du tableau, sont proches de 0).

des <- dimdescr(mca, vars = Taste[,c("Gender","Age","Educ")])
des$dim.1$categories
categories avg.coord.in.cat sd.coord.in.cat sd.coord.in.dim cor
Age.15-24 0.156 0.355 0.369 0.176
Educ.None 0.094 0.324 0.369 0.145
Educ.Low 0.052 0.355 0.369 0.100
Age.25-49 0.006 0.358 0.369 0.015
Gender.Women 0.004 0.370 0.369 0.012
Gender.Men -0.005 0.368 0.369 -0.012
Educ.Medium -0.040 0.376 0.369 -0.051
Age.50+ -0.061 0.369 0.369 -0.142
Educ.High -0.141 0.382 0.369 -0.213

Sur l’axe 2, les hommes, les moins de 50 ans et les plus diplômés s’opposent aux femmes, aux plus de 50 ans et aux sans diplôme.

des$dim.2$categories
categories avg.coord.in.cat sd.coord.in.cat sd.coord.in.dim cor
Age.15-24 0.236 0.320 0.342 0.288
Gender.Men 0.088 0.302 0.342 0.245
Age.25-49 0.049 0.333 0.342 0.123
Educ.High 0.076 0.327 0.342 0.123
Educ.Low 0.016 0.341 0.342 0.034
Educ.Medium 0.007 0.335 0.342 0.009
Educ.None -0.100 0.341 0.342 -0.167
Gender.Women -0.081 0.357 0.342 -0.245
Age.50+ -0.131 0.299 0.342 -0.330

Analyses inter-classes

On dispose d’une enquête post-électorale du Cevipof réalisée en 1997, et en particulier de 20 questions abordant des enjeux politiques, économiques et sociaux (voir l’annexe de Perrineau et al 2000 pour le détail des questions). Les réponses à ces questions permettent de dessiner un espace politique des électeurs français à la fin des années 1990. On souhaite étudier ce qui, dans cet espace politique, différencie les différents électorats, approchés par le vote au premier tour des élections législatives de 1997. On pourrait réaliser une ACM à partir des 20 questions traitées comme variables actives, puis projeter le vote en tant que variable supplémentaire sur l’espace factoriel. C’est l’option adoptée dans l’article mentionné plus haut. Toutefois, avec cette approche, l’espace politique est construit indépendamment du vote, avec qui il n’est mis en relation que dans un second temps. On peut donc préférer une “analyse factorielle inter-classes”, avec laquelle on construit l’espace politique qui différencie le mieux les électorats. Son premier axe est celui qui sépare (ou “explique”) le mieux les votes, le second axe explique le mieux ce qui ne l’a pas été par le premier, et ainsi de suite.

# les données sont disponibles dans le package "soc.ca"
data(political_space97, package = "soc.ca")
str(political_space97)
## tibble [2,980 × 22] (S3: tbl_df/tbl/data.frame)
##  $ quest             : chr [1:2980] "1" "3" "4" "11" ...
##  $ Immigrants        : num [1:2980] 2 4 2 2 4 4 1 4 2 4 ...
##  $ NorthAfricans     : num [1:2980] 4 2 2 3 1 3 2 1 2 1 ...
##  $ Races             : num [1:2980] 4 2 2 4 4 4 2 4 1 4 ...
##  $ AtHome            : num [1:2980] 4 4 2 3 4 4 1 4 2 4 ...
##  $ DeathPenalty      : num [1:2980] 4 4 2 2 4 3 1 4 1 4 ...
##  $ School            : num [1:2980] 2 2 1 2 2 1 2 2 1 2 ...
##  $ StrikeEffectivness: num [1:2980] 3 1 4 2 2 1 3 1 3 3 ...
##  $ Strike95          : num [1:2980] 4 2 3 5 2 3 2 4 3 2 ...
##  $ Unions            : num [1:2980] 3 1 2 3 2 2 3 2 3 2 ...
##  $ PublicServices    : num [1:2980] 2 1 3 3 1 2 3 1 4 1 ...
##  $ Liberalism        : num [1:2980] 2 4 3 2 2 2 3 3 2 2 ...
##  $ Profit            : num [1:2980] 2 2 2 2 2 4 3 3 2 2 ...
##  $ Privatization     : num [1:2980] 5 2 2 2 4 3 2 3 3 2 ...
##  $ Globalization     : num [1:2980] 2 3 2 3 3 2 2 3 3 2 ...
##  $ Democracy         : num [1:2980] 4 2 3 2 2 3 3 2 2 2 ...
##  $ Politicians       : num [1:2980] 3 2 2 3 2 3 3 2 3 3 ...
##  $ Euro              : num [1:2980] 4 2 2 2 2 3 3 2 3 2 ...
##  $ EUpower           : num [1:2980] 2 3 1 3 3 3 3 1 2 3 ...
##  $ EndEU             : num [1:2980] 3 1 1 1 2 2 2 1 2 1 ...
##  $ EUprotection      : num [1:2980] 4 2 2 1 3 3 1 2 2 2 ...
##  $ Vote              : chr [1:2980] "Socialist" "Socialist" "TraditionalRight" "TraditionalRight" ...
# codage des variables actives et du vote
actives <- as.data.frame(lapply(political_space97[,2:21], factor))
vote <- factor(political_space97$Vote)
# les non-réponses sont mises en "junk" (elles ne contribuent pas à l'analyse)
junk <- grep(".5", getindexcat(actives), value = TRUE, fixed = TRUE)
# analyse inter-classes
inter <- bcMCA(actives, vote, excl = junk)

Le premier axe est structuré avant tout par l’opinion sur les grèves de 1995, les syndicats et les privatisations, secondairement sur la peine de mort et l’accueil des immigrés.

barplot_contrib(inter, dim = 1, which = "col", repel = TRUE)

Ce premier axe correspond à une opposition gauche-droite entre partis traditionnels.

barplot_contrib(inter, dim = 1, which = "row", repel = TRUE)

Le second axe est structuré avant tout le rapport à la démocratie, à l’Union Européenne, ainsi qu’à l’immigration et à la peine de mort.

barplot_contrib(inter, dim = 2, which = "col", repel = TRUE)

Ce second axe “explique” quant à lui une opposition entre le Front National et la droite traditionnelle.

barplot_contrib(inter, dim = 2, which = "row", repel = TRUE)

On peut représenter ensemble l’espace politique et le vote.

factoextra::fviz_ca(inter,
                    invisible = "row.sup",
                    col.row = "#d7191c",
                    col.col = "#2c7bb6",
                    title = "Analyse inter-classes",
                    repel = TRUE)

L’approche par variables instrumentales constitue une forme de rapprochement entre l’analyse géométrique des données et les méthodes de régression. En pratique, il s’agit de construire un espace factoriel, à partir d’un premier groupe de variables actives, de manière à ce que cet espace “explique” au mieux un second ensemble de variables, dites variables instrumentales. Dans le cas général, il y a plusieurs variables instrumentales, qui peuvent être catégorielles et/ou numériques. Lorsque les variables actives sont toutes numériques, on parle d’Analyse en Composantes Principales sur Variables Instrumentales (fonction PCAiv()), et lorsque les variables actives sont toutes catégorielles, d’Analyse des Correspondances sur Variables Instrumentales (fonction MCAiv()). Si la variable instrumentale est unique et catégorielle, on peut dire qu’elle partitionne les individus en groupes ou “classes”. Il s’agit donc de faire une analyse inter-classes, en construisant un espace factoriel qui rend compte des différences entre classes (fonctions bcPCA() pour les cas où les variables actives sont continues et bcMCA() pour ceux où elles sont catégorielles). On parle aussi parfois d’analyse discriminante barycentrique ou d’analyse des correspondances discriminantes. Si les variables actives sont continues, une variante est l’analyse factorielle discriminante (AFD), dite aussi analyse discriminante descriptive (fonction DA()), qui est équivalente à l’analyse discriminante linéaire dans la littérature anglo-saxonne. Lorsque les variables actives sont catégorielles, une adaptation de l’AFD a été proposée par Gilbert Saporta (1977) sous le nom de Disqual (fonction DAQ()).

Régressions PLS

Les régressions PLS (Partial Least Squares) s’appliquent aux cas où l’on a une ou plusieurs variables à expliquer \(Y\) et un ensemble de variables explicatives \(X\), quels que soient leurs types (continus ou catégoriels). Elles permettent de construire l’espace des \(X\) qui explique au mieux l’espace des \(Y\). Elles constituent donc un compromis entre les régressions des \(Y\) en fonction des \(X\), l’ACP des \(X\) et l’ACP des \(Y\). Elles autorisent des usages diversifiés :

  • Orientées vers la régression, les régressions PLS permettent de traiter un grand nombre de variables explicatives sans problèmes d’estimation ou de multicolinéarité, et de faire de la sélection de variables.
  • Orientées vers l’analyse factorielle, elles s’appuient sur des espaces et une approche géométrique, relationnelle et multidimensionnelle.

Exemple 1 : la réussite scolaire des enfants immigrés

Pour illustrer le premier usage - orienté vers la régression, on s’appuie sur les données utilisées dans l’article de Jean-Paul Caille et Louis-André Vallet sur la réussite scolaire au collège des enfants étrangers ou issus de l’immigration (Caille et Vallet, 1995).

On cherche à expliquer la réussite scolaire par la nationalité et par d’autres caractéristiques sociales et familiales des élèves : le sexe, le rang dans la fratrie, la PCS de la personne de référence de la famille, le diplôme du père, celui de la mère, activité ou absence d’activité professionnelle de la mère, la taille de la famille, la structure familiale, présence d’un frère ou d’une sœur scolarisée en lycée ou dans l’enseignement supérieur. La réussite scolaire est approchée par le fait d’avoir reçu, quatre ans après l’entrée au collège, une proposition d’orientation en seconde générale ou technologique.

# chargement du jeu de données
load("/home/nicolas/Nextcloud/UVSQ/M1_StatsApplScSoc/ValletCaille.RData")
str(basereg)
## 'data.frame':    15806 obs. of  11 variables:
##  $ Seconde  : Factor w/ 2 levels "0","1": 2 1 1 2 1 1 1 2 2 1 ...
##  $ Natio2   : Factor w/ 2 levels "France","etranger": 2 1 1 1 2 2 2 1 1 1 ...
##  $ Pcsresp  : Factor w/ 8 levels "ouvr_qual","agriculteur",..: 1 4 7 4 8 1 7 5 3 1 ...
##  $ DiploP   : Factor w/ 5 levels "CAP-BEP-BEPC",..: 1 5 5 4 5 4 1 5 1 1 ...
##  $ DiploM   : Factor w/ 5 levels "CAP-BEP-BEPC",..: 1 5 3 1 5 4 2 5 1 1 ...
##  $ Mactiv   : Factor w/ 2 levels "non","oui": 2 2 2 2 NA 2 1 1 2 NA ...
##  $ Sexe     : Factor w/ 2 levels "1","2": 1 2 1 2 1 2 2 2 1 2 ...
##  $ Taillefam: Factor w/ 6 levels "2","1","3","4",..: 3 3 3 1 4 5 5 1 2 1 ...
##  $ Rang     : Factor w/ 4 levels "1","2","3","4 ou plus": 2 1 2 1 1 4 3 2 1 2 ...
##  $ FSetud   : Factor w/ 2 levels "non","oui": 2 1 1 1 1 2 1 1 1 2 ...
##  $ Structpar: Factor w/ 3 levels "biparentale",..: 1 1 2 1 1 1 1 1 1 1 ...

Les variables explicatives, qui sont ici toutes catégorielles, sont recodées sous forme dichotomique. La variable à expliquer étant binaire, on fait le choix de l’utiliser comme une variable numérique, de valeur 0 ou 1 (mais on aurait également pu la recoder sous la forme de deux variables dichotomiques).

# sélection et recodage des variables explicatives
X <- dichotom(basereg[,-1], out = "numeric")
# sélection et recodage de la variable à expliquer
Y <- as.numeric(basereg$Seconde=="1")
# X et Y dans un seul tableau de données
df <- data.frame(Seconde = I(as.matrix(Y)), X = I(as.matrix(X)))

On réalise une première régression PLS sans fixer le nombre de composantes (i.e. axes).

pls1 <- pls::mvr(Seconde ~ X,
                 data = df,
                 scale = TRUE,
                 validation = "CV",
                 jackknife = TRUE,
                 method = "oscorespls")

A titre exploratoire, on étudie les projections des variables dans le plan formé par les deux premières composantes. Au préalable, on interprète chacune des composantes à partir des “poids” (i.e. contributions) des variables explicatives dans leur construction.

La première composante est un axe de hiérarchie sociale : elle oppose les élèves dont les parents sont les plus diplômés et dont la personne de référence de la famille est cadre, aux élèves dont les parents sont les moins diplômés et dont la personne de référence de la famille est ouvrière.

plo_ctr(pls1, comp = 1)

La seconde composante oppose les élèves étrangers, de sexe féminin et/ou de mère inactive aux élèves français, de sexe masculin et/ou de mère active.

plo_ctr(pls1, comp = 2)

Dans le plan formé par ces deux composantes, on voit que la réussite scolaire se situe nettement du côté de la partie supérieure de la hiérarchie sociale, mais aussi, plus légèrement, du côté des élèves étrangers, de sexe féminin et/ou de mère inactive.

plo_var(pls1, comps = c(1,2)) +
  coord_fixed(ratio = 1)

Avant d’obtenir les résultats de la régression PLS sous forme de coefficients, on va déterminer le nombre optimal de composantes à retenir. Il existe plusieurs approches pour cela, notamment :

  • tests de permutation
  • erreur-type des résidus issus de la validation croisée
  • indicateur \(Q^2\) (on retient les composantes dont le \(Q^2 \ge 0.0975\))

Les trois approches ne suggérant pas le même nombre de composantes, on choisit la solution qui conserve le plus d’information, i.e. celle à 2 composantes. On a vu aussi que la variable de nationalité, dont l’interprétation nous intéresse, pèse dans la construction du second axe.

# test de permutation
selectNcomp(pls1, "randomization")
## [1] 2
# validation croisée
selectNcomp(pls1, "onesigma")
## [1] 1
# Q2 des composantes
q2 <- get_Q2(pls1)$Q2h
which(q2>=0.0975)
## 1 comps 
##       1

On peut ensuite obtenir les résultats de la régression sous forme de coefficients, p-values et intervalles de confiance (calculés avec une procédure jackknife). Les coefficients permettent de comparer la force des effets des variables et leur signe. Cependant, ils correspondent aux coefficients des variables explicatives centrées et réduites, leur valeur in abstracto n’a donc pas de signification.

get_coef(pls1, ncomp = 2)
coefficients std error t-value p-value 2.5% 97.5%
Natio2.France -0.00864 0.00136 -6.332 0.00014 -0.01131 -0.00596
Natio2.etranger 0.00864 0.00136 6.332 0.00014 0.00596 0.01131
Pcsresp.ouvr_qual -0.02890 0.00163 -17.729 0.00000 -0.03209 -0.02570
Pcsresp.agriculteur 0.00548 0.00171 3.201 0.01081 0.00212 0.00884
Pcsresp.artcom -0.00805 0.00212 -3.801 0.00421 -0.01219 -0.00390
Pcsresp.cadre 0.03991 0.00239 16.714 0.00000 0.03523 0.04459
Pcsresp.prof_int 0.01991 0.00196 10.149 0.00000 0.01606 0.02375
Pcsresp.empl -0.01012 0.00188 -5.375 0.00045 -0.01381 -0.00643
Pcsresp.ouvr_nqual -0.01978 0.00191 -10.332 0.00000 -0.02353 -0.01603
Pcsresp.inactif -0.00927 0.00117 -7.934 0.00002 -0.01155 -0.00698
DiploP.CAP-BEP-BEPC -0.00079 0.00270 -0.291 0.77732 -0.00609 0.00451
DiploP.aucun -0.02709 0.00187 -14.509 0.00000 -0.03074 -0.02343
DiploP.CEP -0.01585 0.00217 -7.289 0.00005 -0.02011 -0.01159
DiploP.bac ou plus 0.04703 0.00169 27.878 0.00000 0.04372 0.05034
DiploP.inconnu -0.01812 0.00330 -5.495 0.00038 -0.02458 -0.01166
DiploM.CAP-BEP-BEPC 0.00129 0.00161 0.798 0.44529 -0.00187 0.00444
DiploM.aucun -0.03066 0.00285 -10.770 0.00000 -0.03624 -0.02508
DiploM.CEP -0.02785 0.00256 -10.861 0.00000 -0.03287 -0.02282
DiploM.bac ou plus 0.05532 0.00141 39.318 0.00000 0.05256 0.05808
DiploM.inconnu -0.01408 0.00242 -5.828 0.00025 -0.01882 -0.00935
Mactiv.non 0.00404 0.00252 1.602 0.14355 -0.00090 0.00897
Mactiv.oui 0.00452 0.00301 1.499 0.16814 -0.00139 0.01042
Sexe.1 -0.03666 0.00125 -29.425 0.00000 -0.03910 -0.03422
Sexe.2 0.03666 0.00125 29.425 0.00000 0.03422 0.03910
Taillefam.2 0.00964 0.00213 4.525 0.00144 0.00546 0.01382
Taillefam.1 0.00324 0.00194 1.669 0.12944 -0.00056 0.00704
Taillefam.3 -0.00200 0.00202 -0.988 0.34910 -0.00595 0.00196
Taillefam.4 -0.01044 0.00199 -5.238 0.00054 -0.01435 -0.00653
Taillefam.5-7 -0.00930 0.00255 -3.649 0.00533 -0.01430 -0.00430
Taillefam.8 et plus 0.00064 0.00220 0.291 0.77762 -0.00367 0.00495
Rang.1 0.00966 0.00138 6.982 0.00006 0.00694 0.01237
Rang.2 -0.00877 0.00215 -4.079 0.00276 -0.01299 -0.00456
Rang.3 0.00156 0.00309 0.504 0.62657 -0.00450 0.00761
Rang.4 ou plus -0.00569 0.00197 -2.890 0.01789 -0.00956 -0.00183
FSetud.non -0.01689 0.00103 -16.360 0.00000 -0.01891 -0.01486
FSetud.oui 0.01689 0.00103 16.360 0.00000 0.01486 0.01891
Structpar.biparentale 0.01171 0.00111 10.513 0.00000 0.00953 0.01389
Structpar.monoparentale -0.00915 0.00108 -8.475 0.00001 -0.01127 -0.00704
Structpar.autre -0.00775 0.00228 -3.407 0.00779 -0.01221 -0.00329

On peut aussi les représenter sous forme graphique. On constate notamment que la nationalité étrangère est un facteur favorable à la réussite scolaire, même s’il pèse beaucoup moins fortement que le sexe ou la position sociale des parents.

plo_coef(pls1, ncomp = 2, max.pval = 0.05, whiskers = TRUE, ci = 0.95)

On peut également obtenir les coefficients “bruts”, i.e. non standardisés : ils sont exprimés dans leur unité de mesure d’origine. Cela facilite l’interprétation de l’effet d’une variable sur la variable à expliquer, mais ne permet pas la comparaison de la force des effets.

get_coef(pls1, ncomp = 2, raw = TRUE)
coefficients std error t-value p-value 2.5% 97.5%
Natio2.France -0.03916 0.00618 -6.332 0.00014 -0.05128 -0.02704
Natio2.etranger 0.03916 0.00618 6.332 0.00014 0.02704 0.05128
Pcsresp.ouvr_qual -0.06629 0.00374 -17.729 0.00000 -0.07362 -0.05896
Pcsresp.agriculteur 0.02929 0.00915 3.201 0.01081 0.01136 0.04723
Pcsresp.artcom -0.03039 0.00799 -3.801 0.00421 -0.04605 -0.01472
Pcsresp.cadre 0.10242 0.00613 16.714 0.00000 0.09041 0.11443
Pcsresp.prof_int 0.04830 0.00476 10.149 0.00000 0.03897 0.05762
Pcsresp.empl -0.03047 0.00567 -5.375 0.00045 -0.04158 -0.01936
Pcsresp.ouvr_nqual -0.07132 0.00690 -10.332 0.00000 -0.08485 -0.05779
Pcsresp.inactif -0.06835 0.00861 -7.934 0.00002 -0.08523 -0.05146
DiploP.CAP-BEP-BEPC -0.00169 0.00580 -0.291 0.77732 -0.01305 0.00967
DiploP.aucun -0.08614 0.00594 -14.509 0.00000 -0.09778 -0.07451
DiploP.CEP -0.04484 0.00615 -7.289 0.00005 -0.05690 -0.03278
DiploP.bac ou plus 0.10558 0.00379 27.878 0.00000 0.09816 0.11300
DiploP.inconnu -0.05078 0.00924 -5.495 0.00038 -0.06890 -0.03267
DiploM.CAP-BEP-BEPC 0.00278 0.00348 0.798 0.44529 -0.00405 0.00961
DiploM.aucun -0.08996 0.00835 -10.770 0.00000 -0.10633 -0.07359
DiploM.CEP -0.07029 0.00647 -10.861 0.00000 -0.08298 -0.05761
DiploM.bac ou plus 0.12351 0.00314 39.318 0.00000 0.11735 0.12967
DiploM.inconnu -0.05096 0.00874 -5.828 0.00025 -0.06810 -0.03382
Mactiv.non 0.00847 0.00529 1.602 0.14355 -0.00189 0.01884
Mactiv.oui 0.00921 0.00615 1.499 0.16814 -0.00283 0.02126
Sexe.1 -0.07332 0.00249 -29.425 0.00000 -0.07820 -0.06843
Sexe.2 0.07332 0.00249 29.425 0.00000 0.06843 0.07820
Taillefam.2 0.01941 0.00429 4.525 0.00144 0.01100 0.02782
Taillefam.1 0.00992 0.00594 1.669 0.12944 -0.00173 0.02157
Taillefam.3 -0.00438 0.00444 -0.988 0.34910 -0.01308 0.00431
Taillefam.4 -0.03739 0.00714 -5.238 0.00054 -0.05139 -0.02340
Taillefam.5-7 -0.04144 0.01136 -3.649 0.00533 -0.06370 -0.01918
Taillefam.8 et plus 0.00919 0.03159 0.291 0.77762 -0.05272 0.07110
Rang.1 0.01931 0.00277 6.982 0.00006 0.01389 0.02473
Rang.2 -0.01839 0.00451 -4.079 0.00276 -0.02723 -0.00955
Rang.3 0.00503 0.00998 0.504 0.62657 -0.01453 0.02458
Rang.4 ou plus -0.02740 0.00948 -2.890 0.01789 -0.04599 -0.00882
FSetud.non -0.03881 0.00237 -16.360 0.00000 -0.04346 -0.03416
FSetud.oui 0.03881 0.00237 16.360 0.00000 0.03416 0.04346
Structpar.biparentale 0.03794 0.00361 10.513 0.00000 0.03087 0.04502
Structpar.monoparentale -0.03202 0.00378 -8.475 0.00001 -0.03943 -0.02461
Structpar.autre -0.06027 0.01769 -3.407 0.00779 -0.09495 -0.02560

A noter : si l’on souhaite sélectionner les principales variables explicatives du modèles, on peut s’appuyer sur les coefficients de la régression et les p-values associées. Mais une autre approche consiste à utiliser les VIP (Variable Importance in the Prediction), qui mesurent l’importance des variables explicatives dans la prédiction des variables à expliquer. Par convention, on peut considérer une variable comme importante si son \(VIP > 0.8\) ou \(>1\), selon les recommandations. Cela nous amènerait ici à ne conserver que 19 ou même 10 des 39 variables explicatives.

plsVarSel::VIP(pls1, opt.comp = 2) |>sort() |> rev() |> round(3)
##      DiploM.bac ou plus      DiploP.bac ou plus           Pcsresp.cadre 
##                   2.516                   2.437                   2.007 
##            DiploM.aucun       Pcsresp.ouvr_qual            DiploP.aucun 
##                   1.453                   1.438                   1.304 
##                  Sexe.1                  Sexe.2              DiploM.CEP 
##                   1.292                   1.292                   1.210 
##      Pcsresp.ouvr_nqual        Pcsresp.prof_int          DiploP.inconnu 
##                   1.013                   0.961                   0.958 
##              Mactiv.oui              FSetud.non              FSetud.oui 
##                   0.890                   0.869                   0.869 
##           Natio2.France         Natio2.etranger              DiploP.CEP 
##                   0.841                   0.841                   0.830 
##             Taillefam.2           Taillefam.5-7          DiploM.inconnu 
##                   0.770                   0.749                   0.720 
##              Mactiv.non   Structpar.biparentale          Rang.4 ou plus 
##                   0.719                   0.671                   0.597 
## Structpar.monoparentale             Taillefam.4         Pcsresp.inactif 
##                   0.555                   0.528                   0.510 
##            Pcsresp.empl                  Rang.2         Structpar.autre 
##                   0.467                   0.426                   0.379 
##                  Rang.1          Pcsresp.artcom     Taillefam.8 et plus 
##                   0.317                   0.281                   0.275 
##     Pcsresp.agriculteur                  Rang.3             Taillefam.1 
##                   0.182                   0.162                   0.148 
##     DiploM.CAP-BEP-BEPC     DiploP.CAP-BEP-BEPC             Taillefam.3 
##                   0.104                   0.096                   0.094
plo_vip(pls1, ncomp = 2)

Exemple 2 : les loisirs

Pour le deuxième exemple, on utilise les données issues de l’enquête “Histoires de vie” (Insee, 2003) disponibles dans le package questionr.

On va chercher à expliquer les pratiques de loisirs par les propriétés sociales des enquêtés.

  • 10 variables de loisirs : hard-rock, lecture de BD, pêche et chasse, cuisine, bricolage, cinéma, sport et télévision
  • 6 variables de propriétés sociales : sexe, âge, niveau d’études, statut d’emploi, qualification, religiosité

On recode toutes les variables sous forme dichotomique. On a préalablement discrétisé l’âge et la consommation de télévision, pour des raisons d’interprétation, mais, du point de vue technique, cela n’était pas nécessaire.

# chargement du jeu de données
data(hdv2003, package = "questionr")
# sélection et recodage des variables explicatives
X <- select(hdv2003, age, sexe, nivetud, occup, qualif, relig) %>%
     mutate(age = Hmisc::cut2(age, cuts = c(18,26,36,51,66))) %>%  # discrétisation de l'âge
     dichotom(out = "numeric")  # dichotomisation
# sélection et recodage des variables à expliquer
Y <- select(hdv2003, hard.rock:heures.tv) %>%
     mutate(heures.tv = Hmisc::cut2(heures.tv, g = 4) %>% 
                        factor(labels = as.character(1:4))) %>%  # discrétisation de la consommation de tv
     dichotom(out = "numeric") %>%  # dichotomisation
     lapply(scale) %>%  # standardisation
     as.data.frame()
# X et Y dans un seul tableau de données
df <- data.frame(Y = I(as.matrix(Y)), X = I(as.matrix(X)))

On réalise une régression PLS, en ne conservant que les 5 premières composantes.

pls2 <- pls::mvr(Y ~ X, data = df,
                 ncomp = 5,
                 scale = TRUE,
                 validation = "CV",
                 jackknife = TRUE,
                 method = "oscorespls",
                 maxit = 1000)

La projection des variables explicatives sur le plan formé par les deux premières composantes donne un premier regard sur les propriétés sociales les plus structurantes.

plo_var(pls2, which = "X")

Mais l’examen des contributions permet une interprétation plus précise pour chacune des composantes. La première oppose les plus de 50 ans, retraités, peu diplômés et/ou ouvriers, aux étudiants et actifs de moins de 50 ans, diplômés du supérieur et /ou cadres. Elle semble donc à la fois structurée par l’âge et la hiérarchie sociale.

plo_ctr(pls2, comp = 1)

Le second axe traduit quant à lui avant tout une opposition sexuée.

plo_ctr(pls2, comp = 2)

A partir de la projection des variables à expliquer sur le plan formé par les deux premières composantes, on constate que :

  • les loisirs des personnes âgées ou situées en bas de la hiérarchie sociale se limitent à une consommation importante de télévision ;
  • la pratique du sport et du cinéma, associée à une faible consommation de télévision, sont associées aux moins de 50 ans et aux individus situés en haut de la hiérarchie sociale ;
  • la seconde composante oppose la cuisine (loisir féminin) au bricolage, la pêche et la chasse (loisirs masculins).
plo_var(pls2, which = "Y")

L’examen des “redondances” permet de vérifier quelles variables de Y sont les mieux expliquées par les composantes 1 et 2 : ce sont bien celles que l’on avait identifiées à partir de la projection des variables.

redondances <- get_red(pls2)
round(redondances$Yt,3)[,1:2]
##                     t1    t2
## Rd               0.061 0.024
## hard.rock.Non    0.004 0.000
## hard.rock.Oui    0.004 0.000
## lecture.bd.Non   0.013 0.004
## lecture.bd.Oui   0.013 0.004
## peche.chasse.Non 0.000 0.058
## peche.chasse.Oui 0.000 0.058
## cuisine.Non      0.003 0.059
## cuisine.Oui      0.003 0.059
## bricol.Non       0.048 0.075
## bricol.Oui       0.048 0.075
## cinema.Non       0.251 0.021
## cinema.Oui       0.251 0.021
## sport.Non        0.155 0.000
## sport.Oui        0.155 0.000
## heures.tv.1      0.047 0.001
## heures.tv.2      0.013 0.001
## heures.tv.3      0.010 0.001
## heures.tv.4      0.087 0.001

Analyses conditionnelles

Les analyses conditionnelles (ou intra-classes) constituent une autre tentative d’intégration de l’analyse géométrique des données et de la régression. Leur principe consiste à contraindre les axes de l’ACM à être indépendants (i.e. orthogonaux) d’une ou plusieurs variables supplémentaires, c’est-à-dire à construire une ACM “toute chose (de ces variables supplémentaires) égale par ailleurs” (Bry et al, 2016). La comparaison des résultats de l’ACM originelle et de ceux de l’ACM conditionnelle est un moyen d’étudier les effets de structure.

Dans le cas général, il y a plusieurs variables dont il faut “éliminer l’effet” et celles-ci sont parfois appelées variables instrumentales orthogonales. On réalisera donc une Analyse en Composantes Principales sur Variables Instrumentales Orthogonales si les variables actives sont toutes numériques (fonction PCAoiv()) et une Analyse des Correspondances sur Variables Instrumentales Orthogonales si les variables actives sont toutes catégorielles (fonction MCAoiv()).

Comme pour les analyses discriminantes (ou inter-classes), si la variable instrumentale orthogonale est unique et catégorielle, elle partitionne les individus en “classes”. Mais on est cette fois dans le cadre d’une analyse intra-classes : il s’agit de construire l’espace factoriel rendant compte des structures communes aux différentes classes.

Lorsque les variables actives sont catégorielles, on réalise une ACM conditionnelle (Escofier, 1990 et fonction wcMCA()). L’approche conditionnelle s’applique aussi au cas de variables actives numériques (fonction wcPCA()).

On construit maintenant l’espace des goûts communs aux femmes et aux hommes, ou, dit autrement, en neutralisant les différences sexuées.

junk <- c("FrenchPop.NA", "Rap.NA", "Rock.NA", "Jazz.NA", "Classical.NA",
          "Comedy.NA", "Crime.NA", "Animation.NA", "SciFi.NA", "Love.NA", 
          "Musical.NA")
intra <- wcMCA(Taste[,1:11], Taste$Gender, excl = junk)

Dans le plan (1,2), les résultats obtenus sont très proches de ceux de l’ACM spécifique. La principale différence concerne le goût (très féminin) pour les films d’amour, qui se distingue nettement moins sur l’axe 2.

ggcloud_variables(intra, legend = "none") + 
  ggtitle("Analyse intra-classes")

Cela se confirme à l’examen précis des contributions : le goût pour les films d’amour ne contribue qu’à hauteur de 6% à la construction de l’axe 2, contre 17% pour l’ACM.

tabcontrib(intra, dim = 2)
Variable Category Weight Quality of representation Contribution (left) Contribution (right) Total contribution Cumulated contribution Contribution of deviation Proportion to variable
Rock Yes 535 0.290 17.36 23.76 23.76 23.76 100
No 1455 0.287 6.39
SciFi Yes 143 0.214 16.25 16.25 40.01 16.25 92.76
FrenchPop No 741 0.176 9.07 14.41 54.42 14.41 100
Yes 1249 0.174 5.35
Rap Yes 261 0.171 12.14 12.14 66.56 12.14 86.63
Musical Yes 66 0.137 10.84 10.84 77.4 10.84 96.66
Animation Yes 91 0.096 7.52 7.52 84.91 7.52 95.2
Love Yes 225 0.083 6.04 6.04 90.95 6.04 88.87
LS0tCnRpdGxlOiAiTGVzIGFuYWx5c2VzIGZhY3RvcmllbGxlcyBjb250cmFpbnRlcyIKYXV0aG9yOiBOaWNvbGFzIFJvYmV0dGUKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgcm1kZm9ybWF0czo6cm9ib2Jvb2s6CiAgICBoaWdobGlnaHQ6IGthdGUKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQpzZWxmX2NvbnRhaW5lZDogeWVzCm1vZGU6IHNlbGZjb250YWluZWQKIyBvdXRwdXQ6IGRpc3RpbGw6OmRpc3RpbGxfYXJ0aWNsZQotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBjYWNoZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UpCmBgYAoKYGBge3Iga2xpcHB5LCBlY2hvPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmtsaXBweTo6a2xpcHB5KHBvc2l0aW9uID0gInJpZ2h0IikKYGBgCgo8YnIgLz4KCj4gTCdleMOpY3V0aW9uIGRlIGNlIHR1dG9yaWVsIG7DqWNlc3NpdGUgYXUgcHLDqWFsYWJsZSBsJ2luc3RhbGxhdGlvbiAob3UgbGEgbWlzZSDDoCBqb3VyKSBkZXMgcGFja2FnZXMgc3VpdmFudHMgOiBgR0RBdG9vbHNgey5wa2d9IGV0IGBmYWN0b2V4dHJhYHsucGtnfSAoYW5hbHlzZXMgZmFjdG9yaWVsbGVzKSwgYHBsc2B7LnBrZ30sIGBtb3JlcGxzYHsucGtnfSBldCBgcGxzVmFyU2VsYHsucGtnfSAocsOpZ3Jlc3Npb25zIFBMUyksIGxlcyBoYWJpdHVlbHMgYGdncGxvdDJgey5wa2d9IGV0IGBkcGx5cmB7LnBrZ30sIGFpbnNpIHF1ZSBgc29jLmNhYHsucGtnfSBldCBgcXVlc3Rpb25yYHsucGtnfSBkb250IG9uIHZhIHV0aWxpc2VyIGRlcyBqZXV4IGRlIGRvbm7DqWVzLgoKPGJyIC8+CgojIFByZW1pZXJzIHBhcwoKVG91dCBkJ2Fib3JkLCBvbiBjaGFyZ2UgbGVzIHBhY2thZ2VzIG7DqWNlc3NhaXJlcyBlbiBtw6ltb2lyZS4KCmBgYHtyIGluaXQsIGNhY2hlPUZBTFNFfQpsaWJyYXJ5KEdEQXRvb2xzKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocGxzKQpsaWJyYXJ5KG1vcmVwbHMpCmBgYAoKCgojIFZhcmlhYmxlcyBzdXBwbMOpbWVudGFpcmVzCgpQb3VyIGNlIHByZW1pZXIgZXhlbXBsZSwgbm91cyBhbGxvbnMgdXRpbGlzZXIgbCd1biBkZXMgamV1eCBkZSBkb25uw6llcyBmb3VybmlzIGF2ZWMgbGUgcGFja2FnZSBgR0RBdG9vbHNgLiBJbCBzJ2FnaXQgZCdpbmZvcm1hdGlvbnMgc3VyIGxlcyBnb8O7dHMgZXQgbGVzIHByYXRpcXVlcyBjdWx0dXJlbGxlcyBkZSAyMDAwIGluZGl2aWR1cyA6IMOpY291dGUgZGUgZ2VucmVzIG11c2ljYXV4ICh2YXJpw6l0w6kgZnJhbsOnYWlzZSwgcmFwLCByb2NrLCBqYXp6IGV0IGNsYXNzaXF1ZSkgZXQgZ2/Du3QgcG91ciBkZXMgZ2VucmVzIGRlIGZpbG1zIChjb23DqWRpZSwgZmlsbSBwb2xpY2llciwgYW5pbWF0aW9uLCBzY2llbmNlLWZpY3Rpb24sIGZpbG0gZCdhbW91ciwgY29tw6lkaWUgbXVzaWNhbGUpLiBDZXMgMTEgdmFyaWFibGVzIHNlcnZpcm9udCBkZSB2YXJpYWJsZXMgImFjdGl2ZXMiIGRhbnMgdW5lIEFDTSBldCBzZXJvbnQgY29tcGzDqXTDqWVzIHBhciAzIHZhcmlhYmxlcyAic3VwcGzDqW1lbnRhaXJlcyIgOiBsZSBzZXhlLCBsJ8OiZ2UgZXQgbGUgbml2ZWF1IGQnw6lkdWNhdGlvbi4KCmBgYHtyIGFjbV9pbml0fQpkYXRhKFRhc3RlKQpzdHIoVGFzdGUpCmBgYAoKTGVzIHZhcmlhYmxlcyBhY3RpdmVzIG9udCB0b3V0ZXMgdW5lIG1vZGFsaXTDqSAibm9uLXLDqXBvbnNlIiAoIk5BIiksIHF1aSBjb25jZXJuZSBxdWVscXVlcyBpbmRpdmlkdXMuIEwnKkFDTSBzcMOpY2lmaXF1ZSogcGVybWV0IGRlIG5ldXRyYWxpc2VyIGNlcyBtb2RhbGl0w6lzIGRhbnMgbGEgY29uc3RydWN0aW9uIGRlIGwnZXNwYWNlIGZhY3RvcmllbCwgdG91dCBlbiBjb25zZXJ2YW50IGwnZW5zZW1ibGUgZGVzIGluZGl2aWR1cy4KCmBgYHtyIGFjbV9hY219Cmp1bmsgPC0gYygiRnJlbmNoUG9wLk5BIiwgIlJhcC5OQSIsICJSb2NrLk5BIiwgIkphenouTkEiLCAiQ2xhc3NpY2FsLk5BIiwKICAgICAgICAgICJDb21lZHkuTkEiLCAiQ3JpbWUuTkEiLCAiQW5pbWF0aW9uLk5BIiwgIlNjaUZpLk5BIiwgIkxvdmUuTkEiLCAKICAgICAgICAgICJNdXNpY2FsLk5BIikKbWNhIDwtIHNwZU1DQShUYXN0ZVssMToxMV0sIGV4Y2wgPSBqdW5rKQpgYGAKCk9uIHJlcHLDqXNlbnRlIGVuc3VpdGUgbGUgbnVhZ2UgZGVzIG1vZGFsaXTDqXMuIFN1ciBjZSBudWFnZSA6CgotIGwnw6ljb3V0ZSBkZSBqYXp6IGV0IGRlIG11c2lxdWUgY2xhc3NpcXVlIGV0IGxlIGdvw7t0IGRlcyBjb23DqWRpZXMgbXVzaWNhbGVzIHNlbWJsZW50IHMnb3Bwb3NlciDDoCBsJ8OpY291dGUgZGUgcmFwIGV0IGF1IGdvw7t0IHBvdXIgbGVzIGNvbcOpZGllcyBzdXIgbCdheGUgMSA7Ci0gbGUgZ2/Du3QgcG91ciBsJ2FuaW1hdGlvbiBldCBsYSBzY2llbmNlLWZpY3Rpb24gw6AgY2VsdWkgcG91ciBsZXMgZmlsbXMgZCdhbW91ciBldCBsZXMgY29tw6lkaWVzIG11c2ljYWxlcyBzdXIgbCdheGUgMi4KCmBgYHtyIGFjbV9wbG90dmFyfQpnZ2Nsb3VkX3ZhcmlhYmxlcyhtY2EsIHNoYXBlcyA9IEZBTFNFLCBsZWdlbmQgPSAibm9uZSIpCmBgYAoKT24gcmVwcsOpc2VudGUgdHJhZGl0aW9ubmVsbGVtZW50IGxlcyByw6lzdWx0YXRzIGRhbnMgdW4gcGxhbiwgYydlc3Qtw6AtZGlyZSBlbiBkZXV4IGRpbWVuc2lvbnMgKGljaSBsZXMgZGltZW5zaW9ucyAxIGV0IDIpLiBNYWlzIGlsIGVzdCBwYXJmb2lzIHBsdXMgc2ltcGxlIGRlIGNvbmNlbnRyZXIgbCdpbnRlcnByw6l0YXRpb24gc3VyIHVuIHNldWwgYXhlIMOgIGxhIGZvaXMsIGNlIHF1aSBlc3QgcG9zc2libGUgYXZlYyBsYSBmb25jdGlvbmAgZ2dheGlzX3ZhcmlhYmxlcygpYCAocGFyYW3DqXRyw6llIGljaSBwb3VyIGFmZmljaGVyIGxlcyBub21zIGRlcyBtb2RhbGl0w6lzIGF2ZWMgdW5lIHRhaWxsZSBwcm9wb3J0aW9ubmVsbGUgw6AgbGV1ciBjb250cmlidXRpb24gw6AgbGEgY29uc3RydWN0aW9uIGRlIGwnYXhlKS4KCmBgYHtyIGFjbV9heGlzMX0KZ2dheGlzX3ZhcmlhYmxlcyhtY2EsIGF4aXMgPSAxLCBwcm9wID0gImN0ciIpCmBgYAoKYGBge3IgYWNtX2F4aXMyfQpnZ2F4aXNfdmFyaWFibGVzKG1jYSwgYXhpcyA9IDIsIHByb3AgPSAiY3RyIikKYGBgCgpUb3V0ZWZvaXMsIGwnaW50ZXJwcsOpdGF0aW9uIGR1IHBsYW4gZmFjdG9yaWVsLCBwb3VyIMOqdHJlIHJvYnVzdGUsIG5lIHBldXQgcydhcnLDqnRlciDDoCB1biBleGFtZW4gdmlzdWVsIGR1IG51YWdlIGRlcyBtb2RhbGl0w6lzLiBDZWx1aS1jaSBkb2l0IMOqdHJlIGNvbXBsw6l0w6kgcGFyIGwnYW5hbHlzZSBhdHRlbnRpdmUgZCdpbmRpY2F0ZXVycyBzdGF0aXN0aXF1ZXMsIGVuIHBhcnRpY3VsaWVyIGRlcyBjb250cmlidXRpb25zIGRlcyBtb2RhbGl0w6lzIMOgIGxhIGNvbnN0cnVjdGlvbiBkZXMgYXhlcyBldCBkZSBsZXVyIHF1YWxpdMOpIGRlIHJlcHLDqXNlbnRhdGlvbi4KCkxlcyB2YXJpYWJsZXMgZCfDqWNvdXRlIGRlIG11c2lxdWUgY2xhc3NpcXVlIGV0IGRlIGphenogY29udHJpYnVlbnQgw6AgZWxsZXMgc2V1bGVzIHBvdXIgcGx1cyBkZSA2MCAlIMOgIGxhIGNvbnN0cnVjdGlvbiBkZSBsJ2F4ZSAxLiBMJ8OpY291dGUgZGUgY2xhc3NpcXVlIGV0IGRlIGphenogcydvcHBvc2UgZG9uYyDDoCBsZXVyIG5vbi3DqWNvdXRlLCBldCBzZWNvbmRhaXJlbWVudCBhdSBnb8O7dCBwb3VyIGxlcyBjb23DqWRpZXMuCgpgYGB7ciBhY21fdGFiMSwgZXZhbD1GQUxTRX0KdGFiY29udHJpYihtY2EsIGRpbSA9IDEpCmBgYAoKYGBge3IgYWNtX3RhYjFiaXMsIGVjaG89RkFMU0V9CmtuaXRyOjprYWJsZSh0YWJjb250cmliKG1jYSwgZGltID0gMSksIHJvdy5uYW1lcz1GQUxTRSkKYGBgCgpTdXIgbCdheGUgMiwgbCfDqWNvdXRlIGRlIHJvY2sgZXQgZGUgcmFwIGV0IGxlIGdvw7t0IHBvdXIgbGVzIGZpbG1zIGRlIHNjaWVuY2UtZmljdGlvbiBzJ29wcG9zZW50IGF1IGdvw7t0IHBvdXIgbGVzIGZpbG1zIGQnYW1vdXIgZXQgbGVzIGNvbcOpZGllcyBtdXNpY2FsZSBldCDDoCBsJ8OpY291dGUgZGUgdmFyacOpdMOpIGZyYW7Dp2Fpc2UuCgpgYGB7ciBhY21fdGFiMiwgZXZhbD1GQUxTRX0KdGFiY29udHJpYihtY2EsIGRpbSA9IDIpCmBgYAoKYGBge3IgYWNtX3RhYjJiaXMsIGVjaG89RkFMU0V9CmtuaXRyOjprYWJsZSh0YWJjb250cmliKG1jYSwgZGltPTIpLCByb3cubmFtZXM9RkFMU0UpCmBgYAoKT24gcGV1dCBhbGxlciBwbHVzIGxvaW4gZW4gw6l0dWRpYW50IGxhIHJlbGF0aW9uIGVudHJlIGwnZXNwYWNlIGZhY3RvcmllbCBldCBsZXMgdmFyaWFibGVzIHN1cHBsw6ltZW50YWlyZXMsIGVuIGwnb2NjdXJyZW5jZSBsZSBzZXhlLCBsJ8OiZ2UgZXQgbGUgbml2ZWF1IGQnw6lkdWNhdGlvbi4gVW5lIHByZW1pw6hyZSDDqXRhcGUgY29uc2lzdGUgw6AgcHJvamV0ZXIgbGVzIHZhcmlhYmxlcyBzdXBwbMOpbWVudGFpcmVzIHN1ciBsZSBudWFnZSBkZXMgdmFyaWFibGVzLgoKYGBge3IgYWNtX3Bsb3R2YXJzdXB9CnAgPC0gZ2djbG91ZF92YXJpYWJsZXMobWNhLCBzaGFwZXM9RkFMU0UsIGNvbD0ibGlnaHRncmF5IikKZ2dhZGRfc3VwdmFycyhwLCBtY2EsIFRhc3RlWyxjKCJHZW5kZXIiLCJBZ2UiLCJFZHVjIildKQpgYGAKCkxlIG5pdmVhdSBkJ8OpZHVjYXRpb24gc2VtYmxlIGF2YW50IHRvdXQgYXNzb2Npw6kgw6AgbCdheGUgMSwgbGVzIHBsdXMgZGlwbMO0bcOpcyDDqXRhbnQgZHUgY8O0dMOpIGRlIGwnw6ljb3V0ZSBkZSBqYXp6IGV0IGRlIGNsYXNzaXF1ZS4gTGUgc2V4ZSBuZSBzZW1ibGUgbGnDqSBxdSfDoCBsJ2F4ZSAyLCBhdmVjIGxlcyBmZW1tZXMgZGFucyBsZSBiYXMgZHUgcGxhbiBldCBsZXMgaG9tbWVzIGVuIGhhdXQuIFF1YW50IMOgIGwnw6JnZSwgaWwgZXN0IGFzc29jacOpIGF1eCBkZXV4IGF4ZXMgOiBsZXMgaW5kaXZpZHVzIHNlIGTDqXBsYWNlbnQgZHUgcXVhZHJhbnQgbm9yZC1lc3QgYXUgcXVhZHJhbnQgc3VkLW91ZXN0IMOgIG1lc3VyZSBxdWUgbGV1ciDDomdlIGF1Z21lbnRlLgoKT24gcGV1dCBjb25maXJtZXIgc3RhdGlzdGlxdWVtZW50IGNlcyBwcmVtacOocmVzIG9ic2VydmF0aW9ucyBlbiBtZXN1cmFudCBsZSBkZWdyw6kgZCdhc3NvY2lhdGlvbiBlbnRyZSBsZXMgdmFyaWFibGVzIHN1cHBsw6ltZW50YWlyZXMgZXQgbGVzIGF4ZXMgw6AgbCdhaWRlIGR1ICoqcmFwcG9ydCBkZSBjb3Jyw6lsYXRpb24qKiAoKipldGHCsioqKS4KCmBgYHtyIGFjbV9kaW1ldGEyfQpkaW1ldGEyKG1jYSwgVGFzdGVbLGMoIkdlbmRlciIsIkFnZSIsIkVkdWMiKV0pCmBgYAoKTGUgbml2ZWF1IGQnw6lkdWNhdGlvbiBlc3QgbGEgdmFyaWFibGUgc3VwcGzDqW1lbnRhaXJlIGxhIHBsdXMgYXNzb2Npw6llIMOgIGwnYXhlwqAxIDogaWwgImV4cGxpcXVlIiA1LDnCoCUgZGUgbGEgdmFyaWFuY2UgZGVzIGNvb3Jkb25uw6llcyBpbmRpdmlkdWVsbGVzIHN1ciBjZXQgYXhlLiBMJ8OiZ2UgZXN0IMOpZ2FsZW1lbnQgYXNzb2Npw6kgYXUgcHJlbWllciBheGUsIG1haXMgZGUgbWFuacOocmUgbW9pbnMgbWFycXXDqWUsIGV0IGxlIHNleGUgcGFzIGR1IHRvdXQuCgpTdXIgbCdheGXCoDIsIGwnw6JnZSBlc3QgbGEgdmFyaWFibGUgbGEgcGx1cyBzdHJ1Y3R1cmFudGUsIGRldmFudCBsZSBzZXhlIGV0IGxlIG5pdmVhdSBkJ8OpZHVjYXRpb24uIE9uIHZvaXQgZW4gb3V0cmUgcXVlIGwnw6JnZSBlc3QgbmV0dGVtZW50IHBsdXMgbGnDqSDDoCBsJ2F4ZcKgMiBxdSfDoCBsJ2F4ZcKgMS4KCkF1IG5pdmVhdSBkZXMgbW9kYWxpdMOpcywgb24gcGV1dCBjYXJhY3TDqXJpc2VyIGwnYXNzb2NpYXRpb24gZCd1bmUgbW9kYWxpdMOpIGRlIHZhcmlhYmxlIHN1cHBsw6ltZW50YWlyZSBhdmVjIHVuIGF4ZSDDoCBwYXJ0aXIgZGVzIGNvZWZmaWNpZW50cyBkZSBjb3Jyw6lsYXRpb24uCgpTdXIgbCdheGXCoDEsIGxlcyBub24gZXQgcGV1IGRpcGzDtG3DqXMgZXQgbGVzIDE1LTI0wqBhbnMgcydvcHBvc2VudCBhdXggcGx1cyBkaXBsw7Rtw6lzIGV0IGF1eCA1MMKgYW5zIGV0IHBsdXMuIExlcyBhdXRyZXMgbW9kYWxpdMOpcyBhcHBhcmFpc3NlbnQgcGV1IGxpw6llcyDDoCBsJ2F4ZSAobGV1cnMgY29lZmZpY2llbnRzIGRlIGNvcnLDqWxhdGlvbiwgZGFucyBsYSBkZXJuacOocmUgY29sb25uZSBkdSB0YWJsZWF1LCBzb250IHByb2NoZXMgZGUgMCkuCgpgYGB7ciBhY21fY29uZGVzYzEsIGV2YWw9RkFMU0V9CmRlcyA8LSBkaW1kZXNjcihtY2EsIHZhcnMgPSBUYXN0ZVssYygiR2VuZGVyIiwiQWdlIiwiRWR1YyIpXSkKZGVzJGRpbS4xJGNhdGVnb3JpZXMKYGBgCgpgYGB7ciBhY21fY29uZGVzYzFiaXMsIGVjaG89RkFMU0V9CmRlcyA8LSBkaW1kZXNjcihtY2EsIHZhcnMgPSBUYXN0ZVssYygiR2VuZGVyIiwiQWdlIiwiRWR1YyIpXSkKa25pdHI6OmthYmxlKGRlcyRkaW0uMSRjYXRlZ29yaWVzLCByb3cubmFtZXM9RkFMU0UpCmBgYAoKU3VyIGwnYXhlwqAyLCBsZXMgaG9tbWVzLCBsZXMgbW9pbnMgZGUgNTDCoGFucyBldCBsZXMgcGx1cyBkaXBsw7Rtw6lzIHMnb3Bwb3NlbnQgYXV4IGZlbW1lcywgYXV4IHBsdXMgZGUgNTDCoGFucyBldCBhdXggc2FucyBkaXBsw7RtZS4KCmBgYHtyIGFjbV9jb25kZXNjMiwgZXZhbD1GQUxTRX0KZGVzJGRpbS4yJGNhdGVnb3JpZXMKYGBgCgpgYGB7ciBhY21fY29uZGVzYzJiaXMsIGVjaG89RkFMU0V9CmtuaXRyOjprYWJsZShkZXMkZGltLjIkY2F0ZWdvcmllcywgcm93Lm5hbWVzPUZBTFNFKQpgYGAKCgoKIyBBbmFseXNlcyBpbnRlci1jbGFzc2VzCgpPbiBkaXNwb3NlIGQndW5lIGVucXXDqnRlIHBvc3Qtw6lsZWN0b3JhbGUgZHUgQ2V2aXBvZiByw6lhbGlzw6llIGVuIDE5OTcsIGV0IGVuIHBhcnRpY3VsaWVyIGRlIDIwIHF1ZXN0aW9ucyBhYm9yZGFudCBkZXMgZW5qZXV4IHBvbGl0aXF1ZXMsIMOpY29ub21pcXVlcyBldCBzb2NpYXV4ICh2b2lyIGwnYW5uZXhlIGRlIFtQZXJyaW5lYXUgKmV0IGFsKiAyMDAwXShodHRwczovL3NjaWVuY2VzcG8uaGFsLnNjaWVuY2UvaGFsLTAxMDExMzIwKSBwb3VyIGxlIGTDqXRhaWwgZGVzIHF1ZXN0aW9ucykuIExlcyByw6lwb25zZXMgw6AgY2VzIHF1ZXN0aW9ucyBwZXJtZXR0ZW50IGRlIGRlc3NpbmVyIHVuIGVzcGFjZSBwb2xpdGlxdWUgZGVzIMOpbGVjdGV1cnMgZnJhbsOnYWlzIMOgIGxhIGZpbiBkZXMgYW5uw6llcyAxOTkwLgpPbiBzb3VoYWl0ZSDDqXR1ZGllciBjZSBxdWksIGRhbnMgY2V0IGVzcGFjZSBwb2xpdGlxdWUsIGRpZmbDqXJlbmNpZSBsZXMgZGlmZsOpcmVudHMgw6lsZWN0b3JhdHMsIGFwcHJvY2jDqXMgcGFyIGxlIHZvdGUgYXUgcHJlbWllciB0b3VyIGRlcyDDqWxlY3Rpb25zIGzDqWdpc2xhdGl2ZXMgZGUgMTk5Ny4gT24gcG91cnJhaXQgcsOpYWxpc2VyIHVuZSBBQ00gw6AgcGFydGlyIGRlcyAyMCBxdWVzdGlvbnMgdHJhaXTDqWVzIGNvbW1lIHZhcmlhYmxlcyBhY3RpdmVzLCBwdWlzIHByb2pldGVyIGxlIHZvdGUgZW4gdGFudCBxdWUgdmFyaWFibGUgc3VwcGzDqW1lbnRhaXJlIHN1ciBsJ2VzcGFjZSBmYWN0b3JpZWwuIEMnZXN0IGwnb3B0aW9uIGFkb3B0w6llIGRhbnMgbCdhcnRpY2xlIG1lbnRpb25uw6kgcGx1cyBoYXV0LiBUb3V0ZWZvaXMsIGF2ZWMgY2V0dGUgYXBwcm9jaGUsIGwnZXNwYWNlIHBvbGl0aXF1ZSBlc3QgY29uc3RydWl0IGluZMOpcGVuZGFtbWVudCBkdSB2b3RlLCBhdmVjIHF1aSBpbCBuJ2VzdCBtaXMgZW4gcmVsYXRpb24gcXVlIGRhbnMgdW4gc2Vjb25kIHRlbXBzLiBPbiBwZXV0IGRvbmMgcHLDqWbDqXJlciB1bmUgImFuYWx5c2UgZmFjdG9yaWVsbGUgaW50ZXItY2xhc3NlcyIsIGF2ZWMgbGFxdWVsbGUgb24gY29uc3RydWl0IGwnZXNwYWNlIHBvbGl0aXF1ZSBxdWkgKmRpZmbDqXJlbmNpZSBsZSBtaWV1eCogbGVzIMOpbGVjdG9yYXRzLiBTb24gcHJlbWllciBheGUgZXN0IGNlbHVpIHF1aSBzw6lwYXJlIChvdSAiZXhwbGlxdWUiKSBsZSBtaWV1eCBsZXMgdm90ZXMsIGxlIHNlY29uZCBheGUgZXhwbGlxdWUgbGUgbWlldXggY2UgcXVpIG5lIGwnYSBwYXMgw6l0w6kgcGFyIGxlIHByZW1pZXIsIGV0IGFpbnNpIGRlIHN1aXRlLgoKYGBge3IgaW50ZXJfaW5pdH0KIyBsZXMgZG9ubsOpZXMgc29udCBkaXNwb25pYmxlcyBkYW5zIGxlIHBhY2thZ2UgInNvYy5jYSIKZGF0YShwb2xpdGljYWxfc3BhY2U5NywgcGFja2FnZSA9ICJzb2MuY2EiKQpzdHIocG9saXRpY2FsX3NwYWNlOTcpCiMgY29kYWdlIGRlcyB2YXJpYWJsZXMgYWN0aXZlcyBldCBkdSB2b3RlCmFjdGl2ZXMgPC0gYXMuZGF0YS5mcmFtZShsYXBwbHkocG9saXRpY2FsX3NwYWNlOTdbLDI6MjFdLCBmYWN0b3IpKQp2b3RlIDwtIGZhY3Rvcihwb2xpdGljYWxfc3BhY2U5NyRWb3RlKQpgYGAKCmBgYHtyIGludGVyX2JjYX0KIyBsZXMgbm9uLXLDqXBvbnNlcyBzb250IG1pc2VzIGVuICJqdW5rIiAoZWxsZXMgbmUgY29udHJpYnVlbnQgcGFzIMOgIGwnYW5hbHlzZSkKanVuayA8LSBncmVwKCIuNSIsIGdldGluZGV4Y2F0KGFjdGl2ZXMpLCB2YWx1ZSA9IFRSVUUsIGZpeGVkID0gVFJVRSkKIyBhbmFseXNlIGludGVyLWNsYXNzZXMKaW50ZXIgPC0gYmNNQ0EoYWN0aXZlcywgdm90ZSwgZXhjbCA9IGp1bmspCmBgYAoKTGUgcHJlbWllciBheGUgZXN0IHN0cnVjdHVyw6kgYXZhbnQgdG91dCBwYXIgbCdvcGluaW9uIHN1ciBsZXMgZ3LDqHZlcyBkZSAxOTk1LCBsZXMgc3luZGljYXRzIGV0IGxlcyBwcml2YXRpc2F0aW9ucywgc2Vjb25kYWlyZW1lbnQgc3VyIGxhIHBlaW5lIGRlIG1vcnQgZXQgbCdhY2N1ZWlsIGRlcyBpbW1pZ3LDqXMuCgpgYGB7ciBpbnRlcl9heGlzMX0KYmFycGxvdF9jb250cmliKGludGVyLCBkaW0gPSAxLCB3aGljaCA9ICJjb2wiLCByZXBlbCA9IFRSVUUpCmBgYAoKQ2UgcHJlbWllciBheGUgY29ycmVzcG9uZCDDoCB1bmUgb3Bwb3NpdGlvbiBnYXVjaGUtZHJvaXRlIGVudHJlIHBhcnRpcyB0cmFkaXRpb25uZWxzLgoKYGBge3IgaW50ZXJfYXhpczFifQpiYXJwbG90X2NvbnRyaWIoaW50ZXIsIGRpbSA9IDEsIHdoaWNoID0gInJvdyIsIHJlcGVsID0gVFJVRSkKYGBgCgpMZSBzZWNvbmQgYXhlIGVzdCBzdHJ1Y3R1csOpIGF2YW50IHRvdXQgbGUgcmFwcG9ydCDDoCBsYSBkw6ltb2NyYXRpZSwgw6AgbCdVbmlvbiBFdXJvcMOpZW5uZSwgYWluc2kgcXUnw6AgbCdpbW1pZ3JhdGlvbiBldCDDoCBsYSBwZWluZSBkZSBtb3J0LgoKYGBge3IgaW50ZXJfYXhpczJ9CmJhcnBsb3RfY29udHJpYihpbnRlciwgZGltID0gMiwgd2hpY2ggPSAiY29sIiwgcmVwZWwgPSBUUlVFKQpgYGAKCkNlIHNlY29uZCBheGUgImV4cGxpcXVlIiBxdWFudCDDoCBsdWkgdW5lIG9wcG9zaXRpb24gZW50cmUgbGUgRnJvbnQgTmF0aW9uYWwgZXQgbGEgZHJvaXRlIHRyYWRpdGlvbm5lbGxlLgoKYGBge3IgaW50ZXJfYXhpczJifQpiYXJwbG90X2NvbnRyaWIoaW50ZXIsIGRpbSA9IDIsIHdoaWNoID0gInJvdyIsIHJlcGVsID0gVFJVRSkKYGBgCgpPbiBwZXV0IHJlcHLDqXNlbnRlciBlbnNlbWJsZSBsJ2VzcGFjZSBwb2xpdGlxdWUgZXQgbGUgdm90ZS4KCmBgYHtyIGludGVyX3Bsb3R9CmZhY3RvZXh0cmE6OmZ2aXpfY2EoaW50ZXIsCiAgICAgICAgICAgICAgICAgICAgaW52aXNpYmxlID0gInJvdy5zdXAiLAogICAgICAgICAgICAgICAgICAgIGNvbC5yb3cgPSAiI2Q3MTkxYyIsCiAgICAgICAgICAgICAgICAgICAgY29sLmNvbCA9ICIjMmM3YmI2IiwKICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICJBbmFseXNlIGludGVyLWNsYXNzZXMiLAogICAgICAgICAgICAgICAgICAgIHJlcGVsID0gVFJVRSkKYGBgCgo+IEwnYXBwcm9jaGUgcGFyIHZhcmlhYmxlcyBpbnN0cnVtZW50YWxlcyBjb25zdGl0dWUgdW5lIGZvcm1lIGRlIHJhcHByb2NoZW1lbnQgZW50cmUgbCdhbmFseXNlIGfDqW9tw6l0cmlxdWUgZGVzIGRvbm7DqWVzIGV0IGxlcyBtw6l0aG9kZXMgZGUgcsOpZ3Jlc3Npb24uIEVuIHByYXRpcXVlLCBpbCBzJ2FnaXQgZGUgY29uc3RydWlyZSB1biBlc3BhY2UgZmFjdG9yaWVsLCDDoCBwYXJ0aXIgZCd1biBwcmVtaWVyIGdyb3VwZSBkZSB2YXJpYWJsZXMgYWN0aXZlcywgZGUgbWFuacOocmUgw6AgY2UgcXVlIGNldCBlc3BhY2UgImV4cGxpcXVlIiBhdSBtaWV1eCB1biBzZWNvbmQgZW5zZW1ibGUgZGUgdmFyaWFibGVzLCBkaXRlcyAqKnZhcmlhYmxlcyBpbnN0cnVtZW50YWxlcyoqLgpEYW5zIGxlIGNhcyBnw6luw6lyYWwsIGlsIHkgYSBwbHVzaWV1cnMgdmFyaWFibGVzIGluc3RydW1lbnRhbGVzLCBxdWkgcGV1dmVudCDDqnRyZSBjYXTDqWdvcmllbGxlcyBldC9vdSBudW3DqXJpcXVlcy4gTG9yc3F1ZSBsZXMgdmFyaWFibGVzIGFjdGl2ZXMgc29udCB0b3V0ZXMgbnVtw6lyaXF1ZXMsIG9uIHBhcmxlIGQnQW5hbHlzZSBlbiBDb21wb3NhbnRlcyBQcmluY2lwYWxlcyBzdXIgVmFyaWFibGVzIEluc3RydW1lbnRhbGVzIChmb25jdGlvbiBbYFBDQWl2KClgXShodHRwczovL25pY29sYXMtcm9iZXR0ZS5naXRodWIuaW8vR0RBdG9vbHMvcmVmZXJlbmNlL1BDQWl2Lmh0bWwpKSwgZXQgbG9yc3F1ZSBsZXMgdmFyaWFibGVzIGFjdGl2ZXMgc29udCB0b3V0ZXMgY2F0w6lnb3JpZWxsZXMsIGQnQW5hbHlzZSBkZXMgQ29ycmVzcG9uZGFuY2VzIHN1ciBWYXJpYWJsZXMgSW5zdHJ1bWVudGFsZXMgKGZvbmN0aW9uIFtgTUNBaXYoKWBdKGh0dHBzOi8vbmljb2xhcy1yb2JldHRlLmdpdGh1Yi5pby9HREF0b29scy9yZWZlcmVuY2UvTUNBaXYuaHRtbCkpLgpTaSBsYSB2YXJpYWJsZSBpbnN0cnVtZW50YWxlIGVzdCB1bmlxdWUgZXQgY2F0w6lnb3JpZWxsZSwgb24gcGV1dCBkaXJlIHF1J2VsbGUgcGFydGl0aW9ubmUgbGVzIGluZGl2aWR1cyBlbiBncm91cGVzIG91ICJjbGFzc2VzIi4gSWwgcydhZ2l0IGRvbmMgZGUgZmFpcmUgdW5lICoqYW5hbHlzZSBpbnRlci1jbGFzc2VzKiosIGVuIGNvbnN0cnVpc2FudCB1biBlc3BhY2UgZmFjdG9yaWVsIHF1aSByZW5kIGNvbXB0ZSBkZXMgZGlmZsOpcmVuY2VzIGVudHJlIGNsYXNzZXMgKGZvbmN0aW9ucyBbYGJjUENBKClgXShodHRwczovL25pY29sYXMtcm9iZXR0ZS5naXRodWIuaW8vR0RBdG9vbHMvcmVmZXJlbmNlL2JjUENBLmh0bWwpIHBvdXIgbGVzIGNhcyBvw7kgbGVzIHZhcmlhYmxlcyBhY3RpdmVzIHNvbnQgY29udGludWVzIGV0IFtgYmNNQ0EoKWBdKGh0dHBzOi8vbmljb2xhcy1yb2JldHRlLmdpdGh1Yi5pby9HREF0b29scy9yZWZlcmVuY2UvYmNNQ0EuaHRtbCkgcG91ciBjZXV4IG/DuSBlbGxlcyBzb250IGNhdMOpZ29yaWVsbGVzKS4gT24gcGFybGUgYXVzc2kgcGFyZm9pcyBkJyoqYW5hbHlzZSBkaXNjcmltaW5hbnRlIGJhcnljZW50cmlxdWUqKiBvdSBkJyphbmFseXNlIGRlcyBjb3JyZXNwb25kYW5jZXMgZGlzY3JpbWluYW50ZXMqLiBTaSBsZXMgdmFyaWFibGVzIGFjdGl2ZXMgc29udCBjb250aW51ZXMsIHVuZSB2YXJpYW50ZSBlc3QgbCcqKmFuYWx5c2UgZmFjdG9yaWVsbGUgZGlzY3JpbWluYW50ZSoqIChBRkQpLCBkaXRlIGF1c3NpICphbmFseXNlIGRpc2NyaW1pbmFudGUgZGVzY3JpcHRpdmUqIChmb25jdGlvbiBbYERBKClgXShodHRwczovL25pY29sYXMtcm9iZXR0ZS5naXRodWIuaW8vR0RBdG9vbHMvcmVmZXJlbmNlL0RBLmh0bWwpKSwgcXVpIGVzdCDDqXF1aXZhbGVudGUgw6AgbCcqYW5hbHlzZSBkaXNjcmltaW5hbnRlIGxpbsOpYWlyZSogZGFucyBsYSBsaXR0w6lyYXR1cmUgYW5nbG8tc2F4b25uZS4gTG9yc3F1ZSBsZXMgdmFyaWFibGVzIGFjdGl2ZXMgc29udCBjYXTDqWdvcmllbGxlcywgdW5lIGFkYXB0YXRpb24gZGUgbCdBRkQgYSDDqXTDqSBwcm9wb3PDqWUgcGFyIEdpbGJlcnQgU2Fwb3J0YSAoMTk3Nykgc291cyBsZSBub20gZGUgKipEaXNxdWFsKiogKGZvbmN0aW9uIFtgREFRKClgXShodHRwczovL25pY29sYXMtcm9iZXR0ZS5naXRodWIuaW8vR0RBdG9vbHMvcmVmZXJlbmNlL0RBUS5odG1sKSkuCgoKCiMgUsOpZ3Jlc3Npb25zIFBMUwoKTGVzIHLDqWdyZXNzaW9ucyBQTFMgKCpQYXJ0aWFsIExlYXN0IFNxdWFyZXMqKSBzJ2FwcGxpcXVlbnQgYXV4IGNhcyBvw7kgbCdvbiBhIHVuZSBvdSBwbHVzaWV1cnMgdmFyaWFibGVzIMOgIGV4cGxpcXVlciAkWSQgZXQgdW4gZW5zZW1ibGUgZGUgdmFyaWFibGVzIGV4cGxpY2F0aXZlcyAkWCQsIHF1ZWxzIHF1ZSBzb2llbnQgbGV1cnMgdHlwZXMgKGNvbnRpbnVzIG91IGNhdMOpZ29yaWVscykuIEVsbGVzIHBlcm1ldHRlbnQgZGUgY29uc3RydWlyZSBsJ2VzcGFjZSBkZXMgJFgkIHF1aSBleHBsaXF1ZSBhdSBtaWV1eCBsJ2VzcGFjZSBkZXMgJFkkLiBFbGxlcyBjb25zdGl0dWVudCBkb25jIHVuIGNvbXByb21pcyBlbnRyZSBsZXMgcsOpZ3Jlc3Npb25zIGRlcyAkWSQgZW4gZm9uY3Rpb24gZGVzICRYJCwgbCdBQ1AgZGVzICRYJCBldCBsJ0FDUCBkZXMgJFkkLiBFbGxlcyBhdXRvcmlzZW50IGRlcyB1c2FnZXMgZGl2ZXJzaWZpw6lzIDogCgotIE9yaWVudMOpZXMgdmVycyBsYSByw6lncmVzc2lvbiwgbGVzIHLDqWdyZXNzaW9ucyBQTFMgcGVybWV0dGVudCBkZSB0cmFpdGVyIHVuIGdyYW5kIG5vbWJyZSBkZSB2YXJpYWJsZXMgZXhwbGljYXRpdmVzIHNhbnMgcHJvYmzDqG1lcyBkJ2VzdGltYXRpb24gb3UgZGUgbXVsdGljb2xpbsOpYXJpdMOpLCBldCBkZSBmYWlyZSBkZSBsYSBzw6lsZWN0aW9uIGRlIHZhcmlhYmxlcy4KLSBPcmllbnTDqWVzIHZlcnMgbCdhbmFseXNlIGZhY3RvcmllbGxlLCBlbGxlcyBzJ2FwcHVpZW50IHN1ciBkZXMgZXNwYWNlcyBldCB1bmUgYXBwcm9jaGUgZ8Opb23DqXRyaXF1ZSwgcmVsYXRpb25uZWxsZSBldCBtdWx0aWRpbWVuc2lvbm5lbGxlLgoKIyMgRXhlbXBsZSAxIDogbGEgcsOpdXNzaXRlIHNjb2xhaXJlIGRlcyBlbmZhbnRzIGltbWlncsOpcwoKUG91ciBpbGx1c3RyZXIgbGUgcHJlbWllciB1c2FnZSAtIG9yaWVudMOpIHZlcnMgbGEgcsOpZ3Jlc3Npb24sIG9uIHMnYXBwdWllIHN1ciBsZXMgZG9ubsOpZXMgdXRpbGlzw6llcyBkYW5zIGwnYXJ0aWNsZSBkZSBKZWFuLVBhdWwgQ2FpbGxlIGV0IExvdWlzLUFuZHLDqSBWYWxsZXQgc3VyIGxhIHLDqXVzc2l0ZSBzY29sYWlyZSBhdSBjb2xsw6hnZSBkZXMgZW5mYW50cyDDqXRyYW5nZXJzIG91IGlzc3VzIGRlIGwnaW1taWdyYXRpb24gKFtDYWlsbGUgZXQgVmFsbGV0LCAxOTk1XShodHRwczovL2FyY2hpdmVzLXN0YXRpc3RpcXVlcy1kZXBwLmVkdWNhdGlvbi5nb3V2LmZyL0RlZmF1bHQvZG9jL1NZUkFDVVNFLzEyMDUyL2NhcnJpZXJlcy1sZXMtc2NvbGFpcmVzLWF1LWNvbGxlZ2UtZGVzLWVsZXZlcy1ldHJhbmdlcnMtb3UtaXNzdXMtZGUtbC1pbW1pZ3JhdGlvbj9fbGc9ZnItRlIpKS4KCk9uIGNoZXJjaGUgw6AgZXhwbGlxdWVyIGxhIHLDqXVzc2l0ZSBzY29sYWlyZSBwYXIgbGEgbmF0aW9uYWxpdMOpIGV0IHBhciBkJ2F1dHJlcyBjYXJhY3TDqXJpc3RpcXVlcyBzb2NpYWxlcyBldCBmYW1pbGlhbGVzIGRlcyDDqWzDqHZlcyA6IGxlIHNleGUsIGxlIHJhbmcgZGFucyBsYSBmcmF0cmllLCBsYSBQQ1MgZGUgbGEgcGVyc29ubmUgZGUgcsOpZsOpcmVuY2UgZGUgbGEgZmFtaWxsZSwgbGUgZGlwbMO0bWUgZHUgcMOocmUsIGNlbHVpIGRlIGxhIG3DqHJlLCBhY3Rpdml0w6kgb3UgYWJzZW5jZSBkJ2FjdGl2aXTDqSBwcm9mZXNzaW9ubmVsbGUgZGUgbGEgbcOocmUsIGxhIHRhaWxsZSBkZSBsYSBmYW1pbGxlLCBsYSBzdHJ1Y3R1cmUgZmFtaWxpYWxlLCBwcsOpc2VuY2UgZCd1biBmcsOocmUgb3UgZCd1bmUgc8WTdXIgc2NvbGFyaXPDqWUgZW4gbHljw6llIG91IGRhbnMgbCdlbnNlaWduZW1lbnQgc3Vww6lyaWV1ci4gTGEgcsOpdXNzaXRlIHNjb2xhaXJlIGVzdCBhcHByb2Now6llIHBhciBsZSBmYWl0IGQnYXZvaXIgcmXDp3UsIHF1YXRyZSBhbnMgYXByw6hzIGwnZW50csOpZSBhdSBjb2xsw6hnZSwgdW5lIHByb3Bvc2l0aW9uIGQnb3JpZW50YXRpb24gZW4gc2Vjb25kZSBnw6luw6lyYWxlIG91IHRlY2hub2xvZ2lxdWUuCgpgYGB7ciB2YWxsZXRfaW5pdH0KIyBjaGFyZ2VtZW50IGR1IGpldSBkZSBkb25uw6llcwpsb2FkKCIvaG9tZS9uaWNvbGFzL05leHRjbG91ZC9VVlNRL00xX1N0YXRzQXBwbFNjU29jL1ZhbGxldENhaWxsZS5SRGF0YSIpCnN0cihiYXNlcmVnKQpgYGAKCkxlcyB2YXJpYWJsZXMgZXhwbGljYXRpdmVzLCBxdWkgc29udCBpY2kgdG91dGVzIGNhdMOpZ29yaWVsbGVzLCBzb250IHJlY29kw6llcyBzb3VzIGZvcm1lIGRpY2hvdG9taXF1ZS4gTGEgdmFyaWFibGUgw6AgZXhwbGlxdWVyIMOpdGFudCBiaW5haXJlLCBvbiBmYWl0IGxlIGNob2l4IGRlIGwndXRpbGlzZXIgY29tbWUgdW5lIHZhcmlhYmxlIG51bcOpcmlxdWUsIGRlIHZhbGV1ciAwIG91IDEgKG1haXMgb24gYXVyYWl0IMOpZ2FsZW1lbnQgcHUgbGEgcmVjb2RlciBzb3VzIGxhIGZvcm1lIGRlIGRldXggdmFyaWFibGVzIGRpY2hvdG9taXF1ZXMpLgoKYGBge3IgdmFsbGV0X3JlY29kYWdlfQojIHPDqWxlY3Rpb24gZXQgcmVjb2RhZ2UgZGVzIHZhcmlhYmxlcyBleHBsaWNhdGl2ZXMKWCA8LSBkaWNob3RvbShiYXNlcmVnWywtMV0sIG91dCA9ICJudW1lcmljIikKIyBzw6lsZWN0aW9uIGV0IHJlY29kYWdlIGRlIGxhIHZhcmlhYmxlIMOgIGV4cGxpcXVlcgpZIDwtIGFzLm51bWVyaWMoYmFzZXJlZyRTZWNvbmRlPT0iMSIpCiMgWCBldCBZIGRhbnMgdW4gc2V1bCB0YWJsZWF1IGRlIGRvbm7DqWVzCmRmIDwtIGRhdGEuZnJhbWUoU2Vjb25kZSA9IEkoYXMubWF0cml4KFkpKSwgWCA9IEkoYXMubWF0cml4KFgpKSkKYGBgCgpPbiByw6lhbGlzZSB1bmUgcHJlbWnDqHJlIHLDqWdyZXNzaW9uIFBMUyBzYW5zIGZpeGVyIGxlIG5vbWJyZSBkZSBjb21wb3NhbnRlcyAoaS5lLiBheGVzKS4KCmBgYHtyIHZhbGxldF9wbHNyXzF9CnBsczEgPC0gcGxzOjptdnIoU2Vjb25kZSB+IFgsCiAgICAgICAgICAgICAgICAgZGF0YSA9IGRmLAogICAgICAgICAgICAgICAgIHNjYWxlID0gVFJVRSwKICAgICAgICAgICAgICAgICB2YWxpZGF0aW9uID0gIkNWIiwKICAgICAgICAgICAgICAgICBqYWNra25pZmUgPSBUUlVFLAogICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJvc2NvcmVzcGxzIikKYGBgCgpBIHRpdHJlIGV4cGxvcmF0b2lyZSwgb24gw6l0dWRpZSBsZXMgcHJvamVjdGlvbnMgZGVzIHZhcmlhYmxlcyBkYW5zIGxlIHBsYW4gZm9ybcOpIHBhciBsZXMgZGV1eCBwcmVtacOocmVzIGNvbXBvc2FudGVzLiBBdSBwcsOpYWxhYmxlLCBvbiBpbnRlcnByw6h0ZSBjaGFjdW5lIGRlcyBjb21wb3NhbnRlcyDDoCBwYXJ0aXIgZGVzICJwb2lkcyIgKGkuZS4gY29udHJpYnV0aW9ucykgZGVzIHZhcmlhYmxlcyBleHBsaWNhdGl2ZXMgZGFucyBsZXVyIGNvbnN0cnVjdGlvbi4KCkxhIHByZW1pw6hyZSBjb21wb3NhbnRlIGVzdCB1biBheGUgZGUgaGnDqXJhcmNoaWUgc29jaWFsZSA6IGVsbGUgb3Bwb3NlIGxlcyDDqWzDqHZlcyBkb250IGxlcyBwYXJlbnRzIHNvbnQgbGVzIHBsdXMgZGlwbMO0bcOpcyBldCBkb250IGxhIHBlcnNvbm5lIGRlIHLDqWbDqXJlbmNlIGRlIGxhIGZhbWlsbGUgZXN0IGNhZHJlLCBhdXggw6lsw6h2ZXMgZG9udCBsZXMgcGFyZW50cyBzb250IGxlcyBtb2lucyBkaXBsw7Rtw6lzIGV0IGRvbnQgbGEgcGVyc29ubmUgZGUgcsOpZsOpcmVuY2UgZGUgbGEgZmFtaWxsZSBlc3Qgb3V2cmnDqHJlLgoKYGBge3IgdmFsbGV0X2N0cjF9CnBsb19jdHIocGxzMSwgY29tcCA9IDEpCmBgYAoKTGEgc2Vjb25kZSBjb21wb3NhbnRlIG9wcG9zZSBsZXMgw6lsw6h2ZXMgw6l0cmFuZ2VycywgZGUgc2V4ZSBmw6ltaW5pbiBldC9vdSBkZSBtw6hyZSBpbmFjdGl2ZSBhdXggw6lsw6h2ZXMgZnJhbsOnYWlzLCBkZSBzZXhlIG1hc2N1bGluIGV0L291IGRlIG3DqHJlIGFjdGl2ZS4KCmBgYHtyIHZhbGxldF9jdHIyfQpwbG9fY3RyKHBsczEsIGNvbXAgPSAyKQpgYGAKCkRhbnMgbGUgcGxhbiBmb3Jtw6kgcGFyIGNlcyBkZXV4IGNvbXBvc2FudGVzLCBvbiB2b2l0IHF1ZSBsYSByw6l1c3NpdGUgc2NvbGFpcmUgc2Ugc2l0dWUgbmV0dGVtZW50IGR1IGPDtHTDqSBkZSBsYSBwYXJ0aWUgc3Vww6lyaWV1cmUgZGUgbGEgaGnDqXJhcmNoaWUgc29jaWFsZSwgbWFpcyBhdXNzaSwgcGx1cyBsw6lnw6hyZW1lbnQsIGR1IGPDtHTDqSBkZXMgw6lsw6h2ZXMgw6l0cmFuZ2VycywgZGUgc2V4ZSBmw6ltaW5pbiBldC9vdSBkZSBtw6hyZSBpbmFjdGl2ZS4KCmBgYHtyIHZhbGxldF9wbG90dmFyfQpwbG9fdmFyKHBsczEsIGNvbXBzID0gYygxLDIpKSArCiAgY29vcmRfZml4ZWQocmF0aW8gPSAxKQpgYGAKCkF2YW50IGQnb2J0ZW5pciBsZXMgcsOpc3VsdGF0cyBkZSBsYSByw6lncmVzc2lvbiBQTFMgc291cyBmb3JtZSBkZSBjb2VmZmljaWVudHMsIG9uIHZhIGTDqXRlcm1pbmVyIGxlIG5vbWJyZSBvcHRpbWFsIGRlIGNvbXBvc2FudGVzIMOgIHJldGVuaXIuIElsIGV4aXN0ZSBwbHVzaWV1cnMgYXBwcm9jaGVzIHBvdXIgY2VsYSwgbm90YW1tZW50IDoKCi0gdGVzdHMgZGUgcGVybXV0YXRpb24KLSBlcnJldXItdHlwZSBkZXMgcsOpc2lkdXMgaXNzdXMgZGUgbGEgdmFsaWRhdGlvbiBjcm9pc8OpZQotIGluZGljYXRldXIgJFFeMiQgKG9uIHJldGllbnQgbGVzIGNvbXBvc2FudGVzIGRvbnQgbGUgJFFeMiBcZ2UgMC4wOTc1JCkKCkxlcyB0cm9pcyBhcHByb2NoZXMgbmUgc3VnZ8OpcmFudCBwYXMgbGUgbcOqbWUgbm9tYnJlIGRlIGNvbXBvc2FudGVzLCBvbiBjaG9pc2l0IGxhIHNvbHV0aW9uIHF1aSBjb25zZXJ2ZSBsZSBwbHVzIGQnaW5mb3JtYXRpb24sIGkuZS4gY2VsbGUgw6AgMiBjb21wb3NhbnRlcy4gT24gYSB2dSBhdXNzaSBxdWUgbGEgdmFyaWFibGUgZGUgbmF0aW9uYWxpdMOpLCBkb250IGwnaW50ZXJwcsOpdGF0aW9uIG5vdXMgaW50w6lyZXNzZSwgcMOoc2UgZGFucyBsYSBjb25zdHJ1Y3Rpb24gZHUgc2Vjb25kIGF4ZS4KCmBgYHtyIHZhbGxldF9RMn0KIyB0ZXN0IGRlIHBlcm11dGF0aW9uCnNlbGVjdE5jb21wKHBsczEsICJyYW5kb21pemF0aW9uIikKIyB2YWxpZGF0aW9uIGNyb2lzw6llCnNlbGVjdE5jb21wKHBsczEsICJvbmVzaWdtYSIpCiMgUTIgZGVzIGNvbXBvc2FudGVzCnEyIDwtIGdldF9RMihwbHMxKSRRMmgKd2hpY2gocTI+PTAuMDk3NSkKYGBgCgpPbiBwZXV0IGVuc3VpdGUgb2J0ZW5pciBsZXMgcsOpc3VsdGF0cyBkZSBsYSByw6lncmVzc2lvbiBzb3VzIGZvcm1lIGRlIGNvZWZmaWNpZW50cywgKnAtdmFsdWVzKiBldCBpbnRlcnZhbGxlcyBkZSBjb25maWFuY2UgKGNhbGN1bMOpcyBhdmVjIHVuZSBwcm9jw6lkdXJlICpqYWNra25pZmUqKS4gTGVzIGNvZWZmaWNpZW50cyBwZXJtZXR0ZW50IGRlIGNvbXBhcmVyIGxhIGZvcmNlIGRlcyBlZmZldHMgZGVzIHZhcmlhYmxlcyBldCBsZXVyIHNpZ25lLiBDZXBlbmRhbnQsIGlscyBjb3JyZXNwb25kZW50IGF1eCBjb2VmZmljaWVudHMgZGVzIHZhcmlhYmxlcyBleHBsaWNhdGl2ZXMgKmNlbnRyw6llcyBldCByw6lkdWl0ZXMqLCBsZXVyIHZhbGV1ciAqaW4gYWJzdHJhY3RvKiBuJ2EgZG9uYyBwYXMgZGUgc2lnbmlmaWNhdGlvbi4KCmBgYHtyIHZhbGxldF9jb2VmLCBldmFsID0gRkFMU0V9CmdldF9jb2VmKHBsczEsIG5jb21wID0gMikKYGBgCgpgYGB7ciB2YWxsZXRfY29lZmJpcywgZWNobz1GQUxTRX0Ka25pdHI6OmthYmxlKGdldF9jb2VmKHBsczEsIG5jb21wID0gMikpCmBgYAoKT24gcGV1dCBhdXNzaSBsZXMgcmVwcsOpc2VudGVyIHNvdXMgZm9ybWUgZ3JhcGhpcXVlLiBPbiBjb25zdGF0ZSBub3RhbW1lbnQgcXVlIGxhIG5hdGlvbmFsaXTDqSDDqXRyYW5nw6hyZSBlc3QgdW4gZmFjdGV1ciBmYXZvcmFibGUgw6AgbGEgcsOpdXNzaXRlIHNjb2xhaXJlLCBtw6ptZSBzJ2lsIHDDqHNlIGJlYXVjb3VwIG1vaW5zIGZvcnRlbWVudCBxdWUgbGUgc2V4ZSBvdSBsYSBwb3NpdGlvbiBzb2NpYWxlIGRlcyBwYXJlbnRzLgoKYGBge3IgdmFsbGV0X3Bsb3Rjb2VmfQpwbG9fY29lZihwbHMxLCBuY29tcCA9IDIsIG1heC5wdmFsID0gMC4wNSwgd2hpc2tlcnMgPSBUUlVFLCBjaSA9IDAuOTUpCmBgYAoKT24gcGV1dCDDqWdhbGVtZW50IG9idGVuaXIgbGVzIGNvZWZmaWNpZW50cyAiYnJ1dHMiLCBpLmUuIG5vbiBzdGFuZGFyZGlzw6lzIDogaWxzIHNvbnQgZXhwcmltw6lzIGRhbnMgbGV1ciB1bml0w6kgZGUgbWVzdXJlIGQnb3JpZ2luZS4gQ2VsYSBmYWNpbGl0ZSBsJ2ludGVycHLDqXRhdGlvbiBkZSBsJ2VmZmV0IGQndW5lIHZhcmlhYmxlIHN1ciBsYSB2YXJpYWJsZSDDoCBleHBsaXF1ZXIsIG1haXMgbmUgcGVybWV0IHBhcyBsYSBjb21wYXJhaXNvbiBkZSBsYSBmb3JjZSBkZXMgZWZmZXRzLgoKYGBge3IgdmFsbGV0X3Jhd2NvZWYsIGV2YWwgPSBGQUxTRX0KZ2V0X2NvZWYocGxzMSwgbmNvbXAgPSAyLCByYXcgPSBUUlVFKQpgYGAKCmBgYHtyIHZhbGxldF9yYXdjb2VmYmlzLCBlY2hvPUZBTFNFfQprbml0cjo6a2FibGUoZ2V0X2NvZWYocGxzMSwgbmNvbXAgPSAyLCByYXcgPSBUUlVFKSkKYGBgCgpBIG5vdGVyIDogc2kgbCdvbiBzb3VoYWl0ZSBzw6lsZWN0aW9ubmVyIGxlcyBwcmluY2lwYWxlcyB2YXJpYWJsZXMgZXhwbGljYXRpdmVzIGR1IG1vZMOobGVzLCBvbiBwZXV0IHMnYXBwdXllciBzdXIgbGVzIGNvZWZmaWNpZW50cyBkZSBsYSByw6lncmVzc2lvbiBldCBsZXMgKnAtdmFsdWVzKiBhc3NvY2nDqWVzLiBNYWlzIHVuZSBhdXRyZSBhcHByb2NoZSBjb25zaXN0ZSDDoCB1dGlsaXNlciBsZXMgVklQICgqVmFyaWFibGUgSW1wb3J0YW5jZSBpbiB0aGUgUHJlZGljdGlvbiopLCBxdWkgbWVzdXJlbnQgbCdpbXBvcnRhbmNlIGRlcyB2YXJpYWJsZXMgZXhwbGljYXRpdmVzIGRhbnMgbGEgcHLDqWRpY3Rpb24gZGVzIHZhcmlhYmxlcyDDoCBleHBsaXF1ZXIuIFBhciBjb252ZW50aW9uLCBvbiBwZXV0IGNvbnNpZMOpcmVyIHVuZSB2YXJpYWJsZSBjb21tZSBpbXBvcnRhbnRlIHNpIHNvbiAkVklQID4gMC44JCBvdSAkPjEkLCBzZWxvbiBsZXMgcmVjb21tYW5kYXRpb25zLiBDZWxhIG5vdXMgYW3DqG5lcmFpdCBpY2kgw6AgbmUgY29uc2VydmVyIHF1ZSAxOSBvdSBtw6ptZSAxMCBkZXMgMzkgdmFyaWFibGVzIGV4cGxpY2F0aXZlcy4KCmBgYHtyIHZhbGxldF92aXB9CnBsc1ZhclNlbDo6VklQKHBsczEsIG9wdC5jb21wID0gMikgfD5zb3J0KCkgfD4gcmV2KCkgfD4gcm91bmQoMykKcGxvX3ZpcChwbHMxLCBuY29tcCA9IDIpCmBgYAoKCiMjIEV4ZW1wbGUgMiA6IGxlcyBsb2lzaXJzCgpQb3VyIGxlIGRldXhpw6htZSBleGVtcGxlLCBvbiB1dGlsaXNlIGxlcyBkb25uw6llcyBpc3N1ZXMgZGUgbCdlbnF1w6p0ZSAiSGlzdG9pcmVzIGRlIHZpZSIgKEluc2VlLCAyMDAzKSBkaXNwb25pYmxlcyBkYW5zIGxlIHBhY2thZ2UgYHF1ZXN0aW9ucmAuCgpPbiB2YSBjaGVyY2hlciDDoCBleHBsaXF1ZXIgbGVzIHByYXRpcXVlcyBkZSBsb2lzaXJzIHBhciBsZXMgcHJvcHJpw6l0w6lzIHNvY2lhbGVzIGRlcyBlbnF1w6p0w6lzLgoKLSAxMCB2YXJpYWJsZXMgZGUgbG9pc2lycyA6IGhhcmQtcm9jaywgbGVjdHVyZSBkZSBCRCwgcMOqY2hlIGV0IGNoYXNzZSwgY3Vpc2luZSwgYnJpY29sYWdlLCBjaW7DqW1hLCBzcG9ydCBldCB0w6lsw6l2aXNpb24KLSA2IHZhcmlhYmxlcyBkZSBwcm9wcmnDqXTDqXMgc29jaWFsZXMgOiBzZXhlLCDDomdlLCBuaXZlYXUgZCfDqXR1ZGVzLCBzdGF0dXQgZCdlbXBsb2ksIHF1YWxpZmljYXRpb24sIHJlbGlnaW9zaXTDqQoKT24gcmVjb2RlIHRvdXRlcyBsZXMgdmFyaWFibGVzIHNvdXMgZm9ybWUgZGljaG90b21pcXVlLiBPbiBhIHByw6lhbGFibGVtZW50IGRpc2Nyw6l0aXPDqSBsJ8OiZ2UgZXQgbGEgY29uc29tbWF0aW9uIGRlIHTDqWzDqXZpc2lvbiwgcG91ciBkZXMgcmFpc29ucyBkJ2ludGVycHLDqXRhdGlvbiwgbWFpcywgZHUgcG9pbnQgZGUgdnVlIHRlY2huaXF1ZSwgY2VsYSBuJ8OpdGFpdCBwYXMgbsOpY2Vzc2FpcmUuCgpgYGB7ciBoZHZfaW5pdH0KIyBjaGFyZ2VtZW50IGR1IGpldSBkZSBkb25uw6llcwpkYXRhKGhkdjIwMDMsIHBhY2thZ2UgPSAicXVlc3Rpb25yIikKIyBzw6lsZWN0aW9uIGV0IHJlY29kYWdlIGRlcyB2YXJpYWJsZXMgZXhwbGljYXRpdmVzClggPC0gc2VsZWN0KGhkdjIwMDMsIGFnZSwgc2V4ZSwgbml2ZXR1ZCwgb2NjdXAsIHF1YWxpZiwgcmVsaWcpICU+JQogICAgIG11dGF0ZShhZ2UgPSBIbWlzYzo6Y3V0MihhZ2UsIGN1dHMgPSBjKDE4LDI2LDM2LDUxLDY2KSkpICU+JSAgIyBkaXNjcsOpdGlzYXRpb24gZGUgbCfDomdlCiAgICAgZGljaG90b20ob3V0ID0gIm51bWVyaWMiKSAgIyBkaWNob3RvbWlzYXRpb24KIyBzw6lsZWN0aW9uIGV0IHJlY29kYWdlIGRlcyB2YXJpYWJsZXMgw6AgZXhwbGlxdWVyClkgPC0gc2VsZWN0KGhkdjIwMDMsIGhhcmQucm9jazpoZXVyZXMudHYpICU+JQogICAgIG11dGF0ZShoZXVyZXMudHYgPSBIbWlzYzo6Y3V0MihoZXVyZXMudHYsIGcgPSA0KSAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgIGZhY3RvcihsYWJlbHMgPSBhcy5jaGFyYWN0ZXIoMTo0KSkpICU+JSAgIyBkaXNjcsOpdGlzYXRpb24gZGUgbGEgY29uc29tbWF0aW9uIGRlIHR2CiAgICAgZGljaG90b20ob3V0ID0gIm51bWVyaWMiKSAlPiUgICMgZGljaG90b21pc2F0aW9uCiAgICAgbGFwcGx5KHNjYWxlKSAlPiUgICMgc3RhbmRhcmRpc2F0aW9uCiAgICAgYXMuZGF0YS5mcmFtZSgpCiMgWCBldCBZIGRhbnMgdW4gc2V1bCB0YWJsZWF1IGRlIGRvbm7DqWVzCmRmIDwtIGRhdGEuZnJhbWUoWSA9IEkoYXMubWF0cml4KFkpKSwgWCA9IEkoYXMubWF0cml4KFgpKSkKYGBgCgpPbiByw6lhbGlzZSB1bmUgcsOpZ3Jlc3Npb24gUExTLCBlbiBuZSBjb25zZXJ2YW50IHF1ZSBsZXMgNSBwcmVtacOocmVzIGNvbXBvc2FudGVzLgoKYGBge3IgaGR2X3Bsc3J9CnBsczIgPC0gcGxzOjptdnIoWSB+IFgsIGRhdGEgPSBkZiwKICAgICAgICAgICAgICAgICBuY29tcCA9IDUsCiAgICAgICAgICAgICAgICAgc2NhbGUgPSBUUlVFLAogICAgICAgICAgICAgICAgIHZhbGlkYXRpb24gPSAiQ1YiLAogICAgICAgICAgICAgICAgIGphY2trbmlmZSA9IFRSVUUsCiAgICAgICAgICAgICAgICAgbWV0aG9kID0gIm9zY29yZXNwbHMiLAogICAgICAgICAgICAgICAgIG1heGl0ID0gMTAwMCkKYGBgCgpMYSBwcm9qZWN0aW9uIGRlcyB2YXJpYWJsZXMgZXhwbGljYXRpdmVzIHN1ciBsZSBwbGFuIGZvcm3DqSBwYXIgbGVzIGRldXggcHJlbWnDqHJlcyBjb21wb3NhbnRlcyBkb25uZSB1biBwcmVtaWVyIHJlZ2FyZCBzdXIgbGVzIHByb3ByacOpdMOpcyBzb2NpYWxlcyBsZXMgcGx1cyBzdHJ1Y3R1cmFudGVzLgoKYGBge3IgaGR2X3Bsb3Zhclh9CnBsb192YXIocGxzMiwgd2hpY2ggPSAiWCIpCmBgYAoKTWFpcyBsJ2V4YW1lbiBkZXMgY29udHJpYnV0aW9ucyBwZXJtZXQgdW5lIGludGVycHLDqXRhdGlvbiBwbHVzIHByw6ljaXNlIHBvdXIgY2hhY3VuZSBkZXMgY29tcG9zYW50ZXMuIExhIHByZW1pw6hyZSBvcHBvc2UgbGVzIHBsdXMgZGUgNTAgYW5zLCByZXRyYWl0w6lzLCBwZXUgZGlwbMO0bcOpcyBldC9vdSBvdXZyaWVycywgYXV4IMOpdHVkaWFudHMgZXQgYWN0aWZzIGRlIG1vaW5zIGRlIDUwIGFucywgZGlwbMO0bcOpcyBkdSBzdXDDqXJpZXVyIGV0IC9vdSBjYWRyZXMuIEVsbGUgc2VtYmxlIGRvbmMgw6AgbGEgZm9pcyBzdHJ1Y3R1csOpZSBwYXIgbCfDomdlIGV0IGxhIGhpw6lyYXJjaGllIHNvY2lhbGUuCgpgYGB7ciBoZHZfcGxvY3RyMX0KcGxvX2N0cihwbHMyLCBjb21wID0gMSkKYGBgCgpMZSBzZWNvbmQgYXhlIHRyYWR1aXQgcXVhbnQgw6AgbHVpIGF2YW50IHRvdXQgdW5lIG9wcG9zaXRpb24gc2V4dcOpZS4KCmBgYHtyIGhkdl9wbG9jdHIyfQpwbG9fY3RyKHBsczIsIGNvbXAgPSAyKQpgYGAKQSBwYXJ0aXIgZGUgbGEgcHJvamVjdGlvbiBkZXMgdmFyaWFibGVzIMOgIGV4cGxpcXVlciBzdXIgbGUgcGxhbiBmb3Jtw6kgcGFyIGxlcyBkZXV4IHByZW1pw6hyZXMgY29tcG9zYW50ZXMsIG9uIGNvbnN0YXRlIHF1ZSA6IAoKLSBsZXMgbG9pc2lycyBkZXMgcGVyc29ubmVzIMOiZ8OpZXMgb3Ugc2l0dcOpZXMgZW4gYmFzIGRlIGxhIGhpw6lyYXJjaGllIHNvY2lhbGUgc2UgbGltaXRlbnQgw6AgdW5lIGNvbnNvbW1hdGlvbiBpbXBvcnRhbnRlIGRlIHTDqWzDqXZpc2lvbiA7Ci0gbGEgcHJhdGlxdWUgZHUgc3BvcnQgZXQgZHUgY2luw6ltYSwgYXNzb2Npw6llIMOgIHVuZSBmYWlibGUgY29uc29tbWF0aW9uIGRlIHTDqWzDqXZpc2lvbiwgc29udCBhc3NvY2nDqWVzIGF1eCBtb2lucyBkZSA1MCBhbnMgZXQgYXV4IGluZGl2aWR1cyBzaXR1w6lzIGVuIGhhdXQgZGUgbGEgaGnDqXJhcmNoaWUgc29jaWFsZSA7Ci0gbGEgc2Vjb25kZSBjb21wb3NhbnRlIG9wcG9zZSBsYSBjdWlzaW5lIChsb2lzaXIgZsOpbWluaW4pIGF1IGJyaWNvbGFnZSwgbGEgcMOqY2hlIGV0IGxhIGNoYXNzZSAobG9pc2lycyBtYXNjdWxpbnMpLgoKYGBge3IgaGR2X3Bsb3Zhcll9CnBsb192YXIocGxzMiwgd2hpY2ggPSAiWSIpCmBgYAoKTCdleGFtZW4gZGVzICJyZWRvbmRhbmNlcyIgcGVybWV0IGRlIHbDqXJpZmllciBxdWVsbGVzIHZhcmlhYmxlcyBkZSBZIHNvbnQgbGVzIG1pZXV4IGV4cGxpcXXDqWVzIHBhciBsZXMgY29tcG9zYW50ZXMgMSBldCAyIDogY2Ugc29udCBiaWVuIGNlbGxlcyBxdWUgbCdvbiBhdmFpdCBpZGVudGlmacOpZXMgw6AgcGFydGlyIGRlIGxhIHByb2plY3Rpb24gZGVzIHZhcmlhYmxlcy4KCmBgYHtyIGhkdl9yZWR9CnJlZG9uZGFuY2VzIDwtIGdldF9yZWQocGxzMikKcm91bmQocmVkb25kYW5jZXMkWXQsMylbLDE6Ml0KYGBgCgoKCiMgQW5hbHlzZXMgY29uZGl0aW9ubmVsbGVzCgpMZXMgKmFuYWx5c2VzIGNvbmRpdGlvbm5lbGxlcyogKG91IGludHJhLWNsYXNzZXMpIGNvbnN0aXR1ZW50IHVuZSBhdXRyZSB0ZW50YXRpdmUgZCdpbnTDqWdyYXRpb24gZGUgbCdhbmFseXNlIGfDqW9tw6l0cmlxdWUgZGVzIGRvbm7DqWVzIGV0IGRlIGxhIHLDqWdyZXNzaW9uLiBMZXVyIHByaW5jaXBlIGNvbnNpc3RlIMOgIGNvbnRyYWluZHJlIGxlcyBheGVzIGRlIGwnQUNNIMOgIMOqdHJlIGluZMOpcGVuZGFudHMgKGkuZS4gb3J0aG9nb25hdXgpIGQndW5lIG91IHBsdXNpZXVycyB2YXJpYWJsZXMgc3VwcGzDqW1lbnRhaXJlcywgYydlc3Qtw6AtZGlyZSDDoCBjb25zdHJ1aXJlIHVuZSBBQ00gInRvdXRlIGNob3NlIChkZSBjZXMgdmFyaWFibGVzIHN1cHBsw6ltZW50YWlyZXMpIMOpZ2FsZSBwYXIgYWlsbGV1cnMiIChCcnkgZXQgYWwsIDIwMTYpLiBMYSBjb21wYXJhaXNvbiBkZXMgcsOpc3VsdGF0cyBkZSBsJ0FDTSBvcmlnaW5lbGxlIGV0IGRlIGNldXggZGUgbCdBQ00gY29uZGl0aW9ubmVsbGUgZXN0IHVuIG1veWVuIGQnw6l0dWRpZXIgbGVzIGVmZmV0cyBkZSBzdHJ1Y3R1cmUuCgpEYW5zIGxlIGNhcyBnw6luw6lyYWwsIGlsIHkgYSBwbHVzaWV1cnMgdmFyaWFibGVzIGRvbnQgaWwgZmF1dCAiw6lsaW1pbmVyIGwnZWZmZXQiIGV0IGNlbGxlcy1jaSBzb250IHBhcmZvaXMgYXBwZWzDqWVzICoqdmFyaWFibGVzIGluc3RydW1lbnRhbGVzIG9ydGhvZ29uYWxlcyoqLiBPbiByw6lhbGlzZXJhIGRvbmMgdW5lICpBbmFseXNlIGVuIENvbXBvc2FudGVzIFByaW5jaXBhbGVzIHN1ciBWYXJpYWJsZXMgSW5zdHJ1bWVudGFsZXMgT3J0aG9nb25hbGVzKiBzaSBsZXMgdmFyaWFibGVzIGFjdGl2ZXMgc29udCB0b3V0ZXMgbnVtw6lyaXF1ZXMgKGZvbmN0aW9uIFtgUENBb2l2KClgXShodHRwczovL25pY29sYXMtcm9iZXR0ZS5naXRodWIuaW8vR0RBdG9vbHMvcmVmZXJlbmNlL1BDQW9pdi5odG1sKSkgZXQgdW5lICpBbmFseXNlIGRlcyBDb3JyZXNwb25kYW5jZXMgc3VyIFZhcmlhYmxlcyBJbnN0cnVtZW50YWxlcyBPcnRob2dvbmFsZXMqIHNpIGxlcyB2YXJpYWJsZXMgYWN0aXZlcyBzb250IHRvdXRlcyBjYXTDqWdvcmllbGxlcyAoZm9uY3Rpb24gW2BNQ0FvaXYoKWBdKGh0dHBzOi8vbmljb2xhcy1yb2JldHRlLmdpdGh1Yi5pby9HREF0b29scy9yZWZlcmVuY2UvTUNBb2l2Lmh0bWwpKS4KCkNvbW1lIHBvdXIgbGVzIGFuYWx5c2VzIGRpc2NyaW1pbmFudGVzIChvdSBpbnRlci1jbGFzc2VzKSwgc2kgbGEgdmFyaWFibGUgaW5zdHJ1bWVudGFsZSBvcnRob2dvbmFsZSBlc3QgdW5pcXVlIGV0IGNhdMOpZ29yaWVsbGUsIGVsbGUgcGFydGl0aW9ubmUgbGVzIGluZGl2aWR1cyBlbiAiY2xhc3NlcyIuIE1haXMgb24gZXN0IGNldHRlIGZvaXMgZGFucyBsZSBjYWRyZSBkJ3VuZSAqKmFuYWx5c2UgaW50cmEtY2xhc3NlcyoqIDogaWwgcydhZ2l0IGRlIGNvbnN0cnVpcmUgbCdlc3BhY2UgZmFjdG9yaWVsIHJlbmRhbnQgY29tcHRlIGRlcyBzdHJ1Y3R1cmVzIGNvbW11bmVzIGF1eCBkaWZmw6lyZW50ZXMgY2xhc3Nlcy4KCkxvcnNxdWUgbGVzIHZhcmlhYmxlcyBhY3RpdmVzIHNvbnQgY2F0w6lnb3JpZWxsZXMsIG9uIHLDqWFsaXNlIHVuZSAqKkFDTSBjb25kaXRpb25uZWxsZSoqIChFc2NvZmllciwgMTk5MCBldCBmb25jdGlvbiBbYHdjTUNBKClgXShodHRwczovL25pY29sYXMtcm9iZXR0ZS5naXRodWIuaW8vR0RBdG9vbHMvcmVmZXJlbmNlL3djTUNBLmh0bWwpKS4gTCdhcHByb2NoZSBjb25kaXRpb25uZWxsZSBzJ2FwcGxpcXVlIGF1c3NpIGF1IGNhcyBkZSB2YXJpYWJsZXMgYWN0aXZlcyBudW3DqXJpcXVlcyAoZm9uY3Rpb24gW2B3Y1BDQSgpYF0oaHR0cHM6Ly9uaWNvbGFzLXJvYmV0dGUuZ2l0aHViLmlvL0dEQXRvb2xzL3JlZmVyZW5jZS93Y1BDQS5odG1sKSkuCgpPbiBjb25zdHJ1aXQgbWFpbnRlbmFudCBsJ2VzcGFjZSBkZXMgZ2/Du3RzIGNvbW11bnMgYXV4IGZlbW1lcyBldCBhdXggaG9tbWVzLCBvdSwgZGl0IGF1dHJlbWVudCwgZW4gbmV1dHJhbGlzYW50IGxlcyBkaWZmw6lyZW5jZXMgc2V4dcOpZXMuCgpgYGB7ciB3Y193Y2F9Cmp1bmsgPC0gYygiRnJlbmNoUG9wLk5BIiwgIlJhcC5OQSIsICJSb2NrLk5BIiwgIkphenouTkEiLCAiQ2xhc3NpY2FsLk5BIiwKICAgICAgICAgICJDb21lZHkuTkEiLCAiQ3JpbWUuTkEiLCAiQW5pbWF0aW9uLk5BIiwgIlNjaUZpLk5BIiwgIkxvdmUuTkEiLCAKICAgICAgICAgICJNdXNpY2FsLk5BIikKaW50cmEgPC0gd2NNQ0EoVGFzdGVbLDE6MTFdLCBUYXN0ZSRHZW5kZXIsIGV4Y2wgPSBqdW5rKQpgYGAKCkRhbnMgbGUgcGxhbiAoMSwyKSwgbGVzIHLDqXN1bHRhdHMgb2J0ZW51cyBzb250IHRyw6hzIHByb2NoZXMgZGUgY2V1eCBkZSBsJ0FDTSBzcMOpY2lmaXF1ZS4gTGEgcHJpbmNpcGFsZSBkaWZmw6lyZW5jZSBjb25jZXJuZSBsZSBnb8O7dCAodHLDqHMgZsOpbWluaW4pIHBvdXIgbGVzIGZpbG1zIGQnYW1vdXIsIHF1aSBzZSBkaXN0aW5ndWUgbmV0dGVtZW50IG1vaW5zIHN1ciBsJ2F4ZSAyLgoKYGBge3Igd2NfcGxvdHZhcn0KZ2djbG91ZF92YXJpYWJsZXMoaW50cmEsIGxlZ2VuZCA9ICJub25lIikgKyAKICBnZ3RpdGxlKCJBbmFseXNlIGludHJhLWNsYXNzZXMiKQpgYGAKCkNlbGEgc2UgY29uZmlybWUgw6AgbCdleGFtZW4gcHLDqWNpcyBkZXMgY29udHJpYnV0aW9ucyA6IGxlIGdvw7t0IHBvdXIgbGVzIGZpbG1zIGQnYW1vdXIgbmUgY29udHJpYnVlIHF1J8OgIGhhdXRldXIgZGUgNiUgw6AgbGEgY29uc3RydWN0aW9uIGRlIGwnYXhlIDIsIGNvbnRyZSAxNyUgcG91ciBsJ0FDTS4KCmBgYHtyIHdpdGhpbl90YWIyLCBldmFsPUZBTFNFfQp0YWJjb250cmliKGludHJhLCBkaW0gPSAyKQpgYGAKCmBgYHtyIHdpdGhpbl90YWIyYmlzLCBlY2hvPUZBTFNFfQprbml0cjo6a2FibGUodGFiY29udHJpYihpbnRyYSwgZGltID0gMiksIHJvdy5uYW1lcz1GQUxTRSkKYGBgCg==