cat(’

’)

Dataset airquality obsahuje údaje o kvalite ovzdušia a meteorologických podmienkach v New Yorku počas letných mesiacov roku 1973. Sleduje denné hodnoty koncentrácie ozónu, intenzitu slnečného žiarenia, rýchlosť vetra a teplotu, pričom každý záznam je spojený s konkrétnym dňom a mesiacom.

Cieľom týchto dát je skúmať vzťahy medzi poveternostnými faktormi a kvalitou ovzdušia, napríklad ako teplota, slnečné žiarenie a vietor ovplyvňujú koncentráciu ozónu v mestskom prostredí. Dataset sa často používa na regresné analýzy a vizualizácie trendov v ovzduší, ako aj na testovanie štatistických predpokladov v lineárnych a nelineárnych modeloch.

Lineárna regresia

Hypotézy:

H₀ (nulová): Teplota nemá žiadny vplyv na koncentráciu ozónu.

H₁ (alternatívna): Teplota má vplyv na koncentráciu ozónu.

data(airquality)           # načíta vstavaný dataset
airquality <- na.omit(airquality)  # odstráni chýbajúce hodnoty

model <- lm(Ozone ~ Temp, data = airquality)  # lineárna regresia
summary(model)    

Call:
lm(formula = Ozone ~ Temp, data = airquality)

Residuals:
    Min      1Q  Median      3Q     Max 
-40.922 -17.459  -0.874  10.444 118.078 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -147.6461    18.7553  -7.872 2.76e-12 ***
Temp           2.4391     0.2393  10.192  < 2e-16 ***
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 23.92 on 109 degrees of freedom
Multiple R-squared:  0.488, Adjusted R-squared:  0.4833 
F-statistic: 103.9 on 1 and 109 DF,  p-value: < 2.2e-16

Interpretácia koeficientov:

Intercept (-147.65): Ak by teplota bola 0 °F (čo je mimo reálnej oblasti dát, ale formálne v modeli), koncentrácia ozónu by bola záporná – teda v praxi znamená, že model sa vzťahuje len na pozorované teploty.

Temp (2.4391): Pri zvýšení teploty o 1 °F sa koncentrácia ozónu zvýši v priemere o 2.44 jednotky (ppb).

Test významnosti:

p-hodnota pre Temp = < 2e-16, čo je oveľa menšie ako 0.05 → nulovú hypotézu

Sila modelu:

R² = 0.488 znamená, že približne 48.8 % variability ozónu je vysvetlených teplotou. To je stredne silná závislosť – model vystihuje takmer polovicu variácie.

Teplota má štatisticky významný vplyv na koncentráciu ozónu. Dá sa teda povedať, že vyššie teploty sú spojené s vyššou koncentráciou ozónu v ovzduší.

plot(airquality$Temp, airquality$Ozone,
     main = "Vplyv teploty na koncentráciu ozónu",
     xlab = "Teplota (°F)", ylab = "Ozone (ppb)",
     pch = 19, col = "skyblue")
abline(model, col = "red", lwd = 2)

Test odľahlých hodnôt

library(car)
library(lmtest)
library(tseries)

outlier_test <- outlierTest(model)
outlier_test

V dátach sa nachádza jedna odľahlá hodnota (pozorovanie č. 117), ktorá môže mať vplyv na regresnú priamku.

Test heteroskedasticity – Breusch-Pagan test

bptest(model)

    studentized Breusch-Pagan test

data:  model
BP = 1.5088, df = 1, p-value = 0.2193

Model teda nepreukazuje heteroskedasticitu — rezíduá majú približne rovnaký rozptyl

Test autokorelácie – Durbin-Watson

dwtest(model)

    Durbin-Watson test

data:  model
DW = 1.8644, p-value = 0.2123
alternative hypothesis: true autocorrelation is greater than 0

Keďže p-hodnota = 0.2123 > 0.05, → nezamietame nulovú hypotézu.

To znamená, že neexistuje štatisticky významná autokorelácia rezíduí – sú nezávislé, čo je veľmi dobré pre platnosť modelu

Hodnota DW ≈ 2 je ideálna — značí absenciu autokorelácie.


library(ggplot2)
library(ggfortify)
library(patchwork)


# Autoplot vytvorí všetky diagnostické grafy
p <- autoplot(model, smooth.colour = "red", smooth.linetype = "solid", 
              label.size = 3, ncol = 2, nrow = 2) +
  theme_minimal(base_size = 13) +
  theme(
    plot.title = element_text(face = "bold", hjust = 0.5),
    strip.text = element_text(face = "bold", size = 12),
    panel.grid.minor = element_blank()
  )

# Zobraziť 4 grafy spolu
p

library(knitr)
library(kableExtra)

# --- Vytvorenie dátového rámca s informáciami o diagnostických grafoch ---
diagnostika <- data.frame(
  Graf = c("Residuals vs Fitted",
           "Q-Q plot (Normal Q-Q)",
           "Scale-Location (Spread-Location)",
           "Residuals vs Leverage (Cook’s distance)"),
  
  "Čo ukazuje" = c(
    "Závislosť rezíduí od predikovaných hodnôt",
    "Porovnanie rozdelenia rezíduí s teoretickým normálnym rozdelením",
    "Test rovnomernosti rozptylu rezíduí (homoskedasticita)",
    "Detekcia vplyvných alebo odľahlých pozorovaní"
  ),
  
  "Ako dopadol" = c(
    "Rezíduá sú rovnomerne rozložené okolo nulovej osi, bez vzoru",
    "Body ležia približne na priamke",
    "Body sú rovnomerne rozptýlené bez jasného vzoru",
    "Žiadne výrazne vplyvné alebo odľahlé pozorovania"
  ),
  
  "Interpretácia" = c(
    "Predpoklad linearity je splnený",
    "Rezíduá sú takmer normálne rozdelené",
    "Model spĺňa podmienku homoskedasticity",
    "Model neobsahuje významné odľahlé hodnoty"
  ),
  
  stringsAsFactors = FALSE
)

# --- Vytvorenie prehľadnej a pekne formátovanej tabuľky ---
diagnostika %>%
  kable("html", 
        caption = "Diagnostické grafy lineárneho regresného modelu a ich interpretácia",
        align = c("l","l","l","l")) %>%
  kable_styling(full_width = FALSE, 
                bootstrap_options = c("striped", "hover", "condensed", "responsive")) %>%
  row_spec(0, bold = TRUE, color = "white", background = "#4F81BD") %>%
  column_spec(1, bold = TRUE, border_right = TRUE) %>%
  column_spec(2:4, width = "20em")
Diagnostické grafy lineárneho regresného modelu a ich interpretácia
Graf Čo.ukazuje Ako.dopadol Interpretácia
Residuals vs Fitted Závislosť rezíduí od predikovaných hodnôt Rezíduá sú rovnomerne rozložené okolo nulovej osi, bez vzoru Predpoklad linearity je splnený
Q-Q plot (Normal Q-Q) Porovnanie rozdelenia rezíduí s teoretickým normálnym rozdelením Body ležia približne na priamke Rezíduá sú takmer normálne rozdelené
Scale-Location (Spread-Location) Test rovnomernosti rozptylu rezíduí (homoskedasticita) Body sú rovnomerne rozptýlené bez jasného vzoru Model spĺňa podmienku homoskedasticity
Residuals vs Leverage (Cook’s distance) Detekcia vplyvných alebo odľahlých pozorovaní Žiadne výrazne vplyvné alebo odľahlé pozorovania Model neobsahuje významné odľahlé hodnoty
NA

Záver

Analýza lineárnej regresie ukázala, že teplota (Temp) má štatisticky významný vplyv na koncentráciu ozónu v New Yorku. Model vysvetľuje približne 48,8 % variability ozónu, čo predstavuje stredne silnú závislosť.

Diagnostické grafy a testy potvrdili, že model spĺňa základné predpoklady lineárnej regresie:

  • rezíduá sú približne normálne rozdelené a lineárne voči predikovaným hodnotám,

  • rozptyl rezíduí je homogénny (žiadna heteroskedasticita),

  • neexistuje významná autokorelácia rezíduí,

  • prítomná je iba jedna odľahlá hodnota, ktorá nemusí výrazne ovplyvniť výsledky.

Celkovo teda môžeme konštatovať, že vyššie teploty sú spojené s vyššou koncentráciou ozónu, a model je vhodný na približné predpovedanie hodnoty ozónu na základe teploty v rámci pozorovaných dát.

Viacnásobná lineárna regresia

Keďže koncentrácia ozónu môže byť ovplyvnená viacerými faktormi, nie len teplotou, vykonáme viacnásobnú lineárnu regresiu. Do modelu zahrnieme premenné: Temp (teplota), Wind (vietor) a Solar.R (slnečné žiarenie), aby smezistili, ktoré z nich majú štatisticky významný vplyv na koncentráciu ozónu a ako sa ich účinky kombinujú.

Týmto spôsobom získame komplexnejší pohľad na faktory ovplyvňujúce kvalitu ovzdušia v New Yorku.

Hypotézy:

H₀ (nulová): Žiadna z premenných (Temp, Wind, Solar.R) nemá vplyv na koncentráciu ozónu.

H₁ (alternatívna): Aspoň jedna z premenných má štatisticky významný vplyv.

# Načítanie vstavaných dát
data(airquality)

# Odstránenie chýbajúcich hodnôt (inak by model spadol)
airquality <- na.omit(airquality)

# Mnohonásobná lineárna regresia
model2 <- lm(Ozone ~ Temp + Wind + Solar.R, data = airquality)

# Výpis výsledkov
summary(model2)

Call:
lm(formula = Ozone ~ Temp + Wind + Solar.R, data = airquality)

Residuals:
    Min      1Q  Median      3Q     Max 
-40.485 -14.219  -3.551  10.097  95.619 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -64.34208   23.05472  -2.791  0.00623 ** 
Temp          1.65209    0.25353   6.516 2.42e-09 ***
Wind         -3.33359    0.65441  -5.094 1.52e-06 ***
Solar.R       0.05982    0.02319   2.580  0.01124 *  
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 21.18 on 107 degrees of freedom
Multiple R-squared:  0.6059,    Adjusted R-squared:  0.5948 
F-statistic: 54.83 on 3 and 107 DF,  p-value: < 2.2e-16

Model skúma vplyv teploty (Temp), vetra (Wind) a slnečného žiarenia (Solar.R) na koncentráciu ozónu v New Yorku.

Intercept (-64.34): Ak by všetky premenné boli nulové (čo je mimo reálne pozorované hodnoty), koncentrácia ozónu by bola záporná – model je relevantný len v rámci pozorovaných dát.

Temp (1.65): Pri zvýšení teploty o 1 °F sa koncentrácia ozónu zvyšuje v priemere o 1.65 ppb, všetko ostatné nezmenené.

Wind (-3.33): Pri zvýšení rýchlosti vetra o 1 mph sa koncentrácia ozónu znižuje v priemere o 3.33 ppb, ostatné premenné nezmenené.

Solar.R (0.06): Pri zvýšení slnečného žiarenia o 1 jednotku sa koncentrácia ozónu zvyšuje o 0.06 ppb.

Významnosť a sila modelu

Všetky premenné sú štatisticky významné (p < 0.05).

R² = 0.606 → model vysvetľuje približne 60,6 % variability koncentrácie ozónu, čo predstavuje silnejší vzťah než pri jednoduchej regresii.

F-statistic = 54.83, p < 2.2e-16 → model je celkovo štatisticky významný.

Interpretácia

Viacnásobná regresia potvrdzuje, že vyššia teplota a silnejšie slnečné žiarenie zvyšujú koncentráciu ozónu, zatiaľ čo vyšší vietor ju znižuje. Tento model poskytuje komplexnejší pohľad na faktory ovplyvňujúce kvalitu ovzdušia, ako jednoduchá lineárna regresia len s teplotou.

Heteroskedasticita

# Dátová sada
data("airquality")
head(airquality)

# Vynecháme NA hodnoty
air <- na.omit(airquality)

# Model 1 – pôvodný model
model <- lm(Ozone ~ Wind + Temp + Solar.R, data = air)

# Model 2 – logaritmická transformácia závislej premennej
model2 <- lm(log(Ozone) ~ Wind + Temp + Solar.R, data = air)

# Potrebné knižnice
library(ggplot2)
library(patchwork)
# Pôvodný model
p1 <- ggplot(air, aes(x = Wind, y = resid(model)^2)) +
  geom_point(alpha = 0.6) +
  geom_smooth(method = "loess", se = FALSE, color = "red") +
  labs(x = "Wind", y = "Squared Residuals", title = "Residuals² vs Wind") +
  theme_minimal()

p2 <- ggplot(air, aes(x = Temp, y = resid(model)^2)) +
  geom_point(alpha = 0.6) +
  geom_smooth(method = "loess", se = FALSE, color = "red") +
  labs(x = "Temp", y = "Squared Residuals", title = "Residuals² vs Temp") +
  theme_minimal()

p1 + p2

** Log-transformacia závislej premennej **

# Model s log(Ozone)
p3 <- ggplot(air, aes(x = Wind, y = resid(model2)^2)) +
  geom_point(alpha = 0.6) +
  geom_smooth(method = "loess", se = FALSE, color = "red") +
  labs(x = "Wind", y = "Squared Residuals", title = "Residuals² vs Wind (log model)") +
  theme_minimal()

p4 <- ggplot(air, aes(x = Temp, y = resid(model2)^2)) +
  geom_point(alpha = 0.6) +
  geom_smooth(method = "loess", se = FALSE, color = "red") +
  labs(x = "Temp", y = "Squared Residuals", title = "Residuals² vs Temp (log model)") +
  theme_minimal()

p3 + p4

library(lmtest)

# Test pre pôvodný model
bptest(model)

    studentized Breusch-Pagan test

data:  model
BP = 5.0554, df = 3, p-value = 0.1678
# Test pre log model
bptest(model2)

    studentized Breusch-Pagan test

data:  model2
BP = 18.549, df = 3, p-value = 0.0003388
library(lmtest)

# Testy heteroskedasticity
bp1 <- bptest(model)
bp2 <- bptest(model2)

# Výsledky do tabuľky
bp_table <- data.frame(
  Model = c("model", "model2"),
  BP_statistic = c(round(bp1$statistic, 3), round(bp2$statistic, 3)),
  df = c(bp1$parameter, bp2$parameter),
  p_value = c(round(bp1$p.value, 4), round(bp2$p.value, 4)),
  Interpretation = c(
    ifelse(bp1$p.value < 0.05, "Heteroskedasticita prítomná", "Heteroskedasticita neprítomná"),
    ifelse(bp2$p.value < 0.05, "Heteroskedasticita prítomná", "Heteroskedasticita neprítomná")
  )
)

bp_table
NA
#White korekcia pre model 2
# White (heteroskedasticity-consistent) robustné štandardné chyby
library(sandwich)
library(lmtest)

# Výpočet pre model2
coeftest(model2, vcov = vcovHC(model2))

t test of coefficients:

               Estimate  Std. Error t value  Pr(>|t|)    
(Intercept) -0.26213231  0.78449382 -0.3341 0.7389264    
Wind        -0.06156247  0.01921915 -3.2032 0.0017908 ** 
Temp         0.04917112  0.00758673  6.4812 2.865e-09 ***
Solar.R      0.00251518  0.00065566  3.8361 0.0002117 ***
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

V modeli model2 bola prítomná heteroskedasticita (BP test: p = 0.0003).

Preto boli použité White heteroskedasticity-consistent štandardné chyby pomocou funkcie coeftest() z balíka sandwich. Po korekcii zostali všetky vysvetľujúce premenné (Wind, Temp, Solar.R) štatisticky významné (p < 0.05).

Výsledný model je preto možné považovať za robustný voči heteroskedasticite a vhodný na interpretáciu regresných koeficientov.

library(zoo)
library(tseries)
library(lmtest)
library(sandwich)
library(car)
library(MASS)

rm(list=ls())

data("airquality")
udaje <- airquality

# vyhodíme riadky s NA alebo imputujeme
column_medians <- sapply(udaje, median, na.rm = TRUE)

udaje_imputed <- udaje
for (col in names(udaje)) {
  udaje_imputed[[col]][is.na(udaje_imputed[[col]])] <- column_medians[col]
}

udaje <- udaje_imputed
model <- lm(Ozone ~ Temp + Wind + Solar.R, data = udaje)
summary(model)

Call:
lm(formula = Ozone ~ Temp + Wind + Solar.R, data = udaje)

Residuals:
    Min      1Q  Median      3Q     Max 
-37.330 -14.420  -4.931  11.659 103.405 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -39.35406   19.25757  -2.044  0.04276 *  
Temp          1.23295    0.21285   5.793 3.97e-08 ***
Wind         -2.78709    0.55356  -5.035 1.36e-06 ***
Solar.R       0.05696    0.02037   2.796  0.00586 ** 
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 21.32 on 149 degrees of freedom
Multiple R-squared:  0.4721,    Adjusted R-squared:  0.4615 
F-statistic: 44.42 on 3 and 149 DF,  p-value: < 2.2e-16
resettest(model)

    RESET test

data:  model
RESET = 24.149, df1 = 2, df2 = 147, p-value =
8.541e-10

Výsledok RESET testu:

Toto je extrémne malé p (prakticky 0).

To znamená: s veľmi vysokou istotou odmietame H0.

Čiže: model NIE je správne špecifikovaný.

plot(model, which = 1)

Nelineárny model s kvadratickými členmi

model_poly <- lm(Ozone ~ Temp + Wind + Solar.R + I(Temp^2) + I(Wind^2), data = udaje)
summary(model_poly)

Call:
lm(formula = Ozone ~ Temp + Wind + Solar.R + I(Temp^2) + I(Wind^2), 
    data = udaje)

Residuals:
    Min      1Q  Median      3Q     Max 
-43.009  -9.856  -2.672   8.828  91.221 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 317.33945   83.62842   3.795 0.000216 ***
Temp         -7.18897    2.23310  -3.219 0.001582 ** 
Wind        -11.43431    1.98809  -5.751 4.95e-08 ***
Solar.R       0.06068    0.01806   3.360 0.000993 ***
I(Temp^2)     0.05509    0.01464   3.763 0.000242 ***
I(Wind^2)     0.40872    0.09066   4.508 1.33e-05 ***
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 18.88 on 147 degrees of freedom
Multiple R-squared:  0.5918,    Adjusted R-squared:  0.5779 
F-statistic: 42.63 on 5 and 147 DF,  p-value: < 2.2e-16
anova(model, model_poly)
Analysis of Variance Table

Model 1: Ozone ~ Temp + Wind + Solar.R
Model 2: Ozone ~ Temp + Wind + Solar.R + I(Temp^2) + I(Wind^2)
  Res.Df   RSS Df Sum of Sq      F    Pr(>F)    
1    149 67737                                  
2    147 52374  2     15363 21.559 6.159e-09 ***
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
resettest(model_poly)

    RESET test

data:  model_poly
RESET = 3.3975, df1 = 2, df2 = 145, p-value =
0.03614

Po pridaní kvadratických členov Temp² a Wind² sa model výrazne zlepšil.

Nelineárny model lepšie vystihuje reálne vzťahy medzi ozónom a meteorologickými premennými.

Teplota aj vietor majú zakrivený (kvadratický) efekt na množstvo ozónu, čo zodpovedá fyzikálnym a chemickým procesom v atmosfére.

ANOVA potvrdila, že pridané nelineárne členy štatisticky významne zlepšujú model (p < 0.00000001).

RESET test ukazuje, že špecifikácia modelu je podstatne lepšia, ale ešte je tam mierny signál možnej nesprávnej špecifikácie. # Model bez nevýznamných kvadratických členov

model_poly2 <- lm(Ozone ~ Temp + Wind + Solar.R + I(Temp^2), data = udaje)
summary(model_poly2)

Call:
lm(formula = Ozone ~ Temp + Wind + Solar.R + I(Temp^2), data = udaje)

Residuals:
    Min      1Q  Median      3Q     Max 
-42.255 -11.485  -3.296   9.545 108.331 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 349.98700   88.58707   3.951 0.000120 ***
Temp         -9.17912    2.32758  -3.944 0.000123 ***
Wind         -2.74781    0.52114  -5.273 4.68e-07 ***
Solar.R       0.05658    0.01918   2.950 0.003692 ** 
I(Temp^2)     0.06844    0.01524   4.490 1.42e-05 ***
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 20.07 on 148 degrees of freedom
Multiple R-squared:  0.5354,    Adjusted R-squared:  0.5228 
F-statistic: 42.64 on 4 and 148 DF,  p-value: < 2.2e-16

Tento model poskytuje zrozumiteľný a realistický pohľad na faktory ovplyvňujúce tvorbu prízemného ozónu:

Teplota pôsobí nelineárne a jej efekt rastie pri vyšších hodnotách.

Vietor pôsobí ako prirodzený „čistič“ ovzdušia.

Slnečné žiarenie podporuje tvorbu ozónu.

Výsledný nelineárny model teda lepšie zachytáva fyzikálne a chemické procesy, ktoré v skutočnosti prebiehajú, a predstavuje výrazne presnejší odhad ako pôvodný lineárny model.

Dummy premenná a zlom v sklone

udaje$DUM <- ifelse(udaje$Temp < 80, 0, 1)

modelD_auto <- lm(Ozone ~ DUM + Temp + Wind + Solar.R, data = udaje)
summary(modelD_auto)

Call:
lm(formula = Ozone ~ DUM + Temp + Wind + Solar.R, data = udaje)

Residuals:
   Min     1Q Median     3Q    Max 
-38.82 -13.21  -4.48  12.45  98.58 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -6.88953   25.45721  -0.271  0.78705    
DUM         10.85496    5.63328   1.927  0.05590 .  
Temp         0.76125    0.32314   2.356  0.01979 *  
Wind        -2.88001    0.55070  -5.230 5.69e-07 ***
Solar.R      0.05706    0.02019   2.826  0.00537 ** 
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 21.13 on 148 degrees of freedom
Multiple R-squared:  0.485, Adjusted R-squared:  0.4711 
F-statistic: 34.85 on 4 and 148 DF,  p-value: < 2.2e-16
modelD_slope <- lm(Ozone ~ Temp + I(DUM*Temp) + Wind + Solar.R, data = udaje)
summary(modelD_slope)

Call:
lm(formula = Ozone ~ Temp + I(DUM * Temp) + Wind + Solar.R, data = udaje)

Residuals:
   Min     1Q Median     3Q    Max 
-39.07 -13.00  -4.67  12.08  98.34 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)    1.69404   26.14235   0.065  0.94842    
Temp           0.63666    0.33492   1.901  0.05926 .  
I(DUM * Temp)  0.15641    0.06846   2.285  0.02375 *  
Wind          -2.88661    0.54761  -5.271 4.71e-07 ***
Solar.R        0.05689    0.02009   2.832  0.00528 ** 
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 21.03 on 148 degrees of freedom
Multiple R-squared:  0.4901,    Adjusted R-squared:  0.4763 
F-statistic: 35.56 on 4 and 148 DF,  p-value: < 2.2e-16
anova(model, modelD_slope)
Analysis of Variance Table

Model 1: Ozone ~ Temp + Wind + Solar.R
Model 2: Ozone ~ Temp + I(DUM * Temp) + Wind + Solar.R
  Res.Df   RSS Df Sum of Sq      F  Pr(>F)  
1    149 67737                              
2    148 65429  1    2307.7 5.2201 0.02375 *
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
resettest(modelD_slope)

    RESET test

data:  modelD_slope
RESET = 23.402, df1 = 2, df2 = 146, p-value =
1.528e-09

Dummy premenná sama o sebe nemá presvedčivý vplyv na úroveň ozónu.

Interakcia DUM × Temp je však štatisticky významná, čo znamená, že vplyv teploty na ozón sa medzi dvoma skupinami líši.

Teplota zvyšuje ozón výraznejšie v skupine, ktorá má DUM = 1.

Model s interakciou je štatisticky lepší (ANOVA).

RESET test naznačuje, že ani tento model nie je úplný a treba nelineárne členy.

Box–Cox transformácia

Povedzme, že λ = 0.4.

Ztransformujeme Ozone:

boxcox(model)


lambda <- 0.4
udaje$Ozone_tr <- (udaje$Ozone^lambda - 1) / lambda

model_bc <- lm(Ozone_tr ~ Temp + Wind + Solar.R, data = udaje)
summary(model_bc)

Call:
lm(formula = Ozone_tr ~ Temp + Wind + Solar.R, data = udaje)

Residuals:
   Min     1Q Median     3Q    Max 
-5.176 -1.427 -0.249  1.502  6.675 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -2.177750   1.838578  -1.184 0.238111    
Temp         0.141994   0.020321   6.988 8.70e-11 ***
Wind        -0.250570   0.052850  -4.741 4.92e-06 ***
Solar.R      0.007416   0.001945   3.813 0.000201 ***
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2.036 on 149 degrees of freedom
Multiple R-squared:  0.532, Adjusted R-squared:  0.5225 
F-statistic: 56.45 on 3 and 149 DF,  p-value: < 2.2e-16
resettest(model_bc)

    RESET test

data:  model_bc
RESET = 12.571, df1 = 2, df2 = 147, p-value =
9.12e-06

Transformácia pomohla stabilizovať varianciu a priblížiť normalitu reziduálov, ale lineárny model stále nie je dokonalý.

Box-Cox neodhalil nelinearitu vo všetkých prediktoroch – preto RESET test zostáva významný. Autokorelácia rezíduí – Príklad na vstavanom datasete airquality

V tejto časti urobíme kompletnú analýzu autokorelácie rezíduí pri modeli postavenom na dátach airquality. Dataset obsahuje denné merania kvality ovzdušia v New Yorku (Ozone, Solar.R, Wind, Temp, mesiac a deň) počas roku 1973.

Ako vysvetľovanú premennú použijeme Ozone a ako vysvetľujúce premenne Wind, Temp, Solar.R.

  1. Odhad pôvodného regresného modelu data(“airquality”)

model <- lm(Ozone ~ Wind + Temp + Solar.R, data = airquality) summary(model)

Autokorelácia rezíduí

Autokorelácia rezíduí skúma situáciu, keď chyba v čase t je systematicky spätá s chybou v čase t−1.

Dôsledky autokorelácie

Autokorelácia rezíduí spôsobuje:

odhady koeficientov sú nestranné, ale neefektívne,

štandardné chyby sú podhodnotené,

p-hodnoty sa javia menšie → falošná štatistická významnosť,

t-testy a F-testy sú skreslené.

Detekcia autokorelácie

  1. Grafická analýza fitted vs. actual library(ggplot2)
airquality$fitted <- fitted(model)
ggplot(airquality, aes(x = 1:nrow(airquality), y = Ozone)) +
  geom_point(color = "steelblue", size = 2) +
  geom_line(aes(y = fitted), color = "red", size = 1) +
  labs(
    title = "Ozone Levels: Empirické vs. Fitted hodnoty",
    x = "Čas (index pozorovania)",
    y = "Ozone"
  ) +
  theme_minimal()

Interpretácia: Vidíme súvislé úseky, kde empirické hodnoty ležia dlhší čas nad alebo pod fitted hodnotou. To naznačuje možnú autokoreláciu rezíduí.

ACF graf rezíduí

res <- residuals(model)
acf(res, lag.max = 4, main = "Autokorelačná funkcia rezíduí")

Interpretácia: Ak sú všetky stĺpce v rámci 95 % intervalov spoľahlivosti, nepozorujeme významnú autokoreláciu. V prípade airquality väčšinou prvé lagy vychádzajú na hrane, čo môže znamenať miernu autokoreláciu.

Durbin–Watsonov test

library(lmtest)
dwtest(model)

    Durbin-Watson test

data:  model
DW = 1.6372, p-value = 0.009281
alternative hypothesis: true autocorrelation is greater than 0

Interpretácia:

DW < 2 → pozitívna autokorelácia,

p-value < 0.05 → štatisticky významná autokorelácia 1. rádu.

DW test má obmedzenia (nesmie byť oneskorená y ako regresor).

Breusch–Godfrey test

BG test umožňuje testovať autokoreláciu s ľubovoľným počtom lagov.

bgtest(model, order = 1)

    Breusch-Godfrey test for serial correlation of order
    up to 1

data:  model
LM test = 5.0683, df = 1, p-value = 0.02437

Interpretácia: BG test nezamieta H₀ → nepreukazuje autokoreláciu rezíduí pri lag=1.

Tak ako v pôvodnom texte: DW test a BG test môžu dávať rozdielne výsledky.

Riešenie autokorelácie

Dynamizácia modelu – Koyckova rovnica

Urobíme lag premennú Ozone:

library(dplyr)
airquality2 <- airquality %>% 
  mutate(Ozone_lag1 = lag(Ozone))

Odhad AR(1) modelu:

model_koyck <- lm(Ozone ~ Wind + Temp + Solar.R + Ozone_lag1,
                  data = airquality2)
summary(model_koyck)

Call:
lm(formula = Ozone ~ Wind + Temp + Solar.R + Ozone_lag1, data = airquality2)

Residuals:
    Min      1Q  Median      3Q     Max 
-36.181 -14.049  -2.253  11.019 100.245 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -57.24680   27.49168  -2.082 0.040150 *  
Wind         -2.78087    0.73040  -3.807 0.000256 ***
Temp          1.43733    0.33694   4.266 4.91e-05 ***
Solar.R       0.04855    0.02459   1.974 0.051446 .  
Ozone_lag1    0.14398    0.08642   1.666 0.099195 .  
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 21.19 on 90 degrees of freedom
  (58 observations deleted due to missingness)
Multiple R-squared:  0.6132,    Adjusted R-squared:  0.596 
F-statistic: 35.67 on 4 and 90 DF,  p-value: < 2.2e-16

Interpretácia:

Koeficient pri Ozone_lag1 väčšinou vychádza kladný a < 1 → zotrvačnosť Ozone.

Regresory často stratia štatistickú významnosť (kvôli multikolinearite a dynamike).

Adjusted R² sa zvyčajne nezlepší oproti pôvodnému modelu.

Durbin–Watsonov test po dynamizácii

dwtest(model_koyck)

    Durbin-Watson test

data:  model_koyck
DW = 2.1164, p-value = 0.6872
alternative hypothesis: true autocorrelation is greater than 0

Výsledok býva bližšie k 2 → autokorelácia sa oslabila.

Newey–West robustné štandardné chyby

Ak nechceme meniť model, môžeme opraviť štandardné chyby:

library(sandwich)
library(lmtest)
coeftest(model, vcov = NeweyWest(model))

t test of coefficients:

              Estimate Std. Error t value  Pr(>|t|)    
(Intercept) -39.354064  21.762757 -1.8083 0.0725730 .  
Temp          1.232953   0.248974  4.9521 1.966e-06 ***
Wind         -2.787086   0.701009 -3.9758 0.0001090 ***
Solar.R       0.056958   0.016139  3.5293 0.0005545 ***
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Výsledok:

niektoré p-hodnoty narastú,

eliminuje falošnú štatistickú významnosť spôsobenú autokoreláciou.

Záverečné zhrnutie

Autokorelácia rezíduí je dôležitý predpoklad OLS, ktorý pri časových radoch často neplatí.

V datasete airquality Durbin–Watson test zvyčajne naznačí autokoreláciu, BG test menej.

Koyckov model autokoreláciu zmierňuje, ale môže priniesť stratu významnosti regresorov.

Newey–West robustné štandardné chyby sú praktické riešenie bez zmeny modelu.

Moderným nástrojom pri dlhších časových radoch sú ARIMA modely.

Multikolinearita

Korelačná matica 1

data("airquality")
df <- na.omit(airquality)

cor(df[, c("Solar.R", "Wind", "Temp", "Month", "Day")])
            Solar.R        Wind       Temp        Month
Solar.R  1.00000000 -0.12718345  0.2940876 -0.074066683
Wind    -0.12718345  1.00000000 -0.4971897 -0.194495804
Temp     0.29408764 -0.49718972  1.0000000  0.403971709
Month   -0.07406668 -0.19449580  0.4039717  1.000000000
Day     -0.05775380  0.04987102 -0.0965458 -0.009001079
                 Day
Solar.R -0.057753801
Wind     0.049871017
Temp    -0.096545800
Month   -0.009001079
Day      1.000000000
library(corrplot)
corrplot(cor(df[, c("Solar.R", "Wind", "Temp", "Month", "Day")]),
         method = "color", addCoef.col = "black")

Scatterloptová korelačná matica


library(psych)

df <- na.omit(airquality)

pairs.panels(
  df[, c("Solar.R", "Wind", "Temp", "Month", "Day")],
  method = "pearson",   # typ korelácie
  hist.col = "lightgray",
  density = TRUE,
  ellipses = TRUE,
  lm = TRUE,            # regresná čiara
  smoother = TRUE,      # LOESS krivka
  gap = 0.5,            # viac priestoru = prehľadnosť
  scale = FALSE,
  cex.cor = 2           # veľké čísla korelácií = čitateľné
)

Scatterplotová korelačná matica zobrazuje vzťahy medzi premennými Solar.R, Wind, Temp, Month a Day z datasetu airquality.

Na diagonále sa nachádzajú histogramy, ktoré ukazujú distribúciu jednotlivých premenných. Mimo diagonály sú znázornené scatterploty doplnené o:

LOESS hladkú krivku (červená čiara), ktorá ilustruje tvar nelineárnych vzťahov,

elipsu variancie (čierna elipsa), ktorá ukazuje koncentráciu bodov,

regresný stredový bod (červený bod).

V pravom hornom trojuholníku sú uvedené aj korelačné koeficienty medzi dvojicami premenných.

Základné zistenia

Wind a Temp vykazujú stredne silnú negatívnu koreláciu (cca –0.50), čo naznačuje, že pri vyššej teplote sa vietor zvyčajne znižuje.

Temp a Solar.R majú mierne pozitívnu koreláciu (cca 0.29), čo znamená, že vyššie teploty sú zväčša spojené s väčším slnečným žiarením.

Solar.R a Wind vykazujú len slabú koreláciu (~0.13).

Premenné Month a Day majú veľmi slabé až nulové korelácie s ostatnými premennými, čo naznačuje, že samotný dátum (v rámci mesiaca alebo mesiac v roku) nie je silným determinantom fyzikálnych veličín v tomto datasete.

Celkové hodnotenie

Matica ukazuje, že medzi premennými datasetu airquality sa vyskytujú len mierne korelácie, bez známok extrémne silnej multikolinearity. Výnimkou je stredne silný negatívny vzťah medzi teplotou a rýchlosťou vetra, ktorý môže byť dôležitý pri regresnom modelovaní.

VIF (Variance Inflation Factor)

library(car)

model <- lm(Ozone ~ Solar.R + Wind + Temp + Month + Day, data = df)
vif(model)
 Solar.R     Wind     Temp    Month      Day 
1.152087 1.329317 1.722477 1.257273 1.011105 

Interpretácia

VIF > 5 = zvýšená multikolinearita

VIF > 10 = vážny problém

Podľa výsledku žiadna z premenných nespôsobuje zvýšenú kolinearitu, alebo vážny problém.

Condition Number + Condition Index

library(olsrr)

ols_eigen_cindex(model)
NA

Condition Index > 15 = slabá multikolinearita

CI > 30 = vážna multikolinearita

Interpretácia: Z testu sme zistili, že CI = 36.05 znamená, že aspoň dve premenné sú takmer lineárne závislé.

Pri CI > 30 nestačí vedieť, že problém existuje. Musíme zistiť:

→ ktoré premenné spolu kolineárne rastú

library(olsrr)
ols_coll_diag(model)
Tolerance and Variance Inflation Factor
---------------------------------------


Eigenvalue and Condition Index
------------------------------

Čo to znamená?

Toto NIE JE multikolinearita medzi vysvetľujúcimi premennými. Je to len numerický artefakt, kde sa intercept „bije“ s Temp, čo je úplne bežné pri stredne škálovaných premenných.

Záver CI + variancie: Žiadna vážna multikolinearita medzi Solar.R, Wind, Temp, Month, Day.

# Načítanie datasetu
data("airquality")

# Odstránenie riadkov s NA
airquality <- na.omit(airquality)

# Štandardizácia vybraných premenných
airquality_scaled <- airquality
airquality_scaled$Solar_c <- scale(airquality$Solar)
airquality_scaled$Wind_c  <- scale(airquality$Wind)
airquality_scaled$Temp_c  <- scale(airquality$Temp)
airquality_scaled$Month_c <- scale(airquality$Month)
airquality_scaled$Day_c   <- scale(airquality$Day)

# Vytvorenie lineárneho modelu
model_scaled <- lm(Ozone ~ Solar_c + Wind_c + Temp_c + Month_c + Day_c,
                   data = airquality_scaled)

# Matica návrhu
X <- model.matrix(model_scaled)[, -1]

# Výpočet XtX a vlastných čísel
XtX <- t(X) %*% X
eig <- eigen(XtX)

# Výpočet condition number
condition_number <- sqrt(max(eig$values) / min(eig$values))
condition_number
[1] 2.18327

Condition number (číslo podmienky) ukazuje, ako „stabilný“ je model z hľadiska multikolinearity.

Hodnota blízka 1 znamená, že premenné sú takmer ortogonálne (nezávislé), čo je ideálne.

Záver

Na základe výpočtu condition number pre náš lineárny model (hodnota ≈ 2,18) môžeme konštatovať, že multikolinearita v našom datasete nie je problém. Hodnota čísla podmienky je veľmi nízka, čo naznačuje, že vysvetľujúce premenné (Solar_c, Wind_c, Temp_c, Month_c, Day_c) sú voči sebe relatívne nezávislé a model je stabilný.

Podporu tomuto záveru poskytujú aj hodnoty VIF (Variance Inflation Factor):

Solar.R: 1.15

Wind: 1.33

Temp: 1.72

Month: 1.26

Day: 1.01

Všetky hodnoty VIF sú výrazne pod kritickou hranicou 5, čo opäť potvrdzuje, že multikolinearita v dátach nie je významná. Odhady regresných koeficientov sú teda spoľahlivé a výsledky regresnej analýzy nie sú skreslené nadmernou koreláciou medzi premennými.

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKYXV0aG9yOiAiVGVyw6l6aWEgUnlww6Frb3bDoSBzIHBvdcW+aXTDrW0gZG9zdHVwbsO9Y2gga8OzZG92IGEgQ2hhdCBHUFQiCm91dHB1dDogaHRtbF9ub3RlYm9vawpodG1sX2RvY3VtZW50OgogICAgY3NzOiAic3R5bGUuY3NzIgotLS0KCmNhdCgnCgpgYGB7PWh0bWx9CjxzdHlsZT4KYm9keSB7CiAgYmFja2dyb3VuZC1jb2xvcjogI2U2ZjJmZjsgIAp9Cjwvc3R5bGU+CmBgYAoKJykKCkRhdGFzZXQgYWlycXVhbGl0eSBvYnNhaHVqZSDDumRhamUgbyBrdmFsaXRlIG92emR1xaFpYSBhIG1ldGVvcm9sb2dpY2vDvWNoIHBvZG1pZW5rYWNoIHYgTmV3IFlvcmt1IHBvxI1hcyBsZXRuw71jaCBtZXNpYWNvdiByb2t1IDE5NzMuIFNsZWR1amUgZGVubsOpIGhvZG5vdHkga29uY2VudHLDoWNpZSBvesOzbnUsIGludGVueml0dSBzbG5lxI1uw6lobyDFvmlhcmVuaWEsIHLDvWNobG9zxaUgdmV0cmEgYSB0ZXBsb3R1LCBwcmnEjW9tIGthxb5kw70gesOhem5hbSBqZSBzcG9qZW7DvSBzIGtvbmtyw6l0bnltIGTFiG9tIGEgbWVzaWFjb20uCgpDaWXEvm9tIHTDvWNodG8gZMOhdCBqZSBza8O6bWHFpSB2esWlYWh5IG1lZHppIHBvdmV0ZXJub3N0bsO9bWkgZmFrdG9ybWkgYSBrdmFsaXRvdSBvdnpkdcWhaWEsIG5hcHLDrWtsYWQgYWtvIHRlcGxvdGEsIHNsbmXEjW7DqSDFvmlhcmVuaWUgYSB2aWV0b3Igb3ZwbHl2xYh1asO6IGtvbmNlbnRyw6FjaXUgb3rDs251IHYgbWVzdHNrb20gcHJvc3RyZWTDrS4gRGF0YXNldCBzYSDEjWFzdG8gcG91xb7DrXZhIG5hIHJlZ3Jlc27DqSBhbmFsw716eSBhIHZpenVhbGl6w6FjaWUgdHJlbmRvdiB2IG92emR1xaHDrSwgYWtvIGFqIG5hIHRlc3RvdmFuaWUgxaF0YXRpc3RpY2vDvWNoIHByZWRwb2tsYWRvdiB2IGxpbmXDoXJueWNoIGEgbmVsaW5lw6FybnljaCBtb2RlbG9jaC4KCiMgTGluZcOhcm5hIHJlZ3Jlc2lhCgoqKkh5cG90w6l6eToqKgoKKipI4oKAIChudWxvdsOhKToqKiBUZXBsb3RhIG5lbcOhIMW+aWFkbnkgdnBseXYgbmEga29uY2VudHLDoWNpdSBvesOzbnUuCgoqKkjigoEgKGFsdGVybmF0w612bmEpOioqIFRlcGxvdGEgbcOhIHZwbHl2IG5hIGtvbmNlbnRyw6FjaXUgb3rDs251LgoKYGBge3J9CmRhdGEoYWlycXVhbGl0eSkgICAgICAgICAgICMgbmHEjcOtdGEgdnN0YXZhbsO9IGRhdGFzZXQKYWlycXVhbGl0eSA8LSBuYS5vbWl0KGFpcnF1YWxpdHkpICAjIG9kc3Ryw6FuaSBjaMO9YmFqw7pjZSBob2Rub3R5Cgptb2RlbCA8LSBsbShPem9uZSB+IFRlbXAsIGRhdGEgPSBhaXJxdWFsaXR5KSAgIyBsaW5lw6FybmEgcmVncmVzaWEKc3VtbWFyeShtb2RlbCkgICAgCmBgYAoKIyMjIEludGVycHJldMOhY2lhIGtvZWZpY2llbnRvdjoKCioqSW50ZXJjZXB0ICgtMTQ3LjY1KSoqOiBBayBieSB0ZXBsb3RhIGJvbGEgMCDCsEYgKMSNbyBqZSBtaW1vIHJlw6FsbmVqIG9ibGFzdGkgZMOhdCwgYWxlIGZvcm3DoWxuZSB2IG1vZGVsaSksIGtvbmNlbnRyw6FjaWEgb3rDs251IGJ5IGJvbGEgesOhcG9ybsOhIOKAkyB0ZWRhIHYgcHJheGkgem5hbWVuw6EsIMW+ZSBtb2RlbCBzYSB2esWlYWh1amUgbGVuIG5hIHBvem9yb3ZhbsOpIHRlcGxvdHkuCgoqKlRlbXAgKDIuNDM5MSk6KiogUHJpIHp2w73FoWVuw60gdGVwbG90eSBvIDEgwrBGIHNhIGtvbmNlbnRyw6FjaWEgb3rDs251IHp2w73FoWkgdiBwcmllbWVyZSBvIDIuNDQgamVkbm90a3kgKHBwYikuCgojIyBUZXN0IHbDvXpuYW1ub3N0aToKCioqcC1ob2Rub3RhKiogcHJlIFRlbXAgPSBcPCAqKjJlLTE2KiosIMSNbyBqZSBvdmXEvmEgbWVuxaFpZSBha28gMC4wNSDihpIgbnVsb3bDuiBoeXBvdMOpenUKCiMjIFNpbGEgbW9kZWx1OgoKKipSwrIgPSAwLjQ4OCoqIHpuYW1lbsOhLCDFvmUgcHJpYmxpxb5uZSAqKjQ4LjggJSB2YXJpYWJpbGl0eSBvesOzbnUqKiBqZSB2eXN2ZXRsZW7DvWNoIHRlcGxvdG91LiBUbyBqZSAqKnN0cmVkbmUgc2lsbsOhIHrDoXZpc2xvc8WlKiog4oCTIG1vZGVsIHZ5c3RpaHVqZSB0YWttZXIgcG9sb3ZpY3UgdmFyacOhY2llLgoKVGVwbG90YSBtw6EgxaF0YXRpc3RpY2t5IHbDvXpuYW1uw70gdnBseXYgbmEga29uY2VudHLDoWNpdSBvesOzbnUuIETDoSBzYSB0ZWRhIHBvdmVkYcWlLCDFvmUgdnnFocWhaWUgdGVwbG90eSBzw7ogc3BvamVuw6kgcyB2ecWhxaFvdSBrb25jZW50csOhY2lvdSBvesOzbnUgdiBvdnpkdcWhw60uCgpgYGB7cn0KcGxvdChhaXJxdWFsaXR5JFRlbXAsIGFpcnF1YWxpdHkkT3pvbmUsCiAgICAgbWFpbiA9ICJWcGx5diB0ZXBsb3R5IG5hIGtvbmNlbnRyw6FjaXUgb3rDs251IiwKICAgICB4bGFiID0gIlRlcGxvdGEgKMKwRikiLCB5bGFiID0gIk96b25lIChwcGIpIiwKICAgICBwY2ggPSAxOSwgY29sID0gInNreWJsdWUiKQphYmxpbmUobW9kZWwsIGNvbCA9ICJyZWQiLCBsd2QgPSAyKQoKYGBgCgojIyBUZXN0IG9kxL5haGzDvWNoIGhvZG7DtHQKCmBgYHtyfQpsaWJyYXJ5KGNhcikKbGlicmFyeShsbXRlc3QpCmxpYnJhcnkodHNlcmllcykKCm91dGxpZXJfdGVzdCA8LSBvdXRsaWVyVGVzdChtb2RlbCkKb3V0bGllcl90ZXN0CmBgYAoKViBkw6F0YWNoIHNhIG5hY2jDoWR6YSBqZWRuYSBvZMS+YWhsw6EgaG9kbm90YSAocG96b3JvdmFuaWUgxI0uIDExNyksIGt0b3LDoSBtw7TFvmUgbWHFpSB2cGx5diBuYSByZWdyZXNuw7ogcHJpYW1rdS4KCiMjIFRlc3QgaGV0ZXJvc2tlZGFzdGljaXR5IOKAkyBCcmV1c2NoLVBhZ2FuIHRlc3QKCmBgYHtyfQpicHRlc3QobW9kZWwpCmBgYAoKTW9kZWwgdGVkYSBuZXByZXVrYXp1amUgaGV0ZXJvc2tlZGFzdGljaXR1IOKAlCByZXrDrWR1w6EgbWFqw7ogcHJpYmxpxb5uZSByb3ZuYWvDvSByb3pwdHlsCgojIyBUZXN0IGF1dG9rb3JlbMOhY2llIOKAkyBEdXJiaW4tV2F0c29uCgpgYGB7cn0KZHd0ZXN0KG1vZGVsKQpgYGAKCktlxI/FvmUgcC1ob2Rub3RhID0gMC4yMTIzIFw+IDAuMDUsIOKGkiBuZXphbWlldGFtZSBudWxvdsO6IGh5cG90w6l6dS4KClRvIHpuYW1lbsOhLCDFvmUgbmVleGlzdHVqZSDFoXRhdGlzdGlja3kgdsO9em5hbW7DoSBhdXRva29yZWzDoWNpYSByZXrDrWR1w60g4oCTIHPDuiBuZXrDoXZpc2zDqSwgxI1vIGplIHZlxL5taSBkb2Jyw6kgcHJlIHBsYXRub3PFpSBtb2RlbHUKCkhvZG5vdGEgRFcg4omIIDIgamUgaWRlw6FsbmEg4oCUIHpuYcSNw60gYWJzZW5jaXUgYXV0b2tvcmVsw6FjaWUuCgpgYGB7cn0KCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ2ZvcnRpZnkpCmxpYnJhcnkocGF0Y2h3b3JrKQoKCiMgQXV0b3Bsb3Qgdnl0dm9yw60gdsWhZXRreSBkaWFnbm9zdGlja8OpIGdyYWZ5CnAgPC0gYXV0b3Bsb3QobW9kZWwsIHNtb290aC5jb2xvdXIgPSAicmVkIiwgc21vb3RoLmxpbmV0eXBlID0gInNvbGlkIiwgCiAgICAgICAgICAgICAgbGFiZWwuc2l6ZSA9IDMsIG5jb2wgPSAyLCBucm93ID0gMikgKwogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTMpICsKICB0aGVtZSgKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLAogICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBzaXplID0gMTIpLAogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKQogICkKCiMgWm9icmF6acWlIDQgZ3JhZnkgc3BvbHUKcAoKYGBgCgpgYGB7cn0KbGlicmFyeShrbml0cikKbGlicmFyeShrYWJsZUV4dHJhKQoKIyAtLS0gVnl0dm9yZW5pZSBkw6F0b3bDqWhvIHLDoW1jYSBzIGluZm9ybcOhY2lhbWkgbyBkaWFnbm9zdGlja8O9Y2ggZ3JhZm9jaCAtLS0KZGlhZ25vc3Rpa2EgPC0gZGF0YS5mcmFtZSgKICBHcmFmID0gYygiUmVzaWR1YWxzIHZzIEZpdHRlZCIsCiAgICAgICAgICAgIlEtUSBwbG90IChOb3JtYWwgUS1RKSIsCiAgICAgICAgICAgIlNjYWxlLUxvY2F0aW9uIChTcHJlYWQtTG9jYXRpb24pIiwKICAgICAgICAgICAiUmVzaWR1YWxzIHZzIExldmVyYWdlIChDb29r4oCZcyBkaXN0YW5jZSkiKSwKICAKICAixIxvIHVrYXp1amUiID0gYygKICAgICJaw6F2aXNsb3PFpSByZXrDrWR1w60gb2QgcHJlZGlrb3ZhbsO9Y2ggaG9kbsO0dCIsCiAgICAiUG9yb3ZuYW5pZSByb3pkZWxlbmlhIHJlesOtZHXDrSBzIHRlb3JldGlja8O9bSBub3Jtw6FsbnltIHJvemRlbGVuw61tIiwKICAgICJUZXN0IHJvdm5vbWVybm9zdGkgcm96cHR5bHUgcmV6w61kdcOtIChob21vc2tlZGFzdGljaXRhKSIsCiAgICAiRGV0ZWtjaWEgdnBseXZuw71jaCBhbGVibyBvZMS+YWhsw71jaCBwb3pvcm92YW7DrSIKICApLAogIAogICJBa28gZG9wYWRvbCIgPSBjKAogICAgIlJlesOtZHXDoSBzw7ogcm92bm9tZXJuZSByb3psb8W+ZW7DqSBva29sbyBudWxvdmVqIG9zaSwgYmV6IHZ6b3J1IiwKICAgICJCb2R5IGxlxb5pYSBwcmlibGnFvm5lIG5hIHByaWFta2UiLAogICAgIkJvZHkgc8O6IHJvdm5vbWVybmUgcm96cHTDvWxlbsOpIGJleiBqYXNuw6lobyB2em9ydSIsCiAgICAixb1pYWRuZSB2w71yYXpuZSB2cGx5dm7DqSBhbGVibyBvZMS+YWhsw6kgcG96b3JvdmFuaWEiCiAgKSwKICAKICAiSW50ZXJwcmV0w6FjaWEiID0gYygKICAgICJQcmVkcG9rbGFkIGxpbmVhcml0eSBqZSBzcGxuZW7DvSIsCiAgICAiUmV6w61kdcOhIHPDuiB0YWttZXIgbm9ybcOhbG5lIHJvemRlbGVuw6kiLAogICAgIk1vZGVsIHNwxLrFiGEgcG9kbWllbmt1IGhvbW9za2VkYXN0aWNpdHkiLAogICAgIk1vZGVsIG5lb2JzYWh1amUgdsO9em5hbW7DqSBvZMS+YWhsw6kgaG9kbm90eSIKICApLAogIAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopCgojIC0tLSBWeXR2b3JlbmllIHByZWjEvmFkbmVqIGEgcGVrbmUgZm9ybcOhdG92YW5laiB0YWJ1xL5reSAtLS0KZGlhZ25vc3Rpa2EgJT4lCiAga2FibGUoImh0bWwiLCAKICAgICAgICBjYXB0aW9uID0gIkRpYWdub3N0aWNrw6kgZ3JhZnkgbGluZcOhcm5laG8gcmVncmVzbsOpaG8gbW9kZWx1IGEgaWNoIGludGVycHJldMOhY2lhIiwKICAgICAgICBhbGlnbiA9IGMoImwiLCJsIiwibCIsImwiKSkgJT4lCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UsIAogICAgICAgICAgICAgICAgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIsICJyZXNwb25zaXZlIikpICU+JQogIHJvd19zcGVjKDAsIGJvbGQgPSBUUlVFLCBjb2xvciA9ICJ3aGl0ZSIsIGJhY2tncm91bmQgPSAiIzRGODFCRCIpICU+JQogIGNvbHVtbl9zcGVjKDEsIGJvbGQgPSBUUlVFLCBib3JkZXJfcmlnaHQgPSBUUlVFKSAlPiUKICBjb2x1bW5fc3BlYygyOjQsIHdpZHRoID0gIjIwZW0iKQoKYGBgCgojIyBaw6F2ZXIKCkFuYWzDvXphIGxpbmXDoXJuZWogcmVncmVzaWUgdWvDoXphbGEsIMW+ZSB0ZXBsb3RhIChUZW1wKSBtw6EgxaF0YXRpc3RpY2t5IHbDvXpuYW1uw70gdnBseXYgbmEga29uY2VudHLDoWNpdSBvesOzbnUgdiBOZXcgWW9ya3UuIE1vZGVsIHZ5c3ZldMS+dWplIHByaWJsacW+bmUgNDgsOCAlIHZhcmlhYmlsaXR5IG96w7NudSwgxI1vIHByZWRzdGF2dWplIHN0cmVkbmUgc2lsbsO6IHrDoXZpc2xvc8WlLgoKRGlhZ25vc3RpY2vDqSBncmFmeSBhIHRlc3R5IHBvdHZyZGlsaSwgxb5lIG1vZGVsIHNwxLrFiGEgesOha2xhZG7DqSBwcmVkcG9rbGFkeSBsaW5lw6FybmVqIHJlZ3Jlc2llOgoKLSAgIHJlesOtZHXDoSBzw7ogcHJpYmxpxb5uZSBub3Jtw6FsbmUgcm96ZGVsZW7DqSBhIGxpbmXDoXJuZSB2b8SNaSBwcmVkaWtvdmFuw71tIGhvZG5vdMOhbSwKCi0gICByb3pwdHlsIHJlesOtZHXDrSBqZSBob21vZ8Opbm55ICjFvmlhZG5hIGhldGVyb3NrZWRhc3RpY2l0YSksCgotICAgbmVleGlzdHVqZSB2w716bmFtbsOhIGF1dG9rb3JlbMOhY2lhIHJlesOtZHXDrSwKCi0gICBwcsOtdG9tbsOhIGplIGliYSBqZWRuYSBvZMS+YWhsw6EgaG9kbm90YSwga3RvcsOhIG5lbXVzw60gdsO9cmF6bmUgb3ZwbHl2bmnFpSB2w71zbGVka3kuCgpDZWxrb3ZvIHRlZGEgbcO0xb5lbWUga29uxaF0YXRvdmHFpSwgxb5lIHZ5xaHFoWllIHRlcGxvdHkgc8O6IHNwb2plbsOpIHMgdnnFocWhb3Uga29uY2VudHLDoWNpb3Ugb3rDs251LCBhIG1vZGVsIGplIHZob2Ruw70gbmEgcHJpYmxpxb5uw6kgcHJlZHBvdmVkYW5pZSBob2Rub3R5IG96w7NudSBuYSB6w6FrbGFkZSB0ZXBsb3R5IHYgcsOhbWNpIHBvem9yb3ZhbsO9Y2ggZMOhdC4KCiMjIFZpYWNuw6Fzb2Juw6EgbGluZcOhcm5hIHJlZ3Jlc2lhCgpLZcSPxb5lIGtvbmNlbnRyw6FjaWEgb3rDs251IG3DtMW+ZSBiecWlIG92cGx5dm5lbsOhIHZpYWNlcsO9bWkgZmFrdG9ybWksIG5pZSBsZW4gdGVwbG90b3UsIHZ5a29uw6FtZSB2aWFjbsOhc29ibsO6IGxpbmXDoXJudSByZWdyZXNpdS4gRG8gbW9kZWx1IHphaHJuaWVtZSBwcmVtZW5uw6k6ICoqVGVtcCAodGVwbG90YSksIFdpbmQgKHZpZXRvcikgYSBTb2xhci5SIChzbG5lxI1uw6kgxb5pYXJlbmllKSoqLCBhYnkgc21lemlzdGlsaSwga3RvcsOpIHogbmljaCBtYWrDuiDFoXRhdGlzdGlja3kgdsO9em5hbW7DvSB2cGx5diBuYSBrb25jZW50csOhY2l1IG96w7NudSBhIGFrbyBzYSBpY2ggw7rEjWlua3kga29tYmludWrDui4KClTDvW10byBzcMO0c29ib20gesOtc2thbWUga29tcGxleG5lasWhw60gcG9oxL5hZCBuYSBmYWt0b3J5IG92cGx5dsWIdWrDumNlIGt2YWxpdHUgb3Z6ZHXFoWlhIHYgTmV3IFlvcmt1LgoKIyBIeXBvdMOpenk6CgpI4oKAIChudWxvdsOhKTogxb1pYWRuYSB6IHByZW1lbm7DvWNoIChUZW1wLCBXaW5kLCBTb2xhci5SKSBuZW3DoSB2cGx5diBuYSBrb25jZW50csOhY2l1IG96w7NudS4KCkjigoEgKGFsdGVybmF0w612bmEpOiBBc3BvxYggamVkbmEgeiBwcmVtZW5uw71jaCBtw6EgxaF0YXRpc3RpY2t5IHbDvXpuYW1uw70gdnBseXYuCgpgYGB7cn0KIyBOYcSNw610YW5pZSB2c3RhdmFuw71jaCBkw6F0CmRhdGEoYWlycXVhbGl0eSkKCiMgT2RzdHLDoW5lbmllIGNow71iYWrDumNpY2ggaG9kbsO0dCAoaW5hayBieSBtb2RlbCBzcGFkb2wpCmFpcnF1YWxpdHkgPC0gbmEub21pdChhaXJxdWFsaXR5KQoKIyBNbm9ob27DoXNvYm7DoSBsaW5lw6FybmEgcmVncmVzaWEKbW9kZWwyIDwtIGxtKE96b25lIH4gVGVtcCArIFdpbmQgKyBTb2xhci5SLCBkYXRhID0gYWlycXVhbGl0eSkKCiMgVsO9cGlzIHbDvXNsZWRrb3YKc3VtbWFyeShtb2RlbDIpCgpgYGAKCk1vZGVsIHNrw7ptYSB2cGx5diB0ZXBsb3R5IChUZW1wKSwgdmV0cmEgKFdpbmQpIGEgc2xuZcSNbsOpaG8gxb5pYXJlbmlhIChTb2xhci5SKSBuYSBrb25jZW50csOhY2l1IG96w7NudSB2IE5ldyBZb3JrdS4KCioqSW50ZXJjZXB0ICgtNjQuMzQpOioqIEFrIGJ5IHbFoWV0a3kgcHJlbWVubsOpIGJvbGkgbnVsb3bDqSAoxI1vIGplIG1pbW8gcmXDoWxuZSBwb3pvcm92YW7DqSBob2Rub3R5KSwga29uY2VudHLDoWNpYSBvesOzbnUgYnkgYm9sYSB6w6Fwb3Juw6Eg4oCTIG1vZGVsIGplIHJlbGV2YW50bsO9IGxlbiB2IHLDoW1jaSBwb3pvcm92YW7DvWNoIGTDoXQuCgoqKlRlbXAgKDEuNjUpKio6IFByaSB6dsO9xaFlbsOtIHRlcGxvdHkgbyAxIMKwRiBzYSBrb25jZW50csOhY2lhIG96w7NudSB6dnnFoXVqZSB2IHByaWVtZXJlIG8gMS42NSBwcGIsIHbFoWV0a28gb3N0YXRuw6kgbmV6bWVuZW7DqS4KCioqV2luZCAoLTMuMzMpKio6IFByaSB6dsO9xaFlbsOtIHLDvWNobG9zdGkgdmV0cmEgbyAxIG1waCBzYSBrb25jZW50csOhY2lhIG96w7NudSB6bmnFvnVqZSB2IHByaWVtZXJlIG8gMy4zMyBwcGIsIG9zdGF0bsOpIHByZW1lbm7DqSBuZXptZW5lbsOpLgoKKipTb2xhci5SICgwLjA2KSoqOiBQcmkgenbDvcWhZW7DrSBzbG5lxI1uw6lobyDFvmlhcmVuaWEgbyAxIGplZG5vdGt1IHNhIGtvbmNlbnRyw6FjaWEgb3rDs251IHp2ecWhdWplIG8gMC4wNiBwcGIuCgojIyBWw716bmFtbm9zxaUgYSBzaWxhIG1vZGVsdQoKKipWxaFldGt5IHByZW1lbm7DqSBzw7ogxaF0YXRpc3RpY2t5IHbDvXpuYW1uw6kgKHAgXDwgMC4wNSkqKi4KClLCsiA9IDAuNjA2IOKGkiBtb2RlbCB2eXN2ZXTEvnVqZSBwcmlibGnFvm5lIDYwLDYgJSB2YXJpYWJpbGl0eSBrb25jZW50csOhY2llIG96w7NudSwgxI1vIHByZWRzdGF2dWplIHNpbG5lasWhw60gdnrFpWFoIG5lxb4gcHJpIGplZG5vZHVjaGVqIHJlZ3Jlc2lpLgoKRi1zdGF0aXN0aWMgPSA1NC44MywgcCBcPCAyLjJlLTE2IOKGkiBtb2RlbCBqZSBjZWxrb3ZvIMWhdGF0aXN0aWNreSB2w716bmFtbsO9LgoKIyMgSW50ZXJwcmV0w6FjaWEKClZpYWNuw6Fzb2Juw6EgcmVncmVzaWEgcG90dnJkenVqZSwgxb5lIHZ5xaHFoWlhIHRlcGxvdGEgYSBzaWxuZWrFoWllIHNsbmXEjW7DqSDFvmlhcmVuaWUgenZ5xaF1asO6IGtvbmNlbnRyw6FjaXUgb3rDs251LCB6YXRpYcS+IMSNbyB2ecWhxaHDrSB2aWV0b3IganUgem5pxb51amUuIFRlbnRvIG1vZGVsIHBvc2t5dHVqZSBrb21wbGV4bmVqxaHDrSBwb2jEvmFkIG5hIGZha3Rvcnkgb3ZwbHl2xYh1asO6Y2Uga3ZhbGl0dSBvdnpkdcWhaWEsIGFrbyBqZWRub2R1Y2jDoSBsaW5lw6FybmEgcmVncmVzaWEgbGVuIHMgdGVwbG90b3UuCgojIyBIZXRlcm9za2VkYXN0aWNpdGEKCmBgYHtyfQojIETDoXRvdsOhIHNhZGEKZGF0YSgiYWlycXVhbGl0eSIpCmhlYWQoYWlycXVhbGl0eSkKCiMgVnluZWNow6FtZSBOQSBob2Rub3R5CmFpciA8LSBuYS5vbWl0KGFpcnF1YWxpdHkpCgojIE1vZGVsIDEg4oCTIHDDtHZvZG7DvSBtb2RlbAptb2RlbCA8LSBsbShPem9uZSB+IFdpbmQgKyBUZW1wICsgU29sYXIuUiwgZGF0YSA9IGFpcikKCiMgTW9kZWwgMiDigJMgbG9nYXJpdG1pY2vDoSB0cmFuc2Zvcm3DoWNpYSB6w6F2aXNsZWogcHJlbWVubmVqCm1vZGVsMiA8LSBsbShsb2coT3pvbmUpIH4gV2luZCArIFRlbXAgKyBTb2xhci5SLCBkYXRhID0gYWlyKQoKIyBQb3RyZWJuw6kga25pxb5uaWNlCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShwYXRjaHdvcmspCgpgYGAKYGBge3J9CiMgUMO0dm9kbsO9IG1vZGVsCnAxIDwtIGdncGxvdChhaXIsIGFlcyh4ID0gV2luZCwgeSA9IHJlc2lkKG1vZGVsKV4yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG9lc3MiLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJyZWQiKSArCiAgbGFicyh4ID0gIldpbmQiLCB5ID0gIlNxdWFyZWQgUmVzaWR1YWxzIiwgdGl0bGUgPSAiUmVzaWR1YWxzwrIgdnMgV2luZCIpICsKICB0aGVtZV9taW5pbWFsKCkKCnAyIDwtIGdncGxvdChhaXIsIGFlcyh4ID0gVGVtcCwgeSA9IHJlc2lkKG1vZGVsKV4yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG9lc3MiLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJyZWQiKSArCiAgbGFicyh4ID0gIlRlbXAiLCB5ID0gIlNxdWFyZWQgUmVzaWR1YWxzIiwgdGl0bGUgPSAiUmVzaWR1YWxzwrIgdnMgVGVtcCIpICsKICB0aGVtZV9taW5pbWFsKCkKCnAxICsgcDIKCmBgYAoqKiBMb2ctdHJhbnNmb3JtYWNpYSB6w6F2aXNsZWogcHJlbWVubmVqICoqIApgYGB7cn0KIyBNb2RlbCBzIGxvZyhPem9uZSkKcDMgPC0gZ2dwbG90KGFpciwgYWVzKHggPSBXaW5kLCB5ID0gcmVzaWQobW9kZWwyKV4yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG9lc3MiLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJyZWQiKSArCiAgbGFicyh4ID0gIldpbmQiLCB5ID0gIlNxdWFyZWQgUmVzaWR1YWxzIiwgdGl0bGUgPSAiUmVzaWR1YWxzwrIgdnMgV2luZCAobG9nIG1vZGVsKSIpICsKICB0aGVtZV9taW5pbWFsKCkKCnA0IDwtIGdncGxvdChhaXIsIGFlcyh4ID0gVGVtcCwgeSA9IHJlc2lkKG1vZGVsMileMikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC42KSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxvZXNzIiwgc2UgPSBGQUxTRSwgY29sb3IgPSAicmVkIikgKwogIGxhYnMoeCA9ICJUZW1wIiwgeSA9ICJTcXVhcmVkIFJlc2lkdWFscyIsIHRpdGxlID0gIlJlc2lkdWFsc8KyIHZzIFRlbXAgKGxvZyBtb2RlbCkiKSArCiAgdGhlbWVfbWluaW1hbCgpCgpwMyArIHA0CgpgYGAKYGBge3J9CmxpYnJhcnkobG10ZXN0KQoKIyBUZXN0IHByZSBww7R2b2Ruw70gbW9kZWwKYnB0ZXN0KG1vZGVsKQoKIyBUZXN0IHByZSBsb2cgbW9kZWwKYnB0ZXN0KG1vZGVsMikKCmBgYApgYGB7cn0KbGlicmFyeShsbXRlc3QpCgojIFRlc3R5IGhldGVyb3NrZWRhc3RpY2l0eQpicDEgPC0gYnB0ZXN0KG1vZGVsKQpicDIgPC0gYnB0ZXN0KG1vZGVsMikKCiMgVsO9c2xlZGt5IGRvIHRhYnXEvmt5CmJwX3RhYmxlIDwtIGRhdGEuZnJhbWUoCiAgTW9kZWwgPSBjKCJtb2RlbCIsICJtb2RlbDIiKSwKICBCUF9zdGF0aXN0aWMgPSBjKHJvdW5kKGJwMSRzdGF0aXN0aWMsIDMpLCByb3VuZChicDIkc3RhdGlzdGljLCAzKSksCiAgZGYgPSBjKGJwMSRwYXJhbWV0ZXIsIGJwMiRwYXJhbWV0ZXIpLAogIHBfdmFsdWUgPSBjKHJvdW5kKGJwMSRwLnZhbHVlLCA0KSwgcm91bmQoYnAyJHAudmFsdWUsIDQpKSwKICBJbnRlcnByZXRhdGlvbiA9IGMoCiAgICBpZmVsc2UoYnAxJHAudmFsdWUgPCAwLjA1LCAiSGV0ZXJvc2tlZGFzdGljaXRhIHByw610b21uw6EiLCAiSGV0ZXJvc2tlZGFzdGljaXRhIG5lcHLDrXRvbW7DoSIpLAogICAgaWZlbHNlKGJwMiRwLnZhbHVlIDwgMC4wNSwgIkhldGVyb3NrZWRhc3RpY2l0YSBwcsOtdG9tbsOhIiwgIkhldGVyb3NrZWRhc3RpY2l0YSBuZXByw610b21uw6EiKQogICkKKQoKYnBfdGFibGUKCmBgYApgYGB7cn0KI1doaXRlIGtvcmVrY2lhIHByZSBtb2RlbCAyCiMgV2hpdGUgKGhldGVyb3NrZWRhc3RpY2l0eS1jb25zaXN0ZW50KSByb2J1c3Ruw6kgxaF0YW5kYXJkbsOpIGNoeWJ5CmxpYnJhcnkoc2FuZHdpY2gpCmxpYnJhcnkobG10ZXN0KQoKIyBWw71wb8SNZXQgcHJlIG1vZGVsMgpjb2VmdGVzdChtb2RlbDIsIHZjb3YgPSB2Y292SEMobW9kZWwyKSkKCmBgYApWIG1vZGVsaSBtb2RlbDIgYm9sYSBwcsOtdG9tbsOhIGhldGVyb3NrZWRhc3RpY2l0YSAoQlAgdGVzdDogcCA9IDAuMDAwMykuCgpQcmV0byBib2xpIHBvdcW+aXTDqSBXaGl0ZSBoZXRlcm9za2VkYXN0aWNpdHktY29uc2lzdGVudCDFoXRhbmRhcmRuw6kgY2h5YnkgcG9tb2NvdSBmdW5rY2llIGNvZWZ0ZXN0KCkgeiBiYWzDrWthIHNhbmR3aWNoLgpQbyBrb3Jla2NpaSB6b3N0YWxpIHbFoWV0a3kgdnlzdmV0xL51asO6Y2UgcHJlbWVubsOpIChXaW5kLCBUZW1wLCBTb2xhci5SKSDFoXRhdGlzdGlja3kgdsO9em5hbW7DqSAocCA8IDAuMDUpLgoKVsO9c2xlZG7DvSBtb2RlbCBqZSBwcmV0byBtb8W+bsOpIHBvdmHFvm92YcWlIHphIHJvYnVzdG7DvSB2b8SNaSBoZXRlcm9za2VkYXN0aWNpdGUgYSB2aG9kbsO9IG5hIGludGVycHJldMOhY2l1IHJlZ3Jlc27DvWNoIGtvZWZpY2llbnRvdi4KYGBge3J9CmxpYnJhcnkoem9vKQpsaWJyYXJ5KHRzZXJpZXMpCmxpYnJhcnkobG10ZXN0KQpsaWJyYXJ5KHNhbmR3aWNoKQpsaWJyYXJ5KGNhcikKbGlicmFyeShNQVNTKQoKcm0obGlzdD1scygpKQoKZGF0YSgiYWlycXVhbGl0eSIpCnVkYWplIDwtIGFpcnF1YWxpdHkKCiMgdnlob2TDrW1lIHJpYWRreSBzIE5BIGFsZWJvIGltcHV0dWplbWUKY29sdW1uX21lZGlhbnMgPC0gc2FwcGx5KHVkYWplLCBtZWRpYW4sIG5hLnJtID0gVFJVRSkKCnVkYWplX2ltcHV0ZWQgPC0gdWRhamUKZm9yIChjb2wgaW4gbmFtZXModWRhamUpKSB7CiAgdWRhamVfaW1wdXRlZFtbY29sXV1baXMubmEodWRhamVfaW1wdXRlZFtbY29sXV0pXSA8LSBjb2x1bW5fbWVkaWFuc1tjb2xdCn0KCnVkYWplIDwtIHVkYWplX2ltcHV0ZWQKCmBgYApgYGB7cn0KbW9kZWwgPC0gbG0oT3pvbmUgfiBUZW1wICsgV2luZCArIFNvbGFyLlIsIGRhdGEgPSB1ZGFqZSkKc3VtbWFyeShtb2RlbCkKCmBgYApgYGB7cn0KcmVzZXR0ZXN0KG1vZGVsKQoKYGBgCgpWw71zbGVkb2sgUkVTRVQgdGVzdHU6IAoKVG90byBqZSBleHRyw6ltbmUgbWFsw6kgcCAocHJha3RpY2t5IDApLgoKVG8gem5hbWVuw6E6IHMgdmXEvm1pIHZ5c29rb3UgaXN0b3RvdSBvZG1pZXRhbWUgSDAuCgrEjGnFvmU6IG1vZGVsIE5JRSBqZSBzcHLDoXZuZSDFoXBlY2lmaWtvdmFuw70uCmBgYHtyfQpwbG90KG1vZGVsLCB3aGljaCA9IDEpCgpgYGAKIyBOZWxpbmXDoXJueSBtb2RlbCBzIGt2YWRyYXRpY2vDvW1pIMSNbGVubWkgCmBgYHtyfQptb2RlbF9wb2x5IDwtIGxtKE96b25lIH4gVGVtcCArIFdpbmQgKyBTb2xhci5SICsgSShUZW1wXjIpICsgSShXaW5kXjIpLCBkYXRhID0gdWRhamUpCnN1bW1hcnkobW9kZWxfcG9seSkKCmFub3ZhKG1vZGVsLCBtb2RlbF9wb2x5KQpyZXNldHRlc3QobW9kZWxfcG9seSkKCmBgYApQbyBwcmlkYW7DrSBrdmFkcmF0aWNrw71jaCDEjWxlbm92IFRlbXDCsiBhIFdpbmTCsiBzYSBtb2RlbCB2w71yYXpuZSB6bGVwxaFpbC4KCk5lbGluZcOhcm55IG1vZGVsIGxlcMWhaWUgdnlzdGlodWplIHJlw6FsbmUgdnrFpWFoeSBtZWR6aSBvesOzbm9tIGEgbWV0ZW9yb2xvZ2lja8O9bWkgcHJlbWVubsO9bWkuCgpUZXBsb3RhIGFqIHZpZXRvciBtYWrDuiB6YWtyaXZlbsO9IChrdmFkcmF0aWNrw70pIGVmZWt0IG5hIG1ub8W+c3R2byBvesOzbnUsIMSNbyB6b2Rwb3ZlZMOhIGZ5emlrw6FsbnltIGEgY2hlbWlja8O9bSBwcm9jZXNvbSB2IGF0bW9zZsOpcmUuCgpBTk9WQSBwb3R2cmRpbGEsIMW+ZSBwcmlkYW7DqSBuZWxpbmXDoXJuZSDEjWxlbnkgxaF0YXRpc3RpY2t5IHbDvXpuYW1uZSB6bGVwxaF1asO6IG1vZGVsIChwIDwgMC4wMDAwMDAwMSkuCgpSRVNFVCB0ZXN0IHVrYXp1amUsIMW+ZSDFoXBlY2lmaWvDoWNpYSBtb2RlbHUgamUgcG9kc3RhdG5lIGxlcMWhaWEsIGFsZSBlxaF0ZSBqZSB0YW0gbWllcm55IHNpZ27DoWwgbW/Fvm5laiBuZXNwcsOhdm5laiDFoXBlY2lmaWvDoWNpZS4KIyBNb2RlbCBiZXogbmV2w716bmFtbsO9Y2gga3ZhZHJhdGlja8O9Y2ggxI1sZW5vdgpgYGB7cn0KbW9kZWxfcG9seTIgPC0gbG0oT3pvbmUgfiBUZW1wICsgV2luZCArIFNvbGFyLlIgKyBJKFRlbXBeMiksIGRhdGEgPSB1ZGFqZSkKc3VtbWFyeShtb2RlbF9wb2x5MikKYGBgCgpUZW50byBtb2RlbCBwb3NreXR1amUgenJvenVtaXRlxL5uw70gYSByZWFsaXN0aWNrw70gcG9oxL5hZCBuYSBmYWt0b3J5IG92cGx5dsWIdWrDumNlIHR2b3JidSBwcsOtemVtbsOpaG8gb3rDs251OgoKVGVwbG90YSBww7Rzb2LDrSBuZWxpbmXDoXJuZSBhIGplaiBlZmVrdCByYXN0aWUgcHJpIHZ5xaHFocOtY2ggaG9kbm90w6FjaC4KClZpZXRvciBww7Rzb2LDrSBha28gcHJpcm9kemVuw70g4oCexI1pc3RpxI3igJwgb3Z6ZHXFoWlhLgoKU2xuZcSNbsOpIMW+aWFyZW5pZSBwb2Rwb3J1amUgdHZvcmJ1IG96w7NudS4KClbDvXNsZWRuw70gbmVsaW5lw6FybnkgbW9kZWwgdGVkYSBsZXDFoWllIHphY2h5dMOhdmEgZnl6aWvDoWxuZSBhIGNoZW1pY2vDqSBwcm9jZXN5LCBrdG9yw6kgdiBza3V0b8SNbm9zdGkgcHJlYmllaGFqw7osIGEgcHJlZHN0YXZ1amUgdsO9cmF6bmUgcHJlc25lasWhw60gb2RoYWQgYWtvIHDDtHZvZG7DvSBsaW5lw6FybnkgbW9kZWwuCgojIER1bW15IHByZW1lbm7DoSBhIHpsb20gdiBza2xvbmUKCmBgYHtyfQp1ZGFqZSREVU0gPC0gaWZlbHNlKHVkYWplJFRlbXAgPCA4MCwgMCwgMSkKCm1vZGVsRF9hdXRvIDwtIGxtKE96b25lIH4gRFVNICsgVGVtcCArIFdpbmQgKyBTb2xhci5SLCBkYXRhID0gdWRhamUpCnN1bW1hcnkobW9kZWxEX2F1dG8pCgptb2RlbERfc2xvcGUgPC0gbG0oT3pvbmUgfiBUZW1wICsgSShEVU0qVGVtcCkgKyBXaW5kICsgU29sYXIuUiwgZGF0YSA9IHVkYWplKQpzdW1tYXJ5KG1vZGVsRF9zbG9wZSkKCmFub3ZhKG1vZGVsLCBtb2RlbERfc2xvcGUpCnJlc2V0dGVzdChtb2RlbERfc2xvcGUpCgpgYGAKRHVtbXkgcHJlbWVubsOhIHNhbWEgbyBzZWJlIG5lbcOhIHByZXN2ZWTEjWl2w70gdnBseXYgbmEgw7pyb3ZlxYggb3rDs251LgoKSW50ZXJha2NpYSBEVU0gw5cgVGVtcCBqZSB2xaFhayDFoXRhdGlzdGlja3kgdsO9em5hbW7DoSwgxI1vIHpuYW1lbsOhLCDFvmUgdnBseXYgdGVwbG90eSBuYSBvesOzbiBzYSBtZWR6aSBkdm9tYSBza3VwaW5hbWkgbMOtxaFpLgoKVGVwbG90YSB6dnnFoXVqZSBvesOzbiB2w71yYXpuZWrFoWllIHYgc2t1cGluZSwga3RvcsOhIG3DoSBEVU0gPSAxLgoKTW9kZWwgcyBpbnRlcmFrY2lvdSBqZSDFoXRhdGlzdGlja3kgbGVwxaHDrSAoQU5PVkEpLgoKUkVTRVQgdGVzdCBuYXpuYcSNdWplLCDFvmUgYW5pIHRlbnRvIG1vZGVsIG5pZSBqZSDDunBsbsO9IGEgdHJlYmEgbmVsaW5lw6FybmUgxI1sZW55LgoKIyBCb3jigJNDb3ggdHJhbnNmb3Jtw6FjaWEKClBvdmVkem1lLCDFvmUgzrsgPSAwLjQuCgpadHJhbnNmb3JtdWplbWUgT3pvbmU6CmBgYHtyfQpib3hjb3gobW9kZWwpCgpsYW1iZGEgPC0gMC40CnVkYWplJE96b25lX3RyIDwtICh1ZGFqZSRPem9uZV5sYW1iZGEgLSAxKSAvIGxhbWJkYQoKbW9kZWxfYmMgPC0gbG0oT3pvbmVfdHIgfiBUZW1wICsgV2luZCArIFNvbGFyLlIsIGRhdGEgPSB1ZGFqZSkKc3VtbWFyeShtb2RlbF9iYykKcmVzZXR0ZXN0KG1vZGVsX2JjKQpgYGAKClRyYW5zZm9ybcOhY2lhIHBvbW9obGEgc3RhYmlsaXpvdmHFpSB2YXJpYW5jaXUgYSBwcmlibMOtxb5pxaUgbm9ybWFsaXR1IHJlemlkdcOhbG92LCBhbGUgbGluZcOhcm55IG1vZGVsIHN0w6FsZSBuaWUgamUgZG9rb25hbMO9LgoKQm94LUNveCBuZW9kaGFsaWwgbmVsaW5lYXJpdHUgdm8gdsWhZXRrw71jaCBwcmVkaWt0b3JvY2gg4oCTIHByZXRvIFJFU0VUIHRlc3Qgem9zdMOhdmEgdsO9em5hbW7DvS4KQXV0b2tvcmVsw6FjaWEgcmV6w61kdcOtIOKAkyBQcsOta2xhZCBuYSB2c3RhdmFub20gZGF0YXNldGUgYWlycXVhbGl0eQoKViB0ZWp0byDEjWFzdGkgdXJvYsOtbWUga29tcGxldG7DuiBhbmFsw716dSBhdXRva29yZWzDoWNpZSByZXrDrWR1w60gcHJpIG1vZGVsaSBwb3N0YXZlbm9tIG5hIGTDoXRhY2ggYWlycXVhbGl0eS4gRGF0YXNldCBvYnNhaHVqZSBkZW5uw6kgbWVyYW5pYSBrdmFsaXR5IG92emR1xaFpYSB2IE5ldyBZb3JrdSAoT3pvbmUsIFNvbGFyLlIsIFdpbmQsIFRlbXAsIG1lc2lhYyBhIGRlxYgpIHBvxI1hcyByb2t1IDE5NzMuCgpBa28gdnlzdmV0xL5vdmFuw7ogcHJlbWVubsO6IHBvdcW+aWplbWUgT3pvbmUgYSBha28gdnlzdmV0xL51asO6Y2UgcHJlbWVubmUgV2luZCwgVGVtcCwgU29sYXIuUi4KCjEuIE9kaGFkIHDDtHZvZG7DqWhvIHJlZ3Jlc27DqWhvIG1vZGVsdQpkYXRhKCJhaXJxdWFsaXR5IikKCm1vZGVsIDwtIGxtKE96b25lIH4gV2luZCArIFRlbXAgKyBTb2xhci5SLCBkYXRhID0gYWlycXVhbGl0eSkKc3VtbWFyeShtb2RlbCkKCiMgQXV0b2tvcmVsw6FjaWEgcmV6w61kdcOtCgpBdXRva29yZWzDoWNpYSByZXrDrWR1w60gc2vDum1hIHNpdHXDoWNpdSwga2XEjyBjaHliYSB2IMSNYXNlIHQgamUgc3lzdGVtYXRpY2t5IHNww6R0w6EgcyBjaHlib3UgdiDEjWFzZSB04oiSMS4KCgojIyBEw7RzbGVka3kgYXV0b2tvcmVsw6FjaWUKCkF1dG9rb3JlbMOhY2lhIHJlesOtZHXDrSBzcMO0c29idWplOgoKb2RoYWR5IGtvZWZpY2llbnRvdiBzw7ogbmVzdHJhbm7DqSwgYWxlIG5lZWZla3TDrXZuZSwKCsWhdGFuZGFyZG7DqSBjaHlieSBzw7ogcG9kaG9kbm90ZW7DqSwKCnAtaG9kbm90eSBzYSBqYXZpYSBtZW7FoWllIOKGkiBmYWxvxaFuw6EgxaF0YXRpc3RpY2vDoSB2w716bmFtbm9zxaUsCgp0LXRlc3R5IGEgRi10ZXN0eSBzw7ogc2tyZXNsZW7DqS4KCiMjIERldGVrY2lhIGF1dG9rb3JlbMOhY2llCkEpIEdyYWZpY2vDoSBhbmFsw716YSBmaXR0ZWQgdnMuIGFjdHVhbApsaWJyYXJ5KGdncGxvdDIpCgoKYGBge3J9CmFpcnF1YWxpdHkkZml0dGVkIDwtIGZpdHRlZChtb2RlbCkKZ2dwbG90KGFpcnF1YWxpdHksIGFlcyh4ID0gMTpucm93KGFpcnF1YWxpdHkpLCB5ID0gT3pvbmUpKSArCiAgZ2VvbV9wb2ludChjb2xvciA9ICJzdGVlbGJsdWUiLCBzaXplID0gMikgKwogIGdlb21fbGluZShhZXMoeSA9IGZpdHRlZCksIGNvbG9yID0gInJlZCIsIHNpemUgPSAxKSArCiAgbGFicygKICAgIHRpdGxlID0gIk96b25lIExldmVsczogRW1waXJpY2vDqSB2cy4gRml0dGVkIGhvZG5vdHkiLAogICAgeCA9ICLEjGFzIChpbmRleCBwb3pvcm92YW5pYSkiLAogICAgeSA9ICJPem9uZSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgoKSW50ZXJwcmV0w6FjaWE6ClZpZMOtbWUgc8O6dmlzbMOpIMO6c2VreSwga2RlIGVtcGlyaWNrw6kgaG9kbm90eSBsZcW+aWEgZGxoxaHDrSDEjWFzIG5hZCBhbGVibyBwb2QgZml0dGVkIGhvZG5vdG91LiBUbyBuYXpuYcSNdWplIG1vxb5uw7ogYXV0b2tvcmVsw6FjaXUgcmV6w61kdcOtLgoKIyMgQUNGIGdyYWYgcmV6w61kdcOtCgpgYGB7cn0KcmVzIDwtIHJlc2lkdWFscyhtb2RlbCkKYWNmKHJlcywgbGFnLm1heCA9IDQsIG1haW4gPSAiQXV0b2tvcmVsYcSNbsOhIGZ1bmtjaWEgcmV6w61kdcOtIikKYGBgCgoKSW50ZXJwcmV0w6FjaWE6CkFrIHPDuiB2xaFldGt5IHN0xLpwY2UgdiByw6FtY2kgOTUgJSBpbnRlcnZhbG92IHNwb8S+YWhsaXZvc3RpLCBuZXBvem9ydWplbWUgdsO9em5hbW7DuiBhdXRva29yZWzDoWNpdS4gViBwcsOtcGFkZSBhaXJxdWFsaXR5IHbDpMSNxaFpbm91IHBydsOpIGxhZ3kgdnljaMOhZHphasO6IG5hIGhyYW5lLCDEjW8gbcO0xb5lIHpuYW1lbmHFpSBtaWVybnUgYXV0b2tvcmVsw6FjaXUuCgojIyBEdXJiaW7igJNXYXRzb25vdiB0ZXN0CmBgYHtyfQpsaWJyYXJ5KGxtdGVzdCkKZHd0ZXN0KG1vZGVsKQpgYGAKCgpJbnRlcnByZXTDoWNpYToKCkRXIDwgMiDihpIgcG96aXTDrXZuYSBhdXRva29yZWzDoWNpYSwKCgpwLXZhbHVlIDwgMC4wNSDihpIgxaF0YXRpc3RpY2t5IHbDvXpuYW1uw6EgYXV0b2tvcmVsw6FjaWEgMS4gcsOhZHUuCgpEVyB0ZXN0IG3DoSBvYm1lZHplbmlhIChuZXNtaWUgYnnFpSBvbmVza29yZW7DoSB5IGFrbyByZWdyZXNvcikuCgojIyAgQnJldXNjaOKAk0dvZGZyZXkgdGVzdAoKQkcgdGVzdCB1bW/FvsWIdWplIHRlc3RvdmHFpSBhdXRva29yZWzDoWNpdSBzIMS+dWJvdm/Evm7DvW0gcG/EjXRvbSBsYWdvdi4KCmBgYHtyfQpiZ3Rlc3QobW9kZWwsIG9yZGVyID0gMSkKYGBgCgpJbnRlcnByZXTDoWNpYToKQkcgdGVzdCBuZXphbWlldGEgSOKCgCDihpIgbmVwcmV1a2F6dWplIGF1dG9rb3JlbMOhY2l1IHJlesOtZHXDrSBwcmkgbGFnPTEuCgpUYWsgYWtvIHYgcMO0dm9kbm9tIHRleHRlOgpEVyB0ZXN0IGEgQkcgdGVzdCBtw7TFvnUgZMOhdmHFpSByb3pkaWVsbmUgdsO9c2xlZGt5LgoKIyBSaWXFoWVuaWUgYXV0b2tvcmVsw6FjaWUKCiMjIER5bmFtaXrDoWNpYSBtb2RlbHUg4oCTIEtveWNrb3ZhIHJvdm5pY2EKClVyb2LDrW1lIGxhZyBwcmVtZW5uw7ogT3pvbmU6CgpgYGB7cn0KbGlicmFyeShkcGx5cikKYWlycXVhbGl0eTIgPC0gYWlycXVhbGl0eSAlPiUgCiAgbXV0YXRlKE96b25lX2xhZzEgPSBsYWcoT3pvbmUpKQpgYGAKCgpPZGhhZCBBUigxKSBtb2RlbHU6CmBgYHtyfQptb2RlbF9rb3ljayA8LSBsbShPem9uZSB+IFdpbmQgKyBUZW1wICsgU29sYXIuUiArIE96b25lX2xhZzEsCiAgICAgICAgICAgICAgICAgIGRhdGEgPSBhaXJxdWFsaXR5MikKc3VtbWFyeShtb2RlbF9rb3ljaykKYGBgCgpJbnRlcnByZXTDoWNpYToKCktvZWZpY2llbnQgcHJpIE96b25lX2xhZzEgdsOkxI3FoWlub3UgdnljaMOhZHphIGtsYWRuw70gYSA8IDEg4oaSIHpvdHJ2YcSNbm9zxaUgT3pvbmUuCgpSZWdyZXNvcnkgxI1hc3RvIHN0cmF0aWEgxaF0YXRpc3RpY2vDuiB2w716bmFtbm9zxaUgKGt2w7RsaSBtdWx0aWtvbGluZWFyaXRlIGEgZHluYW1pa2UpLgoKQWRqdXN0ZWQgUsKyIHNhIHp2ecSNYWpuZSBuZXpsZXDFocOtIG9wcm90aSBww7R2b2Ruw6ltdSBtb2RlbHUuCgojIyBEdXJiaW7igJNXYXRzb25vdiB0ZXN0IHBvIGR5bmFtaXrDoWNpaQpgYGB7cn0KZHd0ZXN0KG1vZGVsX2tveWNrKQpgYGAKCgpWw71zbGVkb2sgYsO9dmEgYmxpxb7FoWllIGsgMiDihpIgYXV0b2tvcmVsw6FjaWEgc2Egb3NsYWJpbGEuCgojIyBOZXdleeKAk1dlc3Qgcm9idXN0bsOpIMWhdGFuZGFyZG7DqSBjaHlieQoKQWsgbmVjaGNlbWUgbWVuacWlIG1vZGVsLCBtw7TFvmVtZSBvcHJhdmnFpSDFoXRhbmRhcmRuw6kgY2h5Ynk6CgpgYGB7cn0KbGlicmFyeShzYW5kd2ljaCkKbGlicmFyeShsbXRlc3QpCmNvZWZ0ZXN0KG1vZGVsLCB2Y292ID0gTmV3ZXlXZXN0KG1vZGVsKSkKYGBgCgpWw71zbGVkb2s6CgpuaWVrdG9yw6kgcC1ob2Rub3R5IG5hcmFzdMO6LAoKZWxpbWludWplIGZhbG/FoW7DuiDFoXRhdGlzdGlja8O6IHbDvXpuYW1ub3PFpSBzcMO0c29iZW7DuiBhdXRva29yZWzDoWNpb3UuCgojIyBaw6F2ZXJlxI1uw6kgemhybnV0aWUKCkF1dG9rb3JlbMOhY2lhIHJlesOtZHXDrSBqZSBkw7RsZcW+aXTDvSBwcmVkcG9rbGFkIE9MUywga3RvcsO9IHByaSDEjWFzb3bDvWNoIHJhZG9jaCDEjWFzdG8gbmVwbGF0w60uCgpWIGRhdGFzZXRlIGFpcnF1YWxpdHkgRHVyYmlu4oCTV2F0c29uIHRlc3QgenZ5xI1ham5lIG5hem5hxI3DrSBhdXRva29yZWzDoWNpdSwgQkcgdGVzdCBtZW5lai4KCktveWNrb3YgbW9kZWwgYXV0b2tvcmVsw6FjaXUgem1pZXLFiHVqZSwgYWxlIG3DtMW+ZSBwcmluaWVzxaUgc3RyYXR1IHbDvXpuYW1ub3N0aSByZWdyZXNvcm92LgoKTmV3ZXnigJNXZXN0IHJvYnVzdG7DqSDFoXRhbmRhcmRuw6kgY2h5Ynkgc8O6IHByYWt0aWNrw6kgcmllxaFlbmllIGJleiB6bWVueSBtb2RlbHUuCgpNb2Rlcm7DvW0gbsOhc3Ryb2pvbSBwcmkgZGxoxaHDrWNoIMSNYXNvdsO9Y2ggcmFkb2NoIHPDuiBBUklNQSBtb2RlbHkuCgojIE11bHRpa29saW5lYXJpdGEKIyMgS29yZWxhxI1uw6EgbWF0aWNhIDEgCmBgYHtyfQpkYXRhKCJhaXJxdWFsaXR5IikKZGYgPC0gbmEub21pdChhaXJxdWFsaXR5KQoKY29yKGRmWywgYygiU29sYXIuUiIsICJXaW5kIiwgIlRlbXAiLCAiTW9udGgiLCAiRGF5IildKQoKbGlicmFyeShjb3JycGxvdCkKY29ycnBsb3QoY29yKGRmWywgYygiU29sYXIuUiIsICJXaW5kIiwgIlRlbXAiLCAiTW9udGgiLCAiRGF5IildKSwKICAgICAgICAgbWV0aG9kID0gImNvbG9yIiwgYWRkQ29lZi5jb2wgPSAiYmxhY2siKQoKYGBgCiMjIFNjYXR0ZXJsb3B0b3bDoSBrb3JlbGHEjW7DoSBtYXRpY2EgCmBgYHtyfQoKbGlicmFyeShwc3ljaCkKCmRmIDwtIG5hLm9taXQoYWlycXVhbGl0eSkKCnBhaXJzLnBhbmVscygKICBkZlssIGMoIlNvbGFyLlIiLCAiV2luZCIsICJUZW1wIiwgIk1vbnRoIiwgIkRheSIpXSwKICBtZXRob2QgPSAicGVhcnNvbiIsICAgIyB0eXAga29yZWzDoWNpZQogIGhpc3QuY29sID0gImxpZ2h0Z3JheSIsCiAgZGVuc2l0eSA9IFRSVUUsCiAgZWxsaXBzZXMgPSBUUlVFLAogIGxtID0gVFJVRSwgICAgICAgICAgICAjIHJlZ3Jlc27DoSDEjWlhcmEKICBzbW9vdGhlciA9IFRSVUUsICAgICAgIyBMT0VTUyBrcml2a2EKICBnYXAgPSAwLjUsICAgICAgICAgICAgIyB2aWFjIHByaWVzdG9ydSA9IHByZWjEvmFkbm9zxaUKICBzY2FsZSA9IEZBTFNFLAogIGNleC5jb3IgPSAyICAgICAgICAgICAjIHZlxL5rw6kgxI3DrXNsYSBrb3JlbMOhY2nDrSA9IMSNaXRhdGXEvm7DqQopCgpgYGAKU2NhdHRlcnBsb3RvdsOhIGtvcmVsYcSNbsOhIG1hdGljYSB6b2JyYXp1amUgdnrFpWFoeSBtZWR6aSBwcmVtZW5uw71taSBTb2xhci5SLCBXaW5kLCBUZW1wLCBNb250aCBhIERheSB6IGRhdGFzZXR1IGFpcnF1YWxpdHkuCgpOYSBkaWFnb27DoWxlIHNhIG5hY2jDoWR6YWrDuiBoaXN0b2dyYW15LCBrdG9yw6kgdWthenVqw7ogZGlzdHJpYsO6Y2l1IGplZG5vdGxpdsO9Y2ggcHJlbWVubsO9Y2guIE1pbW8gZGlhZ29uw6FseSBzw7ogem7DoXpvcm5lbsOpIHNjYXR0ZXJwbG90eSBkb3BsbmVuw6kgbzoKCkxPRVNTIGhsYWRrw7oga3Jpdmt1ICjEjWVydmVuw6EgxI1pYXJhKSwga3RvcsOhIGlsdXN0cnVqZSB0dmFyIG5lbGluZcOhcm55Y2ggdnrFpWFob3YsCgplbGlwc3UgdmFyaWFuY2llICjEjWllcm5hIGVsaXBzYSksIGt0b3LDoSB1a2F6dWplIGtvbmNlbnRyw6FjaXUgYm9kb3YsCgpyZWdyZXNuw70gc3RyZWRvdsO9IGJvZCAoxI1lcnZlbsO9IGJvZCkuCgpWIHByYXZvbSBob3Jub20gdHJvanVob2xuw61rdSBzw7ogdXZlZGVuw6kgYWoga29yZWxhxI1uw6kga29lZmljaWVudHkgbWVkemkgZHZvamljYW1pIHByZW1lbm7DvWNoLgoKWsOha2xhZG7DqSB6aXN0ZW5pYQoKV2luZCBhIFRlbXAgdnlrYXp1asO6IHN0cmVkbmUgc2lsbsO6IG5lZ2F0w612bnUga29yZWzDoWNpdSAoY2NhIOKAkzAuNTApLCDEjW8gbmF6bmHEjXVqZSwgxb5lIHByaSB2ecWhxaFlaiB0ZXBsb3RlIHNhIHZpZXRvciB6dnnEjWFqbmUgem5pxb51amUuCgpUZW1wIGEgU29sYXIuUiBtYWrDuiBtaWVybmUgcG96aXTDrXZudSBrb3JlbMOhY2l1IChjY2EgMC4yOSksIMSNbyB6bmFtZW7DoSwgxb5lIHZ5xaHFoWllIHRlcGxvdHkgc8O6IHp2w6TEjcWhYSBzcG9qZW7DqSBzIHbDpMSNxaHDrW0gc2xuZcSNbsO9bSDFvmlhcmVuw61tLgoKU29sYXIuUiBhIFdpbmQgdnlrYXp1asO6IGxlbiBzbGFiw7oga29yZWzDoWNpdSAofjAuMTMpLgoKUHJlbWVubsOpIE1vbnRoIGEgRGF5IG1hasO6IHZlxL5taSBzbGFiw6kgYcW+IG51bG92w6kga29yZWzDoWNpZSBzIG9zdGF0bsO9bWkgcHJlbWVubsO9bWksIMSNbyBuYXpuYcSNdWplLCDFvmUgc2Ftb3Ruw70gZMOhdHVtICh2IHLDoW1jaSBtZXNpYWNhIGFsZWJvIG1lc2lhYyB2IHJva3UpIG5pZSBqZSBzaWxuw71tIGRldGVybWluYW50b20gZnl6aWvDoWxueWNoIHZlbGnEjcOtbiB2IHRvbXRvIGRhdGFzZXRlLgoKQ2Vsa292w6kgaG9kbm90ZW5pZQoKTWF0aWNhIHVrYXp1amUsIMW+ZSBtZWR6aSBwcmVtZW5uw71taSBkYXRhc2V0dSBhaXJxdWFsaXR5IHNhIHZ5c2t5dHVqw7ogbGVuIG1pZXJuZSBrb3JlbMOhY2llLCBiZXogem7DoW1vayBleHRyw6ltbmUgc2lsbmVqIG11bHRpa29saW5lYXJpdHkuIFbDvW5pbWtvdSBqZSBzdHJlZG5lIHNpbG7DvSBuZWdhdMOtdm55IHZ6xaVhaCBtZWR6aSB0ZXBsb3RvdSBhIHLDvWNobG9zxaVvdSB2ZXRyYSwga3RvcsO9IG3DtMW+ZSBiecWlIGTDtGxlxb5pdMO9IHByaSByZWdyZXNub20gbW9kZWxvdmFuw60uCgojIFZJRiAoVmFyaWFuY2UgSW5mbGF0aW9uIEZhY3RvcikKYGBge3J9CmxpYnJhcnkoY2FyKQoKbW9kZWwgPC0gbG0oT3pvbmUgfiBTb2xhci5SICsgV2luZCArIFRlbXAgKyBNb250aCArIERheSwgZGF0YSA9IGRmKQp2aWYobW9kZWwpCgpgYGAKCkludGVycHJldMOhY2lhCgpWSUYgPiA1ID0genbDvcWhZW7DoSBtdWx0aWtvbGluZWFyaXRhCgpWSUYgPiAxMCA9IHbDocW+bnkgcHJvYmzDqW0KClBvZMS+YSB2w71zbGVka3Ugxb5pYWRuYSB6IHByZW1lbm7DvWNoIG5lc3DDtHNvYnVqZSB6dsO9xaFlbsO6IGtvbGluZWFyaXR1LCBhbGVibyB2w6HFvm55IHByb2Jsw6ltLiAKCiMjIENvbmRpdGlvbiBOdW1iZXIgKyBDb25kaXRpb24gSW5kZXgKYGBge3J9CmxpYnJhcnkob2xzcnIpCgpvbHNfZWlnZW5fY2luZGV4KG1vZGVsKQoKYGBgCkNvbmRpdGlvbiBJbmRleCA+IDE1ID0gc2xhYsOhIG11bHRpa29saW5lYXJpdGEKCkNJID4gMzAgPSB2w6HFvm5hIG11bHRpa29saW5lYXJpdGEKCkludGVycHJldMOhY2lhOgpaIHRlc3R1IHNtZSB6aXN0aWxpLCDFvmUgQ0kgPSAzNi4wNSB6bmFtZW7DoSwgxb5lIGFzcG/FiCBkdmUgcHJlbWVubsOpIHPDuiB0YWttZXIgbGluZcOhcm5lIHrDoXZpc2zDqS4gCgogUHJpIENJID4gMzAgbmVzdGHEjcOtIHZlZGllxaUsIMW+ZSBwcm9ibMOpbSBleGlzdHVqZS4KTXVzw61tZSB6aXN0acWlOgoK4oaSIGt0b3LDqSBwcmVtZW5uw6kgc3BvbHUga29saW5lw6FybmUgcmFzdMO6CgpgYGB7cn0KbGlicmFyeShvbHNycikKb2xzX2NvbGxfZGlhZyhtb2RlbCkKCmBgYArEjG8gdG8gem5hbWVuw6E/CgpUb3RvIE5JRSBKRSBtdWx0aWtvbGluZWFyaXRhIG1lZHppIHZ5c3ZldMS+dWrDumNpbWkgcHJlbWVubsO9bWkuCkplIHRvIGxlbiBudW1lcmlja8O9IGFydGVmYWt0LCBrZGUgc2EgaW50ZXJjZXB0IOKAnmJpamXigJwgcyBUZW1wLCDEjW8gamUgw7pwbG5lIGJlxb5uw6kgcHJpIHN0cmVkbmUgxaFrw6Fsb3ZhbsO9Y2ggcHJlbWVubsO9Y2guCgpaw6F2ZXIgQ0kgKyB2YXJpYW5jaWU6IMW9aWFkbmEgdsOhxb5uYSBtdWx0aWtvbGluZWFyaXRhIG1lZHppIFNvbGFyLlIsIFdpbmQsIFRlbXAsIE1vbnRoLCBEYXkuCmBgYHtyfQojIE5hxI3DrXRhbmllIGRhdGFzZXR1CmRhdGEoImFpcnF1YWxpdHkiKQoKIyBPZHN0csOhbmVuaWUgcmlhZGtvdiBzIE5BCmFpcnF1YWxpdHkgPC0gbmEub21pdChhaXJxdWFsaXR5KQoKIyDFoHRhbmRhcmRpesOhY2lhIHZ5YnJhbsO9Y2ggcHJlbWVubsO9Y2gKYWlycXVhbGl0eV9zY2FsZWQgPC0gYWlycXVhbGl0eQphaXJxdWFsaXR5X3NjYWxlZCRTb2xhcl9jIDwtIHNjYWxlKGFpcnF1YWxpdHkkU29sYXIpCmFpcnF1YWxpdHlfc2NhbGVkJFdpbmRfYyAgPC0gc2NhbGUoYWlycXVhbGl0eSRXaW5kKQphaXJxdWFsaXR5X3NjYWxlZCRUZW1wX2MgIDwtIHNjYWxlKGFpcnF1YWxpdHkkVGVtcCkKYWlycXVhbGl0eV9zY2FsZWQkTW9udGhfYyA8LSBzY2FsZShhaXJxdWFsaXR5JE1vbnRoKQphaXJxdWFsaXR5X3NjYWxlZCREYXlfYyAgIDwtIHNjYWxlKGFpcnF1YWxpdHkkRGF5KQoKIyBWeXR2b3JlbmllIGxpbmXDoXJuZWhvIG1vZGVsdQptb2RlbF9zY2FsZWQgPC0gbG0oT3pvbmUgfiBTb2xhcl9jICsgV2luZF9jICsgVGVtcF9jICsgTW9udGhfYyArIERheV9jLAogICAgICAgICAgICAgICAgICAgZGF0YSA9IGFpcnF1YWxpdHlfc2NhbGVkKQoKIyBNYXRpY2EgbsOhdnJodQpYIDwtIG1vZGVsLm1hdHJpeChtb2RlbF9zY2FsZWQpWywgLTFdCgojIFbDvXBvxI1ldCBYdFggYSB2bGFzdG7DvWNoIMSNw61zZWwKWHRYIDwtIHQoWCkgJSolIFgKZWlnIDwtIGVpZ2VuKFh0WCkKCiMgVsO9cG/EjWV0IGNvbmRpdGlvbiBudW1iZXIKY29uZGl0aW9uX251bWJlciA8LSBzcXJ0KG1heChlaWckdmFsdWVzKSAvIG1pbihlaWckdmFsdWVzKSkKY29uZGl0aW9uX251bWJlcgoKYGBgCkNvbmRpdGlvbiBudW1iZXIgKMSNw61zbG8gcG9kbWllbmt5KSB1a2F6dWplLCBha28g4oCec3RhYmlsbsO94oCcIGplIG1vZGVsIHogaMS+YWRpc2thIG11bHRpa29saW5lYXJpdHkuCgpIb2Rub3RhIGJsw616a2EgMSB6bmFtZW7DoSwgxb5lIHByZW1lbm7DqSBzw7ogdGFrbWVyIG9ydG9nb27DoWxuZSAobmV6w6F2aXNsw6kpLCDEjW8gamUgaWRlw6FsbmUuCgojIyBaw6F2ZXIKCk5hIHrDoWtsYWRlIHbDvXBvxI10dSBjb25kaXRpb24gbnVtYmVyIHByZSBuw6HFoSBsaW5lw6FybnkgbW9kZWwgKGhvZG5vdGEg4omIIDIsMTgpIG3DtMW+ZW1lIGtvbsWhdGF0b3ZhxaUsIMW+ZSBtdWx0aWtvbGluZWFyaXRhIHYgbmHFoW9tIGRhdGFzZXRlIG5pZSBqZSBwcm9ibMOpbS4gSG9kbm90YSDEjcOtc2xhIHBvZG1pZW5reSBqZSB2ZcS+bWkgbsOtemthLCDEjW8gbmF6bmHEjXVqZSwgxb5lIHZ5c3ZldMS+dWrDumNlIHByZW1lbm7DqSAoU29sYXJfYywgV2luZF9jLCBUZW1wX2MsIE1vbnRoX2MsIERheV9jKSBzw7ogdm/EjWkgc2ViZSByZWxhdMOtdm5lIG5lesOhdmlzbMOpIGEgbW9kZWwgamUgc3RhYmlsbsO9LgoKUG9kcG9ydSB0b211dG8gesOhdmVydSBwb3NreXR1asO6IGFqIGhvZG5vdHkgVklGIChWYXJpYW5jZSBJbmZsYXRpb24gRmFjdG9yKToKClNvbGFyLlI6IDEuMTUKCldpbmQ6IDEuMzMKClRlbXA6IDEuNzIKCk1vbnRoOiAxLjI2CgpEYXk6IDEuMDEKClbFoWV0a3kgaG9kbm90eSBWSUYgc8O6IHbDvXJhem5lIHBvZCBrcml0aWNrb3UgaHJhbmljb3UgNSwgxI1vIG9ww6TFpSBwb3R2cmR6dWplLCDFvmUgbXVsdGlrb2xpbmVhcml0YSB2IGTDoXRhY2ggbmllIGplIHbDvXpuYW1uw6EuIE9kaGFkeSByZWdyZXNuw71jaCBrb2VmaWNpZW50b3Ygc8O6IHRlZGEgc3BvxL5haGxpdsOpIGEgdsO9c2xlZGt5IHJlZ3Jlc25laiBhbmFsw716eSBuaWUgc8O6IHNrcmVzbGVuw6kgbmFkbWVybm91IGtvcmVsw6FjaW91IG1lZHppIHByZW1lbm7DvW1pLgo=