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"

Introduction

Test diagnostic

Etant donné un patient \(X\), caractérisé par un ensemble de variables, un test diagnostic est simplement un classificateur, qui classe en deux catégories distinctes :

  • Diagnostiqué positif, notés \(D_X = 1\).
  • Diagnostiqué négatif, notés \(D_X = 0\).

On ne s’intéresse donc, ni plus, ni moins, qu’à une simple variable de Bernoulli. Tout l’objet de la section suivante consiste à étudier la distribution de cette variable parmi la population de malades et de non-malades.

Threshold sur mesure continue

Un cas classique auquel on s’intéressera ensuite consiste à classer un individu \(X\) selon une certaine mesure continue \(T(X)\), en définissant un threshold \(t\) et en fixant :

\[ D_X = \delta_{T(X) > t} \]

Les individus diagnostiqués positifs sont alors tous ceux dont la mesure \(T(X)\) est supérieure au threshold \(t\).

Reste ensuite à savoir si cette valeur sépare bien nos individus malades de nos individus non-malades. Ce sera l’objet de la troisième section.

Comparaison à un gold standard

Le gold standard donne la vérité

On dispose d’un test diagnostic appelé gold standard qui classe nos individus en deux premières catégories qui vont constituer notre référence :

  • Malades, notés \(M=1\).
  • Non-Malades, notés \(M=0\).

Une équipe de recherche biomédicale met au point un nouveau test diagnostic, et souhaite connaître les caractéristiques de ce test. Ce test classe nos individus en deux catégories :

  • Diagnostiqués positifs, notés \(D=1\).
  • Diagnostiqués négatifs, notés \(D=0\).

Questions et définitions classiques

On peut se poser tout un tas de questions à propos de la performance de notre test diagnostic, Notamment par exemple les plus courantes :

Quelle est la probabilité qu’un patient soit diagnostiqué positif \(D=1\) sachant qu’il est malade \(M=1\) ?

C’est ce qu’on appelle la sensibilité du test : \(\mathbb{P}(D = 1 | M=1)\).

Quelle est la probabilité qu’un patient soit diagnostiqué négatif \(D=0\) sachant qu’il est non-malade \(M=0\) ?

C’est ce qu’on appelle la spécificité du test : \(\mathbb{P}(D = 0 | M=0)\).

Etant donné qu’un patient est diagnostiqué positif \(D = 1\), quelle est la probabilité qu’il soit malade ?

C’est ce qu’on appelle la valeur prédictive positive : \(\mathbb{P}(M=1 | D=1)\).

Etant donné qu’un patient est diagnostiqué négatif \(D = 0\), quelle est la probabilité qu’il ne soit pas malade ?

C’est ce qu’on appelle la valeur prédictive négative : \(\mathbb{P}(M=0 | D=0)\).

Quelle est la probabilité qu’un patient soit classé correctement ?

C’est l’exactitude du test (on parle en général en anglais en utilisant le terme accuracy).

Comparaison en population générale

Pour nous fixer les idées sur un scénario particulier, nous allons considérer le scénario suivant :

  • un échantillon de taille totale \(n = 1e3\) patients.
  • chaque patient a une probabilité d’être malade \(\mathbb{P}(M=1) = 0.1\), c’est ce qu’on appelle la prévalence.
  • sachant qu’il est malade, il a une probabilité d’être diagnostiqué positif de \(\mathbb{P}(D = 1 | M = 1) = 0.8\) (sensibilité).
  • sachant qu’il est non-malade, il a une probabilité d’être diagnostiqué positif de \(\mathbb{P}(D = 1 | M = 0) = 0.1\) (1 - spécificité).

On peut simuler une réalisation de ce scénario de comparaison de test diagnostic :

n <- 1e3

p_M <- 0.1
preval_th <- p_M

p_D_given_M <- 0.8
sens_th <- p_D_given_M

p_D_given_NM <- 0.1
spec_th <- 1 - p_D_given_NM

# Simulation des malades et non-malades
n_M <- rbinom(1, n, p_M)
n_NM <- n - n_M

# Puis des diagnostics positifs et négatifs parmi les malades et non malades
n_D_M <- rbinom(1, n_M, p_D_given_M)
n_ND_M <- n_M - n_D_M
n_D_NM <- rbinom(1, n_NM, p_D_given_NM)
n_ND_NM <- n_NM - n_D_NM

# Organisation dans un tableau de données
lM <- "Malade\n(M=1)"
lNM <- "Non-Malade\n(M=0)"
lD <- "Diagnostiqué positif\n(D=1)"
lND <- "Diagnostiqué négatif\n(D=0)"

d <- data.frame(maladie = factor(c(lM, lM, lNM, lNM), levels=c(lNM, lM)),
                diagnostic = as.factor(c(lD, lND, lD, lND)),
                n = c(n_D_M, n_ND_M, n_D_NM, n_ND_NM))

# Plot des valeurs
d %>% ggplot(aes(x = maladie, y = diagnostic)) +
    geom_raster(aes(fill=n)) +
    geom_text(aes(label=n)) +
    scale_x_discrete(position = "top") +
    theme_bw()

L’avantage de faire notre comparaison en population générale est que le nombre de malades et non-malades est directement représentatif de la prévalence.

Toutes les quantités définies ci-dessus s’estiment donc de façon très naturelle en prenant en compte les bonnes cases du tableau :

sens_estim <- n_D_M / (n_D_M + n_ND_M)
spec_estim <- n_ND_NM / (n_ND_NM + n_D_NM)
vpp_estim <- n_D_M / (n_D_M + n_D_NM)
vpn_estim <- n_ND_NM / (n_ND_NM + n_ND_M)
acc_estim <- (n_D_M + n_ND_NM) / n

Sans surprise, les estimations ponctuelles de sensibilité et spécificité sont proches des valeurs fixées dans la simulation, avec respectivement 0.8278689 et 0.9077449, à comparer à 0.8 et 0.9.

La valeur prédictive positive (VPP) est estimée à 0.5549451, la valeur prédictive négative (VPN) est estimée à 0.9743276, ce qu’on peut comparer aux valeurs théoriques qu’on peut obtenir après un rapide calcul. Pour la VPP théorique, on a :

\[\begin{align*} VPP &= \mathbb{P}(M = 1 | D = 1) \\ &= \frac{ \mathbb{P}(M = 1, D = 1) }{ \mathbb{P}(D = 1) }\\ &= \frac{ \mathbb{P}(D = 1 | M = 1) \times \mathbb{P}(M = 1) }{ \mathbb{P}(D = 1 | M = 1) \times \mathbb{P}(M=1) + \mathbb{P}(D = 1 | M = 0) \times \mathbb{P}(M=0) }\\ &= \frac{ \text{sensibilité} \times \text{prévalence} }{ \text{sensibilité} \times \text{prévalence} + (1 - \text{spécificité}) \times (1 - \text{prévalence}) } \end{align*}\]

Numériquement, on obtient 0.4705882.

Pour la VPN théorique, on a :

\[\begin{align*} VPN &= \mathbb{P}(M = 0 | D = 0) \\ &= \frac{ \mathbb{P}(M = 0, D = 0) }{ \mathbb{P}(D = 0) }\\ &= \frac{ \mathbb{P}(D = 0 | M = 0) \times \mathbb{P}(M = 0) }{ \mathbb{P}(D = 0 | M = 0) \times \mathbb{P}(M=0) + \mathbb{P}(D = 0 | M = 1) \times \mathbb{P}(M=1) }\\ &= \frac{ \text{spécificité} \times (1 - \text{prévalence}) }{ \text{spécificité} \times (1 - \text{prévalence}) + (1 - \text{sensibilité}) \times \text{prévalence} } \end{align*}\]

Numériquement, avec les valeurs prises dans les simulations, on obtient 0.9759036.

Enfin, l’accuracy est estimée à 0.898. La valeur théorique, quant à elle, peut être calculée à partir des valeurs choisies dans la simulation :

\[\begin{align*} ACC &= \mathbb{P}((D = 1, M = 1) \cup (D = 0, M = 0))\\ &= \mathbb{P}(D = 1, M = 1) + \mathbb{P}(D = 0, M = 0)\\ &= \mathbb{P}(D = 1 | M = 1) \times \mathbb{P}(M = 1) + \mathbb{P}(D = 0 | M = 0) * \mathbb{P}(M = 0) \\ &= \text{sensibilité} \times \text{prévalence} + \text{spécificité} \times (1 - \text{prévalence}) \end{align*}\]

Numériquement, on obtient 0.89.

Comparaison dans une population controlant la maladie

Lorsque la prévalence d’une maladie est très faible, il peut être pénible et/ou trop coûteux de faire la comparaison sur un échantillon représentatif de la population générale.

Plus précisément, on peut toujours le faire, mais le nombre de malades dans notre échantillon risque d’être très faible, et la variance associée à notre estimateur de sensibilité sera très grande.

Dans ce cas, le design de l’étude peut être modifié, pour effectuer la comparaison du test diagnostic vs. gold standard sur un nombre fixé de patients malades et non-malades.

On peut à nouveau se fixer les idées sur un scénario simulé :

  • un échantillon de 60 malades et 80 non-malades.
  • sachant qu’il est malade, un patient a une probabilité d’être diagnostiqué positif de \(\mathbb{P}(D = 1 | M = 1) = 0.8\) (sensibilité).
  • sachant qu’il est non-malade, un patient a une probabilité d’être diagnostiqué positif de \(\mathbb{P}(D = 1 | M = 0) = 0.1\) (1 - spécificité).

On peut simuler une réalisation de ce scénario de comparaison de test diagnostic :

p_D_given_M <- 0.8
sens_th <- p_D_given_M

p_D_given_NM <- 0.1
spec_th <- 1 - p_D_given_NM

# Simulation des malades et non-malades
n_M <- 60
n_NM <- 80

# Puis des diagnostics positifs et négatifs parmi les malades et non malades
n_D_M <- rbinom(1, n_M, p_D_given_M)
n_ND_M <- n_M - n_D_M
n_D_NM <- rbinom(1, n_NM, p_D_given_NM)
n_ND_NM <- n_NM - n_D_NM

# Organisation dans un tableau de données
lM <- "Malade\n(M=1)"
lNM <- "Non-Malade\n(M=0)"
lD <- "Diagnostiqué positif\n(D=1)"
lND <- "Diagnostiqué négatif\n(D=0)"

d <- data.frame(maladie = factor(c(lM, lM, lNM, lNM), levels=c(lNM, lM)),
                diagnostic = as.factor(c(lD, lND, lD, lND)),
                n = c(n_D_M, n_ND_M, n_D_NM, n_ND_NM))

# Plot des valeurs
d %>% ggplot(aes(x = maladie, y = diagnostic)) +
    geom_raster(aes(fill=n)) +
    geom_text(aes(label=n)) +
    scale_x_discrete(position = "top") +
    theme_bw()

A présent, que peut-on faire pour analyser de telles données ?

Comme précédemment, on peut toujours s’intéresser à la sensibilité et à la spécificité, puisqu’il s’agit de quantités qui peuvent directement être estimées parmi nos malades et nos non-malades.

sens_estim <- n_D_M / (n_D_M + n_ND_M)
spec_estim <- n_ND_NM / (n_ND_NM + n_D_NM)

Sans surprise, les estimations ponctuelles de sensibilité et spécificité sont proches des valeurs fixées dans la simulation, avec respectivement 0.75 et 0.9125, à comparer à 0.8 et 0.9.

En revanche, impossible de calculer les autres quantités d’intérêt, telles VPP et VPN, qui nécessitent de contrôler le nombre de patients diagnostiqués positifs et négatifs. Si on souhaite calculer ces quantités, on est contraint de reposer sur une information externe à propos de la prévalence de la maladie, de façon à pouvoir utiliser les formules dérivées précédemment.

De même, le calcul de l’accuracy doit reposer sur une information de prévalence externe.

Comparaison dans une population contrôlant les diagnostiqués

Le cas symétrique consiste à effectuer la même étude, mais cette fois sur un échantillon constitué d’un nombre fixé de patients diagnostiqués positifs \(D=1\) et de patients diagnostiqués négatifs \(D=0\).

Un tel cas design d’étude semble toutefois bien moins réaliste que les deux précédents. Il pourrait se rencontrer si le nouveau test diagnostic est réalisé en première intention, et que le gold standard, plus coûteux et long à réaliser, n’est réalisé que plus tard.

Dans un tel cas, on ne pourrait estimer que VPP et VPN à partir des données. Il nous faudrait alors une information de prévalence externe pour pouvoir en déduire quelque chose sur la sensibilité et la spécificité du test diagnostic.

Threshold sur mesure continue

Contexte

Intéressons-nous à présent au cas d’un test diagnostic reposant sur une mesure continue \(T(X)\) sur nos individus \(X\), et sur l’établissement d’un threshold \(t\) permettant de classer en \(D=1\) ou \(D=0\).

Le test est alors simplement \(D = \delta_{T(X) > t}\).

Supposons à présent qu’on possède les données de \(T(X)\) pour un ensemble d’individus, pour lesquels on a également accès au classement en \(M=1\) ou \(M=0\) donné par un gold standard.

LA question centrale d’établissement d’un nouveau test adéquat est donc :

Quelle valeur de threshold optimale \(t\) faut-il prendre ?

Et la question subsidiaire, extrêmement importante :

Que signifie l’ “optimalité”, dont il est question dans la question précédente ?

Scenario simulé

Ici encore, on va se fixer les idées en simulant un jeu de données fictif selon un scénario bien choisi :

  • la prévalence, \(\mathbb{P}(M = 1)\) est de 0.1.
  • Sachant qu’un individu est malade, \(T(X) \sim \mathcal{N}(\mu_M, \sigma_M)\).
  • Sachant qu’un individu est non-malade, \(T(X) \sim \mathcal{N}(\mu_{\bar{M}}, \sigma_\bar{M})\).
n <- 1e3
preval <- 0.1
mu_NM <- 4
mu_M <- 6
sigma_M <- 1
sigma_NM <- 2

d <- data.frame(id = seq(1, n),
                 maladie = rbinom(n, size=1, prob=preval)) %>%
    mutate(mesure = rnorm(n, 
                          mean=ifelse(maladie==1, mu_M, mu_NM),
                          sd=ifelse(maladie==1, sigma_M, sigma_NM) ))

d %>% ggplot(aes(x = mesure)) +
    geom_histogram() +
    theme_bw()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

Sensibilité, spécificité, accuracy

Mettons qu’on fixe une valeur de threshold \(t\). On dispose alors d’un test diagnostic, qu’on peut comparer à notre gold standard comme décrit dans la section précédente. Par exemple, pour \(t = 5\), on obtient :

get_contingency_table <- function(t, data){
    table <- data %>% mutate(diagnostic = ifelse(mesure > t, 1, 0)) %>%
        group_by(maladie, diagnostic) %>%
        summarise(nb = n()) %>% ungroup()

    return (table)
}

# Plot des valeurs
table <- get_contingency_table(t=5, data=d)
## `summarise()` has grouped output by 'maladie'. You can override using the
## `.groups` argument.
table %>% mutate(maladie = ifelse(maladie==1, "malade", "non-malade"),
             diagnostic = ifelse(diagnostic==1, "diagnostic positif", "diagnostic négatif")) %>%
    ggplot(aes(x = maladie, y = diagnostic)) +
    geom_raster(aes(fill=nb)) +
    geom_text(aes(label=nb)) +
    scale_x_discrete(position = "top") +
    theme_bw()

On peut alors en déduire la sensibilité et la spécificité du test :

get_sens_spec_acc <- function(table){
    n_M_D <- table %>% filter(maladie==1, diagnostic==1) %>% pull(nb)
    n_M_D <- ifelse(length(n_M_D) == 0, 0, n_M_D)

    n_M_ND <- table %>% filter(maladie==1, diagnostic==0) %>% pull(nb)
    n_M_ND <- ifelse(length(n_M_ND) == 0, 0, n_M_ND)

    n_NM_ND <- table %>% filter(maladie==0, diagnostic==0) %>% pull(nb)
    n_NM_ND <- ifelse(length(n_NM_ND) == 0, 0, n_NM_ND)

    n_NM_D <- table %>% filter(maladie==0, diagnostic==1) %>% pull(nb)
    n_NM_D <- ifelse(length(n_NM_D) == 0, 0, n_NM_D)

    sens_estim <- n_M_D / (n_M_D + n_M_ND)
    spec_estim <- n_NM_ND / (n_NM_ND + n_NM_D)
    acc_estim <- (n_M_D + n_NM_ND) / (n_M_D + n_M_ND + n_NM_ND + n_NM_D)

    return (list(sens = sens_estim, spec = spec_estim, acc = acc_estim))
}
estim <- get_sens_spec_acc(table)

On obtient ici des valeurs numériques respectivement à 0.8658537 pour la sensibilité, 0.6797386 pour la spécificité, et 0.695 pour l’accuracy.

Choix du seuil

On peut ensuite aller plus loin, et calculer ces valeurs pour toutes les valeurs de threshold qui pourraient nous intéresser, par exemple toutes les valeurs entre -2 et 12, dans l’espoir de guider le choix de \(t\).

t_list <- seq(from=-2, to=12, by=0.2)
sens_list <- c()
spec_list <- c()
acc_list <- c()

for (t in t_list){
    table <- get_contingency_table(t, d)
    estim <- get_sens_spec_acc(table)
    
    sens_list <- c(sens_list, estim$sens)
    spec_list <- c(spec_list, estim$spec)
    acc_list <- c(acc_list, estim$acc)
}

d_charac <- data.frame(t = rep(t_list, 3),
                          val = c(sens_list, spec_list, acc_list),
                          type = c(rep("sensibilité", length(t_list)),
                                   rep("spécificité", length(t_list)),
                                   rep("accuracy", length(t_list)) ) )

d_charac %>% ggplot(aes(x=t, y=val, col=type)) +
    theme_bw() +
    geom_point() +
    geom_line()

On pourrait donc penser à choisir :

  • le seuil qui maximise la sensibilité,
  • le seuil qui maximise la spécificité,
  • le seuil qui maximise l’accuracy.

Selon ce qui fait le plus sens dans une situation donnée.

Courbe ROC

Une autre représentation graphique très appréciée consiste à représenter cette fois-ci la sensibilité en fonction de (1 - spécificité).

Cette courbe s’appelle la courbe ROC, pour Receiver Operating Characteristic, ou plus simplement, dans le domaine des stats médicales, courbe sensibilité/spécificité, et la voici dans le cas qui nous intéresse :

d_roc <- data.frame(sensibility = sens_list,
                    specificity = spec_list,
                    one_minus_specificity = 1 - spec_list,
                    threshold = t_list)

d_roc %>% ggplot(aes(y = sensibility, x = one_minus_specificity)) +
    geom_point() +
    geom_line() +
    xlab("1 - spécificité") +
    ylab("sensibilité") +
    geom_abline(intercept = 0, slope = 1, col="gray") +
    theme_bw()

Chaque point de cette courbe correspond à une valeur de seuil différente. Elle permet de se faire une bonne idée du trade-off qui existe entre la sensbilité et la spécificité :

  • en haut à droite on se place au point qui maximise la sensibilité, au prix d’une spécificité faible,
  • en bas à gauche, on se place au point qui maximise la spécificité au prix d’une sensibilité faible,
  • entre les deux, sur la courbe, on peut choisir le point qui nous semble être le meilleur compromis entre les deux,
  • en haut à gauche, au point de coordonnées (0,1), il y aurait un test diagnostic parfait, qui ne ferait aucun faux positif ni aucun faux négatif.

Notons que la droite grise superposée au graphe correspond à ce qu’on obtient si on diagnostique nos individus au hasard, en jouant à pile ou face. La distance à cette courbe permet d’apprécier à quel point la valeur sur laquelle on se base pour établir le diagnostic est distribuée différemment entre les malades et les non-malades.

A présent qu’on a bien mis les mains dans le cambouis pour comprendre comment ça fonctionne, place à la magie de R et des multiples packages qui existent ! Evidemment, toutes ces opérations pénibles à faire peuvent être réalisées en trois ligne de code à l’aide du package ROCR (par exemple).

library(ROCR)
# on créé un objet de "prédiction" à partir de notre mesure et du gold standard
pred <- prediction(d$mesure, d$maladie)
# on calcule les indicateurs de "tpr" : true positive rate = sensibilité
# et "fpr" : false positive rate = 1 - specificité,
# pour toutes les valeurs de threshold possibles
perf <- performance(pred, "tpr", "fpr")
plot(perf)

Notons que, si on souhaite tout de même utiliser ggplot pour améliorer ce graphique, la meilleure option consiste à regarder le contenu de l’object perf et à en utiliser des morceaux bien choisis. Par exemple :

str(perf)
## Formal class 'performance' [package "ROCR"] with 6 slots
##   ..@ x.name      : chr "False positive rate"
##   ..@ y.name      : chr "True positive rate"
##   ..@ alpha.name  : chr "Cutoff"
##   ..@ x.values    :List of 1
##   .. ..$ : num [1:1001] 0 0.00109 0.00218 0.00327 0.00436 ...
##   ..@ y.values    :List of 1
##   .. ..$ : num [1:1001] 0 0 0 0 0 0 0 0 0 0 ...
##   ..@ alpha.values:List of 1
##   .. ..$ : num [1:1001] Inf 10.5 10.29 10.15 9.75 ...
data.frame(sensibility = perf@y.values[[1]], one_minus_specificity = perf@x.values[[1]]) %>%
    ggplot(aes(y = sensibility, x = one_minus_specificity)) +
    geom_line() +
    xlab("1 - spécificité") +
    ylab("sensibilité") +
    geom_abline(intercept = 0, slope = 1, col="gray") +
    theme_bw()

Un autre package alternatif, pROC, est également disponible. Voici quelques lignes pour débuter avec :

library(pROC)
roc(maladie ~ mesure, d) %>% plot.roc()
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases

De noombreuses options sont disponibles pour modifier le comportement de la fonction roc et de la fonction d’affichage plot.roc.

ROC AUC

Imaginons qu’on souhaite, à présent, comparer deux (ou plus) tests diagnostics. Si on sait déjà qu’on souhaite prioriser, par exemple, l’accuracy, la question est relativement simple à résoudre :

  • on prend le seuil maximisant l’accuracy dans le premier test,
  • on prend le seuil maximisant l’accuracy dans le second test,
  • et on prend le test (et le seuil) maximisant l’accuracy entre les deux tests.

En revanche, lorsqu’on ne sait pas bien choisir si on souhaite prioriser sensibilité, spécificité ou accuracy, on peut s’appuyer sur une nouvelle quantité intéressante basée sur la courbe ROC : la ROC AUC, pour ROC Area Under The Curve.

L’idée est très simple : plus l’aire sous la courbe est importante, meilleur sera considéré notre test diagnostic. Simulons un deuxième jeu de données pour visualiser ce que ça donne, avec une autre mesure permettant de diagnostiquer notre maladie :

n <- 1e3
preval <- 0.1

mu1_NM <- 4
mu1_M <- 6
sigma1_M <- 1
sigma1_NM <- 2

mu2_NM <- 3
mu2_M <- 6
sigma2_M <- 3
sigma2_NM <- 3

d <- data.frame(id = seq(1, n),
                 maladie = rbinom(n, size=1, prob=preval)) %>%
    mutate(mesure1 = rnorm(n, 
                          mean=ifelse(maladie==1, mu1_M, mu1_NM),
                          sd=ifelse(maladie==1, sigma1_M, sigma1_NM)),
           mesure2 = rnorm(n, 
                          mean=ifelse(maladie==1, mu2_M, mu2_NM),
                          sd=ifelse(maladie==1, sigma2_M, sigma2_NM) ))

On va utiliser le package pROC pour nous calculer la courbe ROC, ainsi que l’aire sous la courbe :

r1 <- roc(maladie ~ mesure1, d)
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
r2 <- roc(maladie ~ mesure2, d)
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
auc1 <- r1$auc
auc2 <- r2$auc

data.frame(sens = c(r1$sensitivities, r2$sensitivities),
           one_minus_spec = c(1-r1$specificities, 1-r2$specificities),
           test = c(rep("1", n+1), rep("2", n+1)) ) %>%
    ggplot(aes(x=one_minus_spec, y=sens, col=test)) +
    geom_line() +
    theme_bw() +
    ylab("sensibilité") +
    xlab("1 - spécificité")

L’aire sous la courbe vaut 0.8060096 pour le test diagnostic numéro 1, et 0.8002912 pour le test diagnostic numéro 2. Sur la base de la ROC AUC, on choisirait donc préférentiellement le test diagnostic numéro 1. Notons qu’on choisirait également le test diagnostic numéro 1 si on souhaite obtenir un test diagnostic ayant une forte sensibilité. En revanche, on choisirait préférentiellement le test diagnostic numéro 2 si on souhaitait obtenir un test diagnostic à plus forte spécificité.

Conclusion

On peut identifier un certain nombre de take-home messages sur ce sujet :

  • la sensibilité est la probabilité d’être diagnostiqué positif sachant qu’on est malade. \[ \mathbb{P}(D = 1 | M = 1) \]
  • la spécificité est la probabilité d’être diagnostiqué négatif sachant qu’on est non-malade. \[ \mathbb{P}(D = 0 | M = 0) \]
  • la valeur prédictive positive (VPP) est la probabilité d’être malade sachant qu’on est diagnostiqué positif. \[ \mathbb{P}(M = 1 | D = 1) \]
  • la valeur prédictive négative (VPN) est la probabilité d’être non-malade sachant qu’on est diagnostiqué négatif. \[ \mathbb{P}(M = 0 | D = 0) \]
  • dans un échantillon représentatif de la population générale, la comparaison d’un test diagnostic au gold standard permet d’estimer la sensibilité, la spécificité, la VPP et la VPN à partir de la table de contingence.
  • dans un échantillon contenant un nombre fixé de malades et non-malades, on ne peut estimer que la sensibilité et la spécificité. Le calcul de la VPP et VPN requièrent d’avoir accès à la prévalence de la maladie.
  • si on souhaite créer un test diagnostic à partir d’une mesure continue, on peut calculer la sensibilité et spécificité pour toutes les valeurs de seuil possibles, et construire une courbe ROC. On peut alors choisir le seuil que l’on souhaite, maximisant plutôt la sensibilité ou la spécificité.
  • l’aire sous la courbe ROC (ROC AUC) permet de comparer des tests diagnostics lorsqu’on ne sait pas si on doit prioriser une bonne sensibilité ou une bonne spécificité.
LS0tCnRpdGxlOiAiU2Vuc2liaWxpdMOpLCBTcMOpY2lmaWNpdMOpIGV0IFJPQyBwb3VyIHVuIHRlc3QgZGlhZ25vc3RpYyIKYXV0aG9yOiAiTWFyYyBNYW5jZWF1IgpkYXRlOiAiMjAyMy0wNyIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiBUUlVFCiAgICB0b2NfZGVwdGg6IDIKICAgIHRvY19mbG9hdDogVFJVRQogICAgaGlnaGxpZ2h0OiAidGFuZ28iCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFCi0tLQoKUG91ciBsZXMgdXRpbGlzYXRldXJzIGRlIFIsIG9uIGNvbW1lbmNlIHBhciBjaGFyZ2VyIHVuIGNlcnRhaW4gbm9tYnJlIGRlIG1vZHVsZXMgaW50w6lyZXNzYW50cy4KCmBgYHtyfQojIGxlIHBhY2thZ2UgbW9kZXJuZSBzdGFuZGFyZCBwb3VyIG1hbmlwdWxlciBzZXMgZG9ubsOpZXMKbGlicmFyeSh0aWR5dmVyc2UpCiMgcG91ciBmYWlyZSBkZXMgZ3JhcGhlcyBtdWx0aXBsZXMKbGlicmFyeShjb3dwbG90KQojIHBvdXIgYWZmaWNoZXIgam9saW1lbnQgbGVzIHRhYmxlYXV4IGRhbnMgdW4gZG9jdW1lbnQgcm1hcmtkb3duCmxpYnJhcnkoa25pdHIpCgojIHF1ZWxxdWVzIGNvdWxldXJzIG1hbnVlbGxlcwpibGV1Zm9uY2UgPC0gIiMzZDU0NjgiCmJsZXVjbGFpciA8LSAiIzViN2M5OCIKcm9zZSA8LSAiI2ZmNTU1NSIKYGBgCgojIEludHJvZHVjdGlvbgoKIyMgVGVzdCBkaWFnbm9zdGljCgpFdGFudCBkb25uw6kgdW4gcGF0aWVudCAkWCQsIGNhcmFjdMOpcmlzw6kgcGFyIHVuIGVuc2VtYmxlIGRlIHZhcmlhYmxlcywKdW4gdGVzdCBkaWFnbm9zdGljIGVzdCBzaW1wbGVtZW50IHVuIGNsYXNzaWZpY2F0ZXVyLCBxdWkKY2xhc3NlIGVuIGRldXggY2F0w6lnb3JpZXMgZGlzdGluY3RlcyA6CgoqIERpYWdub3N0aXF1w6kgcG9zaXRpZiwgbm90w6lzICREX1ggPSAxJC4KKiBEaWFnbm9zdGlxdcOpIG7DqWdhdGlmLCBub3TDqXMgJERfWCA9IDAkLgoKT24gbmUgcydpbnTDqXJlc3NlIGRvbmMsIG5pIHBsdXMsIG5pIG1vaW5zLCBxdSfDoCB1bmUgc2ltcGxlIHZhcmlhYmxlIGRlIEJlcm5vdWxsaS4KVG91dCBsJ29iamV0IGRlIGxhIHNlY3Rpb24gc3VpdmFudGUgY29uc2lzdGUgw6Agw6l0dWRpZXIgbGEgZGlzdHJpYnV0aW9uCmRlIGNldHRlIHZhcmlhYmxlIHBhcm1pIGxhIHBvcHVsYXRpb24gZGUgbWFsYWRlcyBldCBkZSBub24tbWFsYWRlcy4KCmBgYHtyIGVjaG89RiwgZmlnLndpZHRoPTEwfQpkIDwtIGRhdGEuZnJhbWUocHJvcG9ydGlvbiA9IGMoODgsIDEyLCA5MiwgOCksCgkJCQltYWxhZGUgPSBjKCJtYWxhZGUiLCAibWFsYWRlIiwgIm5vbi1tYWxhZGUiLCAibm9uLW1hbGFkZSIpLAoJCQkJZGlhZ25vc3RpYyA9IGMoInBvc2l0aWYiLCAibsOpZ2F0aWYiLCAibsOpZ2F0aWYiLCAicG9zaXRpZiIpKQoKZCAlPiUgZ2dwbG90KGFlcyh4ID0gbWFsYWRlLCB5ID0gcHJvcG9ydGlvbiwgIGZpbGwgPSBkaWFnbm9zdGljKSkgKwoJdGhlbWVfYncoKSArCglnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpCmBgYAoKIyMgVGhyZXNob2xkIHN1ciBtZXN1cmUgY29udGludWUKClVuIGNhcyBjbGFzc2lxdWUgYXVxdWVsIG9uIHMnaW50w6lyZXNzZXJhIGVuc3VpdGUgY29uc2lzdGUgw6AgY2xhc3NlciB1biBpbmRpdmlkdSAkWCQgc2Vsb24KdW5lIGNlcnRhaW5lIG1lc3VyZSBjb250aW51ZSAkVChYKSQsIGVuIGTDqWZpbmlzc2FudCB1biB0aHJlc2hvbGQgJHQkCmV0IGVuIGZpeGFudCA6CgokJApEX1ggPSBcZGVsdGFfe1QoWCkgPiB0fQokJAoKTGVzIGluZGl2aWR1cyBkaWFnbm9zdGlxdcOpcyBwb3NpdGlmcyBzb250IGFsb3JzIHRvdXMgY2V1eCBkb250IGxhIG1lc3VyZQokVChYKSQgZXN0IHN1cMOpcmlldXJlIGF1IHRocmVzaG9sZCAkdCQuCgpSZXN0ZSBlbnN1aXRlIMOgIHNhdm9pciBzaSBjZXR0ZSB2YWxldXIgc8OpcGFyZSBiaWVuIG5vcyBpbmRpdmlkdXMgbWFsYWRlcwpkZSBub3MgaW5kaXZpZHVzIG5vbi1tYWxhZGVzLiBDZSBzZXJhIGwnb2JqZXQgZGUgbGEgdHJvaXNpw6htZSBzZWN0aW9uLgoKYGBge3IgZWNobz1GLCBmaWcud2lkdGg9MTB9CnggPC0gc2VxKGZyb209MCwgdG89MTAsIGJ5PSAwLjA1KQpkIDwtIGRhdGEuZnJhbWUobWVzdXJlID0gYyh4LHgpLAoJCQkJZGVuc2l0ZSA9IGMoZG5vcm0oeCwgbWVhbj00LCBzZD0xKSwgZG5vcm0oeCwgbWVhbj02LCBzZD0yKSksCgkJCQltYWxhZGUgPSBjKHJlcCgibm9uLW1hbGFkZSIsIGxlbmd0aCh4KSksIHJlcCgibWFsYWRlIiwgbGVuZ3RoKHgpKSkgKQoKZCAlPiUgZ2dwbG90KGFlcyh4ID0gbWVzdXJlLCB5ID0gZGVuc2l0ZSwgY29sID0gbWFsYWRlKSkgKwoJdGhlbWVfYncoKSArCglnZW9tX2xpbmUoKSArCglnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA1KQpgYGAKCiMgQ29tcGFyYWlzb24gw6AgdW4gZ29sZCBzdGFuZGFyZAoKIyMgTGUgZ29sZCBzdGFuZGFyZCBkb25uZSBsYSB2w6lyaXTDqQoKT24gZGlzcG9zZSBkJ3VuIHRlc3QgZGlhZ25vc3RpYyBhcHBlbMOpICpnb2xkIHN0YW5kYXJkKiBxdWkgY2xhc3NlIG5vcyBpbmRpdmlkdXMKZW4gZGV1eCBwcmVtacOocmVzIGNhdMOpZ29yaWVzIHF1aSB2b250IGNvbnN0aXR1ZXIgbm90cmUgcsOpZsOpcmVuY2UgOgoKKiBNYWxhZGVzLCBub3TDqXMgJE09MSQuCiogTm9uLU1hbGFkZXMsIG5vdMOpcyAkTT0wJC4KClVuZSDDqXF1aXBlIGRlIHJlY2hlcmNoZSBiaW9tw6lkaWNhbGUgbWV0IGF1IHBvaW50IHVuIG5vdXZlYXUgdGVzdCBkaWFnbm9zdGljLApldCBzb3VoYWl0ZSBjb25uYcOudHJlIGxlcyBjYXJhY3TDqXJpc3RpcXVlcyBkZSBjZSB0ZXN0LgpDZSB0ZXN0IGNsYXNzZSBub3MgaW5kaXZpZHVzIGVuIGRldXggY2F0w6lnb3JpZXMgOgoKKiBEaWFnbm9zdGlxdcOpcyBwb3NpdGlmcywgbm90w6lzICREPTEkLgoqIERpYWdub3N0aXF1w6lzIG7DqWdhdGlmcywgbm90w6lzICREPTAkLgoKIyMgUXVlc3Rpb25zIGV0IGTDqWZpbml0aW9ucyBjbGFzc2lxdWVzCgpPbiBwZXV0IHNlIHBvc2VyIHRvdXQgdW4gdGFzIGRlIHF1ZXN0aW9ucyDDoCBwcm9wb3MgZGUgbGEgcGVyZm9ybWFuY2UgZGUgbm90cmUgdGVzdCBkaWFnbm9zdGljLApOb3RhbW1lbnQgcGFyIGV4ZW1wbGUgbGVzIHBsdXMgY291cmFudGVzIDoKCj4gUXVlbGxlIGVzdCBsYSBwcm9iYWJpbGl0w6kgcXUndW4gcGF0aWVudCBzb2l0IGRpYWdub3N0aXF1w6kgcG9zaXRpZiAkRD0xJCBzYWNoYW50IHF1J2lsIGVzdCBtYWxhZGUgJE09MSQgPwoKQydlc3QgY2UgcXUnb24gYXBwZWxsZSBsYSAqc2Vuc2liaWxpdMOpKiBkdSB0ZXN0IDogJFxtYXRoYmJ7UH0oRCA9IDEgfCBNPTEpJC4KCj4gUXVlbGxlIGVzdCBsYSBwcm9iYWJpbGl0w6kgcXUndW4gcGF0aWVudCBzb2l0IGRpYWdub3N0aXF1w6kgbsOpZ2F0aWYgJEQ9MCQgc2FjaGFudCBxdSdpbCBlc3Qgbm9uLW1hbGFkZSAkTT0wJCA/CgpDJ2VzdCBjZSBxdSdvbiBhcHBlbGxlIGxhICpzcMOpY2lmaWNpdMOpKiBkdSB0ZXN0IDogJFxtYXRoYmJ7UH0oRCA9IDAgfCBNPTApJC4KCj4gRXRhbnQgZG9ubsOpIHF1J3VuIHBhdGllbnQgZXN0IGRpYWdub3N0aXF1w6kgcG9zaXRpZiAkRCA9IDEkLCBxdWVsbGUgZXN0IGxhIHByb2JhYmlsaXTDqSBxdSdpbCBzb2l0IG1hbGFkZSA/CgpDJ2VzdCBjZSBxdSdvbiBhcHBlbGxlIGxhICp2YWxldXIgcHLDqWRpY3RpdmUgcG9zaXRpdmUqIDogJFxtYXRoYmJ7UH0oTT0xIHwgRD0xKSQuCgo+IEV0YW50IGRvbm7DqSBxdSd1biBwYXRpZW50IGVzdCBkaWFnbm9zdGlxdcOpIG7DqWdhdGlmICREID0gMCQsIHF1ZWxsZSBlc3QgbGEgcHJvYmFiaWxpdMOpIHF1J2lsIG5lIHNvaXQgcGFzIG1hbGFkZSA/CgpDJ2VzdCBjZSBxdSdvbiBhcHBlbGxlIGxhICp2YWxldXIgcHLDqWRpY3RpdmUgbsOpZ2F0aXZlKiA6ICRcbWF0aGJie1B9KE09MCB8IEQ9MCkkLgoKPiBRdWVsbGUgZXN0IGxhIHByb2JhYmlsaXTDqSBxdSd1biBwYXRpZW50IHNvaXQgY2xhc3PDqSBjb3JyZWN0ZW1lbnQgPwoKQydlc3QgbCcqZXhhY3RpdHVkZSogZHUgdGVzdCAob24gcGFybGUgZW4gZ8OpbsOpcmFsIGVuIGFuZ2xhaXMgZW4gdXRpbGlzYW50IGxlIHRlcm1lICphY2N1cmFjeSopLiAKCgojIyBDb21wYXJhaXNvbiBlbiBwb3B1bGF0aW9uIGfDqW7DqXJhbGUKClBvdXIgbm91cyBmaXhlciBsZXMgaWTDqWVzIHN1ciB1biBzY8OpbmFyaW8gcGFydGljdWxpZXIsIG5vdXMgYWxsb25zIGNvbnNpZMOpcmVyIGxlIHNjw6luYXJpbyBzdWl2YW50IDoKCiogdW4gw6ljaGFudGlsbG9uIGRlIHRhaWxsZSB0b3RhbGUgJG4gPSAxZTMkIHBhdGllbnRzLgoqIGNoYXF1ZSBwYXRpZW50IGEgdW5lIHByb2JhYmlsaXTDqSBkJ8OqdHJlIG1hbGFkZSAkXG1hdGhiYntQfShNPTEpID0gMC4xJCwgYydlc3QgY2UgcXUnb24gYXBwZWxsZSBsYSAqcHLDqXZhbGVuY2UqLgoqIHNhY2hhbnQgcXUnaWwgZXN0IG1hbGFkZSwgaWwgYSB1bmUgcHJvYmFiaWxpdMOpIGQnw6p0cmUgZGlhZ25vc3RpcXXDqSBwb3NpdGlmIGRlICRcbWF0aGJie1B9KEQgPSAxIHwgTSA9IDEpID0gMC44JCAoc2Vuc2liaWxpdMOpKS4KKiBzYWNoYW50IHF1J2lsIGVzdCBub24tbWFsYWRlLCBpbCBhIHVuZSBwcm9iYWJpbGl0w6kgZCfDqnRyZSBkaWFnbm9zdGlxdcOpIHBvc2l0aWYgZGUgJFxtYXRoYmJ7UH0oRCA9IDEgfCBNID0gMCkgPSAwLjEkICgxIC0gc3DDqWNpZmljaXTDqSkuCgpPbiBwZXV0IHNpbXVsZXIgdW5lIHLDqWFsaXNhdGlvbiBkZSBjZSBzY8OpbmFyaW8gZGUgY29tcGFyYWlzb24gZGUgdGVzdCBkaWFnbm9zdGljIDoKCmBgYHtyfQpuIDwtIDFlMwoKcF9NIDwtIDAuMQpwcmV2YWxfdGggPC0gcF9NCgpwX0RfZ2l2ZW5fTSA8LSAwLjgKc2Vuc190aCA8LSBwX0RfZ2l2ZW5fTQoKcF9EX2dpdmVuX05NIDwtIDAuMQpzcGVjX3RoIDwtIDEgLSBwX0RfZ2l2ZW5fTk0KCiMgU2ltdWxhdGlvbiBkZXMgbWFsYWRlcyBldCBub24tbWFsYWRlcwpuX00gPC0gcmJpbm9tKDEsIG4sIHBfTSkKbl9OTSA8LSBuIC0gbl9NCgojIFB1aXMgZGVzIGRpYWdub3N0aWNzIHBvc2l0aWZzIGV0IG7DqWdhdGlmcyBwYXJtaSBsZXMgbWFsYWRlcyBldCBub24gbWFsYWRlcwpuX0RfTSA8LSByYmlub20oMSwgbl9NLCBwX0RfZ2l2ZW5fTSkKbl9ORF9NIDwtIG5fTSAtIG5fRF9NCm5fRF9OTSA8LSByYmlub20oMSwgbl9OTSwgcF9EX2dpdmVuX05NKQpuX05EX05NIDwtIG5fTk0gLSBuX0RfTk0KCiMgT3JnYW5pc2F0aW9uIGRhbnMgdW4gdGFibGVhdSBkZSBkb25uw6llcwpsTSA8LSAiTWFsYWRlXG4oTT0xKSIKbE5NIDwtICJOb24tTWFsYWRlXG4oTT0wKSIKbEQgPC0gIkRpYWdub3N0aXF1w6kgcG9zaXRpZlxuKEQ9MSkiCmxORCA8LSAiRGlhZ25vc3RpcXXDqSBuw6lnYXRpZlxuKEQ9MCkiCgpkIDwtIGRhdGEuZnJhbWUobWFsYWRpZSA9IGZhY3RvcihjKGxNLCBsTSwgbE5NLCBsTk0pLCBsZXZlbHM9YyhsTk0sIGxNKSksCgkJCQlkaWFnbm9zdGljID0gYXMuZmFjdG9yKGMobEQsIGxORCwgbEQsIGxORCkpLAoJCQkJbiA9IGMobl9EX00sIG5fTkRfTSwgbl9EX05NLCBuX05EX05NKSkKCiMgUGxvdCBkZXMgdmFsZXVycwpkICU+JSBnZ3Bsb3QoYWVzKHggPSBtYWxhZGllLCB5ID0gZGlhZ25vc3RpYykpICsKCWdlb21fcmFzdGVyKGFlcyhmaWxsPW4pKSArCglnZW9tX3RleHQoYWVzKGxhYmVsPW4pKSArCglzY2FsZV94X2Rpc2NyZXRlKHBvc2l0aW9uID0gInRvcCIpICsKCXRoZW1lX2J3KCkKYGBgCgpMJ2F2YW50YWdlIGRlIGZhaXJlIG5vdHJlIGNvbXBhcmFpc29uIGVuIHBvcHVsYXRpb24gZ8OpbsOpcmFsZSBlc3QgcXVlIGxlIG5vbWJyZQpkZSBtYWxhZGVzIGV0IG5vbi1tYWxhZGVzIGVzdCBkaXJlY3RlbWVudCByZXByw6lzZW50YXRpZiBkZSBsYSBwcsOpdmFsZW5jZS4KClRvdXRlcyBsZXMgcXVhbnRpdMOpcyBkw6lmaW5pZXMgY2ktZGVzc3VzIHMnZXN0aW1lbnQgZG9uYyBkZSBmYcOnb24gdHLDqHMgbmF0dXJlbGxlCmVuIHByZW5hbnQgZW4gY29tcHRlIGxlcyBib25uZXMgY2FzZXMgZHUgdGFibGVhdSA6CgpgYGB7cn0Kc2Vuc19lc3RpbSA8LSBuX0RfTSAvIChuX0RfTSArIG5fTkRfTSkKc3BlY19lc3RpbSA8LSBuX05EX05NIC8gKG5fTkRfTk0gKyBuX0RfTk0pCnZwcF9lc3RpbSA8LSBuX0RfTSAvIChuX0RfTSArIG5fRF9OTSkKdnBuX2VzdGltIDwtIG5fTkRfTk0gLyAobl9ORF9OTSArIG5fTkRfTSkKYWNjX2VzdGltIDwtIChuX0RfTSArIG5fTkRfTk0pIC8gbgpgYGAKClNhbnMgc3VycHJpc2UsIGxlcyBlc3RpbWF0aW9ucyBwb25jdHVlbGxlcyBkZSBzZW5zaWJpbGl0w6kgZXQgc3DDqWNpZmljaXTDqSBzb250IHByb2NoZXMKZGVzIHZhbGV1cnMgZml4w6llcyBkYW5zIGxhIHNpbXVsYXRpb24sIGF2ZWMgcmVzcGVjdGl2ZW1lbnQgYHIgc2Vuc19lc3RpbWAgZXQgYHIgc3BlY19lc3RpbWAsCsOgIGNvbXBhcmVyIMOgIGByIHNlbnNfdGhgIGV0IGByIHNwZWNfdGhgLgoKTGEgdmFsZXVyIHByw6lkaWN0aXZlIHBvc2l0aXZlIChWUFApIGVzdCBlc3RpbcOpZSDDoCBgciB2cHBfZXN0aW1gLApsYSB2YWxldXIgcHLDqWRpY3RpdmUgbsOpZ2F0aXZlIChWUE4pIGVzdCBlc3RpbcOpZSDDoCBgciB2cG5fZXN0aW1gLApjZSBxdSdvbiBwZXV0IGNvbXBhcmVyIGF1eCB2YWxldXJzIHRow6lvcmlxdWVzIHF1J29uIHBldXQgb2J0ZW5pciBhcHLDqHMgdW4gcmFwaWRlIGNhbGN1bC4KUG91ciBsYSBWUFAgdGjDqW9yaXF1ZSwgb24gYSA6CgpcYmVnaW57YWxpZ24qfQpWUFAgCiY9IFxtYXRoYmJ7UH0oTSA9IDEgfCBEID0gMSkgXFwKJj0gXGZyYWN7IFxtYXRoYmJ7UH0oTSA9IDEsIEQgPSAxKSB9eyBcbWF0aGJie1B9KEQgPSAxKSB9XFwKJj0gXGZyYWN7IFxtYXRoYmJ7UH0oRCA9IDEgfCBNID0gMSkgXHRpbWVzIFxtYXRoYmJ7UH0oTSA9IDEpIH17IFxtYXRoYmJ7UH0oRCA9IDEgfCBNID0gMSkgXHRpbWVzIFxtYXRoYmJ7UH0oTT0xKSArIFxtYXRoYmJ7UH0oRCA9IDEgfCBNID0gMCkgXHRpbWVzIFxtYXRoYmJ7UH0oTT0wKSB9XFwKJj0gXGZyYWN7IFx0ZXh0e3NlbnNpYmlsaXTDqX0gXHRpbWVzIFx0ZXh0e3Byw6l2YWxlbmNlfSB9eyBcdGV4dHtzZW5zaWJpbGl0w6l9IFx0aW1lcyBcdGV4dHtwcsOpdmFsZW5jZX0gKyAoMSAtIFx0ZXh0e3Nww6ljaWZpY2l0w6l9KSBcdGltZXMgKDEgLSBcdGV4dHtwcsOpdmFsZW5jZX0pICB9ClxlbmR7YWxpZ24qfQoKTnVtw6lyaXF1ZW1lbnQsIG9uIG9idGllbnQgYHIgc2Vuc190aCAqIHByZXZhbF90aCAvIChzZW5zX3RoKnByZXZhbF90aCArICgxLXNwZWNfdGgpKigxLXByZXZhbF90aCkpYC4KClBvdXIgbGEgVlBOIHRow6lvcmlxdWUsIG9uIGEgOgoKXGJlZ2lue2FsaWduKn0KVlBOIAomPSBcbWF0aGJie1B9KE0gPSAwIHwgRCA9IDApIFxcCiY9IFxmcmFjeyBcbWF0aGJie1B9KE0gPSAwLCBEID0gMCkgfXsgXG1hdGhiYntQfShEID0gMCkgfVxcCiY9IFxmcmFjeyBcbWF0aGJie1B9KEQgPSAwIHwgTSA9IDApIFx0aW1lcyBcbWF0aGJie1B9KE0gPSAwKSB9eyBcbWF0aGJie1B9KEQgPSAwIHwgTSA9IDApIFx0aW1lcyBcbWF0aGJie1B9KE09MCkgKyBcbWF0aGJie1B9KEQgPSAwIHwgTSA9IDEpIFx0aW1lcyBcbWF0aGJie1B9KE09MSkgfVxcCiY9IFxmcmFjeyBcdGV4dHtzcMOpY2lmaWNpdMOpfSBcdGltZXMgKDEgLSBcdGV4dHtwcsOpdmFsZW5jZX0pIH17IFx0ZXh0e3Nww6ljaWZpY2l0w6l9IFx0aW1lcyAoMSAtIFx0ZXh0e3Byw6l2YWxlbmNlfSkgKyAoMSAtIFx0ZXh0e3NlbnNpYmlsaXTDqX0pIFx0aW1lcyBcdGV4dHtwcsOpdmFsZW5jZX0gIH0KXGVuZHthbGlnbip9CgpOdW3DqXJpcXVlbWVudCwgYXZlYyBsZXMgdmFsZXVycyBwcmlzZXMgZGFucyBsZXMgc2ltdWxhdGlvbnMsIG9uIG9idGllbnQgYHIgc3BlY190aCooMS1wcmV2YWxfdGgpIC8gKHNwZWNfdGgqKDEtcHJldmFsX3RoKSArICgxLXNlbnNfdGgpKnByZXZhbF90aClgLgoKRW5maW4sIGwnYWNjdXJhY3kgZXN0IGVzdGltw6llIMOgIGByIGFjY19lc3RpbWAuCkxhIHZhbGV1ciB0aMOpb3JpcXVlLCBxdWFudCDDoCBlbGxlLCBwZXV0IMOqdHJlIGNhbGN1bMOpZSDDoCBwYXJ0aXIgZGVzIHZhbGV1cnMgY2hvaXNpZXMgZGFucyBsYSBzaW11bGF0aW9uIDoKClxiZWdpbnthbGlnbip9CkFDQwomPSBcbWF0aGJie1B9KChEID0gMSwgTSA9IDEpIFxjdXAgKEQgPSAwLCBNID0gMCkpXFwKJj0gXG1hdGhiYntQfShEID0gMSwgTSA9IDEpICsgXG1hdGhiYntQfShEID0gMCwgTSA9IDApXFwKJj0gXG1hdGhiYntQfShEID0gMSB8IE0gPSAxKSBcdGltZXMgXG1hdGhiYntQfShNID0gMSkgKyBcbWF0aGJie1B9KEQgPSAwIHwgTSA9IDApICogXG1hdGhiYntQfShNID0gMCkgXFwKJj0gXHRleHR7c2Vuc2liaWxpdMOpfSBcdGltZXMgXHRleHR7cHLDqXZhbGVuY2V9ICsgXHRleHR7c3DDqWNpZmljaXTDqX0gXHRpbWVzICgxIC0gXHRleHR7cHLDqXZhbGVuY2V9KQpcZW5ke2FsaWduKn0KCk51bcOpcmlxdWVtZW50LCBvbiBvYnRpZW50IGByIHNlbnNfdGgqcHJldmFsX3RoICsgc3BlY190aCooMS1wcmV2YWxfdGgpYC4KCgojIyBDb21wYXJhaXNvbiBkYW5zIHVuZSBwb3B1bGF0aW9uIGNvbnRyb2xhbnQgbGEgbWFsYWRpZQoKTG9yc3F1ZSBsYSBwcsOpdmFsZW5jZSBkJ3VuZSBtYWxhZGllIGVzdCB0csOocyBmYWlibGUsIGlsIHBldXQgw6p0cmUgcMOpbmlibGUgZXQvb3UgdHJvcCBjb8O7dGV1eApkZSBmYWlyZSBsYSBjb21wYXJhaXNvbiBzdXIgdW4gw6ljaGFudGlsbG9uIHJlcHLDqXNlbnRhdGlmIGRlIGxhIHBvcHVsYXRpb24gZ8OpbsOpcmFsZS4KClBsdXMgcHLDqWNpc8OpbWVudCwgb24gcGV1dCB0b3Vqb3VycyBsZSBmYWlyZSwgbWFpcyBsZSBub21icmUgZGUgbWFsYWRlcwpkYW5zIG5vdHJlIMOpY2hhbnRpbGxvbiByaXNxdWUgZCfDqnRyZSB0csOocyBmYWlibGUsIGV0IGxhIHZhcmlhbmNlCmFzc29jacOpZSDDoCBub3RyZSBlc3RpbWF0ZXVyIGRlIHNlbnNpYmlsaXTDqSBzZXJhIHRyw6hzIGdyYW5kZS4KCkRhbnMgY2UgY2FzLCBsZSBkZXNpZ24gZGUgbCfDqXR1ZGUgcGV1dCDDqnRyZSBtb2RpZmnDqSwgcG91ciBlZmZlY3R1ZXIgbGEgY29tcGFyYWlzb24KZHUgdGVzdCBkaWFnbm9zdGljIHZzLiBnb2xkIHN0YW5kYXJkIHN1ciB1biBub21icmUgZml4w6kgZGUgcGF0aWVudHMgbWFsYWRlcyBldCBub24tbWFsYWRlcy4KCk9uIHBldXQgw6Agbm91dmVhdSBzZSBmaXhlciBsZXMgaWTDqWVzIHN1ciB1biBzY8OpbmFyaW8gc2ltdWzDqSA6CgoqIHVuIMOpY2hhbnRpbGxvbiBkZSA2MCBtYWxhZGVzIGV0IDgwIG5vbi1tYWxhZGVzLgoqIHNhY2hhbnQgcXUnaWwgZXN0IG1hbGFkZSwgdW4gcGF0aWVudCBhIHVuZSBwcm9iYWJpbGl0w6kgZCfDqnRyZSBkaWFnbm9zdGlxdcOpIHBvc2l0aWYgZGUgJFxtYXRoYmJ7UH0oRCA9IDEgfCBNID0gMSkgPSAwLjgkIChzZW5zaWJpbGl0w6kpLgoqIHNhY2hhbnQgcXUnaWwgZXN0IG5vbi1tYWxhZGUsIHVuIHBhdGllbnQgYSB1bmUgcHJvYmFiaWxpdMOpIGQnw6p0cmUgZGlhZ25vc3RpcXXDqSBwb3NpdGlmIGRlICRcbWF0aGJie1B9KEQgPSAxIHwgTSA9IDApID0gMC4xJCAoMSAtIHNww6ljaWZpY2l0w6kpLgoKT24gcGV1dCBzaW11bGVyIHVuZSByw6lhbGlzYXRpb24gZGUgY2Ugc2PDqW5hcmlvIGRlIGNvbXBhcmFpc29uIGRlIHRlc3QgZGlhZ25vc3RpYyA6CgpgYGB7cn0KcF9EX2dpdmVuX00gPC0gMC44CnNlbnNfdGggPC0gcF9EX2dpdmVuX00KCnBfRF9naXZlbl9OTSA8LSAwLjEKc3BlY190aCA8LSAxIC0gcF9EX2dpdmVuX05NCgojIFNpbXVsYXRpb24gZGVzIG1hbGFkZXMgZXQgbm9uLW1hbGFkZXMKbl9NIDwtIDYwCm5fTk0gPC0gODAKCiMgUHVpcyBkZXMgZGlhZ25vc3RpY3MgcG9zaXRpZnMgZXQgbsOpZ2F0aWZzIHBhcm1pIGxlcyBtYWxhZGVzIGV0IG5vbiBtYWxhZGVzCm5fRF9NIDwtIHJiaW5vbSgxLCBuX00sIHBfRF9naXZlbl9NKQpuX05EX00gPC0gbl9NIC0gbl9EX00Kbl9EX05NIDwtIHJiaW5vbSgxLCBuX05NLCBwX0RfZ2l2ZW5fTk0pCm5fTkRfTk0gPC0gbl9OTSAtIG5fRF9OTQoKIyBPcmdhbmlzYXRpb24gZGFucyB1biB0YWJsZWF1IGRlIGRvbm7DqWVzCmxNIDwtICJNYWxhZGVcbihNPTEpIgpsTk0gPC0gIk5vbi1NYWxhZGVcbihNPTApIgpsRCA8LSAiRGlhZ25vc3RpcXXDqSBwb3NpdGlmXG4oRD0xKSIKbE5EIDwtICJEaWFnbm9zdGlxdcOpIG7DqWdhdGlmXG4oRD0wKSIKCmQgPC0gZGF0YS5mcmFtZShtYWxhZGllID0gZmFjdG9yKGMobE0sIGxNLCBsTk0sIGxOTSksIGxldmVscz1jKGxOTSwgbE0pKSwKCQkJCWRpYWdub3N0aWMgPSBhcy5mYWN0b3IoYyhsRCwgbE5ELCBsRCwgbE5EKSksCgkJCQluID0gYyhuX0RfTSwgbl9ORF9NLCBuX0RfTk0sIG5fTkRfTk0pKQoKIyBQbG90IGRlcyB2YWxldXJzCmQgJT4lIGdncGxvdChhZXMoeCA9IG1hbGFkaWUsIHkgPSBkaWFnbm9zdGljKSkgKwoJZ2VvbV9yYXN0ZXIoYWVzKGZpbGw9bikpICsKCWdlb21fdGV4dChhZXMobGFiZWw9bikpICsKCXNjYWxlX3hfZGlzY3JldGUocG9zaXRpb24gPSAidG9wIikgKwoJdGhlbWVfYncoKQpgYGAKCkEgcHLDqXNlbnQsIHF1ZSBwZXV0LW9uIGZhaXJlIHBvdXIgYW5hbHlzZXIgZGUgdGVsbGVzIGRvbm7DqWVzID8KCkNvbW1lIHByw6ljw6lkZW1tZW50LCBvbiBwZXV0IHRvdWpvdXJzIHMnaW50w6lyZXNzZXIgw6AgbGEgc2Vuc2liaWxpdMOpIGV0IMOgIGxhIHNww6ljaWZpY2l0w6ksCnB1aXNxdSdpbCBzJ2FnaXQgZGUgcXVhbnRpdMOpcyBxdWkgcGV1dmVudCBkaXJlY3RlbWVudCDDqnRyZSBlc3RpbcOpZXMgcGFybWkgbm9zIG1hbGFkZXMgZXQgbm9zIG5vbi1tYWxhZGVzLgoKYGBge3J9CnNlbnNfZXN0aW0gPC0gbl9EX00gLyAobl9EX00gKyBuX05EX00pCnNwZWNfZXN0aW0gPC0gbl9ORF9OTSAvIChuX05EX05NICsgbl9EX05NKQpgYGAKClNhbnMgc3VycHJpc2UsIGxlcyBlc3RpbWF0aW9ucyBwb25jdHVlbGxlcyBkZSBzZW5zaWJpbGl0w6kgZXQgc3DDqWNpZmljaXTDqSBzb250IHByb2NoZXMKZGVzIHZhbGV1cnMgZml4w6llcyBkYW5zIGxhIHNpbXVsYXRpb24sIGF2ZWMgcmVzcGVjdGl2ZW1lbnQgYHIgc2Vuc19lc3RpbWAgZXQgYHIgc3BlY19lc3RpbWAsCsOgIGNvbXBhcmVyIMOgIGByIHNlbnNfdGhgIGV0IGByIHNwZWNfdGhgLgoKRW4gcmV2YW5jaGUsIGltcG9zc2libGUgZGUgY2FsY3VsZXIgbGVzIGF1dHJlcyBxdWFudGl0w6lzIGQnaW50w6lyw6p0LAp0ZWxsZXMgVlBQIGV0IFZQTiwgcXVpIG7DqWNlc3NpdGVudCBkZSBjb250csO0bGVyIGxlIG5vbWJyZSBkZSBwYXRpZW50cwpkaWFnbm9zdGlxdcOpcyBwb3NpdGlmcyBldCBuw6lnYXRpZnMuClNpIG9uIHNvdWhhaXRlIGNhbGN1bGVyIGNlcyBxdWFudGl0w6lzLCBvbiBlc3QgY29udHJhaW50IGRlIHJlcG9zZXIKc3VyIHVuZSBpbmZvcm1hdGlvbiBleHRlcm5lIMOgIHByb3BvcyBkZSBsYSBwcsOpdmFsZW5jZSBkZSBsYSBtYWxhZGllLApkZSBmYcOnb24gw6AgcG91dm9pciB1dGlsaXNlciBsZXMgZm9ybXVsZXMgZMOpcml2w6llcyBwcsOpY8OpZGVtbWVudC4KCkRlIG3Dqm1lLCBsZSBjYWxjdWwgZGUgbCdhY2N1cmFjeSBkb2l0IHJlcG9zZXIgc3VyIHVuZSBpbmZvcm1hdGlvbiBkZSBwcsOpdmFsZW5jZSBleHRlcm5lLgoKCiMjIENvbXBhcmFpc29uIGRhbnMgdW5lIHBvcHVsYXRpb24gY29udHLDtGxhbnQgbGVzIGRpYWdub3N0aXF1w6lzCgpMZSBjYXMgc3ltw6l0cmlxdWUgY29uc2lzdGUgw6AgZWZmZWN0dWVyIGxhIG3Dqm1lIMOpdHVkZSwgbWFpcyBjZXR0ZSBmb2lzIHN1ciB1bgrDqWNoYW50aWxsb24gY29uc3RpdHXDqSBkJ3VuIG5vbWJyZSBmaXjDqSBkZSBwYXRpZW50cyBkaWFnbm9zdGlxdcOpcyBwb3NpdGlmcyAkRD0xJCBldCBkZQpwYXRpZW50cyBkaWFnbm9zdGlxdcOpcyBuw6lnYXRpZnMgJEQ9MCQuCgpVbiB0ZWwgY2FzIGRlc2lnbiBkJ8OpdHVkZSBzZW1ibGUgdG91dGVmb2lzIGJpZW4gbW9pbnMgcsOpYWxpc3RlIHF1ZSBsZXMgZGV1eCBwcsOpY8OpZGVudHMuCklsIHBvdXJyYWl0IHNlIHJlbmNvbnRyZXIgc2kgbGUgbm91dmVhdSB0ZXN0IGRpYWdub3N0aWMgZXN0IHLDqWFsaXPDqSBlbiBwcmVtacOocmUgaW50ZW50aW9uLApldCBxdWUgbGUgZ29sZCBzdGFuZGFyZCwgcGx1cyBjb8O7dGV1eCBldCBsb25nIMOgIHLDqWFsaXNlciwgbidlc3QgcsOpYWxpc8OpIHF1ZSBwbHVzIHRhcmQuCgpEYW5zIHVuIHRlbCBjYXMsIG9uIG5lIHBvdXJyYWl0IGVzdGltZXIgcXVlIFZQUCBldCBWUE4gw6AgcGFydGlyIGRlcyBkb25uw6llcy4KSWwgbm91cyBmYXVkcmFpdCBhbG9ycyB1bmUgaW5mb3JtYXRpb24gZGUgcHLDqXZhbGVuY2UgZXh0ZXJuZSBwb3VyIHBvdXZvaXIgZW4gZMOpZHVpcmUKcXVlbHF1ZSBjaG9zZSBzdXIgbGEgc2Vuc2liaWxpdMOpIGV0IGxhIHNww6ljaWZpY2l0w6kgZHUgdGVzdCBkaWFnbm9zdGljLgoKCiMgVGhyZXNob2xkIHN1ciBtZXN1cmUgY29udGludWUKCiMjIENvbnRleHRlCgpJbnTDqXJlc3NvbnMtbm91cyDDoCBwcsOpc2VudCBhdSBjYXMgZCd1biB0ZXN0IGRpYWdub3N0aWMgcmVwb3NhbnQgc3VyIHVuZSBtZXN1cmUgY29udGludWUKJFQoWCkkIHN1ciBub3MgaW5kaXZpZHVzICRYJCwgZXQgc3VyIGwnw6l0YWJsaXNzZW1lbnQgZCd1biB0aHJlc2hvbGQgJHQkIHBlcm1ldHRhbnQKZGUgY2xhc3NlciBlbiAkRD0xJCBvdSAkRD0wJC4KCkxlIHRlc3QgZXN0IGFsb3JzIHNpbXBsZW1lbnQgJEQgPSBcZGVsdGFfe1QoWCkgPiB0fSQuCgpTdXBwb3NvbnMgw6AgcHLDqXNlbnQgcXUnb24gcG9zc8OoZGUgbGVzIGRvbm7DqWVzIGRlICRUKFgpJCBwb3VyIHVuIGVuc2VtYmxlIGQnaW5kaXZpZHVzLApwb3VyIGxlc3F1ZWxzIG9uIGEgw6lnYWxlbWVudCBhY2PDqHMgYXUgY2xhc3NlbWVudCBlbiAkTT0xJCBvdSAkTT0wJCBkb25uw6kgcGFyCnVuIGdvbGQgc3RhbmRhcmQuCgpMQSBxdWVzdGlvbiBjZW50cmFsZSBkJ8OpdGFibGlzc2VtZW50IGQndW4gbm91dmVhdSB0ZXN0IGFkw6lxdWF0IGVzdCBkb25jIDoKCj4gUXVlbGxlIHZhbGV1ciBkZSB0aHJlc2hvbGQgb3B0aW1hbGUgJHQkIGZhdXQtaWwgcHJlbmRyZSA/CgpFdCBsYSBxdWVzdGlvbiBzdWJzaWRpYWlyZSwgZXh0csOqbWVtZW50IGltcG9ydGFudGUgOgoKPiBRdWUgc2lnbmlmaWUgbCcgIm9wdGltYWxpdMOpIiwgZG9udCBpbCBlc3QgcXVlc3Rpb24gZGFucyBsYSBxdWVzdGlvbiBwcsOpY8OpZGVudGUgPwoKIyMgU2NlbmFyaW8gc2ltdWzDqQoKSWNpIGVuY29yZSwgb24gdmEgc2UgZml4ZXIgbGVzIGlkw6llcyBlbiBzaW11bGFudCB1biBqZXUgZGUgZG9ubsOpZXMgZmljdGlmCnNlbG9uIHVuIHNjw6luYXJpbyBiaWVuIGNob2lzaSA6CgoqIGxhIHByw6l2YWxlbmNlLCAkXG1hdGhiYntQfShNID0gMSkkIGVzdCBkZSAwLjEuCiogU2FjaGFudCBxdSd1biBpbmRpdmlkdSBlc3QgbWFsYWRlLCAkVChYKSBcc2ltIFxtYXRoY2Fse059KFxtdV9NLCBcc2lnbWFfTSkkLgoqIFNhY2hhbnQgcXUndW4gaW5kaXZpZHUgZXN0IG5vbi1tYWxhZGUsICRUKFgpIFxzaW0gXG1hdGhjYWx7Tn0oXG11X3tcYmFye019fSwgXHNpZ21hX1xiYXJ7TX0pJC4KCmBgYHtyIGZpZy53aWR0aD0xMH0KbiA8LSAxZTMKcHJldmFsIDwtIDAuMQptdV9OTSA8LSA0Cm11X00gPC0gNgpzaWdtYV9NIDwtIDEKc2lnbWFfTk0gPC0gMgoKZCA8LSBkYXRhLmZyYW1lKGlkID0gc2VxKDEsIG4pLAoJCQkJIG1hbGFkaWUgPSByYmlub20obiwgc2l6ZT0xLCBwcm9iPXByZXZhbCkpICU+JQoJbXV0YXRlKG1lc3VyZSA9IHJub3JtKG4sIAoJCQkJCQkgIG1lYW49aWZlbHNlKG1hbGFkaWU9PTEsIG11X00sIG11X05NKSwKCQkJCQkJICBzZD1pZmVsc2UobWFsYWRpZT09MSwgc2lnbWFfTSwgc2lnbWFfTk0pICkpCgpkICU+JSBnZ3Bsb3QoYWVzKHggPSBtZXN1cmUpKSArCglnZW9tX2hpc3RvZ3JhbSgpICsKCXRoZW1lX2J3KCkKYGBgCgoKIyMgU2Vuc2liaWxpdMOpLCBzcMOpY2lmaWNpdMOpLCBhY2N1cmFjeQoKTWV0dG9ucyBxdSdvbiBmaXhlIHVuZSB2YWxldXIgZGUgdGhyZXNob2xkICR0JC4KT24gZGlzcG9zZSBhbG9ycyBkJ3VuIHRlc3QgZGlhZ25vc3RpYywgcXUnb24gcGV1dCBjb21wYXJlciDDoCBub3RyZSBnb2xkIHN0YW5kYXJkCmNvbW1lIGTDqWNyaXQgZGFucyBsYSBzZWN0aW9uIHByw6ljw6lkZW50ZS4KUGFyIGV4ZW1wbGUsIHBvdXIgJHQgPSA1JCwgb24gb2J0aWVudCA6CgpgYGB7cn0KZ2V0X2NvbnRpbmdlbmN5X3RhYmxlIDwtIGZ1bmN0aW9uKHQsIGRhdGEpewoJdGFibGUgPC0gZGF0YSAlPiUgbXV0YXRlKGRpYWdub3N0aWMgPSBpZmVsc2UobWVzdXJlID4gdCwgMSwgMCkpICU+JQoJCWdyb3VwX2J5KG1hbGFkaWUsIGRpYWdub3N0aWMpICU+JQoJCXN1bW1hcmlzZShuYiA9IG4oKSkgJT4lIHVuZ3JvdXAoKQoKCXJldHVybiAodGFibGUpCn0KCiMgUGxvdCBkZXMgdmFsZXVycwp0YWJsZSA8LSBnZXRfY29udGluZ2VuY3lfdGFibGUodD01LCBkYXRhPWQpCnRhYmxlICU+JSBtdXRhdGUobWFsYWRpZSA9IGlmZWxzZShtYWxhZGllPT0xLCAibWFsYWRlIiwgIm5vbi1tYWxhZGUiKSwKCQkJIGRpYWdub3N0aWMgPSBpZmVsc2UoZGlhZ25vc3RpYz09MSwgImRpYWdub3N0aWMgcG9zaXRpZiIsICJkaWFnbm9zdGljIG7DqWdhdGlmIikpICU+JQoJZ2dwbG90KGFlcyh4ID0gbWFsYWRpZSwgeSA9IGRpYWdub3N0aWMpKSArCglnZW9tX3Jhc3RlcihhZXMoZmlsbD1uYikpICsKCWdlb21fdGV4dChhZXMobGFiZWw9bmIpKSArCglzY2FsZV94X2Rpc2NyZXRlKHBvc2l0aW9uID0gInRvcCIpICsKCXRoZW1lX2J3KCkKYGBgCgpPbiBwZXV0IGFsb3JzIGVuIGTDqWR1aXJlIGxhIHNlbnNpYmlsaXTDqSBldCBsYSBzcMOpY2lmaWNpdMOpIGR1IHRlc3QgOgoKYGBge3J9CmdldF9zZW5zX3NwZWNfYWNjIDwtIGZ1bmN0aW9uKHRhYmxlKXsKCW5fTV9EIDwtIHRhYmxlICU+JSBmaWx0ZXIobWFsYWRpZT09MSwgZGlhZ25vc3RpYz09MSkgJT4lIHB1bGwobmIpCgluX01fRCA8LSBpZmVsc2UobGVuZ3RoKG5fTV9EKSA9PSAwLCAwLCBuX01fRCkKCgluX01fTkQgPC0gdGFibGUgJT4lIGZpbHRlcihtYWxhZGllPT0xLCBkaWFnbm9zdGljPT0wKSAlPiUgcHVsbChuYikKCW5fTV9ORCA8LSBpZmVsc2UobGVuZ3RoKG5fTV9ORCkgPT0gMCwgMCwgbl9NX05EKQoKCW5fTk1fTkQgPC0gdGFibGUgJT4lIGZpbHRlcihtYWxhZGllPT0wLCBkaWFnbm9zdGljPT0wKSAlPiUgcHVsbChuYikKCW5fTk1fTkQgPC0gaWZlbHNlKGxlbmd0aChuX05NX05EKSA9PSAwLCAwLCBuX05NX05EKQoKCW5fTk1fRCA8LSB0YWJsZSAlPiUgZmlsdGVyKG1hbGFkaWU9PTAsIGRpYWdub3N0aWM9PTEpICU+JSBwdWxsKG5iKQoJbl9OTV9EIDwtIGlmZWxzZShsZW5ndGgobl9OTV9EKSA9PSAwLCAwLCBuX05NX0QpCgoJc2Vuc19lc3RpbSA8LSBuX01fRCAvIChuX01fRCArIG5fTV9ORCkKCXNwZWNfZXN0aW0gPC0gbl9OTV9ORCAvIChuX05NX05EICsgbl9OTV9EKQoJYWNjX2VzdGltIDwtIChuX01fRCArIG5fTk1fTkQpIC8gKG5fTV9EICsgbl9NX05EICsgbl9OTV9ORCArIG5fTk1fRCkKCglyZXR1cm4gKGxpc3Qoc2VucyA9IHNlbnNfZXN0aW0sIHNwZWMgPSBzcGVjX2VzdGltLCBhY2MgPSBhY2NfZXN0aW0pKQp9CmVzdGltIDwtIGdldF9zZW5zX3NwZWNfYWNjKHRhYmxlKQpgYGAKCk9uIG9idGllbnQgaWNpIGRlcyB2YWxldXJzIG51bcOpcmlxdWVzIHJlc3BlY3RpdmVtZW50IMOgCmByIGVzdGltJHNlbnNgIHBvdXIgbGEgc2Vuc2liaWxpdMOpLCAKYHIgZXN0aW0kc3BlY2AgcG91ciBsYSBzcMOpY2lmaWNpdMOpLApldCBgciBlc3RpbSRhY2NgIHBvdXIgbCdhY2N1cmFjeS4KCiMjIENob2l4IGR1IHNldWlsCgpPbiBwZXV0IGVuc3VpdGUgYWxsZXIgcGx1cyBsb2luLCBldCBjYWxjdWxlciBjZXMgdmFsZXVycyBwb3VyIHRvdXRlcyBsZXMgdmFsZXVycyBkZSB0aHJlc2hvbGQgcXVpIHBvdXJyYWllbnQgbm91cyBpbnTDqXJlc3NlciwgcGFyIGV4ZW1wbGUgdG91dGVzIGxlcyB2YWxldXJzIGVudHJlIC0yIGV0IDEyLApkYW5zIGwnZXNwb2lyIGRlIGd1aWRlciBsZSBjaG9peCBkZSAkdCQuCgpgYGB7ciBmaWcud2lkdGg9MTAsIG1lc3NhZ2U9Rn0KdF9saXN0IDwtIHNlcShmcm9tPS0yLCB0bz0xMiwgYnk9MC4yKQpzZW5zX2xpc3QgPC0gYygpCnNwZWNfbGlzdCA8LSBjKCkKYWNjX2xpc3QgPC0gYygpCgpmb3IgKHQgaW4gdF9saXN0KXsKCXRhYmxlIDwtIGdldF9jb250aW5nZW5jeV90YWJsZSh0LCBkKQoJZXN0aW0gPC0gZ2V0X3NlbnNfc3BlY19hY2ModGFibGUpCgkKCXNlbnNfbGlzdCA8LSBjKHNlbnNfbGlzdCwgZXN0aW0kc2VucykKCXNwZWNfbGlzdCA8LSBjKHNwZWNfbGlzdCwgZXN0aW0kc3BlYykKCWFjY19saXN0IDwtIGMoYWNjX2xpc3QsIGVzdGltJGFjYykKfQoKZF9jaGFyYWMgPC0gZGF0YS5mcmFtZSh0ID0gcmVwKHRfbGlzdCwgMyksCgkJCQkJCSAgdmFsID0gYyhzZW5zX2xpc3QsIHNwZWNfbGlzdCwgYWNjX2xpc3QpLAoJCQkJCQkgIHR5cGUgPSBjKHJlcCgic2Vuc2liaWxpdMOpIiwgbGVuZ3RoKHRfbGlzdCkpLAoJCQkJCQkJCSAgIHJlcCgic3DDqWNpZmljaXTDqSIsIGxlbmd0aCh0X2xpc3QpKSwKCQkJCQkJCQkgICByZXAoImFjY3VyYWN5IiwgbGVuZ3RoKHRfbGlzdCkpICkgKQoKZF9jaGFyYWMgJT4lIGdncGxvdChhZXMoeD10LCB5PXZhbCwgY29sPXR5cGUpKSArCgl0aGVtZV9idygpICsKCWdlb21fcG9pbnQoKSArCglnZW9tX2xpbmUoKQpgYGAKCk9uIHBvdXJyYWl0IGRvbmMgcGVuc2VyIMOgIGNob2lzaXIgOgoKKiBsZSBzZXVpbCBxdWkgbWF4aW1pc2UgbGEgc2Vuc2liaWxpdMOpLAoqIGxlIHNldWlsIHF1aSBtYXhpbWlzZSBsYSBzcMOpY2lmaWNpdMOpLAoqIGxlIHNldWlsIHF1aSBtYXhpbWlzZSBsJ2FjY3VyYWN5LgoKU2Vsb24gY2UgcXVpIGZhaXQgbGUgcGx1cyBzZW5zIGRhbnMgdW5lIHNpdHVhdGlvbiBkb25uw6llLgoKIyMgQ291cmJlIFJPQyAKClVuZSBhdXRyZSByZXByw6lzZW50YXRpb24gZ3JhcGhpcXVlIHRyw6hzIGFwcHLDqWNpw6llIGNvbnNpc3RlIMOgIHJlcHLDqXNlbnRlcgpjZXR0ZSBmb2lzLWNpIGxhIHNlbnNpYmlsaXTDqSBlbiBmb25jdGlvbiBkZSAoMSAtIHNww6ljaWZpY2l0w6kpLgoKQ2V0dGUgY291cmJlIHMnYXBwZWxsZSBsYSAqY291cmJlIFJPQyosIHBvdXIgKlJlY2VpdmVyIE9wZXJhdGluZyBDaGFyYWN0ZXJpc3RpYyosCm91IHBsdXMgc2ltcGxlbWVudCwgZGFucyBsZSBkb21haW5lIGRlcyBzdGF0cyBtw6lkaWNhbGVzLCAqY291cmJlIHNlbnNpYmlsaXTDqS9zcMOpY2lmaWNpdMOpKiwKZXQgbGEgdm9pY2kgZGFucyBsZSBjYXMgcXVpIG5vdXMgaW50w6lyZXNzZSA6CgpgYGB7ciBmaWcud2lkdGg9MTB9CmRfcm9jIDwtIGRhdGEuZnJhbWUoc2Vuc2liaWxpdHkgPSBzZW5zX2xpc3QsCgkJCQkJc3BlY2lmaWNpdHkgPSBzcGVjX2xpc3QsCgkJCQkJb25lX21pbnVzX3NwZWNpZmljaXR5ID0gMSAtIHNwZWNfbGlzdCwKCQkJCQl0aHJlc2hvbGQgPSB0X2xpc3QpCgpkX3JvYyAlPiUgZ2dwbG90KGFlcyh5ID0gc2Vuc2liaWxpdHksIHggPSBvbmVfbWludXNfc3BlY2lmaWNpdHkpKSArCglnZW9tX3BvaW50KCkgKwoJZ2VvbV9saW5lKCkgKwoJeGxhYigiMSAtIHNww6ljaWZpY2l0w6kiKSArCgl5bGFiKCJzZW5zaWJpbGl0w6kiKSArCglnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbD0iZ3JheSIpICsKCXRoZW1lX2J3KCkKYGBgCgpDaGFxdWUgcG9pbnQgZGUgY2V0dGUgY291cmJlIGNvcnJlc3BvbmQgw6AgdW5lIHZhbGV1ciBkZSBzZXVpbCBkaWZmw6lyZW50ZS4KRWxsZSBwZXJtZXQgZGUgc2UgZmFpcmUgdW5lIGJvbm5lIGlkw6llIGR1IHRyYWRlLW9mZiBxdWkgZXhpc3RlIGVudHJlIGxhIHNlbnNiaWxpdMOpCmV0IGxhIHNww6ljaWZpY2l0w6kgOgoKKiBlbiBoYXV0IMOgIGRyb2l0ZSBvbiBzZSBwbGFjZSBhdSBwb2ludCBxdWkgbWF4aW1pc2UgbGEgc2Vuc2liaWxpdMOpLCBhdSBwcml4IGQndW5lIHNww6ljaWZpY2l0w6kgZmFpYmxlLAoqIGVuIGJhcyDDoCBnYXVjaGUsIG9uIHNlIHBsYWNlIGF1IHBvaW50IHF1aSBtYXhpbWlzZSBsYSBzcMOpY2lmaWNpdMOpIGF1IHByaXggZCd1bmUgc2Vuc2liaWxpdMOpIGZhaWJsZSwKKiBlbnRyZSBsZXMgZGV1eCwgc3VyIGxhIGNvdXJiZSwgb24gcGV1dCBjaG9pc2lyIGxlIHBvaW50IHF1aSBub3VzIHNlbWJsZSDDqnRyZSBsZSBtZWlsbGV1ciBjb21wcm9taXMgZW50cmUgbGVzIGRldXgsCiogZW4gaGF1dCDDoCBnYXVjaGUsIGF1IHBvaW50IGRlIGNvb3Jkb25uw6llcyAoMCwxKSwgaWwgeSBhdXJhaXQgdW4gdGVzdCBkaWFnbm9zdGljIHBhcmZhaXQsIHF1aSBuZSBmZXJhaXQKICBhdWN1biBmYXV4IHBvc2l0aWYgbmkgYXVjdW4gZmF1eCBuw6lnYXRpZi4KCk5vdG9ucyBxdWUgbGEgZHJvaXRlIGdyaXNlIHN1cGVycG9zw6llIGF1IGdyYXBoZSBjb3JyZXNwb25kIMOgIGNlIHF1J29uIG9idGllbnQgc2kgb24gZGlhZ25vc3RpcXVlCm5vcyBpbmRpdmlkdXMgYXUgaGFzYXJkLCBlbiBqb3VhbnQgw6AgcGlsZSBvdSBmYWNlLgpMYSBkaXN0YW5jZSDDoCBjZXR0ZSBjb3VyYmUgcGVybWV0IGQnYXBwcsOpY2llciDDoCBxdWVsIHBvaW50IGxhIHZhbGV1ciBzdXIgbGFxdWVsbGUgb24gc2UgYmFzZQpwb3VyIMOpdGFibGlyIGxlIGRpYWdub3N0aWMgZXN0IGRpc3RyaWJ1w6llIGRpZmbDqXJlbW1lbnQgZW50cmUgbGVzIG1hbGFkZXMgZXQgbGVzIG5vbi1tYWxhZGVzLgoKQSBwcsOpc2VudCBxdSdvbiBhIGJpZW4gbWlzIGxlcyBtYWlucyBkYW5zIGxlIGNhbWJvdWlzIHBvdXIgY29tcHJlbmRyZSBjb21tZW50IMOnYSBmb25jdGlvbm5lLApwbGFjZSDDoCBsYSBtYWdpZSBkZSBSIGV0IGRlcyBtdWx0aXBsZXMgcGFja2FnZXMgcXVpIGV4aXN0ZW50ICEKRXZpZGVtbWVudCwgdG91dGVzIGNlcyBvcMOpcmF0aW9ucyBww6luaWJsZXMgw6AgZmFpcmUgcGV1dmVudCDDqnRyZSByw6lhbGlzw6llcyBlbiB0cm9pcyBsaWduZSBkZSBjb2RlCsOgIGwnYWlkZSBkdSBwYWNrYWdlIGBST0NSYCAocGFyIGV4ZW1wbGUpLgoKYGBge3IgZmlnLndpZHRoPTEwfQpsaWJyYXJ5KFJPQ1IpCiMgb24gY3LDqcOpIHVuIG9iamV0IGRlICJwcsOpZGljdGlvbiIgw6AgcGFydGlyIGRlIG5vdHJlIG1lc3VyZSBldCBkdSBnb2xkIHN0YW5kYXJkCnByZWQgPC0gcHJlZGljdGlvbihkJG1lc3VyZSwgZCRtYWxhZGllKQojIG9uIGNhbGN1bGUgbGVzIGluZGljYXRldXJzIGRlICJ0cHIiIDogdHJ1ZSBwb3NpdGl2ZSByYXRlID0gc2Vuc2liaWxpdMOpCiMgZXQgImZwciIgOiBmYWxzZSBwb3NpdGl2ZSByYXRlID0gMSAtIHNwZWNpZmljaXTDqSwKIyBwb3VyIHRvdXRlcyBsZXMgdmFsZXVycyBkZSB0aHJlc2hvbGQgcG9zc2libGVzCnBlcmYgPC0gcGVyZm9ybWFuY2UocHJlZCwgInRwciIsICJmcHIiKQpwbG90KHBlcmYpCmBgYAoKTm90b25zIHF1ZSwgc2kgb24gc291aGFpdGUgdG91dCBkZSBtw6ptZSB1dGlsaXNlciBgZ2dwbG90YCBwb3VyIGFtw6lsaW9yZXIgY2UgZ3JhcGhpcXVlLApsYSBtZWlsbGV1cmUgb3B0aW9uIGNvbnNpc3RlIMOgIHJlZ2FyZGVyIGxlIGNvbnRlbnUgZGUgbCdvYmplY3QgYHBlcmZgIGV0IMOgIGVuIHV0aWxpc2VyCmRlcyBtb3JjZWF1eCBiaWVuIGNob2lzaXMuIFBhciBleGVtcGxlIDoKCmBgYHtyIGZpZy53aWR0aD0xMH0Kc3RyKHBlcmYpCmRhdGEuZnJhbWUoc2Vuc2liaWxpdHkgPSBwZXJmQHkudmFsdWVzW1sxXV0sIG9uZV9taW51c19zcGVjaWZpY2l0eSA9IHBlcmZAeC52YWx1ZXNbWzFdXSkgJT4lCglnZ3Bsb3QoYWVzKHkgPSBzZW5zaWJpbGl0eSwgeCA9IG9uZV9taW51c19zcGVjaWZpY2l0eSkpICsKCWdlb21fbGluZSgpICsKCXhsYWIoIjEgLSBzcMOpY2lmaWNpdMOpIikgKwoJeWxhYigic2Vuc2liaWxpdMOpIikgKwoJZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2w9ImdyYXkiKSArCgl0aGVtZV9idygpCmBgYAoKVW4gYXV0cmUgcGFja2FnZSBhbHRlcm5hdGlmLCBgcFJPQ2AsIGVzdCDDqWdhbGVtZW50IGRpc3BvbmlibGUuClZvaWNpIHF1ZWxxdWVzIGxpZ25lcyBwb3VyIGTDqWJ1dGVyIGF2ZWMgOgoKYGBge3IgZmlnLndpZHRoPTEwfQpsaWJyYXJ5KHBST0MpCnJvYyhtYWxhZGllIH4gbWVzdXJlLCBkKSAlPiUgcGxvdC5yb2MoKQpgYGAKCkRlIG5vb21icmV1c2VzIG9wdGlvbnMgc29udCBkaXNwb25pYmxlcyBwb3VyIG1vZGlmaWVyIGxlIGNvbXBvcnRlbWVudApkZSBsYSBmb25jdGlvbiBgcm9jYCBldCBkZSBsYSBmb25jdGlvbiBkJ2FmZmljaGFnZSBgcGxvdC5yb2NgLgoKIyMgUk9DIEFVQwoKSW1hZ2lub25zIHF1J29uIHNvdWhhaXRlLCDDoCBwcsOpc2VudCwgY29tcGFyZXIgZGV1eCAob3UgcGx1cykgdGVzdHMgZGlhZ25vc3RpY3MuClNpIG9uIHNhaXQgZMOpasOgIHF1J29uIHNvdWhhaXRlIHByaW9yaXNlciwgcGFyIGV4ZW1wbGUsIGwnYWNjdXJhY3ksIGxhIHF1ZXN0aW9uIGVzdCByZWxhdGl2ZW1lbnQgc2ltcGxlCsOgIHLDqXNvdWRyZSA6CgoqIG9uIHByZW5kIGxlIHNldWlsIG1heGltaXNhbnQgbCdhY2N1cmFjeSBkYW5zIGxlIHByZW1pZXIgdGVzdCwKKiBvbiBwcmVuZCBsZSBzZXVpbCBtYXhpbWlzYW50IGwnYWNjdXJhY3kgZGFucyBsZSBzZWNvbmQgdGVzdCwKKiBldCBvbiBwcmVuZCBsZSB0ZXN0IChldCBsZSBzZXVpbCkgbWF4aW1pc2FudCBsJ2FjY3VyYWN5IGVudHJlIGxlcyBkZXV4IHRlc3RzLgoKRW4gcmV2YW5jaGUsIGxvcnNxdSdvbiBuZSBzYWl0IHBhcyBiaWVuIGNob2lzaXIgc2kgb24gc291aGFpdGUgcHJpb3Jpc2VyIHNlbnNpYmlsaXTDqSwgc3DDqWNpZmljaXTDqSBvdSBhY2N1cmFjeSwKb24gcGV1dCBzJ2FwcHV5ZXIgc3VyIHVuZSBub3V2ZWxsZSBxdWFudGl0w6kgaW50w6lyZXNzYW50ZSBiYXPDqWUgc3VyIGxhIGNvdXJiZSBST0MgOgpsYSAqUk9DIEFVQyosIHBvdXIgKlJPQyBBcmVhIFVuZGVyIFRoZSBDdXJ2ZSouCgpMJ2lkw6llIGVzdCB0csOocyBzaW1wbGUgOiBwbHVzIGwnYWlyZSBzb3VzIGxhIGNvdXJiZSBlc3QgaW1wb3J0YW50ZSwgCm1laWxsZXVyIHNlcmEgY29uc2lkw6lyw6kgbm90cmUgdGVzdCBkaWFnbm9zdGljLgpTaW11bG9ucyB1biBkZXV4acOobWUgamV1IGRlIGRvbm7DqWVzIHBvdXIgdmlzdWFsaXNlciBjZSBxdWUgw6dhIGRvbm5lLCAKYXZlYyB1bmUgYXV0cmUgbWVzdXJlIHBlcm1ldHRhbnQgZGUgZGlhZ25vc3RpcXVlciBub3RyZSBtYWxhZGllIDoKCmBgYHtyIGZpZy53aWR0aD0xMH0KbiA8LSAxZTMKcHJldmFsIDwtIDAuMQoKbXUxX05NIDwtIDQKbXUxX00gPC0gNgpzaWdtYTFfTSA8LSAxCnNpZ21hMV9OTSA8LSAyCgptdTJfTk0gPC0gMwptdTJfTSA8LSA2CnNpZ21hMl9NIDwtIDMKc2lnbWEyX05NIDwtIDMKCmQgPC0gZGF0YS5mcmFtZShpZCA9IHNlcSgxLCBuKSwKCQkJCSBtYWxhZGllID0gcmJpbm9tKG4sIHNpemU9MSwgcHJvYj1wcmV2YWwpKSAlPiUKCW11dGF0ZShtZXN1cmUxID0gcm5vcm0obiwgCgkJCQkJCSAgbWVhbj1pZmVsc2UobWFsYWRpZT09MSwgbXUxX00sIG11MV9OTSksCgkJCQkJCSAgc2Q9aWZlbHNlKG1hbGFkaWU9PTEsIHNpZ21hMV9NLCBzaWdtYTFfTk0pKSwKCQkgICBtZXN1cmUyID0gcm5vcm0obiwgCgkJCQkJCSAgbWVhbj1pZmVsc2UobWFsYWRpZT09MSwgbXUyX00sIG11Ml9OTSksCgkJCQkJCSAgc2Q9aWZlbHNlKG1hbGFkaWU9PTEsIHNpZ21hMl9NLCBzaWdtYTJfTk0pICkpCmBgYAoKT24gdmEgdXRpbGlzZXIgbGUgcGFja2FnZSBgcFJPQ2AgcG91ciBub3VzIGNhbGN1bGVyIGxhIGNvdXJiZSBST0MsIGFpbnNpCnF1ZSBsJ2FpcmUgc291cyBsYSBjb3VyYmUgOgoKYGBge3IgZmlnLndpZHRoPTEwfQpyMSA8LSByb2MobWFsYWRpZSB+IG1lc3VyZTEsIGQpCnIyIDwtIHJvYyhtYWxhZGllIH4gbWVzdXJlMiwgZCkKCmF1YzEgPC0gcjEkYXVjCmF1YzIgPC0gcjIkYXVjCgpkYXRhLmZyYW1lKHNlbnMgPSBjKHIxJHNlbnNpdGl2aXRpZXMsIHIyJHNlbnNpdGl2aXRpZXMpLAoJCSAgIG9uZV9taW51c19zcGVjID0gYygxLXIxJHNwZWNpZmljaXRpZXMsIDEtcjIkc3BlY2lmaWNpdGllcyksCgkJICAgdGVzdCA9IGMocmVwKCIxIiwgbisxKSwgcmVwKCIyIiwgbisxKSkgKSAlPiUKCWdncGxvdChhZXMoeD1vbmVfbWludXNfc3BlYywgeT1zZW5zLCBjb2w9dGVzdCkpICsKCWdlb21fbGluZSgpICsKCXRoZW1lX2J3KCkgKwoJeWxhYigic2Vuc2liaWxpdMOpIikgKwoJeGxhYigiMSAtIHNww6ljaWZpY2l0w6kiKQpgYGAKCkwnYWlyZSBzb3VzIGxhIGNvdXJiZSB2YXV0IGByIGF1YzFgIHBvdXIgbGUgdGVzdCBkaWFnbm9zdGljIG51bcOpcm8gMSwKZXQgYHIgYXVjMmAgcG91ciBsZSB0ZXN0IGRpYWdub3N0aWMgbnVtw6lybyAyLgpTdXIgbGEgYmFzZSBkZSBsYSBST0MgQVVDLCBvbiBjaG9pc2lyYWl0IGRvbmMgcHLDqWbDqXJlbnRpZWxsZW1lbnQgbGUgdGVzdCBkaWFnbm9zdGljIG51bcOpcm8gMS4KTm90b25zIHF1J29uIGNob2lzaXJhaXQgw6lnYWxlbWVudCBsZSB0ZXN0IGRpYWdub3N0aWMgbnVtw6lybyAxIHNpIG9uIHNvdWhhaXRlIG9idGVuaXIgdW4KdGVzdCBkaWFnbm9zdGljIGF5YW50IHVuZSBmb3J0ZSBzZW5zaWJpbGl0w6kuCkVuIHJldmFuY2hlLCBvbiBjaG9pc2lyYWl0IHByw6lmw6lyZW50aWVsbGVtZW50IGxlIHRlc3QgZGlhZ25vc3RpYyBudW3DqXJvIDIgc2kgb24gc291aGFpdGFpdApvYnRlbmlyIHVuIHRlc3QgZGlhZ25vc3RpYyDDoCBwbHVzIGZvcnRlIHNww6ljaWZpY2l0w6kuCgoKIyBDb25jbHVzaW9uCgpPbiBwZXV0IGlkZW50aWZpZXIgdW4gY2VydGFpbiBub21icmUgZGUgKnRha2UtaG9tZSBtZXNzYWdlcyogc3VyIGNlIHN1amV0IDoKCiogbGEgc2Vuc2liaWxpdMOpIGVzdCBsYSBwcm9iYWJpbGl0w6kgZCfDqnRyZSBkaWFnbm9zdGlxdcOpIHBvc2l0aWYgc2FjaGFudCBxdSdvbiBlc3QgbWFsYWRlLgogICQkIFxtYXRoYmJ7UH0oRCA9IDEgfCBNID0gMSkgJCQKKiBsYSBzcMOpY2lmaWNpdMOpIGVzdCBsYSBwcm9iYWJpbGl0w6kgZCfDqnRyZSBkaWFnbm9zdGlxdcOpIG7DqWdhdGlmIHNhY2hhbnQgcXUnb24gZXN0IG5vbi1tYWxhZGUuCiAgJCQgXG1hdGhiYntQfShEID0gMCB8IE0gPSAwKSAkJAoqIGxhIHZhbGV1ciBwcsOpZGljdGl2ZSBwb3NpdGl2ZSAoVlBQKSBlc3QgbGEgcHJvYmFiaWxpdMOpIGQnw6p0cmUgbWFsYWRlIHNhY2hhbnQgcXUnb24gZXN0IGRpYWdub3N0aXF1w6kgcG9zaXRpZi4KICAkJCBcbWF0aGJie1B9KE0gPSAxIHwgRCA9IDEpICQkCiogbGEgdmFsZXVyIHByw6lkaWN0aXZlIG7DqWdhdGl2ZSAoVlBOKSBlc3QgbGEgcHJvYmFiaWxpdMOpIGQnw6p0cmUgbm9uLW1hbGFkZSBzYWNoYW50IHF1J29uIGVzdCBkaWFnbm9zdGlxdcOpIG7DqWdhdGlmLgogICQkIFxtYXRoYmJ7UH0oTSA9IDAgfCBEID0gMCkgJCQKKiBkYW5zIHVuIMOpY2hhbnRpbGxvbiByZXByw6lzZW50YXRpZiBkZSBsYSBwb3B1bGF0aW9uIGfDqW7DqXJhbGUsIGxhIGNvbXBhcmFpc29uIGQndW4gdGVzdCBkaWFnbm9zdGljIGF1IGdvbGQgc3RhbmRhcmQgcGVybWV0IGQnZXN0aW1lciBsYSBzZW5zaWJpbGl0w6ksIGxhIHNww6ljaWZpY2l0w6ksIGxhIFZQUCBldCBsYSBWUE4gw6AgcGFydGlyIGRlIGxhIHRhYmxlIGRlIGNvbnRpbmdlbmNlLgoqIGRhbnMgdW4gw6ljaGFudGlsbG9uIGNvbnRlbmFudCB1biBub21icmUgZml4w6kgZGUgbWFsYWRlcyBldCBub24tbWFsYWRlcywgb24gbmUgcGV1dCBlc3RpbWVyIHF1ZSBsYSBzZW5zaWJpbGl0w6kKICBldCBsYSBzcMOpY2lmaWNpdMOpLiBMZSBjYWxjdWwgZGUgbGEgVlBQIGV0IFZQTiByZXF1acOocmVudCBkJ2F2b2lyIGFjY8OocyDDoCBsYSBwcsOpdmFsZW5jZSBkZSBsYSBtYWxhZGllLgoqIHNpIG9uIHNvdWhhaXRlIGNyw6llciB1biB0ZXN0IGRpYWdub3N0aWMgw6AgcGFydGlyIGQndW5lIG1lc3VyZSBjb250aW51ZSwgb24gcGV1dCBjYWxjdWxlciBsYSBzZW5zaWJpbGl0w6kKICBldCBzcMOpY2lmaWNpdMOpIHBvdXIgdG91dGVzIGxlcyB2YWxldXJzIGRlIHNldWlsIHBvc3NpYmxlcywgZXQgY29uc3RydWlyZSB1bmUgY291cmJlIFJPQy4KICBPbiBwZXV0IGFsb3JzIGNob2lzaXIgbGUgc2V1aWwgcXVlIGwnb24gc291aGFpdGUsIG1heGltaXNhbnQgcGx1dMO0dCBsYSBzZW5zaWJpbGl0w6kgb3UgbGEgc3DDqWNpZmljaXTDqS4KKiBsJ2FpcmUgc291cyBsYSBjb3VyYmUgUk9DIChST0MgQVVDKSBwZXJtZXQgZGUgY29tcGFyZXIgZGVzIHRlc3RzIGRpYWdub3N0aWNzIGxvcnNxdSdvbiBuZSBzYWl0IHBhcwogIHNpIG9uIGRvaXQgcHJpb3Jpc2VyIHVuZSBib25uZSBzZW5zaWJpbGl0w6kgb3UgdW5lIGJvbm5lIHNww6ljaWZpY2l0w6kuCgoK