Exercice 1 – Simulation et estimation d’une fonction de régression

Dans ce premier exercice, on souhaite étudier une relation de la forme \(Y_i = f(x_i) + \varepsilon_i\) en simulant des observations pour lesquelles la variance du bruit dépend de la valeur de \(x\). On prendra \(f(x) = x\) et un bruit de variance croissante via un facteur \(w_i = 1 + x_i/2\). Les instructions sont données dans le texte de l’énoncé. On suit la démarche pas à pas et on commente les résultats obtenus.

Génération des données

On commence par créer un jeu de données contenant les variables x, w et Y. La variable x est une suite allant de 1 à 30 par pas de 0,5. On fixe un graine (set.seed) pour rendre les résultats reproductibles. On définit ensuite w = 1 + x/2, on génère un bruit Z gaussien standard et on calcule Y = x + w * Z. Enfin, on rassemble tout dans un data.frame tab.

set.seed(42)                      # graine pour la reproductibilité
x <- seq(1, 30, by = 0.5)         # points de 1 à 30 avec un pas de 0,5
w <- 1 + x/2                      # facteur de variance croissante
Z <- rnorm(length(x))            # bruit gaussien de moyenne 0 et variance 1
Y <- x + w * Z                    # modèle Y = f(x) + w * Z avec f(x)=x
tab <- data.frame(x = x, w = w, Y = Y)
head(tab)

Observation : on observe que la variable w augmente avec x, ce qui signifie que l’ampleur du bruit est faible pour les petites valeurs de x et plus forte pour les grandes valeurs. Autrement dit, on crée intentionnellement une hétéroscédasticité, c’est‑à‑dire une variance non constante de Y en fonction de x.

Nuage de points (x, Y)

Nous représentons ensuite le nuage de points \((x_i, Y_i)\) pour visualiser la relation entre la variable explicative x et la variable réponse Y. On s’attend à voir un alignement approximatif le long de la droite de pente 1, mais avec une dispersion qui augmente lorsque x croît.

plot(tab$x, tab$Y,
     pch = 16, cex = 0.7, col = "steelblue",
     xlab = "x", ylab = "Y",
     main = "Exercice 1 – Nuage de points (x, Y)")
grid()

Observation : le nuage de points s’élargit clairement lorsque x augmente : les points situés à droite sont beaucoup plus dispersés que ceux situés à gauche. Cela reflète bien la variance hétérogène introduite par w. On voit également que la tendance centrale suit une droite approximative de pente 1.

Estimation par moindres carrés pondérés

Pour estimer la fonction de régression \(f\), on peut effectuer une régression linéaire pondérée. Comme on sait que la variance conditionnelle de Y vaut \(\mathrm{Var}(Y_i\mid x_i) = w_i^2\), un choix naturel est d’utiliser des poids égaux à \(1 / w_i^2\), c’est‑à‑dire l’inverse de la variance. De cette manière, les observations les moins bruitées (petit w) ont plus d’influence que les observations très bruitées.

# Ajustement par moindres carrés pondérés
reg1 <- lm(Y ~ x, data = tab, weights = 1/(w^2))
summary(reg1)

Call:
lm(formula = Y ~ x, data = tab, weights = 1/(w^2))

Weighted Residuals:
    Min      1Q  Median      3Q     Max 
-2.7710 -0.5259 -0.0963  0.7512  2.1938 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   1.4869     0.9604   1.548    0.127    
x             0.8320     0.1207   6.891 4.83e-09 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.145 on 57 degrees of freedom
Multiple R-squared:  0.4545,    Adjusted R-squared:  0.4449 
F-statistic: 47.49 on 1 and 57 DF,  p-value: 4.832e-09
# Tracé de la droite ajustée
plot(tab$x, tab$Y,
     pch = 16, cex = 0.7, col = "steelblue",
     xlab = "x", ylab = "Y",
     main = "Exercice 1 – MCP pondérés : droite ajustée")
grid()
abline(reg1, col = "darkorange", lwd = 2)

Observation : l’estimation linéaire pondérée affiche des coefficients proches de 1 pour la pente et une ordonnée à l’origine faible (proche de 0). La droite ajustée suit bien la tendance centrale des données. Le fait de pondérer corrige l’influence excessive des points très bruités situés à droite et donne un ajustement plus stable que si l’on utilisait une régression ordinaire.

Estimation par polynômes locaux (loess)

Une alternative non paramétrique consiste à utiliser un estimateur local par polynômes (fonction loess), qui ajuste une courbe lisse sans supposer une forme linéaire globale. On utilise ici le lissage par défaut de loess.

# Ajustement par polynômes locaux
locpoly <- loess(Y ~ x, data = tab)
fitted_values <- fitted(locpoly)

# Tracé du nuage et de la courbe lissée
plot(tab$x, tab$Y,
     pch = 16, cex = 0.7, col = "steelblue",
     xlab = "x", ylab = "Y",
     main = "Exercice 1 – Loess : courbe ajustée")
grid()
lines(tab$x, fitted_values, col = "forestgreen", lwd = 2)

Observation : la courbe issue du lissage local suit globalement la tendance linéaire mais on peut observer de petites ondulations dues au bruit. Le lissage présente l’avantage de ne pas imposer une forme particulière pour \(f\), tout en s’adaptant localement aux données. Si la fenêtre (span) était plus petite, la courbe serait plus ondulée ; si elle était plus grande, la courbe se rapprocherait d’une droite.

Exercice 2 – Estimation non paramétrique d’une série chronologique

On considère une fonction périodique \(f(t) = 0{,}5 + 0{,}4 \sin(2\pi t)\) et une série chronologique bruitée \(Y_t = f(t) + \varepsilon_t\)\(\varepsilon_t\sim \mathcal{N}(0, 0{,}25)\). L’objectif est d’illustrer différentes méthodes de lissage et de comparer leur comportement.

Représentation de f(t) et de Y_t sur [0, 1]

On crée une grille fine t allant de 0 à 1 par pas de 0,01, on calcule la fonction f et on simule un bruit gaussien de variance 0,25 (écart‑type 0,5). On trace ensuite les courbes.

set.seed(123)
t <- seq(0, 1, by = 0.01)
f <- 0.5 + 0.4 * sin(2 * pi * t)
epsilon <- rnorm(length(t), mean = 0, sd = 0.5)
Y <- f + epsilon

plot(t, f, type = "l", lwd = 2, col = "darkgreen",
     ylim = range(c(f, Y)),
     xlab = "t", ylab = "Valeur",
     main = "Exercice 2 – Fonction f(t) et série Y_t")
lines(t, Y, col = "steelblue", lty = 2)
legend("topright", legend = c("f(t)", "Y_t"),
       col = c("darkgreen", "steelblue"), lty = c(1, 2), lwd = 2)

Observation : la fonction \(f\) est sinusoïdale et régulière. La courbe Y_t suit cette tendance périodique mais les observations sont perturbées par un bruit gaussien, d’où une dispersion autour de f(t).

Génération d’une grille T1 et d’une réalisation Y sur T1

On crée maintenant une grille T1 sur \([0, 1]\) avec un pas de 0,05 et on simule une réalisation de Y sur ces points uniquement. Cette grille plus grossière sert de base aux estimations.

T1 <- seq(0, 1, by = 0.05)
fT1 <- 0.5 + 0.4 * sin(2 * pi * T1)
set.seed(456)
Y_T1 <- fT1 + rnorm(length(T1), mean = 0, sd = 0.5)
data_ex2 <- data.frame(T1 = T1, Y = Y_T1)
head(data_ex2)

Observation : la grille T1 comporte 21 points. La série Y générée sur ces points présente des écarts parfois importants par rapport à la fonction sous‑jacente f(T1) en raison du bruit.

Lissage par polynômes locaux (locpoly)

On utilise la fonction locpoly du package KernSmooth pour estimer la fonction de régression par polynômes locaux d’ordre 1. Le paramètre bandwidth \(h\) contrôle la largeur de la fenêtre de lissage. Pour h = 0.05, on obtient l’estimation suivante :

library(KernSmooth)
h <- 0.05
reg_locpoly <- locpoly(T1, Y_T1, bandwidth = h)

plot(T1, Y_T1, pch = 16, cex = 0.7, col = "steelblue",
     xlab = "t", ylab = "Y",
     main = paste("Exercice 2 – locpoly avec h =", h))
grid()
lines(reg_locpoly, col = "darkorange", lwd = 2)
lines(T1, fT1, col = "darkgreen", lwd = 2, lty = 2)
legend("topright", legend = c("Y_t", "locpoly", "f(t)"),
       col = c("steelblue", "darkorange", "darkgreen"),
       lty = c(NA, 1, 2), pch = c(16, NA, NA), lwd = c(NA, 2, 2))

Observation : pour h = 0.05, l’estimateur locpoly suit de près la structure sinusoïdale de la fonction réelle. Les variations rapides sont bien capturées, bien que le bruit puisse induire quelques petites oscillations. Le choix d’un h trop petit rendrait l’estimation trop « courbée », tandis qu’un h trop grand lisserait exagérément la courbe.

Effet du paramètre de lissage h

On fait varier la valeur de h pour observer l’influence du lissage. Ci‑dessous, on considère h = 0.15, soit une fenêtre trois fois plus large. Cela produit un lissage plus agressif.

h2 <- 0.15
reg_locpoly2 <- locpoly(T1, Y_T1, bandwidth = h2)

plot(T1, Y_T1, pch = 16, cex = 0.7, col = "steelblue",
     xlab = "t", ylab = "Y",
     main = paste("Exercice 2 – locpoly avec h =", h2))
grid()
lines(reg_locpoly2, col = "darkorange", lwd = 2)
lines(T1, fT1, col = "darkgreen", lwd = 2, lty = 2)
legend("topright", legend = c("Y_t", "locpoly", "f(t)"),
       col = c("steelblue", "darkorange", "darkgreen"),
       lty = c(NA, 1, 2), pch = c(16, NA, NA), lwd = c(NA, 2, 2))

Observation : avec h = 0.15, l’estimation est beaucoup plus lisse. La courbe suit toujours la tendance globale, mais les sommets et creux de la sinusoïde sont atténués. Un h trop élevé peut masquer des structures réelles des données, tandis qu’un h trop faible peut sur‑ajuster le bruit.

Lissage à noyau (ksmooth)

La fonction ksmooth fournit un estimateur de Nadaraya–Watson basé sur un noyau (par défaut gaussien). On teste deux choix de bande de lissage : bandwidth = 0.2 et bandwidth = 0.05 (note : la valeur h choisie pour locpoly précédemment). On représente ces estimations en rouge et bleu.

library(stats)

estim1 <- ksmooth(T1, Y_T1, x.points = T1, bandwidth = 0.2, kernel = "normal")
estim2 <- ksmooth(T1, Y_T1, x.points = T1, bandwidth = 0.05, kernel = "normal")

plot(T1, Y_T1, pch = 16, cex = 0.7, col = "steelblue",
     xlab = "t", ylab = "Y",
     main = "Exercice 2 – Comparaison ksmooth")
grid()
lines(estim1$x, estim1$y, col = "red", lwd = 2)
lines(estim2$x, estim2$y, col = "blue", lwd = 2)
lines(T1, fT1, col = "darkgreen", lwd = 2, lty = 2)
legend("topright", legend = c("Y_t", "ksmooth h=0.2", "ksmooth h=0.05", "f(t)"),
       col = c("steelblue", "red", "blue", "darkgreen"),
       lty = c(NA, 1, 1, 2), pch = c(16, NA, NA, NA), lwd = c(NA, 2, 2, 2))

Observation : l’estimateur à noyau avec h = 0.2 (rouge) est plus lisse que celui avec h = 0.05 (bleu). La courbe rouge suit la tendance globale en atténuant fortement les fluctuations locales, tandis que la courbe bleue réagit davantage aux variations point par point. Le choix du paramètre bandwidth est donc crucial pour ajuster le degré de lissage.

Influence de différentes valeurs de h

On peut tester des valeurs de bande beaucoup plus petites ou plus grandes. Ci‑dessous, on compare h = 0.5 (lissage très large) et h = 0.1 (lissage modéré).

estim_h05 <- ksmooth(T1, Y_T1, x.points = T1, bandwidth = 0.5, kernel = "normal")
estim_h01 <- ksmooth(T1, Y_T1, x.points = T1, bandwidth = 0.1, kernel = "normal")

plot(T1, Y_T1, pch = 16, cex = 0.7, col = "steelblue",
     xlab = "t", ylab = "Y",
     main = "Exercice 2 – Effet de h sur ksmooth")
grid()
lines(estim_h05$x, estim_h05$y, col = "purple", lwd = 2)
lines(estim_h01$x, estim_h01$y, col = "darkorange", lwd = 2)
lines(T1, fT1, col = "darkgreen", lwd = 2, lty = 2)
legend("topright", legend = c("Y_t", "ksmooth h=0.5", "ksmooth h=0.1", "f(t)"),
       col = c("steelblue", "purple", "darkorange", "darkgreen"),
       lty = c(NA, 1, 1, 2), pch = c(16, NA, NA, NA), lwd = c(NA, 2, 2, 2))

Observation : avec h = 0.5, la courbe (violette) est très lisse et ne suit plus vraiment la forme sinusoïdale : le lissage écrase la structure périodique. Avec h = 0.1 (orange), l’ajustement est intermédiaire : on voit la sinusoïde, mais l’estimation est moins oscillante qu’avec h = 0.05.

Comparaison des qualités prédictives

Pour comparer les méthodes locpoly et ksmooth, on calcule l’erreur quadratique moyenne (EQM) entre les estimations et la fonction réelle f(T1). Une EQM plus faible indique une meilleure précision prédictive. On considère locpoly avec h=0.05 et ksmooth avec les mêmes deux paramètres.

# Prévisions locpoly
pred_loc <- approx(x = reg_locpoly$x, y = reg_locpoly$y, xout = T1)$y
# Prévisions ksmooth
pred_k05 <- estim1$y
pred_k005 <- estim2$y

# Calculs de l'EQM par rapport à fT1
EQM_loc <- mean((pred_loc - fT1)^2)
EQM_k05 <- mean((pred_k05 - fT1)^2)
EQM_k005 <- mean((pred_k005 - fT1)^2)

EQM <- data.frame(Méthode = c("locpoly h=0.05", "ksmooth h=0.2", "ksmooth h=0.05"),
                  EQM = c(EQM_loc, EQM_k05, EQM_k005))
EQM

Observation : en général, l’estimateur locpoly et l’estimateur ksmooth avec un paramètre de lissage raisonnable donnent des EQM du même ordre. Une bande trop large augmente l’EQM car la courbe lissée s’éloigne de la structure réelle, alors qu’une bande trop petite peut sur‑ajuster le bruit et également augmenter l’EQM. Le choix optimal de h se situe entre ces extrêmes.

Exercice 3 – Estimation non paramétrique à partir d’un petit jeu de données

On cherche à estimer la fonction de régression \(f\) par deux estimateurs de Nadaraya–Watson : l’un utilisant un noyau uniforme (fenêtre rectangulaire) et l’autre un noyau gaussien, tous deux avec une fenêtre de lissage h = 1.5. On comparera les courbes obtenues et on analysera les résidus.

On dispose d’un petit échantillon de cinq observations :

x3 <- c(-2, -1, -1, 1, 0)
y3 <- c( 2,  1,  4, 2, -2)
data3 <- data.frame(x = x3, y = y3)
data3

Estimateur de Nadaraya–Watson avec noyau uniforme

Pour calculer l’estimateur avec noyau uniforme, on utilise la fonction ksmooth avec l’option kernel = "box". On calcule les prédictions sur une grille fine de valeurs de x allant de min(x3) à max(x3).

grid_x <- seq(min(x3) - 0.5, max(x3) + 0.5, length.out = 200)
nw_uniform <- ksmooth(x3, y3, kernel = "box", bandwidth = 1.5, x.points = grid_x)

plot(x3, y3, pch = 16, cex = 1.2, col = "steelblue",
     xlab = "x", ylab = "y",
     main = "Exercice 3 – Nadaraya–Watson noyau uniforme (h = 1,5)")
grid()
lines(nw_uniform$x, nw_uniform$y, col = "darkorange", lwd = 2)

Estimateur de Nadaraya–Watson avec noyau gaussien

On répète l’opération avec un noyau gaussien (kernel = "normal") et la même bande de lissage h = 1.5.

nw_gaussian <- ksmooth(x3, y3, kernel = "normal", bandwidth = 1.5, x.points = grid_x)

plot(x3, y3, pch = 16, cex = 1.2, col = "steelblue",
     xlab = "x", ylab = "y",
     main = "Exercice 3 – Nadaraya–Watson noyau gaussien (h = 1,5)")
grid()
lines(nw_gaussian$x, nw_gaussian$y, col = "forestgreen", lwd = 2)

Comparaison et analyse des résidus

On superpose les deux estimateurs sur le même graphique et on calcule les résidus (observations moins valeurs ajustées) en chacun des points x3. Pour le noyau uniforme, on obtient les valeurs ajustées en interpolant la courbe lissée sur les points x3.

# Superposition des deux courbes
plot(x3, y3, pch = 16, cex = 1.2, col = "steelblue",
     xlab = "x", ylab = "y",
     main = "Exercice 3 – Comparaison des estimateurs")
grid()
lines(nw_uniform$x, nw_uniform$y, col = "darkorange", lwd = 2)
lines(nw_gaussian$x, nw_gaussian$y, col = "forestgreen", lwd = 2)
legend("topright", legend = c("Observations", "NW uniforme", "NW gaussien"),
       col = c("steelblue", "darkorange", "forestgreen"),
       lty = c(NA, 1, 1), pch = c(16, NA, NA), lwd = c(NA, 2, 2))


# Interpolation pour calculer les valeurs ajustées aux points x3
fit_uniform <- approx(x = nw_uniform$x, y = nw_uniform$y, xout = x3)$y
fit_gaussian <- approx(x = nw_gaussian$x, y = nw_gaussian$y, xout = x3)$y

resid_uniform <- y3 - fit_uniform
resid_gaussian <- y3 - fit_gaussian

data.frame(x = x3, y = y3, NW_uniform = fit_uniform, NW_gaussian = fit_gaussian,
           resid_uniform = resid_uniform, resid_gaussian = resid_gaussian)

Observation : les deux estimateurs donnent des courbes relativement similaires, mais le noyau gaussien produit une courbe plus lisse et moins anguleuse que le noyau uniforme. Les résidus montrent que certains points (notamment x = -1 avec y = 4) sont mal ajustés, car leur valeur est plus éloignée des autres observations. Dans un échantillon aussi petit, le choix du noyau et de la bande de lissage influe fortement sur l’aspect de la courbe.

Exercice 4 – Analyse de la vitesse d’un camion

On dispose des mesures de la vitesse instantanée V (en km/h) d’un camion en fonction du temps t (en secondes). Les données sont présentées dans un tableau et reproduites ci‑dessous.

t4 <- c(0, 5, 10, 15, 20, 30, 40, 50, 60, 70, 80, 90, 95, 100, 105, 110, 115, 120)
V4 <- c(0, 10, 20, 30, 40, 52, 60, 63, 65, 63, 58, 50, 45, 38, 29, 20, 12, 3)
camion <- data.frame(t = t4, V = V4)
camion

Représentation graphique et ajustement linéaire

On trace d’abord la vitesse en fonction du temps, puis on ajuste une droite de tendance au moyen d’une régression linéaire lm(V ~ t). On observe si une relation linéaire est plausible.

# Ajustement linéaire
reg_camion_lin <- lm(V ~ t, data = camion)

plot(camion$t, camion$V,
     pch = 16, cex = 1.0, col = "steelblue",
     xlab = "Temps (s)", ylab = "Vitesse (km/h)",
     main = "Exercice 4 – Vitesse du camion et ajustement linéaire")
grid()
abline(reg_camion_lin, col = "darkorange", lwd = 2)

summary(reg_camion_lin)

Call:
lm(formula = V ~ t, data = camion)

Residuals:
    Min      1Q  Median      3Q     Max 
-35.797 -16.838   2.468  19.876  28.468 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)   
(Intercept) 35.79735    9.72052   3.683  0.00201 **
t            0.01224    0.13139   0.093  0.92693   
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 22.55 on 16 degrees of freedom
Multiple R-squared:  0.0005421, Adjusted R-squared:  -0.06192 
F-statistic: 0.008679 on 1 and 16 DF,  p-value: 0.9269

Observation : la vitesse augmente d’abord, atteint un maximum autour de 65 km/h vers 60 secondes, puis décroît progressivement. La droite de tendance linéaire ne reproduit pas cette forme en cloche : elle sur‑estime la vitesse au début et la sous‑estime après 60 s. La relation n’est donc pas vraiment linéaire.

Ajustement quadratique : modèle \(v(t) = θ_0 + θ_1 t + θ_2 t^2\)

Puisque la relation semble parabolique, on ajuste un modèle de régression quadratique. On calcule les coefficients \(\theta_0\), \(\theta_1\) et \(\theta_2\) via lm(V ~ t + I(t^2)). On trace ensuite la courbe quadratique obtenue.

# Ajustement quadratique
reg_camion_quad <- lm(V ~ t + I(t^2), data = camion)
summary(reg_camion_quad)

Call:
lm(formula = V ~ t + I(t^2), data = camion)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.4624 -1.0905 -0.3722  0.7969  2.8613 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)  1.1053526  0.8143340   1.357    0.195    
t            2.1613244  0.0346062  62.455   <2e-16 ***
I(t^2)      -0.0179830  0.0002813 -63.925   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.408 on 15 degrees of freedom
Multiple R-squared:  0.9963,    Adjusted R-squared:  0.9959 
F-statistic:  2044 on 2 and 15 DF,  p-value: < 2.2e-16
# Courbe quadratique
t_seq <- seq(min(camion$t), max(camion$t), length.out = 200)
pred_quad <- predict(reg_camion_quad, newdata = data.frame(t = t_seq))

plot(camion$t, camion$V,
     pch = 16, cex = 1.0, col = "steelblue",
     xlab = "Temps (s)", ylab = "Vitesse (km/h)",
     main = "Exercice 4 – Ajustement quadratique de la vitesse")
grid()
lines(t_seq, pred_quad, col = "forestgreen", lwd = 2)

Observation : la courbe quadratique épouse bien la trajectoire en cloche de la vitesse : accélération jusqu’à environ 60 s, puis décélération. Les coefficients obtenus reflètent cette forme : un terme quadratique négatif rend la fonction concave. Le modèle quadratique améliore nettement l’ajustement par rapport à la droite.

Ajustement quadratique via poly

On peut retrouver le même résultat en utilisant la fonction poly(t, deg = 2) qui génère des polynômes orthogonaux de degré 2. L’ajustement se fait ensuite avec lm(V ~ polynome).

reg_camion_poly <- lm(V ~ poly(t, 2), data = camion)
summary(reg_camion_poly)

Call:
lm(formula = V ~ poly(t, 2), data = camion)

Residuals:
    Min      1Q  Median      3Q     Max 
-1.4624 -1.0905 -0.3722  0.7969  2.8613 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   36.556      0.332 110.115   <2e-16 ***
poly(t, 2)1    2.101      1.409   1.492    0.157    
poly(t, 2)2  -90.035      1.409 -63.925   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.408 on 15 degrees of freedom
Multiple R-squared:  0.9963,    Adjusted R-squared:  0.9959 
F-statistic:  2044 on 2 and 15 DF,  p-value: < 2.2e-16
# Comparaison des prédictions
t_seq <- seq(min(camion$t), max(camion$t), length.out = 200)
pred_poly <- predict(reg_camion_poly, newdata = data.frame(t = t_seq))

plot(camion$t, camion$V,
     pch = 16, cex = 1.0, col = "steelblue",
     xlab = "Temps (s)", ylab = "Vitesse (km/h)",
     main = "Exercice 4 – Ajustement polynomial (degré 2)")
grid()
lines(t_seq, pred_poly, col = "darkred", lwd = 2)

Observation : l’utilisation de poly(t, deg=2) donne un ajustement identique à celui obtenu avec I(t^2). La syntaxe est plus compacte et permet de généraliser facilement à des polynômes de degré supérieur. Les courbes se superposent parfaitement.

Exercice 5 – Régression non paramétrique sur des données de verre

On s’intéresse à des mesures portant sur des fragments de verre. On veut expliquer l’indice de réfraction RI en fonction de la teneur en aluminium Al. Les données proviennent du jeu de données Glass du package mlbench.

Importation et représentation des données

On charge le jeu de données, on conserve les colonnes d’intérêt et on réalise un nuage de points. On appelle verres le data.frame contenant les variables Al et RI.

library(mlbench)
data(Glass)

# Création du data.frame verres
verres <- data.frame(Al = Glass$Al, RI = Glass$RI)
head(verres)

plot(verres$Al, verres$RI,
     pch = 16, cex = 0.7, col = "steelblue",
     xlab = "Aluminium (%)", ylab = "Indice de réfraction (RI)",
     main = "Exercice 5 – Nuage de points : RI en fonction de Al")
grid()

Observation : la relation entre Al et RI semble non linéaire : l’indice de réfraction diminue pour des teneurs en aluminium faibles, puis augmente légèrement. On explore maintenant des méthodes non paramétriques pour estimer cette relation.

Estimateur par noyau avec la fonction npreg

Le package np fournit des outils d’estimation non paramétrique multivariée. On commence par déterminer la largeur de bande optimale à l’aide de npregbw, puis on ajuste le modèle avec npreg. On peut visualiser la courbe ajustée et les intervalles de confiance asymptotiques.

library(np)
attach(verres)                # attache des variables Al et RI
h_np <- npregbw(RI ~ Al)      # estimation automatique de la bande

Multistart 1 of 1 |
Multistart 1 of 1 |
Multistart 1 of 1 |
Multistart 1 of 1 /
Multistart 1 of 1 |
Multistart 1 of 1 |
                   
summary(h_np)

Regression Data (214 observations, 1 variable(s)):

Regression Type: Local-Constant
Bandwidth Selection Method: Least Squares Cross-Validation
Formula: RI ~ Al
Bandwidth Type: Fixed
Objective Function Value: 6.854372e-06 (achieved on multistart 1)

Exp. Var. Name: Al Bandwidth: 0.132813 Scale Factor: 1.247614

Continuous Kernel Type: Second-Order Gaussian
No. Continuous Explanatory Vars.: 1
Estimation Time: 0.022 seconds
verres_np <- npreg(h_np)      # ajustement non paramétrique
plot(verres_np, plot.errors.method = "asymptotic")

detach(verres)

Observation : l’estimation indique une courbe lisse qui suit la forme générale du nuage de points. Les bandes d’erreur asymptotiques montrent où l’incertitude est plus grande, notamment aux extrémités où les données sont moins nombreuses.

Estimateur à noyau via ksmooth

Une méthode plus simple consiste à utiliser ksmooth avec un noyau gaussien et une bande de lissage fixée, ici bandwidth = 5. On trace cette courbe pour la comparer à celle obtenue avec npreg.

smooth_ks <- ksmooth(verres$Al, verres$RI, kernel = "normal", bandwidth = 5, x.points = verres$Al)

plot(verres$Al, verres$RI,
     pch = 16, cex = 0.7, col = "steelblue",
     xlab = "Aluminium (%)", ylab = "RI",
     main = "Exercice 5 – ksmooth (bandwidth = 5)")
grid()
lines(smooth_ks$x, smooth_ks$y, col = "darkorange", lwd = 2)

Observation : la courbe ksmooth est relativement lisse et suit bien la tendance moyenne. Cependant, le choix de la bande bandwidth = 5 peut être arbitraire ; une bande trop large sur‑lisse les données, tandis qu’une bande trop petite conduit à une courbe plus ondulée.

Comparaison avec un ajustement par polynômes locaux (loess)

Enfin, on ajuste la relation RI ~ Al par des polynômes locaux via loess avec un paramètre span = 0.5 et une famille gaussienne. On trace la courbe obtenue et on la compare à celle de ksmooth.

verres_loess <- loess(RI ~ Al, data = verres, span = 0.5, family = "gaussian")
pred_loess <- predict(verres_loess, newdata = data.frame(Al = verres$Al))

plot(verres$Al, verres$RI,
     pch = 16, cex = 0.7, col = "steelblue",
     xlab = "Aluminium (%)", ylab = "RI",
     main = "Exercice 5 – Comparaison des ajustements")
grid()
lines(smooth_ks$x, smooth_ks$y, col = "darkorange", lwd = 2)
lines(verres$Al, pred_loess, col = "forestgreen", lwd = 2)
legend("topright", legend = c("ksmooth", "loess"),
       col = c("darkorange", "forestgreen"), lty = 1, lwd = 2)

Observation : la courbe loess (vert) suit étroitement la forme du nuage et peut capter des variations locales grâce à un paramètre span modéré. La courbe ksmooth (orange) est légèrement plus lisse. Selon la valeur de span ou de bandwidth, l’une ou l’autre méthode peut offrir un meilleur compromis entre biais et variance.

FIN

LS0tCnRpdGxlOiAiVFA1LTA1REVDIC0gUmVncmVzc2lvbiBub24gbGluw6lhaXJlIgphdXRob3I6ICJCZW4gSW1hbiBBQkRBTExBSCIKc3VidGl0bGU6IEVuY2FkcsOpIHBhciBTb2x5bSBNQU5PVS1BQkkKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyBFeGVyY2ljZSZuYnNwOzEg4oCTIFNpbXVsYXRpb24gZXQgZXN0aW1hdGlvbiBk4oCZdW5lIGZvbmN0aW9uIGRlIHLDqWdyZXNzaW9uCgpEYW5zIGNlIHByZW1pZXIgZXhlcmNpY2UsIG9uIHNvdWhhaXRlIMOpdHVkaWVyIHVuZSByZWxhdGlvbiBkZSBsYSBmb3JtZSBcKFlfaSA9IGYoeF9pKSArIFx2YXJlcHNpbG9uX2lcKSBlbiBzaW11bGFudCBkZXMgb2JzZXJ2YXRpb25zIHBvdXIgbGVzcXVlbGxlcyBsYSB2YXJpYW5jZSBkdSBicnVpdCBkw6lwZW5kIGRlIGxhIHZhbGV1ciBkZSBcKHhcKS4gIE9uIHByZW5kcmEKXChmKHgpID0geFwpIGV0IHVuIGJydWl0IGRlIHZhcmlhbmNlIGNyb2lzc2FudGUgdmlhIHVuIGZhY3RldXIgXCh3X2kgPSAxICsgeF9pLzJcKS4gIExlcyBpbnN0cnVjdGlvbnMgc29udCBkb25uw6llcyBkYW5zIGxlIHRleHRlIGRlIGzigJnDqW5vbmPDqS4gIE9uIHN1aXQgbGEgZMOpbWFyY2hlIHBhcyDDoCBwYXMgZXQgb24gY29tbWVudGUgbGVzIHLDqXN1bHRhdHMgb2J0ZW51cy4KCiMjIEfDqW7DqXJhdGlvbiBkZXMgZG9ubsOpZXMKCk9uIGNvbW1lbmNlIHBhciBjcsOpZXIgdW4gamV1IGRlIGRvbm7DqWVzIGNvbnRlbmFudCBsZXMgdmFyaWFibGVzIGB4YCwgYHdgIGV0IGBZYC4gIExhIHZhcmlhYmxlIGB4YCBlc3QgdW5lIHN1aXRlIGFsbGFudCBkZSAxIMOgIDMwIHBhciBwYXMgZGUgMCw1LiAgT24gZml4ZSB1biBncmFpbmUgKGBzZXQuc2VlZGApIHBvdXIgcmVuZHJlIGxlcyByw6lzdWx0YXRzIHJlcHJvZHVjdGlibGVzLiAgT24gZMOpZmluaXQgZW5zdWl0ZSBgdyA9IDEgKyB4LzJgLCBvbiBnw6luw6hyZSB1biBicnVpdCBgWmAgZ2F1c3NpZW4gc3RhbmRhcmQgZXQgb24gY2FsY3VsZSBgWSA9IHggKyB3ICogWmAuICBFbmZpbiwgb24gcmFzc2VtYmxlIHRvdXQgZGFucyB1biBkYXRhLmZyYW1lIGB0YWJgLgoKYGBge3IgZXgxLWRhdGEsIG1lc3NhZ2U9RkFMU0V9CnNldC5zZWVkKDQyKSAgICAgICAgICAgICAgICAgICAgICAjIGdyYWluZSBwb3VyIGxhIHJlcHJvZHVjdGliaWxpdMOpCnggPC0gc2VxKDEsIDMwLCBieSA9IDAuNSkgICAgICAgICAjIHBvaW50cyBkZSAxIMOgIDMwIGF2ZWMgdW4gcGFzIGRlIDAsNQp3IDwtIDEgKyB4LzIgICAgICAgICAgICAgICAgICAgICAgIyBmYWN0ZXVyIGRlIHZhcmlhbmNlIGNyb2lzc2FudGUKWiA8LSBybm9ybShsZW5ndGgoeCkpICAgICAgICAgICAgIyBicnVpdCBnYXVzc2llbiBkZSBtb3llbm5lIDAgZXQgdmFyaWFuY2UgMQpZIDwtIHggKyB3ICogWiAgICAgICAgICAgICAgICAgICAgIyBtb2TDqGxlIFkgPSBmKHgpICsgdyAqIFogYXZlYyBmKHgpPXgKdGFiIDwtIGRhdGEuZnJhbWUoeCA9IHgsIHcgPSB3LCBZID0gWSkKaGVhZCh0YWIpCmBgYAoKKipPYnNlcnZhdGlvbiA6Kiogb24gb2JzZXJ2ZSBxdWUgbGEgdmFyaWFibGUgYHdgIGF1Z21lbnRlIGF2ZWMgYHhgLCBjZSBxdWkgc2lnbmlmaWUgcXVlIGzigJlhbXBsZXVyIGR1IGJydWl0IGVzdCBmYWlibGUgcG91ciBsZXMgcGV0aXRlcyB2YWxldXJzIGRlIGB4YCBldCBwbHVzIGZvcnRlIHBvdXIgbGVzIGdyYW5kZXMgdmFsZXVycy4gIEF1dHJlbWVudCBkaXQsIG9uIGNyw6llIGludGVudGlvbm5lbGxlbWVudCB1bmUgaMOpdMOpcm9zY8OpZGFzdGljaXTDqSwgY+KAmWVzdOKAkcOg4oCRZGlyZSB1bmUgdmFyaWFuY2Ugbm9uIGNvbnN0YW50ZSBkZSBgWWAgZW4gZm9uY3Rpb24gZGUgYHhgLgoKIyMgTnVhZ2UgZGUgcG9pbnRzIGAoeCwgWSlgCgpOb3VzIHJlcHLDqXNlbnRvbnMgZW5zdWl0ZSBsZSBudWFnZSBkZSBwb2ludHMgXCgoeF9pLCBZX2kpXCkgcG91ciB2aXN1YWxpc2VyIGxhIHJlbGF0aW9uIGVudHJlIGxhIHZhcmlhYmxlIGV4cGxpY2F0aXZlIGB4YCBldCBsYSB2YXJpYWJsZSByw6lwb25zZSBgWWAuICBPbiBz4oCZYXR0ZW5kIMOgIHZvaXIgdW4gYWxpZ25lbWVudCBhcHByb3hpbWF0aWYgbGUgbG9uZyBkZSBsYSBkcm9pdGUgZGUgcGVudGUgMSwgbWFpcyBhdmVjIHVuZSBkaXNwZXJzaW9uIHF1aSBhdWdtZW50ZSBsb3JzcXVlIGB4YCBjcm/DrnQuCgpgYGB7ciBleDEtc2NhdHRlciwgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9NH0KcGxvdCh0YWIkeCwgdGFiJFksCiAgICAgcGNoID0gMTYsIGNleCA9IDAuNywgY29sID0gInN0ZWVsYmx1ZSIsCiAgICAgeGxhYiA9ICJ4IiwgeWxhYiA9ICJZIiwKICAgICBtYWluID0gIkV4ZXJjaWNlIDEg4oCTIE51YWdlIGRlIHBvaW50cyAoeCwgWSkiKQpncmlkKCkKYGBgCgoqKk9ic2VydmF0aW9uIDoqKiBsZSBudWFnZSBkZSBwb2ludHMgc+KAmcOpbGFyZ2l0IGNsYWlyZW1lbnQgbG9yc3F1ZSBgeGAgYXVnbWVudGUgOiBsZXMgcG9pbnRzIHNpdHXDqXMgw6AgZHJvaXRlIHNvbnQgYmVhdWNvdXAgcGx1cyBkaXNwZXJzw6lzIHF1ZSBjZXV4IHNpdHXDqXMgw6AgZ2F1Y2hlLiAgQ2VsYSByZWZsw6h0ZSBiaWVuIGxhIHZhcmlhbmNlIGjDqXTDqXJvZ8OobmUgaW50cm9kdWl0ZSBwYXIgYHdgLiAgT24gdm9pdCDDqWdhbGVtZW50IHF1ZSBsYSB0ZW5kYW5jZSBjZW50cmFsZSBzdWl0IHVuZSBkcm9pdGUgYXBwcm94aW1hdGl2ZSBkZSBwZW50ZSAxLgoKIyMgRXN0aW1hdGlvbiBwYXIgbW9pbmRyZXMgY2FycsOpcyBwb25kw6lyw6lzCgpQb3VyIGVzdGltZXIgbGEgZm9uY3Rpb24gZGUgcsOpZ3Jlc3Npb24gXChmXCksIG9uIHBldXQgZWZmZWN0dWVyIHVuZSByw6lncmVzc2lvbiBsaW7DqWFpcmUgcG9uZMOpcsOpZS4gIENvbW1lIG9uIHNhaXQgcXVlIGxhIHZhcmlhbmNlIGNvbmRpdGlvbm5lbGxlIGRlIGBZYCB2YXV0IFwoXG1hdGhybXtWYXJ9KFlfaVxtaWQgeF9pKSA9IHdfaV4yXCksIHVuIGNob2l4IG5hdHVyZWwgZXN0IGTigJl1dGlsaXNlciBkZXMgcG9pZHMgw6lnYXV4IMOgIFwoMSAvIHdfaV4yXCksIGPigJllc3TigJHDoOKAkWRpcmUgbOKAmWludmVyc2UgZGUgbGEgdmFyaWFuY2UuICBEZSBjZXR0ZSBtYW5pw6hyZSwgbGVzIG9ic2VydmF0aW9ucyBsZXMgbW9pbnMgYnJ1aXTDqWVzIChwZXRpdCBgd2ApIG9udCBwbHVzIGTigJlpbmZsdWVuY2UgcXVlIGxlcyBvYnNlcnZhdGlvbnMgdHLDqHMgYnJ1aXTDqWVzLgoKYGBge3IgZXgxLXdscywgbWVzc2FnZT1GQUxTRX0KIyBBanVzdGVtZW50IHBhciBtb2luZHJlcyBjYXJyw6lzIHBvbmTDqXLDqXMKcmVnMSA8LSBsbShZIH4geCwgZGF0YSA9IHRhYiwgd2VpZ2h0cyA9IDEvKHdeMikpCnN1bW1hcnkocmVnMSkKCiMgVHJhY8OpIGRlIGxhIGRyb2l0ZSBhanVzdMOpZQpwbG90KHRhYiR4LCB0YWIkWSwKICAgICBwY2ggPSAxNiwgY2V4ID0gMC43LCBjb2wgPSAic3RlZWxibHVlIiwKICAgICB4bGFiID0gIngiLCB5bGFiID0gIlkiLAogICAgIG1haW4gPSAiRXhlcmNpY2UgMSDigJMgTUNQIHBvbmTDqXLDqXMgOiBkcm9pdGUgYWp1c3TDqWUiKQpncmlkKCkKYWJsaW5lKHJlZzEsIGNvbCA9ICJkYXJrb3JhbmdlIiwgbHdkID0gMikKYGBgCgoqKk9ic2VydmF0aW9uIDoqKiBs4oCZZXN0aW1hdGlvbiBsaW7DqWFpcmUgcG9uZMOpcsOpZSBhZmZpY2hlIGRlcyBjb2VmZmljaWVudHMgcHJvY2hlcyBkZSAxIHBvdXIgbGEgcGVudGUgZXQgdW5lIG9yZG9ubsOpZSDDoCBs4oCZb3JpZ2luZSBmYWlibGUgKHByb2NoZSBkZSAwKS4gIExhIGRyb2l0ZSBhanVzdMOpZSBzdWl0IGJpZW4gbGEgdGVuZGFuY2UgY2VudHJhbGUgZGVzIGRvbm7DqWVzLiAgTGUgZmFpdCBkZSBwb25kw6lyZXIgY29ycmlnZSBs4oCZaW5mbHVlbmNlIGV4Y2Vzc2l2ZSBkZXMgcG9pbnRzIHRyw6hzIGJydWl0w6lzIHNpdHXDqXMgw6AgZHJvaXRlIGV0IGRvbm5lIHVuIGFqdXN0ZW1lbnQgcGx1cyBzdGFibGUgcXVlIHNpIGzigJlvbiB1dGlsaXNhaXQgdW5lIHLDqWdyZXNzaW9uIG9yZGluYWlyZS4KCiMjIEVzdGltYXRpb24gcGFyIHBvbHluw7RtZXMgbG9jYXV4IChsb2VzcykKClVuZSBhbHRlcm5hdGl2ZSBub24gcGFyYW3DqXRyaXF1ZSBjb25zaXN0ZSDDoCB1dGlsaXNlciB1biBlc3RpbWF0ZXVyIGxvY2FsIHBhciBwb2x5bsO0bWVzIChmb25jdGlvbiBgbG9lc3NgKSwgcXVpIGFqdXN0ZSB1bmUgY291cmJlIGxpc3NlIHNhbnMgc3VwcG9zZXIgdW5lIGZvcm1lIGxpbsOpYWlyZSBnbG9iYWxlLiAgT24gdXRpbGlzZSBpY2kgbGUgbGlzc2FnZSBwYXIgZMOpZmF1dCBkZSBgbG9lc3NgLgoKYGBge3IgZXgxLWxvZXNzLCBtZXNzYWdlPUZBTFNFfQojIEFqdXN0ZW1lbnQgcGFyIHBvbHluw7RtZXMgbG9jYXV4CmxvY3BvbHkgPC0gbG9lc3MoWSB+IHgsIGRhdGEgPSB0YWIpCmZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKGxvY3BvbHkpCgojIFRyYWPDqSBkdSBudWFnZSBldCBkZSBsYSBjb3VyYmUgbGlzc8OpZQpwbG90KHRhYiR4LCB0YWIkWSwKICAgICBwY2ggPSAxNiwgY2V4ID0gMC43LCBjb2wgPSAic3RlZWxibHVlIiwKICAgICB4bGFiID0gIngiLCB5bGFiID0gIlkiLAogICAgIG1haW4gPSAiRXhlcmNpY2UgMSDigJMgTG9lc3MgOiBjb3VyYmUgYWp1c3TDqWUiKQpncmlkKCkKbGluZXModGFiJHgsIGZpdHRlZF92YWx1ZXMsIGNvbCA9ICJmb3Jlc3RncmVlbiIsIGx3ZCA9IDIpCmBgYAoKKipPYnNlcnZhdGlvbiA6KiogbGEgY291cmJlIGlzc3VlIGR1IGxpc3NhZ2UgbG9jYWwgc3VpdCBnbG9iYWxlbWVudCBsYSB0ZW5kYW5jZSBsaW7DqWFpcmUgbWFpcyBvbiBwZXV0IG9ic2VydmVyIGRlIHBldGl0ZXMgb25kdWxhdGlvbnMgZHVlcyBhdSBicnVpdC4gIExlIGxpc3NhZ2UgcHLDqXNlbnRlIGzigJlhdmFudGFnZSBkZSBuZSBwYXMgaW1wb3NlciB1bmUgZm9ybWUgcGFydGljdWxpw6hyZSBwb3VyIFwoZlwpLCB0b3V0IGVuIHPigJlhZGFwdGFudCBsb2NhbGVtZW50IGF1eCBkb25uw6llcy4gIFNpIGxhIGZlbsOqdHJlIChzcGFuKSDDqXRhaXQgcGx1cyBwZXRpdGUsIGxhIGNvdXJiZSBzZXJhaXQgcGx1cyBvbmR1bMOpZSA7IHNpIGVsbGUgw6l0YWl0IHBsdXMgZ3JhbmRlLCBsYSBjb3VyYmUgc2UgcmFwcHJvY2hlcmFpdCBk4oCZdW5lIGRyb2l0ZS4KCiMgRXhlcmNpY2UmbmJzcDsyIOKAkyBFc3RpbWF0aW9uIG5vbiBwYXJhbcOpdHJpcXVlIGTigJl1bmUgc8OpcmllIGNocm9ub2xvZ2lxdWUKCk9uIGNvbnNpZMOocmUgdW5lIGZvbmN0aW9uIHDDqXJpb2RpcXVlIFwoZih0KSA9IDB7LH01ICsgMHssfTQgXHNpbigyXHBpIHQpXCkgZXQgdW5lIHPDqXJpZSBjaHJvbm9sb2dpcXVlIGJydWl0w6llIFwoWV90ID0gZih0KSArIFx2YXJlcHNpbG9uX3RcKSBvw7kgXChcdmFyZXBzaWxvbl90XHNpbSBcbWF0aGNhbHtOfSgwLCAweyx9MjUpXCkuICBM4oCZb2JqZWN0aWYgZXN0IGTigJlpbGx1c3RyZXIgZGlmZsOpcmVudGVzIG3DqXRob2RlcyBkZSBsaXNzYWdlIGV0IGRlIGNvbXBhcmVyIGxldXIgY29tcG9ydGVtZW50LgoKIyMgUmVwcsOpc2VudGF0aW9uIGRlIGBmKHQpYCBldCBkZSBgWV90YCBzdXIgWzAsIDFdCgpPbiBjcsOpZSB1bmUgZ3JpbGxlIGZpbmUgYHRgIGFsbGFudCBkZSAwIMOgIDEgcGFyIHBhcyBkZSAwLDAxLCBvbiBjYWxjdWxlIGxhIGZvbmN0aW9uIGBmYCBldCBvbiBzaW11bGUgdW4gYnJ1aXQgZ2F1c3NpZW4gZGUgdmFyaWFuY2UgMCwyNSAow6ljYXJ04oCRdHlwZSAwLDUpLiAgT24gdHJhY2UgZW5zdWl0ZSBsZXMgY291cmJlcy4KCmBgYHtyIGV4Mi1mdCwgbWVzc2FnZT1GQUxTRSwgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9NH0Kc2V0LnNlZWQoMTIzKQp0IDwtIHNlcSgwLCAxLCBieSA9IDAuMDEpCmYgPC0gMC41ICsgMC40ICogc2luKDIgKiBwaSAqIHQpCmVwc2lsb24gPC0gcm5vcm0obGVuZ3RoKHQpLCBtZWFuID0gMCwgc2QgPSAwLjUpClkgPC0gZiArIGVwc2lsb24KCnBsb3QodCwgZiwgdHlwZSA9ICJsIiwgbHdkID0gMiwgY29sID0gImRhcmtncmVlbiIsCiAgICAgeWxpbSA9IHJhbmdlKGMoZiwgWSkpLAogICAgIHhsYWIgPSAidCIsIHlsYWIgPSAiVmFsZXVyIiwKICAgICBtYWluID0gIkV4ZXJjaWNlIDIg4oCTIEZvbmN0aW9uIGYodCkgZXQgc8OpcmllIFlfdCIpCmxpbmVzKHQsIFksIGNvbCA9ICJzdGVlbGJsdWUiLCBsdHkgPSAyKQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kID0gYygiZih0KSIsICJZX3QiKSwKICAgICAgIGNvbCA9IGMoImRhcmtncmVlbiIsICJzdGVlbGJsdWUiKSwgbHR5ID0gYygxLCAyKSwgbHdkID0gMikKYGBgCgoqKk9ic2VydmF0aW9uIDoqKiBsYSBmb25jdGlvbiBcKGZcKSBlc3Qgc2ludXNvw69kYWxlIGV0IHLDqWd1bGnDqHJlLiAgTGEgY291cmJlIGBZX3RgIHN1aXQgY2V0dGUgdGVuZGFuY2UgcMOpcmlvZGlxdWUgbWFpcyBsZXMgb2JzZXJ2YXRpb25zIHNvbnQgcGVydHVyYsOpZXMgcGFyIHVuIGJydWl0IGdhdXNzaWVuLCBk4oCZb8O5IHVuZSBkaXNwZXJzaW9uIGF1dG91ciBkZSBgZih0KWAuCgojIyBHw6luw6lyYXRpb24gZOKAmXVuZSBncmlsbGUgYFQxYCBldCBk4oCZdW5lIHLDqWFsaXNhdGlvbiBgWWAgc3VyIGBUMWAKCk9uIGNyw6llIG1haW50ZW5hbnQgdW5lIGdyaWxsZSBgVDFgIHN1ciBcKFswLCAxXVwpIGF2ZWMgdW4gcGFzIGRlIDAsMDUgZXQgb24gc2ltdWxlIHVuZSByw6lhbGlzYXRpb24gZGUgYFlgIHN1ciBjZXMgcG9pbnRzIHVuaXF1ZW1lbnQuICBDZXR0ZSBncmlsbGUgcGx1cyBncm9zc2nDqHJlIHNlcnQgZGUgYmFzZSBhdXggZXN0aW1hdGlvbnMuCgpgYGB7ciBleDItZ3JpZCwgbWVzc2FnZT1GQUxTRX0KVDEgPC0gc2VxKDAsIDEsIGJ5ID0gMC4wNSkKZlQxIDwtIDAuNSArIDAuNCAqIHNpbigyICogcGkgKiBUMSkKc2V0LnNlZWQoNDU2KQpZX1QxIDwtIGZUMSArIHJub3JtKGxlbmd0aChUMSksIG1lYW4gPSAwLCBzZCA9IDAuNSkKZGF0YV9leDIgPC0gZGF0YS5mcmFtZShUMSA9IFQxLCBZID0gWV9UMSkKaGVhZChkYXRhX2V4MikKYGBgCgoqKk9ic2VydmF0aW9uIDoqKiBsYSBncmlsbGUgYFQxYCBjb21wb3J0ZSAyMSBwb2ludHMuICBMYSBzw6lyaWUgYFlgIGfDqW7DqXLDqWUgc3VyIGNlcyBwb2ludHMgcHLDqXNlbnRlIGRlcyDDqWNhcnRzIHBhcmZvaXMgaW1wb3J0YW50cyBwYXIgcmFwcG9ydCDDoCBsYSBmb25jdGlvbiBzb3Vz4oCRamFjZW50ZSBgZihUMSlgIGVuIHJhaXNvbiBkdSBicnVpdC4KCiMjIExpc3NhZ2UgcGFyIHBvbHluw7RtZXMgbG9jYXV4IChgbG9jcG9seWApCgpPbiB1dGlsaXNlIGxhIGZvbmN0aW9uIGBsb2Nwb2x5YCBkdSBwYWNrYWdlICoqS2VyblNtb290aCoqIHBvdXIgZXN0aW1lciBsYSBmb25jdGlvbiBkZSByw6lncmVzc2lvbiBwYXIgcG9seW7DtG1lcyBsb2NhdXggZOKAmW9yZHJlIDEuICBMZSBwYXJhbcOodHJlIGBiYW5kd2lkdGhgIFwoaFwpIGNvbnRyw7RsZSBsYSBsYXJnZXVyIGRlIGxhIGZlbsOqdHJlIGRlIGxpc3NhZ2UuICBQb3VyIGBoID0gMC4wNWAsIG9uIG9idGllbnQgbOKAmWVzdGltYXRpb24gc3VpdmFudGUgOgoKYGBge3IgZXgyLWxvY3BvbHkxLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KEtlcm5TbW9vdGgpCmggPC0gMC4wNQpyZWdfbG9jcG9seSA8LSBsb2Nwb2x5KFQxLCBZX1QxLCBiYW5kd2lkdGggPSBoKQoKcGxvdChUMSwgWV9UMSwgcGNoID0gMTYsIGNleCA9IDAuNywgY29sID0gInN0ZWVsYmx1ZSIsCiAgICAgeGxhYiA9ICJ0IiwgeWxhYiA9ICJZIiwKICAgICBtYWluID0gcGFzdGUoIkV4ZXJjaWNlIDIg4oCTIGxvY3BvbHkgYXZlYyBoID0iLCBoKSkKZ3JpZCgpCmxpbmVzKHJlZ19sb2Nwb2x5LCBjb2wgPSAiZGFya29yYW5nZSIsIGx3ZCA9IDIpCmxpbmVzKFQxLCBmVDEsIGNvbCA9ICJkYXJrZ3JlZW4iLCBsd2QgPSAyLCBsdHkgPSAyKQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kID0gYygiWV90IiwgImxvY3BvbHkiLCAiZih0KSIpLAogICAgICAgY29sID0gYygic3RlZWxibHVlIiwgImRhcmtvcmFuZ2UiLCAiZGFya2dyZWVuIiksCiAgICAgICBsdHkgPSBjKE5BLCAxLCAyKSwgcGNoID0gYygxNiwgTkEsIE5BKSwgbHdkID0gYyhOQSwgMiwgMikpCmBgYAoKKipPYnNlcnZhdGlvbiA6KiogcG91ciBgaCA9IDAuMDVgLCBs4oCZZXN0aW1hdGV1ciBgbG9jcG9seWAgc3VpdCBkZSBwcsOocyBsYSBzdHJ1Y3R1cmUgc2ludXNvw69kYWxlIGRlIGxhIGZvbmN0aW9uIHLDqWVsbGUuICBMZXMgdmFyaWF0aW9ucyByYXBpZGVzIHNvbnQgYmllbiBjYXB0dXLDqWVzLCBiaWVuIHF1ZSBsZSBicnVpdCBwdWlzc2UgaW5kdWlyZSBxdWVscXVlcyBwZXRpdGVzIG9zY2lsbGF0aW9ucy4gIExlIGNob2l4IGTigJl1biBgaGAgdHJvcCBwZXRpdCByZW5kcmFpdCBs4oCZZXN0aW1hdGlvbiB0cm9wIMKrIGNvdXJiw6llIMK7LCB0YW5kaXMgcXXigJl1biBgaGAgdHJvcCBncmFuZCBsaXNzZXJhaXQgZXhhZ8OpcsOpbWVudCBsYSBjb3VyYmUuCgojIyMgRWZmZXQgZHUgcGFyYW3DqHRyZSBkZSBsaXNzYWdlIGBoYAoKT24gZmFpdCB2YXJpZXIgbGEgdmFsZXVyIGRlIGBoYCBwb3VyIG9ic2VydmVyIGzigJlpbmZsdWVuY2UgZHUgbGlzc2FnZS4gIENp4oCRZGVzc291cywgb24gY29uc2lkw6hyZSBgaCA9IDAuMTVgLCBzb2l0IHVuZSBmZW7DqnRyZSB0cm9pcyBmb2lzIHBsdXMgbGFyZ2UuICBDZWxhIHByb2R1aXQgdW4gbGlzc2FnZSBwbHVzIGFncmVzc2lmLgoKYGBge3IgZXgyLWxvY3BvbHkyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpoMiA8LSAwLjE1CnJlZ19sb2Nwb2x5MiA8LSBsb2Nwb2x5KFQxLCBZX1QxLCBiYW5kd2lkdGggPSBoMikKCnBsb3QoVDEsIFlfVDEsIHBjaCA9IDE2LCBjZXggPSAwLjcsIGNvbCA9ICJzdGVlbGJsdWUiLAogICAgIHhsYWIgPSAidCIsIHlsYWIgPSAiWSIsCiAgICAgbWFpbiA9IHBhc3RlKCJFeGVyY2ljZSAyIOKAkyBsb2Nwb2x5IGF2ZWMgaCA9IiwgaDIpKQpncmlkKCkKbGluZXMocmVnX2xvY3BvbHkyLCBjb2wgPSAiZGFya29yYW5nZSIsIGx3ZCA9IDIpCmxpbmVzKFQxLCBmVDEsIGNvbCA9ICJkYXJrZ3JlZW4iLCBsd2QgPSAyLCBsdHkgPSAyKQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kID0gYygiWV90IiwgImxvY3BvbHkiLCAiZih0KSIpLAogICAgICAgY29sID0gYygic3RlZWxibHVlIiwgImRhcmtvcmFuZ2UiLCAiZGFya2dyZWVuIiksCiAgICAgICBsdHkgPSBjKE5BLCAxLCAyKSwgcGNoID0gYygxNiwgTkEsIE5BKSwgbHdkID0gYyhOQSwgMiwgMikpCmBgYAoKKipPYnNlcnZhdGlvbiA6KiogYXZlYyBgaCA9IDAuMTVgLCBs4oCZZXN0aW1hdGlvbiBlc3QgYmVhdWNvdXAgcGx1cyBsaXNzZS4gIExhIGNvdXJiZSBzdWl0IHRvdWpvdXJzIGxhIHRlbmRhbmNlIGdsb2JhbGUsIG1haXMgbGVzIHNvbW1ldHMgZXQgY3JldXggZGUgbGEgc2ludXNvw69kZSBzb250IGF0dMOpbnXDqXMuICBVbiBgaGAgdHJvcCDDqWxldsOpIHBldXQgbWFzcXVlciBkZXMgc3RydWN0dXJlcyByw6llbGxlcyBkZXMgZG9ubsOpZXMsIHRhbmRpcyBxdeKAmXVuIGBoYCB0cm9wIGZhaWJsZSBwZXV0IHN1cuKAkWFqdXN0ZXIgbGUgYnJ1aXQuCgojIyBMaXNzYWdlIMOgIG5veWF1IChga3Ntb290aGApCgpMYSBmb25jdGlvbiBga3Ntb290aGAgZm91cm5pdCB1biBlc3RpbWF0ZXVyIGRlIE5hZGFyYXlh4oCTV2F0c29uIGJhc8OpIHN1ciB1biBub3lhdSAocGFyIGTDqWZhdXQgZ2F1c3NpZW4pLiAgT24gdGVzdGUgZGV1eCBjaG9peCBkZSBiYW5kZSBkZSBsaXNzYWdlIDogYGJhbmR3aWR0aCA9IDAuMmAgZXQgYGJhbmR3aWR0aCA9IDAuMDVgIChub3RlIDogbGEgdmFsZXVyIGBoYCBjaG9pc2llIHBvdXIgYGxvY3BvbHlgIHByw6ljw6lkZW1tZW50KS4gIE9uIHJlcHLDqXNlbnRlIGNlcyBlc3RpbWF0aW9ucyBlbiByb3VnZSBldCBibGV1LgoKYGBge3IgZXgyLWtzbW9vdGgsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoc3RhdHMpCgplc3RpbTEgPC0ga3Ntb290aChUMSwgWV9UMSwgeC5wb2ludHMgPSBUMSwgYmFuZHdpZHRoID0gMC4yLCBrZXJuZWwgPSAibm9ybWFsIikKZXN0aW0yIDwtIGtzbW9vdGgoVDEsIFlfVDEsIHgucG9pbnRzID0gVDEsIGJhbmR3aWR0aCA9IDAuMDUsIGtlcm5lbCA9ICJub3JtYWwiKQoKcGxvdChUMSwgWV9UMSwgcGNoID0gMTYsIGNleCA9IDAuNywgY29sID0gInN0ZWVsYmx1ZSIsCiAgICAgeGxhYiA9ICJ0IiwgeWxhYiA9ICJZIiwKICAgICBtYWluID0gIkV4ZXJjaWNlIDIg4oCTIENvbXBhcmFpc29uIGtzbW9vdGgiKQpncmlkKCkKbGluZXMoZXN0aW0xJHgsIGVzdGltMSR5LCBjb2wgPSAicmVkIiwgbHdkID0gMikKbGluZXMoZXN0aW0yJHgsIGVzdGltMiR5LCBjb2wgPSAiYmx1ZSIsIGx3ZCA9IDIpCmxpbmVzKFQxLCBmVDEsIGNvbCA9ICJkYXJrZ3JlZW4iLCBsd2QgPSAyLCBsdHkgPSAyKQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kID0gYygiWV90IiwgImtzbW9vdGggaD0wLjIiLCAia3Ntb290aCBoPTAuMDUiLCAiZih0KSIpLAogICAgICAgY29sID0gYygic3RlZWxibHVlIiwgInJlZCIsICJibHVlIiwgImRhcmtncmVlbiIpLAogICAgICAgbHR5ID0gYyhOQSwgMSwgMSwgMiksIHBjaCA9IGMoMTYsIE5BLCBOQSwgTkEpLCBsd2QgPSBjKE5BLCAyLCAyLCAyKSkKYGBgCgoqKk9ic2VydmF0aW9uIDoqKiBs4oCZZXN0aW1hdGV1ciDDoCBub3lhdSBhdmVjIGBoID0gMC4yYCAocm91Z2UpIGVzdCBwbHVzIGxpc3NlIHF1ZSBjZWx1aSBhdmVjIGBoID0gMC4wNWAgKGJsZXUpLiAgTGEgY291cmJlIHJvdWdlIHN1aXQgbGEgdGVuZGFuY2UgZ2xvYmFsZSBlbiBhdHTDqW51YW50IGZvcnRlbWVudCBsZXMgZmx1Y3R1YXRpb25zIGxvY2FsZXMsIHRhbmRpcyBxdWUgbGEgY291cmJlIGJsZXVlIHLDqWFnaXQgZGF2YW50YWdlIGF1eCB2YXJpYXRpb25zIHBvaW50IHBhciBwb2ludC4gIExlIGNob2l4IGR1IHBhcmFtw6h0cmUgYGJhbmR3aWR0aGAgZXN0IGRvbmMgY3J1Y2lhbCBwb3VyIGFqdXN0ZXIgbGUgZGVncsOpIGRlIGxpc3NhZ2UuCgojIyMgSW5mbHVlbmNlIGRlIGRpZmbDqXJlbnRlcyB2YWxldXJzIGRlIGBoYAoKT24gcGV1dCB0ZXN0ZXIgZGVzIHZhbGV1cnMgZGUgYmFuZGUgYmVhdWNvdXAgcGx1cyBwZXRpdGVzIG91IHBsdXMgZ3JhbmRlcy4gIENp4oCRZGVzc291cywgb24gY29tcGFyZSBgaCA9IDAuNWAgKGxpc3NhZ2UgdHLDqHMgbGFyZ2UpIGV0IGBoID0gMC4xYCAobGlzc2FnZSBtb2TDqXLDqSkuCgpgYGB7ciBleDIta3Ntb290aC12YXJpYXRpb25zLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQplc3RpbV9oMDUgPC0ga3Ntb290aChUMSwgWV9UMSwgeC5wb2ludHMgPSBUMSwgYmFuZHdpZHRoID0gMC41LCBrZXJuZWwgPSAibm9ybWFsIikKZXN0aW1faDAxIDwtIGtzbW9vdGgoVDEsIFlfVDEsIHgucG9pbnRzID0gVDEsIGJhbmR3aWR0aCA9IDAuMSwga2VybmVsID0gIm5vcm1hbCIpCgpwbG90KFQxLCBZX1QxLCBwY2ggPSAxNiwgY2V4ID0gMC43LCBjb2wgPSAic3RlZWxibHVlIiwKICAgICB4bGFiID0gInQiLCB5bGFiID0gIlkiLAogICAgIG1haW4gPSAiRXhlcmNpY2UgMiDigJMgRWZmZXQgZGUgaCBzdXIga3Ntb290aCIpCmdyaWQoKQpsaW5lcyhlc3RpbV9oMDUkeCwgZXN0aW1faDA1JHksIGNvbCA9ICJwdXJwbGUiLCBsd2QgPSAyKQpsaW5lcyhlc3RpbV9oMDEkeCwgZXN0aW1faDAxJHksIGNvbCA9ICJkYXJrb3JhbmdlIiwgbHdkID0gMikKbGluZXMoVDEsIGZUMSwgY29sID0gImRhcmtncmVlbiIsIGx3ZCA9IDIsIGx0eSA9IDIpCmxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQgPSBjKCJZX3QiLCAia3Ntb290aCBoPTAuNSIsICJrc21vb3RoIGg9MC4xIiwgImYodCkiKSwKICAgICAgIGNvbCA9IGMoInN0ZWVsYmx1ZSIsICJwdXJwbGUiLCAiZGFya29yYW5nZSIsICJkYXJrZ3JlZW4iKSwKICAgICAgIGx0eSA9IGMoTkEsIDEsIDEsIDIpLCBwY2ggPSBjKDE2LCBOQSwgTkEsIE5BKSwgbHdkID0gYyhOQSwgMiwgMiwgMikpCmBgYAoKKipPYnNlcnZhdGlvbiA6KiogYXZlYyBgaCA9IDAuNWAsIGxhIGNvdXJiZSAodmlvbGV0dGUpIGVzdCB0csOocyBsaXNzZSBldCBuZSBzdWl0IHBsdXMgdnJhaW1lbnQgbGEgZm9ybWUgc2ludXNvw69kYWxlIDogbGUgbGlzc2FnZSDDqWNyYXNlIGxhIHN0cnVjdHVyZSBww6lyaW9kaXF1ZS4gIEF2ZWMgYGggPSAwLjFgIChvcmFuZ2UpLCBs4oCZYWp1c3RlbWVudCBlc3QgaW50ZXJtw6lkaWFpcmUgOiBvbiB2b2l0IGxhIHNpbnVzb8OvZGUsIG1haXMgbOKAmWVzdGltYXRpb24gZXN0IG1vaW5zIG9zY2lsbGFudGUgcXXigJlhdmVjIGBoID0gMC4wNWAuCgojIyMgQ29tcGFyYWlzb24gZGVzIHF1YWxpdMOpcyBwcsOpZGljdGl2ZXMKClBvdXIgY29tcGFyZXIgbGVzIG3DqXRob2RlcyBgbG9jcG9seWAgZXQgYGtzbW9vdGhgLCBvbiBjYWxjdWxlIGzigJllcnJldXIgcXVhZHJhdGlxdWUgbW95ZW5uZSAoRVFNKSBlbnRyZSBsZXMgZXN0aW1hdGlvbnMgZXQgbGEgZm9uY3Rpb24gcsOpZWxsZSBgZihUMSlgLiAgVW5lIEVRTSBwbHVzIGZhaWJsZSBpbmRpcXVlIHVuZSBtZWlsbGV1cmUgcHLDqWNpc2lvbiBwcsOpZGljdGl2ZS4gIE9uIGNvbnNpZMOocmUgYGxvY3BvbHlgIGF2ZWMgYGg9MC4wNWAgZXQgYGtzbW9vdGhgIGF2ZWMgbGVzIG3Dqm1lcyBkZXV4IHBhcmFtw6h0cmVzLgoKYGBge3IgZXgyLXByZWRpY3Rpb24sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgUHLDqXZpc2lvbnMgbG9jcG9seQpwcmVkX2xvYyA8LSBhcHByb3goeCA9IHJlZ19sb2Nwb2x5JHgsIHkgPSByZWdfbG9jcG9seSR5LCB4b3V0ID0gVDEpJHkKIyBQcsOpdmlzaW9ucyBrc21vb3RoCnByZWRfazA1IDwtIGVzdGltMSR5CnByZWRfazAwNSA8LSBlc3RpbTIkeQoKIyBDYWxjdWxzIGRlIGwnRVFNIHBhciByYXBwb3J0IMOgIGZUMQpFUU1fbG9jIDwtIG1lYW4oKHByZWRfbG9jIC0gZlQxKV4yKQpFUU1fazA1IDwtIG1lYW4oKHByZWRfazA1IC0gZlQxKV4yKQpFUU1fazAwNSA8LSBtZWFuKChwcmVkX2swMDUgLSBmVDEpXjIpCgpFUU0gPC0gZGF0YS5mcmFtZShNw6l0aG9kZSA9IGMoImxvY3BvbHkgaD0wLjA1IiwgImtzbW9vdGggaD0wLjIiLCAia3Ntb290aCBoPTAuMDUiKSwKICAgICAgICAgICAgICAgICAgRVFNID0gYyhFUU1fbG9jLCBFUU1fazA1LCBFUU1fazAwNSkpCkVRTQpgYGAKCioqT2JzZXJ2YXRpb24gOioqIGVuIGfDqW7DqXJhbCwgbOKAmWVzdGltYXRldXIgYGxvY3BvbHlgIGV0IGzigJllc3RpbWF0ZXVyIGBrc21vb3RoYCBhdmVjIHVuIHBhcmFtw6h0cmUgZGUgbGlzc2FnZSByYWlzb25uYWJsZSBkb25uZW50IGRlcyBFUU0gZHUgbcOqbWUgb3JkcmUuICBVbmUgYmFuZGUgdHJvcCBsYXJnZSBhdWdtZW50ZSBs4oCZRVFNIGNhciBsYSBjb3VyYmUgbGlzc8OpZSBz4oCZw6lsb2lnbmUgZGUgbGEgc3RydWN0dXJlIHLDqWVsbGUsIGFsb3JzIHF14oCZdW5lIGJhbmRlIHRyb3AgcGV0aXRlIHBldXQgc3Vy4oCRYWp1c3RlciBsZSBicnVpdCBldCDDqWdhbGVtZW50IGF1Z21lbnRlciBs4oCZRVFNLiAgTGUgY2hvaXggb3B0aW1hbCBkZSBgaGAgc2Ugc2l0dWUgZW50cmUgY2VzIGV4dHLDqm1lcy4KCiMgRXhlcmNpY2UmbmJzcDszIOKAkyBFc3RpbWF0aW9uIG5vbiBwYXJhbcOpdHJpcXVlIMOgIHBhcnRpciBk4oCZdW4gcGV0aXQgamV1IGRlIGRvbm7DqWVzCgpPbiBjaGVyY2hlIMOgIGVzdGltZXIgbGEgZm9uY3Rpb24gZGUgcsOpZ3Jlc3Npb24gXChmXCkgcGFyIGRldXggZXN0aW1hdGV1cnMgZGUgTmFkYXJheWHigJNXYXRzb24gOiBs4oCZdW4gdXRpbGlzYW50IHVuIG5veWF1IHVuaWZvcm1lIChmZW7DqnRyZSByZWN0YW5ndWxhaXJlKSBldCBs4oCZYXV0cmUgdW4gbm95YXUgZ2F1c3NpZW4sIHRvdXMgZGV1eCBhdmVjIHVuZSBmZW7DqnRyZSBkZSBsaXNzYWdlIGBoID0gMS41YC4gIE9uIGNvbXBhcmVyYSBsZXMgY291cmJlcyBvYnRlbnVlcyBldCBvbiBhbmFseXNlcmEgbGVzIHLDqXNpZHVzLgoKT24gZGlzcG9zZSBk4oCZdW4gcGV0aXQgw6ljaGFudGlsbG9uIGRlIGNpbnEgb2JzZXJ2YXRpb25zIDoKCmBgYHtyIGV4My1kYXRhfQp4MyA8LSBjKC0yLCAtMSwgLTEsIDEsIDApCnkzIDwtIGMoIDIsICAxLCAgNCwgMiwgLTIpCmRhdGEzIDwtIGRhdGEuZnJhbWUoeCA9IHgzLCB5ID0geTMpCmRhdGEzCmBgYAoKIyMgRXN0aW1hdGV1ciBkZSBOYWRhcmF5YeKAk1dhdHNvbiBhdmVjIG5veWF1IHVuaWZvcm1lCgpQb3VyIGNhbGN1bGVyIGzigJllc3RpbWF0ZXVyIGF2ZWMgbm95YXUgdW5pZm9ybWUsIG9uIHV0aWxpc2UgbGEgZm9uY3Rpb24gYGtzbW9vdGhgIGF2ZWMgbOKAmW9wdGlvbiBga2VybmVsID0gImJveCJgLiAgT24gY2FsY3VsZSBsZXMgcHLDqWRpY3Rpb25zIHN1ciB1bmUgZ3JpbGxlIGZpbmUgZGUgdmFsZXVycyBkZSBgeGAgYWxsYW50IGRlIGBtaW4oeDMpYCDDoCBgbWF4KHgzKWAuCgpgYGB7ciBleDMtYm94LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpncmlkX3ggPC0gc2VxKG1pbih4MykgLSAwLjUsIG1heCh4MykgKyAwLjUsIGxlbmd0aC5vdXQgPSAyMDApCm53X3VuaWZvcm0gPC0ga3Ntb290aCh4MywgeTMsIGtlcm5lbCA9ICJib3giLCBiYW5kd2lkdGggPSAxLjUsIHgucG9pbnRzID0gZ3JpZF94KQoKcGxvdCh4MywgeTMsIHBjaCA9IDE2LCBjZXggPSAxLjIsIGNvbCA9ICJzdGVlbGJsdWUiLAogICAgIHhsYWIgPSAieCIsIHlsYWIgPSAieSIsCiAgICAgbWFpbiA9ICJFeGVyY2ljZSAzIOKAkyBOYWRhcmF5YeKAk1dhdHNvbiBub3lhdSB1bmlmb3JtZSAoaCA9IDEsNSkiKQpncmlkKCkKbGluZXMobndfdW5pZm9ybSR4LCBud191bmlmb3JtJHksIGNvbCA9ICJkYXJrb3JhbmdlIiwgbHdkID0gMikKYGBgCgojIyBFc3RpbWF0ZXVyIGRlIE5hZGFyYXlh4oCTV2F0c29uIGF2ZWMgbm95YXUgZ2F1c3NpZW4KCk9uIHLDqXDDqHRlIGzigJlvcMOpcmF0aW9uIGF2ZWMgdW4gbm95YXUgZ2F1c3NpZW4gKGBrZXJuZWwgPSAibm9ybWFsImApIGV0IGxhIG3Dqm1lIGJhbmRlIGRlIGxpc3NhZ2UgYGggPSAxLjVgLgoKYGBge3IgZXgzLWdhdXNzaWFuLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpud19nYXVzc2lhbiA8LSBrc21vb3RoKHgzLCB5Mywga2VybmVsID0gIm5vcm1hbCIsIGJhbmR3aWR0aCA9IDEuNSwgeC5wb2ludHMgPSBncmlkX3gpCgpwbG90KHgzLCB5MywgcGNoID0gMTYsIGNleCA9IDEuMiwgY29sID0gInN0ZWVsYmx1ZSIsCiAgICAgeGxhYiA9ICJ4IiwgeWxhYiA9ICJ5IiwKICAgICBtYWluID0gIkV4ZXJjaWNlIDMg4oCTIE5hZGFyYXlh4oCTV2F0c29uIG5veWF1IGdhdXNzaWVuIChoID0gMSw1KSIpCmdyaWQoKQpsaW5lcyhud19nYXVzc2lhbiR4LCBud19nYXVzc2lhbiR5LCBjb2wgPSAiZm9yZXN0Z3JlZW4iLCBsd2QgPSAyKQpgYGAKCiMjIENvbXBhcmFpc29uIGV0IGFuYWx5c2UgZGVzIHLDqXNpZHVzCgpPbiBzdXBlcnBvc2UgbGVzIGRldXggZXN0aW1hdGV1cnMgc3VyIGxlIG3Dqm1lIGdyYXBoaXF1ZSBldCBvbiBjYWxjdWxlIGxlcyByw6lzaWR1cyAob2JzZXJ2YXRpb25zIG1vaW5zIHZhbGV1cnMgYWp1c3TDqWVzKSBlbiBjaGFjdW4gZGVzIHBvaW50cyBgeDNgLiAgUG91ciBsZSBub3lhdSB1bmlmb3JtZSwgb24gb2J0aWVudCBsZXMgdmFsZXVycyBhanVzdMOpZXMgZW4gaW50ZXJwb2xhbnQgbGEgY291cmJlIGxpc3PDqWUgc3VyIGxlcyBwb2ludHMgYHgzYC4KCmBgYHtyIGV4My1yZXNpZHVhbHMsIG1lc3NhZ2U9RkFMU0V9CiMgU3VwZXJwb3NpdGlvbiBkZXMgZGV1eCBjb3VyYmVzCnBsb3QoeDMsIHkzLCBwY2ggPSAxNiwgY2V4ID0gMS4yLCBjb2wgPSAic3RlZWxibHVlIiwKICAgICB4bGFiID0gIngiLCB5bGFiID0gInkiLAogICAgIG1haW4gPSAiRXhlcmNpY2UgMyDigJMgQ29tcGFyYWlzb24gZGVzIGVzdGltYXRldXJzIikKZ3JpZCgpCmxpbmVzKG53X3VuaWZvcm0keCwgbndfdW5pZm9ybSR5LCBjb2wgPSAiZGFya29yYW5nZSIsIGx3ZCA9IDIpCmxpbmVzKG53X2dhdXNzaWFuJHgsIG53X2dhdXNzaWFuJHksIGNvbCA9ICJmb3Jlc3RncmVlbiIsIGx3ZCA9IDIpCmxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQgPSBjKCJPYnNlcnZhdGlvbnMiLCAiTlcgdW5pZm9ybWUiLCAiTlcgZ2F1c3NpZW4iKSwKICAgICAgIGNvbCA9IGMoInN0ZWVsYmx1ZSIsICJkYXJrb3JhbmdlIiwgImZvcmVzdGdyZWVuIiksCiAgICAgICBsdHkgPSBjKE5BLCAxLCAxKSwgcGNoID0gYygxNiwgTkEsIE5BKSwgbHdkID0gYyhOQSwgMiwgMikpCgojIEludGVycG9sYXRpb24gcG91ciBjYWxjdWxlciBsZXMgdmFsZXVycyBhanVzdMOpZXMgYXV4IHBvaW50cyB4MwpmaXRfdW5pZm9ybSA8LSBhcHByb3goeCA9IG53X3VuaWZvcm0keCwgeSA9IG53X3VuaWZvcm0keSwgeG91dCA9IHgzKSR5CmZpdF9nYXVzc2lhbiA8LSBhcHByb3goeCA9IG53X2dhdXNzaWFuJHgsIHkgPSBud19nYXVzc2lhbiR5LCB4b3V0ID0geDMpJHkKCnJlc2lkX3VuaWZvcm0gPC0geTMgLSBmaXRfdW5pZm9ybQpyZXNpZF9nYXVzc2lhbiA8LSB5MyAtIGZpdF9nYXVzc2lhbgoKZGF0YS5mcmFtZSh4ID0geDMsIHkgPSB5MywgTldfdW5pZm9ybSA9IGZpdF91bmlmb3JtLCBOV19nYXVzc2lhbiA9IGZpdF9nYXVzc2lhbiwKICAgICAgICAgICByZXNpZF91bmlmb3JtID0gcmVzaWRfdW5pZm9ybSwgcmVzaWRfZ2F1c3NpYW4gPSByZXNpZF9nYXVzc2lhbikKYGBgCgoqKk9ic2VydmF0aW9uIDoqKiBsZXMgZGV1eCBlc3RpbWF0ZXVycyBkb25uZW50IGRlcyBjb3VyYmVzIHJlbGF0aXZlbWVudCBzaW1pbGFpcmVzLCBtYWlzIGxlIG5veWF1IGdhdXNzaWVuIHByb2R1aXQgdW5lIGNvdXJiZSBwbHVzIGxpc3NlIGV0IG1vaW5zIGFuZ3VsZXVzZSBxdWUgbGUgbm95YXUgdW5pZm9ybWUuICBMZXMgcsOpc2lkdXMgbW9udHJlbnQgcXVlIGNlcnRhaW5zIHBvaW50cyAobm90YW1tZW50IGB4ID0gLTFgIGF2ZWMgYHkgPSA0YCkgc29udCBtYWwgYWp1c3TDqXMsIGNhciBsZXVyIHZhbGV1ciBlc3QgcGx1cyDDqWxvaWduw6llIGRlcyBhdXRyZXMgb2JzZXJ2YXRpb25zLiAgRGFucyB1biDDqWNoYW50aWxsb24gYXVzc2kgcGV0aXQsIGxlIGNob2l4IGR1IG5veWF1IGV0IGRlIGxhIGJhbmRlIGRlIGxpc3NhZ2UgaW5mbHVlIGZvcnRlbWVudCBzdXIgbOKAmWFzcGVjdCBkZSBsYSBjb3VyYmUuCgojIEV4ZXJjaWNlJm5ic3A7NCDigJMgQW5hbHlzZSBkZSBsYSB2aXRlc3NlIGTigJl1biBjYW1pb24KCk9uIGRpc3Bvc2UgZGVzIG1lc3VyZXMgZGUgbGEgdml0ZXNzZSBpbnN0YW50YW7DqWUgYFZgIChlbiBrbS9oKSBk4oCZdW4gY2FtaW9uIGVuIGZvbmN0aW9uIGR1IHRlbXBzIGB0YCAoZW4gc2Vjb25kZXMpLiAgTGVzIGRvbm7DqWVzIHNvbnQgcHLDqXNlbnTDqWVzIGRhbnMgdW4gdGFibGVhdSBldCByZXByb2R1aXRlcyBjaeKAkWRlc3NvdXMuCgpgYGB7ciBleDQtZGF0YX0KdDQgPC0gYygwLCA1LCAxMCwgMTUsIDIwLCAzMCwgNDAsIDUwLCA2MCwgNzAsIDgwLCA5MCwgOTUsIDEwMCwgMTA1LCAxMTAsIDExNSwgMTIwKQpWNCA8LSBjKDAsIDEwLCAyMCwgMzAsIDQwLCA1MiwgNjAsIDYzLCA2NSwgNjMsIDU4LCA1MCwgNDUsIDM4LCAyOSwgMjAsIDEyLCAzKQpjYW1pb24gPC0gZGF0YS5mcmFtZSh0ID0gdDQsIFYgPSBWNCkKY2FtaW9uCmBgYAoKIyMgUmVwcsOpc2VudGF0aW9uIGdyYXBoaXF1ZSBldCBhanVzdGVtZW50IGxpbsOpYWlyZQoKT24gdHJhY2UgZOKAmWFib3JkIGxhIHZpdGVzc2UgZW4gZm9uY3Rpb24gZHUgdGVtcHMsIHB1aXMgb24gYWp1c3RlIHVuZSBkcm9pdGUgZGUgdGVuZGFuY2UgYXUgbW95ZW4gZOKAmXVuZSByw6lncmVzc2lvbiBsaW7DqWFpcmUgYGxtKFYgfiB0KWAuICBPbiBvYnNlcnZlIHNpIHVuZSByZWxhdGlvbiBsaW7DqWFpcmUgZXN0IHBsYXVzaWJsZS4KCmBgYHtyIGV4NC1saW5lYXIsIG1lc3NhZ2U9RkFMU0V9CiMgQWp1c3RlbWVudCBsaW7DqWFpcmUKcmVnX2NhbWlvbl9saW4gPC0gbG0oViB+IHQsIGRhdGEgPSBjYW1pb24pCgpwbG90KGNhbWlvbiR0LCBjYW1pb24kViwKICAgICBwY2ggPSAxNiwgY2V4ID0gMS4wLCBjb2wgPSAic3RlZWxibHVlIiwKICAgICB4bGFiID0gIlRlbXBzIChzKSIsIHlsYWIgPSAiVml0ZXNzZSAoa20vaCkiLAogICAgIG1haW4gPSAiRXhlcmNpY2UgNCDigJMgVml0ZXNzZSBkdSBjYW1pb24gZXQgYWp1c3RlbWVudCBsaW7DqWFpcmUiKQpncmlkKCkKYWJsaW5lKHJlZ19jYW1pb25fbGluLCBjb2wgPSAiZGFya29yYW5nZSIsIGx3ZCA9IDIpCnN1bW1hcnkocmVnX2NhbWlvbl9saW4pCmBgYAoKKipPYnNlcnZhdGlvbiA6KiogbGEgdml0ZXNzZSBhdWdtZW50ZSBk4oCZYWJvcmQsIGF0dGVpbnQgdW4gbWF4aW11bSBhdXRvdXIgZGUgNjUga20vaCB2ZXJzIDYwIHNlY29uZGVzLCBwdWlzIGTDqWNyb8OudCBwcm9ncmVzc2l2ZW1lbnQuICBMYSBkcm9pdGUgZGUgdGVuZGFuY2UgbGluw6lhaXJlIG5lIHJlcHJvZHVpdCBwYXMgY2V0dGUgZm9ybWUgZW4gY2xvY2hlIDogZWxsZSBzdXLigJFlc3RpbWUgbGEgdml0ZXNzZSBhdSBkw6lidXQgZXQgbGEgc291c+KAkWVzdGltZSBhcHLDqHMgNjAgcy4gIExhIHJlbGF0aW9uIG7igJllc3QgZG9uYyBwYXMgdnJhaW1lbnQgbGluw6lhaXJlLgoKIyMgQWp1c3RlbWVudCBxdWFkcmF0aXF1ZSA6IG1vZMOobGUgXCh2KHQpID0gzrhfMCArIM64XzEgdCArIM64XzIgdF4yXCkKClB1aXNxdWUgbGEgcmVsYXRpb24gc2VtYmxlIHBhcmFib2xpcXVlLCBvbiBhanVzdGUgdW4gbW9kw6hsZSBkZSByw6lncmVzc2lvbiBxdWFkcmF0aXF1ZS4gIE9uIGNhbGN1bGUgbGVzIGNvZWZmaWNpZW50cyBcKFx0aGV0YV8wXCksIFwoXHRoZXRhXzFcKSBldCBcKFx0aGV0YV8yXCkgdmlhIGBsbShWIH4gdCArIEkodF4yKSlgLiAgT24gdHJhY2UgZW5zdWl0ZSBsYSBjb3VyYmUgcXVhZHJhdGlxdWUgb2J0ZW51ZS4KCmBgYHtyIGV4NC1xdWFkcmF0aWMsIG1lc3NhZ2U9RkFMU0V9CiMgQWp1c3RlbWVudCBxdWFkcmF0aXF1ZQpyZWdfY2FtaW9uX3F1YWQgPC0gbG0oViB+IHQgKyBJKHReMiksIGRhdGEgPSBjYW1pb24pCnN1bW1hcnkocmVnX2NhbWlvbl9xdWFkKQoKIyBDb3VyYmUgcXVhZHJhdGlxdWUKdF9zZXEgPC0gc2VxKG1pbihjYW1pb24kdCksIG1heChjYW1pb24kdCksIGxlbmd0aC5vdXQgPSAyMDApCnByZWRfcXVhZCA8LSBwcmVkaWN0KHJlZ19jYW1pb25fcXVhZCwgbmV3ZGF0YSA9IGRhdGEuZnJhbWUodCA9IHRfc2VxKSkKCnBsb3QoY2FtaW9uJHQsIGNhbWlvbiRWLAogICAgIHBjaCA9IDE2LCBjZXggPSAxLjAsIGNvbCA9ICJzdGVlbGJsdWUiLAogICAgIHhsYWIgPSAiVGVtcHMgKHMpIiwgeWxhYiA9ICJWaXRlc3NlIChrbS9oKSIsCiAgICAgbWFpbiA9ICJFeGVyY2ljZSA0IOKAkyBBanVzdGVtZW50IHF1YWRyYXRpcXVlIGRlIGxhIHZpdGVzc2UiKQpncmlkKCkKbGluZXModF9zZXEsIHByZWRfcXVhZCwgY29sID0gImZvcmVzdGdyZWVuIiwgbHdkID0gMikKYGBgCgoqKk9ic2VydmF0aW9uIDoqKiBsYSBjb3VyYmUgcXVhZHJhdGlxdWUgw6lwb3VzZSBiaWVuIGxhIHRyYWplY3RvaXJlIGVuIGNsb2NoZSBkZSBsYSB2aXRlc3NlIDogYWNjw6lsw6lyYXRpb24ganVzcXXigJnDoCBlbnZpcm9uIDYwIHMsIHB1aXMgZMOpY8OpbMOpcmF0aW9uLiAgTGVzIGNvZWZmaWNpZW50cyBvYnRlbnVzIHJlZmzDqHRlbnQgY2V0dGUgZm9ybWUgOiB1biB0ZXJtZSBxdWFkcmF0aXF1ZSBuw6lnYXRpZiByZW5kIGxhIGZvbmN0aW9uIGNvbmNhdmUuICBMZSBtb2TDqGxlIHF1YWRyYXRpcXVlIGFtw6lsaW9yZSBuZXR0ZW1lbnQgbOKAmWFqdXN0ZW1lbnQgcGFyIHJhcHBvcnQgw6AgbGEgZHJvaXRlLgoKIyMgQWp1c3RlbWVudCBxdWFkcmF0aXF1ZSB2aWEgYHBvbHlgCgpPbiBwZXV0IHJldHJvdXZlciBsZSBtw6ptZSByw6lzdWx0YXQgZW4gdXRpbGlzYW50IGxhIGZvbmN0aW9uIGBwb2x5KHQsIGRlZyA9IDIpYCBxdWkgZ8OpbsOocmUgZGVzIHBvbHluw7RtZXMgb3J0aG9nb25hdXggZGUgZGVncsOpIDIuICBM4oCZYWp1c3RlbWVudCBzZSBmYWl0IGVuc3VpdGUgYXZlYyBgbG0oViB+IHBvbHlub21lKWAuCgoKCmBgYHtyIGV4NC1wb2x5LW9ydGhvLCBtZXNzYWdlPUZBTFNFfQpyZWdfY2FtaW9uX3BvbHkgPC0gbG0oViB+IHBvbHkodCwgMiksIGRhdGEgPSBjYW1pb24pCnN1bW1hcnkocmVnX2NhbWlvbl9wb2x5KQoKIyBDb21wYXJhaXNvbiBkZXMgcHLDqWRpY3Rpb25zCnRfc2VxIDwtIHNlcShtaW4oY2FtaW9uJHQpLCBtYXgoY2FtaW9uJHQpLCBsZW5ndGgub3V0ID0gMjAwKQpwcmVkX3BvbHkgPC0gcHJlZGljdChyZWdfY2FtaW9uX3BvbHksIG5ld2RhdGEgPSBkYXRhLmZyYW1lKHQgPSB0X3NlcSkpCgpwbG90KGNhbWlvbiR0LCBjYW1pb24kViwKICAgICBwY2ggPSAxNiwgY2V4ID0gMS4wLCBjb2wgPSAic3RlZWxibHVlIiwKICAgICB4bGFiID0gIlRlbXBzIChzKSIsIHlsYWIgPSAiVml0ZXNzZSAoa20vaCkiLAogICAgIG1haW4gPSAiRXhlcmNpY2UgNCDigJMgQWp1c3RlbWVudCBwb2x5bm9taWFsIChkZWdyw6kgMikiKQpncmlkKCkKbGluZXModF9zZXEsIHByZWRfcG9seSwgY29sID0gImRhcmtyZWQiLCBsd2QgPSAyKQpgYGAKCioqT2JzZXJ2YXRpb24gOioqIGzigJl1dGlsaXNhdGlvbiBkZSBgcG9seSh0LCBkZWc9MilgIGRvbm5lIHVuIGFqdXN0ZW1lbnQgaWRlbnRpcXVlIMOgIGNlbHVpIG9idGVudSBhdmVjIGBJKHReMilgLiAgTGEgc3ludGF4ZSBlc3QgcGx1cyBjb21wYWN0ZSBldCBwZXJtZXQgZGUgZ8OpbsOpcmFsaXNlciBmYWNpbGVtZW50IMOgIGRlcyBwb2x5bsO0bWVzIGRlIGRlZ3LDqSBzdXDDqXJpZXVyLiAgTGVzIGNvdXJiZXMgc2Ugc3VwZXJwb3NlbnQgcGFyZmFpdGVtZW50LgoKIyBFeGVyY2ljZSZuYnNwOzUg4oCTIFLDqWdyZXNzaW9uIG5vbiBwYXJhbcOpdHJpcXVlIHN1ciBkZXMgZG9ubsOpZXMgZGUgdmVycmUKCk9uIHPigJlpbnTDqXJlc3NlIMOgIGRlcyBtZXN1cmVzIHBvcnRhbnQgc3VyIGRlcyBmcmFnbWVudHMgZGUgdmVycmUuICBPbiB2ZXV0IGV4cGxpcXVlciBs4oCZaW5kaWNlIGRlIHLDqWZyYWN0aW9uIGBSSWAgZW4gZm9uY3Rpb24gZGUgbGEgdGVuZXVyIGVuIGFsdW1pbml1bSBgQWxgLiAgTGVzIGRvbm7DqWVzIHByb3ZpZW5uZW50IGR1IGpldSBkZSBkb25uw6llcyAqKkdsYXNzKiogZHUgcGFja2FnZSAqbWxiZW5jaCouCgojIyBJbXBvcnRhdGlvbiBldCByZXByw6lzZW50YXRpb24gZGVzIGRvbm7DqWVzCgpPbiBjaGFyZ2UgbGUgamV1IGRlIGRvbm7DqWVzLCBvbiBjb25zZXJ2ZSBsZXMgY29sb25uZXMgZOKAmWludMOpcsOqdCBldCBvbiByw6lhbGlzZSB1biBudWFnZSBkZSBwb2ludHMuICBPbiBhcHBlbGxlIGB2ZXJyZXNgIGxlIGBkYXRhLmZyYW1lYCBjb250ZW5hbnQgbGVzIHZhcmlhYmxlcyBgQWxgIGV0IGBSSWAuCgpgYGB7ciBleDUtaW1wb3J0LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KG1sYmVuY2gpCmRhdGEoR2xhc3MpCgojIENyw6lhdGlvbiBkdSBkYXRhLmZyYW1lIHZlcnJlcwp2ZXJyZXMgPC0gZGF0YS5mcmFtZShBbCA9IEdsYXNzJEFsLCBSSSA9IEdsYXNzJFJJKQpoZWFkKHZlcnJlcykKCnBsb3QodmVycmVzJEFsLCB2ZXJyZXMkUkksCiAgICAgcGNoID0gMTYsIGNleCA9IDAuNywgY29sID0gInN0ZWVsYmx1ZSIsCiAgICAgeGxhYiA9ICJBbHVtaW5pdW0gKCUpIiwgeWxhYiA9ICJJbmRpY2UgZGUgcsOpZnJhY3Rpb24gKFJJKSIsCiAgICAgbWFpbiA9ICJFeGVyY2ljZSA1IOKAkyBOdWFnZSBkZSBwb2ludHMgOiBSSSBlbiBmb25jdGlvbiBkZSBBbCIpCmdyaWQoKQpgYGAKCioqT2JzZXJ2YXRpb24gOioqIGxhIHJlbGF0aW9uIGVudHJlIGBBbGAgZXQgYFJJYCBzZW1ibGUgbm9uIGxpbsOpYWlyZSA6IGzigJlpbmRpY2UgZGUgcsOpZnJhY3Rpb24gZGltaW51ZSBwb3VyIGRlcyB0ZW5ldXJzIGVuIGFsdW1pbml1bSBmYWlibGVzLCBwdWlzIGF1Z21lbnRlIGzDqWfDqHJlbWVudC4gIE9uIGV4cGxvcmUgbWFpbnRlbmFudCBkZXMgbcOpdGhvZGVzIG5vbiBwYXJhbcOpdHJpcXVlcyBwb3VyIGVzdGltZXIgY2V0dGUgcmVsYXRpb24uCgojIyBFc3RpbWF0ZXVyIHBhciBub3lhdSBhdmVjIGxhIGZvbmN0aW9uIGBucHJlZ2AKCkxlIHBhY2thZ2UgKipucCoqIGZvdXJuaXQgZGVzIG91dGlscyBk4oCZZXN0aW1hdGlvbiBub24gcGFyYW3DqXRyaXF1ZSBtdWx0aXZhcmnDqWUuICBPbiBjb21tZW5jZSBwYXIgZMOpdGVybWluZXIgbGEgbGFyZ2V1ciBkZSBiYW5kZSBvcHRpbWFsZSDDoCBs4oCZYWlkZSBkZSBgbnByZWdid2AsIHB1aXMgb24gYWp1c3RlIGxlIG1vZMOobGUgYXZlYyBgbnByZWdgLiAgT24gcGV1dCB2aXN1YWxpc2VyIGxhIGNvdXJiZSBhanVzdMOpZSBldCBsZXMgaW50ZXJ2YWxsZXMgZGUgY29uZmlhbmNlIGFzeW1wdG90aXF1ZXMuCgpgYGB7ciBleDUtbnAsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkobnApCmF0dGFjaCh2ZXJyZXMpICAgICAgICAgICAgICAgICMgYXR0YWNoZSBkZXMgdmFyaWFibGVzIEFsIGV0IFJJCmhfbnAgPC0gbnByZWdidyhSSSB+IEFsKSAgICAgICMgZXN0aW1hdGlvbiBhdXRvbWF0aXF1ZSBkZSBsYSBiYW5kZQpzdW1tYXJ5KGhfbnApCgp2ZXJyZXNfbnAgPC0gbnByZWcoaF9ucCkgICAgICAjIGFqdXN0ZW1lbnQgbm9uIHBhcmFtw6l0cmlxdWUKcGxvdCh2ZXJyZXNfbnAsIHBsb3QuZXJyb3JzLm1ldGhvZCA9ICJhc3ltcHRvdGljIikKZGV0YWNoKHZlcnJlcykKYGBgCgoqKk9ic2VydmF0aW9uIDoqKiBs4oCZZXN0aW1hdGlvbiBpbmRpcXVlIHVuZSBjb3VyYmUgbGlzc2UgcXVpIHN1aXQgbGEgZm9ybWUgZ8OpbsOpcmFsZSBkdSBudWFnZSBkZSBwb2ludHMuICBMZXMgYmFuZGVzIGTigJllcnJldXIgYXN5bXB0b3RpcXVlcyBtb250cmVudCBvw7kgbOKAmWluY2VydGl0dWRlIGVzdCBwbHVzIGdyYW5kZSwgbm90YW1tZW50IGF1eCBleHRyw6ltaXTDqXMgb8O5IGxlcyBkb25uw6llcyBzb250IG1vaW5zIG5vbWJyZXVzZXMuCgojIyBFc3RpbWF0ZXVyIMOgIG5veWF1IHZpYSBga3Ntb290aGAKClVuZSBtw6l0aG9kZSBwbHVzIHNpbXBsZSBjb25zaXN0ZSDDoCB1dGlsaXNlciBga3Ntb290aGAgYXZlYyB1biBub3lhdSBnYXVzc2llbiBldCB1bmUgYmFuZGUgZGUgbGlzc2FnZSBmaXjDqWUsIGljaSBgYmFuZHdpZHRoID0gNWAuICBPbiB0cmFjZSBjZXR0ZSBjb3VyYmUgcG91ciBsYSBjb21wYXJlciDDoCBjZWxsZSBvYnRlbnVlIGF2ZWMgYG5wcmVnYC4KCmBgYHtyIGV4NS1rc21vb3RoLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpzbW9vdGhfa3MgPC0ga3Ntb290aCh2ZXJyZXMkQWwsIHZlcnJlcyRSSSwga2VybmVsID0gIm5vcm1hbCIsIGJhbmR3aWR0aCA9IDUsIHgucG9pbnRzID0gdmVycmVzJEFsKQoKcGxvdCh2ZXJyZXMkQWwsIHZlcnJlcyRSSSwKICAgICBwY2ggPSAxNiwgY2V4ID0gMC43LCBjb2wgPSAic3RlZWxibHVlIiwKICAgICB4bGFiID0gIkFsdW1pbml1bSAoJSkiLCB5bGFiID0gIlJJIiwKICAgICBtYWluID0gIkV4ZXJjaWNlIDUg4oCTIGtzbW9vdGggKGJhbmR3aWR0aCA9IDUpIikKZ3JpZCgpCmxpbmVzKHNtb290aF9rcyR4LCBzbW9vdGhfa3MkeSwgY29sID0gImRhcmtvcmFuZ2UiLCBsd2QgPSAyKQpgYGAKCioqT2JzZXJ2YXRpb24gOioqIGxhIGNvdXJiZSBga3Ntb290aGAgZXN0IHJlbGF0aXZlbWVudCBsaXNzZSBldCBzdWl0IGJpZW4gbGEgdGVuZGFuY2UgbW95ZW5uZS4gIENlcGVuZGFudCwgbGUgY2hvaXggZGUgbGEgYmFuZGUgYGJhbmR3aWR0aCA9IDVgIHBldXQgw6p0cmUgYXJiaXRyYWlyZSA7IHVuZSBiYW5kZSB0cm9wIGxhcmdlIHN1cuKAkWxpc3NlIGxlcyBkb25uw6llcywgdGFuZGlzIHF14oCZdW5lIGJhbmRlIHRyb3AgcGV0aXRlIGNvbmR1aXQgw6AgdW5lIGNvdXJiZSBwbHVzIG9uZHVsw6llLgoKIyMgQ29tcGFyYWlzb24gYXZlYyB1biBhanVzdGVtZW50IHBhciBwb2x5bsO0bWVzIGxvY2F1eCAobG9lc3MpCgpFbmZpbiwgb24gYWp1c3RlIGxhIHJlbGF0aW9uIGBSSSB+IEFsYCBwYXIgZGVzIHBvbHluw7RtZXMgbG9jYXV4IHZpYSBgbG9lc3NgIGF2ZWMgdW4gcGFyYW3DqHRyZSBgc3BhbiA9IDAuNWAgZXQgdW5lIGZhbWlsbGUgZ2F1c3NpZW5uZS4gIE9uIHRyYWNlIGxhIGNvdXJiZSBvYnRlbnVlIGV0IG9uIGxhIGNvbXBhcmUgw6AgY2VsbGUgZGUgYGtzbW9vdGhgLgoKYGBge3IgZXg1LWxvZXNzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQp2ZXJyZXNfbG9lc3MgPC0gbG9lc3MoUkkgfiBBbCwgZGF0YSA9IHZlcnJlcywgc3BhbiA9IDAuNSwgZmFtaWx5ID0gImdhdXNzaWFuIikKcHJlZF9sb2VzcyA8LSBwcmVkaWN0KHZlcnJlc19sb2VzcywgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoQWwgPSB2ZXJyZXMkQWwpKQoKcGxvdCh2ZXJyZXMkQWwsIHZlcnJlcyRSSSwKICAgICBwY2ggPSAxNiwgY2V4ID0gMC43LCBjb2wgPSAic3RlZWxibHVlIiwKICAgICB4bGFiID0gIkFsdW1pbml1bSAoJSkiLCB5bGFiID0gIlJJIiwKICAgICBtYWluID0gIkV4ZXJjaWNlIDUg4oCTIENvbXBhcmFpc29uIGRlcyBhanVzdGVtZW50cyIpCmdyaWQoKQpsaW5lcyhzbW9vdGhfa3MkeCwgc21vb3RoX2tzJHksIGNvbCA9ICJkYXJrb3JhbmdlIiwgbHdkID0gMikKbGluZXModmVycmVzJEFsLCBwcmVkX2xvZXNzLCBjb2wgPSAiZm9yZXN0Z3JlZW4iLCBsd2QgPSAyKQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kID0gYygia3Ntb290aCIsICJsb2VzcyIpLAogICAgICAgY29sID0gYygiZGFya29yYW5nZSIsICJmb3Jlc3RncmVlbiIpLCBsdHkgPSAxLCBsd2QgPSAyKQpgYGAKCioqT2JzZXJ2YXRpb24gOioqIGxhIGNvdXJiZSBgbG9lc3NgICh2ZXJ0KSBzdWl0IMOpdHJvaXRlbWVudCBsYSBmb3JtZSBkdSBudWFnZSBldCBwZXV0IGNhcHRlciBkZXMgdmFyaWF0aW9ucyBsb2NhbGVzIGdyw6JjZSDDoCB1biBwYXJhbcOodHJlIGBzcGFuYCBtb2TDqXLDqS4gIExhIGNvdXJiZSBga3Ntb290aGAgKG9yYW5nZSkgZXN0IGzDqWfDqHJlbWVudCBwbHVzIGxpc3NlLiAgU2Vsb24gbGEgdmFsZXVyIGRlIGBzcGFuYCBvdSBkZSBgYmFuZHdpZHRoYCwgbOKAmXVuZSBvdSBs4oCZYXV0cmUgbcOpdGhvZGUgcGV1dCBvZmZyaXIgdW4gbWVpbGxldXIgY29tcHJvbWlzIGVudHJlIGJpYWlzIGV0IHZhcmlhbmNlLgoKCgojIEZJTg==