Pour les utilisateurs de R, on commence par charger un certain nombre de modules intéressants.

# le package moderne standard pour manipuler ses données
library(tidyverse)
# pour faire des graphes multiples
library(cowplot)
# pour afficher joliment les tableaux dans un document rmarkdown
library(knitr)

# quelques couleurs manuelles
bleufonce <- "#3d5468"
bleuclair <- "#5b7c98"
rose <- "#ff5555"

On importe ensuite nos données pour travailler dessus :

d <- read.csv("etude_fictive_diabetes.csv")
head(d)
##   X id   group sex age height weight_t0 diabetes steps_t0 t_compliance
## 1 1  1 control   F  72   1.61     80.25       II     2642           32
## 2 2  2 control   M  71   1.81     79.66        I     1675           48
## 3 3  3 control   F  32   1.72     67.25       II     2698            8
## 4 4  4 control   M  76   1.74     73.59        I     2519            2
## 5 5  5 control   F  34   1.53     83.98        I     2764           12
## 6 6  6 control   F  70   1.71     69.14       II     2606           24
##     age_group      bmi bmi_group diff_weight diff_steps is_depressed
## 1  plus de 60 30.95945     obese  -3.6168091  -26.66633            1
## 2  plus de 60 24.31550    normal  -2.6491665  656.62903            1
## 3 moins de 40 22.73188    normal  -1.5288284   17.31293            0
## 4  plus de 60 24.30638    normal  -3.0515988 -589.42996            1
## 5 moins de 40 35.87509     obese   0.8933788  761.91017            1
## 6  plus de 60 23.64488    normal  -0.3074968  216.47307            1
##   feels_sleepy n_meals_glycemia_out_of_target weight_tf steps_tf
## 1      morning                             53  76.63319 2615.334
## 2      morning                             19  77.01083 2331.629
## 3      morning                              4  65.72117 2715.313
## 4      morning                             17  70.53840 1929.570
## 5    afternoon                            188  84.87338 3525.910
## 6       always                              9  68.83250 2822.473

Nous sommes prêts à travailler sur ce jeu de données, et, pour ce qui nous intéresse ici, à analyser les données discrètes qu’il renferme.

Comparaison de proportions

Visuellement

Nous avons deux colonnes binaires qui se prêtent bien à ce premier exercice : sex et is_depressed. On souhaiterait savoir si

  1. la proportion de femmes dans l’ensemble de l’étude est différente de 50%,
  2. les deux groupes contiennent la même proportion de femmes,
  3. les deux groupes contiennent la même proportion de patients en dépression.

Comme toujours en stats, il est intéressant de représenter les données pour se faire une idée intuitive !

p1 <- d %>% 
    ggplot(aes(x = group, fill = sex)) +
    geom_bar(position = "dodge", alpha=0.7) +
    theme_bw() + 
    scale_fill_manual(values=c(bleuclair, rose))

p2 <- d %>% 
    mutate(is_depressed = as.factor(ifelse(is_depressed==1, "patient dépressif", "patient non dépressif"))) %>%
    ggplot(aes(x = group, fill = is_depressed)) +
    geom_bar(position = "dodge", alpha=0.7) +
    theme_bw() + 
    ylab("nombre") +
    xlab("") +
    scale_fill_manual(values=c(bleuclair, rose))

plot_grid(p1, p2, nrow=1, ncol=2, label_size = 12)

# Les deux lignes commentées suivantes permettent d'enregistrer les figures
#ggsave("../../Figures/illu_diabete_is_depressed_barplot.pdf", plot=p2, width=8, height=5)
#ggsave("../../Figures/illu_diabete_sex_barplot.pdf", plot=p1, width=8, height=5)

A présent qu’on voit un peu mieux à quoi ressemblent nos données, on aimerait savoir si les différences de hauteur de barres sont dues à des fluctuations d’échantillonnage ou non.

Avec un attendu théorique binomial

On souhaite tout d’abord comparer notre proportion observée avec une certaine proportion fixée, et répondre à la question suivante :

La proportion de femmes dans l’ensemble de l’étude est-elle différente de 50% ?

La première solution – appelons-la “solution exacte” – consiste à s’appuyer sur la distribution attendue sous H0 du nombre de succès dans notre suite d’essais, avec une probabilité de succès fixée.

Attendu théorique

Le nombre de femmes (succès) dans un échantillon de 500 individus (essais) avec une proportion de 50% de femmes (probabilité de succès) est distribué selon une loi binomiale de paramètre \(\mathcal{B}(n=500, p=0.5)\).

On va tout simplement comparer le nombre de femmes observées dans notre échantillon à cette distribution théorique sous H0, de façon à rejeter H0 si la valeur observée est trop extrême, ou à conserver H0 dans le cas inverse.

Et avec R ?

La syntaxe est la suivante :

t <- table(d$sex)
kable(t)
Var1 Freq
F 250
M 250
res <- binom.test(t, p=0.5, conf.level=0.95)
res
## 
##  Exact binomial test
## 
## data:  t
## number of successes = 250, number of trials = 500, p-value = 1
## alternative hypothesis: true probability of success is not equal to 0.5
## 95 percent confidence interval:
##  0.4552856 0.5447144
## sample estimates:
## probability of success 
##                    0.5

Que concluons-nous ?

Avec un attendu théorique : version \(\chi^2\)

La seconde solution pour répondre à la même question consiste à faire un test du \(\chi^2\), qui n’est pas un test exact mais repose sur un résultat théorique asymptotique.

  • Inconvénient : non-exact et repose sur un théorème compliqué,
  • Avantage : très général et largement connu !

Ce que dit la théorie

On dispose d’un échantillon de \(n\) valeurs observées \((X_1, X_2, ..., X_n)\) qui prennent leurs valeurs dans un ensemble discret de \(d\) valeurs \(\mathcal{X} = \{ x_1, x_2, ..., x_d \}\).

On considère que nos \(n\) observations proviennent de variables aléatoires indépendantes et identiquement distribuées dans une même loi qu’on note \(p = (p_1, p_2, ..., p_d)\), où \(p_i\) est la probabilité que la variable aléatoire prenne la valeur \(x_i\).

Par ailleurs, on dispose d’une loi de référence sur \(\mathcal{X}\) qu’on appelle \(p^\text{ref}\), à laquelle on souhaiterait comparer notre échantillon. Plus précisément, on souhaite tester :

  • H0 : \(p = p^\text{ref}\)
  • H1 : \(p \neq p^\text{ref}\).

On peut estimer chacun des \(p_i\) par la fréquence empirique \(\hat{p}_i\) de l’observation \(x_i\), c’est à dire : \[ \hat{p}_i = \frac{1}{n} \sum_{k=1}^n \delta_{X_k = x_i} \] où on note \(\delta_{X_k = x_i}\) la variable indicatrice de l’événement \(X_k = x_i\), c’est à dire la variable valant 1 lorsque \(X_k = x_i\) et 0 sinon.

On souhaite ensuite mesurer une certaine idée de “distance” entre la distribution empiriquement observée \(\hat{p}\) et celle de référence \(p^\text{ref}\). Cette distance est appelée “pseudo-distance du \(\chi^2\)”, et elle s’exprime comme :

\[ D^2_n( \hat{p}, p^\text{ref} ) = n \sum_{i=1}^d \frac{\left(\hat{p}_i - p^\text{ref}_i \right)^2}{p^\text{ref}_i} \]

Un théorème nous assure alors que :

  • sous H0, \(D^2_n ( \hat{p}, p^\text{ref} )\) tend en loi vers une distribution du \(\chi^2\) à \(d-1\) degrés de liberté.
  • sous H1, \(D^2_n ( \hat{p}, p^\text{ref} )\) tend vers l’infini presque sûrement.

Utilisation de la théorie pour construire un test

Ce théorème est un théorème asymptotique : on ne peut donc l’appliquer pour effectuer notre test sur de vraies données que pour de grands effectifs \(n\). On demande aussi généralement à ce que \(n p^\text{ref}_i\) soit au moins égal à 5 pour tout \(i\).

Si ces conditions d’effectifs suffisants sont respectées, le test fonctionne ainsi :

  • la statistique de test \(D^2_n( \hat{p}, p^\text{ref} )\) est calculée,
  • on fixe l’erreur de premier ordre \(\alpha\) acceptable,
  • on calcule \(q_{1-\alpha}\), le quantile de la loi du \(\chi^2\) à \(d-1\) degrés de liberté de niveau \(1-\alpha\),
  • on rejette H0 si \(D^2_n( \hat{p}, p^\text{ref} ) > q_{1-\alpha}\).

Intuitivement, on mesure une pseudo-distance entre notre distribution empirique et notre distribution de référence, et on rejette l’hypothèse nulle d’égalité des deux distributions lorsque cette pseudo-distance devient trop grande.

Et avec R ?

En une ligne très simple, est-ce que le sexe est distribué de façon uniforme ?

pobs <- t
pth <- c(0.5, 0.5)
res <- chisq.test(x=pobs, p=pth)
res
## 
##  Chi-squared test for given probabilities
## 
## data:  pobs
## X-squared = 0, df = 1, p-value = 1

Note pour les utilisateurs de R : une fonction alternative existe pour faire la même chose :

prop.test(t, p=0.5, conf.level=0.95)
## 
##  1-sample proportions test without continuity correction
## 
## data:  t, null probability 0.5
## X-squared = 0, df = 1, p-value = 1
## alternative hypothesis: true p is not equal to 0.5
## 95 percent confidence interval:
##  0.4563413 0.5436587
## sample estimates:
##   p 
## 0.5

On remarque une très légère différence de p-value, qui est due à un paramètre par défaut de “correction de continuité” dans cette fonction. On retrouvera un résultat tout à fait similaire à un bon vieux \(\chi^2\) avec la commande suivante :

prop.test(t, p=0.5, conf.level=0.95, correct=F)
## 
##  1-sample proportions test without continuity correction
## 
## data:  t, null probability 0.5
## X-squared = 0, df = 1, p-value = 1
## alternative hypothesis: true p is not equal to 0.5
## 95 percent confidence interval:
##  0.4563413 0.5436587
## sample estimates:
##   p 
## 0.5

Que concluons-nous ?

Compréhension visuelle du test

Dobs <- res$statistic
# On représente la densité de la loi du chi2 qui nous intéresse
x <- seq(0, 10, 0.01)
data_chisq <- data.frame(x = x,
                         density = dchisq(x, df=res$parameter),
                         cdf = pchisq(x, df=res$parameter))

plot_density <- data_chisq %>% ggplot(aes(x = x, y = density)) +
    geom_line() +
    geom_vline(xintercept = Dobs, lty="dashed") +
    geom_area(data=data_chisq[x > Dobs,], fill="red") +
    ylab("Densité sous H0") +
    xlab("statistique de test") +
    theme_bw()

plot_tail <- data_chisq %>% ggplot() +
    geom_line(aes(x = x, y = 1-cdf)) +
    geom_hline(yintercept = res$p.value, lty="dashed", color="red") +
    geom_vline(xintercept = Dobs, lty="dashed") +
    ylab("Queue de distribution sous H0") +
    xlab("statistique de test") +
    theme_bw()

plot_grid(plot_density, plot_tail, ncol = 1, nrow = 2)

Entre groupes : version Fisher

A présent, on souhaite comparer nos proportions de femmes dans les deux bras du RCT, on construit donc la table de contingence suivante :

t2 <- table(d$sex, d$group)
kable(t2)
control treatment
F 127 123
M 123 127

On considère ensuite qu’on connaît très exactement le nombre d’hommes et de femmes, ainsi que le nombre de patients dans les groupes traitement et contrôle :

rowSums(t2)
##   F   M 
## 250 250
colSums(t2)
##   control treatment 
##       250       250

La distribution des tables de contingence qu’on attend sous H0 est celle qui correspond à l’indépendance des deux marginales, c’est à dire celle où on obtient le contenu de la première colonne en piochant sans remise 250 fois dans le pool connu de femmes et d’hommes.

Note : ça ne correspond pas totalement à l’expérience réelle, dans laquelle techniquement seul le nombre de patients dans les deux groupes est connu. Il existe des alternatives à ça, où on ne considère qu’une seule des marginales comme étant fixée. Mais comme en pratique personne n’utilise ces alternatives, on n’en parlera pas plus.

En R, on applique ce test avec la commande suivante :

fisher.test(t2, conf.level=0.95)
## 
##  Fisher's Exact Test for Count Data
## 
## data:  t2
## p-value = 0.7885
## alternative hypothesis: true odds ratio is not equal to 1
## 95 percent confidence interval:
##  0.7393262 1.5372832
## sample estimates:
## odds ratio 
##   1.065954

Que concluons-nous ?

Entre groupes : version \(\chi^2\)

Contexte pratique

On continue de filer le même exemple que précédemment. Pour chaque individu \(i\), on observe le sexe \(X_i\) et le groupe \(Y_i\). On se demande si le choix du groupe est indépendant du sexe.

  • H0 : le choix du groupe est indépendant du sexe (i.e. la loi jointe est la loi produit des deux marginales),
  • H1 : le choix du groupe dépend du sexe.

L’idée intuitive est similaire à ce qui précède : on considère encore comme hypothèse nulle l’indépendance des marginales.

La distribution théorique attendue est donc :

p_sex <- rowSums(t2)/sum(t2)
p_group <- colSums(t2)/sum(t2)
pth <- p_sex %o% p_group
pth
##   control treatment
## F    0.25      0.25
## M    0.25      0.25

Que l’on compare à la distribution observée :

t2 / sum(t2)
##    
##     control treatment
##   F   0.254     0.246
##   M   0.246     0.254

Contexte formalisé

On dispose cette fois de \(n\) couples d’observations de variables discrètes \((X_i, Y_i)\) mesurées sur chaque individu \(i\). Les \((X_i)\) sont à valeur dans un ensemble \(\mathcal{X}\) à \(d\) éléments, tandis que les \((Y_i)\) sont à valeur dans un ensemble \(\mathcal{Y}\) à \(e\) éléments.

On suppose que nos couples sont indépendants et identiquement distribués selon une loi \(\nu\) sur \(\mathcal{X} \times \mathcal{Y}\). Typiquement, une telle distribution serait paramétrée par \((d e - 1)\) valeurs.

On souhaite à présent savoir si les \(X\) et les \(Y\) prennent leurs valeurs indépendamment l’un de l’autre, c’est à dire si la loi \(\nu\) appartient à la famille des lois produit. On note cette famille : \[ \mathcal{F} = \{ p \otimes q, ~ p \in \mathcal{P}(\mathcal{X}), q \in \mathcal{P}(\mathcal{Y}) \} \]\(\mathcal{P}(\mathcal{X})\) désigne l’ensemble des distributions sur l’ensemble discret \(\mathcal{X}\).

Typiquement, une distribution de \(\mathcal{F}\) n’est donc plus paramétrée que par \((d + e - 2)\) valeurs : les \((d-1)\) valeurs qui paramétrisent \(p\) et les \((e-1)\) valeurs qui paramétrisent \(q\).

Avec ces notations, on pourra tester :

  • H0 : \(\nu \in \mathcal{F}\),
  • H1 : \(\nu \notin \mathcal{F}\).

Ce que dit la théorie

On peut estimer chacun des \(p_i\) par la fréquence empirique de l’observation \(x_i\), c’est à dire : \[ \hat{p}_i = \frac{1}{n} \sum_{k=1}^n \delta_{X_k = x_i} \] On peut également faire de même pour les fréquences empiriques des \(y_j\), en calculant : \[ \hat{q}_j = \frac{1}{n} \sum_{k=1}^n \delta_{Y_k = y_j} \] Et enfin, faire de même pour les fréquences empiriques des différents couples \((x_i, y_j)\) : \[ \hat{\nu}_{i,j} = \frac{1}{n} \sum_{k=1}^n \delta_{(X_k, Y_k) = (x_i, y_j)} \]

On souhaite ensuite mesurer la “pseudo-distance du \(\chi^2\)” entre la distribution empiriquement observée \(\hat{\nu}\) et celle donnée par le meilleur ajustement à une loi produit \(\hat{p} \otimes \hat{q}\), soit : \[ D^2_n( \hat{\nu}, \hat{p}\otimes\hat{q} ) = n \sum_{i=1}^d \sum_{j=1}^e \frac{\left( \hat{\nu}_{i,j} - \hat{p}_i \hat{q}_j \right)^2}{\hat{p}_i \hat{q}_j} \]

Un théorème nous assure alors que

  • sous H0, \(D^2_n( \hat{\nu}, \hat{p}\otimes\hat{q} )\) tend en loi vers une distribution du \(\chi^2\) à \((d-1) (e-1)\) degrés de liberté.
  • sous H1, \(D^2_n( \hat{\nu}, \hat{p}\otimes\hat{q} )\) tend vers l’infini presque sûrement.

Pour ceux qui souhaiteraient savoir d’où vient le nombre de degrés de liberté, notez que, comme précédemment, il correspond à la dimension de l’espace dans lequel vit la loi de probabilité \(\nu\) (soit \(de -1\)) moins la dimension de l’espace dans lequel vit la distribution à laquelle on souhaite se ramener (ici \(d + e - 2\)).

Utilisation de la théorie pour construire un test

Comme toujours avec les tests du \(\chi^2\), notre test repose sur un théorème asymptotique : on ne peut donc l’appliquer pour effectuer notre test sur de vraies données que pour de grands effectifs \(n\) !

Si ces conditions d’effectifs suffisants sont respectées, le test fonctionne ainsi :

  • la statistique de test \(D^2_n( \hat{\nu}, \hat{p}\otimes\hat{q} )\) est calculée,
  • on fixe l’erreur de premier ordre \(\alpha\) acceptable,
  • on calcule \(q_{1-\alpha}\), le quantile de niveau \(1-\alpha\) de la loi du \(\chi^2\) à \((d-1) (e-1)\) degrés de liberté,
  • on rejette H0 si \(D^2_n( \hat{\nu}, \hat{p}\otimes\hat{q} ) > q_{1-\alpha}\).

Intuitivement, on mesure une pseudo-distance entre notre distribution empirique et notre meilleure estimation de loi produit, et on rejette l’hypothèse nulle d’égalité des deux distributions lorsque cette pseudo-distance devient trop grande.

Et avec R ?

Dans notre exemple, on souhaite tester l’indépendance du sexe et du groupe chez nos individus. En une ligne, il nous suffit d’écrire :

res <- chisq.test(t2)
res
## 
##  Pearson's Chi-squared test with Yates' continuity correction
## 
## data:  t2
## X-squared = 0.072, df = 1, p-value = 0.7884

Là encore, une fonction alternative existe :

prop.test(t2)
## 
##  2-sample test for equality of proportions with continuity correction
## 
## data:  t2
## X-squared = 0.072, df = 1, p-value = 0.7884
## alternative hypothesis: two.sided
## 95 percent confidence interval:
##  -0.07564103  0.10764103
## sample estimates:
## prop 1 prop 2 
##  0.508  0.492

Cette fois-ci, le comportement des deux fonctions est le même par défaut, avec cette “correction de continuité” dans les deux cas.

Que concluons-nous ?

Vérifier que les tests de \(\chi^2\) et de Fisher semblent cohérents.

Sur l’outcome de dépression

Il ne reste qu’à ré-appliquer les même fonctions que précedemment avec une variable différente :

t3 <- table(d$is_depressed, d$group)
fisher.test(t3)
## 
##  Fisher's Exact Test for Count Data
## 
## data:  t3
## p-value = 0.0001962
## alternative hypothesis: true odds ratio is not equal to 1
## 95 percent confidence interval:
##  0.3413613 0.7260068
## sample estimates:
## odds ratio 
##  0.4986961
chisq.test(t3)
## 
##  Pearson's Chi-squared test with Yates' continuity correction
## 
## data:  t3
## X-squared = 13.801, df = 1, p-value = 0.0002032

Que concluons-nous ?

Pour des données catégorielles

La bonne nouvelle, c’est que tout est très similaire à ce qu’on vient de voir.

D’ailleurs, tout ce que je pouvais écrire de façon générale précédemment a été écrit de façon générale, avec plus de deux catégories notamment.

Il ne reste donc qu’à s’entraîner ! On s’intéresse à présent aux données d’outcome feels_sleepy.

d %>% ggplot(aes(x = group, fill = feels_sleepy)) +
    geom_bar(position = "dodge") +
    theme_bw() + 
    scale_fill_manual(values=c(bleuclair, bleufonce, rose))

Avec une loi donnée

On peut utiliser un test du \(\chi^2\), par exemple pour tester la différence de la distribution observée par rapport à une distribution homogène sur les trois catégories :

t4 <- table(d$feels_sleepy)
kable(t4)
Var1 Freq
afternoon 174
always 190
morning 136
chisq.test(t4, p=c(1/3, 1/3, 1/3))
## 
##  Chi-squared test for given probabilities
## 
## data:  t4
## X-squared = 9.232, df = 2, p-value = 0.009892

Pour les utilisateurs de R, notons que, par défaut, le comportement de la fonction chisq.test à qui on ne donne pas d’argument p est justement de comparer à une distribution uniforme :

chisq.test(t4)
## 
##  Chi-squared test for given probabilities
## 
## data:  t4
## X-squared = 9.232, df = 2, p-value = 0.009892

Que concluons-nous ?

Entre groupes : version Fisher

Le même principe que dans le cas 2x2 est appliqué sur notre table de contingence 3x2.

t5 <- table(d$feels_sleepy, d$group)
kable(t5)
control treatment
afternoon 103 71
always 74 116
morning 73 63
fisher.test(t5)
## 
##  Fisher's Exact Test for Count Data
## 
## data:  t5
## p-value = 0.0003521
## alternative hypothesis: two.sided

Que concluons-nous ?

Entre groupes : version \(\chi^2\)

chisq.test(t5)
## 
##  Pearson's Chi-squared test
## 
## data:  t5
## X-squared = 15.905, df = 2, p-value = 0.0003519

Encore un résultat bien similaire à celui du test de Fisher : ouf !

Que concluons-nous ?

Régression logistique

Jusqu’à présent, nous avons cherché à rejeter une hypothèse nulle d’homogénéité d’un certain nombre de distributions discrètes, définies selon la valeur d’une covariable. Mais nous n’avons pas quantifié l’impact de cette covariable sur notre distribution d’intérêt. Nous n’étions également pas en mesure de regarder l’impact d’une variable continue sur un outcome binaire.

Le cadre approprié pour faire ça est celui de la régression logistique.

Cadre formel

Dans la régression logistique, on s’intéresse à une variable de réponse \(Y\) binaire, qu’on souhaite expliquer avec des variables \(X_1, X_2, ..., X_n\) discrètes ou continues.

On suppose que la loi de \(Y\) sachant \(X\) est une loi de Bernoulli, de paramètre \(p(X)\).

On souhaite modéliser \(p(X)\) en prenant en compte n’importe quelle somme de variables explicatives, mais toujours de façon à avoir \(p(X) \in (0,1)\).

Onn choisit la fonction suivante :

\[ p(X) = \frac{1}{1 + e^{-(a_0 + a_1 x_1 + a_2 x_2 + ... + a_n x_n)}} \\ \ln \left( \frac{p}{1-p} \right) = a_0 + a_1 x_1 + a_2 x_2 + ... + a_n x_n \]

Sur la première ligne, la fonction de droite \(u \mapsto \frac{1}{1+e^{-u}}\) est appelée fonction logistique. Sur la seconde ligne, la fonction de gauche \(u \mapsto \ln \frac{u}{1-u}\) est la réciproque de la fonction logistique, appelée fonction logit.

Exemple avec une covariable continue

On commence avec une covariable continue, de façon à se faire une idée visuelle de la forme de la fonction logistique.

On souhaiterait dans un premier temps expliquer is_depressed à partir du poids initial weight_t0. Première chose à faire : un graphe !

p <- d %>% ggplot(aes(x=weight_t0, y=as.numeric(is_depressed))) + 
    geom_point() +
    theme_bw() +
    xlab("Poids initial (kg)") +
    ylab("Indicatrice de dépression")
p

L’illustration suivante superpose à ce premier graphique le fit d’un modèle logistique, en comparaison avec ce qui pourrait être une mauvaise utilisation d’un simple modèle linéaire.

p + geom_smooth(formula = "y~x",
                method = "glm",
                method.args = list(family = binomial),
                se = FALSE,
                color = bleuclair) +
    geom_smooth(formula = "y~x", method = "lm", color = rose, se = F) 

L’avantage de la régression logistique saute aux yeux : on prédit bien une variable entre 0 et 1, et la valeur de la courbe bleue peut directement s’interpréter comme la probabilité, pour un patient ayant le poids en abscisse, d’avoir perdu du poids à l’issue de l’essai.

On peut récupérer les valeurs de notre fit avec un appel à :

res <- glm("is_depressed ~ weight_t0", family=binomial, data=d)
summary(res)
## 
## Call:
## glm(formula = "is_depressed ~ weight_t0", family = binomial, 
##     data = d)
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept) -14.82559    1.32129  -11.22   <2e-16 ***
## weight_t0     0.20773    0.01873   11.09   <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 680.29  on 499  degrees of freedom
## Residual deviance: 443.24  on 498  degrees of freedom
## AIC: 447.24
## 
## Number of Fisher Scoring iterations: 5

Les coefficients renvoyés correspondent à ceux de la combinaison linéaire de variables explicatives, c’est à dire aux \((a_i)\) des formules pré-citées. Dans notre cas, le coefficient en face de weight_t0 est positif, donc un patient plus lourd initialement est plus susceptible d’être dépressif à l’issue de l’essai.

Pour se donner une idée quantitative de l’impact d’un kg supplémentaire au début de l’essai, on peut calculer rapidement l’odds ratio associé à ce kg supplémentaire en prenant l’exponentielle de notre paramètre. En effet,

\[ \begin{align*} \frac{ \frac{ p(X_1 = x+1) }{ 1 - p(X_1 = x + 1) } }{ \frac{ p(X_1=x) }{ 1 - p(X_1 = x) } } &= \exp \ln \frac{ \frac{ p(X_1 = x+1) }{ 1 - p(X_1 = x + 1) } }{ \frac{ p(X_1=x) }{ 1 - p(X_1 = x) } } \\ &= \exp \left( \ln \frac{ p(X_1 = x+1) }{ 1 - p(X_1 = x + 1) } - \ln \frac{ p(X_1=x) }{ 1 - p(X_1 = x) } \right) \\ &= \exp \left( a_0 + a_1(x + 1) - a_0 - a_1x \right) \\ &= \exp a_1 \end{align*} \]

Dans notre cas, ça donne :

exp(coef(res)[2])
## weight_t0 
##  1.230876

L’output de la régression logistique nous donne aussi la p-value associée au test de non-nullité de nos coefficients de régression. Ici, les p-values sont petites, on peut donc rejeter l’hypothèse nulle de nullité de nos deux coefficients.

Sur un exemple différent, en cherchant à expliquer sex à partir de age:

dbis <- d %>% mutate(sex = ifelse(sex == "F", 1, 0))
res <- glm("sex ~ age", family=binomial, data=dbis)
summary(res)
## 
## Call:
## glm(formula = "sex ~ age", family = binomial, data = dbis)
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)
## (Intercept)  0.262221   0.265448   0.988    0.323
## age         -0.005438   0.005182  -1.049    0.294
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 693.15  on 499  degrees of freedom
## Residual deviance: 692.04  on 498  degrees of freedom
## AIC: 696.04
## 
## Number of Fisher Scoring iterations: 3

Cette fois les p-values sont grandes, et on ne peut pas conclure à une différence significative de nos paramètres par rapport à zéro.

Exemple avec une covariable discrète

Tout l’intérêt d’une régression logistique est de pouvoir utiliser tout ce qu’on souhaite comme variable explicative. Qu’obtient-on si on cherche à expliquer is_depressed à partir de group ?

Un dessin tout d’abord :

d %>% 
    ggplot(aes(x = group, fill = as.factor(is_depressed))) +
    geom_bar(position = "dodge") +
    theme_bw() + 
    scale_fill_manual(values=c(bleuclair, rose))

Et la régression logistique, ensuite :

res <- glm("is_depressed ~ group", family=binomial, data=d)
summary(res)
## 
## Call:
## glm(formula = "is_depressed ~ group", family = binomial, data = d)
## 
## Coefficients:
##                Estimate Std. Error z value Pr(>|z|)    
## (Intercept)      0.0160     0.1265   0.126 0.899344    
## grouptreatment  -0.6972     0.1842  -3.785 0.000154 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 680.29  on 499  degrees of freedom
## Residual deviance: 665.73  on 498  degrees of freedom
## AIC: 669.73
## 
## Number of Fisher Scoring iterations: 4

La p-value petite nous pousse à conclure à un effet significativement différent de 0 du bras de traitement sur l’outcome.

Exemple multivarié

Comme dans le modèle linéaire, on peut proposer des effets additifs de différentes covariables. Par exemple, que se passe-t-il si on souhaite expliquer is_depressed à partir du group et de weight_t0 ?

res <- glm("is_depressed ~ group + weight_t0", family=binomial, data=d)
summary(res)
## 
## Call:
## glm(formula = "is_depressed ~ group + weight_t0", family = binomial, 
##     data = d)
## 
## Coefficients:
##                 Estimate Std. Error z value Pr(>|z|)    
## (Intercept)    -14.83797    1.35251  -10.97  < 2e-16 ***
## grouptreatment  -0.93792    0.24555   -3.82 0.000134 ***
## weight_t0        0.21453    0.01945   11.03  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 680.29  on 499  degrees of freedom
## Residual deviance: 428.01  on 497  degrees of freedom
## AIC: 434.01
## 
## Number of Fisher Scoring iterations: 5

Le modèle avec une influence des deux variables est bien supporté par nos données ! On obtient donc une estimation de l’effet du bras de traitement tout en ayant pris en compte l’influence d’une autre variable explicative.

Conclusion

Nous avons vu aujourd’hui un certain nombre de méthodes permettant d’analyser des variables discrètes. Les points principaux à retenir sont les suivants :

  1. pour des données binaires, un test exact de différence par rapport à une proportion fixée existe, mais un test du \(\chi^2\) fait aussi l’affaire.
  2. pour des données bivariées discrètes, un test de Fisher d’indépendance entre les deux variables existe, mais un test du \(\chi^2\) fait aussi l’affaire.
  3. certains préfèreront toujours effectuer un test de Fisher, considéré comme “exact” sous ses hypothèses. D’autres préfèrent le test du \(\chi^2\), plus général et “robuste”, mais reposant sur un résultat théorique asymptotique.
  4. la régression logistique permet d’étendre le framework de la régression linéaire à des données binaires.
LS0tCnRpdGxlOiAiU3RhdGlzdGlxdWVzIGF1IExBUEVDIC0tIEFuYWx5c2UgZGUgZG9ubsOpZXMgYmluYWlyZXMiCmF1dGhvcjogIk1hcmMgTWFuY2VhdSIKZGF0ZTogIjIwMjQtMDEiCm91dHB1dDogCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogVFJVRQogICAgdG9jX2RlcHRoOiAyCiAgICB0b2NfZmxvYXQ6IFRSVUUKICAgIGhpZ2hsaWdodDogInRhbmdvIgogICAgY29kZV9kb3dubG9hZDogVFJVRQotLS0KClBvdXIgbGVzIHV0aWxpc2F0ZXVycyBkZSBSLCBvbiBjb21tZW5jZSBwYXIgY2hhcmdlciB1biBjZXJ0YWluIG5vbWJyZSBkZSBtb2R1bGVzIGludMOpcmVzc2FudHMuCgpgYGB7cn0KIyBsZSBwYWNrYWdlIG1vZGVybmUgc3RhbmRhcmQgcG91ciBtYW5pcHVsZXIgc2VzIGRvbm7DqWVzCmxpYnJhcnkodGlkeXZlcnNlKQojIHBvdXIgZmFpcmUgZGVzIGdyYXBoZXMgbXVsdGlwbGVzCmxpYnJhcnkoY293cGxvdCkKIyBwb3VyIGFmZmljaGVyIGpvbGltZW50IGxlcyB0YWJsZWF1eCBkYW5zIHVuIGRvY3VtZW50IHJtYXJrZG93bgpsaWJyYXJ5KGtuaXRyKQoKIyBxdWVscXVlcyBjb3VsZXVycyBtYW51ZWxsZXMKYmxldWZvbmNlIDwtICIjM2Q1NDY4IgpibGV1Y2xhaXIgPC0gIiM1YjdjOTgiCnJvc2UgPC0gIiNmZjU1NTUiCmBgYAoKT24gaW1wb3J0ZSBlbnN1aXRlIG5vcyBkb25uw6llcyBwb3VyIHRyYXZhaWxsZXIgZGVzc3VzIDoKCmBgYHtyfQpkIDwtIHJlYWQuY3N2KCJldHVkZV9maWN0aXZlX2RpYWJldGVzLmNzdiIpCmhlYWQoZCkKYGBgCgpOb3VzIHNvbW1lcyBwcsOqdHMgw6AgdHJhdmFpbGxlciBzdXIgY2UgamV1IGRlIGRvbm7DqWVzLCBldCwgcG91ciBjZSBxdWkgbm91cyBpbnTDqXJlc3NlIGljaSwKw6AgYW5hbHlzZXIgbGVzIGRvbm7DqWVzIGRpc2Nyw6h0ZXMgcXUnaWwgcmVuZmVybWUuCgoKIyBDb21wYXJhaXNvbiBkZSBwcm9wb3J0aW9ucwoKIyMgVmlzdWVsbGVtZW50CgpOb3VzIGF2b25zIGRldXggY29sb25uZXMgYmluYWlyZXMgcXVpIHNlIHByw6p0ZW50IGJpZW4gw6AgY2UgcHJlbWllciBleGVyY2ljZSA6IGBzZXhgIGV0IGBpc19kZXByZXNzZWRgLgpPbiBzb3VoYWl0ZXJhaXQgc2F2b2lyIHNpCgooQCkgbGEgcHJvcG9ydGlvbiBkZSBmZW1tZXMgZGFucyBsJ2Vuc2VtYmxlIGRlIGwnw6l0dWRlIGVzdCBkaWZmw6lyZW50ZSBkZSA1MCUsCihAKSBsZXMgZGV1eCBncm91cGVzIGNvbnRpZW5uZW50IGxhIG3Dqm1lIHByb3BvcnRpb24gZGUgZmVtbWVzLAooQCkgbGVzIGRldXggZ3JvdXBlcyBjb250aWVubmVudCBsYSBtw6ptZSBwcm9wb3J0aW9uIGRlIHBhdGllbnRzIGVuIGTDqXByZXNzaW9uLgoKQ29tbWUgdG91am91cnMgZW4gc3RhdHMsIGlsIGVzdCBpbnTDqXJlc3NhbnQgZGUgcmVwcsOpc2VudGVyIGxlcyBkb25uw6llcyBwb3VyIHNlIGZhaXJlIHVuZSBpZMOpZSBpbnR1aXRpdmUgIQoKYGBge3IgZmlnLndpZHRoPTEwfQpwMSA8LSBkICU+JSAKCWdncGxvdChhZXMoeCA9IGdyb3VwLCBmaWxsID0gc2V4KSkgKwoJZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiLCBhbHBoYT0wLjcpICsKCXRoZW1lX2J3KCkgKyAKCXNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKGJsZXVjbGFpciwgcm9zZSkpCgpwMiA8LSBkICU+JSAKCW11dGF0ZShpc19kZXByZXNzZWQgPSBhcy5mYWN0b3IoaWZlbHNlKGlzX2RlcHJlc3NlZD09MSwgInBhdGllbnQgZMOpcHJlc3NpZiIsICJwYXRpZW50IG5vbiBkw6lwcmVzc2lmIikpKSAlPiUKCWdncGxvdChhZXMoeCA9IGdyb3VwLCBmaWxsID0gaXNfZGVwcmVzc2VkKSkgKwoJZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiLCBhbHBoYT0wLjcpICsKCXRoZW1lX2J3KCkgKyAKCXlsYWIoIm5vbWJyZSIpICsKCXhsYWIoIiIpICsKCXNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKGJsZXVjbGFpciwgcm9zZSkpCgpwbG90X2dyaWQocDEsIHAyLCBucm93PTEsIG5jb2w9MiwgbGFiZWxfc2l6ZSA9IDEyKQojIExlcyBkZXV4IGxpZ25lcyBjb21tZW50w6llcyBzdWl2YW50ZXMgcGVybWV0dGVudCBkJ2VucmVnaXN0cmVyIGxlcyBmaWd1cmVzCiNnZ3NhdmUoIi4uLy4uL0ZpZ3VyZXMvaWxsdV9kaWFiZXRlX2lzX2RlcHJlc3NlZF9iYXJwbG90LnBkZiIsIHBsb3Q9cDIsIHdpZHRoPTgsIGhlaWdodD01KQojZ2dzYXZlKCIuLi8uLi9GaWd1cmVzL2lsbHVfZGlhYmV0ZV9zZXhfYmFycGxvdC5wZGYiLCBwbG90PXAxLCB3aWR0aD04LCBoZWlnaHQ9NSkKYGBgCgpBIHByw6lzZW50IHF1J29uIHZvaXQgdW4gcGV1IG1pZXV4IMOgIHF1b2kgcmVzc2VtYmxlbnQgbm9zIGRvbm7DqWVzLApvbiBhaW1lcmFpdCBzYXZvaXIgc2kgbGVzIGRpZmbDqXJlbmNlcyBkZSBoYXV0ZXVyIGRlIGJhcnJlcyBzb250IGR1ZXMKw6AgZGVzIGZsdWN0dWF0aW9ucyBkJ8OpY2hhbnRpbGxvbm5hZ2Ugb3Ugbm9uLgoKIyMgQXZlYyB1biBhdHRlbmR1IHRow6lvcmlxdWUgYmlub21pYWwKCk9uIHNvdWhhaXRlIHRvdXQgZCdhYm9yZCBjb21wYXJlciBub3RyZSBwcm9wb3J0aW9uIG9ic2VydsOpZQphdmVjIHVuZSBjZXJ0YWluZSBwcm9wb3J0aW9uIGZpeMOpZSwgZXQgcsOpcG9uZHJlIMOgIGxhIHF1ZXN0aW9uIHN1aXZhbnRlIDoKCkxhIHByb3BvcnRpb24gZGUgZmVtbWVzIGRhbnMgbCdlbnNlbWJsZSBkZSBsJ8OpdHVkZSBlc3QtZWxsZSBkaWZmw6lyZW50ZSBkZSA1MCUgPwoKTGEgcHJlbWnDqHJlIHNvbHV0aW9uIC0tIGFwcGVsb25zLWxhICJzb2x1dGlvbiBleGFjdGUiIC0tIApjb25zaXN0ZSDDoCBzJ2FwcHV5ZXIgc3VyIGxhIGRpc3RyaWJ1dGlvbiBhdHRlbmR1ZSBzb3VzIEgwCmR1IG5vbWJyZSBkZSBzdWNjw6hzIGRhbnMgbm90cmUgc3VpdGUgZCdlc3NhaXMsIGF2ZWMgdW5lIHByb2JhYmlsaXTDqQpkZSBzdWNjw6hzIGZpeMOpZS4KCiMjIyBBdHRlbmR1IHRow6lvcmlxdWUKCkxlIG5vbWJyZSBkZSBmZW1tZXMgKHN1Y2PDqHMpIGRhbnMgdW4gw6ljaGFudGlsbG9uIGRlIDUwMCBpbmRpdmlkdXMgKGVzc2FpcykKYXZlYyB1bmUgcHJvcG9ydGlvbiBkZSA1MCUgZGUgZmVtbWVzIChwcm9iYWJpbGl0w6kgZGUgc3VjY8OocykgZXN0CmRpc3RyaWJ1w6kgc2Vsb24gdW5lIGxvaSBiaW5vbWlhbGUgZGUgcGFyYW3DqHRyZSAkXG1hdGhjYWx7Qn0obj01MDAsIHA9MC41KSQuCgpPbiB2YSB0b3V0IHNpbXBsZW1lbnQgY29tcGFyZXIgbGUgbm9tYnJlIGRlIGZlbW1lcyBvYnNlcnbDqWVzIGRhbnMgbm90cmUgw6ljaGFudGlsbG9uCsOgIGNldHRlIGRpc3RyaWJ1dGlvbiB0aMOpb3JpcXVlIHNvdXMgSDAsIGRlIGZhw6dvbiDDoCByZWpldGVyIEgwIHNpIGxhIHZhbGV1ciBvYnNlcnbDqWUKZXN0IHRyb3AgZXh0csOqbWUsIG91IMOgIGNvbnNlcnZlciBIMCBkYW5zIGxlIGNhcyBpbnZlcnNlLgoKIyMjIEV0IGF2ZWMgUiA/CgpMYSBzeW50YXhlIGVzdCBsYSBzdWl2YW50ZSA6CgpgYGB7cn0KdCA8LSB0YWJsZShkJHNleCkKa2FibGUodCkKcmVzIDwtIGJpbm9tLnRlc3QodCwgcD0wLjUsIGNvbmYubGV2ZWw9MC45NSkKcmVzCmBgYAoKPiBRdWUgY29uY2x1b25zLW5vdXMgPwoKCiMjIEF2ZWMgdW4gYXR0ZW5kdSB0aMOpb3JpcXVlIDogdmVyc2lvbiAkXGNoaV4yJAoKTGEgc2Vjb25kZSBzb2x1dGlvbiBwb3VyIHLDqXBvbmRyZSDDoCBsYSBtw6ptZSBxdWVzdGlvbiBjb25zaXN0ZSDDoCBmYWlyZSB1biB0ZXN0IGR1ICRcY2hpXjIkLApxdWkgbidlc3QgcGFzIHVuIHRlc3QgZXhhY3QgbWFpcyByZXBvc2Ugc3VyIHVuIHLDqXN1bHRhdCB0aMOpb3JpcXVlIGFzeW1wdG90aXF1ZS4KCi0gSW5jb252w6luaWVudCA6IG5vbi1leGFjdCBldCByZXBvc2Ugc3VyIHVuIHRow6lvcsOobWUgY29tcGxpcXXDqSwKLSBBdmFudGFnZSA6IHRyw6hzIGfDqW7DqXJhbCBldCBsYXJnZW1lbnQgY29ubnUgIQoKIyMjIENlIHF1ZSBkaXQgbGEgdGjDqW9yaWUKCk9uIGRpc3Bvc2UgZCd1biDDqWNoYW50aWxsb24gZGUgJG4kIHZhbGV1cnMgb2JzZXJ2w6llcyAkKFhfMSwgWF8yLCAuLi4sIFhfbikkIApxdWkgcHJlbm5lbnQgbGV1cnMgdmFsZXVycyBkYW5zIHVuIGVuc2VtYmxlIGRpc2NyZXQgZGUgJGQkIHZhbGV1cnMgJFxtYXRoY2Fse1h9ID0gXHsgeF8xLCB4XzIsIC4uLiwgeF9kIFx9JC4KCk9uIGNvbnNpZMOocmUgcXVlIG5vcyAkbiQgb2JzZXJ2YXRpb25zIHByb3ZpZW5uZW50IGRlIHZhcmlhYmxlcyBhbMOpYXRvaXJlcyBpbmTDqXBlbmRhbnRlcyBldCBpZGVudGlxdWVtZW50IGRpc3RyaWJ1w6llcyAKZGFucyB1bmUgbcOqbWUgbG9pIHF1J29uIG5vdGUgJHAgPSAocF8xLCBwXzIsIC4uLiwgcF9kKSQsIApvw7kgJHBfaSQgZXN0IGxhIHByb2JhYmlsaXTDqSBxdWUgbGEgdmFyaWFibGUgYWzDqWF0b2lyZSBwcmVubmUgbGEgdmFsZXVyICR4X2kkLgoKUGFyIGFpbGxldXJzLCBvbiBkaXNwb3NlIGQndW5lIGxvaSBkZSByw6lmw6lyZW5jZSBzdXIgJFxtYXRoY2Fse1h9JCBxdSdvbiBhcHBlbGxlICRwXlx0ZXh0e3JlZn0kLCAKw6AgbGFxdWVsbGUgb24gc291aGFpdGVyYWl0IGNvbXBhcmVyIG5vdHJlIMOpY2hhbnRpbGxvbi4KUGx1cyBwcsOpY2lzw6ltZW50LCBvbiBzb3VoYWl0ZSB0ZXN0ZXIgOgoKKiBIMCA6ICRwID0gcF5cdGV4dHtyZWZ9JAoqIEgxIDogJHAgXG5lcSBwXlx0ZXh0e3JlZn0kLgoKT24gcGV1dCBlc3RpbWVyIGNoYWN1biBkZXMgJHBfaSQgcGFyIGxhIGZyw6lxdWVuY2UgZW1waXJpcXVlICRcaGF0e3B9X2kkIGRlIGwnb2JzZXJ2YXRpb24gJHhfaSQsIGMnZXN0IMOgIGRpcmUgOgokJCBcaGF0e3B9X2kgPSBcZnJhY3sxfXtufSBcc3VtX3trPTF9Xm4gXGRlbHRhX3tYX2sgPSB4X2l9ICQkCm/DuSBvbiBub3RlICRcZGVsdGFfe1hfayA9IHhfaX0kIGxhIHZhcmlhYmxlIGluZGljYXRyaWNlIGRlIGwnw6l2w6luZW1lbnQgJFhfayA9IHhfaSQsIApjJ2VzdCDDoCBkaXJlIGxhIHZhcmlhYmxlIHZhbGFudCAxIGxvcnNxdWUgJFhfayA9IHhfaSQgZXQgMCBzaW5vbi4KCk9uIHNvdWhhaXRlIGVuc3VpdGUgbWVzdXJlciB1bmUgY2VydGFpbmUgaWTDqWUgZGUgImRpc3RhbmNlIiBlbnRyZSAKbGEgZGlzdHJpYnV0aW9uIGVtcGlyaXF1ZW1lbnQgb2JzZXJ2w6llICRcaGF0e3B9JCAKZXQgY2VsbGUgZGUgcsOpZsOpcmVuY2UgJHBeXHRleHR7cmVmfSQuIApDZXR0ZSBkaXN0YW5jZSBlc3QgYXBwZWzDqWUgInBzZXVkby1kaXN0YW5jZSBkdSAkXGNoaV4yJCIsIGV0IGVsbGUgcydleHByaW1lIGNvbW1lIDoKCiQkIApEXjJfbiggXGhhdHtwfSwgcF5cdGV4dHtyZWZ9ICkgPSBuIFxzdW1fe2k9MX1eZCBcZnJhY3tcbGVmdChcaGF0e3B9X2kgLSBwXlx0ZXh0e3JlZn1faSBccmlnaHQpXjJ9e3BeXHRleHR7cmVmfV9pfSAKJCQKClVuIHRow6lvcsOobWUgbm91cyBhc3N1cmUgYWxvcnMgcXVlIDoKCi0gc291cyBIMCwgJEReMl9uICggXGhhdHtwfSwgcF5cdGV4dHtyZWZ9ICkkIHRlbmQgZW4gbG9pIAogIHZlcnMgdW5lIGRpc3RyaWJ1dGlvbiBkdSAkXGNoaV4yJCDDoCAkZC0xJCBkZWdyw6lzIGRlIGxpYmVydMOpLgotIHNvdXMgSDEsICREXjJfbiAoIFxoYXR7cH0sIHBeXHRleHR7cmVmfSApJCB0ZW5kIHZlcnMgbCdpbmZpbmkgcHJlc3F1ZSBzw7tyZW1lbnQuCgojIyMgVXRpbGlzYXRpb24gZGUgbGEgdGjDqW9yaWUgcG91ciBjb25zdHJ1aXJlIHVuIHRlc3QKCkNlIHRow6lvcsOobWUgZXN0IHVuIHRow6lvcsOobWUgYXN5bXB0b3RpcXVlIDogb24gbmUgcGV1dCBkb25jIGwnYXBwbGlxdWVyIHBvdXIgZWZmZWN0dWVyIG5vdHJlIHRlc3QgCnN1ciBkZSB2cmFpZXMgZG9ubsOpZXMgcXVlIHBvdXIgZGUgZ3JhbmRzIGVmZmVjdGlmcyAkbiQuCk9uIGRlbWFuZGUgYXVzc2kgZ8OpbsOpcmFsZW1lbnQgw6AgY2UgcXVlICRuIHBeXHRleHR7cmVmfV9pJCBzb2l0IGF1IG1vaW5zIMOpZ2FsIMOgIDUgcG91ciB0b3V0ICRpJC4KClNpIGNlcyBjb25kaXRpb25zIGQnZWZmZWN0aWZzIHN1ZmZpc2FudHMgc29udCByZXNwZWN0w6llcywgbGUgdGVzdCBmb25jdGlvbm5lIGFpbnNpIDoKCi0gbGEgc3RhdGlzdGlxdWUgZGUgdGVzdCAkRF4yX24oIFxoYXR7cH0sIHBeXHRleHR7cmVmfSApJCBlc3QgY2FsY3Vsw6llLAotIG9uIGZpeGUgbCdlcnJldXIgZGUgcHJlbWllciBvcmRyZSAkXGFscGhhJCBhY2NlcHRhYmxlLAotIG9uIGNhbGN1bGUgJHFfezEtXGFscGhhfSQsIGxlIHF1YW50aWxlIGRlIGxhIGxvaSBkdSAkXGNoaV4yJCDDoCAkZC0xJCBkZWdyw6lzIGRlIGxpYmVydMOpIGRlIG5pdmVhdSAkMS1cYWxwaGEkLAotIG9uIHJlamV0dGUgSDAgc2kgJEReMl9uKCBcaGF0e3B9LCBwXlx0ZXh0e3JlZn0gKSA+IHFfezEtXGFscGhhfSQuCgpJbnR1aXRpdmVtZW50LCBvbiBtZXN1cmUgdW5lIHBzZXVkby1kaXN0YW5jZSBlbnRyZSBub3RyZSBkaXN0cmlidXRpb24gZW1waXJpcXVlIApldCBub3RyZSBkaXN0cmlidXRpb24gZGUgcsOpZsOpcmVuY2UsIApldCBvbiByZWpldHRlIGwnaHlwb3Row6hzZSBudWxsZSBkJ8OpZ2FsaXTDqSBkZXMgZGV1eCBkaXN0cmlidXRpb25zIApsb3JzcXVlIGNldHRlIHBzZXVkby1kaXN0YW5jZSBkZXZpZW50IHRyb3AgZ3JhbmRlLgoKIyMjIEV0IGF2ZWMgUiA/CgpFbiB1bmUgbGlnbmUgdHLDqHMgc2ltcGxlLCBlc3QtY2UgcXVlIGxlIHNleGUgZXN0IGRpc3RyaWJ1w6kgZGUgZmHDp29uIHVuaWZvcm1lID8KCmBgYHtyfQpwb2JzIDwtIHQKcHRoIDwtIGMoMC41LCAwLjUpCnJlcyA8LSBjaGlzcS50ZXN0KHg9cG9icywgcD1wdGgpCnJlcwpgYGAKCk5vdGUgcG91ciBsZXMgdXRpbGlzYXRldXJzIGRlIFIgOiB1bmUgZm9uY3Rpb24gYWx0ZXJuYXRpdmUgZXhpc3RlIHBvdXIgZmFpcmUgbGEgbcOqbWUgY2hvc2UgOgoKYGBge3J9CnByb3AudGVzdCh0LCBwPTAuNSwgY29uZi5sZXZlbD0wLjk1KQpgYGAKCk9uIHJlbWFycXVlIHVuZSB0csOocyBsw6lnw6hyZSBkaWZmw6lyZW5jZSBkZSBwLXZhbHVlLCBxdWkgZXN0IGR1ZSDDoCB1biBwYXJhbcOodHJlIHBhciBkw6lmYXV0CmRlICJjb3JyZWN0aW9uIGRlIGNvbnRpbnVpdMOpIiBkYW5zIGNldHRlIGZvbmN0aW9uLgpPbiByZXRyb3V2ZXJhIHVuIHLDqXN1bHRhdCB0b3V0IMOgIGZhaXQgc2ltaWxhaXJlIMOgIHVuIGJvbiB2aWV1eCAkXGNoaV4yJCBhdmVjIGxhIGNvbW1hbmRlIHN1aXZhbnRlIDoKCmBgYHtyfQpwcm9wLnRlc3QodCwgcD0wLjUsIGNvbmYubGV2ZWw9MC45NSwgY29ycmVjdD1GKQpgYGAKCj4gUXVlIGNvbmNsdW9ucy1ub3VzID8KCiMjIyBDb21wcsOpaGVuc2lvbiB2aXN1ZWxsZSBkdSB0ZXN0CgpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9OH0KRG9icyA8LSByZXMkc3RhdGlzdGljCiMgT24gcmVwcsOpc2VudGUgbGEgZGVuc2l0w6kgZGUgbGEgbG9pIGR1IGNoaTIgcXVpIG5vdXMgaW50w6lyZXNzZQp4IDwtIHNlcSgwLCAxMCwgMC4wMSkKZGF0YV9jaGlzcSA8LSBkYXRhLmZyYW1lKHggPSB4LAogICAgICAgICAgICAgICAgICAgICAgICAgZGVuc2l0eSA9IGRjaGlzcSh4LCBkZj1yZXMkcGFyYW1ldGVyKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGNkZiA9IHBjaGlzcSh4LCBkZj1yZXMkcGFyYW1ldGVyKSkKCnBsb3RfZGVuc2l0eSA8LSBkYXRhX2NoaXNxICU+JSBnZ3Bsb3QoYWVzKHggPSB4LCB5ID0gZGVuc2l0eSkpICsKICAgIGdlb21fbGluZSgpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IERvYnMsIGx0eT0iZGFzaGVkIikgKwogICAgZ2VvbV9hcmVhKGRhdGE9ZGF0YV9jaGlzcVt4ID4gRG9icyxdLCBmaWxsPSJyZWQiKSArCiAgICB5bGFiKCJEZW5zaXTDqSBzb3VzIEgwIikgKwogICAgeGxhYigic3RhdGlzdGlxdWUgZGUgdGVzdCIpICsKICAgIHRoZW1lX2J3KCkKCnBsb3RfdGFpbCA8LSBkYXRhX2NoaXNxICU+JSBnZ3Bsb3QoKSArCiAgICBnZW9tX2xpbmUoYWVzKHggPSB4LCB5ID0gMS1jZGYpKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSByZXMkcC52YWx1ZSwgbHR5PSJkYXNoZWQiLCBjb2xvcj0icmVkIikgKwogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gRG9icywgbHR5PSJkYXNoZWQiKSArCiAgICB5bGFiKCJRdWV1ZSBkZSBkaXN0cmlidXRpb24gc291cyBIMCIpICsKICAgIHhsYWIoInN0YXRpc3RpcXVlIGRlIHRlc3QiKSArCiAgICB0aGVtZV9idygpCgpwbG90X2dyaWQocGxvdF9kZW5zaXR5LCBwbG90X3RhaWwsIG5jb2wgPSAxLCBucm93ID0gMikKYGBgCgojIyBFbnRyZSBncm91cGVzIDogdmVyc2lvbiBGaXNoZXIgCgpBIHByw6lzZW50LCBvbiBzb3VoYWl0ZSBjb21wYXJlciBub3MgcHJvcG9ydGlvbnMgZGUgZmVtbWVzIGRhbnMgbGVzIGRldXggYnJhcyBkdSBSQ1QsCm9uIGNvbnN0cnVpdCBkb25jIGxhIHRhYmxlIGRlIGNvbnRpbmdlbmNlIHN1aXZhbnRlIDoKCmBgYHtyfQp0MiA8LSB0YWJsZShkJHNleCwgZCRncm91cCkKa2FibGUodDIpCmBgYAoKT24gY29uc2lkw6hyZSBlbnN1aXRlIHF1J29uIGNvbm5hw650IHRyw6hzIGV4YWN0ZW1lbnQgbGUgbm9tYnJlIGQnaG9tbWVzIGV0IGRlIGZlbW1lcywKYWluc2kgcXVlIGxlIG5vbWJyZSBkZSBwYXRpZW50cyBkYW5zIGxlcyBncm91cGVzIHRyYWl0ZW1lbnQgZXQgY29udHLDtGxlIDoKCmBgYHtyfQpyb3dTdW1zKHQyKQpjb2xTdW1zKHQyKQpgYGAKCkxhIGRpc3RyaWJ1dGlvbiBkZXMgdGFibGVzIGRlIGNvbnRpbmdlbmNlIHF1J29uIGF0dGVuZCBzb3VzIEgwIGVzdCBjZWxsZSBxdWkgY29ycmVzcG9uZCDDoCBsJyppbmTDqXBlbmRhbmNlKgpkZXMgZGV1eCBtYXJnaW5hbGVzLCBjJ2VzdCDDoCBkaXJlIGNlbGxlIG/DuSBvbiBvYnRpZW50IGxlIGNvbnRlbnUgZGUgbGEgcHJlbWnDqHJlIGNvbG9ubmUKZW4gcGlvY2hhbnQgc2FucyByZW1pc2UgMjUwIGZvaXMgZGFucyBsZSBwb29sIGNvbm51IGRlIGZlbW1lcyBldCBkJ2hvbW1lcy4KCk5vdGUgOiDDp2EgbmUgY29ycmVzcG9uZCBwYXMgdG90YWxlbWVudCDDoCBsJ2V4cMOpcmllbmNlIHLDqWVsbGUsIGRhbnMgbGFxdWVsbGUgdGVjaG5pcXVlbWVudApzZXVsIGxlIG5vbWJyZSBkZSBwYXRpZW50cyBkYW5zIGxlcyBkZXV4IGdyb3VwZXMgZXN0IGNvbm51LgpJbCBleGlzdGUgZGVzIGFsdGVybmF0aXZlcyDDoCDDp2EsIG/DuSBvbiBuZSBjb25zaWTDqHJlIHF1J3VuZSBzZXVsZSBkZXMgbWFyZ2luYWxlcyBjb21tZSDDqXRhbnQgZml4w6llLiAKTWFpcyBjb21tZSBlbiBwcmF0aXF1ZSBwZXJzb25uZSBuJ3V0aWxpc2UgY2VzIGFsdGVybmF0aXZlcywgb24gbidlbiBwYXJsZXJhIHBhcyBwbHVzLgoKRW4gUiwgb24gYXBwbGlxdWUgY2UgdGVzdCBhdmVjIGxhIGNvbW1hbmRlIHN1aXZhbnRlIDoKCmBgYHtyfQpmaXNoZXIudGVzdCh0MiwgY29uZi5sZXZlbD0wLjk1KQpgYGAKCj4gUXVlIGNvbmNsdW9ucy1ub3VzID8KCgojIyBFbnRyZSBncm91cGVzIDogdmVyc2lvbiAkXGNoaV4yJAoKIyMjIENvbnRleHRlIHByYXRpcXVlCgpPbiBjb250aW51ZSBkZSBmaWxlciBsZSBtw6ptZSBleGVtcGxlIHF1ZSBwcsOpY8OpZGVtbWVudC4KUG91ciBjaGFxdWUgaW5kaXZpZHUgJGkkLCBvbiBvYnNlcnZlIGxlIHNleGUgJFhfaSQgZXQgbGUgZ3JvdXBlICRZX2kkLiAKT24gc2UgZGVtYW5kZSBzaSBsZSBjaG9peCBkdSBncm91cGUgZXN0IGluZMOpcGVuZGFudCBkdSBzZXhlLgoKLSBIMCA6IGxlIGNob2l4IGR1IGdyb3VwZSBlc3QgaW5kw6lwZW5kYW50IGR1IHNleGUgKGkuZS4gbGEgbG9pIGpvaW50ZSBlc3QgbGEgbG9pIHByb2R1aXQgZGVzIGRldXggbWFyZ2luYWxlcyksCi0gSDEgOiBsZSBjaG9peCBkdSBncm91cGUgZMOpcGVuZCBkdSBzZXhlLgoKTCdpZMOpZSBpbnR1aXRpdmUgZXN0IHNpbWlsYWlyZSDDoCBjZSBxdWkgcHLDqWPDqGRlIDogb24gY29uc2lkw6hyZSBlbmNvcmUKY29tbWUgaHlwb3Row6hzZSBudWxsZSBsJ2luZMOpcGVuZGFuY2UgZGVzIG1hcmdpbmFsZXMuCgpMYSBkaXN0cmlidXRpb24gdGjDqW9yaXF1ZSBhdHRlbmR1ZSBlc3QgZG9uYyA6CmBgYHtyfQpwX3NleCA8LSByb3dTdW1zKHQyKS9zdW0odDIpCnBfZ3JvdXAgPC0gY29sU3Vtcyh0Mikvc3VtKHQyKQpwdGggPC0gcF9zZXggJW8lIHBfZ3JvdXAKcHRoCmBgYAoKUXVlIGwnb24gY29tcGFyZSDDoCBsYSBkaXN0cmlidXRpb24gb2JzZXJ2w6llIDoKYGBge3J9CnQyIC8gc3VtKHQyKQpgYGAKCiMjIyBDb250ZXh0ZSBmb3JtYWxpc8OpCgpPbiBkaXNwb3NlIGNldHRlIGZvaXMgZGUgJG4kIGNvdXBsZXMgZCdvYnNlcnZhdGlvbnMgZGUgdmFyaWFibGVzIGRpc2Nyw6h0ZXMgJChYX2ksIFlfaSkkIAptZXN1csOpZXMgc3VyIGNoYXF1ZSBpbmRpdmlkdSAkaSQuCkxlcyAkKFhfaSkkIHNvbnQgw6AgdmFsZXVyIGRhbnMgdW4gZW5zZW1ibGUgJFxtYXRoY2Fse1h9JCDDoCAkZCQgw6lsw6ltZW50cywgCnRhbmRpcyBxdWUgbGVzICQoWV9pKSQgc29udCDDoCB2YWxldXIgZGFucyB1biBlbnNlbWJsZSAkXG1hdGhjYWx7WX0kIMOgICRlJCDDqWzDqW1lbnRzLgoKT24gc3VwcG9zZSBxdWUgbm9zIGNvdXBsZXMgc29udCBpbmTDqXBlbmRhbnRzIGV0IGlkZW50aXF1ZW1lbnQgZGlzdHJpYnXDqXMgCnNlbG9uIHVuZSBsb2kgJFxudSQgc3VyICRcbWF0aGNhbHtYfSBcdGltZXMgXG1hdGhjYWx7WX0kLiAKVHlwaXF1ZW1lbnQsIHVuZSB0ZWxsZSBkaXN0cmlidXRpb24gc2VyYWl0IHBhcmFtw6l0csOpZSBwYXIgJChkIGUgLSAxKSQgdmFsZXVycy4gCgpPbiBzb3VoYWl0ZSDDoCBwcsOpc2VudCBzYXZvaXIgc2kgbGVzICRYJCBldCBsZXMgJFkkIHByZW5uZW50IGxldXJzIHZhbGV1cnMgaW5kw6lwZW5kYW1tZW50IGwndW4gZGUgbCdhdXRyZSwgCmMnZXN0IMOgIGRpcmUgc2kgbGEgbG9pICRcbnUkIGFwcGFydGllbnQgw6AgbGEgZmFtaWxsZSBkZXMgbG9pcyBwcm9kdWl0LgpPbiBub3RlIGNldHRlIGZhbWlsbGUgOgokJCBcbWF0aGNhbHtGfSA9IFx7IHAgXG90aW1lcyBxLCB+IHAgXGluIFxtYXRoY2Fse1B9KFxtYXRoY2Fse1h9KSwgcSBcaW4gXG1hdGhjYWx7UH0oXG1hdGhjYWx7WX0pIFx9ICQkCm/DuSAkXG1hdGhjYWx7UH0oXG1hdGhjYWx7WH0pJCBkw6lzaWduZSBsJ2Vuc2VtYmxlIGRlcyBkaXN0cmlidXRpb25zIHN1ciBsJ2Vuc2VtYmxlIGRpc2NyZXQgJFxtYXRoY2Fse1h9JC4KClR5cGlxdWVtZW50LCB1bmUgZGlzdHJpYnV0aW9uIGRlICRcbWF0aGNhbHtGfSQgbidlc3QgZG9uYyBwbHVzIHBhcmFtw6l0csOpZSBxdWUgcGFyICQoZCArIGUgLSAyKSQgdmFsZXVycyA6IApsZXMgJChkLTEpJCB2YWxldXJzIHF1aSBwYXJhbcOpdHJpc2VudCAkcCQgZXQgbGVzICQoZS0xKSQgdmFsZXVycyBxdWkgcGFyYW3DqXRyaXNlbnQgJHEkLgoKQXZlYyBjZXMgbm90YXRpb25zLCBvbiBwb3VycmEgdGVzdGVyIDoKCi0gSDAgOiAkXG51IFxpbiBcbWF0aGNhbHtGfSQsCi0gSDEgOiAkXG51IFxub3RpbiBcbWF0aGNhbHtGfSQuCgoKIyMjIENlIHF1ZSBkaXQgbGEgdGjDqW9yaWUKCk9uIHBldXQgZXN0aW1lciBjaGFjdW4gZGVzICRwX2kkIHBhciBsYSBmcsOpcXVlbmNlIGVtcGlyaXF1ZSBkZSBsJ29ic2VydmF0aW9uICR4X2kkLCBjJ2VzdCDDoCBkaXJlIDoKJCQgXGhhdHtwfV9pID0gXGZyYWN7MX17bn0gXHN1bV97az0xfV5uIFxkZWx0YV97WF9rID0geF9pfSAkJApPbiBwZXV0IMOpZ2FsZW1lbnQgZmFpcmUgZGUgbcOqbWUgcG91ciBsZXMgZnLDqXF1ZW5jZXMgZW1waXJpcXVlcyBkZXMgJHlfaiQsIGVuIGNhbGN1bGFudCA6CiQkIFxoYXR7cX1faiA9IFxmcmFjezF9e259IFxzdW1fe2s9MX1ebiBcZGVsdGFfe1lfayA9IHlfan0gJCQKRXQgZW5maW4sIGZhaXJlIGRlIG3Dqm1lIHBvdXIgbGVzIGZyw6lxdWVuY2VzIGVtcGlyaXF1ZXMgZGVzIGRpZmbDqXJlbnRzIGNvdXBsZXMgJCh4X2ksIHlfaikkIDoKJCQgXGhhdHtcbnV9X3tpLGp9ID0gXGZyYWN7MX17bn0gXHN1bV97az0xfV5uIFxkZWx0YV97KFhfaywgWV9rKSA9ICh4X2ksIHlfail9ICQkCgpPbiBzb3VoYWl0ZSBlbnN1aXRlIG1lc3VyZXIgbGEgInBzZXVkby1kaXN0YW5jZSBkdSAkXGNoaV4yJCIgCmVudHJlIGxhIGRpc3RyaWJ1dGlvbiBlbXBpcmlxdWVtZW50IG9ic2VydsOpZSAkXGhhdHtcbnV9JCAKZXQgY2VsbGUgZG9ubsOpZSBwYXIgbGUgbWVpbGxldXIgYWp1c3RlbWVudCDDoCB1bmUgbG9pIHByb2R1aXQgJFxoYXR7cH0gXG90aW1lcyBcaGF0e3F9JCwgc29pdCA6CiQkIApEXjJfbiggXGhhdHtcbnV9LCBcaGF0e3B9XG90aW1lc1xoYXR7cX0gKSA9IApuIFxzdW1fe2k9MX1eZCBcc3VtX3tqPTF9XmUgXGZyYWN7XGxlZnQoIFxoYXR7XG51fV97aSxqfSAtIFxoYXR7cH1faSBcaGF0e3F9X2ogXHJpZ2h0KV4yfXtcaGF0e3B9X2kgXGhhdHtxfV9qfSAKJCQKClVuIHRow6lvcsOobWUgbm91cyBhc3N1cmUgYWxvcnMgcXVlCgotIHNvdXMgSDAsICREXjJfbiggXGhhdHtcbnV9LCBcaGF0e3B9XG90aW1lc1xoYXR7cX0gKSQgdGVuZCBlbiBsb2kgdmVycyB1bmUgZGlzdHJpYnV0aW9uIGR1ICRcY2hpXjIkIMOgICQoZC0xKSAoZS0xKSQgZGVncsOpcyBkZSBsaWJlcnTDqS4KLSBzb3VzIEgxLCAkRF4yX24oIFxoYXR7XG51fSwgXGhhdHtwfVxvdGltZXNcaGF0e3F9ICkkIHRlbmQgdmVycyBsJ2luZmluaSBwcmVzcXVlIHPDu3JlbWVudC4KClBvdXIgY2V1eCBxdWkgc291aGFpdGVyYWllbnQgc2F2b2lyIGQnb8O5IHZpZW50IGxlIG5vbWJyZSBkZSBkZWdyw6lzIGRlIGxpYmVydMOpLCBub3RleiBxdWUsIGNvbW1lIHByw6ljw6lkZW1tZW50LCAKaWwgY29ycmVzcG9uZCDDoCBsYSBkaW1lbnNpb24gZGUgbCdlc3BhY2UgZGFucyBsZXF1ZWwgdml0IGxhIGxvaSBkZSBwcm9iYWJpbGl0w6kgJFxudSQgKHNvaXQgJGRlIC0xJCkgCm1vaW5zIGxhIGRpbWVuc2lvbiBkZSBsJ2VzcGFjZSBkYW5zIGxlcXVlbCB2aXQgbGEgZGlzdHJpYnV0aW9uIMOgIGxhcXVlbGxlIG9uIHNvdWhhaXRlIHNlIHJhbWVuZXIgKGljaSAkZCArIGUgLSAyJCkuCgojIyMgVXRpbGlzYXRpb24gZGUgbGEgdGjDqW9yaWUgcG91ciBjb25zdHJ1aXJlIHVuIHRlc3QKCkNvbW1lIHRvdWpvdXJzIGF2ZWMgbGVzIHRlc3RzIGR1ICRcY2hpXjIkLCBub3RyZSB0ZXN0IHJlcG9zZSBzdXIgdW4gdGjDqW9yw6htZSBhc3ltcHRvdGlxdWUgOiAKb24gbmUgcGV1dCBkb25jIGwnYXBwbGlxdWVyIHBvdXIgZWZmZWN0dWVyIG5vdHJlIHRlc3Qgc3VyIGRlIHZyYWllcyBkb25uw6llcyBxdWUgcG91ciBkZSBncmFuZHMgZWZmZWN0aWZzICRuJCAhCgpTaSBjZXMgY29uZGl0aW9ucyBkJ2VmZmVjdGlmcyBzdWZmaXNhbnRzIHNvbnQgcmVzcGVjdMOpZXMsIGxlIHRlc3QgZm9uY3Rpb25uZSBhaW5zaSA6CgotIGxhIHN0YXRpc3RpcXVlIGRlIHRlc3QgJEReMl9uKCBcaGF0e1xudX0sIFxoYXR7cH1cb3RpbWVzXGhhdHtxfSApJCBlc3QgY2FsY3Vsw6llLAotIG9uIGZpeGUgbCdlcnJldXIgZGUgcHJlbWllciBvcmRyZSAkXGFscGhhJCBhY2NlcHRhYmxlLAotIG9uIGNhbGN1bGUgJHFfezEtXGFscGhhfSQsIGxlIHF1YW50aWxlIGRlIG5pdmVhdSAkMS1cYWxwaGEkIGRlIGxhIGxvaSBkdSAkXGNoaV4yJCDDoCAkKGQtMSkgKGUtMSkkIGRlZ3LDqXMgZGUgbGliZXJ0w6ksCi0gb24gcmVqZXR0ZSBIMCBzaSAkRF4yX24oIFxoYXR7XG51fSwgXGhhdHtwfVxvdGltZXNcaGF0e3F9ICkgPiBxX3sxLVxhbHBoYX0kLgoKSW50dWl0aXZlbWVudCwgb24gbWVzdXJlIHVuZSBwc2V1ZG8tZGlzdGFuY2UgZW50cmUgbm90cmUgZGlzdHJpYnV0aW9uIGVtcGlyaXF1ZSAKZXQgbm90cmUgbWVpbGxldXJlIGVzdGltYXRpb24gZGUgbG9pIHByb2R1aXQsIGV0IG9uIHJlamV0dGUgbCdoeXBvdGjDqHNlIG51bGxlIGQnw6lnYWxpdMOpIGRlcyBkZXV4IGRpc3RyaWJ1dGlvbnMgCmxvcnNxdWUgY2V0dGUgcHNldWRvLWRpc3RhbmNlIGRldmllbnQgdHJvcCBncmFuZGUuCgojIyMgRXQgYXZlYyBSID8KCkRhbnMgbm90cmUgZXhlbXBsZSwgb24gc291aGFpdGUgdGVzdGVyIGwnaW5kw6lwZW5kYW5jZSBkdSBzZXhlIGV0IGR1IGdyb3VwZSBjaGV6IG5vcyBpbmRpdmlkdXMuCkVuIHVuZSBsaWduZSwgaWwgbm91cyBzdWZmaXQgZCfDqWNyaXJlIDoKCmBgYHtyfQpyZXMgPC0gY2hpc3EudGVzdCh0MikKcmVzCmBgYAoKTMOgIGVuY29yZSwgdW5lIGZvbmN0aW9uIGFsdGVybmF0aXZlIGV4aXN0ZSA6CgpgYGB7cn0KcHJvcC50ZXN0KHQyKQpgYGAKCkNldHRlIGZvaXMtY2ksIGxlIGNvbXBvcnRlbWVudCBkZXMgZGV1eCBmb25jdGlvbnMgZXN0IGxlIG3Dqm1lIHBhciBkw6lmYXV0LAphdmVjIGNldHRlICJjb3JyZWN0aW9uIGRlIGNvbnRpbnVpdMOpIiBkYW5zIGxlcyBkZXV4IGNhcy4KCj4gUXVlIGNvbmNsdW9ucy1ub3VzID8KCj4gVsOpcmlmaWVyIHF1ZSBsZXMgdGVzdHMgZGUgJFxjaGleMiQgZXQgZGUgRmlzaGVyIHNlbWJsZW50IGNvaMOpcmVudHMuCgoKIyMgU3VyIGwnb3V0Y29tZSBkZSBkw6lwcmVzc2lvbgoKSWwgbmUgcmVzdGUgcXUnw6AgcsOpLWFwcGxpcXVlciBsZXMgbcOqbWUgZm9uY3Rpb25zIHF1ZSBwcsOpY2VkZW1tZW50IGF2ZWMgdW5lIHZhcmlhYmxlIGRpZmbDqXJlbnRlIDoKCmBgYHtyfQp0MyA8LSB0YWJsZShkJGlzX2RlcHJlc3NlZCwgZCRncm91cCkKZmlzaGVyLnRlc3QodDMpCmNoaXNxLnRlc3QodDMpCmBgYAoKPiBRdWUgY29uY2x1b25zLW5vdXMgPwoKIyMgUG91ciBkZXMgZG9ubsOpZXMgY2F0w6lnb3JpZWxsZXMKCkxhIGJvbm5lIG5vdXZlbGxlLCBjJ2VzdCBxdWUgdG91dCBlc3QgdHLDqHMgc2ltaWxhaXJlIMOgIGNlIHF1J29uIHZpZW50IGRlIHZvaXIuCgpEJ2FpbGxldXJzLCB0b3V0IGNlIHF1ZSBqZSBwb3V2YWlzIMOpY3JpcmUgZGUgZmHDp29uIGfDqW7DqXJhbGUgcHLDqWPDqWRlbW1lbnQgYSDDqXTDqSDDqWNyaXQKZGUgZmHDp29uIGfDqW7DqXJhbGUsIGF2ZWMgcGx1cyBkZSBkZXV4IGNhdMOpZ29yaWVzIG5vdGFtbWVudC4KCklsIG5lIHJlc3RlIGRvbmMgcXUnw6AgcydlbnRyYcOubmVyICEgT24gcydpbnTDqXJlc3NlIMOgIHByw6lzZW50IGF1eCBkb25uw6llcyBkJ291dGNvbWUgYGZlZWxzX3NsZWVweWAuCgpgYGB7ciBmaWcud2lkdGg9MTB9CmQgJT4lIGdncGxvdChhZXMoeCA9IGdyb3VwLCBmaWxsID0gZmVlbHNfc2xlZXB5KSkgKwoJZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArCgl0aGVtZV9idygpICsgCglzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YyhibGV1Y2xhaXIsIGJsZXVmb25jZSwgcm9zZSkpCmBgYAoKIyMjIEF2ZWMgdW5lIGxvaSBkb25uw6llCgpPbiBwZXV0IHV0aWxpc2VyIHVuIHRlc3QgZHUgJFxjaGleMiQsIHBhciBleGVtcGxlIHBvdXIgdGVzdGVyIGxhIGRpZmbDqXJlbmNlIGRlIGxhIGRpc3RyaWJ1dGlvbgpvYnNlcnbDqWUgcGFyIHJhcHBvcnQgw6AgdW5lIGRpc3RyaWJ1dGlvbiBob21vZ8OobmUgc3VyIGxlcyB0cm9pcyBjYXTDqWdvcmllcyA6CgpgYGB7cn0KdDQgPC0gdGFibGUoZCRmZWVsc19zbGVlcHkpCmthYmxlKHQ0KQpjaGlzcS50ZXN0KHQ0LCBwPWMoMS8zLCAxLzMsIDEvMykpCmBgYAoKUG91ciBsZXMgdXRpbGlzYXRldXJzIGRlIFIsIG5vdG9ucyBxdWUsIHBhciBkw6lmYXV0LCBsZSBjb21wb3J0ZW1lbnQgZGUgbGEgZm9uY3Rpb24gYGNoaXNxLnRlc3RgCsOgIHF1aSBvbiBuZSBkb25uZSBwYXMgZCdhcmd1bWVudCBgcGAgZXN0IGp1c3RlbWVudCBkZSBjb21wYXJlciDDoCB1bmUgZGlzdHJpYnV0aW9uIHVuaWZvcm1lIDoKCmBgYHtyfQpjaGlzcS50ZXN0KHQ0KQpgYGAKCj4gUXVlIGNvbmNsdW9ucy1ub3VzID8KCiMjIyBFbnRyZSBncm91cGVzIDogdmVyc2lvbiBGaXNoZXIKCkxlIG3Dqm1lIHByaW5jaXBlIHF1ZSBkYW5zIGxlIGNhcyAyeDIgZXN0IGFwcGxpcXXDqSBzdXIgbm90cmUgdGFibGUgZGUgY29udGluZ2VuY2UgM3gyLgoKYGBge3J9CnQ1IDwtIHRhYmxlKGQkZmVlbHNfc2xlZXB5LCBkJGdyb3VwKQprYWJsZSh0NSkKZmlzaGVyLnRlc3QodDUpCmBgYAoKPiBRdWUgY29uY2x1b25zLW5vdXMgPwoKIyMjIEVudHJlIGdyb3VwZXMgOiB2ZXJzaW9uICRcY2hpXjIkCgpgYGB7cn0KY2hpc3EudGVzdCh0NSkKYGBgCgpFbmNvcmUgdW4gcsOpc3VsdGF0IGJpZW4gc2ltaWxhaXJlIMOgIGNlbHVpIGR1IHRlc3QgZGUgRmlzaGVyIDogb3VmICEKCj4gUXVlIGNvbmNsdW9ucy1ub3VzID8KCgoKIyBSw6lncmVzc2lvbiBsb2dpc3RpcXVlCgpKdXNxdSfDoCBwcsOpc2VudCwgbm91cyBhdm9ucyBjaGVyY2jDqSDDoCByZWpldGVyIHVuZSBoeXBvdGjDqHNlIG51bGxlIGQnaG9tb2fDqW7DqWl0w6kgZCd1biBjZXJ0YWluIG5vbWJyZSBkZQpkaXN0cmlidXRpb25zIGRpc2Nyw6h0ZXMsIGTDqWZpbmllcyBzZWxvbiBsYSB2YWxldXIgZCd1bmUgY292YXJpYWJsZS4KTWFpcyBub3VzIG4nYXZvbnMgcGFzICpxdWFudGlmacOpKiBsJ2ltcGFjdCBkZSBjZXR0ZSBjb3ZhcmlhYmxlIHN1ciBub3RyZSBkaXN0cmlidXRpb24gZCdpbnTDqXLDqnQuCk5vdXMgbifDqXRpb25zIMOpZ2FsZW1lbnQgcGFzIGVuIG1lc3VyZSBkZSByZWdhcmRlciBsJ2ltcGFjdCBkJ3VuZSB2YXJpYWJsZSBjb250aW51ZQpzdXIgdW4gb3V0Y29tZSBiaW5haXJlLgoKTGUgY2FkcmUgYXBwcm9wcmnDqSBwb3VyIGZhaXJlIMOnYSBlc3QgY2VsdWkgZGUgbGEgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZS4KCiMjIENhZHJlIGZvcm1lbAoKRGFucyBsYSByw6lncmVzc2lvbiBsb2dpc3RpcXVlLCBvbiBzJ2ludMOpcmVzc2Ugw6AgdW5lIHZhcmlhYmxlIGRlIHLDqXBvbnNlICRZJCBiaW5haXJlLCAKcXUnb24gc291aGFpdGUgZXhwbGlxdWVyIGF2ZWMgZGVzIHZhcmlhYmxlcwokWF8xLCBYXzIsIC4uLiwgWF9uJCBkaXNjcsOodGVzIG91IGNvbnRpbnVlcy4KCk9uIHN1cHBvc2UgcXVlIGxhIGxvaSBkZSAkWSQgc2FjaGFudCAkWCQgZXN0IHVuZSBsb2kgZGUgQmVybm91bGxpLApkZSBwYXJhbcOodHJlICRwKFgpJC4KCk9uIHNvdWhhaXRlIG1vZMOpbGlzZXIgJHAoWCkkIGVuIHByZW5hbnQgZW4gY29tcHRlIG4naW1wb3J0ZSBxdWVsbGUgc29tbWUKZGUgdmFyaWFibGVzIGV4cGxpY2F0aXZlcywgbWFpcyB0b3Vqb3VycyBkZSBmYcOnb24gw6AgYXZvaXIgJHAoWCkgXGluICgwLDEpJC4KCk9ubiBjaG9pc2l0IGxhIGZvbmN0aW9uIHN1aXZhbnRlIDoKCiQkCnAoWCkgPSBcZnJhY3sxfXsxICsgZV57LShhXzAgKyBhXzEgeF8xICsgYV8yIHhfMiArIC4uLiArIGFfbiB4X24pfX0gXFwKXGxuIFxsZWZ0KCBcZnJhY3twfXsxLXB9IFxyaWdodCkgPSBhXzAgKyBhXzEgeF8xICsgYV8yIHhfMiArIC4uLiArIGFfbiB4X24KJCQKClN1ciBsYSBwcmVtacOocmUgbGlnbmUsIGxhIGZvbmN0aW9uIGRlIGRyb2l0ZSAkdSBcbWFwc3RvIFxmcmFjezF9ezErZV57LXV9fSQgZXN0IGFwcGVsw6llICpmb25jdGlvbiBsb2dpc3RpcXVlKi4KU3VyIGxhIHNlY29uZGUgbGlnbmUsIGxhIGZvbmN0aW9uIGRlIGdhdWNoZSAkdSBcbWFwc3RvIFxsbiBcZnJhY3t1fXsxLXV9JCBlc3QgbGEgcsOpY2lwcm9xdWUgZGUgbGEgZm9uY3Rpb24KbG9naXN0aXF1ZSwgYXBwZWzDqWUgKmZvbmN0aW9uIGxvZ2l0Ki4KCgojIyBFeGVtcGxlIGF2ZWMgdW5lIGNvdmFyaWFibGUgY29udGludWUKCk9uIGNvbW1lbmNlIGF2ZWMgdW5lIGNvdmFyaWFibGUgY29udGludWUsIGRlIGZhw6dvbiDDoCBzZSBmYWlyZSB1bmUgaWTDqWUgdmlzdWVsbGUgZGUgbGEgZm9ybWUgZGUgbGEgZm9uY3Rpb24gbG9naXN0aXF1ZS4KCk9uIHNvdWhhaXRlcmFpdCBkYW5zIHVuIHByZW1pZXIgdGVtcHMgZXhwbGlxdWVyIGBpc19kZXByZXNzZWRgIMOgIHBhcnRpciBkdSBwb2lkcyBpbml0aWFsIGB3ZWlnaHRfdDBgLgpQcmVtacOocmUgY2hvc2Ugw6AgZmFpcmUgOiB1biBncmFwaGUgIQoKYGBge3IgZmlnLndpZHRoPTEwfQpwIDwtIGQgJT4lIGdncGxvdChhZXMoeD13ZWlnaHRfdDAsIHk9YXMubnVtZXJpYyhpc19kZXByZXNzZWQpKSkgKyAKICAgIGdlb21fcG9pbnQoKSArCiAgICB0aGVtZV9idygpICsKCXhsYWIoIlBvaWRzIGluaXRpYWwgKGtnKSIpICsKCXlsYWIoIkluZGljYXRyaWNlIGRlIGTDqXByZXNzaW9uIikKcApgYGAKCkwnaWxsdXN0cmF0aW9uIHN1aXZhbnRlIHN1cGVycG9zZSDDoCBjZSBwcmVtaWVyIGdyYXBoaXF1ZSBsZSBmaXQgZCd1biBtb2TDqGxlIGxvZ2lzdGlxdWUsCmVuIGNvbXBhcmFpc29uIGF2ZWMgY2UgcXVpIHBvdXJyYWl0IMOqdHJlIHVuZSBtYXV2YWlzZSB1dGlsaXNhdGlvbiBkJ3VuIHNpbXBsZSBtb2TDqGxlIGxpbsOpYWlyZS4KCmBgYHtyIGZpZy53aWR0aD0xMH0KcCArIGdlb21fc21vb3RoKGZvcm11bGEgPSAieX54IiwKICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJnbG0iLAogICAgICAgICAgICAgICAgbWV0aG9kLmFyZ3MgPSBsaXN0KGZhbWlseSA9IGJpbm9taWFsKSwKICAgICAgICAgICAgICAgIHNlID0gRkFMU0UsCgkJCQljb2xvciA9IGJsZXVjbGFpcikgKwoJZ2VvbV9zbW9vdGgoZm9ybXVsYSA9ICJ5fngiLCBtZXRob2QgPSAibG0iLCBjb2xvciA9IHJvc2UsIHNlID0gRikgCmBgYAoKTCdhdmFudGFnZSBkZSBsYSByw6lncmVzc2lvbiBsb2dpc3RpcXVlIHNhdXRlIGF1eCB5ZXV4IDogb24gcHLDqWRpdCBiaWVuIHVuZSB2YXJpYWJsZSBlbnRyZSAwIGV0IDEsCmV0IGxhIHZhbGV1ciBkZSBsYSBjb3VyYmUgYmxldWUgcGV1dCBkaXJlY3RlbWVudCBzJ2ludGVycHLDqXRlciBjb21tZSBsYSBwcm9iYWJpbGl0w6ksIHBvdXIgdW4gcGF0aWVudApheWFudCBsZSBwb2lkcyBlbiBhYnNjaXNzZSwgZCdhdm9pciBwZXJkdSBkdSBwb2lkcyDDoCBsJ2lzc3VlIGRlIGwnZXNzYWkuCgpPbiBwZXV0IHLDqWN1cMOpcmVyIGxlcyB2YWxldXJzIGRlIG5vdHJlIGZpdCBhdmVjIHVuIGFwcGVsIMOgIDoKCmBgYHtyfQpyZXMgPC0gZ2xtKCJpc19kZXByZXNzZWQgfiB3ZWlnaHRfdDAiLCBmYW1pbHk9Ymlub21pYWwsIGRhdGE9ZCkKc3VtbWFyeShyZXMpCmBgYAoKTGVzIGNvZWZmaWNpZW50cyByZW52b3nDqXMgY29ycmVzcG9uZGVudCDDoCBjZXV4IGRlIGxhIGNvbWJpbmFpc29uIGxpbsOpYWlyZSBkZSB2YXJpYWJsZXMgZXhwbGljYXRpdmVzLCAKYydlc3Qgw6AgZGlyZSBhdXggJChhX2kpJCBkZXMgZm9ybXVsZXMgcHLDqS1jaXTDqWVzLgpEYW5zIG5vdHJlIGNhcywgbGUgY29lZmZpY2llbnQgZW4gZmFjZSBkZSBgd2VpZ2h0X3QwYCBlc3QgcG9zaXRpZiwgZG9uYyB1biBwYXRpZW50IHBsdXMgbG91cmQgaW5pdGlhbGVtZW50CmVzdCBwbHVzIHN1c2NlcHRpYmxlIGQnw6p0cmUgZMOpcHJlc3NpZiDDoCBsJ2lzc3VlIGRlIGwnZXNzYWkuCgpQb3VyIHNlIGRvbm5lciB1bmUgaWTDqWUgcXVhbnRpdGF0aXZlIGRlIGwnaW1wYWN0IGQndW4ga2cgc3VwcGzDqW1lbnRhaXJlIGF1IGTDqWJ1dCBkZSBsJ2Vzc2FpLCBvbiBwZXV0IGNhbGN1bGVyIApyYXBpZGVtZW50IGwnb2RkcyByYXRpbyBhc3NvY2nDqSDDoCBjZSBrZyBzdXBwbMOpbWVudGFpcmUgZW4gcHJlbmFudCBsJ2V4cG9uZW50aWVsbGUgZGUgbm90cmUgcGFyYW3DqHRyZS4KRW4gZWZmZXQsCgokJApcYmVnaW57YWxpZ24qfQpcZnJhY3sgXGZyYWN7IHAoWF8xID0geCsxKSB9eyAxIC0gcChYXzEgPSB4ICsgMSkgfSB9eyBcZnJhY3sgcChYXzE9eCkgfXsgMSAtIHAoWF8xID0geCkgfSB9IAomPSBcZXhwIFxsbiBcZnJhY3sgXGZyYWN7IHAoWF8xID0geCsxKSB9eyAxIC0gcChYXzEgPSB4ICsgMSkgfSB9eyBcZnJhY3sgcChYXzE9eCkgfXsgMSAtIHAoWF8xID0geCkgfSB9IFxcCiY9IFxleHAgXGxlZnQoIFxsbiBcZnJhY3sgcChYXzEgPSB4KzEpIH17IDEgLSBwKFhfMSA9IHggKyAxKSB9IC0gXGxuIFxmcmFjeyBwKFhfMT14KSB9eyAxIC0gcChYXzEgPSB4KSB9IFxyaWdodCkgXFwKJj0gXGV4cCBcbGVmdCggYV8wICsgYV8xKHggKyAxKSAtIGFfMCAtIGFfMXggXHJpZ2h0KSBcXAomPSBcZXhwIGFfMQpcZW5ke2FsaWduKn0KJCQKCkRhbnMgbm90cmUgY2FzLCDDp2EgZG9ubmUgOgoKYGBge3J9CmV4cChjb2VmKHJlcylbMl0pCmBgYAoKTCdvdXRwdXQgZGUgbGEgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSBub3VzIGRvbm5lIGF1c3NpIGxhIHAtdmFsdWUgYXNzb2Npw6llIGF1IHRlc3QgZGUgbm9uLW51bGxpdMOpCmRlIG5vcyBjb2VmZmljaWVudHMgZGUgcsOpZ3Jlc3Npb24uCkljaSwgbGVzIHAtdmFsdWVzIHNvbnQgcGV0aXRlcywgb24gcGV1dCBkb25jIHJlamV0ZXIgbCdoeXBvdGjDqHNlIG51bGxlIGRlIG51bGxpdMOpIGRlIG5vcyBkZXV4IGNvZWZmaWNpZW50cy4KClN1ciB1biBleGVtcGxlIGRpZmbDqXJlbnQsIGVuIGNoZXJjaGFudCDDoCBleHBsaXF1ZXIgYHNleGAgw6AgcGFydGlyIGRlIGBhZ2VgOgoKYGBge3J9CmRiaXMgPC0gZCAlPiUgbXV0YXRlKHNleCA9IGlmZWxzZShzZXggPT0gIkYiLCAxLCAwKSkKcmVzIDwtIGdsbSgic2V4IH4gYWdlIiwgZmFtaWx5PWJpbm9taWFsLCBkYXRhPWRiaXMpCnN1bW1hcnkocmVzKQpgYGAKCkNldHRlIGZvaXMgbGVzIHAtdmFsdWVzIHNvbnQgZ3JhbmRlcywgZXQgb24gbmUgcGV1dCBwYXMgY29uY2x1cmUgw6AgdW5lIGRpZmbDqXJlbmNlCnNpZ25pZmljYXRpdmUgZGUgbm9zIHBhcmFtw6h0cmVzIHBhciByYXBwb3J0IMOgIHrDqXJvLgoKIyMgRXhlbXBsZSBhdmVjIHVuZSBjb3ZhcmlhYmxlIGRpc2Nyw6h0ZQoKVG91dCBsJ2ludMOpcsOqdCBkJ3VuZSByw6lncmVzc2lvbiBsb2dpc3RpcXVlIGVzdCBkZSBwb3V2b2lyIHV0aWxpc2VyIAp0b3V0IGNlIHF1J29uIHNvdWhhaXRlIGNvbW1lIHZhcmlhYmxlIGV4cGxpY2F0aXZlLgpRdSdvYnRpZW50LW9uIHNpIG9uIGNoZXJjaGUgw6AgZXhwbGlxdWVyIGBpc19kZXByZXNzZWRgIMOgIHBhcnRpciBkZSBgZ3JvdXBgID8KClVuIGRlc3NpbiB0b3V0IGQnYWJvcmQgOgoKYGBge3IgZmlnLndpZHRoPTEwfQpkICU+JSAKICAgIGdncGxvdChhZXMoeCA9IGdyb3VwLCBmaWxsID0gYXMuZmFjdG9yKGlzX2RlcHJlc3NlZCkpKSArCiAgICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpICsKICAgIHRoZW1lX2J3KCkgKyAKICAgIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKGJsZXVjbGFpciwgcm9zZSkpCmBgYAoKRXQgbGEgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSwgZW5zdWl0ZSA6CgpgYGB7cn0KcmVzIDwtIGdsbSgiaXNfZGVwcmVzc2VkIH4gZ3JvdXAiLCBmYW1pbHk9Ymlub21pYWwsIGRhdGE9ZCkKc3VtbWFyeShyZXMpCmBgYAoKTGEgcC12YWx1ZSBwZXRpdGUgbm91cyBwb3Vzc2Ugw6AgY29uY2x1cmUgw6AgdW4gZWZmZXQgc2lnbmlmaWNhdGl2ZW1lbnQgZGlmZsOpcmVudCBkZSAwIGR1IGJyYXMgZGUgdHJhaXRlbWVudApzdXIgbCdvdXRjb21lLgoKIyMgRXhlbXBsZSBtdWx0aXZhcmnDqQoKQ29tbWUgZGFucyBsZSBtb2TDqGxlIGxpbsOpYWlyZSwgb24gcGV1dCBwcm9wb3NlciBkZXMgZWZmZXRzIGFkZGl0aWZzIGRlIGRpZmbDqXJlbnRlcyBjb3ZhcmlhYmxlcy4KUGFyIGV4ZW1wbGUsIHF1ZSBzZSBwYXNzZS10LWlsIHNpIG9uIHNvdWhhaXRlIGV4cGxpcXVlciBgaXNfZGVwcmVzc2VkYCDDoCBwYXJ0aXIgZHUgYGdyb3VwYCBldCBkZSBgd2VpZ2h0X3QwYCA/CgpgYGB7cn0KcmVzIDwtIGdsbSgiaXNfZGVwcmVzc2VkIH4gZ3JvdXAgKyB3ZWlnaHRfdDAiLCBmYW1pbHk9Ymlub21pYWwsIGRhdGE9ZCkKc3VtbWFyeShyZXMpCmBgYAoKTGUgbW9kw6hsZSBhdmVjIHVuZSBpbmZsdWVuY2UgZGVzIGRldXggdmFyaWFibGVzIGVzdCBiaWVuIHN1cHBvcnTDqSBwYXIgbm9zIGRvbm7DqWVzICEKT24gb2J0aWVudCBkb25jIHVuZSBlc3RpbWF0aW9uIGRlIGwnZWZmZXQgZHUgYnJhcyBkZSB0cmFpdGVtZW50IHRvdXQgZW4gYXlhbnQgcHJpcwplbiBjb21wdGUgbCdpbmZsdWVuY2UgZCd1bmUgYXV0cmUgdmFyaWFibGUgZXhwbGljYXRpdmUuCgoKCiMgQ29uY2x1c2lvbgoKTm91cyBhdm9ucyB2dSBhdWpvdXJkJ2h1aSB1biBjZXJ0YWluIG5vbWJyZSBkZSBtw6l0aG9kZXMgcGVybWV0dGFudCBkJ2FuYWx5c2VyIGRlcyB2YXJpYWJsZXMgZGlzY3LDqHRlcy4KTGVzIHBvaW50cyBwcmluY2lwYXV4IMOgIHJldGVuaXIgc29udCBsZXMgc3VpdmFudHMgOgoKMS4gcG91ciBkZXMgZG9ubsOpZXMgYmluYWlyZXMsIHVuIHRlc3QgZXhhY3QgZGUgZGlmZsOpcmVuY2UgcGFyIHJhcHBvcnQgw6AgdW5lIHByb3BvcnRpb24gZml4w6llIGV4aXN0ZSwKbWFpcyB1biB0ZXN0IGR1ICRcY2hpXjIkIGZhaXQgYXVzc2kgbCdhZmZhaXJlLgoyLiBwb3VyIGRlcyBkb25uw6llcyBiaXZhcmnDqWVzIGRpc2Nyw6h0ZXMsIHVuIHRlc3QgZGUgRmlzaGVyIGQnaW5kw6lwZW5kYW5jZSBlbnRyZSBsZXMgZGV1eCB2YXJpYWJsZXMgZXhpc3RlLAptYWlzIHVuIHRlc3QgZHUgJFxjaGleMiQgZmFpdCBhdXNzaSBsJ2FmZmFpcmUuCjMuIGNlcnRhaW5zIHByw6lmw6hyZXJvbnQgdG91am91cnMgZWZmZWN0dWVyIHVuIHRlc3QgZGUgRmlzaGVyLCBjb25zaWTDqXLDqSBjb21tZSAiZXhhY3QiIHNvdXMgc2VzIGh5cG90aMOoc2VzLgpEJ2F1dHJlcyBwcsOpZsOocmVudCBsZSB0ZXN0IGR1ICRcY2hpXjIkLCBwbHVzIGfDqW7DqXJhbCBldCAicm9idXN0ZSIsIG1haXMgcmVwb3NhbnQgc3VyIHVuIHLDqXN1bHRhdCB0aMOpb3JpcXVlIGFzeW1wdG90aXF1ZS4KNC4gbGEgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSBwZXJtZXQgZCfDqXRlbmRyZSBsZSBmcmFtZXdvcmsgCiAgIGRlIGxhIHLDqWdyZXNzaW9uIGxpbsOpYWlyZSDDoCBkZXMgZG9ubsOpZXMgYmluYWlyZXMuCg==