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)
# graphes multiples
library(cowplot)

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

Pour se focaliser sur les stats et uniquement les stats, on travaille sur des jeux de données idéalisés, qui présentent l’avantage :

  • de ne pas présenter de données manquantes,
  • d’être parfaitement bien formattés dans chaque colonne,
  • de pouvoir être partagés sans problème avec toute personne qui souhaite refaire tourner les tests.

L’objectif de ce document est de réaliser les simulations, ainsi que diverses représentations graphiques qui seront incluses dans les présentations. C’est l’occasion de partager des morceaux de code R avec des participants qui seraient intéressés.

Dataset 1 : RCT diabète

Description

Le premier jeu de données simule un RCT (Randomized Controlled Trial) ayant pour but de tester et quantifier l’impact d’une intervention de conseil en nutrition et activité physique sur des patients diabétiques.

Les variables d’outcome sont mesurées 6 mois après le début de l’intervention.

  • l’étude possède deux bras: control et treatment.
  • on enregistre des variables démographiques à l’inclusion : sex, age, age_group, height, weight_t0, bmi, bmi_categories, diabetes, steps_t0.
  • une variable d’outcome lié à la prise de poids : diff_weight.
  • une variable d’outcome lié à l’activité physique : diff_steps.
  • 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 repas après lesquels la glycémie est hors cible : n_meals_glycemia_out_target.

Loi de simulation

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

n_per_arm <- 250
# data that do not depend on the group
d <- data.frame(id = seq(1,500),
                group = as.factor(rep(c("control", "treatment"), each=n_per_arm)),
                sex = sample(c("F","M"), size=n_per_arm*2, replace=T),
                age = round(runif(n=2*n_per_arm, min=19, max=77)),
                height = round(rnorm(n=2*n_per_arm, mean=1.7, sd=0.1), digits=2),
                weight_t0 = round(rnorm(n=2*n_per_arm, mean=70, sd=10), 2),
                diabetes = sample(c("I", "II"), size=2*n_per_arm, replace=T),
                steps_t0 = round(rnorm(n=2*n_per_arm, mean=2500, sd=500)),
                t_compliance = round(rexp(n=2*n_per_arm, rate=0.05))  )

# data that do not depend on the group but depend on previously simulated data
d <- d %>% 
    mutate(age_group = as.factor(ifelse(age < 40, 
                                        "moins de 40", 
                                        ifelse(age < 60, 
                                               "entre 40 et 60", 
                                               "plus de 60"))),
           bmi = weight_t0 / height^2,
           bmi_group = as.factor(ifelse(bmi < 18.5, 
                                        "underweight", 
                                        ifelse(bmi < 25, 
                                               "normal", 
                                               ifelse(bmi < 30, 
                                                      "overweight", 
                                                      "obese")))) )

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

f_logistic <- function(x){
    return (1 / (1 + exp(-x)))
}

# data that do depend on the group
d <- d %>% mutate(diff_weight = rnorm(n=n_per_arm*2, 
                                      mean=-0.04 * t_compliance * ifelse(group=="treatment",1,0) +
                                            ifelse(diabetes == "I", -0.5, 0) +
                                            -(bmi-20)/10, 
                                      sd=2.5), 
                  diff_steps = rnorm(n=n_per_arm*2,
                                     mean=25 * t_compliance * ifelse(group=="treatment", 1, 0), 
                                     sd=500), 
                  is_depressed = rbinom(n=2*n_per_arm,
                                    size=1,
                                    prob=sapply(X=(weight_t0-70)/5 + ifelse(group=="treatment", -1, 0), 
                                                FUN=f_logistic) ),
                  feels_sleepy = c( sample(modalites, n_per_arm, prob = c(0.3, 0.4, 0.3), replace = TRUE),
                                    sample(modalites, n_per_arm, prob = c(0.2, 0.3, 0.5), replace = TRUE) ),
                  n_meals_glycemia_out_of_target = rpois(n=2*n_per_arm, 
                                                         lambda=exp((bmi-20)/5) * 
                                                             ifelse(group=="control" & diabetes== "I", 
                                                                       8,
                                                                       ifelse(group=="control" & diabetes=="II",
                                                                              5,
                                                                              ifelse(group=="treatment" & diabetes=="I", 
                                                                                     4, 
                                                                                     2)))))

# last, the outcome at the end of the trial
d <- d %>% 
    mutate(weight_tf = weight_t0 + diff_weight,
           steps_tf = steps_t0 + diff_steps)

Ecriture du dataset

On enregistre le dataset dans le fichier suivant :

write.csv(d, "etude_fictive_diabetes.csv")

Dataset 2 : n of 1 souris

Description

On dispose ici de 50 souris, et on s’intéresse à leur système cardio-vasculaire. On souhaite quantifier l’effet de plusieurs interventions qui s’effectuent chacune à l’échelle d’une journée :

  1. un régime alimentaire particulier
  2. un programme d’exercice.

Après chaque intervention, on entregistre :

  • la fréquence cardiaque au repos,
  • l’attitude de la souris : soit “sociable”, soit “prostrée”,
  • le nombre d’extrasystoles enregistré sur 10min de suivi.

En plus de ça, on possède des mesures de baseline de différentes quantités, qu’on va supposer fixes durant l’expérience :

  • le poids de la souris,
  • sa lignée (sauvage ou KO pour un certain gène d’intérêt),
  • sa quantité de CRP

Loi de simulation

n_mouse <- 50
diets <- c("low sugar", "high sugar")
exercises <- c("I", "II", "III", "IV")

# data that do not depend on the group
d2 <- tibble(id = rep(seq(1,n_mouse), each= length(diets)*length(exercises)),
            diet = rep(rep(diets, each=length(exercises)), n_mouse),
            exercise = rep(rep(exercises, length(diets)), n_mouse),
            weight = round(rep( rnorm(n=n_mouse, mean=20, sd=4), each=length(diets)*length(exercises))),
            lineage = rep( sample(c("wild type", "KO"), size=n_mouse, replace=T), each=length(diets)*length(exercises)),
            sex = rep( sample(c("F", "M"), size=n_mouse, replace=T), each=length(diets)*length(exercises)),
            crp = round(rep( rnorm(n=n_mouse, mean=2000, sd=500), each=length(diets)*length(exercises))),
            alea = rep( rnorm(n=n_mouse, mean=0, sd=1), each=length(diets)*length(exercises)) )

# data that do depend on the group
d2 <- d2 %>% mutate(fc = rnorm(n=n_mouse*length(diets)*length(exercises), 
                             mean= 600 + alea*5 + ifelse(sex=="F", 10, 0) + (weight-20)*10 + ifelse(exercise=="IV", -50, 0) + ifelse(diet=="high sugar", 50, 0) + ifelse(lineage=="KO", 20, 0),
                             sd = 50),
                  attitude = rbinom(n=n_mouse*length(diets)*length(exercises),
                                    size=1,
                                    prob=sapply(X=alea + (crp-2000)/500 + ifelse(diet=="high sugar", 1, 0), 
                                                FUN=f_logistic) ),
                  n_extrasystole = rpois(n=n_mouse*length(diets)*length(exercises),
                                         lambda= exp(alea + ifelse(diet=="high sugar", 1, 0) + (weight-20)/10 + ifelse(lineage=="KO", 2,0)) ))

d2 <- d2 %>%
    mutate(attitude = ifelse(attitude==1, "sociable", "prostrate")) %>%
    select(-alea)

Ecriture du dataset

On enregistre le dataset dans le fichier suivant :

write.csv(d2, "etude_fictive_souris.csv")

Figures de la présentation 1

Histogramme des données

get_barplots <- function(d){

    p1 <- d %>% ggplot(aes(x=is_depressed, fill=group)) +
        geom_bar(alpha=0.5, position="identity") +
        theme_bw() +
        ylab("effectif") + 
        xlab("dépression du patient") +
        scale_fill_manual(values=c(bleuclair, rose))

    m <- d %>% 
        group_by(group) %>% 
        summarise(mean=mean(is_depressed),
                  ci_low=prop.test(sum(is_depressed), n=250)$conf.int[1],
                  ci_up=prop.test(sum(is_depressed), n=250)$conf.int[2])

    p2 <- p1  +
        geom_vline(data=m, aes(xintercept=mean, col=group)) +
        scale_color_manual(values=c(bleuclair, rose))

    p3 <- p2 +
        geom_vline(data=m, aes(xintercept=ci_low, col=group), linetype="dashed") +
        geom_vline(data=m, aes(xintercept=ci_up, col=group), linetype="dashed")

    return(list(without_mean = p1, with_mean = p2, with_ci = p3))
}
get_histograms <- function(d){

    p1 <- d %>% ggplot(aes(x=diff_weight, fill=group)) +
        geom_histogram(alpha=0.5, position="identity", bins=30) +
        theme_bw() +
        ylab("effectif") + 
        xlab("différence de poids à 6 mois") +
        scale_fill_manual(values=c(bleuclair, rose))

    m <- d %>% 
        group_by(group) %>% 
        summarise(mean=mean(diff_weight),
                  ci_low=t.test(diff_weight)$conf.int[1],
                  ci_up=t.test(diff_weight)$conf.int[2])

    p2 <- p1  +
        geom_vline(data=m, aes(xintercept=mean, col=group)) +
        scale_color_manual(values=c(bleuclair, rose))

    p3 <- p2 +
        geom_vline(data=m, aes(xintercept=ci_low, col=group), linetype="dashed") +
        geom_vline(data=m, aes(xintercept=ci_up, col=group), linetype="dashed")

    return(list(without_mean = p1, with_mean = p2, with_ci = p3))
}

pb <- get_barplots(d)
ph <- get_histograms(d)

w <- 12
h <- 5

plot_raw <- plot_grid(pb$without_mean, ph$without_mean)
plot_raw

ggsave("../../Figures/illu_diabete_raw_data.pdf", plot=plot_raw, height=h, width=w)

plot_mean <- plot_grid(pb$with_mean, ph$with_mean)
plot_mean

ggsave("../../Figures/illu_diabete_raw_data_mean.pdf", plot=plot_mean, height=h, width=w)

plot_ci <- plot_grid(pb$with_ci, ph$with_ci)
plot_ci

ggsave("../../Figures/illu_diabete_raw_data_ci.pdf", plot=plot_ci, height=h, width=w)

ggsave("../../Figures/illu_diabete_diff_weight_two_means.pdf", plot=ph$with_mean, height=h, width=w)

Mean, median, sd, quantiles

p_raw <- d %>% ggplot(aes(x=diff_weight)) +
    geom_histogram(alpha=0.5, position="identity", bins=30, fill=bleuclair) +
    theme_bw() +
    ylab("effectif") + 
    xlab("différence de poids à 6 mois")

m <- d %>% 
    summarise(mean=mean(diff_weight),
              median=median(diff_weight),
              sd=sd(diff_weight),
              q1=quantile(diff_weight, probs=0.025),
              q2=quantile(diff_weight, probs=0.975))

p_mean <- p_raw  +
    geom_vline(data=m, aes(xintercept=mean), col=bleufonce)

p_mean_median <- p_mean  +
    geom_vline(data=m, aes(xintercept=median), col=rose)

p_quantiles <- p_raw +
    geom_vline(data=m, aes(xintercept=q1), col=rose) +
    geom_vline(data=m, aes(xintercept=q2), col=rose)

p_raw

p_mean

p_mean_median

p_quantiles

ggsave("../../Figures/illu_diabete_histogram.pdf", plot=p_raw, height=h, width=w)
ggsave("../../Figures/illu_diabete_mean.pdf", plot=p_mean, height=h, width=w)
ggsave("../../Figures/illu_diabete_median.pdf", plot=p_mean_median, height=h, width=w)
ggsave("../../Figures/illu_diabete_quantiles.pdf", plot=p_quantiles, height=h, width=w)

Pour la variable t_compliance avec sa moyenne et sa médiane :

pt_raw <- d %>% ggplot(aes(x=t_compliance)) +
    geom_histogram(alpha=0.5, position="identity", bins=30, fill=bleuclair) +
    theme_bw() +
    ylab("effectif") + 
    xlab("temps de suivi de l'intervention")

mt <- d %>% 
    summarise(mean=mean(t_compliance),
              median=median(t_compliance),
              sd=sd(t_compliance),
              q1=quantile(t_compliance, probs=0.025),
              q2=quantile(t_compliance, probs=0.975))

pt_mean <- pt_raw  +
    geom_vline(data=mt, aes(xintercept=mean), col=bleufonce)

pt_mean_median <- pt_mean  +
    geom_vline(data=mt, aes(xintercept=median), col=rose)

pt_quantiles <- pt_raw +
    geom_vline(data=mt, aes(xintercept=q1), col=rose) +
    geom_vline(data=mt, aes(xintercept=q2), col=rose)

pt_raw

pt_mean

pt_mean_median

pt_quantiles

ggsave("../../Figures/illu_diabete_t_histogram.pdf", plot=pt_raw, height=h, width=w)
ggsave("../../Figures/illu_diabete_t_mean.pdf", plot=pt_mean, height=h, width=w)
ggsave("../../Figures/illu_diabete_t_median.pdf", plot=pt_mean_median, height=h, width=w)
ggsave("../../Figures/illu_diabete_t_quantiles.pdf", plot=pt_quantiles, height=h, width=w)

Présentations des outcomes d’intérêt

Les graphes permettant de donner un aperçu de différentes variables de types différents :

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

pc <- d %>% ggplot(aes(x=n_meals_glycemia_out_of_target, fill=group)) +
    geom_histogram(alpha=0.5, position="identity", bins=30) +
    scale_fill_manual(values=c(bleuclair, rose)) +
    theme_bw()

ph$without_mean

pb$without_mean

pm

pc

ggsave("../../Figures/illu_diabete_diff_weight.pdf", plot=ph$without_mean, height=h, width=w)
ggsave("../../Figures/illu_diabete_is_depressed.pdf", plot=pb$without_mean, height=h, width=w)
ggsave("../../Figures/illu_diabete_feels_sleepy.pdf", plot=pm, height=h, width=w)
ggsave("../../Figures/illu_diabete_n_meals.pdf", plot=pc, height=h, width=w)

Scénario moyennes identiques

Une série de simulations sous H0 (pas de différence entre les deux groupes), pour observer l’amplitude de différence entre les deux moyennes estimées.

for (i in 1:5){
    dh0 <- tibble(group = rep(c("control", "treatment"), each=n_per_arm)) %>%
            mutate(diff_weight = c(rnorm(n_per_arm, mean=0, sd=4), rnorm(n_per_arm, mean=0, sd=4)))

    p <- get_histograms(dh0)
    ggsave(paste("../../Figures/data_diff_weight_h0_",i,".pdf",sep=""),
           plot = p$with_mean,
           width = w,
           height = h)
}

Scénario moyennes différentes

Une série de simulations sous H1 (il y a une différence entre les deux groupes), pour observer l’amplitude de différence entre les deux moyennes estimées.

for (i in 1:5){
    dh1 <- tibble(group = rep(c("control", "treatment"), each=n_per_arm)) %>%
            mutate(diff_weight = c(rnorm(n_per_arm, mean=0, sd=4), rnorm(n_per_arm, mean=-0.5, sd=4)))
    
    p <- get_histograms(dh1)
    ggsave(paste("../../Figures/data_diff_weight_h1_",i,".pdf",sep=""),
           plot = p$with_mean,
           width = w,
           height = h)
}

Allure d’une loi binomiale

Pour visualiser l’allure d’une loi binomiale avec 250 tirages de Bernoulli, et une probabilité de succès de 0.5.

dbinom <- tibble(x = seq(90, 160),
                 y = dbinom(x, size=n_per_arm, prob=0.5))

p_illu_binom <- dbinom %>% ggplot(aes(x=x, y=y)) +
    geom_bar(stat="identity", fill=bleuclair) +
    xlab("nombre de dépressions sur 250 patients") +
    ylab("probabilité") +
    theme_bw()

p_illu_binom_2 <- p_illu_binom +
    geom_vline(xintercept = qbinom(c(0.025, 0.975), size=250, prob=0.5), col=rose)
p_illu_binom_2

ggsave("../../Figures/allure_binomiale_250.pdf", plot=p_illu_binom, width=w, height=h)
ggsave("../../Figures/allure_binomiale_250_quantiles.pdf", plot=p_illu_binom_2, width=w, height=h)

Principe de régression linéaire

Des figures pour illustrer les résidus, la variance, la covariance, la droite de régression…

npoints <- 50 
dreg <- tibble(x = runif(npoints, 0, 10),
               haty = 2*x -5,
               y = haty + rnorm(npoints, sd=3))

preg_0 <- dreg %>% ggplot(aes(x=x, y=y)) +
    geom_point() +
    ylab("variable de réponse") +
    xlab("variable explicative") +
    theme_bw()
preg_0

preg_1 <- preg_0 +
    geom_abline(slope = 2, intercept = -5, col=rose)
preg_1

preg_2 <- preg_1 +
    geom_segment(data=dreg, aes(x=x, xend=x, y=y, yend=haty), col=bleuclair)
preg_2

preg_3 <- preg_0 +
    geom_hline(yintercept = 5, col=rose) +
    geom_segment(data=dreg, aes(x=x, xend=x, y=y), yend=5, col=bleuclair)
preg_3

preg_4 <- preg_3 +
    geom_vline(xintercept = 5, col=rose) +
    geom_segment(data=dreg, aes(x=x, yend=y, y=y), xend=5, col=bleuclair)
preg_4

ggsave("../../Figures/reglin_principe_nuage.pdf", plot=preg_0, width=w, height=h)
ggsave("../../Figures/reglin_principe_nuage_abline.pdf", plot=preg_1, width=w, height=h)
ggsave("../../Figures/reglin_principe_nuage_abline_residuals.pdf", plot=preg_2, width=w, height=h)
ggsave("../../Figures/reglin_principe_nuage_hline_variance.pdf", plot=preg_3, width=w, height=h)
ggsave("../../Figures/reglin_principe_nuage_hline_covariance.pdf", plot=preg_4, width=w, height=h)

ggsave("../../Figures/reglin_principe_2_sce.pdf", plot=plot_grid(preg_3, preg_2), width=w, height=h)
LS0tCnRpdGxlOiAiU3RhdGlzdGlxdWVzIGF1IExBUEVDIC0tIFNpbXVsYXRpb24gZGUgamV1eCBkZSBkb25uw6llcyBmaWN0aWZzIgphdXRob3I6ICJNYXJjIE1hbmNlYXUiCmRhdGU6ICIyMDI0LTAxIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IFRSVUUKICAgIHRvY19kZXB0aDogMgogICAgdG9jX2Zsb2F0OiBUUlVFCiAgICBoaWdobGlnaHQ6ICJ0YW5nbyIKICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUKLS0tCgpQb3VyIGxlcyB1dGlsaXNhdGV1cnMgZGUgUiwgb24gY29tbWVuY2UgcGFyIGNoYXJnZXIgdW4gY2VydGFpbiBub21icmUgZGUgbW9kdWxlcyBpbnTDqXJlc3NhbnRzLgoKYGBge3IgbWVzc2FnZT1GfQojIGxlIHBhY2thZ2UgbW9kZXJuZSBzdGFuZGFyZCBwb3VyIG1hbmlwdWxlciBzZXMgZG9ubsOpZXMKbGlicmFyeSh0aWR5dmVyc2UpCiMgZ3JhcGhlcyBtdWx0aXBsZXMKbGlicmFyeShjb3dwbG90KQoKIyBxdWVscXVlcyBjb3VsZXVycyBtYW51ZWxsZXMKYmxldWZvbmNlIDwtICIjM2Q1NDY4IgpibGV1Y2xhaXIgPC0gIiM1YjdjOTgiCnJvc2UgPC0gIiNmZjU1NTUiCmBgYAoKUG91ciBzZSBmb2NhbGlzZXIgc3VyIGxlcyBzdGF0cyBldCB1bmlxdWVtZW50IGxlcyBzdGF0cywgCm9uIHRyYXZhaWxsZSBzdXIgZGVzIGpldXggZGUgZG9ubsOpZXMgaWTDqWFsaXPDqXMsCnF1aSBwcsOpc2VudGVudCBsJ2F2YW50YWdlIDoKCiogZGUgbmUgcGFzIHByw6lzZW50ZXIgZGUgZG9ubsOpZXMgbWFucXVhbnRlcywKKiBkJ8OqdHJlIHBhcmZhaXRlbWVudCBiaWVuIGZvcm1hdHTDqXMgZGFucyBjaGFxdWUgY29sb25uZSwKKiBkZSBwb3V2b2lyIMOqdHJlIHBhcnRhZ8OpcyBzYW5zIHByb2Jsw6htZSBhdmVjIHRvdXRlIHBlcnNvbm5lCiAgcXVpIHNvdWhhaXRlIHJlZmFpcmUgdG91cm5lciBsZXMgdGVzdHMuCgpMJ29iamVjdGlmIGRlIGNlIGRvY3VtZW50IGVzdCBkZSByw6lhbGlzZXIgbGVzIHNpbXVsYXRpb25zLAphaW5zaSBxdWUgZGl2ZXJzZXMgcmVwcsOpc2VudGF0aW9ucyBncmFwaGlxdWVzCnF1aSBzZXJvbnQgaW5jbHVzZXMgZGFucyBsZXMgcHLDqXNlbnRhdGlvbnMuCkMnZXN0IGwnb2NjYXNpb24gZGUgcGFydGFnZXIgZGVzIG1vcmNlYXV4IGRlIGNvZGUgUgphdmVjIGRlcyBwYXJ0aWNpcGFudHMgcXVpIHNlcmFpZW50IGludMOpcmVzc8Opcy4KCgoKIyBEYXRhc2V0IDEgOiBSQ1QgZGlhYsOodGUKCiMjIERlc2NyaXB0aW9uCgpMZSBwcmVtaWVyIGpldSBkZSBkb25uw6llcyBzaW11bGUgdW4gUkNUIChSYW5kb21pemVkIENvbnRyb2xsZWQgVHJpYWwpCmF5YW50IHBvdXIgYnV0IGRlIHRlc3RlciBldCBxdWFudGlmaWVyIGwnaW1wYWN0IGQndW5lIGludGVydmVudGlvbgpkZSBjb25zZWlsIGVuIG51dHJpdGlvbiBldCBhY3Rpdml0w6kgcGh5c2lxdWUgc3VyIGRlcyBwYXRpZW50cyBkaWFiw6l0aXF1ZXMuCgpMZXMgdmFyaWFibGVzIGQnb3V0Y29tZSBzb250IG1lc3Vyw6llcyA2IG1vaXMgYXByw6hzIGxlIGTDqWJ1dCBkZSBsJ2ludGVydmVudGlvbi4KCi0gbCfDqXR1ZGUgcG9zc8OoZGUgZGV1eCBicmFzOiBgY29udHJvbGAgZXQgYHRyZWF0bWVudGAuCi0gb24gZW5yZWdpc3RyZSBkZXMgdmFyaWFibGVzIGTDqW1vZ3JhcGhpcXVlcyDDoCBsJ2luY2x1c2lvbiA6IGBzZXhgLCBgYWdlYCwgYGFnZV9ncm91cGAsIGBoZWlnaHRgLCBgd2VpZ2h0X3QwYCwgYGJtaWAsIGBibWlfY2F0ZWdvcmllc2AsIGBkaWFiZXRlc2AsIGBzdGVwc190MGAuCi0gdW5lIHZhcmlhYmxlIGQnb3V0Y29tZSBsacOpIMOgIGxhIHByaXNlIGRlIHBvaWRzIDogYGRpZmZfd2VpZ2h0YC4KLSB1bmUgdmFyaWFibGUgZCdvdXRjb21lIGxpw6kgw6AgbCdhY3Rpdml0w6kgcGh5c2lxdWUgOiBgZGlmZl9zdGVwc2AuCi0gdW5lIHZhcmlhYmxlIGQnb3V0Y29tZSDDoCB0cm9pcyBtb2RhbGl0w6lzIDogYGZlZWxzX3NsZWVweWAuCi0gdW5lIHZhcmlhYmxlIGRlIHRlbXBzIGRlIHN1aXZpIGR1IHRyYWl0ZW1lbnQgOiBgdF9jb21wbGlhbmNlYC4KLSB1bmUgdmFyaWFibGUgZCdvdXRjb21lIGRlIG5vbWJyZSBkZSByZXBhcyBhcHLDqHMgbGVzcXVlbHMgbGEgZ2x5Y8OpbWllIGVzdCBob3JzIGNpYmxlIDogYG5fbWVhbHNfZ2x5Y2VtaWFfb3V0X3RhcmdldGAuCgojIyBMb2kgZGUgc2ltdWxhdGlvbgoKUG91ciBjZXV4IHF1ZSDDp2EgaW50w6lyZXNzZSwgdm9pY2kgbGEgbG9pIGRhbnMgbGFxdWVsbGUgbGVzIGRvbm7DqWVzIHNvbnQgc2ltdWzDqWVzIDoKCmBgYHtyfQpuX3Blcl9hcm0gPC0gMjUwCiMgZGF0YSB0aGF0IGRvIG5vdCBkZXBlbmQgb24gdGhlIGdyb3VwCmQgPC0gZGF0YS5mcmFtZShpZCA9IHNlcSgxLDUwMCksCiAgICAgICAgICAgICAgICBncm91cCA9IGFzLmZhY3RvcihyZXAoYygiY29udHJvbCIsICJ0cmVhdG1lbnQiKSwgZWFjaD1uX3Blcl9hcm0pKSwKICAgICAgICAgICAgICAgIHNleCA9IHNhbXBsZShjKCJGIiwiTSIpLCBzaXplPW5fcGVyX2FybSoyLCByZXBsYWNlPVQpLAogICAgICAgICAgICAgICAgYWdlID0gcm91bmQocnVuaWYobj0yKm5fcGVyX2FybSwgbWluPTE5LCBtYXg9NzcpKSwKICAgICAgICAgICAgICAgIGhlaWdodCA9IHJvdW5kKHJub3JtKG49MipuX3Blcl9hcm0sIG1lYW49MS43LCBzZD0wLjEpLCBkaWdpdHM9MiksCiAgICAgICAgICAgICAgICB3ZWlnaHRfdDAgPSByb3VuZChybm9ybShuPTIqbl9wZXJfYXJtLCBtZWFuPTcwLCBzZD0xMCksIDIpLAoJCQkJZGlhYmV0ZXMgPSBzYW1wbGUoYygiSSIsICJJSSIpLCBzaXplPTIqbl9wZXJfYXJtLCByZXBsYWNlPVQpLAogICAgICAgICAgICAgICAgc3RlcHNfdDAgPSByb3VuZChybm9ybShuPTIqbl9wZXJfYXJtLCBtZWFuPTI1MDAsIHNkPTUwMCkpLAogICAgICAgICAgICAgICAgdF9jb21wbGlhbmNlID0gcm91bmQocmV4cChuPTIqbl9wZXJfYXJtLCByYXRlPTAuMDUpKSAgKQoKIyBkYXRhIHRoYXQgZG8gbm90IGRlcGVuZCBvbiB0aGUgZ3JvdXAgYnV0IGRlcGVuZCBvbiBwcmV2aW91c2x5IHNpbXVsYXRlZCBkYXRhCmQgPC0gZCAlPiUgCiAgICBtdXRhdGUoYWdlX2dyb3VwID0gYXMuZmFjdG9yKGlmZWxzZShhZ2UgPCA0MCwgCgkJCQkJCQkJCQkibW9pbnMgZGUgNDAiLCAKCQkJCQkJCQkJCWlmZWxzZShhZ2UgPCA2MCwgCgkJCQkJCQkJCQkJICAgImVudHJlIDQwIGV0IDYwIiwgCgkJCQkJCQkJCQkJICAgInBsdXMgZGUgNjAiKSkpLAoJCSAgIGJtaSA9IHdlaWdodF90MCAvIGhlaWdodF4yLAoJCSAgIGJtaV9ncm91cCA9IGFzLmZhY3RvcihpZmVsc2UoYm1pIDwgMTguNSwgCgkJCQkJCQkJCQkidW5kZXJ3ZWlnaHQiLCAKCQkJCQkJCQkJCWlmZWxzZShibWkgPCAyNSwgCgkJCQkJCQkJCQkJICAgIm5vcm1hbCIsIAoJCQkJCQkJCQkJCSAgIGlmZWxzZShibWkgPCAzMCwgCgkJCQkJCQkJCQkJCQkgICJvdmVyd2VpZ2h0IiwgCgkJCQkJCQkJCQkJCQkgICJvYmVzZSIpKSkpICkKCiMgcG9zc2liaWxpdGllcyBmb3IgdGhlICJmZWVsc19zbGVlcHkiIHZhcmlhYmxlCm1vZGFsaXRlcyA8LSBjKCJtb3JuaW5nIiwgImFmdGVybm9vbiIsICJhbHdheXMiKQoKZl9sb2dpc3RpYyA8LSBmdW5jdGlvbih4KXsKCXJldHVybiAoMSAvICgxICsgZXhwKC14KSkpCn0KCiMgZGF0YSB0aGF0IGRvIGRlcGVuZCBvbiB0aGUgZ3JvdXAKZCA8LSBkICU+JSBtdXRhdGUoZGlmZl93ZWlnaHQgPSBybm9ybShuPW5fcGVyX2FybSoyLCAKCQkJCQkJCQkJICBtZWFuPS0wLjA0ICogdF9jb21wbGlhbmNlICogaWZlbHNlKGdyb3VwPT0idHJlYXRtZW50IiwxLDApICsKCQkJCQkJCQkJCQlpZmVsc2UoZGlhYmV0ZXMgPT0gIkkiLCAtMC41LCAwKSArCgkJCQkJCQkJCQkJLShibWktMjApLzEwLCAKCQkJCQkJCQkJICBzZD0yLjUpLCAKCQkJCSAgZGlmZl9zdGVwcyA9IHJub3JtKG49bl9wZXJfYXJtKjIsCgkJCQkJCQkJCSBtZWFuPTI1ICogdF9jb21wbGlhbmNlICogaWZlbHNlKGdyb3VwPT0idHJlYXRtZW50IiwgMSwgMCksIAoJCQkJCQkJCQkgc2Q9NTAwKSwgCgkJCQkgIGlzX2RlcHJlc3NlZCA9IHJiaW5vbShuPTIqbl9wZXJfYXJtLAoJCQkJCQkJCQlzaXplPTEsCgkJCQkJCQkJCXByb2I9c2FwcGx5KFg9KHdlaWdodF90MC03MCkvNSArIGlmZWxzZShncm91cD09InRyZWF0bWVudCIsIC0xLCAwKSwgCgkJCQkJCQkJCQkJCUZVTj1mX2xvZ2lzdGljKSApLAogICAgICAgICAgICAgICAgICBmZWVsc19zbGVlcHkgPSBjKCBzYW1wbGUobW9kYWxpdGVzLCBuX3Blcl9hcm0sIHByb2IgPSBjKDAuMywgMC40LCAwLjMpLCByZXBsYWNlID0gVFJVRSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNhbXBsZShtb2RhbGl0ZXMsIG5fcGVyX2FybSwgcHJvYiA9IGMoMC4yLCAwLjMsIDAuNSksIHJlcGxhY2UgPSBUUlVFKSApLAogICAgICAgICAgICAgICAgICBuX21lYWxzX2dseWNlbWlhX291dF9vZl90YXJnZXQgPSBycG9pcyhuPTIqbl9wZXJfYXJtLCAKCQkJCQkJCQkJCQkJCQkgbGFtYmRhPWV4cCgoYm1pLTIwKS81KSAqIAoJCQkJCQkJCQkJCQkJCQkgaWZlbHNlKGdyb3VwPT0iY29udHJvbCIgJiBkaWFiZXRlcz09ICJJIiwgCgkJCQkJCQkJCQkJCQkJCQkJICAgOCwKCQkJCQkJCQkJCQkJCQkJCQkgICBpZmVsc2UoZ3JvdXA9PSJjb250cm9sIiAmIGRpYWJldGVzPT0iSUkiLAoJCQkJCQkJCQkJCQkJCQkJCQkJICA1LAoJCQkJCQkJCQkJCQkJCQkJCQkJICBpZmVsc2UoZ3JvdXA9PSJ0cmVhdG1lbnQiICYgZGlhYmV0ZXM9PSJJIiwgCgkJCQkJCQkJCQkJCQkJCQkJCQkJCSA0LCAKCQkJCQkJCQkJCQkJCQkJCQkJCQkJIDIpKSkpKQoKIyBsYXN0LCB0aGUgb3V0Y29tZSBhdCB0aGUgZW5kIG9mIHRoZSB0cmlhbApkIDwtIGQgJT4lIAoJbXV0YXRlKHdlaWdodF90ZiA9IHdlaWdodF90MCArIGRpZmZfd2VpZ2h0LAoJCSAgIHN0ZXBzX3RmID0gc3RlcHNfdDAgKyBkaWZmX3N0ZXBzKQpgYGAKCgojIyBFY3JpdHVyZSBkdSBkYXRhc2V0CgpPbiBlbnJlZ2lzdHJlIGxlIGRhdGFzZXQgZGFucyBsZSBmaWNoaWVyIHN1aXZhbnQgOgoKYGBge3J9CndyaXRlLmNzdihkLCAiZXR1ZGVfZmljdGl2ZV9kaWFiZXRlcy5jc3YiKQpgYGAKCgojIERhdGFzZXQgMiA6IG4gb2YgMSBzb3VyaXMKCiMjIERlc2NyaXB0aW9uCgpPbiBkaXNwb3NlIGljaSBkZSA1MCBzb3VyaXMsIGV0IG9uIHMnaW50w6lyZXNzZSDDoCBsZXVyIHN5c3TDqG1lIGNhcmRpby12YXNjdWxhaXJlLgpPbiBzb3VoYWl0ZSBxdWFudGlmaWVyIGwnZWZmZXQgZGUgcGx1c2lldXJzIGludGVydmVudGlvbnMgcXVpIHMnZWZmZWN0dWVudCBjaGFjdW5lCsOgIGwnw6ljaGVsbGUgZCd1bmUgam91cm7DqWUgOgoKMS4gdW4gcsOpZ2ltZSBhbGltZW50YWlyZSBwYXJ0aWN1bGllcgoyLiB1biBwcm9ncmFtbWUgZCdleGVyY2ljZS4KCkFwcsOocyBjaGFxdWUgaW50ZXJ2ZW50aW9uLCBvbiBlbnRyZWdpc3RyZSA6CgoqIGxhIGZyw6lxdWVuY2UgY2FyZGlhcXVlIGF1IHJlcG9zLAoqIGwnYXR0aXR1ZGUgZGUgbGEgc291cmlzIDogc29pdCAic29jaWFibGUiLCBzb2l0ICJwcm9zdHLDqWUiLAoqIGxlIG5vbWJyZSBkJ2V4dHJhc3lzdG9sZXMgZW5yZWdpc3Ryw6kgc3VyIDEwbWluIGRlIHN1aXZpLgoKRW4gcGx1cyBkZSDDp2EsIG9uIHBvc3PDqGRlIGRlcyBtZXN1cmVzIGRlIGJhc2VsaW5lIGRlIGRpZmbDqXJlbnRlcyBxdWFudGl0w6lzLApxdSdvbiB2YSBzdXBwb3NlciBmaXhlcyBkdXJhbnQgbCdleHDDqXJpZW5jZSA6CgoqIGxlIHBvaWRzIGRlIGxhIHNvdXJpcywKKiBzYSBsaWduw6llIChzYXV2YWdlIG91IEtPIHBvdXIgdW4gY2VydGFpbiBnw6huZSBkJ2ludMOpcsOqdCksCiogc2EgcXVhbnRpdMOpIGRlIENSUAoKIyMgTG9pIGRlIHNpbXVsYXRpb24KCmBgYHtyfQpuX21vdXNlIDwtIDUwCmRpZXRzIDwtIGMoImxvdyBzdWdhciIsICJoaWdoIHN1Z2FyIikKZXhlcmNpc2VzIDwtIGMoIkkiLCAiSUkiLCAiSUlJIiwgIklWIikKCiMgZGF0YSB0aGF0IGRvIG5vdCBkZXBlbmQgb24gdGhlIGdyb3VwCmQyIDwtIHRpYmJsZShpZCA9IHJlcChzZXEoMSxuX21vdXNlKSwgZWFjaD0gbGVuZ3RoKGRpZXRzKSpsZW5ndGgoZXhlcmNpc2VzKSksCgkJCWRpZXQgPSByZXAocmVwKGRpZXRzLCBlYWNoPWxlbmd0aChleGVyY2lzZXMpKSwgbl9tb3VzZSksCgkJCWV4ZXJjaXNlID0gcmVwKHJlcChleGVyY2lzZXMsIGxlbmd0aChkaWV0cykpLCBuX21vdXNlKSwKCQkJd2VpZ2h0ID0gcm91bmQocmVwKCBybm9ybShuPW5fbW91c2UsIG1lYW49MjAsIHNkPTQpLCBlYWNoPWxlbmd0aChkaWV0cykqbGVuZ3RoKGV4ZXJjaXNlcykpKSwKCQkJbGluZWFnZSA9IHJlcCggc2FtcGxlKGMoIndpbGQgdHlwZSIsICJLTyIpLCBzaXplPW5fbW91c2UsIHJlcGxhY2U9VCksIGVhY2g9bGVuZ3RoKGRpZXRzKSpsZW5ndGgoZXhlcmNpc2VzKSksCgkJCXNleCA9IHJlcCggc2FtcGxlKGMoIkYiLCAiTSIpLCBzaXplPW5fbW91c2UsIHJlcGxhY2U9VCksIGVhY2g9bGVuZ3RoKGRpZXRzKSpsZW5ndGgoZXhlcmNpc2VzKSksCgkJCWNycCA9IHJvdW5kKHJlcCggcm5vcm0obj1uX21vdXNlLCBtZWFuPTIwMDAsIHNkPTUwMCksIGVhY2g9bGVuZ3RoKGRpZXRzKSpsZW5ndGgoZXhlcmNpc2VzKSkpLAoJCQlhbGVhID0gcmVwKCBybm9ybShuPW5fbW91c2UsIG1lYW49MCwgc2Q9MSksIGVhY2g9bGVuZ3RoKGRpZXRzKSpsZW5ndGgoZXhlcmNpc2VzKSkgKQoKIyBkYXRhIHRoYXQgZG8gZGVwZW5kIG9uIHRoZSBncm91cApkMiA8LSBkMiAlPiUgbXV0YXRlKGZjID0gcm5vcm0obj1uX21vdXNlKmxlbmd0aChkaWV0cykqbGVuZ3RoKGV4ZXJjaXNlcyksIAoJCQkJCQkJIG1lYW49IDYwMCArIGFsZWEqNSArIGlmZWxzZShzZXg9PSJGIiwgMTAsIDApICsgKHdlaWdodC0yMCkqMTAgKyBpZmVsc2UoZXhlcmNpc2U9PSJJViIsIC01MCwgMCkgKyBpZmVsc2UoZGlldD09ImhpZ2ggc3VnYXIiLCA1MCwgMCkgKyBpZmVsc2UobGluZWFnZT09IktPIiwgMjAsIDApLAoJCQkJCQkJIHNkID0gNTApLAoJCQkJICBhdHRpdHVkZSA9IHJiaW5vbShuPW5fbW91c2UqbGVuZ3RoKGRpZXRzKSpsZW5ndGgoZXhlcmNpc2VzKSwKCQkJCQkJCQkJc2l6ZT0xLAoJCQkJCQkJCQlwcm9iPXNhcHBseShYPWFsZWEgKyAoY3JwLTIwMDApLzUwMCArIGlmZWxzZShkaWV0PT0iaGlnaCBzdWdhciIsIDEsIDApLCAKCQkJCQkJCQkJCQkJRlVOPWZfbG9naXN0aWMpICksCgkJCQkgIG5fZXh0cmFzeXN0b2xlID0gcnBvaXMobj1uX21vdXNlKmxlbmd0aChkaWV0cykqbGVuZ3RoKGV4ZXJjaXNlcyksCgkJCQkJCQkJCQkgbGFtYmRhPSBleHAoYWxlYSArIGlmZWxzZShkaWV0PT0iaGlnaCBzdWdhciIsIDEsIDApICsgKHdlaWdodC0yMCkvMTAgKyBpZmVsc2UobGluZWFnZT09IktPIiwgMiwwKSkgKSkKCmQyIDwtIGQyICU+JQoJbXV0YXRlKGF0dGl0dWRlID0gaWZlbHNlKGF0dGl0dWRlPT0xLCAic29jaWFibGUiLCAicHJvc3RyYXRlIikpICU+JQoJc2VsZWN0KC1hbGVhKQpgYGAKCiMjIEVjcml0dXJlIGR1IGRhdGFzZXQKCk9uIGVucmVnaXN0cmUgbGUgZGF0YXNldCBkYW5zIGxlIGZpY2hpZXIgc3VpdmFudCA6CgpgYGB7cn0Kd3JpdGUuY3N2KGQyLCAiZXR1ZGVfZmljdGl2ZV9zb3VyaXMuY3N2IikKYGBgCgoKIyBGaWd1cmVzIGRlIGxhIHByw6lzZW50YXRpb24gMQoKIyMgSGlzdG9ncmFtbWUgZGVzIGRvbm7DqWVzCgpgYGB7ciBmaWcud2lkdGg9MTB9CmdldF9iYXJwbG90cyA8LSBmdW5jdGlvbihkKXsKCglwMSA8LSBkICU+JSBnZ3Bsb3QoYWVzKHg9aXNfZGVwcmVzc2VkLCBmaWxsPWdyb3VwKSkgKwoJCWdlb21fYmFyKGFscGhhPTAuNSwgcG9zaXRpb249ImlkZW50aXR5IikgKwoJCXRoZW1lX2J3KCkgKwoJCXlsYWIoImVmZmVjdGlmIikgKyAKCQl4bGFiKCJkw6lwcmVzc2lvbiBkdSBwYXRpZW50IikgKwoJCXNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz1jKGJsZXVjbGFpciwgcm9zZSkpCgoJbSA8LSBkICU+JSAKCQlncm91cF9ieShncm91cCkgJT4lIAoJCXN1bW1hcmlzZShtZWFuPW1lYW4oaXNfZGVwcmVzc2VkKSwKCQkJCSAgY2lfbG93PXByb3AudGVzdChzdW0oaXNfZGVwcmVzc2VkKSwgbj0yNTApJGNvbmYuaW50WzFdLAoJCQkJICBjaV91cD1wcm9wLnRlc3Qoc3VtKGlzX2RlcHJlc3NlZCksIG49MjUwKSRjb25mLmludFsyXSkKCglwMiA8LSBwMSAgKwoJCWdlb21fdmxpbmUoZGF0YT1tLCBhZXMoeGludGVyY2VwdD1tZWFuLCBjb2w9Z3JvdXApKSArCgkJc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1jKGJsZXVjbGFpciwgcm9zZSkpCgoJcDMgPC0gcDIgKwoJCWdlb21fdmxpbmUoZGF0YT1tLCBhZXMoeGludGVyY2VwdD1jaV9sb3csIGNvbD1ncm91cCksIGxpbmV0eXBlPSJkYXNoZWQiKSArCgkJZ2VvbV92bGluZShkYXRhPW0sIGFlcyh4aW50ZXJjZXB0PWNpX3VwLCBjb2w9Z3JvdXApLCBsaW5ldHlwZT0iZGFzaGVkIikKCglyZXR1cm4obGlzdCh3aXRob3V0X21lYW4gPSBwMSwgd2l0aF9tZWFuID0gcDIsIHdpdGhfY2kgPSBwMykpCn0KZ2V0X2hpc3RvZ3JhbXMgPC0gZnVuY3Rpb24oZCl7CgoJcDEgPC0gZCAlPiUgZ2dwbG90KGFlcyh4PWRpZmZfd2VpZ2h0LCBmaWxsPWdyb3VwKSkgKwoJCWdlb21faGlzdG9ncmFtKGFscGhhPTAuNSwgcG9zaXRpb249ImlkZW50aXR5IiwgYmlucz0zMCkgKwoJCXRoZW1lX2J3KCkgKwoJCXlsYWIoImVmZmVjdGlmIikgKyAKCQl4bGFiKCJkaWZmw6lyZW5jZSBkZSBwb2lkcyDDoCA2IG1vaXMiKSArCgkJc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoYmxldWNsYWlyLCByb3NlKSkKCgltIDwtIGQgJT4lIAoJCWdyb3VwX2J5KGdyb3VwKSAlPiUgCgkJc3VtbWFyaXNlKG1lYW49bWVhbihkaWZmX3dlaWdodCksCgkJCQkgIGNpX2xvdz10LnRlc3QoZGlmZl93ZWlnaHQpJGNvbmYuaW50WzFdLAoJCQkJICBjaV91cD10LnRlc3QoZGlmZl93ZWlnaHQpJGNvbmYuaW50WzJdKQoKCXAyIDwtIHAxICArCgkJZ2VvbV92bGluZShkYXRhPW0sIGFlcyh4aW50ZXJjZXB0PW1lYW4sIGNvbD1ncm91cCkpICsKCQlzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPWMoYmxldWNsYWlyLCByb3NlKSkKCglwMyA8LSBwMiArCgkJZ2VvbV92bGluZShkYXRhPW0sIGFlcyh4aW50ZXJjZXB0PWNpX2xvdywgY29sPWdyb3VwKSwgbGluZXR5cGU9ImRhc2hlZCIpICsKCQlnZW9tX3ZsaW5lKGRhdGE9bSwgYWVzKHhpbnRlcmNlcHQ9Y2lfdXAsIGNvbD1ncm91cCksIGxpbmV0eXBlPSJkYXNoZWQiKQoKCXJldHVybihsaXN0KHdpdGhvdXRfbWVhbiA9IHAxLCB3aXRoX21lYW4gPSBwMiwgd2l0aF9jaSA9IHAzKSkKfQoKcGIgPC0gZ2V0X2JhcnBsb3RzKGQpCnBoIDwtIGdldF9oaXN0b2dyYW1zKGQpCgp3IDwtIDEyCmggPC0gNQoKcGxvdF9yYXcgPC0gcGxvdF9ncmlkKHBiJHdpdGhvdXRfbWVhbiwgcGgkd2l0aG91dF9tZWFuKQpwbG90X3JhdwpnZ3NhdmUoIi4uLy4uL0ZpZ3VyZXMvaWxsdV9kaWFiZXRlX3Jhd19kYXRhLnBkZiIsIHBsb3Q9cGxvdF9yYXcsIGhlaWdodD1oLCB3aWR0aD13KQoKcGxvdF9tZWFuIDwtIHBsb3RfZ3JpZChwYiR3aXRoX21lYW4sIHBoJHdpdGhfbWVhbikKcGxvdF9tZWFuCmdnc2F2ZSgiLi4vLi4vRmlndXJlcy9pbGx1X2RpYWJldGVfcmF3X2RhdGFfbWVhbi5wZGYiLCBwbG90PXBsb3RfbWVhbiwgaGVpZ2h0PWgsIHdpZHRoPXcpCgpwbG90X2NpIDwtIHBsb3RfZ3JpZChwYiR3aXRoX2NpLCBwaCR3aXRoX2NpKQpwbG90X2NpCmdnc2F2ZSgiLi4vLi4vRmlndXJlcy9pbGx1X2RpYWJldGVfcmF3X2RhdGFfY2kucGRmIiwgcGxvdD1wbG90X2NpLCBoZWlnaHQ9aCwgd2lkdGg9dykKCmdnc2F2ZSgiLi4vLi4vRmlndXJlcy9pbGx1X2RpYWJldGVfZGlmZl93ZWlnaHRfdHdvX21lYW5zLnBkZiIsIHBsb3Q9cGgkd2l0aF9tZWFuLCBoZWlnaHQ9aCwgd2lkdGg9dykKYGBgCgojIyBNZWFuLCBtZWRpYW4sIHNkLCBxdWFudGlsZXMKCmBgYHtyIHdpZHRoPTEwfQpwX3JhdyA8LSBkICU+JSBnZ3Bsb3QoYWVzKHg9ZGlmZl93ZWlnaHQpKSArCglnZW9tX2hpc3RvZ3JhbShhbHBoYT0wLjUsIHBvc2l0aW9uPSJpZGVudGl0eSIsIGJpbnM9MzAsIGZpbGw9YmxldWNsYWlyKSArCgl0aGVtZV9idygpICsKCXlsYWIoImVmZmVjdGlmIikgKyAKCXhsYWIoImRpZmbDqXJlbmNlIGRlIHBvaWRzIMOgIDYgbW9pcyIpCgptIDwtIGQgJT4lIAoJc3VtbWFyaXNlKG1lYW49bWVhbihkaWZmX3dlaWdodCksCgkJCSAgbWVkaWFuPW1lZGlhbihkaWZmX3dlaWdodCksCgkJCSAgc2Q9c2QoZGlmZl93ZWlnaHQpLAoJCQkgIHExPXF1YW50aWxlKGRpZmZfd2VpZ2h0LCBwcm9icz0wLjAyNSksCgkJCSAgcTI9cXVhbnRpbGUoZGlmZl93ZWlnaHQsIHByb2JzPTAuOTc1KSkKCnBfbWVhbiA8LSBwX3JhdyAgKwoJZ2VvbV92bGluZShkYXRhPW0sIGFlcyh4aW50ZXJjZXB0PW1lYW4pLCBjb2w9YmxldWZvbmNlKQoKcF9tZWFuX21lZGlhbiA8LSBwX21lYW4gICsKCWdlb21fdmxpbmUoZGF0YT1tLCBhZXMoeGludGVyY2VwdD1tZWRpYW4pLCBjb2w9cm9zZSkKCnBfcXVhbnRpbGVzIDwtIHBfcmF3ICsKCWdlb21fdmxpbmUoZGF0YT1tLCBhZXMoeGludGVyY2VwdD1xMSksIGNvbD1yb3NlKSArCglnZW9tX3ZsaW5lKGRhdGE9bSwgYWVzKHhpbnRlcmNlcHQ9cTIpLCBjb2w9cm9zZSkKCnBfcmF3CnBfbWVhbgpwX21lYW5fbWVkaWFuCnBfcXVhbnRpbGVzCmdnc2F2ZSgiLi4vLi4vRmlndXJlcy9pbGx1X2RpYWJldGVfaGlzdG9ncmFtLnBkZiIsIHBsb3Q9cF9yYXcsIGhlaWdodD1oLCB3aWR0aD13KQpnZ3NhdmUoIi4uLy4uL0ZpZ3VyZXMvaWxsdV9kaWFiZXRlX21lYW4ucGRmIiwgcGxvdD1wX21lYW4sIGhlaWdodD1oLCB3aWR0aD13KQpnZ3NhdmUoIi4uLy4uL0ZpZ3VyZXMvaWxsdV9kaWFiZXRlX21lZGlhbi5wZGYiLCBwbG90PXBfbWVhbl9tZWRpYW4sIGhlaWdodD1oLCB3aWR0aD13KQpnZ3NhdmUoIi4uLy4uL0ZpZ3VyZXMvaWxsdV9kaWFiZXRlX3F1YW50aWxlcy5wZGYiLCBwbG90PXBfcXVhbnRpbGVzLCBoZWlnaHQ9aCwgd2lkdGg9dykKYGBgCgpQb3VyIGxhIHZhcmlhYmxlIGB0X2NvbXBsaWFuY2VgIGF2ZWMgc2EgbW95ZW5uZSBldCBzYSBtw6lkaWFuZSA6CgpgYGB7ciB3aWR0aD0xMH0KcHRfcmF3IDwtIGQgJT4lIGdncGxvdChhZXMoeD10X2NvbXBsaWFuY2UpKSArCglnZW9tX2hpc3RvZ3JhbShhbHBoYT0wLjUsIHBvc2l0aW9uPSJpZGVudGl0eSIsIGJpbnM9MzAsIGZpbGw9YmxldWNsYWlyKSArCgl0aGVtZV9idygpICsKCXlsYWIoImVmZmVjdGlmIikgKyAKCXhsYWIoInRlbXBzIGRlIHN1aXZpIGRlIGwnaW50ZXJ2ZW50aW9uIikKCm10IDwtIGQgJT4lIAoJc3VtbWFyaXNlKG1lYW49bWVhbih0X2NvbXBsaWFuY2UpLAoJCQkgIG1lZGlhbj1tZWRpYW4odF9jb21wbGlhbmNlKSwKCQkJICBzZD1zZCh0X2NvbXBsaWFuY2UpLAoJCQkgIHExPXF1YW50aWxlKHRfY29tcGxpYW5jZSwgcHJvYnM9MC4wMjUpLAoJCQkgIHEyPXF1YW50aWxlKHRfY29tcGxpYW5jZSwgcHJvYnM9MC45NzUpKQoKcHRfbWVhbiA8LSBwdF9yYXcgICsKCWdlb21fdmxpbmUoZGF0YT1tdCwgYWVzKHhpbnRlcmNlcHQ9bWVhbiksIGNvbD1ibGV1Zm9uY2UpCgpwdF9tZWFuX21lZGlhbiA8LSBwdF9tZWFuICArCglnZW9tX3ZsaW5lKGRhdGE9bXQsIGFlcyh4aW50ZXJjZXB0PW1lZGlhbiksIGNvbD1yb3NlKQoKcHRfcXVhbnRpbGVzIDwtIHB0X3JhdyArCglnZW9tX3ZsaW5lKGRhdGE9bXQsIGFlcyh4aW50ZXJjZXB0PXExKSwgY29sPXJvc2UpICsKCWdlb21fdmxpbmUoZGF0YT1tdCwgYWVzKHhpbnRlcmNlcHQ9cTIpLCBjb2w9cm9zZSkKCnB0X3JhdwpwdF9tZWFuCnB0X21lYW5fbWVkaWFuCnB0X3F1YW50aWxlcwpnZ3NhdmUoIi4uLy4uL0ZpZ3VyZXMvaWxsdV9kaWFiZXRlX3RfaGlzdG9ncmFtLnBkZiIsIHBsb3Q9cHRfcmF3LCBoZWlnaHQ9aCwgd2lkdGg9dykKZ2dzYXZlKCIuLi8uLi9GaWd1cmVzL2lsbHVfZGlhYmV0ZV90X21lYW4ucGRmIiwgcGxvdD1wdF9tZWFuLCBoZWlnaHQ9aCwgd2lkdGg9dykKZ2dzYXZlKCIuLi8uLi9GaWd1cmVzL2lsbHVfZGlhYmV0ZV90X21lZGlhbi5wZGYiLCBwbG90PXB0X21lYW5fbWVkaWFuLCBoZWlnaHQ9aCwgd2lkdGg9dykKZ2dzYXZlKCIuLi8uLi9GaWd1cmVzL2lsbHVfZGlhYmV0ZV90X3F1YW50aWxlcy5wZGYiLCBwbG90PXB0X3F1YW50aWxlcywgaGVpZ2h0PWgsIHdpZHRoPXcpCmBgYAoKCiMjIFByw6lzZW50YXRpb25zIGRlcyBvdXRjb21lcyBkJ2ludMOpcsOqdAoKTGVzIGdyYXBoZXMgcGVybWV0dGFudCBkZSBkb25uZXIgdW4gYXBlcsOndSBkZSBkaWZmw6lyZW50ZXMgdmFyaWFibGVzCmRlIHR5cGVzIGRpZmbDqXJlbnRzIDoKCmBgYHtyIGZpZy53aWR0aD0xMH0KcG0gPC0gZCAlPiUgZ2dwbG90KGFlcyh4PWZlZWxzX3NsZWVweSwgZmlsbD1ncm91cCkpICsKCWdlb21fYmFyKHBvc2l0aW9uPSJkb2RnZSIsIGFscGhhPTAuNSkgKwoJc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoYmxldWNsYWlyLCByb3NlKSkgKwoJdGhlbWVfYncoKQoKcGMgPC0gZCAlPiUgZ2dwbG90KGFlcyh4PW5fbWVhbHNfZ2x5Y2VtaWFfb3V0X29mX3RhcmdldCwgZmlsbD1ncm91cCkpICsKCWdlb21faGlzdG9ncmFtKGFscGhhPTAuNSwgcG9zaXRpb249ImlkZW50aXR5IiwgYmlucz0zMCkgKwoJc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzPWMoYmxldWNsYWlyLCByb3NlKSkgKwoJdGhlbWVfYncoKQoKcGgkd2l0aG91dF9tZWFuCnBiJHdpdGhvdXRfbWVhbgpwbQpwYwoKZ2dzYXZlKCIuLi8uLi9GaWd1cmVzL2lsbHVfZGlhYmV0ZV9kaWZmX3dlaWdodC5wZGYiLCBwbG90PXBoJHdpdGhvdXRfbWVhbiwgaGVpZ2h0PWgsIHdpZHRoPXcpCmdnc2F2ZSgiLi4vLi4vRmlndXJlcy9pbGx1X2RpYWJldGVfaXNfZGVwcmVzc2VkLnBkZiIsIHBsb3Q9cGIkd2l0aG91dF9tZWFuLCBoZWlnaHQ9aCwgd2lkdGg9dykKZ2dzYXZlKCIuLi8uLi9GaWd1cmVzL2lsbHVfZGlhYmV0ZV9mZWVsc19zbGVlcHkucGRmIiwgcGxvdD1wbSwgaGVpZ2h0PWgsIHdpZHRoPXcpCmdnc2F2ZSgiLi4vLi4vRmlndXJlcy9pbGx1X2RpYWJldGVfbl9tZWFscy5wZGYiLCBwbG90PXBjLCBoZWlnaHQ9aCwgd2lkdGg9dykKYGBgCgoKIyMgU2PDqW5hcmlvIG1veWVubmVzIGlkZW50aXF1ZXMKClVuZSBzw6lyaWUgZGUgc2ltdWxhdGlvbnMgc291cyBIMCAocGFzIGRlIGRpZmbDqXJlbmNlIGVudHJlIGxlcyBkZXV4IGdyb3VwZXMpLApwb3VyIG9ic2VydmVyIGwnYW1wbGl0dWRlIGRlIGRpZmbDqXJlbmNlIGVudHJlIGxlcyBkZXV4IG1veWVubmVzIGVzdGltw6llcy4KCmBgYHtyfQpmb3IgKGkgaW4gMTo1KXsKCWRoMCA8LSB0aWJibGUoZ3JvdXAgPSByZXAoYygiY29udHJvbCIsICJ0cmVhdG1lbnQiKSwgZWFjaD1uX3Blcl9hcm0pKSAlPiUKCQkJbXV0YXRlKGRpZmZfd2VpZ2h0ID0gYyhybm9ybShuX3Blcl9hcm0sIG1lYW49MCwgc2Q9NCksIHJub3JtKG5fcGVyX2FybSwgbWVhbj0wLCBzZD00KSkpCgoJcCA8LSBnZXRfaGlzdG9ncmFtcyhkaDApCglnZ3NhdmUocGFzdGUoIi4uLy4uL0ZpZ3VyZXMvZGF0YV9kaWZmX3dlaWdodF9oMF8iLGksIi5wZGYiLHNlcD0iIiksCgkJICAgcGxvdCA9IHAkd2l0aF9tZWFuLAoJCSAgIHdpZHRoID0gdywKCQkgICBoZWlnaHQgPSBoKQp9CmBgYAoKIyMgU2PDqW5hcmlvIG1veWVubmVzIGRpZmbDqXJlbnRlcwoKVW5lIHPDqXJpZSBkZSBzaW11bGF0aW9ucyBzb3VzIEgxIChpbCB5IGEgdW5lIGRpZmbDqXJlbmNlIGVudHJlIGxlcyBkZXV4IGdyb3VwZXMpLApwb3VyIG9ic2VydmVyIGwnYW1wbGl0dWRlIGRlIGRpZmbDqXJlbmNlIGVudHJlIGxlcyBkZXV4IG1veWVubmVzIGVzdGltw6llcy4KCmBgYHtyfQpmb3IgKGkgaW4gMTo1KXsKCWRoMSA8LSB0aWJibGUoZ3JvdXAgPSByZXAoYygiY29udHJvbCIsICJ0cmVhdG1lbnQiKSwgZWFjaD1uX3Blcl9hcm0pKSAlPiUKCQkJbXV0YXRlKGRpZmZfd2VpZ2h0ID0gYyhybm9ybShuX3Blcl9hcm0sIG1lYW49MCwgc2Q9NCksIHJub3JtKG5fcGVyX2FybSwgbWVhbj0tMC41LCBzZD00KSkpCgkKCXAgPC0gZ2V0X2hpc3RvZ3JhbXMoZGgxKQoJZ2dzYXZlKHBhc3RlKCIuLi8uLi9GaWd1cmVzL2RhdGFfZGlmZl93ZWlnaHRfaDFfIixpLCIucGRmIixzZXA9IiIpLAoJCSAgIHBsb3QgPSBwJHdpdGhfbWVhbiwKCQkgICB3aWR0aCA9IHcsCgkJICAgaGVpZ2h0ID0gaCkKfQpgYGAKCiMjIEFsbHVyZSBkJ3VuZSBsb2kgYmlub21pYWxlCgpQb3VyIHZpc3VhbGlzZXIgbCdhbGx1cmUgZCd1bmUgbG9pIGJpbm9taWFsZSBhdmVjIDI1MCB0aXJhZ2VzIGRlIEJlcm5vdWxsaSwKZXQgdW5lIHByb2JhYmlsaXTDqSBkZSBzdWNjw6hzIGRlIDAuNS4KCmBgYHtyIGZpZy53aWR0aD0xMH0KZGJpbm9tIDwtIHRpYmJsZSh4ID0gc2VxKDkwLCAxNjApLAoJCQkJIHkgPSBkYmlub20oeCwgc2l6ZT1uX3Blcl9hcm0sIHByb2I9MC41KSkKCnBfaWxsdV9iaW5vbSA8LSBkYmlub20gJT4lIGdncGxvdChhZXMoeD14LCB5PXkpKSArCglnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGZpbGw9YmxldWNsYWlyKSArCgl4bGFiKCJub21icmUgZGUgZMOpcHJlc3Npb25zIHN1ciAyNTAgcGF0aWVudHMiKSArCgl5bGFiKCJwcm9iYWJpbGl0w6kiKSArCgl0aGVtZV9idygpCgpwX2lsbHVfYmlub21fMiA8LSBwX2lsbHVfYmlub20gKwoJZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gcWJpbm9tKGMoMC4wMjUsIDAuOTc1KSwgc2l6ZT0yNTAsIHByb2I9MC41KSwgY29sPXJvc2UpCnBfaWxsdV9iaW5vbV8yCgpnZ3NhdmUoIi4uLy4uL0ZpZ3VyZXMvYWxsdXJlX2Jpbm9taWFsZV8yNTAucGRmIiwgcGxvdD1wX2lsbHVfYmlub20sIHdpZHRoPXcsIGhlaWdodD1oKQpnZ3NhdmUoIi4uLy4uL0ZpZ3VyZXMvYWxsdXJlX2Jpbm9taWFsZV8yNTBfcXVhbnRpbGVzLnBkZiIsIHBsb3Q9cF9pbGx1X2Jpbm9tXzIsIHdpZHRoPXcsIGhlaWdodD1oKQpgYGAKCgojIyBQcmluY2lwZSBkZSByw6lncmVzc2lvbiBsaW7DqWFpcmUKCkRlcyBmaWd1cmVzIHBvdXIgaWxsdXN0cmVyIGxlcyByw6lzaWR1cywgbGEgdmFyaWFuY2UsIGxhIGNvdmFyaWFuY2UsCmxhIGRyb2l0ZSBkZSByw6lncmVzc2lvbi4uLgoKYGBge3IgZmlnLndpZHRoPTEwfQpucG9pbnRzIDwtIDUwIApkcmVnIDwtIHRpYmJsZSh4ID0gcnVuaWYobnBvaW50cywgMCwgMTApLAoJCQkgICBoYXR5ID0gMip4IC01LAoJCQkgICB5ID0gaGF0eSArIHJub3JtKG5wb2ludHMsIHNkPTMpKQoKcHJlZ18wIDwtIGRyZWcgJT4lIGdncGxvdChhZXMoeD14LCB5PXkpKSArCglnZW9tX3BvaW50KCkgKwoJeWxhYigidmFyaWFibGUgZGUgcsOpcG9uc2UiKSArCgl4bGFiKCJ2YXJpYWJsZSBleHBsaWNhdGl2ZSIpICsKCXRoZW1lX2J3KCkKcHJlZ18wCgpwcmVnXzEgPC0gcHJlZ18wICsKCWdlb21fYWJsaW5lKHNsb3BlID0gMiwgaW50ZXJjZXB0ID0gLTUsIGNvbD1yb3NlKQpwcmVnXzEKCnByZWdfMiA8LSBwcmVnXzEgKwoJZ2VvbV9zZWdtZW50KGRhdGE9ZHJlZywgYWVzKHg9eCwgeGVuZD14LCB5PXksIHllbmQ9aGF0eSksIGNvbD1ibGV1Y2xhaXIpCnByZWdfMgoKcHJlZ18zIDwtIHByZWdfMCArCglnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSA1LCBjb2w9cm9zZSkgKwoJZ2VvbV9zZWdtZW50KGRhdGE9ZHJlZywgYWVzKHg9eCwgeGVuZD14LCB5PXkpLCB5ZW5kPTUsIGNvbD1ibGV1Y2xhaXIpCnByZWdfMwoKcHJlZ180IDwtIHByZWdfMyArCglnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA1LCBjb2w9cm9zZSkgKwoJZ2VvbV9zZWdtZW50KGRhdGE9ZHJlZywgYWVzKHg9eCwgeWVuZD15LCB5PXkpLCB4ZW5kPTUsIGNvbD1ibGV1Y2xhaXIpCnByZWdfNAoKZ2dzYXZlKCIuLi8uLi9GaWd1cmVzL3JlZ2xpbl9wcmluY2lwZV9udWFnZS5wZGYiLCBwbG90PXByZWdfMCwgd2lkdGg9dywgaGVpZ2h0PWgpCmdnc2F2ZSgiLi4vLi4vRmlndXJlcy9yZWdsaW5fcHJpbmNpcGVfbnVhZ2VfYWJsaW5lLnBkZiIsIHBsb3Q9cHJlZ18xLCB3aWR0aD13LCBoZWlnaHQ9aCkKZ2dzYXZlKCIuLi8uLi9GaWd1cmVzL3JlZ2xpbl9wcmluY2lwZV9udWFnZV9hYmxpbmVfcmVzaWR1YWxzLnBkZiIsIHBsb3Q9cHJlZ18yLCB3aWR0aD13LCBoZWlnaHQ9aCkKZ2dzYXZlKCIuLi8uLi9GaWd1cmVzL3JlZ2xpbl9wcmluY2lwZV9udWFnZV9obGluZV92YXJpYW5jZS5wZGYiLCBwbG90PXByZWdfMywgd2lkdGg9dywgaGVpZ2h0PWgpCmdnc2F2ZSgiLi4vLi4vRmlndXJlcy9yZWdsaW5fcHJpbmNpcGVfbnVhZ2VfaGxpbmVfY292YXJpYW5jZS5wZGYiLCBwbG90PXByZWdfNCwgd2lkdGg9dywgaGVpZ2h0PWgpCgpnZ3NhdmUoIi4uLy4uL0ZpZ3VyZXMvcmVnbGluX3ByaW5jaXBlXzJfc2NlLnBkZiIsIHBsb3Q9cGxvdF9ncmlkKHByZWdfMywgcHJlZ18yKSwgd2lkdGg9dywgaGVpZ2h0PWgpCmBgYAo=