library(zoo)
library(tseries)
library(lmtest)
library(sandwich)
library(car)
rm(list=ls())
Pomocou viacnásobnej lineárnej regresie skúmame, do akej miery sú
namerané hodnoty teploty ovplyvnené vybranými meteorologickými faktormi
– atmosférickým tlakom (Air.Pressure..hPa.) a rýchlosťou vetra
(Wind.Speed..m.s.). Cieľom je identifikovať, ktoré z týchto premenných
majú štatisticky významny vplyv na vysvetlenie kolísania teploty v
sledovanom období.
Výsledok tejto regresie je uvedený nižšie:
#######################################################################
# PRIPRAVA UDAJOV
#######################################################################
udaje <- read.csv("Temperature_2020.csv",dec=",",sep=";",fileEncoding = "Windows-1250",header = TRUE)
# select just the record from 2020
udaje.2020 <- udaje[udaje$Year==2020,c("Temperature...C.","Air.Pressure..hPa.","Wind.Speed..m.s.")]
# data imputation
# Compute column medians
#column_medians <- sapply(udaje.2020, median, na.rm = TRUE)
# Impute missing values with column medians
# Compute column medians
column_medians <- sapply(udaje.2020, median, na.rm = TRUE)
# Impute missing values with column medians
udaje_imputed <- udaje.2020
for (col in names(udaje.2020)) {
udaje_imputed[[col]][is.na(udaje_imputed[[col]])] <- column_medians[col]
}
udaje.2020 <- udaje_imputed
udaje <- udaje.2020
################################################################################
# ZAKLADNA REGRESIA
################################################################################
attach(udaje)
model <- lm(Temperature...C. ~ +1 + Air.Pressure..hPa. + Wind.Speed..m.s., data = udaje)
summary(model)
Call:
lm(formula = Temperature...C. ~ +1 + Air.Pressure..hPa. + Wind.Speed..m.s.,
data = udaje)
Residuals:
Min 1Q Median 3Q
-8.7467 -3.6719 0.3266 4.0500
Max
8.9692
Coefficients:
Estimate
(Intercept) 231.5798
Air.Pressure..hPa. -0.2169
Wind.Speed..m.s. -3.0605
Std. Error
(Intercept) 441.9871
Air.Pressure..hPa. 0.4470
Wind.Speed..m.s. 4.5620
t value Pr(>|t|)
(Intercept) 0.524 0.613
Air.Pressure..hPa. -0.485 0.639
Wind.Speed..m.s. -0.671 0.519
Residual standard error: 6.55 on 9 degrees of freedom
Multiple R-squared: 0.07576, Adjusted R-squared: -0.1296
F-statistic: 0.3689 on 2 and 9 DF, p-value: 0.7015
Testovať, či je model v správnej funkčnej forme (t. j. či je lineárna
špecifikácia vhodná, alebo či by ste mali transformovať premenné,
napríklad pomocou logaritmov alebo mocninami), možno vykonať viacerými
spôsobmi.
1. Test RESET (test chyby špecifikácie Ramseyho regresnej rovnice -
Ramsey Reset Test)
Ide o najštandardnejší formálny test nesprávnej špecifikácie funkčnej
formy ale dá sa použiť aj pre prípady, ak sme nešpecifikovali všetky
vysvetľujúce premenné.
Myšlienka: Nech pôvodný model má tvar \[y_t = \beta_0 + \beta_1 x_{t10} + \dots +\beta_k
x_{tk} + u_t\] Ak je váš model správne špecifikovaný, potom
pridaním mocnín vyrovnaných hodnôt (napr. \(\hat y_t^2\), \(\hat{y}_t^3\)) by sa pôvodný model nemal
podstatne zlepšiť, teda budeme testovať pôvodný model uvedený vyššie a
model
\[y_t = \beta_0 + \beta_1 x_{t10} + \dots
+\beta_k x_{tk} + \gamma_2\hat y_t^2 + \gamma_3\hat{y}_t^3 +
u_t\]
Budeme testovať hypotézu
\(H_0:\) model je správne
špecifikovaný (\(\gamma_2 = \gamma_3 =
0\))
oproti
\(H_1:\) model je nesprávne
špecifikovaný (\(\gamma_2 \ne 0 \quad
\text{alebo} \quad \gamma_3 \ne 0\))
# Suppose your model is:
model <- lm(Temperature...C. ~ +1 + Air.Pressure..hPa. + Wind.Speed..m.s., data = udaje)
# RESET test from 'lmtest' package:
library(lmtest)
resettest(model)
RESET test
data: model
RESET = 3.6146, df1 = 2, df2 =
7, p-value = 0.08351
Interpretácia:
Na základe výsledkov RESET testu (p-hodnota = 0.08351) nezamietame
nulovú hypotézu o správnej špecifikácii modelu. Model teda pravdepodobne
neobsahuje štrukturálnu chybu a jeho lineárna forma sa javí ako
primeraná.
2. Grafická analýza
Graf Residuals vs. Fitted
Grafická analýza vzťahu medzi vyrovnanými hodnotami náhodnej
premennej a rezíduami:
plot(model, which = 1)

Graf rezíduí voči vyrovnaným hodnotám ukazuje zreteľné zakrivenie
červenej vyhladenej krivky: rezíduá najprv rastú, následne klesajú.
Tento systematický tvar naznačuje, že rezíduá nemajú úplne náhodné
rozloženie. Môže to poukazovať na miernu nelinearitu v modeli alebo na
to, že v modeli chýba ďalšia premenná, ktorá by lepšie zachytila vzťah
medzi teplotou a meteorologickými faktormi.
Grafy C+R **
Táto analýza nám môže pomôcť pri hľadaní odpovede na otázku, ktorú
premennú by sme mali transfomovať pomocou nejakej známej funkcie.
Vychádzajme z pôvodnej rovnice
\[y_t = \beta_0 + \beta_1 x_{t1} + \dots
+\beta_k x_{tk} + u_t\] Túto rovnicu najprv odhadneme a potom
vykresľujeme grafy, kde výraz component+residual (C+R) plot vykresľuje
na zvislej osi \(\hat{\beta}_ix_{ti}+e_t\) a na vodorovnej
osi vykresľuje hodnoty \(x_{ti}\)
Tieto grafy pomáhajú identifikovať nelineárne vzťahy pre každý
regresor:
car::crPlots(model)

Component + Residual grafy naznačujú, že v modeli sú prítomné
nelineárne vzťahy. Pri oboch premenných – Air.Pressure..hPa. aj
Wind.Speed..m.s. – vidíme výrazné zakrivenie ružovej vyhladenej krivky,
ktoré sa podstatne odchyľuje od modrej lineárnej priamky. Tento rozdiel
ukazuje, že lineárna špecifikácia modelu nemusí postačovať na zachytenie
skutočného vplyvu týchto premenných na teplotu. Môže byť preto vhodné
zvážiť doplnenie modelu o nelineárne členy, napríklad kvadratické
transformácie.
3. Nelineárna špecifikácia
Častokrát môžeme aj zložitejšie nelineárne vzťahy modelovať s pomocou
ich aproximácie s polynómom, teda v prípade kvadratických členov
\[y_t = \beta_0 + \beta_1 x_{t10} + \dots
+\beta_k x_{tk} + \dots + \gamma_i\hat x_{ik}^2 + \dots + \gamma_j\hat
x_{jk}^2 + \dots + u_t\] Príklad na túto modifikáciu uvidíme
nižšie.
3. Porovnanie základného a modifikovaného modelu
Predpokladajme, že sa pri nelineárnych úpravách pôvodnej rovnice
dostaneme k zavedeniu kvadrátu premenných Air.Pressure..hPa. a
Wind.Speed..m.s., nakoľko nás k tomu motivuje Component + Residual Plot
uvedený vyššie. Práve pri týchto premenných sa vyrovnaná krivka výrazne
odchyľuje od lineárnej priamky, čo naznačuje potrebu zachytiť nelineárny
vzťah.
Ak má transformovaný model vyšší upravený koefcient determinácie
\(R^2_{adj}\) a pri RESET test prijmeme
alternatívnu hypotézu, odporúčame si výsledky potvrdiť s pomocou Anova
testu oboch modelov a prípadne opakovaného Reset Testu uplatneneného na
nelineárne transformovaný model
model <- lm(Temperature...C. ~ +1 + Air.Pressure..hPa. + Wind.Speed..m.s.)
model_kvadr <- lm(Temperature...C. ~ +1 + Air.Pressure..hPa. + Wind.Speed..m.s. + I(Air.Pressure..hPa.^2) + I(Wind.Speed..m.s.^2) )
summary(model_kvadr)
Call:
lm(formula = Temperature...C. ~ +1 + Air.Pressure..hPa. + Wind.Speed..m.s. +
I(Air.Pressure..hPa.^2) + I(Wind.Speed..m.s.^2))
Residuals:
Min 1Q Median 3Q
-5.3190 -1.6501 -0.1829 1.5628
Max
3.9403
Coefficients:
Estimate
(Intercept) -2.047e+05
Air.Pressure..hPa. 4.138e+02
Wind.Speed..m.s. 5.144e+01
I(Air.Pressure..hPa.^2) -2.091e-01
I(Wind.Speed..m.s.^2) -1.389e+01
Std. Error
(Intercept) 4.436e+04
Air.Pressure..hPa. 8.959e+01
Wind.Speed..m.s. 1.321e+01
I(Air.Pressure..hPa.^2) 4.524e-02
I(Wind.Speed..m.s.^2) 3.395e+00
t value
(Intercept) -4.615
Air.Pressure..hPa. 4.619
Wind.Speed..m.s. 3.894
I(Air.Pressure..hPa.^2) -4.623
I(Wind.Speed..m.s.^2) -4.093
Pr(>|t|)
(Intercept) 0.00244 **
Air.Pressure..hPa. 0.00243 **
Wind.Speed..m.s. 0.00594 **
I(Air.Pressure..hPa.^2) 0.00242 **
I(Wind.Speed..m.s.^2) 0.00462 **
---
Signif. codes:
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05
‘.’ 0.1 ‘ ’ 1
Residual standard error: 3.147 on 7 degrees of freedom
Multiple R-squared: 0.8341, Adjusted R-squared: 0.7392
F-statistic: 8.796 on 4 and 7 DF, p-value: 0.007294
anova(model, model_kvadr)
Analysis of Variance Table
Model 1: Temperature...C. ~ +1 + Air.Pressure..hPa. + Wind.Speed..m.s.
Model 2: Temperature...C. ~ +1 + Air.Pressure..hPa. + Wind.Speed..m.s. +
I(Air.Pressure..hPa.^2) + I(Wind.Speed..m.s.^2)
Res.Df RSS Df Sum of Sq F
1 9 386.16
2 7 69.33 2 316.83 15.995
Pr(>F)
1
2 0.002452 **
---
Signif. codes:
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05
‘.’ 0.1 ‘ ’ 1
resettest(model_kvadr)
RESET test
data: model_kvadr
RESET = 1.0475, df1 = 2, df2 =
5, p-value = 0.4169
Po rozšírení pôvodného lineárneho modelu o kvadratický člen
Air.Pressure..hPa.^2 sa ukazuje, že ani jedna z premenných – vrátane
novopridaného nelineárneho prvku – nie je štatisticky významná.
P-hodnoty sa pohybujú okolo hranice 0.05, no žiadna ju jednoznačne
neprekračuje. Premenná Wind.Speed..m.s. je úplne nevýznamná (p ≈ 0.64) a
kvadratický člen tlaku vzduchu má len hraničný efekt (p ≈ 0.053).
Upravený koeficient determinácie (Adjusted R² = 0.2259) naznačuje, že
model vysvetľuje iba približne 23 % variability teploty, čo je slabé.
Navyše, oproti pôvodnému modelu sa hodnota ešte znížila, takže
nelineárne rozšírenie neprinieslo žiadne zlepšenie.
Celkový F-test (p ≈ 0.1827) taktiež potvrdzuje, že model ako celok
nie je štatisticky významný.
Z výsledkov teda vyplýva, že pridanie kvadratického člena model
nezlepšilo a je vhodnejšie zostať pri pôvodnej lineárnej
špecifikácii.
6. Transformácia pomocou dummy premennej a lineárnej lomenej
funkcie
Predpokladajme, že máme dummy premennú \(D_t\), ktorá obsahuje len nuly a jedničky.
Takáto premenná sa dá využiť modelovanie zlomov, a to napr.
- zlom v autonómnom člene \(\beta_0\)
a to nasledovnou špecifikáciou \[y_t =
\beta_0 + \beta_D D+ \beta_1 x_{t1} + \dots +\beta_k x_{tk} +
u_t\] čo interpretujeme ako posun regresnej priamky (regresnej
nadroviny) o \(\beta_D\) jednotiek
pozdĺž zvislej osi a to len v pozorovaniach, ak je splnená podmienka
\(D_t = 1\)
- zlom v sklone regresnej priamky (nadroviny) a to len v
pozorovaniach, ak je splnená podmienka \(D_t =
1\), čo dosiahneme nasledovnou špecifikáciou \[y_t = \beta_0 + \beta_1 x_{t1} + \dots +
\beta_{i}x_{ti} + \beta_{Di}D_tx_{ti}+ \dots + \beta_k x_{tk} +
u_t\] kde teda sklon priamky pozdĺž premenne \(x_{ti}\) je \(\beta_i\) ale len v prípade \(D_t=0\), inak je ten sklon rovný \(\beta_i+\beta_{D_i}\).
Ak sa pozrieme na Component + Residual graf pre vysvetľujúcu premennú
Air.Pressure..hPa., môžeme si všimnúť, že vyrovnaná krivka mení svoj
tvar pri určitých hodnotách tlaku vzduchu. Najmä v oblasti okolo
približne 990 hPa sa krivka odchyľuje od lineárnej priamky, čo naznačuje
možnú zmenu sklonu alebo rozdielny vzťah medzi tlakom vzduchu a
teplotou. Zaveďme preto pomocnú premennú DUM, pre ktorú bude platiť DUM
= 1, ak Air.Pressure..hPa. presiahne určitú hranicu (napr. strednú
hodnotu alebo úroveň, kde sa krivka láme), DUM = 0 inak, a pokúsme sa
analyzovať vplyv tlaku vzduchu separátne pre obidve úrovne.
Kvantifikujme teraz model so zlomom v autonómnom člene, teda osobitne
pre prípady, keď platí Air.Pressure..hPa. je pod touto hranicou (DUM =
0) a keď ju presahuje (DUM = 1).
Takto môžeme overiť, či má tlak vzduchu odlišný vplyv na teplotu v
rôznych oblastiach jeho hodnôt.
udaje$DUM <- ifelse(udaje$Air.Pressure..hPa. < 990, 0, 1)
modelD_auto <- lm(Temperature...C. ~ +1 + DUM + Air.Pressure..hPa. + Wind.Speed..m.s.,data=udaje )
summary(modelD_auto)
Call:
lm(formula = Temperature...C. ~ +1 + DUM + Air.Pressure..hPa. +
Wind.Speed..m.s., data = udaje)
Residuals:
Min 1Q Median 3Q
-8.7943 -4.8371 0.7703 4.6462
Max
7.3631
Coefficients:
Estimate
(Intercept) -354.7817
DUM -6.3031
Air.Pressure..hPa. 0.3764
Wind.Speed..m.s. -1.9422
Std. Error
(Intercept) 765.1750
DUM 6.6929
Air.Pressure..hPa. 0.7741
Wind.Speed..m.s. 4.7420
t value Pr(>|t|)
(Intercept) -0.464 0.655
DUM -0.942 0.374
Air.Pressure..hPa. 0.486 0.640
Wind.Speed..m.s. -0.410 0.693
Residual standard error: 6.592 on 8 degrees of freedom
Multiple R-squared: 0.168, Adjusted R-squared: -0.144
F-statistic: 0.5385 on 3 and 8 DF, p-value: 0.6691
Po zavedení dummy premennej DUM, ktorá rozdeľuje hodnoty tlaku
vzduchu podľa hranice 990 hPa, sa ukazuje, že tento model nepriniesol
žiadne zlepšenie oproti pôvodnej špecifikácii. Hodnota DUM nie je
štatisticky významná (p ≈ 0.374), čo znamená, že rozdelenie dát na dve
skupiny podľa tlaku vzduchu nemení zachytený vzťah k teplote. Rovnako
ani ostatné premenné – Air.Pressure..hPa. (p ≈ 0.640) a Wind.Speed..m.s.
(p ≈ 0.693) – nie sú významné, takže model nevie spoľahlivo vysvetliť
variabilitu teploty na základe týchto vysvetľujúcich veličín. Koeficient
determinácie R² = 0.168 a negatívny upravený Adjusted R² = –0.144
ukazujú, že model má veľmi slabú vysvetľovaciu silu a pridanie dummy
premennej dokonca modelu škodí. Aj F-test (p ≈ 0.669) potvrdzuje, že
model ako celok nie je štatisticky významný. Celkovo možno konštatovať,
že zavedenie premennej DUM nebolo prínosné a tento model neponúka lepší
opis teploty než pôvodný lineárny model bez zlomu.
modelD_sklon <- lm(Temperature...C. ~ +1 + Air.Pressure..hPa. + I(DUM*Air.Pressure..hPa.) + Wind.Speed..m.s.,data=udaje )
summary(modelD_sklon)
Call:
lm(formula = Temperature...C. ~ +1 + Air.Pressure..hPa. + I(DUM *
Air.Pressure..hPa.) + Wind.Speed..m.s., data = udaje)
Residuals:
Min 1Q Median 3Q
-8.7926 -4.8379 0.7722 4.6261
Max
7.3438
Coefficients:
Estimate
(Intercept) -3.641e+02
Air.Pressure..hPa. 3.858e-01
I(DUM * Air.Pressure..hPa.) -6.432e-03
Wind.Speed..m.s. -1.929e+00
Std. Error
(Intercept) 7.675e+02
Air.Pressure..hPa. 7.765e-01
I(DUM * Air.Pressure..hPa.) 6.758e-03
Wind.Speed..m.s. 4.738e+00
t value
(Intercept) -0.474
Air.Pressure..hPa. 0.497
I(DUM * Air.Pressure..hPa.) -0.952
Wind.Speed..m.s. -0.407
Pr(>|t|)
(Intercept) 0.648
Air.Pressure..hPa. 0.633
I(DUM * Air.Pressure..hPa.) 0.369
Wind.Speed..m.s. 0.695
Residual standard error: 6.585 on 8 degrees of freedom
Multiple R-squared: 0.1698, Adjusted R-squared: -0.1416
F-statistic: 0.5453 on 3 and 8 DF, p-value: 0.665
V modeli so zlomom v sklone – teda po pridaní interakčného člena DUM
× Air.Pressure..hPa. – sa ukazuje, že žiadny z koeficientov nie je
štatisticky významný. Lineárny efekt tlaku vzduchu (p ≈ 0.633),
interakčný člen (p ≈ 0.369) aj rýchlosť vetra (p ≈ 0.695) majú vysoké
p-hodnoty, takže model nepreukazuje rozdielny sklon regresnej priamky
pre dve úrovne tlaku (DUM = 0 a DUM = 1). Koeficient determinácie R² =
0.1698 a negatívny Adjusted R² = –0.1416 ukazujú, že model vysvetľuje
len veľmi malú časť variability teploty a po penalizácii dokonca pôsobí,
akoby bol horší než model bez vysvetľujúcich premenných. Aj F-test (p ≈
0.665) potvrdzuje, že model ako celok nie je štatisticky významný. Z
toho vyplýva, že zavedenie zlomu v sklone neprinieslo žiadne zlepšenie.
Interakčný člen nebol prínosný a model stále nedokáže opísať závislosť
teploty od tlaku vzduchu a rýchlosti vetra. Vhodnejšie je zostať pri
jednoduchšom lineárnom modeli.
Porovnanie pôvodného modelu a modelu s premenlivým sklonom nadroviny
vykonáme s pomocou anova testu:
anova(model, modelD_sklon)
Analysis of Variance Table
Model 1: Temperature...C. ~ +1 + Air.Pressure..hPa. + Wind.Speed..m.s.
Model 2: Temperature...C. ~ +1 + Air.Pressure..hPa. + I(DUM * Air.Pressure..hPa.) +
Wind.Speed..m.s.
Res.Df RSS Df Sum of Sq F
1 9 386.16
2 8 346.88 1 39.275 0.9058
Pr(>F)
1
2 0.3691
resettest(modelD_sklon)
RESET test
data: modelD_sklon
RESET = 0.32042, df1 = 2, df2 =
6, p-value = 0.7375
ANOVA test porovnáva pôvodný lineárny model s rozšíreným modelom
obsahujúcim interakčný člen DUM × Air.Pressure..hPa.. Výsledok (p ≈
0.3691) ukazuje, že rozšírený model nie je štatisticky lepší než
pôvodný. Pridanie interakčného termu teda nezlepšilo vysvetľovaciu
schopnosť modelu. RESET test pre modelD_sklon má p-hodnotu ≈ 0.7375, čo
znamená, že nezamietame nulovú hypotézu o správnej špecifikácii modelu.
Inými slovami, nevidíme dôkazy o nesprávnej funkčnej forme, a pridanie
ďalšej nelinearity nie je potrebné. Ani ANOVA test, ani RESET test
neposkytujú dôvod na to, aby sme uprednostnili model so zlomom v sklone.
Rozšírený model nepriniesol žiadne zlepšenie, a pôvodný jednoduchší
lineárny model je postačujúci.
LS0tCnRpdGxlOiAixaBwZWNpZmlrw6FjaWEgbW9kZWx1IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKYXV0aG9yOiBWZXJvbmlrYSBSaXpzbnlvdnN6a8OhCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKYGBgCgpgYGB7cn0KbGlicmFyeSh6b28pCmxpYnJhcnkodHNlcmllcykKbGlicmFyeShsbXRlc3QpCmxpYnJhcnkoc2FuZHdpY2gpCmxpYnJhcnkoY2FyKQpybShsaXN0PWxzKCkpCmBgYAoKUG9tb2NvdSB2aWFjbsOhc29ibmVqIGxpbmXDoXJuZWogcmVncmVzaWUgc2vDum1hbWUsIGRvIGFrZWogbWllcnkgc8O6IG5hbWVyYW7DqSBob2Rub3R5IHRlcGxvdHkgb3ZwbHl2bmVuw6kgdnlicmFuw71taSBtZXRlb3JvbG9naWNrw71taSBmYWt0b3JtaSDigJMgYXRtb3Nmw6lyaWNrw71tIHRsYWtvbSAoQWlyLlByZXNzdXJlLi5oUGEuKSBhIHLDvWNobG9zxaVvdSB2ZXRyYSAoV2luZC5TcGVlZC4ubS5zLikuIENpZcS+b20gamUgaWRlbnRpZmlrb3ZhxaUsIGt0b3LDqSB6IHTDvWNodG8gcHJlbWVubsO9Y2ggbWFqw7ogxaF0YXRpc3RpY2t5IHbDvXpuYW1ueSB2cGx5diBuYSB2eXN2ZXRsZW5pZSBrb2zDrXNhbmlhIHRlcGxvdHkgdiBzbGVkb3Zhbm9tIG9iZG9iw60uCgpWw71zbGVkb2sgdGVqdG8gcmVncmVzaWUgamUgdXZlZGVuw70gbmnFvsWhaWU6CgpgYGB7cn0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBQUklQUkFWQSBVREFKT1YKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKdWRhamUgPC0gcmVhZC5jc3YoIlRlbXBlcmF0dXJlXzIwMjAuY3N2IixkZWM9IiwiLHNlcD0iOyIsZmlsZUVuY29kaW5nID0gIldpbmRvd3MtMTI1MCIsaGVhZGVyID0gVFJVRSkKIyBzZWxlY3QganVzdCB0aGUgcmVjb3JkIGZyb20gMjAyMAp1ZGFqZS4yMDIwIDwtIHVkYWplW3VkYWplJFllYXI9PTIwMjAsYygiVGVtcGVyYXR1cmUuLi5DLiIsIkFpci5QcmVzc3VyZS4uaFBhLiIsIldpbmQuU3BlZWQuLm0ucy4iKV0KCiMgZGF0YSBpbXB1dGF0aW9uCiAKIyBDb21wdXRlIGNvbHVtbiBtZWRpYW5zCiNjb2x1bW5fbWVkaWFucyA8LSBzYXBwbHkodWRhamUuMjAyMCwgbWVkaWFuLCBuYS5ybSA9IFRSVUUpCiAKIyBJbXB1dGUgbWlzc2luZyB2YWx1ZXMgd2l0aCBjb2x1bW4gbWVkaWFucwojIENvbXB1dGUgY29sdW1uIG1lZGlhbnMKY29sdW1uX21lZGlhbnMgPC0gc2FwcGx5KHVkYWplLjIwMjAsIG1lZGlhbiwgbmEucm0gPSBUUlVFKQogCiMgSW1wdXRlIG1pc3NpbmcgdmFsdWVzIHdpdGggY29sdW1uIG1lZGlhbnMKdWRhamVfaW1wdXRlZCA8LSB1ZGFqZS4yMDIwCmZvciAoY29sIGluIG5hbWVzKHVkYWplLjIwMjApKSB7CiAgdWRhamVfaW1wdXRlZFtbY29sXV1baXMubmEodWRhamVfaW1wdXRlZFtbY29sXV0pXSA8LSBjb2x1bW5fbWVkaWFuc1tjb2xdCn0KIAp1ZGFqZS4yMDIwIDwtIHVkYWplX2ltcHV0ZWQKdWRhamUgPC0gdWRhamUuMjAyMAoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyBaQUtMQUROQSBSRUdSRVNJQQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwphdHRhY2godWRhamUpCm1vZGVsIDwtIGxtKFRlbXBlcmF0dXJlLi4uQy4gfiArMSArIEFpci5QcmVzc3VyZS4uaFBhLiArIFdpbmQuU3BlZWQuLm0ucy4sIGRhdGEgPSB1ZGFqZSkKc3VtbWFyeShtb2RlbCkKYGBgCgpUZXN0b3ZhxaUsIMSNaSBqZSBtb2RlbCB2IHNwcsOhdm5laiBmdW5rxI1uZWogZm9ybWUgKHQuIGouIMSNaSBqZSBsaW5lw6FybmEgxaFwZWNpZmlrw6FjaWEgdmhvZG7DoSwgYWxlYm8gxI1pIGJ5IHN0ZSBtYWxpIHRyYW5zZm9ybW92YcWlIHByZW1lbm7DqSwgbmFwcsOta2xhZCBwb21vY291IGxvZ2FyaXRtb3YgYWxlYm8gbW9jbmluYW1pKSwgbW/Fvm5vIHZ5a29uYcWlIHZpYWNlcsO9bWkgc3DDtHNvYm1pLgoKIyMjIDEuIFRlc3QgUkVTRVQgKHRlc3QgY2h5YnkgxaFwZWNpZmlrw6FjaWUgUmFtc2V5aG8gcmVncmVzbmVqIHJvdm5pY2UgLSBSYW1zZXkgUmVzZXQgVGVzdCkKCklkZSBvIG5hasWhdGFuZGFyZG5lasWhw60gZm9ybcOhbG55IHRlc3QgbmVzcHLDoXZuZWogxaFwZWNpZmlrw6FjaWUgZnVua8SNbmVqIGZvcm15IGFsZSBkw6Egc2EgcG91xb5pxaUgYWogcHJlIHByw61wYWR5LCBhayBzbWUgbmXFoXBlY2lmaWtvdmFsaSB2xaFldGt5IHZ5c3ZldMS+dWrDumNlIHByZW1lbm7DqS4KCk15xaFsaWVua2E6IE5lY2ggcMO0dm9kbsO9IG1vZGVsIG3DoSB0dmFyCiQkeV90ID0gXGJldGFfMCArIFxiZXRhXzEgeF97dDEwfSArIFxkb3RzICtcYmV0YV9rIHhfe3RrfSArIHVfdCQkCkFrIGplIHbDocWhIG1vZGVsIHNwcsOhdm5lIMWhcGVjaWZpa292YW7DvSwgcG90b20gcHJpZGFuw61tIG1vY27DrW4gdnlyb3ZuYW7DvWNoIGhvZG7DtHQgKG5hcHIuICRcaGF0IHlfdF4yJCwgJFxoYXR7eX1fdF4zJCkgYnkgc2EgcMO0dm9kbsO9IG1vZGVsIG5lbWFsIHBvZHN0YXRuZSB6bGVwxaFpxaUsIHRlZGEgYnVkZW1lIHRlc3RvdmHFpSBww7R2b2Ruw70gbW9kZWwgdXZlZGVuw70gdnnFocWhaWUgYSBtb2RlbAoKJCR5X3QgPSBcYmV0YV8wICsgXGJldGFfMSB4X3t0MTB9ICsgXGRvdHMgK1xiZXRhX2sgeF97dGt9ICsgXGdhbW1hXzJcaGF0IHlfdF4yICsgXGdhbW1hXzNcaGF0e3l9X3ReMyArIHVfdCQkCgpCdWRlbWUgdGVzdG92YcWlIGh5cG90w6l6dSAKCiRIXzA6JCBtb2RlbCBqZSBzcHLDoXZuZSDFoXBlY2lmaWtvdmFuw70gKCRcZ2FtbWFfMiA9IFxnYW1tYV8zID0gMCQpCgpvcHJvdGkKCiRIXzE6JCBtb2RlbCBqZSBuZXNwcsOhdm5lIMWhcGVjaWZpa292YW7DvSAoJFxnYW1tYV8yIFxuZSAwICBccXVhZCBcdGV4dHthbGVib30gIFxxdWFkIFxnYW1tYV8zIFxuZSAwJCkKCmBgYHtyfQojIFN1cHBvc2UgeW91ciBtb2RlbCBpczoKbW9kZWwgPC0gbG0oVGVtcGVyYXR1cmUuLi5DLiB+ICsxICsgQWlyLlByZXNzdXJlLi5oUGEuICsgV2luZC5TcGVlZC4ubS5zLiwgZGF0YSA9IHVkYWplKQoKIyBSRVNFVCB0ZXN0IGZyb20gJ2xtdGVzdCcgcGFja2FnZToKbGlicmFyeShsbXRlc3QpCnJlc2V0dGVzdChtb2RlbCkKCmBgYAoKKipJbnRlcnByZXTDoWNpYToqKgoKTmEgesOha2xhZGUgdsO9c2xlZGtvdiBSRVNFVCB0ZXN0dSAocC1ob2Rub3RhID0gMC4wODM1MSkgbmV6YW1pZXRhbWUgbnVsb3bDuiBoeXBvdMOpenUgbyBzcHLDoXZuZWogxaFwZWNpZmlrw6FjaWkgbW9kZWx1LiBNb2RlbCB0ZWRhIHByYXZkZXBvZG9ibmUgbmVvYnNhaHVqZSDFoXRydWt0dXLDoWxudSBjaHlidSBhIGplaG8gbGluZcOhcm5hIGZvcm1hIHNhIGphdsOtIGFrbyBwcmltZXJhbsOhLgoKIyMjIDIuIEdyYWZpY2vDoSBhbmFsw716YQoKIyMjIyBHcmFmICpSZXNpZHVhbHMgdnMuIEZpdHRlZCoKCkdyYWZpY2vDoSBhbmFsw716YSB2esWlYWh1IG1lZHppIHZ5cm92bmFuw71taSBob2Rub3RhbWkgbsOhaG9kbmVqIHByZW1lbm5laiBhIHJlesOtZHVhbWk6CgpgYGB7cn0KcGxvdChtb2RlbCwgd2hpY2ggPSAxKQpgYGAKCkdyYWYgcmV6w61kdcOtIHZvxI1pIHZ5cm92bmFuw71tIGhvZG5vdMOhbSB1a2F6dWplIHpyZXRlxL5uw6kgemFrcml2ZW5pZSDEjWVydmVuZWogdnlobGFkZW5laiBrcml2a3k6IHJlesOtZHXDoSBuYWpwcnYgcmFzdMO6LCBuw6FzbGVkbmUga2xlc2Fqw7ouIFRlbnRvIHN5c3RlbWF0aWNrw70gdHZhciBuYXpuYcSNdWplLCDFvmUgcmV6w61kdcOhIG5lbWFqw7ogw7pwbG5lIG7DoWhvZG7DqSByb3psb8W+ZW5pZS4gTcO0xb5lIHRvIHBvdWthem92YcWlIG5hIG1pZXJudSBuZWxpbmVhcml0dSB2IG1vZGVsaSBhbGVibyBuYSB0bywgxb5lIHYgbW9kZWxpIGNow71iYSDEj2FsxaFpYSBwcmVtZW5uw6EsIGt0b3LDoSBieSBsZXDFoWllIHphY2h5dGlsYSB2esWlYWggbWVkemkgdGVwbG90b3UgYSBtZXRlb3JvbG9naWNrw71taSBmYWt0b3JtaS4KCiMjIyMgR3JhZnkgQytSICoqCgpUw6F0byBhbmFsw716YSBuw6FtIG3DtMW+ZSBwb23DtGPFpSBwcmkgaMS+YWRhbsOtIG9kcG92ZWRlIG5hIG90w6F6a3UsIGt0b3LDuiBwcmVtZW5uw7ogYnkgc21lIG1hbGkgdHJhbnNmb21vdmHFpSBwb21vY291IG5lamFrZWogem7DoW1laiBmdW5rY2llLiBWeWNow6FkemFqbWUgeiBww7R2b2RuZWogcm92bmljZQoKJCR5X3QgPSBcYmV0YV8wICsgXGJldGFfMSB4X3t0MX0gKyBcZG90cyArXGJldGFfayB4X3t0a30gKyB1X3QkJApUw7p0byByb3ZuaWN1IG5hanBydiBvZGhhZG5lbWUgYSBwb3RvbSB2eWtyZXPEvnVqZW1lIGdyYWZ5LCBrZGUgdsO9cmF6IGNvbXBvbmVudCtyZXNpZHVhbCAoQytSKSBwbG90IHZ5a3Jlc8S+dWplIG5hIHp2aXNsZWogb3NpICRcaGF0e1xiZXRhfV9peF97dGl9K2VfdCQgYSBuYSB2b2Rvcm92bmVqIG9zaSB2eWtyZXPEvnVqZSBob2Rub3R5ICR4X3t0aX0kCgpUaWV0byBncmFmeSBwb23DoWhhasO6IGlkZW50aWZpa292YcWlIG5lbGluZcOhcm5lIHZ6xaVhaHkgcHJlIGthxb5kw70gcmVncmVzb3I6CgpgYGB7cn0KY2FyOjpjclBsb3RzKG1vZGVsKQoKYGBgCkNvbXBvbmVudCArIFJlc2lkdWFsIGdyYWZ5IG5hem5hxI11asO6LCDFvmUgdiBtb2RlbGkgc8O6IHByw610b21uw6kgbmVsaW5lw6FybmUgdnrFpWFoeS4gUHJpIG9ib2NoIHByZW1lbm7DvWNoIOKAkyBBaXIuUHJlc3N1cmUuLmhQYS4gYWogV2luZC5TcGVlZC4ubS5zLiDigJMgdmlkw61tZSB2w71yYXpuw6kgemFrcml2ZW5pZSBydcW+b3ZlaiB2eWhsYWRlbmVqIGtyaXZreSwga3RvcsOpIHNhIHBvZHN0YXRuZSBvZGNoecS+dWplIG9kIG1vZHJlaiBsaW5lw6FybmVqIHByaWFta3kuIFRlbnRvIHJvemRpZWwgdWthenVqZSwgxb5lIGxpbmXDoXJuYSDFoXBlY2lmaWvDoWNpYSBtb2RlbHUgbmVtdXPDrSBwb3N0YcSNb3ZhxaUgbmEgemFjaHl0ZW5pZSBza3V0b8SNbsOpaG8gdnBseXZ1IHTDvWNodG8gcHJlbWVubsO9Y2ggbmEgdGVwbG90dS4gTcO0xb5lIGJ5xaUgcHJldG8gdmhvZG7DqSB6dsOhxb5pxaUgZG9wbG5lbmllIG1vZGVsdSBvIG5lbGluZcOhcm5lIMSNbGVueSwgbmFwcsOta2xhZCBrdmFkcmF0aWNrw6kgdHJhbnNmb3Jtw6FjaWUuCgojIyAzLiBOZWxpbmXDoXJuYSDFoXBlY2lmaWvDoWNpYQoKxIxhc3Rva3LDoXQgbcO0xb5lbWUgYWogemxvxb5pdGVqxaFpZSBuZWxpbmXDoXJuZSB2esWlYWh5IG1vZGVsb3ZhxaUgcyBwb21vY291IGljaCBhcHJveGltw6FjaWUgcyBwb2x5bsOzbW9tLCB0ZWRhIHYgcHLDrXBhZGUga3ZhZHJhdGlja8O9Y2ggxI1sZW5vdgoKJCR5X3QgPSBcYmV0YV8wICsgXGJldGFfMSB4X3t0MTB9ICsgXGRvdHMgK1xiZXRhX2sgeF97dGt9ICsgXGRvdHMgKyBcZ2FtbWFfaVxoYXQgeF97aWt9XjIgKyBcZG90cyArIFxnYW1tYV9qXGhhdCB4X3tqa31eMiArIFxkb3RzICsgdV90JCQKUHLDrWtsYWQgbmEgdMO6dG8gbW9kaWZpa8OhY2l1IHV2aWTDrW1lIG5pxb7FoWllLgoKIyMgMy4gUG9yb3ZuYW5pZSB6w6FrbGFkbsOpaG8gYSBtb2RpZmlrb3ZhbsOpaG8gbW9kZWx1CgpQcmVkcG9rbGFkYWptZSwgxb5lIHNhIHByaSBuZWxpbmXDoXJueWNoIMO6cHJhdsOhY2ggcMO0dm9kbmVqIHJvdm5pY2UgZG9zdGFuZW1lIGsgemF2ZWRlbml1IGt2YWRyw6F0dSBwcmVtZW5uw71jaCBBaXIuUHJlc3N1cmUuLmhQYS4gYSBXaW5kLlNwZWVkLi5tLnMuLCBuYWtvxL5rbyBuw6FzIGsgdG9tdSBtb3RpdnVqZSBDb21wb25lbnQgKyBSZXNpZHVhbCBQbG90IHV2ZWRlbsO9IHZ5xaHFoWllLiBQcsOhdmUgcHJpIHTDvWNodG8gcHJlbWVubsO9Y2ggc2Egdnlyb3ZuYW7DoSBrcml2a2EgdsO9cmF6bmUgb2RjaHnEvnVqZSBvZCBsaW5lw6FybmVqIHByaWFta3ksIMSNbyBuYXpuYcSNdWplIHBvdHJlYnUgemFjaHl0acWlIG5lbGluZcOhcm55IHZ6xaVhaC4gCgpBayBtw6EgdHJhbnNmb3Jtb3ZhbsO9IG1vZGVsIHZ5xaHFocOtIHVwcmF2ZW7DvSBrb2VmY2llbnQgZGV0ZXJtaW7DoWNpZSAkUl4yX3thZGp9JCBhIHByaSBSRVNFVCB0ZXN0IHByaWptZW1lIGFsdGVybmF0w612bnUgaHlwb3TDqXp1LCBvZHBvcsO6xI1hbWUgc2kgdsO9c2xlZGt5IHBvdHZyZGnFpSBzIHBvbW9jb3UgQW5vdmEgdGVzdHUgb2JvY2ggbW9kZWxvdiBhIHByw61wYWRuZSBvcGFrb3ZhbsOpaG8gUmVzZXQgVGVzdHUgdXBsYXRuZW5lbsOpaG8gbmEgbmVsaW5lw6FybmUgdHJhbnNmb3Jtb3ZhbsO9IG1vZGVsCgpgYGB7cn0KbW9kZWwgPC0gbG0oVGVtcGVyYXR1cmUuLi5DLiB+ICsxICsgQWlyLlByZXNzdXJlLi5oUGEuICsgV2luZC5TcGVlZC4ubS5zLikKbW9kZWxfa3ZhZHIgPC0gbG0oVGVtcGVyYXR1cmUuLi5DLiB+ICsxICsgQWlyLlByZXNzdXJlLi5oUGEuICsgV2luZC5TcGVlZC4ubS5zLiArICBJKEFpci5QcmVzc3VyZS4uaFBhLl4yKSArIEkoV2luZC5TcGVlZC4ubS5zLl4yKSAgKQpzdW1tYXJ5KG1vZGVsX2t2YWRyKQphbm92YShtb2RlbCwgbW9kZWxfa3ZhZHIpCnJlc2V0dGVzdChtb2RlbF9rdmFkcikKCmBgYAoKUG8gcm96xaHDrXJlbsOtIHDDtHZvZG7DqWhvIGxpbmXDoXJuZWhvIG1vZGVsdSBvIGt2YWRyYXRpY2vDvSDEjWxlbiBBaXIuUHJlc3N1cmUuLmhQYS5eMiBzYSB1a2F6dWplLCDFvmUgYW5pIGplZG5hIHogcHJlbWVubsO9Y2gg4oCTIHZyw6F0YW5lIG5vdm9wcmlkYW7DqWhvIG5lbGluZcOhcm5laG8gcHJ2a3Ug4oCTIG5pZSBqZSDFoXRhdGlzdGlja3kgdsO9em5hbW7DoS4gUC1ob2Rub3R5IHNhIHBvaHlidWrDuiBva29sbyBocmFuaWNlIDAuMDUsIG5vIMW+aWFkbmEganUgamVkbm96bmHEjW5lIG5lcHJla3JhxI11amUuIFByZW1lbm7DoSBXaW5kLlNwZWVkLi5tLnMuIGplIMO6cGxuZSBuZXbDvXpuYW1uw6EgKHAg4omIIDAuNjQpIGEga3ZhZHJhdGlja8O9IMSNbGVuIHRsYWt1IHZ6ZHVjaHUgbcOhIGxlbiBocmFuacSNbsO9IGVmZWt0IChwIOKJiCAwLjA1MykuCgpVcHJhdmVuw70ga29lZmljaWVudCBkZXRlcm1pbsOhY2llIChBZGp1c3RlZCBSwrIgPSAwLjIyNTkpIG5hem5hxI11amUsIMW+ZSBtb2RlbCB2eXN2ZXTEvnVqZSBpYmEgcHJpYmxpxb5uZSAyMyAlIHZhcmlhYmlsaXR5IHRlcGxvdHksIMSNbyBqZSBzbGFiw6kuIE5hdnnFoWUsIG9wcm90aSBww7R2b2Ruw6ltdSBtb2RlbHUgc2EgaG9kbm90YSBlxaF0ZSB6bsOtxb5pbGEsIHRha8W+ZSBuZWxpbmXDoXJuZSByb3rFocOtcmVuaWUgbmVwcmluaWVzbG8gxb5pYWRuZSB6bGVwxaFlbmllLgoKQ2Vsa292w70gRi10ZXN0IChwIOKJiCAwLjE4MjcpIHRha3RpZcW+IHBvdHZyZHp1amUsIMW+ZSBtb2RlbCBha28gY2Vsb2sgbmllIGplIMWhdGF0aXN0aWNreSB2w716bmFtbsO9LgoKWiB2w71zbGVka292IHRlZGEgdnlwbMO9dmEsIMW+ZSBwcmlkYW5pZSBrdmFkcmF0aWNrw6lobyDEjWxlbmEgbW9kZWwgbmV6bGVwxaFpbG8gYSBqZSB2aG9kbmVqxaFpZSB6b3N0YcWlIHByaSBww7R2b2RuZWogbGluZcOhcm5laiDFoXBlY2lmaWvDoWNpaS4KCgojIyMgNi4gVHJhbnNmb3Jtw6FjaWEgcG9tb2NvdSBkdW1teSBwcmVtZW5uZWogYSBsaW5lw6FybmVqIGxvbWVuZWogZnVua2NpZQoKUHJlZHBva2xhZGFqbWUsIMW+ZSBtw6FtZSBkdW1teSBwcmVtZW5uw7ogJERfdCQsIGt0b3LDoSBvYnNhaHVqZSBsZW4gbnVseSBhIGplZG5pxI1reS4gVGFrw6F0byBwcmVtZW5uw6Egc2EgZMOhIHZ5dcW+acWlIG1vZGVsb3ZhbmllIHpsb21vdiwgYSB0byBuYXByLgoKMS4gemxvbSB2IGF1dG9uw7Ntbm9tIMSNbGVuZSAkXGJldGFfMCQgYSB0byBuYXNsZWRvdm5vdSDFoXBlY2lmaWvDoWNpb3UKJCR5X3QgPSBcYmV0YV8wICsgXGJldGFfRCBEKyBcYmV0YV8xIHhfe3QxfSArIFxkb3RzICtcYmV0YV9rIHhfe3RrfSArIHVfdCQkCsSNbyBpbnRlcnByZXR1amVtZSBha28gcG9zdW4gcmVncmVzbmVqIHByaWFta3kgKHJlZ3Jlc25laiBuYWRyb3ZpbnkpIG8gJFxiZXRhX0QkIGplZG5vdGllayBwb3pkxLrFviB6dmlzbGVqIG9zaSBhIHRvIGxlbiB2IHBvem9yb3ZhbmlhY2gsIGFrIGplIHNwbG5lbsOhIHBvZG1pZW5rYSAkRF90ID0gMSQKMi4gemxvbSB2IHNrbG9uZSByZWdyZXNuZWogcHJpYW1reSAobmFkcm92aW55KSBhIHRvIGxlbiB2IHBvem9yb3ZhbmlhY2gsIGFrIGplIHNwbG5lbsOhIHBvZG1pZW5rYSAkRF90ID0gMSQsIMSNbyBkb3NpYWhuZW1lIG5hc2xlZG92bm91IMWhcGVjaWZpa8OhY2lvdQokJHlfdCA9IFxiZXRhXzAgKyAgXGJldGFfMSB4X3t0MX0gKyBcZG90cyArIFxiZXRhX3tpfXhfe3RpfSArIFxiZXRhX3tEaX1EX3R4X3t0aX0rIFxkb3RzICsgXGJldGFfayB4X3t0a30gKyB1X3QkJAprZGUgdGVkYSBza2xvbiBwcmlhbWt5IHBvemTEusW+IHByZW1lbm5lICR4X3t0aX0kIGplICRcYmV0YV9pJCBhbGUgbGVuIHYgcHLDrXBhZGUgJERfdD0wJCwgaW5hayBqZSB0ZW4gc2tsb24gcm92bsO9ICRcYmV0YV9pK1xiZXRhX3tEX2l9JC4KCkFrIHNhIHBvenJpZW1lIG5hIENvbXBvbmVudCArIFJlc2lkdWFsIGdyYWYgcHJlIHZ5c3ZldMS+dWrDumN1IHByZW1lbm7DuiBBaXIuUHJlc3N1cmUuLmhQYS4sIG3DtMW+ZW1lIHNpIHbFoWltbsO6xaUsIMW+ZSB2eXJvdm5hbsOhIGtyaXZrYSBtZW7DrSBzdm9qIHR2YXIgcHJpIHVyxI1pdMO9Y2ggaG9kbm90w6FjaCB0bGFrdSB2emR1Y2h1LiBOYWptw6QgdiBvYmxhc3RpIG9rb2xvIHByaWJsacW+bmUgOTkwIGhQYSBzYSBrcml2a2Egb2RjaHnEvnVqZSBvZCBsaW5lw6FybmVqIHByaWFta3ksIMSNbyBuYXpuYcSNdWplIG1vxb5uw7ogem1lbnUgc2tsb251IGFsZWJvIHJvemRpZWxueSB2esWlYWggbWVkemkgdGxha29tIHZ6ZHVjaHUgYSB0ZXBsb3RvdS4KWmF2ZcSPbWUgcHJldG8gcG9tb2Nuw7ogcHJlbWVubsO6IERVTSwgcHJlIGt0b3LDuiBidWRlIHBsYXRpxaUgRFVNID0gMSwgYWsgQWlyLlByZXNzdXJlLi5oUGEuIHByZXNpYWhuZSB1csSNaXTDuiBocmFuaWN1IChuYXByLiBzdHJlZG7DuiBob2Rub3R1IGFsZWJvIMO6cm92ZcWILCBrZGUgc2Ega3JpdmthIGzDoW1lKSwgRFVNID0gMCBpbmFrLCBhIHBva8O6c21lIHNhIGFuYWx5em92YcWlIHZwbHl2IHRsYWt1IHZ6ZHVjaHUgc2VwYXLDoXRuZSBwcmUgb2JpZHZlIMO6cm92bmUuCgpLdmFudGlmaWt1am1lIHRlcmF6IG1vZGVsIHNvIHpsb21vbSB2IGF1dG9uw7Ntbm9tIMSNbGVuZSwgdGVkYSBvc29iaXRuZSBwcmUgcHLDrXBhZHksIGtlxI8gcGxhdMOtIEFpci5QcmVzc3VyZS4uaFBhLiBqZSBwb2QgdG91dG8gaHJhbmljb3UgKERVTSA9IDApIGEga2XEjyBqdSBwcmVzYWh1amUgKERVTSA9IDEpLgoKVGFrdG8gbcO0xb5lbWUgb3ZlcmnFpSwgxI1pIG3DoSB0bGFrIHZ6ZHVjaHUgb2RsacWhbsO9IHZwbHl2IG5hIHRlcGxvdHUgdiByw7R6bnljaCBvYmxhc3RpYWNoIGplaG8gaG9kbsO0dC4KCgpgYGB7cn0KdWRhamUkRFVNIDwtIGlmZWxzZSh1ZGFqZSRBaXIuUHJlc3N1cmUuLmhQYS4gPCA5OTAsIDAsIDEpCm1vZGVsRF9hdXRvIDwtIGxtKFRlbXBlcmF0dXJlLi4uQy4gfiArMSArIERVTSArIEFpci5QcmVzc3VyZS4uaFBhLiArIFdpbmQuU3BlZWQuLm0ucy4sZGF0YT11ZGFqZSApIApzdW1tYXJ5KG1vZGVsRF9hdXRvKQpgYGAKUG8gemF2ZWRlbsOtIGR1bW15IHByZW1lbm5laiBEVU0sIGt0b3LDoSByb3pkZcS+dWplIGhvZG5vdHkgdGxha3UgdnpkdWNodSBwb2TEvmEgaHJhbmljZSA5OTAgaFBhLCBzYSB1a2F6dWplLCDFvmUgdGVudG8gbW9kZWwgbmVwcmluaWVzb2wgxb5pYWRuZSB6bGVwxaFlbmllIG9wcm90aSBww7R2b2RuZWogxaFwZWNpZmlrw6FjaWkuIEhvZG5vdGEgRFVNIG5pZSBqZSDFoXRhdGlzdGlja3kgdsO9em5hbW7DoSAocCDiiYggMC4zNzQpLCDEjW8gem5hbWVuw6EsIMW+ZSByb3pkZWxlbmllIGTDoXQgbmEgZHZlIHNrdXBpbnkgcG9kxL5hIHRsYWt1IHZ6ZHVjaHUgbmVtZW7DrSB6YWNoeXRlbsO9IHZ6xaVhaCBrIHRlcGxvdGUuClJvdm5ha28gYW5pIG9zdGF0bsOpIHByZW1lbm7DqSDigJMgQWlyLlByZXNzdXJlLi5oUGEuIChwIOKJiCAwLjY0MCkgYSBXaW5kLlNwZWVkLi5tLnMuIChwIOKJiCAwLjY5Mykg4oCTIG5pZSBzw7ogdsO9em5hbW7DqSwgdGFrxb5lIG1vZGVsIG5ldmllIHNwb8S+YWhsaXZvIHZ5c3ZldGxpxaUgdmFyaWFiaWxpdHUgdGVwbG90eSBuYSB6w6FrbGFkZSB0w71jaHRvIHZ5c3ZldMS+dWrDumNpY2ggdmVsacSNw61uLgpLb2VmaWNpZW50IGRldGVybWluw6FjaWUgUsKyID0gMC4xNjggYSBuZWdhdMOtdm55IHVwcmF2ZW7DvSBBZGp1c3RlZCBSwrIgPSDigJMwLjE0NCB1a2F6dWrDuiwgxb5lIG1vZGVsIG3DoSB2ZcS+bWkgc2xhYsO6IHZ5c3ZldMS+b3ZhY2l1IHNpbHUgYSBwcmlkYW5pZSBkdW1teSBwcmVtZW5uZWogZG9rb25jYSBtb2RlbHUgxaFrb2TDrS4gQWogRi10ZXN0IChwIOKJiCAwLjY2OSkgcG90dnJkenVqZSwgxb5lIG1vZGVsIGFrbyBjZWxvayBuaWUgamUgxaF0YXRpc3RpY2t5IHbDvXpuYW1uw70uCkNlbGtvdm8gbW/Fvm5vIGtvbsWhdGF0b3ZhxaUsIMW+ZSB6YXZlZGVuaWUgcHJlbWVubmVqIERVTSBuZWJvbG8gcHLDrW5vc27DqSBhIHRlbnRvIG1vZGVsIG5lcG9uw7prYSBsZXDFocOtIG9waXMgdGVwbG90eSBuZcW+IHDDtHZvZG7DvSBsaW5lw6FybnkgbW9kZWwgYmV6IHpsb211LgoKCgpgYGB7cn0KCm1vZGVsRF9za2xvbiA8LSBsbShUZW1wZXJhdHVyZS4uLkMuIH4gKzEgICsgQWlyLlByZXNzdXJlLi5oUGEuICsgSShEVU0qQWlyLlByZXNzdXJlLi5oUGEuKSArICBXaW5kLlNwZWVkLi5tLnMuLGRhdGE9dWRhamUgKSAKc3VtbWFyeShtb2RlbERfc2tsb24pCmBgYApWIG1vZGVsaSBzbyB6bG9tb20gdiBza2xvbmUg4oCTIHRlZGEgcG8gcHJpZGFuw60gaW50ZXJha8SNbsOpaG8gxI1sZW5hIERVTSDDlyBBaXIuUHJlc3N1cmUuLmhQYS4g4oCTIHNhIHVrYXp1amUsIMW+ZSDFvmlhZG55IHoga29lZmljaWVudG92IG5pZSBqZSDFoXRhdGlzdGlja3kgdsO9em5hbW7DvS4gTGluZcOhcm55IGVmZWt0IHRsYWt1IHZ6ZHVjaHUgKHAg4omIIDAuNjMzKSwgaW50ZXJha8SNbsO9IMSNbGVuIChwIOKJiCAwLjM2OSkgYWogcsO9Y2hsb3PFpSB2ZXRyYSAocCDiiYggMC42OTUpIG1hasO6IHZ5c29rw6kgcC1ob2Rub3R5LCB0YWvFvmUgbW9kZWwgbmVwcmV1a2F6dWplIHJvemRpZWxueSBza2xvbiByZWdyZXNuZWogcHJpYW1reSBwcmUgZHZlIMO6cm92bmUgdGxha3UgKERVTSA9IDAgYSBEVU0gPSAxKS4KS29lZmljaWVudCBkZXRlcm1pbsOhY2llIFLCsiA9IDAuMTY5OCBhIG5lZ2F0w612bnkgQWRqdXN0ZWQgUsKyID0g4oCTMC4xNDE2IHVrYXp1asO6LCDFvmUgbW9kZWwgdnlzdmV0xL51amUgbGVuIHZlxL5taSBtYWzDuiDEjWFzxaUgdmFyaWFiaWxpdHkgdGVwbG90eSBhIHBvIHBlbmFsaXrDoWNpaSBkb2tvbmNhIHDDtHNvYsOtLCBha29ieSBib2wgaG9yxaHDrSBuZcW+IG1vZGVsIGJleiB2eXN2ZXTEvnVqw7pjaWNoIHByZW1lbm7DvWNoLiBBaiBGLXRlc3QgKHAg4omIIDAuNjY1KSBwb3R2cmR6dWplLCDFvmUgbW9kZWwgYWtvIGNlbG9rIG5pZSBqZSDFoXRhdGlzdGlja3kgdsO9em5hbW7DvS4KWiB0b2hvIHZ5cGzDvXZhLCDFvmUgemF2ZWRlbmllIHpsb211IHYgc2tsb25lIG5lcHJpbmllc2xvIMW+aWFkbmUgemxlcMWhZW5pZS4gSW50ZXJha8SNbsO9IMSNbGVuIG5lYm9sIHByw61ub3Nuw70gYSBtb2RlbCBzdMOhbGUgbmVkb2vDocW+ZSBvcMOtc2HFpSB6w6F2aXNsb3PFpSB0ZXBsb3R5IG9kIHRsYWt1IHZ6ZHVjaHUgYSByw71jaGxvc3RpIHZldHJhLiBWaG9kbmVqxaFpZSBqZSB6b3N0YcWlIHByaSBqZWRub2R1Y2jFoW9tIGxpbmXDoXJub20gbW9kZWxpLgoKUG9yb3ZuYW5pZSBww7R2b2Ruw6lobyBtb2RlbHUgYSBtb2RlbHUgcyBwcmVtZW5saXbDvW0gc2tsb25vbSBuYWRyb3Zpbnkgdnlrb27DoW1lIHMgcG9tb2NvdSBhbm92YSB0ZXN0dToKCmBgYHtyfQphbm92YShtb2RlbCwgbW9kZWxEX3NrbG9uKQpyZXNldHRlc3QobW9kZWxEX3NrbG9uKQpgYGAKQU5PVkEgdGVzdCBwb3Jvdm7DoXZhIHDDtHZvZG7DvSBsaW5lw6FybnkgbW9kZWwgcyByb3rFocOtcmVuw71tIG1vZGVsb20gb2JzYWh1asO6Y2ltIGludGVyYWvEjW7DvSDEjWxlbiBEVU0gw5cgQWlyLlByZXNzdXJlLi5oUGEuLgpWw71zbGVkb2sgKHAg4omIIDAuMzY5MSkgdWthenVqZSwgxb5lIHJvesWhw61yZW7DvSBtb2RlbCBuaWUgamUgxaF0YXRpc3RpY2t5IGxlcMWhw60gbmXFviBww7R2b2Ruw70uIFByaWRhbmllIGludGVyYWvEjW7DqWhvIHRlcm11IHRlZGEgbmV6bGVwxaFpbG8gdnlzdmV0xL5vdmFjaXUgc2Nob3Bub3PFpSBtb2RlbHUuClJFU0VUIHRlc3QgcHJlIG1vZGVsRF9za2xvbiBtw6EgcC1ob2Rub3R1IOKJiCAwLjczNzUsIMSNbyB6bmFtZW7DoSwgxb5lIG5lemFtaWV0YW1lIG51bG92w7ogaHlwb3TDqXp1IG8gc3Byw6F2bmVqIMWhcGVjaWZpa8OhY2lpIG1vZGVsdS4KSW7DvW1pIHNsb3ZhbWksIG5ldmlkw61tZSBkw7RrYXp5IG8gbmVzcHLDoXZuZWogZnVua8SNbmVqIGZvcm1lLCBhIHByaWRhbmllIMSPYWzFoWVqIG5lbGluZWFyaXR5IG5pZSBqZSBwb3RyZWJuw6kuCkFuaSBBTk9WQSB0ZXN0LCBhbmkgUkVTRVQgdGVzdCBuZXBvc2t5dHVqw7ogZMO0dm9kIG5hIHRvLCBhYnkgc21lIHVwcmVkbm9zdG5pbGkgbW9kZWwgc28gemxvbW9tIHYgc2tsb25lLgpSb3rFocOtcmVuw70gbW9kZWwgbmVwcmluaWVzb2wgxb5pYWRuZSB6bGVwxaFlbmllLCBhIHDDtHZvZG7DvSBqZWRub2R1Y2jFocOtIGxpbmXDoXJueSBtb2RlbCBqZSBwb3N0YcSNdWrDumNpLgoK