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)
## ── Attaching core tidyverse packages ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.3     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.0
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# pour faire des graphes multiples
library(cowplot)
## 
## Attaching package: 'cowplot'
## 
## The following object is masked from 'package:lubridate':
## 
##     stamp
# pour afficher joliment les tableaux dans un document rmarkdown
library(knitr)

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

Préparation des données

Simulation

Pour se focaliser sur les stats et uniquement les stats, on travaille sur un jeu de données idéalisé. Et puisqu’on bosse dans les essais cliniques, on va simuler le RCT (Randomized Controlled Trial) ayant les caractéristiques suivantes :

  • l’étude possède deux bras: control et treatment.
  • on enregistre des variables démographiques à l’inclusion, type sex, age, age_group, height, weight_t0.
  • des variables d’outcomes liés à la prise de poids : diff_weight, has_lost_weight.
  • une variable d’outcome à trois modalités : feels_sleepy.
  • une variable de temps de suivi du traitement : t_compliance.
  • une variable d’outcome de nombre de chutes pendant le suivi : n_falls.

Pour ceux que ça intéresse, voici la loi dans laquelle les données sont simulées :

# data that do not depend on the group
d <- data.frame(id = seq(1,500),
                group = as.factor(c(rep("control",250), rep("treatment",250))),
                sex = ifelse(rbinom(n=500, size=1, prob=0.4) == 0, "F", "M"),
                age = round(runif(n=500, min=19, max=77)),
                height = round(rnorm(n=500, mean=1.7, sd=0.1), digits=2),
                weight_t0 = round(rnorm(n=500, mean=70, sd=10)),
                t_compliance = round(rexp(n=500, rate=0.05))  )

# possibilities for the "feels_sleepy" variable
modalites <- c("morning", "afternoon", "always")

# data that do depend on the group
d <- d %>% 
    mutate(diff_weight = c(rnorm(n=250, mean=0.5*(d$weight_t0[1:250]-70), sd=2.5), 
                           rnorm(n=250, mean=0.5*(d$weight_t0[251:500]-70)+0.25*d$t_compliance[251:500], sd=2.5)),
           feels_sleepy = c( sample(modalites, 250, prob = c(0.3, 0.4, 0.3), replace = TRUE),
                                    sample(modalites, 250, prob = c(0.2, 0.3, 0.5), replace = TRUE) ),
           n_falls = c(rpois(n=250, lambda=6), rpois(n=250, lambda=5.5)) )

d <- d %>% mutate(weight_tf = weight_t0 - diff_weight,
                  age_group = as.factor(ifelse(age < 40, 1, ifelse(age < 60, 2, 3))),
                  has_lost_weight = (diff_weight > 0) )

Ecriture/Lecture du dataset

Pour être sûr de travailler sur le fichier tel qu’il sera lu par tout le monde, on enregistre le dataset simulé puis on écrase le dataset avec l’import.

write.csv(d, "etude_fictive.csv")
d <- read.csv("etude_fictive.csv")
head(d)
##   X id   group sex age height weight_t0 t_compliance diff_weight feels_sleepy
## 1 1  1 control   M  38   1.83        62           35   -5.202373      morning
## 2 2  2 control   F  73   1.69        66           13   -2.903321       always
## 3 3  3 control   F  46   1.54        79           21    2.600586    afternoon
## 4 4  4 control   F  36   1.71        72           55    1.120590    afternoon
## 5 5  5 control   F  30   1.66        64            3   -2.330265    afternoon
## 6 6  6 control   F  75   1.75        63           12   -2.758114      morning
##   n_falls weight_tf age_group has_lost_weight
## 1       8  67.20237         1           FALSE
## 2       6  68.90332         3           FALSE
## 3       9  76.39941         2            TRUE
## 4       6  70.87941         1            TRUE
## 5       8  66.33026         1           FALSE
## 6      11  65.75811         3           FALSE

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

Comparer des proportions

Visuellement

Nous avons deux colonnes binaires qui se prêtent bien à ce premier exercice : sex et has_lost_weight. 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 ayant perdu du poids.

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") +
    theme_bw() + 
    scale_fill_manual(values=c(bleuclair, rose))

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

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

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 305
M 195
res <- binom.test(t, p=0.5, conf.level=0.95)
res
## 
##  Exact binomial test
## 
## data:  t
## number of successes = 305, number of trials = 500, p-value = 9.909e-07
## alternative hypothesis: true probability of success is not equal to 0.5
## 95 percent confidence interval:
##  0.5656996 0.6529917
## sample estimates:
## probability of success 
##                   0.61

Très petite p-value donc : on peut rejeter H0.

Compréhension visuelle du test

Pour que ça semble moins tombé du ciel, il nous est possible de représenter facilement cette distribution théorique, sous H0 : profitons-en !

# La p-value est la probabilité d'avoir une valeur de statistique de test supérieure à la valeur observée, sous H0
pval <- res$p.value
nF <- t[1]

# On représente la densité de la loi binomiale qui nous intéresse
x <- seq(150, 350, 1)
data_binom <- data.frame(x = x,
                         density = dbinom(x, size=500, prob=0.5),
                         cdf = pbinom(x, size=500, prob=0.5))

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

plot_tail <- data_binom %>% ggplot() +
    geom_line(aes(x = x, y = 1-cdf)) +
    geom_hline(yintercept = pval, lty="dashed", color="red") +
    geom_vline(xintercept = nF, lty="dashed") +
    ylab("Queue de distribution") +
    xlab("nombre de femmes") +
    theme_bw()

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

Sur le premier graphe, la valeur observée est extrême par rapport à la densité attendue sous H0. Sur le second graphe, la probabilité de voir une valeur aussi extrême ou plus extrême qu’observée se lit directement en ordonnée de la courbe pointillée rouge : cette valeur est notre p-value !

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, on en reparlera !

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 = 24.2, df = 1, p-value = 8.683e-07

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 with continuity correction
## 
## data:  t, null probability 0.5
## X-squared = 23.762, df = 1, p-value = 1.09e-06
## alternative hypothesis: true p is not equal to 0.5
## 95 percent confidence interval:
##  0.5655522 0.6527314
## sample estimates:
##    p 
## 0.61

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 = 24.2, df = 1, p-value = 8.683e-07
## alternative hypothesis: true p is not equal to 0.5
## 95 percent confidence interval:
##  0.5665640 0.6517587
## sample estimates:
##    p 
## 0.61

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 157 148
M 93 102

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 
## 305 195
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.

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.4633
## alternative hypothesis: true odds ratio is not equal to 1
## 95 percent confidence interval:
##  0.7990228 1.6945366
## sample estimates:
## odds ratio 
##   1.163114

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.

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.305     0.305
## M   0.195     0.195

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

t2 / sum(t2)
##    
##     control treatment
##   F   0.314     0.296
##   M   0.186     0.204

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 example, 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.53804, df = 1, p-value = 0.4632

Là encore, une fonction alternative existe :

prop.test(t2)
## 
##  2-sample test for equality of proportions with continuity correction
## 
## data:  t2
## X-squared = 0.53804, df = 1, p-value = 0.4632
## alternative hypothesis: two.sided
## 95 percent confidence interval:
##  -0.05615244  0.13181448
## sample estimates:
##    prop 1    prop 2 
## 0.5147541 0.4769231

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

Notons enfin la très grande proximité des résultats de chisq.test et fisher.test : ouf !

Comparer 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 :

t3 <- table(d$feels_sleepy)
kable(t3)
Var1 Freq
afternoon 169
always 210
morning 121
chisq.test(t3, p=c(1/3, 1/3, 1/3))
## 
##  Chi-squared test for given probabilities
## 
## data:  t3
## X-squared = 23.812, df = 2, p-value = 6.75e-06

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(t3)
## 
##  Chi-squared test for given probabilities
## 
## data:  t3
## X-squared = 23.812, df = 2, p-value = 6.75e-06

Entre groupes : version Fisher

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

t4 <- table(d$feels_sleepy, d$group)
kable(t4)
control treatment
afternoon 96 73
always 82 128
morning 72 49
fisher.test(t4)
## 
##  Fisher's Exact Test for Count Data
## 
## data:  t4
## p-value = 0.0001462
## alternative hypothesis: two.sided

Cette fois, la p-value est petite ! On peut conclure à une association entre l’outcome feels_sleepy et le bras de traitement.

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

chisq.test(t4)
## 
##  Pearson's Chi-squared test
## 
## data:  t4
## X-squared = 17.578, df = 2, p-value = 0.0001524

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

Intro GLM : la 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.

Le cadre approprié pour faire ça est celui de la régression logistique, qui se trouve être un exemple de GLM : Generalized Linear Model.

Cadre formel

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

Il se trouve simplement que la réponse \(Y\) ne réagit pas forcément linéairement aux modifications de \(X\). On introduit donc ce qu’on appelle une fonction de lien \(g\), et on suppose que la variable de réponse est distribuée suivant une loi dont l’espérance est une transformation d’une combinaison linéaire de nos variables explicatives \((X_i)\).

\[ \mathbb{E}( Y | X ) = g^{-1}( a_0 + a_1 x_1 + a_2 x_2 + ... + a_n x_n ) \]

Dans le cas d’une variable de réponse \(Y\) binaire, la loi de \(Y\) sachant \(X\) est une loi de Bernoulli, de paramètre \(p(X)\). Ce paramètre \(p(X)\) se trouve être la probabilité d’obtenir un succès (codé par un 1), et est donc également l’espérance de \(Y | X\).

Que prendre, alors, comme fonction de lien \(g\) ? Il nous faut une fonction \(g\) de \([0,1]\) dans \(\mathbb{R}\). Pour d’autres raisons qu’on ne détaillera pas ici, on 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 has_lost_weight à partir du poids initial weight_t0. Première chose à faire : un graphe !

p <- d %>% ggplot(aes(x=weight_t0, y=as.numeric(has_lost_weight))) + 
    geom_point() +
    theme_bw() +
    xlab("Poids initial (kg)") +
    ylab("Indicatrice de perte de poids")
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("has_lost_weight ~ weight_t0", family=binomial, data=d)
summary(res)
## 
## Call:
## glm(formula = "has_lost_weight ~ weight_t0", family = binomial, 
##     data = d)
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept) -15.12487    1.39353  -10.85   <2e-16 ***
## weight_t0     0.22973    0.02058   11.16   <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: 658.96  on 499  degrees of freedom
## Residual deviance: 407.41  on 498  degrees of freedom
## AIC: 411.41
## 
## 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’avoir perdu du poids à 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.258264

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.823832   0.276540   2.979  0.00289 **
## age         -0.007917   0.005456  -1.451  0.14679   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 668.75  on 499  degrees of freedom
## Residual deviance: 666.64  on 498  degrees of freedom
## AIC: 670.64
## 
## Number of Fisher Scoring iterations: 4

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

Exemple avec une covariable discrète

Tout l’intérêt du GLM est de pouvoir utiliser tout ce qu’on souhaite comme variable explicative. Qu’obtient-on si on cherche à expliquer has_lost_weight à partir de group ?

Un dessin tout d’abord :

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

Et la régression logistique, ensuite :

res <- glm("has_lost_weight ~ group", family=binomial, data=dbis)
summary(res)
## 
## Call:
## glm(formula = "has_lost_weight ~ group", family = binomial, data = dbis)
## 
## Coefficients:
##                Estimate Std. Error z value Pr(>|z|)    
## (Intercept)      0.0160     0.1265   0.126    0.899    
## grouptreatment   1.1149     0.1941   5.743  9.3e-09 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 658.96  on 499  degrees of freedom
## Residual deviance: 624.38  on 498  degrees of freedom
## AIC: 628.38
## 
## 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 has_lost_weight à partir du group et de weight_t0 ?

res <- glm("has_lost_weight ~ group + weight_t0", family=binomial, data=dbis)
summary(res)
## 
## Call:
## glm(formula = "has_lost_weight ~ group + weight_t0", family = binomial, 
##     data = dbis)
## 
## Coefficients:
##                 Estimate Std. Error z value Pr(>|z|)    
## (Intercept)    -20.49007    1.90096 -10.779  < 2e-16 ***
## grouptreatment   2.48993    0.33260   7.486 7.08e-14 ***
## weight_t0        0.29197    0.02685  10.872  < 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: 658.96  on 499  degrees of freedom
## Residual deviance: 332.59  on 497  degrees of freedom
## AIC: 338.59
## 
## Number of Fisher Scoring iterations: 6

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 à 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. le GLM permet d’étendre le framework de la régression linéaire à des données non continues.
  5. la régression logistique est un exemple de GLM, qui permet d’expliquer des données binaires par des données discrètes ou continues.

Notez pour finir qu’il existe d’autres types de régressions bien adaptées à des données discrètes différentes. Parmi les plus célèbres, vous rencontrerez peut-être un jour la régression softmax, pour des données catégorielles, ou la régression de Poisson, pour des données de comptage d’événements. Elles fonctionnent toutes sur le même principe, avec une fonction de lien différente.

LS0tCnRpdGxlOiAiU2Vzc2lvbiAzIDogU3RhdGlzdGlxdWVzIHByYXRpcXVlcyBzdXIgZG9ubsOpZXMgZGlzY3LDqHRlcyIKYXV0aG9yOiAiTWFyYyBNYW5jZWF1IgpkYXRlOiAiMjAyMy0wNCIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiBUUlVFCiAgICB0b2NfZGVwdGg6IDIKICAgIHRvY19mbG9hdDogVFJVRQogICAgaGlnaGxpZ2h0OiAidGFuZ28iCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFCi0tLQoKUG91ciBsZXMgdXRpbGlzYXRldXJzIGRlIFIsIG9uIGNvbW1lbmNlIHBhciBjaGFyZ2VyIHVuIGNlcnRhaW4gbm9tYnJlIGRlIG1vZHVsZXMgaW50w6lyZXNzYW50cy4KCmBgYHtyfQojIGxlIHBhY2thZ2UgbW9kZXJuZSBzdGFuZGFyZCBwb3VyIG1hbmlwdWxlciBzZXMgZG9ubsOpZXMKbGlicmFyeSh0aWR5dmVyc2UpCiMgcG91ciBmYWlyZSBkZXMgZ3JhcGhlcyBtdWx0aXBsZXMKbGlicmFyeShjb3dwbG90KQojIHBvdXIgYWZmaWNoZXIgam9saW1lbnQgbGVzIHRhYmxlYXV4IGRhbnMgdW4gZG9jdW1lbnQgcm1hcmtkb3duCmxpYnJhcnkoa25pdHIpCgojIHF1ZWxxdWVzIGNvdWxldXJzIG1hbnVlbGxlcwpibGV1Zm9uY2UgPC0gIiMzZDU0NjgiCmJsZXVjbGFpciA8LSAiIzViN2M5OCIKcm9zZSA8LSAiI2ZmNTU1NSIKYGBgCgojIFByw6lwYXJhdGlvbiBkZXMgZG9ubsOpZXMKCiMjIFNpbXVsYXRpb24KClBvdXIgc2UgZm9jYWxpc2VyIHN1ciBsZXMgc3RhdHMgZXQgdW5pcXVlbWVudCBsZXMgc3RhdHMsIApvbiB0cmF2YWlsbGUgc3VyIHVuIGpldSBkZSBkb25uw6llcyBpZMOpYWxpc8OpLgpFdCBwdWlzcXUnb24gYm9zc2UgZGFucyBsZXMgZXNzYWlzIGNsaW5pcXVlcywgb24gdmEgc2ltdWxlciBsZSBSQ1QgKFJhbmRvbWl6ZWQgQ29udHJvbGxlZCBUcmlhbCkKYXlhbnQgbGVzIGNhcmFjdMOpcmlzdGlxdWVzIHN1aXZhbnRlcyA6CgotIGwnw6l0dWRlIHBvc3PDqGRlIGRldXggYnJhczogYGNvbnRyb2xgIGV0IGB0cmVhdG1lbnRgLgotIG9uIGVucmVnaXN0cmUgZGVzIHZhcmlhYmxlcyBkw6ltb2dyYXBoaXF1ZXMgw6AgbCdpbmNsdXNpb24sIHR5cGUgYHNleGAsIGBhZ2VgLCBgYWdlX2dyb3VwYCwgYGhlaWdodGAsIGB3ZWlnaHRfdDBgLgotIGRlcyB2YXJpYWJsZXMgZCdvdXRjb21lcyBsacOpcyDDoCBsYSBwcmlzZSBkZSBwb2lkcyA6IGBkaWZmX3dlaWdodGAsIGBoYXNfbG9zdF93ZWlnaHRgLgotIHVuZSB2YXJpYWJsZSBkJ291dGNvbWUgw6AgdHJvaXMgbW9kYWxpdMOpcyA6IGBmZWVsc19zbGVlcHlgLgotIHVuZSB2YXJpYWJsZSBkZSB0ZW1wcyBkZSBzdWl2aSBkdSB0cmFpdGVtZW50IDogYHRfY29tcGxpYW5jZWAuCi0gdW5lIHZhcmlhYmxlIGQnb3V0Y29tZSBkZSBub21icmUgZGUgY2h1dGVzIHBlbmRhbnQgbGUgc3VpdmkgOiBgbl9mYWxsc2AuCgpQb3VyIGNldXggcXVlIMOnYSBpbnTDqXJlc3NlLCB2b2ljaSBsYSBsb2kgZGFucyBsYXF1ZWxsZSBsZXMgZG9ubsOpZXMgc29udCBzaW11bMOpZXMgOgoKYGBge3J9CiMgZGF0YSB0aGF0IGRvIG5vdCBkZXBlbmQgb24gdGhlIGdyb3VwCmQgPC0gZGF0YS5mcmFtZShpZCA9IHNlcSgxLDUwMCksCiAgICAgICAgICAgICAgICBncm91cCA9IGFzLmZhY3RvcihjKHJlcCgiY29udHJvbCIsMjUwKSwgcmVwKCJ0cmVhdG1lbnQiLDI1MCkpKSwKICAgICAgICAgICAgICAgIHNleCA9IGlmZWxzZShyYmlub20obj01MDAsIHNpemU9MSwgcHJvYj0wLjQpID09IDAsICJGIiwgIk0iKSwKICAgICAgICAgICAgICAgIGFnZSA9IHJvdW5kKHJ1bmlmKG49NTAwLCBtaW49MTksIG1heD03NykpLAogICAgICAgICAgICAgICAgaGVpZ2h0ID0gcm91bmQocm5vcm0obj01MDAsIG1lYW49MS43LCBzZD0wLjEpLCBkaWdpdHM9MiksCiAgICAgICAgICAgICAgICB3ZWlnaHRfdDAgPSByb3VuZChybm9ybShuPTUwMCwgbWVhbj03MCwgc2Q9MTApKSwKICAgICAgICAgICAgICAgIHRfY29tcGxpYW5jZSA9IHJvdW5kKHJleHAobj01MDAsIHJhdGU9MC4wNSkpICApCgojIHBvc3NpYmlsaXRpZXMgZm9yIHRoZSAiZmVlbHNfc2xlZXB5IiB2YXJpYWJsZQptb2RhbGl0ZXMgPC0gYygibW9ybmluZyIsICJhZnRlcm5vb24iLCAiYWx3YXlzIikKCiMgZGF0YSB0aGF0IGRvIGRlcGVuZCBvbiB0aGUgZ3JvdXAKZCA8LSBkICU+JSAKCW11dGF0ZShkaWZmX3dlaWdodCA9IGMocm5vcm0obj0yNTAsIG1lYW49MC41KihkJHdlaWdodF90MFsxOjI1MF0tNzApLCBzZD0yLjUpLCAKCQkJCQkJICAgcm5vcm0obj0yNTAsIG1lYW49MC41KihkJHdlaWdodF90MFsyNTE6NTAwXS03MCkrMC4yNSpkJHRfY29tcGxpYW5jZVsyNTE6NTAwXSwgc2Q9Mi41KSksCgkJICAgZmVlbHNfc2xlZXB5ID0gYyggc2FtcGxlKG1vZGFsaXRlcywgMjUwLCBwcm9iID0gYygwLjMsIDAuNCwgMC4zKSwgcmVwbGFjZSA9IFRSVUUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGUobW9kYWxpdGVzLCAyNTAsIHByb2IgPSBjKDAuMiwgMC4zLCAwLjUpLCByZXBsYWNlID0gVFJVRSkgKSwKCQkgICBuX2ZhbGxzID0gYyhycG9pcyhuPTI1MCwgbGFtYmRhPTYpLCBycG9pcyhuPTI1MCwgbGFtYmRhPTUuNSkpICkKCmQgPC0gZCAlPiUgbXV0YXRlKHdlaWdodF90ZiA9IHdlaWdodF90MCAtIGRpZmZfd2VpZ2h0LAogICAgICAgICAgICAgICAgICBhZ2VfZ3JvdXAgPSBhcy5mYWN0b3IoaWZlbHNlKGFnZSA8IDQwLCAxLCBpZmVsc2UoYWdlIDwgNjAsIDIsIDMpKSksCgkJCQkgIGhhc19sb3N0X3dlaWdodCA9IChkaWZmX3dlaWdodCA+IDApICkKYGBgCgoKIyMgRWNyaXR1cmUvTGVjdHVyZSBkdSBkYXRhc2V0CgpQb3VyIMOqdHJlIHPDu3IgZGUgdHJhdmFpbGxlciBzdXIgbGUgZmljaGllciB0ZWwgcXUnaWwgc2VyYSBsdSBwYXIgdG91dCBsZSBtb25kZSwgCm9uIGVucmVnaXN0cmUgbGUgZGF0YXNldCBzaW11bMOpIHB1aXMgb24gw6ljcmFzZSBsZSBkYXRhc2V0IGF2ZWMgbCdpbXBvcnQuCgpgYGB7cn0Kd3JpdGUuY3N2KGQsICJldHVkZV9maWN0aXZlLmNzdiIpCmQgPC0gcmVhZC5jc3YoImV0dWRlX2ZpY3RpdmUuY3N2IikKaGVhZChkKQpgYGAKCk5vdXMgc29tbWVzIHByw6p0cyDDoCB0cmF2YWlsbGVyIHN1ciBjZSBqZXUgZGUgZG9ubsOpZXMsIGV0LCBwb3VyIGNlIHF1aSBub3VzIGludMOpcmVzc2UgYXVqb3VyZCdodWksCsOgIGFuYWx5c2VyIGxlcyBkb25uw6llcyBkaXNjcsOodGVzIHF1J2lsIHJlbmZlcm1lLgoKCiMgQ29tcGFyZXIgZGVzIHByb3BvcnRpb25zCgojIyBWaXN1ZWxsZW1lbnQKCk5vdXMgYXZvbnMgZGV1eCBjb2xvbm5lcyBiaW5haXJlcyBxdWkgc2UgcHLDqnRlbnQgYmllbiDDoCBjZSBwcmVtaWVyIGV4ZXJjaWNlIDogYHNleGAgZXQgYGhhc19sb3N0X3dlaWdodGAuCk9uIHNvdWhhaXRlcmFpdCBzYXZvaXIgc2kKCihAKSBsYSBwcm9wb3J0aW9uIGRlIGZlbW1lcyBkYW5zIGwnZW5zZW1ibGUgZGUgbCfDqXR1ZGUgZXN0IGRpZmbDqXJlbnRlIGRlIDUwJSwKKEApIGxlcyBkZXV4IGdyb3VwZXMgY29udGllbm5lbnQgbGEgbcOqbWUgcHJvcG9ydGlvbiBkZSBmZW1tZXMsCihAKSBsZXMgZGV1eCBncm91cGVzIGNvbnRpZW5uZW50IGxhIG3Dqm1lIHByb3BvcnRpb24gZGUgcGF0aWVudHMgYXlhbnQgcGVyZHUgZHUgcG9pZHMuCgpDb21tZSB0b3Vqb3VycyBlbiBzdGF0cywgaWwgZXN0IGludMOpcmVzc2FudCBkZSByZXByw6lzZW50ZXIgbGVzIGRvbm7DqWVzIHBvdXIgc2UgZmFpcmUgdW5lIGlkw6llIGludHVpdGl2ZSAhCgpgYGB7ciBmaWcud2lkdGg9MTB9CnAxIDwtIGQgJT4lIAoJZ2dwbG90KGFlcyh4ID0gZ3JvdXAsIGZpbGwgPSBzZXgpKSArCglnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpICsKCXRoZW1lX2J3KCkgKyAKCXNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKGJsZXVjbGFpciwgcm9zZSkpCgpwMiA8LSBkICU+JSAKCWdncGxvdChhZXMoeCA9IGdyb3VwLCBmaWxsID0gaGFzX2xvc3Rfd2VpZ2h0KSkgKwoJZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArCgl0aGVtZV9idygpICsgCglzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YyhibGV1Y2xhaXIsIHJvc2UpKQoKcGxvdF9ncmlkKHAxLCBwMiwgbnJvdz0xLCBuY29sPTIsIGxhYmVsX3NpemUgPSAxMikKYGBgCgpBIHByw6lzZW50IHF1J29uIHZvaXQgdW4gcGV1IG1pZXV4IMOgIHF1b2kgcmVzc2VtYmxlbnQgbm9zIGRvbm7DqWVzLApvbiBhaW1lcmFpdCBzYXZvaXIgc2kgbGVzIGRpZmbDqXJlbmNlcyBkZSBoYXV0ZXVyIGRlIGJhcnJlcyBzb250IGR1ZXMKw6AgZGVzIGZsdWN0dWF0aW9ucyBkJ8OpY2hhbnRpbGxvbm5hZ2Ugb3Ugbm9uLgoKIyMgQXZlYyB1biBhdHRlbmR1IHRow6lvcmlxdWUgYmlub21pYWwKCk9uIHNvdWhhaXRlIHRvdXQgZCdhYm9yZCBjb21wYXJlciBub3RyZSBwcm9wb3J0aW9uIG9ic2VydsOpZQphdmVjIHVuZSBjZXJ0YWluZSBwcm9wb3J0aW9uIGZpeMOpZSwgZXQgcsOpcG9uZHJlIMOgIGxhIHF1ZXN0aW9uIHN1aXZhbnRlIDoKCkxhIHByb3BvcnRpb24gZGUgZmVtbWVzIGRhbnMgbCdlbnNlbWJsZSBkZSBsJ8OpdHVkZSBlc3QtZWxsZSBkaWZmw6lyZW50ZSBkZSA1MCUgPwoKTGEgcHJlbWnDqHJlIHNvbHV0aW9uIC0tIGFwcGVsb25zIGxhICJzb2x1dGlvbiBleGFjdGUiIC0tIApjb25zaXN0ZSDDoCBzJ2FwcHV5ZXIgc3VyIGxhIGRpc3RyaWJ1dGlvbiBhdHRlbmR1ZSBzb3VzIEgwCmR1IG5vbWJyZSBkZSBzdWNjw6hzIGRhbnMgbm90cmUgc3VpdGUgZCdlc3NhaXMsIGF2ZWMgdW5lIHByb2JhYmlsaXTDqQpkZSBzdWNjw6hzIGZpeMOpZS4KCiMjIyBBdHRlbmR1IHRow6lvcmlxdWUKCkxlIG5vbWJyZSBkZSBmZW1tZXMgKHN1Y2PDqHMpIGRhbnMgdW4gw6ljaGFudGlsbG9uIGRlIDUwMCBpbmRpdmlkdXMgKGVzc2FpcykKYXZlYyB1bmUgcHJvcG9ydGlvbiBkZSA1MCUgZGUgZmVtbWVzIChwcm9iYWJpbGl0w6kgZGUgc3VjY8OocykgZXN0CmRpc3RyaWJ1w6kgc2Vsb24gdW5lIGxvaSBiaW5vbWlhbGUgZGUgcGFyYW3DqHRyZSAkXG1hdGhjYWx7Qn0obj01MDAsIHA9MC41KSQuCgpPbiB2YSB0b3V0IHNpbXBsZW1lbnQgY29tcGFyZXIgbGUgbm9tYnJlIGRlIGZlbW1lcyBvYnNlcnbDqWVzIGRhbnMgbm90cmUgw6ljaGFudGlsbG9uCsOgIGNldHRlIGRpc3RyaWJ1dGlvbiB0aMOpb3JpcXVlIHNvdXMgSDAsIGRlIGZhw6dvbiDDoCByZWpldGVyIEgwIHNpIGxhIHZhbGV1ciBvYnNlcnbDqWUKZXN0IHRyb3AgZXh0csOqbWUsIG91IMOgIGNvbnNlcnZlciBIMCBkYW5zIGxlIGNhcyBpbnZlcnNlLgoKIyMjIEV0IGF2ZWMgUiA/CgpMYSBzeW50YXhlIGVzdCBsYSBzdWl2YW50ZSA6CgpgYGB7cn0KdCA8LSB0YWJsZShkJHNleCkKa2FibGUodCkKcmVzIDwtIGJpbm9tLnRlc3QodCwgcD0wLjUsIGNvbmYubGV2ZWw9MC45NSkKcmVzCmBgYAoKVHLDqHMgcGV0aXRlIHAtdmFsdWUgZG9uYyA6IG9uIHBldXQgcmVqZXRlciBIMC4KCiMjIyBDb21wcsOpaGVuc2lvbiB2aXN1ZWxsZSBkdSB0ZXN0CgpQb3VyIHF1ZSDDp2Egc2VtYmxlIG1vaW5zIHRvbWLDqSBkdSBjaWVsLCBpbCBub3VzIGVzdCBwb3NzaWJsZSBkZSByZXByw6lzZW50ZXIgCmZhY2lsZW1lbnQgY2V0dGUgZGlzdHJpYnV0aW9uIHRow6lvcmlxdWUsIHNvdXMgSDAgOiBwcm9maXRvbnMtZW4gIQoKYGBge3IgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTh9CiMgTGEgcC12YWx1ZSBlc3QgbGEgcHJvYmFiaWxpdMOpIGQnYXZvaXIgdW5lIHZhbGV1ciBkZSBzdGF0aXN0aXF1ZSBkZSB0ZXN0IHN1cMOpcmlldXJlIMOgIGxhIHZhbGV1ciBvYnNlcnbDqWUsIHNvdXMgSDAKcHZhbCA8LSByZXMkcC52YWx1ZQpuRiA8LSB0WzFdCgojIE9uIHJlcHLDqXNlbnRlIGxhIGRlbnNpdMOpIGRlIGxhIGxvaSBiaW5vbWlhbGUgcXVpIG5vdXMgaW50w6lyZXNzZQp4IDwtIHNlcSgxNTAsIDM1MCwgMSkKZGF0YV9iaW5vbSA8LSBkYXRhLmZyYW1lKHggPSB4LAogICAgICAgICAgICAgICAgICAgICAgICAgZGVuc2l0eSA9IGRiaW5vbSh4LCBzaXplPTUwMCwgcHJvYj0wLjUpLAogICAgICAgICAgICAgICAgICAgICAgICAgY2RmID0gcGJpbm9tKHgsIHNpemU9NTAwLCBwcm9iPTAuNSkpCgpwbG90X2RlbnNpdHkgPC0gZGF0YV9iaW5vbSAlPiUgZ2dwbG90KGFlcyh4ID0geCwgeSA9IGRlbnNpdHkpKSArCiAgICBnZW9tX2xpbmUoKSArCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBuRiwgbHR5PSJkYXNoZWQiKSArCiAgICBnZW9tX2FyZWEoZGF0YT1kYXRhX2Jpbm9tW3g+bkYsXSwgZmlsbD0icmVkIikgKwogICAgeWxhYigiRGVuc2l0w6kgc291cyBIMCIpICsKICAgIHhsYWIoIm5vbWJyZSBkZSBmZW1tZXMiKSArCiAgICB0aGVtZV9idygpCgpwbG90X3RhaWwgPC0gZGF0YV9iaW5vbSAlPiUgZ2dwbG90KCkgKwogICAgZ2VvbV9saW5lKGFlcyh4ID0geCwgeSA9IDEtY2RmKSkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gcHZhbCwgbHR5PSJkYXNoZWQiLCBjb2xvcj0icmVkIikgKwogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbkYsIGx0eT0iZGFzaGVkIikgKwogICAgeWxhYigiUXVldWUgZGUgZGlzdHJpYnV0aW9uIikgKwogICAgeGxhYigibm9tYnJlIGRlIGZlbW1lcyIpICsKICAgIHRoZW1lX2J3KCkKCnBsb3RfZ3JpZChwbG90X2RlbnNpdHksIHBsb3RfdGFpbCwgbmNvbCA9IDEsIG5yb3cgPSAyKQpgYGAKClN1ciBsZSBwcmVtaWVyIGdyYXBoZSwgbGEgdmFsZXVyIG9ic2VydsOpZSBlc3QgZXh0csOqbWUgcGFyIHJhcHBvcnQgw6AgbGEgZGVuc2l0w6kgYXR0ZW5kdWUgc291cyBIMC4KU3VyIGxlIHNlY29uZCBncmFwaGUsIGxhIHByb2JhYmlsaXTDqSBkZSB2b2lyIHVuZSB2YWxldXIgYXVzc2kgZXh0csOqbWUgb3UgcGx1cyBleHRyw6ptZSBxdSdvYnNlcnbDqWUKc2UgbGl0IGRpcmVjdGVtZW50IGVuIG9yZG9ubsOpZSBkZSBsYSBjb3VyYmUgcG9pbnRpbGzDqWUgcm91Z2UgOiBjZXR0ZSB2YWxldXIgZXN0IG5vdHJlIHAtdmFsdWUgIQoKCiMjIEF2ZWMgdW4gYXR0ZW5kdSB0aMOpb3JpcXVlIDogdmVyc2lvbiAkXGNoaV4yJAoKTGEgc2Vjb25kZSBzb2x1dGlvbiBwb3VyIHLDqXBvbmRyZSDDoCBsYSBtw6ptZSBxdWVzdGlvbiBjb25zaXN0ZSDDoCBmYWlyZSB1biB0ZXN0IGR1ICRcY2hpXjIkLApxdWkgbidlc3QgcGFzIHVuIHRlc3QgZXhhY3QgbWFpcyByZXBvc2Ugc3VyIHVuIHLDqXN1bHRhdCB0aMOpb3JpcXVlIGFzeW1wdG90aXF1ZS4KCi0gSW5jb252w6luaWVudCA6IG5vbi1leGFjdCBldCByZXBvc2Ugc3VyIHVuIHRow6lvcsOobWUgY29tcGxpcXXDqSwKLSBBdmFudGFnZSA6IHRyw6hzIGfDqW7DqXJhbCwgb24gZW4gcmVwYXJsZXJhICEKCiMjIyBDZSBxdWUgZGl0IGxhIHRow6lvcmllCgpPbiBkaXNwb3NlIGQndW4gw6ljaGFudGlsbG9uIGRlICRuJCB2YWxldXJzIG9ic2VydsOpZXMgJChYXzEsIFhfMiwgLi4uLCBYX24pJCAKcXVpIHByZW5uZW50IGxldXJzIHZhbGV1cnMgZGFucyB1biBlbnNlbWJsZSBkaXNjcmV0IGRlICRkJCB2YWxldXJzICRcbWF0aGNhbHtYfSA9IFx7IHhfMSwgeF8yLCAuLi4sIHhfZCBcfSQuCgpPbiBjb25zaWTDqHJlIHF1ZSBub3MgJG4kIG9ic2VydmF0aW9ucyBwcm92aWVubmVudCBkZSB2YXJpYWJsZXMgYWzDqWF0b2lyZXMgaW5kw6lwZW5kYW50ZXMgZXQgaWRlbnRpcXVlbWVudCBkaXN0cmlidcOpZXMgCmRhbnMgdW5lIG3Dqm1lIGxvaSBxdSdvbiBub3RlICRwID0gKHBfMSwgcF8yLCAuLi4sIHBfZCkkLCAKb8O5ICRwX2kkIGVzdCBsYSBwcm9iYWJpbGl0w6kgcXVlIGxhIHZhcmlhYmxlIGFsw6lhdG9pcmUgcHJlbm5lIGxhIHZhbGV1ciAkeF9pJC4KClBhciBhaWxsZXVycywgb24gZGlzcG9zZSBkJ3VuZSBsb2kgZGUgcsOpZsOpcmVuY2Ugc3VyICRcbWF0aGNhbHtYfSQgcXUnb24gYXBwZWxsZSAkcF5cdGV4dHtyZWZ9JCwgCsOgIGxhcXVlbGxlIG9uIHNvdWhhaXRlcmFpdCBjb21wYXJlciBub3RyZSDDqWNoYW50aWxsb24uClBsdXMgcHLDqWNpc8OpbWVudCwgb24gc291aGFpdGUgdGVzdGVyIDoKCiogSDAgOiAkcCA9IHBeXHRleHR7cmVmfSQKKiBIMSA6ICRwIFxuZXEgcF5cdGV4dHtyZWZ9JC4KCk9uIHBldXQgZXN0aW1lciBjaGFjdW4gZGVzICRwX2kkIHBhciBsYSBmcsOpcXVlbmNlIGVtcGlyaXF1ZSAkXGhhdHtwfV9pJCBkZSBsJ29ic2VydmF0aW9uICR4X2kkLCBjJ2VzdCDDoCBkaXJlIDoKJCQgXGhhdHtwfV9pID0gXGZyYWN7MX17bn0gXHN1bV97az0xfV5uIFxkZWx0YV97WF9rID0geF9pfSAkJApvw7kgb24gbm90ZSAkXGRlbHRhX3tYX2sgPSB4X2l9JCBsYSB2YXJpYWJsZSBpbmRpY2F0cmljZSBkZSBsJ8OpdsOpbmVtZW50ICRYX2sgPSB4X2kkLCAKYydlc3Qgw6AgZGlyZSBsYSB2YXJpYWJsZSB2YWxhbnQgMSBsb3JzcXVlICRYX2sgPSB4X2kkIGV0IDAgc2lub24uCgpPbiBzb3VoYWl0ZSBlbnN1aXRlIG1lc3VyZXIgdW5lIGNlcnRhaW5lIGlkw6llIGRlICJkaXN0YW5jZSIgZW50cmUgbGEgZGlzdHJpYnV0aW9uIGVtcGlyaXF1ZW1lbnQgb2JzZXJ2w6llICRcaGF0e3B9JCAKZXQgY2VsbGUgZGUgcsOpZsOpcmVuY2UgJHBeXHRleHR7cmVmfSQuIApDZXR0ZSBkaXN0YW5jZSBlc3QgYXBwZWzDqWUgInBzZXVkby1kaXN0YW5jZSBkdSAkXGNoaV4yJCIsIGV0IGVsbGUgcydleHByaW1lIGNvbW1lIDoKJCQgRF4yX24oIFxoYXR7cH0sIHBeXHRleHR7cmVmfSApID0gbiBcc3VtX3tpPTF9XmQgXGZyYWN7XGxlZnQoXGhhdHtwfV9pIC0gcF5cdGV4dHtyZWZ9X2kgXHJpZ2h0KV4yfXtwXlx0ZXh0e3JlZn1faX0gJCQKClVuIHRow6lvcsOobWUgbm91cyBhc3N1cmUgYWxvcnMgcXVlIDoKCi0gc291cyBIMCwgJEReMl9uICggXGhhdHtwfSwgcF5cdGV4dHtyZWZ9ICkkIHRlbmQgZW4gbG9pIHZlcnMgdW5lIGRpc3RyaWJ1dGlvbiBkdSAkXGNoaV4yJCDDoCAkZC0xJCBkZWdyw6lzIGRlIGxpYmVydMOpLgotIHNvdXMgSDEsICREXjJfbiAoIFxoYXR7cH0sIHBeXHRleHR7cmVmfSApJCB0ZW5kIHZlcnMgbCdpbmZpbmkgcHJlc3F1ZSBzw7tyZW1lbnQuCgojIyMgVXRpbGlzYXRpb24gZGUgbGEgdGjDqW9yaWUgcG91ciBjb25zdHJ1aXJlIHVuIHRlc3QKCkNlIHRow6lvcsOobWUgZXN0IHVuIHRow6lvcsOobWUgYXN5bXB0b3RpcXVlIDogb24gbmUgcGV1dCBkb25jIGwnYXBwbGlxdWVyIHBvdXIgZWZmZWN0dWVyIG5vdHJlIHRlc3QgCnN1ciBkZSB2cmFpZXMgZG9ubsOpZXMgcXVlIHBvdXIgZGUgZ3JhbmRzIGVmZmVjdGlmcyAkbiQgISAKT24gZGVtYW5kZSBhdXNzaSBnw6luw6lyYWxlbWVudCDDoCBjZSBxdWUgJG4gcF5cdGV4dHtyZWZ9X2kkIHNvaXQgYXUgbW9pbnMgw6lnYWwgw6AgNSBwb3VyIHRvdXQgJGkkLgoKU2kgY2VzIGNvbmRpdGlvbnMgZCdlZmZlY3RpZnMgc3VmZmlzYW50cyBzb250IHJlc3BlY3TDqWVzLCBsZSB0ZXN0IGZvbmN0aW9ubmUgYWluc2kgOgoKLSBsYSBzdGF0aXN0aXF1ZSBkZSB0ZXN0ICREXjJfbiggXGhhdHtwfSwgcF5cdGV4dHtyZWZ9ICkkIGVzdCBjYWxjdWzDqWUsCi0gb24gZml4ZSBsJ2VycmV1ciBkZSBwcmVtaWVyIG9yZHJlICRcYWxwaGEkIGFjY2VwdGFibGUsCi0gb24gY2FsY3VsZSAkcV97MS1cYWxwaGF9JCwgbGUgcXVhbnRpbGUgZGUgbGEgbG9pIGR1ICRcY2hpXjIkIMOgICRkLTEkIGRlZ3LDqXMgZGUgbGliZXJ0w6kgZGUgbml2ZWF1ICQxLVxhbHBoYSQsCi0gb24gcmVqZXR0ZSBIMCBzaSAkRF4yX24oIFxoYXR7cH0sIHBeXHRleHR7cmVmfSApID4gcV97MS1cYWxwaGF9JC4KCkludHVpdGl2ZW1lbnQsIG9uIG1lc3VyZSB1bmUgcHNldWRvLWRpc3RhbmNlIGVudHJlIG5vdHJlIGRpc3RyaWJ1dGlvbiBlbXBpcmlxdWUgZXQgbm90cmUgZGlzdHJpYnV0aW9uIGRlIHLDqWbDqXJlbmNlLCBldCBvbiByZWpldHRlIGwnaHlwb3Row6hzZSBudWxsZSBkJ8OpZ2FsaXTDqSBkZXMgZGV1eCBkaXN0cmlidXRpb25zIGxvcnNxdWUgY2V0dGUgcHNldWRvLWRpc3RhbmNlIGRldmllbnQgdHJvcCBncmFuZGUuCgojIyMgRXQgYXZlYyBSID8KCkVuIHVuZSBsaWduZSB0csOocyBzaW1wbGUsIGVzdC1jZSBxdWUgbGUgc2V4ZSBlc3QgZGlzdHJpYnXDqSBkZSBmYcOnb24gdW5pZm9ybWUgPwoKYGBge3J9CnBvYnMgPC0gdApwdGggPC0gYygwLjUsIDAuNSkKcmVzIDwtIGNoaXNxLnRlc3QoeD1wb2JzLCBwPXB0aCkKcmVzCmBgYAoKTm90ZSBwb3VyIGxlcyB1dGlsaXNhdGV1cnMgZGUgUiA6IHVuZSBmb25jdGlvbiBhbHRlcm5hdGl2ZSBleGlzdGUgcG91ciBmYWlyZSBsYSBtw6ptZSBjaG9zZSA6CgpgYGB7cn0KcHJvcC50ZXN0KHQsIHA9MC41LCBjb25mLmxldmVsPTAuOTUpCmBgYAoKT24gcmVtYXJxdWUgdW5lIHRyw6hzIGzDqWfDqHJlIGRpZmbDqXJlbmNlIGRlIHAtdmFsdWUsIHF1aSBlc3QgZHVlIMOgIHVuIHBhcmFtw6h0cmUgcGFyIGTDqWZhdXQKZGUgImNvcnJlY3Rpb24gZGUgY29udGludWl0w6kiIGRhbnMgY2V0dGUgZm9uY3Rpb24uCk9uIHJldHJvdXZlcmEgdW4gcsOpc3VsdGF0IHRvdXQgw6AgZmFpdCBzaW1pbGFpcmUgw6AgdW4gYm9uIHZpZXV4ICRcY2hpXjIkIGF2ZWMgbGEgY29tbWFuZGUgc3VpdmFudGUgOgoKYGBge3J9CnByb3AudGVzdCh0LCBwPTAuNSwgY29uZi5sZXZlbD0wLjk1LCBjb3JyZWN0PUYpCmBgYAoKIyMjIENvbXByw6loZW5zaW9uIHZpc3VlbGxlIGR1IHRlc3QKCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD04fQpEb2JzIDwtIHJlcyRzdGF0aXN0aWMKIyBPbiByZXByw6lzZW50ZSBsYSBkZW5zaXTDqSBkZSBsYSBsb2kgZHUgY2hpMiBxdWkgbm91cyBpbnTDqXJlc3NlCnggPC0gc2VxKDAsIDEwLCAwLjAxKQpkYXRhX2NoaXNxIDwtIGRhdGEuZnJhbWUoeCA9IHgsCiAgICAgICAgICAgICAgICAgICAgICAgICBkZW5zaXR5ID0gZGNoaXNxKHgsIGRmPXJlcyRwYXJhbWV0ZXIpLAogICAgICAgICAgICAgICAgICAgICAgICAgY2RmID0gcGNoaXNxKHgsIGRmPXJlcyRwYXJhbWV0ZXIpKQoKcGxvdF9kZW5zaXR5IDwtIGRhdGFfY2hpc3EgJT4lIGdncGxvdChhZXMoeCA9IHgsIHkgPSBkZW5zaXR5KSkgKwogICAgZ2VvbV9saW5lKCkgKwogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gRG9icywgbHR5PSJkYXNoZWQiKSArCiAgICBnZW9tX2FyZWEoZGF0YT1kYXRhX2NoaXNxW3ggPiBEb2JzLF0sIGZpbGw9InJlZCIpICsKICAgIHlsYWIoIkRlbnNpdMOpIHNvdXMgSDAiKSArCiAgICB4bGFiKCJzdGF0aXN0aXF1ZSBkZSB0ZXN0IikgKwogICAgdGhlbWVfYncoKQoKcGxvdF90YWlsIDwtIGRhdGFfY2hpc3EgJT4lIGdncGxvdCgpICsKICAgIGdlb21fbGluZShhZXMoeCA9IHgsIHkgPSAxLWNkZikpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IHJlcyRwLnZhbHVlLCBsdHk9ImRhc2hlZCIsIGNvbG9yPSJyZWQiKSArCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBEb2JzLCBsdHk9ImRhc2hlZCIpICsKICAgIHlsYWIoIlF1ZXVlIGRlIGRpc3RyaWJ1dGlvbiBzb3VzIEgwIikgKwogICAgeGxhYigic3RhdGlzdGlxdWUgZGUgdGVzdCIpICsKICAgIHRoZW1lX2J3KCkKCnBsb3RfZ3JpZChwbG90X2RlbnNpdHksIHBsb3RfdGFpbCwgbmNvbCA9IDEsIG5yb3cgPSAyKQpgYGAKCiMjIEVudHJlIGdyb3VwZXMgOiB2ZXJzaW9uIEZpc2hlciAKCkEgcHLDqXNlbnQsIG9uIHNvdWhhaXRlIGNvbXBhcmVyIG5vcyBwcm9wb3J0aW9ucyBkZSBmZW1tZXMgZGFucyBsZXMgZGV1eCBicmFzIGR1IFJDVCwKb24gY29uc3RydWl0IGRvbmMgbGEgdGFibGUgZGUgY29udGluZ2VuY2Ugc3VpdmFudGUgOgoKYGBge3J9CnQyIDwtIHRhYmxlKGQkc2V4LCBkJGdyb3VwKQprYWJsZSh0MikKYGBgCgpPbiBjb25zaWTDqHJlIGVuc3VpdGUgcXUnb24gY29ubmHDrnQgdHLDqHMgZXhhY3RlbWVudCBsZSBub21icmUgZCdob21tZXMgZXQgZGUgZmVtbWVzLAphaW5zaSBxdWUgbGUgbm9tYnJlIGRlIHBhdGllbnRzIGRhbnMgbGVzIGdyb3VwZXMgdHJhaXRlbWVudCBldCBjb250csO0bGUgOgoKYGBge3J9CnJvd1N1bXModDIpCmNvbFN1bXModDIpCmBgYAoKTGEgZGlzdHJpYnV0aW9uIGRlcyB0YWJsZXMgZGUgY29udGluZ2VuY2UgcXUnb24gYXR0ZW5kIHNvdXMgSDAgZXN0IGNlbGxlIHF1aSBjb3JyZXNwb25kIMOgIGwnKmluZMOpcGVuZGFuY2UqCmRlcyBkZXV4IG1hcmdpbmFsZXMsIGMnZXN0IMOgIGRpcmUgY2VsbGUgb8O5IG9uIG9idGllbnQgbGUgY29udGVudSBkZSBsYSBwcmVtacOocmUgY29sb25uZQplbiBwaW9jaGFudCBzYW5zIHJlbWlzZSAyNTAgZm9pcyBkYW5zIGxlIHBvb2wgY29ubnUgZGUgZmVtbWVzIGV0IGQnaG9tbWVzLgoKRW4gUiwgb24gYXBwbGlxdWUgY2UgdGVzdCBhdmVjIGxhIGNvbW1hbmRlIHN1aXZhbnRlIDoKCmBgYHtyfQpmaXNoZXIudGVzdCh0MiwgY29uZi5sZXZlbD0wLjk1KQpgYGAKCk5vdGUgOiDDp2EgbmUgY29ycmVzcG9uZCBwYXMgdG90YWxlbWVudCDDoCBsJ2V4cMOpcmllbmNlIHLDqWVsbGUsIGRhbnMgbGFxdWVsbGUgdGVjaG5pcXVlbWVudApzZXVsIGxlIG5vbWJyZSBkZSBwYXRpZW50cyBkYW5zIGxlcyBkZXV4IGdyb3VwZXMgZXN0IGNvbm51LgpJbCBleGlzdGUgZGVzIGFsdGVybmF0aXZlcyDDoCDDp2EsIG/DuSBvbiBuZSBjb25zaWTDqHJlIHF1J3VuZSBzZXVsZSBkZXMgbWFyZ2luYWxlcwpjb21tZSDDqXRhbnQgZml4w6llLiBNYWlzIGNvbW1lIGVuIHByYXRpcXVlIHBlcnNvbm5lIG4ndXRpbGlzZSBjZXMgYWx0ZXJuYXRpdmVzLCBvbiBuJ2VuIHBhcmxlcmEgcGFzIHBsdXMuCgoKIyMgRW50cmUgZ3JvdXBlcyA6IHZlcnNpb24gJFxjaGleMiQKCiMjIyBDb250ZXh0ZSBwcmF0aXF1ZQoKT24gY29udGludWUgZGUgZmlsZXIgbGUgbcOqbWUgZXhlbXBsZSBxdWUgcHLDqWPDqWRlbW1lbnQuClBvdXIgY2hhcXVlIGluZGl2aWR1ICRpJCwgb24gb2JzZXJ2ZSBsZSBzZXhlICRYX2kkIGV0IGxlIGdyb3VwZSAkWV9pJC4gCk9uIHNlIGRlbWFuZGUgc2kgbGUgY2hvaXggZHUgZ3JvdXBlIGVzdCBpbmTDqXBlbmRhbnQgZHUgc2V4ZS4KCi0gSDAgOiBsZSBjaG9peCBkdSBncm91cGUgZXN0IGluZMOpcGVuZGFudCBkdSBzZXhlIChpLmUuIGxhIGxvaSBqb2ludGUgZXN0IGxhIGxvaSBwcm9kdWl0IGRlcyBkZXV4IG1hcmdpbmFsZXMpLAotIEgxIDogbGUgY2hvaXggZHUgZ3JvdXBlIGTDqXBlbmQgZHUgc2V4ZS4KCkwnaWTDqWUgaW50dWl0aXZlIGVzdCBzaW1pbGFpcmUgw6AgY2UgcXVpIHByw6ljw6hkZSA6IG9uIGNvbnNpZMOocmUgZW5jb3JlCmNvbW1lIGh5cG90aMOoc2UgbnVsbGUgbCdpbmTDqXBlbmRhbmNlIGRlcyBtYXJnaW5hbGVzLgoKTGEgZGlzdHJpYnV0aW9uIHRow6lvcmlxdWUgYXR0ZW5kdWUgZXN0IGRvbmMgOgpgYGB7cn0KcF9zZXggPC0gcm93U3Vtcyh0Mikvc3VtKHQyKQpwX2dyb3VwIDwtIGNvbFN1bXModDIpL3N1bSh0MikKcHRoIDwtIHBfc2V4ICVvJSBwX2dyb3VwCnB0aApgYGAKClF1ZSBsJ29uIGNvbXBhcmUgw6AgbGEgZGlzdHJpYnV0aW9uIG9ic2VydsOpZSA6CmBgYHtyfQp0MiAvIHN1bSh0MikKYGBgCgojIyMgQ29udGV4dGUgZm9ybWFsaXPDqQoKT24gZGlzcG9zZSBjZXR0ZSBmb2lzIGRlICRuJCBjb3VwbGVzIGQnb2JzZXJ2YXRpb25zIGRlIHZhcmlhYmxlcyBkaXNjcsOodGVzICQoWF9pLCBZX2kpJCBtZXN1csOpZXMgc3VyIGNoYXF1ZSBpbmRpdmlkdSAkaSQuCkxlcyAkKFhfaSkkIHNvbnQgw6AgdmFsZXVyIGRhbnMgdW4gZW5zZW1ibGUgJFxtYXRoY2Fse1h9JCDDoCAkZCQgw6lsw6ltZW50cywgCnRhbmRpcyBxdWUgbGVzICQoWV9pKSQgc29udCDDoCB2YWxldXIgZGFucyB1biBlbnNlbWJsZSAkXG1hdGhjYWx7WX0kIMOgICRlJCDDqWzDqW1lbnRzLgoKT24gc3VwcG9zZSBxdWUgbm9zIGNvdXBsZXMgc29udCBpbmTDqXBlbmRhbnRzIGV0IGlkZW50aXF1ZW1lbnQgZGlzdHJpYnXDqXMgCnNlbG9uIHVuZSBsb2kgJFxudSQgc3VyICRcbWF0aGNhbHtYfSBcdGltZXMgXG1hdGhjYWx7WX0kLiAKVHlwaXF1ZW1lbnQsIHVuZSB0ZWxsZSBkaXN0cmlidXRpb24gc2VyYWl0IHBhcmFtw6l0csOpZSBwYXIgJChkIGUgLSAxKSQgdmFsZXVycy4gCgpPbiBzb3VoYWl0ZSDDoCBwcsOpc2VudCBzYXZvaXIgc2kgbGVzICRYJCBldCBsZXMgJFkkIHByZW5uZW50IGxldXJzIHZhbGV1cnMgaW5kw6lwZW5kYW1tZW50IGwndW4gZGUgbCdhdXRyZSwgCmMnZXN0IMOgIGRpcmUgc2kgbGEgbG9pICRcbnUkIGFwcGFydGllbnQgw6AgbGEgZmFtaWxsZSBkZXMgbG9pcyBwcm9kdWl0LgpPbiBub3RlIGNldHRlIGZhbWlsbGUgOgokJCBcbWF0aGNhbHtGfSA9IFx7IHAgXG90aW1lcyBxLCB+IHAgXGluIFxtYXRoY2Fse1B9KFxtYXRoY2Fse1h9KSwgcSBcaW4gXG1hdGhjYWx7UH0oXG1hdGhjYWx7WX0pIFx9ICQkCm/DuSAkXG1hdGhjYWx7UH0oXG1hdGhjYWx7WH0pJCBkw6lzaWduZSBsJ2Vuc2VtYmxlIGRlcyBkaXN0cmlidXRpb25zIHN1ciBsJ2Vuc2VtYmxlIGRpc2NyZXQgJFxtYXRoY2Fse1h9JC4KClR5cGlxdWVtZW50LCB1bmUgZGlzdHJpYnV0aW9uIGRlICRcbWF0aGNhbHtGfSQgbidlc3QgZG9uYyBwbHVzIHBhcmFtw6l0csOpZSBxdWUgcGFyICQoZCArIGUgLSAyKSQgdmFsZXVycyA6IApsZXMgJChkLTEpJCB2YWxldXJzIHF1aSBwYXJhbcOpdHJpc2VudCAkcCQgZXQgbGVzICQoZS0xKSQgdmFsZXVycyBxdWkgcGFyYW3DqXRyaXNlbnQgJHEkLgoKQXZlYyBjZXMgbm90YXRpb25zLCBvbiBwb3VycmEgdGVzdGVyIDoKCi0gSDAgOiAkXG51IFxpbiBcbWF0aGNhbHtGfSQsCi0gSDEgOiAkXG51IFxub3RpbiBcbWF0aGNhbHtGfSQuCgoKIyMjIENlIHF1ZSBkaXQgbGEgdGjDqW9yaWUKCk9uIHBldXQgZXN0aW1lciBjaGFjdW4gZGVzICRwX2kkIHBhciBsYSBmcsOpcXVlbmNlIGVtcGlyaXF1ZSBkZSBsJ29ic2VydmF0aW9uICR4X2kkLCBjJ2VzdCDDoCBkaXJlIDoKJCQgXGhhdHtwfV9pID0gXGZyYWN7MX17bn0gXHN1bV97az0xfV5uIFxkZWx0YV97WF9rID0geF9pfSAkJApPbiBwZXV0IMOpZ2FsZW1lbnQgZmFpcmUgZGUgbcOqbWUgcG91ciBsZXMgZnLDqXF1ZW5jZXMgZW1waXJpcXVlcyBkZXMgJHlfaiQsIGVuIGNhbGN1bGFudCA6CiQkIFxoYXR7cX1faiA9IFxmcmFjezF9e259IFxzdW1fe2s9MX1ebiBcZGVsdGFfe1lfayA9IHlfan0gJCQKRXQgZW5maW4sIGZhaXJlIGRlIG3Dqm1lIHBvdXIgbGVzIGZyw6lxdWVuY2VzIGVtcGlyaXF1ZXMgZGVzIGRpZmbDqXJlbnRzIGNvdXBsZXMgJCh4X2ksIHlfaikkIDoKJCQgXGhhdHtcbnV9X3tpLGp9ID0gXGZyYWN7MX17bn0gXHN1bV97az0xfV5uIFxkZWx0YV97KFhfaywgWV9rKSA9ICh4X2ksIHlfail9ICQkCgpPbiBzb3VoYWl0ZSBlbnN1aXRlIG1lc3VyZXIgbGEgInBzZXVkby1kaXN0YW5jZSBkdSAkXGNoaV4yJCIgCmVudHJlIGxhIGRpc3RyaWJ1dGlvbiBlbXBpcmlxdWVtZW50IG9ic2VydsOpZSAkXGhhdHtcbnV9JCAKZXQgY2VsbGUgZG9ubsOpZSBwYXIgbGUgbWVpbGxldXIgYWp1c3RlbWVudCDDoCB1bmUgbG9pIHByb2R1aXQgJFxoYXR7cH0gXG90aW1lcyBcaGF0e3F9JCwgc29pdCA6CiQkIApEXjJfbiggXGhhdHtcbnV9LCBcaGF0e3B9XG90aW1lc1xoYXR7cX0gKSA9IApuIFxzdW1fe2k9MX1eZCBcc3VtX3tqPTF9XmUgXGZyYWN7XGxlZnQoIFxoYXR7XG51fV97aSxqfSAtIFxoYXR7cH1faSBcaGF0e3F9X2ogXHJpZ2h0KV4yfXtcaGF0e3B9X2kgXGhhdHtxfV9qfSAKJCQKClVuIHRow6lvcsOobWUgbm91cyBhc3N1cmUgYWxvcnMgcXVlCgotIHNvdXMgSDAsICREXjJfbiggXGhhdHtcbnV9LCBcaGF0e3B9XG90aW1lc1xoYXR7cX0gKSQgdGVuZCBlbiBsb2kgdmVycyB1bmUgZGlzdHJpYnV0aW9uIGR1ICRcY2hpXjIkIMOgICQoZC0xKSAoZS0xKSQgZGVncsOpcyBkZSBsaWJlcnTDqS4KLSBzb3VzIEgxLCAkRF4yX24oIFxoYXR7XG51fSwgXGhhdHtwfVxvdGltZXNcaGF0e3F9ICkkIHRlbmQgdmVycyBsJ2luZmluaSBwcmVzcXVlIHPDu3JlbWVudC4KClBvdXIgY2V1eCBxdWkgc291aGFpdGVyYWllbnQgc2F2b2lyIGQnb8O5IHZpZW50IGxlIG5vbWJyZSBkZSBkZWdyw6lzIGRlIGxpYmVydMOpLCBub3RleiBxdWUsIGNvbW1lIHByw6ljw6lkZW1tZW50LCAKaWwgY29ycmVzcG9uZCDDoCBsYSBkaW1lbnNpb24gZGUgbCdlc3BhY2UgZGFucyBsZXF1ZWwgdml0IGxhIGxvaSBkZSBwcm9iYWJpbGl0w6kgJFxudSQgKHNvaXQgJGRlIC0xJCkgCm1vaW5zIGxhIGRpbWVuc2lvbiBkZSBsJ2VzcGFjZSBkYW5zIGxlcXVlbCB2aXQgbGEgZGlzdHJpYnV0aW9uIMOgIGxhcXVlbGxlIG9uIHNvdWhhaXRlIHNlIHJhbWVuZXIgKGljaSAkZCArIGUgLSAyJCkuCgojIyMgVXRpbGlzYXRpb24gZGUgbGEgdGjDqW9yaWUgcG91ciBjb25zdHJ1aXJlIHVuIHRlc3QKCkNvbW1lIHRvdWpvdXJzIGF2ZWMgbGVzIHRlc3RzIGR1ICRcY2hpXjIkLCBub3RyZSB0ZXN0IHJlcG9zZSBzdXIgdW4gdGjDqW9yw6htZSBhc3ltcHRvdGlxdWUgOiAKb24gbmUgcGV1dCBkb25jIGwnYXBwbGlxdWVyIHBvdXIgZWZmZWN0dWVyIG5vdHJlIHRlc3Qgc3VyIGRlIHZyYWllcyBkb25uw6llcyBxdWUgcG91ciBkZSBncmFuZHMgZWZmZWN0aWZzICRuJCAhCgpTaSBjZXMgY29uZGl0aW9ucyBkJ2VmZmVjdGlmcyBzdWZmaXNhbnRzIHNvbnQgcmVzcGVjdMOpZXMsIGxlIHRlc3QgZm9uY3Rpb25uZSBhaW5zaSA6CgotIGxhIHN0YXRpc3RpcXVlIGRlIHRlc3QgJEReMl9uKCBcaGF0e1xudX0sIFxoYXR7cH1cb3RpbWVzXGhhdHtxfSApJCBlc3QgY2FsY3Vsw6llLAotIG9uIGZpeGUgbCdlcnJldXIgZGUgcHJlbWllciBvcmRyZSAkXGFscGhhJCBhY2NlcHRhYmxlLAotIG9uIGNhbGN1bGUgJHFfezEtXGFscGhhfSQsIGxlIHF1YW50aWxlIGRlIG5pdmVhdSAkMS1cYWxwaGEkIGRlIGxhIGxvaSBkdSAkXGNoaV4yJCDDoCAkKGQtMSkgKGUtMSkkIGRlZ3LDqXMgZGUgbGliZXJ0w6ksCi0gb24gcmVqZXR0ZSBIMCBzaSAkRF4yX24oIFxoYXR7XG51fSwgXGhhdHtwfVxvdGltZXNcaGF0e3F9ICkgPiBxX3sxLVxhbHBoYX0kLgoKSW50dWl0aXZlbWVudCwgb24gbWVzdXJlIHVuZSBwc2V1ZG8tZGlzdGFuY2UgZW50cmUgbm90cmUgZGlzdHJpYnV0aW9uIGVtcGlyaXF1ZSAKZXQgbm90cmUgbWVpbGxldXJlIGVzdGltYXRpb24gZGUgbG9pIHByb2R1aXQsIGV0IG9uIHJlamV0dGUgbCdoeXBvdGjDqHNlIG51bGxlIGQnw6lnYWxpdMOpIGRlcyBkZXV4IGRpc3RyaWJ1dGlvbnMgCmxvcnNxdWUgY2V0dGUgcHNldWRvLWRpc3RhbmNlIGRldmllbnQgdHJvcCBncmFuZGUuCgojIyMgRXQgYXZlYyBSID8KCkRhbnMgbm90cmUgZXhhbXBsZSwgb24gc291aGFpdGUgdGVzdGVyIGwnaW5kw6lwZW5kYW5jZSBkdSBzZXhlIGV0IGR1IGdyb3VwZSBjaGV6IG5vcyBpbmRpdmlkdXMuCkVuIHVuZSBsaWduZSwgaWwgbm91cyBzdWZmaXQgZCfDqWNyaXJlIDoKCmBgYHtyfQpyZXMgPC0gY2hpc3EudGVzdCh0MikKcmVzCmBgYAoKTMOgIGVuY29yZSwgdW5lIGZvbmN0aW9uIGFsdGVybmF0aXZlIGV4aXN0ZSA6CgpgYGB7cn0KcHJvcC50ZXN0KHQyKQpgYGAKCkNldHRlIGZvaXMtY2ksIGxlIGNvbXBvcnRlbWVudCBkZXMgZGV1eCBmb25jdGlvbnMgZXN0IGxlIG3Dqm1lIHBhciBkw6lmYXV0LAphdmVjIGNldHRlICJjb3JyZWN0aW9uIGRlIGNvbnRpbnVpdMOpIiBkYW5zIGxlcyBkZXV4IGNhcy4KCk5vdG9ucyBlbmZpbiBsYSB0csOocyBncmFuZGUgcHJveGltaXTDqSBkZXMgcsOpc3VsdGF0cyBkZSBgY2hpc3EudGVzdGAgZXQgYGZpc2hlci50ZXN0YCA6IG91ZiAhCgoKIyBDb21wYXJlciBkZXMgZG9ubsOpZXMgY2F0w6lnb3JpZWxsZXMKCkxhIGJvbm5lIG5vdXZlbGxlLCBjJ2VzdCBxdWUgdG91dCBlc3QgdHLDqHMgc2ltaWxhaXJlIMOgIGNlIHF1J29uIHZpZW50IGRlIHZvaXIuCgpEJ2FpbGxldXJzLCB0b3V0IGNlIHF1ZSBqZSBwb3V2YWlzIMOpY3JpcmUgZGUgZmHDp29uIGfDqW7DqXJhbGUgcHLDqWPDqWRlbW1lbnQgYSDDqXTDqSDDqWNyaXQKZGUgZmHDp29uIGfDqW7DqXJhbGUsIGF2ZWMgcGx1cyBkZSBkZXV4IGNhdMOpZ29yaWVzIG5vdGFtbWVudC4KCklsIG5lIHJlc3RlIGRvbmMgcXUnw6AgcydlbnRyYcOubmVyICEgT24gcydpbnTDqXJlc3NlIMOgIHByw6lzZW50IGF1eCBkb25uw6llcyBkJ291dGNvbWUgYGZlZWxzX3NsZWVweWAuCgpgYGB7ciBmaWcud2lkdGg9MTB9CmQgJT4lIGdncGxvdChhZXMoeCA9IGdyb3VwLCBmaWxsID0gZmVlbHNfc2xlZXB5KSkgKwoJZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArCgl0aGVtZV9idygpICsgCglzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YyhibGV1Y2xhaXIsIGJsZXVmb25jZSwgcm9zZSkpCmBgYAoKIyMgQXZlYyB1bmUgbG9pIGRvbm7DqWUKCk9uIHBldXQgdXRpbGlzZXIgdW4gdGVzdCBkdSAkXGNoaV4yJCwgcGFyIGV4ZW1wbGUgcG91ciB0ZXN0ZXIgbGEgZGlmZsOpcmVuY2UgZGUgbGEgZGlzdHJpYnV0aW9uCm9ic2VydsOpZSBwYXIgcmFwcG9ydCDDoCB1bmUgZGlzdHJpYnV0aW9uIGhvbW9nw6huZSBzdXIgbGVzIHRyb2lzIGNhdMOpZ29yaWVzIDoKCmBgYHtyfQp0MyA8LSB0YWJsZShkJGZlZWxzX3NsZWVweSkKa2FibGUodDMpCmNoaXNxLnRlc3QodDMsIHA9YygxLzMsIDEvMywgMS8zKSkKYGBgCgpQb3VyIGxlcyB1dGlsaXNhdGV1cnMgZGUgUiwgbm90b25zIHF1ZSwgcGFyIGTDqWZhdXQsIGxlIGNvbXBvcnRlbWVudCBkZSBsYSBmb25jdGlvbiBgY2hpc3EudGVzdGAKw6AgcXVpIG9uIG5lIGRvbm5lIHBhcyBkJ2FyZ3VtZW50IGBwYCBlc3QganVzdGVtZW50IGRlIGNvbXBhcmVyIMOgIHVuZSBkaXN0cmlidXRpb24gdW5pZm9ybWUgOgoKYGBge3J9CmNoaXNxLnRlc3QodDMpCmBgYAoKIyMgRW50cmUgZ3JvdXBlcyA6IHZlcnNpb24gRmlzaGVyCgpMZSBtw6ptZSBwcmluY2lwZSBxdWUgZGFucyBsZSBjYXMgMngyIGVzdCBhcHBsaXF1w6kgc3VyIG5vdHJlIHRhYmxlIGRlIGNvbnRpbmdlbmNlIDN4Mi4KCmBgYHtyfQp0NCA8LSB0YWJsZShkJGZlZWxzX3NsZWVweSwgZCRncm91cCkKa2FibGUodDQpCmZpc2hlci50ZXN0KHQ0KQpgYGAKCkNldHRlIGZvaXMsIGxhIHAtdmFsdWUgZXN0IHBldGl0ZSAhCk9uIHBldXQgY29uY2x1cmUgw6AgdW5lIGFzc29jaWF0aW9uIGVudHJlIGwnb3V0Y29tZSBgZmVlbHNfc2xlZXB5YCBldCBsZSBicmFzIGRlIHRyYWl0ZW1lbnQuCgojIyBFbnRyZSBncm91cGVzIDogdmVyc2lvbiAkXGNoaV4yJAoKYGBge3J9CmNoaXNxLnRlc3QodDQpCmBgYAoKRW5jb3JlIHVuIHLDqXN1bHRhdCBiaWVuIHNpbWlsYWlyZSDDoCBjZWx1aSBkdSB0ZXN0IGRlIEZpc2hlciA6IG91ZiAhCgoKIyBJbnRybyBHTE0gOiBsYSByw6lncmVzc2lvbiBsb2dpc3RpcXVlCgpKdXNxdSfDoCBwcsOpc2VudCwgbm91cyBhdm9ucyBjaGVyY2jDqSDDoCByZWpldGVyIHVuZSBoeXBvdGjDqHNlIG51bGxlIGQnaG9tb2fDqW7DqWl0w6kgZCd1biBjZXJ0YWluIG5vbWJyZSBkZQpkaXN0cmlidXRpb25zIGRpc2Nyw6h0ZXMsIGTDqWZpbmllcyBzZWxvbiBsYSB2YWxldXIgZCd1bmUgY292YXJpYWJsZS4KTWFpcyBub3VzIG4nYXZvbnMgcGFzICpxdWFudGlmacOpKiBsJ2ltcGFjdCBkZSBjZXR0ZSBjb3ZhcmlhYmxlIHN1ciBub3RyZSBkaXN0cmlidXRpb24gZCdpbnTDqXLDqnQuCgpMZSBjYWRyZSBhcHByb3ByacOpIHBvdXIgZmFpcmUgw6dhIGVzdCBjZWx1aSBkZSBsYSByw6lncmVzc2lvbiBsb2dpc3RpcXVlLCBxdWkgc2UgdHJvdXZlIMOqdHJlIHVuIGV4ZW1wbGUgZGUKKkdMTSA6IEdlbmVyYWxpemVkIExpbmVhciBNb2RlbCouCgojIyBDYWRyZSBmb3JtZWwKCkRhbnMgbGUgR0xNLCBvbiBzJ2ludMOpcmVzc2Ugw6AgdW5lIHZhcmlhYmxlIGRlIHLDqXBvbnNlICRZJCwgcXUnb24gc291aGFpdGUgZXhwbGlxdWVyIGF2ZWMgZGVzIHZhcmlhYmxlcwokWF8xLCBYXzIsIC4uLiwgWF9uJCBkaXNjcsOodGVzIG91IGNvbnRpbnVlcy4KCklsIHNlIHRyb3V2ZSBzaW1wbGVtZW50IHF1ZSBsYSByw6lwb25zZSAkWSQgbmUgcsOpYWdpdCBwYXMgZm9yY8OpbWVudCBsaW7DqWFpcmVtZW50IGF1eCBtb2RpZmljYXRpb25zIGRlICRYJC4KT24gaW50cm9kdWl0IGRvbmMgY2UgcXUnb24gYXBwZWxsZSB1bmUgZm9uY3Rpb24gZGUgbGllbiAkZyQsIGV0IG9uIHN1cHBvc2UgcXVlCmxhIHZhcmlhYmxlIGRlIHLDqXBvbnNlIGVzdCBkaXN0cmlidcOpZSBzdWl2YW50IHVuZSBsb2kgZG9udCBsJ2VzcMOpcmFuY2UgZXN0IHVuZSB0cmFuc2Zvcm1hdGlvbgpkJ3VuZSBjb21iaW5haXNvbiBsaW7DqWFpcmUgZGUgbm9zIHZhcmlhYmxlcyBleHBsaWNhdGl2ZXMgJChYX2kpJC4KCiQkClxtYXRoYmJ7RX0oIFkgfCBYICkgPSBnXnstMX0oIGFfMCArIGFfMSB4XzEgKyBhXzIgeF8yICsgLi4uICsgYV9uIHhfbiApCiQkCgpEYW5zIGxlIGNhcyBkJ3VuZSB2YXJpYWJsZSBkZSByw6lwb25zZSAkWSQgYmluYWlyZSwgbGEgbG9pIGRlICRZJCBzYWNoYW50ICRYJCBlc3QgdW5lIGxvaSBkZSBCZXJub3VsbGksCmRlIHBhcmFtw6h0cmUgJHAoWCkkLgpDZSBwYXJhbcOodHJlICRwKFgpJCBzZSB0cm91dmUgw6p0cmUgbGEgcHJvYmFiaWxpdMOpIGQnb2J0ZW5pciB1biBzdWNjw6hzIChjb2TDqSBwYXIgdW4gMSksIApldCBlc3QgZG9uYyDDqWdhbGVtZW50IGwnZXNww6lyYW5jZSBkZSAkWSB8IFgkLgoKUXVlIHByZW5kcmUsIGFsb3JzLCBjb21tZSBmb25jdGlvbiBkZSBsaWVuICRnJCA/CklsIG5vdXMgZmF1dCB1bmUgZm9uY3Rpb24gJGckIGRlICRbMCwxXSQgZGFucyAkXG1hdGhiYntSfSQuClBvdXIgZCdhdXRyZXMgcmFpc29ucyBxdSdvbiBuZSBkw6l0YWlsbGVyYSBwYXMgaWNpLCBvbiBjaG9pc2l0IGxhIGZvbmN0aW9uIHN1aXZhbnRlIDoKCiQkCnAoWCkgPSBcZnJhY3sxfXsxICsgZV57LShhXzAgKyBhXzEgeF8xICsgYV8yIHhfMiArIC4uLiArIGFfbiB4X24pfX0gXFwKXGxuIFxsZWZ0KCBcZnJhY3twfXsxLXB9IFxyaWdodCkgPSBhXzAgKyBhXzEgeF8xICsgYV8yIHhfMiArIC4uLiArIGFfbiB4X24KJCQKClN1ciBsYSBwcmVtacOocmUgbGlnbmUsIGxhIGZvbmN0aW9uIGRlIGRyb2l0ZSAkdSBcbWFwc3RvIFxmcmFjezF9ezErZV57LXV9fSQgZXN0IGFwcGVsw6llICpmb25jdGlvbiBsb2dpc3RpcXVlKi4KU3VyIGxhIHNlY29uZGUgbGlnbmUsIGxhIGZvbmN0aW9uIGRlIGdhdWNoZSAkdSBcbWFwc3RvIFxsbiBcZnJhY3t1fXsxLXV9JCBlc3QgbGEgcsOpY2lwcm9xdWUgZGUgbGEgZm9uY3Rpb24KbG9naXN0aXF1ZSwgYXBwZWzDqWUgKmZvbmN0aW9uIGxvZ2l0Ki4KCiMjIEV4ZW1wbGUgYXZlYyB1bmUgY292YXJpYWJsZSBjb250aW51ZQoKT24gY29tbWVuY2UgYXZlYyB1bmUgY292YXJpYWJsZSBjb250aW51ZSwgZGUgZmHDp29uIMOgIHNlIGZhaXJlIHVuZSBpZMOpZSB2aXN1ZWxsZSBkZSBsYSBmb3JtZSBkZSBsYSBmb25jdGlvbiBsb2dpc3RpcXVlLgoKT24gc291aGFpdGVyYWl0IGRhbnMgdW4gcHJlbWllciB0ZW1wcyBleHBsaXF1ZXIgYGhhc19sb3N0X3dlaWdodGAgw6AgcGFydGlyIGR1IHBvaWRzIGluaXRpYWwgYHdlaWdodF90MGAuClByZW1pw6hyZSBjaG9zZSDDoCBmYWlyZSA6IHVuIGdyYXBoZSAhCgpgYGB7ciBmaWcud2lkdGg9MTB9CnAgPC0gZCAlPiUgZ2dwbG90KGFlcyh4PXdlaWdodF90MCwgeT1hcy5udW1lcmljKGhhc19sb3N0X3dlaWdodCkpKSArIAogICAgZ2VvbV9wb2ludCgpICsKICAgIHRoZW1lX2J3KCkgKwoJeGxhYigiUG9pZHMgaW5pdGlhbCAoa2cpIikgKwoJeWxhYigiSW5kaWNhdHJpY2UgZGUgcGVydGUgZGUgcG9pZHMiKQpwCmBgYAoKTCdpbGx1c3RyYXRpb24gc3VpdmFudGUgc3VwZXJwb3NlIMOgIGNlIHByZW1pZXIgZ3JhcGhpcXVlIGxlIGZpdCBkJ3VuIG1vZMOobGUgbG9naXN0aXF1ZSwKZW4gY29tcGFyYWlzb24gYXZlYyBjZSBxdWkgcG91cnJhaXQgw6p0cmUgdW5lIG1hdXZhaXNlIHV0aWxpc2F0aW9uIGQndW4gc2ltcGxlIG1vZMOobGUgbGluw6lhaXJlLgoKYGBge3IgZmlnLndpZHRoPTEwfQpwICsgZ2VvbV9zbW9vdGgoZm9ybXVsYSA9ICJ5fngiLAogICAgICAgICAgICAgICAgbWV0aG9kID0gImdsbSIsCiAgICAgICAgICAgICAgICBtZXRob2QuYXJncyA9IGxpc3QoZmFtaWx5ID0gYmlub21pYWwpLAogICAgICAgICAgICAgICAgc2UgPSBGQUxTRSwKCQkJCWNvbG9yID0gYmxldWNsYWlyKSArCglnZW9tX3Ntb290aChmb3JtdWxhID0gInl+eCIsIG1ldGhvZCA9ICJsbSIsIGNvbG9yID0gcm9zZSwgc2UgPSBGKSAKYGBgCgpMJ2F2YW50YWdlIGRlIGxhIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUgc2F1dGUgYXV4IHlldXggOiBvbiBwcsOpZGl0IGJpZW4gdW5lIHZhcmlhYmxlIGVudHJlIDAgZXQgMSwKZXQgbGEgdmFsZXVyIGRlIGxhIGNvdXJiZSBibGV1ZSBwZXV0IGRpcmVjdGVtZW50IHMnaW50ZXJwcsOpdGVyIGNvbW1lIGxhIHByb2JhYmlsaXTDqSwgcG91ciB1biBwYXRpZW50CmF5YW50IGxlIHBvaWRzIGVuIGFic2Npc3NlLCBkJ2F2b2lyIHBlcmR1IGR1IHBvaWRzIMOgIGwnaXNzdWUgZGUgbCdlc3NhaS4KCk9uIHBldXQgcsOpY3Vww6lyZXIgbGVzIHZhbGV1cnMgZGUgbm90cmUgZml0IGF2ZWMgdW4gYXBwZWwgw6AgOgoKYGBge3J9CnJlcyA8LSBnbG0oImhhc19sb3N0X3dlaWdodCB+IHdlaWdodF90MCIsIGZhbWlseT1iaW5vbWlhbCwgZGF0YT1kKQpzdW1tYXJ5KHJlcykKYGBgCgpMZXMgY29lZmZpY2llbnRzIHJlbnZvecOpcyBjb3JyZXNwb25kZW50IMOgIGNldXggZGUgbGEgY29tYmluYWlzb24gbGluw6lhaXJlIGRlIHZhcmlhYmxlcyBleHBsaWNhdGl2ZXMsIApjJ2VzdCDDoCBkaXJlIGF1eCAkKGFfaSkkIGRlcyBmb3JtdWxlcyBwcsOpLWNpdMOpZXMuCkRhbnMgbm90cmUgY2FzLCBsZSBjb2VmZmljaWVudCBlbiBmYWNlIGRlIGB3ZWlnaHRfdDBgIGVzdCBwb3NpdGlmLCBkb25jIHVuIHBhdGllbnQgcGx1cyBsb3VyZCBpbml0aWFsZW1lbnQKZXN0IHBsdXMgc3VzY2VwdGlibGUgZCdhdm9pciBwZXJkdSBkdSBwb2lkcyDDoCBsJ2lzc3VlIGRlIGwnZXNzYWkuCgpQb3VyIHNlIGRvbm5lciB1bmUgaWTDqWUgcXVhbnRpdGF0aXZlIGRlIGwnaW1wYWN0IGQndW4ga2cgc3VwcGzDqW1lbnRhaXJlIGF1IGTDqWJ1dCBkZSBsJ2Vzc2FpLCBvbiBwZXV0IGNhbGN1bGVyIApyYXBpZGVtZW50IGwnb2RkcyByYXRpbyBhc3NvY2nDqSDDoCBjZSBrZyBzdXBwbMOpbWVudGFpcmUgZW4gcHJlbmFudCBsJ2V4cG9uZW50aWVsbGUgZGUgbm90cmUgcGFyYW3DqHRyZS4KRW4gZWZmZXQsCgokJApcYmVnaW57YWxpZ24qfQpcZnJhY3sgXGZyYWN7IHAoWF8xID0geCsxKSB9eyAxIC0gcChYXzEgPSB4ICsgMSkgfSB9eyBcZnJhY3sgcChYXzE9eCkgfXsgMSAtIHAoWF8xID0geCkgfSB9IAomPSBcZXhwIFxsbiBcZnJhY3sgXGZyYWN7IHAoWF8xID0geCsxKSB9eyAxIC0gcChYXzEgPSB4ICsgMSkgfSB9eyBcZnJhY3sgcChYXzE9eCkgfXsgMSAtIHAoWF8xID0geCkgfSB9IFxcCiY9IFxleHAgXGxlZnQoIFxsbiBcZnJhY3sgcChYXzEgPSB4KzEpIH17IDEgLSBwKFhfMSA9IHggKyAxKSB9IC0gXGxuIFxmcmFjeyBwKFhfMT14KSB9eyAxIC0gcChYXzEgPSB4KSB9IFxyaWdodCkgXFwKJj0gXGV4cCBcbGVmdCggYV8wICsgYV8xKHggKyAxKSAtIGFfMCAtIGFfMXggXHJpZ2h0KSBcXAomPSBcZXhwIGFfMQpcZW5ke2FsaWduKn0KJCQKCkRhbnMgbm90cmUgY2FzLCDDp2EgZG9ubmUgOgoKYGBge3J9CmV4cChjb2VmKHJlcylbMl0pCmBgYAoKTCdvdXRwdXQgZGUgbGEgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSBub3VzIGRvbm5lIGF1c3NpIGxhIHAtdmFsdWUgYXNzb2Npw6llIGF1IHRlc3QgZGUgbm9uLW51bGxpdMOpCmRlIG5vcyBjb2VmZmljaWVudHMgZGUgcsOpZ3Jlc3Npb24uCkljaSwgbGVzIHAtdmFsdWVzIHNvbnQgcGV0aXRlcywgb24gcGV1dCBkb25jIHJlamV0ZXIgbCdoeXBvdGjDqHNlIG51bGxlIGRlIG51bGxpdMOpIGRlIG5vcyBkZXV4IGNvZWZmaWNpZW50cy4KClN1ciB1biBleGVtcGxlIGRpZmbDqXJlbnQsIGVuIGNoZXJjaGFudCDDoCBleHBsaXF1ZXIgYHNleGAgw6AgcGFydGlyIGRlIGBhZ2VgOgoKYGBge3J9CmRiaXMgPC0gZCAlPiUgbXV0YXRlKHNleCA9IGlmZWxzZShzZXggPT0gIkYiLCAxLCAwKSkKcmVzIDwtIGdsbSgic2V4IH4gYWdlIiwgZmFtaWx5PWJpbm9taWFsLCBkYXRhPWRiaXMpCnN1bW1hcnkocmVzKQpgYGAKCkNldHRlIGZvaXMgbGVzIHAtdmFsdWVzIHNvbnQgZ3JhbmRlcywgZXQgb24gbmUgcGV1dCBwYXMgY29uY2x1cmUgw6AgdW5lIGRpZmbDqXJlbmNlCnNpZ25pZmljYXRpdmUgZGUgbm9zIHBhcmFtw6h0cmVzIGF2ZWMgesOpcm8uCgojIyBFeGVtcGxlIGF2ZWMgdW5lIGNvdmFyaWFibGUgZGlzY3LDqHRlCgpUb3V0IGwnaW50w6lyw6p0IGR1IEdMTSBlc3QgZGUgcG91dm9pciB1dGlsaXNlciB0b3V0IGNlIHF1J29uIHNvdWhhaXRlIGNvbW1lIHZhcmlhYmxlIGV4cGxpY2F0aXZlLgpRdSdvYnRpZW50LW9uIHNpIG9uIGNoZXJjaGUgw6AgZXhwbGlxdWVyIGBoYXNfbG9zdF93ZWlnaHRgIMOgIHBhcnRpciBkZSBgZ3JvdXBgID8KClVuIGRlc3NpbiB0b3V0IGQnYWJvcmQgOgoKYGBge3IgZmlnLndpZHRoPTEwfQpkICU+JSAKICAgIGdncGxvdChhZXMoeCA9IGdyb3VwLCBmaWxsID0gaGFzX2xvc3Rfd2VpZ2h0KSkgKwogICAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZG9kZ2UiKSArCiAgICB0aGVtZV9idygpICsgCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXM9YyhibGV1Y2xhaXIsIHJvc2UpKQpgYGAKCkV0IGxhIHLDqWdyZXNzaW9uIGxvZ2lzdGlxdWUsIGVuc3VpdGUgOgoKYGBge3J9CnJlcyA8LSBnbG0oImhhc19sb3N0X3dlaWdodCB+IGdyb3VwIiwgZmFtaWx5PWJpbm9taWFsLCBkYXRhPWRiaXMpCnN1bW1hcnkocmVzKQpgYGAKCkxhIHAtdmFsdWUgcGV0aXRlIG5vdXMgcG91c3NlIMOgIGNvbmNsdXJlIMOgIHVuIGVmZmV0IHNpZ25pZmljYXRpdmVtZW50IGRpZmbDqXJlbnQgZGUgMCBkdSBicmFzIGRlIHRyYWl0ZW1lbnQKc3VyIGwnb3V0Y29tZS4KCiMjIEV4ZW1wbGUgbXVsdGl2YXJpw6kKCkNvbW1lIGRhbnMgbGUgbW9kw6hsZSBsaW7DqWFpcmUsIG9uIHBldXQgcHJvcG9zZXIgZGVzIGVmZmV0cyBhZGRpdGlmcyBkZSBkaWZmw6lyZW50ZXMgY292YXJpYWJsZXMuClBhciBleGVtcGxlLCBxdWUgc2UgcGFzc2UtdC1pbCBzaSBvbiBzb3VoYWl0ZSBleHBsaXF1ZXIgYGhhc19sb3N0X3dlaWdodGAgw6AgcGFydGlyIGR1IGBncm91cGAgZXQgZGUgYHdlaWdodF90MGAgPwoKYGBge3J9CnJlcyA8LSBnbG0oImhhc19sb3N0X3dlaWdodCB+IGdyb3VwICsgd2VpZ2h0X3QwIiwgZmFtaWx5PWJpbm9taWFsLCBkYXRhPWRiaXMpCnN1bW1hcnkocmVzKQpgYGAKCkxlIG1vZMOobGUgYXZlYyB1bmUgaW5mbHVlbmNlIGRlcyBkZXV4IHZhcmlhYmxlcyBlc3QgYmllbiBzdXBwb3J0w6kgcGFyIG5vcyBkb25uw6llcyAhCk9uIG9idGllbnQgZG9uYyB1bmUgZXN0aW1hdGlvbiBkZSBsJ2VmZmV0IGR1IGJyYXMgZGUgdHJhaXRlbWVudCB0b3V0IGVuIGF5YW50IHByaXMKZW4gY29tcHRlIGwnaW5mbHVlbmNlIGQndW5lIGF1dHJlIHZhcmlhYmxlIGV4cGxpY2F0aXZlLgoKIyBDb25jbHVzaW9uCgpOb3VzIGF2b25zIHZ1IGF1am91cmQnaHVpIHVuIGNlcnRhaW4gbm9tYnJlIGRlIG3DqXRob2RlcyBwZXJtZXR0YW50IGQnYW5hbHlzZXIgZGVzIHZhcmlhYmxlcyBkaXNjcsOodGVzLgpMZXMgcG9pbnRzIHByaW5jaXBhdXggw6AgcmV0ZW5pciBzb250IGxlcyBzdWl2YW50cyA6CgoxLiBwb3VyIGRlcyBkb25uw6llcyBiaW5haXJlcywgdW4gdGVzdCBleGFjdCBkZSBkaWZmw6lyZW5jZSDDoCB1bmUgcHJvcG9ydGlvbiBmaXjDqWUgZXhpc3RlLAptYWlzIHVuIHRlc3QgZHUgJFxjaGleMiQgZmFpdCBhdXNzaSBsJ2FmZmFpcmUuCjIuIHBvdXIgZGVzIGRvbm7DqWVzIGJpdmFyacOpZXMgZGlzY3LDqHRlcywgdW4gdGVzdCBkZSBGaXNoZXIgZCdpbmTDqXBlbmRhbmNlIGVudHJlIGxlcyBkZXV4IHZhcmlhYmxlcyBleGlzdGUsCm1haXMgdW4gdGVzdCBkdSAkXGNoaV4yJCBmYWl0IGF1c3NpIGwnYWZmYWlyZS4KMy4gY2VydGFpbnMgcHLDqWbDqHJlcm9udCB0b3Vqb3VycyBlZmZlY3R1ZXIgdW4gdGVzdCBkZSBGaXNoZXIsIGNvbnNpZMOpcsOpIGNvbW1lICJleGFjdCIgc291cyBzZXMgaHlwb3Row6hzZXMuCkQnYXV0cmVzIHByw6lmw6hyZW50IGxlIHRlc3QgZHUgJFxjaGleMiQsIHBsdXMgZ8OpbsOpcmFsIGV0ICJyb2J1c3RlIiwgbWFpcyByZXBvc2FudCBzdXIgdW4gcsOpc3VsdGF0IHRow6lvcmlxdWUgYXN5bXB0b3RpcXVlLgo0LiBsZSBHTE0gcGVybWV0IGQnw6l0ZW5kcmUgbGUgZnJhbWV3b3JrIGRlIGxhIHLDqWdyZXNzaW9uIGxpbsOpYWlyZSDDoCBkZXMgZG9ubsOpZXMgbm9uIGNvbnRpbnVlcy4KNS4gbGEgcsOpZ3Jlc3Npb24gbG9naXN0aXF1ZSBlc3QgdW4gZXhlbXBsZSBkZSBHTE0sCnF1aSBwZXJtZXQgZCdleHBsaXF1ZXIgZGVzIGRvbm7DqWVzIGJpbmFpcmVzIHBhciBkZXMgZG9ubsOpZXMgZGlzY3LDqHRlcyBvdSBjb250aW51ZXMuCgpOb3RleiBwb3VyIGZpbmlyIHF1J2lsIGV4aXN0ZSBkJ2F1dHJlcyB0eXBlcyBkZSByw6lncmVzc2lvbnMgYmllbiBhZGFwdMOpZXMgw6AgZGVzIGRvbm7DqWVzIGRpc2Nyw6h0ZXMgZGlmZsOpcmVudGVzLgpQYXJtaSBsZXMgcGx1cyBjw6lsw6hicmVzLCB2b3VzIHJlbmNvbnRyZXJleiBwZXV0LcOqdHJlIHVuIGpvdXIgbGEgKnLDqWdyZXNzaW9uIHNvZnRtYXgqLCBwb3VyIGRlcyBkb25uw6llcyBjYXTDqWdvcmllbGxlcywgCm91IGxhICpyw6lncmVzc2lvbiBkZSBQb2lzc29uKiwgcG91ciBkZXMgZG9ubsOpZXMgZGUgY29tcHRhZ2UgZCfDqXbDqW5lbWVudHMuCkVsbGVzIGZvbmN0aW9ubmVudCB0b3V0ZXMgc3VyIGxlIG3Dqm1lIHByaW5jaXBlLCBhdmVjIHVuZSBmb25jdGlvbiBkZSBsaWVuIGRpZmbDqXJlbnRlLgoK