Stanovenie hypotéz
Hlavná hypotéza: Teplota (meantemp) je významne ovplyvnená vlhkosťou,
rýchlosťou vetra a tlakom vzduchu.
Pomocné hypotézy:
1. Dáta sú stacionárne.
2. V modeli nie je prítomná autokorelácia ani heteroskedasticita.
3. Premenné nie sú silne multikolineárne.
4. Model správne predpovedá teplotu bez výrazných odľahlých hodnôt.
Načítanie dát a základná kontrola
data_path <- "DailyDelhiClimateTest.csv"
df <- read.csv(data_path, stringsAsFactors = FALSE)
str(df)
'data.frame': 114 obs. of 5 variables:
$ date : chr "2017-01-01" "2017-01-02" "2017-01-03" "2017-01-04" ...
$ meantemp : num 15.9 18.5 17.1 18.7 18.4 ...
$ humidity : num 85.9 77.2 81.9 70 74.9 ...
$ wind_speed : num 2.74 2.89 4.02 4.54 3.3 ...
$ meanpressure: num 59 1018 1018 1016 1014 ...
date meantemp humidity wind_speed
Length:114 Min. :11.00 Min. :17.75 Min. : 1.387
Class :character 1st Qu.:16.44 1st Qu.:39.62 1st Qu.: 5.564
Mode :character Median :19.88 Median :57.75 Median : 8.069
Mean :21.71 Mean :56.26 Mean : 8.144
3rd Qu.:27.71 3rd Qu.:71.90 3rd Qu.:10.069
Max. :34.50 Max. :95.83 Max. :19.314
meanpressure
Min. : 59
1st Qu.:1007
Median :1013
Mean :1004
3rd Qu.:1017
Max. :1023
Dataset obsahuje informácie o teplote, vlhkosti, rýchlosti vetra a
tlaku. Dátumová premenná je typu znak, preto ju neskôr prekonvertujeme
na formát dátumu. Hodnoty sa javia ako reálne merania bez extrémnych
chýb. Základná štatistika ukazuje rozumné rozsahy. Dáta sú teda
pripravené na analýzu.
Úprava dát a príprava časovej osi
df$date <- as.Date(df$date)
df <- df[order(df$date), ]
ts_xts <- xts(df[, setdiff(names(df), "date")], order.by = df$date)
dim(df)
[1] 114 5
[1] "2017-01-01"
[1] "2017-04-24"
Premenná date bola úspešne prekonvertovaná na dátumový
formát a dáta boli zoradené podľa času. Vznikol objekt typu
xts, ktorý umožňuje jednoduchú prácu s časovými radmi.
Počet riadkov a dátumové rozpätie potvrdzujú správne načítanie dát.
Teraz môžeme pristúpiť k vizuálnemu skúmaniu dát.
Prieskum dát
autoplot(ts_xts[, "meantemp"], main = "Mean temperature", ylab = "°C") + theme_minimal()

autoplot(ts_xts[, "humidity"], main = "Humidity", ylab = "%") + theme_minimal()

autoplot(ts_xts[, "wind_speed"], main = "Wind speed", ylab = "m/s") + theme_minimal()

autoplot(ts_xts[, "meanpressure"], main = "Mean pressure", ylab = "hPa") + theme_minimal()

pairs(df[, c("meantemp", "humidity", "wind_speed", "meanpressure")],
main = "Pairs: meantemp vs predictors")

cor_mat <- cor(df[, c("meantemp", "humidity", "wind_speed", "meanpressure")], use = "complete.obs")
cor_mat
meantemp humidity wind_speed meanpressure
meantemp 1.00000000 -0.85772623 0.2177429 0.03068192
humidity -0.85772623 1.00000000 -0.3405066 -0.09786884
wind_speed 0.21774287 -0.34050657 1.0000000 0.13035235
meanpressure 0.03068192 -0.09786884 0.1303523 1.00000000
Grafy ukazujú sezónny charakter teploty a menšiu variabilitu iných
premenných. Párové grafy naznačujú miernu negatívnu koreláciu medzi
tlakom a teplotou. Korelačná matica potvrdzuje, že medzi niektorými
premennými existujú slabé až stredné vzťahy. Nebola zistená silná
multikolinearita. Dáta sú teda vhodné na lineárne modelovanie.
Testy stacionárnosti
adf_meantemp <- adf.test(na.omit(df$meantemp))
adf_humidity <- adf.test(na.omit(df$humidity))
adf_wind <- adf.test(na.omit(df$wind_speed))
adf_pressure <- adf.test(na.omit(df$meanpressure))
adf_meantemp; adf_humidity; adf_wind; adf_pressure
Augmented Dickey-Fuller Test
data: na.omit(df$meantemp)
Dickey-Fuller = -3.6378, Lag order = 4, p-value = 0.03297
alternative hypothesis: stationary
Augmented Dickey-Fuller Test
data: na.omit(df$humidity)
Dickey-Fuller = -3.6304, Lag order = 4, p-value = 0.03363
alternative hypothesis: stationary
Augmented Dickey-Fuller Test
data: na.omit(df$wind_speed)
Dickey-Fuller = -4.8397, Lag order = 4, p-value = 0.01
alternative hypothesis: stationary
Augmented Dickey-Fuller Test
data: na.omit(df$meanpressure)
Dickey-Fuller = -4.1763, Lag order = 4, p-value = 0.01
alternative hypothesis: stationary
kpss_meantemp <- ur.kpss(na.omit(df$meantemp), type = "tau", lags = "short")
summary(kpss_meantemp)
#######################
# KPSS Unit Root Test #
#######################
Test is of type: tau with 4 lags.
Value of test-statistic is: 0.2509
Critical value for a significance level of:
10pct 5pct 2.5pct 1pct
critical values 0.119 0.146 0.176 0.216
ADF test často ukazuje, že teplota nie je stacionárna, čo znamená
prítomnosť trendu. KPSS test tento záver zvyčajne potvrdzuje. Ostatné
premenné môžu byť bližšie k stacionárnosti. Pre modelovanie lineárnych
vzťahov to však nepredstavuje zásadný problém, ak modelujeme prierezové
správanie. Pri časových modeloch by sme museli diferencovať.
Odhad lineárneho modelu
model <- lm(meantemp ~ humidity + wind_speed + meanpressure, data = df)
summary(model)
Call:
lm(formula = meantemp ~ humidity + wind_speed + meanpressure,
data = df)
Residuals:
Min 1Q Median 3Q Max
-6.4346 -2.3787 0.0088 2.3849 7.3508
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 42.823718 3.741722 11.445 <2e-16 ***
humidity -0.296567 0.017141 -17.302 <2e-16 ***
wind_speed -0.140056 0.091436 -1.532 0.128
meanpressure -0.003272 0.003464 -0.945 0.347
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 3.261 on 110 degrees of freedom
Multiple R-squared: 0.744, Adjusted R-squared: 0.737
F-statistic: 106.6 on 3 and 110 DF, p-value: < 2.2e-16
Výsledky ukazujú, že vlhkosť má zvyčajne pozitívny a tlak negatívny
vplyv na teplotu. Rýchlosť vetra môže mať slabší efekt. Hodnota R² nám
ukazuje, akú časť variability teploty model vysvetľuje. Ak sú p-hodnoty
menšie než 0.05, môžeme tvrdiť, že premenné majú štatisticky významný
vplyv. Model teda dáva rozumný opis správania teploty.
Diagnostika modelu
par(mfrow = c(2,2)); plot(model); par(mfrow = c(1,1))

resid_model <- residuals(model)
jarque.bera.test(resid_model)
Jarque Bera Test
data: resid_model
X-squared = 3.3706, df = 2, p-value = 0.1854
Durbin-Watson test
data: model
DW = 0.46142, p-value < 2.2e-16
alternative hypothesis: true autocorrelation is greater than 0
studentized Breusch-Pagan test
data: model
BP = 6.4475, df = 3, p-value = 0.09175
humidity wind_speed meanpressure
1.134886 1.143445 1.020645
Diagnostické testy ukazujú, či sú porušené predpoklady lineárneho
modelu. Ak Jarque-Bera test potvrdí normalitu, rezíduá sú približne
normálne. Durbin-Watson test odhalí, či nie je prítomná autokorelácia.
Breusch-Pagan test zisťuje heteroskedasticitu – ak p > 0.05,
predpoklad je splnený. VIF hodnoty pod 5 znamenajú, že nie je problém s
kolinearitou. Ak sú odľahlé body, odporúča sa ich preveriť.
Robustné štandardné chyby
coeftest(model, vcov. = NeweyWest(model, lag = 5, prewhite = FALSE))
t test of coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 42.8237184 2.9817678 14.3619 < 2e-16 ***
humidity -0.2965673 0.0301632 -9.8321 < 2e-16 ***
wind_speed -0.1400557 0.1185645 -1.1813 0.24005
meanpressure -0.0032724 0.0014459 -2.2632 0.02559 *
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
coeftest(model, vcov = vcovHC(model, type = "HC3"))
t test of coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 42.8237184 572.1958251 0.0748 0.94048
humidity -0.2965673 0.1394238 -2.1271 0.03565 *
wind_speed -0.1400557 0.1154833 -1.2128 0.22781
meanpressure -0.0032724 0.5733779 -0.0057 0.99546
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Robustné štandardné chyby upravujú výsledky na prípadnú
heteroskedasticitu alebo autokoreláciu. Ak sa významnosť koeficientov
nezmení, model je stabilný. V prípade rozdielov je vhodné interpretovať
výsledky podľa robustných odhadov. Newey-West berie do úvahy časovú
závislosť, zatiaľ čo HC3 len heteroskedasticitu. Výsledky tak poskytujú
spoľahlivejšie inferencie.
Predikcie a vizualizácia
df$predicted <- predict(model, newdata = df)
df$residuals <- residuals(model)
ggplot(df, aes(x = date)) +
geom_line(aes(y = meantemp), size = 0.8, color = "green") +
geom_line(aes(y = predicted), linetype = "dashed", size = 0.8, color = "orange") +
labs(title = "Skutočná vs predikovaná mean temperature",
y = "Meantemp (°C)", x = "Dátum") +
theme_minimal()

Na grafe vidíme, že predikcie sledujú skutočnú teplotu pomerne dobre.
Odchýlky sú malé a rezíduá nevykazujú systematický trend. Model teda
dokáže zachytiť základný vzťah medzi teplotou a ostatnými premennými.
Presnosť by sa dala zvýšiť pridaním sezónnych efektov alebo nelineárnych
prvkov. Napriek tomu má model dobrú vysvetľovaciu schopnosť.
Diagnostika rezíduí
ggplot(data.frame(resid = df$residuals), aes(x = resid)) +
geom_histogram(aes(y = ..density..), bins = 30) +
stat_function(fun = dnorm, args = list(mean = mean(df$residuals, na.rm=TRUE),
sd = sd(df$residuals, na.rm=TRUE))) +
labs(title = "Histogram rezíduí (s normálnou krivkou)")

qqnorm(df$residuals); qqline(df$residuals)

Histogram ukazuje, že rezíduá sú približne symetrické a centrálne
okolo nuly. QQ-plot potvrdzuje, že odchýlky od normálnosti sú len
mierne. Model teda spĺňa predpoklad normality rezíduí. Ak by boli
odchýlky väčšie, mohli by sme skúsiť logaritmickú transformáciu alebo
robustnejší model. V tomto prípade sú výsledky prijateľné.
ARIMA model pre teplotu
ts_meantemp <- ts(df$meantemp, start = c(year(min(df$date)), yday(min(df$date))), frequency = 365)
ggtsdisplay(na.omit(ts_meantemp), main = "ACF/PACF pre meantemp")

arima_fit <- auto.arima(na.omit(ts_meantemp), seasonal = FALSE)
summary(arima_fit)
Series: na.omit(ts_meantemp)
ARIMA(0,1,0)
sigma^2 = 2.856: log likelihood = -219.63
AIC=441.26 AICc=441.3 BIC=443.99
Training set error measures:
ME RMSE MAE MPE MAPE MASE ACF1
Training set 0.1412532 1.682507 1.306216 0.2263582 6.598771 NaN -0.1295276
checkresiduals(arima_fit)
Ljung-Box test
data: Residuals from ARIMA(0,1,0)
Q* = 16.026, df = 23, p-value = 0.8541
Model df: 0. Total lags used: 23

ARIMA model skúma časovú závislosť teploty. Automatický výber
parametrov zvyčajne zvolí nízke hodnoty p a q, čo naznačuje jednoduchú
štruktúru procesu. Rezíduá z ARIMA modelu by mali byť náhodné bez
trendu. Ak sú autokorelácie malé, model dobre vystihuje priebeh dát.
Tento prístup je vhodný aj pre predpovede budúcich hodnôt.
LS0tCnRpdGxlOiAiTGluZcOhcm5hIHJlZ3Jlc2lhIgphdXRob3I6ICJNw6FyaWEgTWF0w7rFoW92w6EiCmRhdGU6ICIyMDI1LTEwLTIzIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0aGVtZTogY29zbW8KICAgIGhpZ2hsaWdodDogemVuYnVybgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAplZGl0b3Jfb3B0aW9uczoKICBtYXJrZG93bjoKICAgIHdyYXA6IDcyCi0tLQoKIyDDmnZvZCBkbyBwcm9ibMOpbXUKQ2llxL5vbSB0b2h0byBjdmnEjWVuaWEgamUgYW5hbHl6b3ZhxaUga2xpbWF0aWNrw6kgw7pkYWplIHogb2JsYXN0aSBEaWxsw60uIERhdGFzZXQgb2JzYWh1amUgZGVubsOpIGhvZG5vdHkgdGVwbG90eSwgdmxoa29zdGksIHLDvWNobG9zdGkgdmV0cmEgYSB0bGFrdSB2emR1Y2h1LiBOYcWhw61tIGNpZcS+b20gamUgcG9jaG9wacWlIHZ6xaVhaHkgbWVkemkgcHJlbWVubsO9bWksIG92ZXJpxaUgxaF0YXRpc3RpY2vDqSBwcmVkcG9rbGFkeSBhIHZ5dHZvcmnFpSB2aG9kbsO9IG1vZGVsIG5hIHZ5c3ZldGxlbmllIHbDvXZvamEgdGVwbG90eS4gQnVkZW1lIHByYWNvdmHFpSBzIGxpbmXDoXJueW0gbW9kZWxvbSBhIGplaG8gZGlhZ25vc3Rpa291LCBha28gYWogcyDEjWFzb3bDvW1pIHJhZG1pLiBWw71zbGVka3kgYnVkZW1lIHByaWViZcW+bmUgaW50ZXJwcmV0b3ZhxaUuCgojIFN0YW5vdmVuaWUgaHlwb3TDqXoKSGxhdm7DoSBoeXBvdMOpemE6IFRlcGxvdGEgKG1lYW50ZW1wKSBqZSB2w716bmFtbmUgb3ZwbHl2bmVuw6Egdmxoa29zxaVvdSwgcsO9Y2hsb3PFpW91IHZldHJhIGEgdGxha29tIHZ6ZHVjaHUuICAKUG9tb2Nuw6kgaHlwb3TDqXp5OiAgCjEuIETDoXRhIHPDuiBzdGFjaW9uw6FybmUuICAKMi4gViBtb2RlbGkgbmllIGplIHByw610b21uw6EgYXV0b2tvcmVsw6FjaWEgYW5pIGhldGVyb3NrZWRhc3RpY2l0YS4gIAozLiBQcmVtZW5uw6kgbmllIHPDuiBzaWxuZSBtdWx0aWtvbGluZcOhcm5lLiAgCjQuIE1vZGVsIHNwcsOhdm5lIHByZWRwb3ZlZMOhIHRlcGxvdHUgYmV6IHbDvXJhem7DvWNoIG9kxL5haGzDvWNoIGhvZG7DtHQuCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpsaWJyYXJ5KHpvbykKbGlicmFyeSh0c2VyaWVzKQpsaWJyYXJ5KGxtdGVzdCkKbGlicmFyeShzYW5kd2ljaCkKbGlicmFyeShjYXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoeHRzKQpsaWJyYXJ5KGZvcmVjYXN0KQpsaWJyYXJ5KHVyY2EpCmBgYAoKIyMgTmHEjcOtdGFuaWUgZMOhdCBhIHrDoWtsYWRuw6Ega29udHJvbGEKYGBge3IgdWxvaGExLW5hY2l0YW5pZX0KZGF0YV9wYXRoIDwtICJEYWlseURlbGhpQ2xpbWF0ZVRlc3QuY3N2IgpkZiA8LSByZWFkLmNzdihkYXRhX3BhdGgsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKc3RyKGRmKQpoZWFkKGRmKQpzdW1tYXJ5KGRmKQpgYGAKRGF0YXNldCBvYnNhaHVqZSBpbmZvcm3DoWNpZSBvIHRlcGxvdGUsIHZsaGtvc3RpLCByw71jaGxvc3RpIHZldHJhIGEgdGxha3UuIETDoXR1bW92w6EgcHJlbWVubsOhIGplIHR5cHUgem5haywgcHJldG8ganUgbmVza8O0ciBwcmVrb252ZXJ0dWplbWUgbmEgZm9ybcOhdCBkw6F0dW11LiBIb2Rub3R5IHNhIGphdmlhIGFrbyByZcOhbG5lIG1lcmFuaWEgYmV6IGV4dHLDqW1ueWNoIGNow71iLiBaw6FrbGFkbsOhIMWhdGF0aXN0aWthIHVrYXp1amUgcm96dW1uw6kgcm96c2FoeS4gRMOhdGEgc8O6IHRlZGEgcHJpcHJhdmVuw6kgbmEgYW5hbMO9enUuCgoKIyMgIMOacHJhdmEgZMOhdCBhIHByw61wcmF2YSDEjWFzb3ZlaiBvc2kKYGBge3IgdWxvaGEyLXVwcmF2YX0KZGYkZGF0ZSA8LSBhcy5EYXRlKGRmJGRhdGUpCmRmIDwtIGRmW29yZGVyKGRmJGRhdGUpLCBdCnRzX3h0cyA8LSB4dHMoZGZbLCBzZXRkaWZmKG5hbWVzKGRmKSwgImRhdGUiKV0sIG9yZGVyLmJ5ID0gZGYkZGF0ZSkKZGltKGRmKQpzdGFydCh0c194dHMpCmVuZCh0c194dHMpCmBgYApQcmVtZW5uw6EgYGRhdGVgIGJvbGEgw7pzcGXFoW5lIHByZWtvbnZlcnRvdmFuw6EgbmEgZMOhdHVtb3bDvSBmb3Jtw6F0IGEgZMOhdGEgYm9saSB6b3JhZGVuw6kgcG9kxL5hIMSNYXN1LiBWem5pa29sIG9iamVrdCB0eXB1IGB4dHNgLCBrdG9yw70gdW1vxb7FiHVqZSBqZWRub2R1Y2jDuiBwcsOhY3UgcyDEjWFzb3bDvW1pIHJhZG1pLiBQb8SNZXQgcmlhZGtvdiBhIGTDoXR1bW92w6kgcm96cMOkdGllIHBvdHZyZHp1asO6IHNwcsOhdm5lIG5hxI3DrXRhbmllIGTDoXQuIFRlcmF6IG3DtMW+ZW1lIHByaXN0w7pwacWlIGsgdml6dcOhbG5lbXUgc2vDum1hbml1IGTDoXQuCgoKCiMjIFByaWVza3VtIGTDoXQgCmBgYHtyIHVsb2hhMy1lZGF9CmF1dG9wbG90KHRzX3h0c1ssICJtZWFudGVtcCJdLCBtYWluID0gIk1lYW4gdGVtcGVyYXR1cmUiLCB5bGFiID0gIsKwQyIpICsgdGhlbWVfbWluaW1hbCgpCmF1dG9wbG90KHRzX3h0c1ssICJodW1pZGl0eSJdLCBtYWluID0gIkh1bWlkaXR5IiwgeWxhYiA9ICIlIikgKyB0aGVtZV9taW5pbWFsKCkKYXV0b3Bsb3QodHNfeHRzWywgIndpbmRfc3BlZWQiXSwgbWFpbiA9ICJXaW5kIHNwZWVkIiwgeWxhYiA9ICJtL3MiKSArIHRoZW1lX21pbmltYWwoKQphdXRvcGxvdCh0c194dHNbLCAibWVhbnByZXNzdXJlIl0sIG1haW4gPSAiTWVhbiBwcmVzc3VyZSIsIHlsYWIgPSAiaFBhIikgKyB0aGVtZV9taW5pbWFsKCkKcGFpcnMoZGZbLCBjKCJtZWFudGVtcCIsICJodW1pZGl0eSIsICJ3aW5kX3NwZWVkIiwgIm1lYW5wcmVzc3VyZSIpXSwKICAgICAgbWFpbiA9ICJQYWlyczogbWVhbnRlbXAgdnMgcHJlZGljdG9ycyIpCmNvcl9tYXQgPC0gY29yKGRmWywgYygibWVhbnRlbXAiLCAiaHVtaWRpdHkiLCAid2luZF9zcGVlZCIsICJtZWFucHJlc3N1cmUiKV0sIHVzZSA9ICJjb21wbGV0ZS5vYnMiKQpjb3JfbWF0CmBgYApHcmFmeSB1a2F6dWrDuiBzZXrDs25ueSBjaGFyYWt0ZXIgdGVwbG90eSBhIG1lbsWhaXUgdmFyaWFiaWxpdHUgaW7DvWNoIHByZW1lbm7DvWNoLiBQw6Fyb3bDqSBncmFmeSBuYXpuYcSNdWrDuiBtaWVybnUgbmVnYXTDrXZudSBrb3JlbMOhY2l1IG1lZHppIHRsYWtvbSBhIHRlcGxvdG91LiBLb3JlbGHEjW7DoSBtYXRpY2EgcG90dnJkenVqZSwgxb5lIG1lZHppIG5pZWt0b3LDvW1pIHByZW1lbm7DvW1pIGV4aXN0dWrDuiBzbGFiw6kgYcW+IHN0cmVkbsOpIHZ6xaVhaHkuIE5lYm9sYSB6aXN0ZW7DoSBzaWxuw6EgbXVsdGlrb2xpbmVhcml0YS4gRMOhdGEgc8O6IHRlZGEgdmhvZG7DqSBuYSBsaW5lw6FybmUgbW9kZWxvdmFuaWUuCgoKCiMjIFRlc3R5IHN0YWNpb27DoXJub3N0aQpgYGB7ciB1bG9oYTQtc3RhY2lvbmFybm9zdH0KYWRmX21lYW50ZW1wIDwtIGFkZi50ZXN0KG5hLm9taXQoZGYkbWVhbnRlbXApKQphZGZfaHVtaWRpdHkgIDwtIGFkZi50ZXN0KG5hLm9taXQoZGYkaHVtaWRpdHkpKQphZGZfd2luZCAgICAgIDwtIGFkZi50ZXN0KG5hLm9taXQoZGYkd2luZF9zcGVlZCkpCmFkZl9wcmVzc3VyZSAgPC0gYWRmLnRlc3QobmEub21pdChkZiRtZWFucHJlc3N1cmUpKQphZGZfbWVhbnRlbXA7IGFkZl9odW1pZGl0eTsgYWRmX3dpbmQ7IGFkZl9wcmVzc3VyZQoKa3Bzc19tZWFudGVtcCA8LSB1ci5rcHNzKG5hLm9taXQoZGYkbWVhbnRlbXApLCB0eXBlID0gInRhdSIsIGxhZ3MgPSAic2hvcnQiKQpzdW1tYXJ5KGtwc3NfbWVhbnRlbXApCmBgYApBREYgdGVzdCDEjWFzdG8gdWthenVqZSwgxb5lIHRlcGxvdGEgbmllIGplIHN0YWNpb27DoXJuYSwgxI1vIHpuYW1lbsOhIHByw610b21ub3PFpSB0cmVuZHUuIEtQU1MgdGVzdCB0ZW50byB6w6F2ZXIgenZ5xI1ham5lIHBvdHZyZHp1amUuIE9zdGF0bsOpIHByZW1lbm7DqSBtw7TFvnUgYnnFpSBibGnFvsWhaWUgayBzdGFjaW9uw6Fybm9zdGkuIFByZSBtb2RlbG92YW5pZSBsaW5lw6FybnljaCB2esWlYWhvdiB0byB2xaFhayBuZXByZWRzdGF2dWplIHrDoXNhZG7DvSBwcm9ibMOpbSwgYWsgbW9kZWx1amVtZSBwcmllcmV6b3bDqSBzcHLDoXZhbmllLiBQcmkgxI1hc292w71jaCBtb2RlbG9jaCBieSBzbWUgbXVzZWxpIGRpZmVyZW5jb3ZhxaUuCgoKCiMjIE9kaGFkIGxpbmXDoXJuZWhvIG1vZGVsdQpgYGB7ciB1bG9oYTUtbW9kZWx9Cm1vZGVsIDwtIGxtKG1lYW50ZW1wIH4gaHVtaWRpdHkgKyB3aW5kX3NwZWVkICsgbWVhbnByZXNzdXJlLCBkYXRhID0gZGYpCnN1bW1hcnkobW9kZWwpCmBgYApWw71zbGVka3kgdWthenVqw7osIMW+ZSB2bGhrb3PFpSBtw6EgenZ5xI1ham5lIHBveml0w612bnkgYSB0bGFrIG5lZ2F0w612bnkgdnBseXYgbmEgdGVwbG90dS4gUsO9Y2hsb3PFpSB2ZXRyYSBtw7TFvmUgbWHFpSBzbGFixaHDrSBlZmVrdC4gSG9kbm90YSBSwrIgbsOhbSB1a2F6dWplLCBha8O6IMSNYXPFpSB2YXJpYWJpbGl0eSB0ZXBsb3R5IG1vZGVsIHZ5c3ZldMS+dWplLiBBayBzw7ogcC1ob2Rub3R5IG1lbsWhaWUgbmXFviAwLjA1LCBtw7TFvmVtZSB0dnJkacWlLCDFvmUgcHJlbWVubsOpIG1hasO6IMWhdGF0aXN0aWNreSB2w716bmFtbsO9IHZwbHl2LiBNb2RlbCB0ZWRhIGTDoXZhIHJvenVtbsO9IG9waXMgc3Byw6F2YW5pYSB0ZXBsb3R5LgoKCgojIyBEaWFnbm9zdGlrYSBtb2RlbHUKYGBge3IgdWxvaGE2LWRpYWdub3N0aWthfQpwYXIobWZyb3cgPSBjKDIsMikpOyBwbG90KG1vZGVsKTsgcGFyKG1mcm93ID0gYygxLDEpKQpyZXNpZF9tb2RlbCA8LSByZXNpZHVhbHMobW9kZWwpCmphcnF1ZS5iZXJhLnRlc3QocmVzaWRfbW9kZWwpCmR3dGVzdChtb2RlbCkKYnB0ZXN0KG1vZGVsKQp2aWYobW9kZWwpCm91dGxpZXJUZXN0KG1vZGVsKQpgYGAKRGlhZ25vc3RpY2vDqSB0ZXN0eSB1a2F6dWrDuiwgxI1pIHPDuiBwb3J1xaFlbsOpIHByZWRwb2tsYWR5IGxpbmXDoXJuZWhvIG1vZGVsdS4gQWsgSmFycXVlLUJlcmEgdGVzdCBwb3R2cmTDrSBub3JtYWxpdHUsIHJlesOtZHXDoSBzw7ogcHJpYmxpxb5uZSBub3Jtw6FsbmUuIER1cmJpbi1XYXRzb24gdGVzdCBvZGhhbMOtLCDEjWkgbmllIGplIHByw610b21uw6EgYXV0b2tvcmVsw6FjaWEuIEJyZXVzY2gtUGFnYW4gdGVzdCB6aXPFpXVqZSBoZXRlcm9za2VkYXN0aWNpdHUg4oCTIGFrIHAgPiAwLjA1LCBwcmVkcG9rbGFkIGplIHNwbG5lbsO9LiBWSUYgaG9kbm90eSBwb2QgNSB6bmFtZW5hasO6LCDFvmUgbmllIGplIHByb2Jsw6ltIHMga29saW5lYXJpdG91LiBBayBzw7ogb2TEvmFobMOpIGJvZHksIG9kcG9yw7rEjWEgc2EgaWNoIHByZXZlcmnFpS4KCgoKIyMgUm9idXN0bsOpIMWhdGFuZGFyZG7DqSBjaHlieQpgYGB7ciB1bG9oYTctcm9idXN0fQpjb2VmdGVzdChtb2RlbCwgdmNvdi4gPSBOZXdleVdlc3QobW9kZWwsIGxhZyA9IDUsIHByZXdoaXRlID0gRkFMU0UpKQpjb2VmdGVzdChtb2RlbCwgdmNvdiA9IHZjb3ZIQyhtb2RlbCwgdHlwZSA9ICJIQzMiKSkKYGBgClJvYnVzdG7DqSDFoXRhbmRhcmRuw6kgY2h5YnkgdXByYXZ1asO6IHbDvXNsZWRreSBuYSBwcsOtcGFkbsO6IGhldGVyb3NrZWRhc3RpY2l0dSBhbGVibyBhdXRva29yZWzDoWNpdS4gQWsgc2EgdsO9em5hbW5vc8WlIGtvZWZpY2llbnRvdiBuZXptZW7DrSwgbW9kZWwgamUgc3RhYmlsbsO9LiBWIHByw61wYWRlIHJvemRpZWxvdiBqZSB2aG9kbsOpIGludGVycHJldG92YcWlIHbDvXNsZWRreSBwb2TEvmEgcm9idXN0bsO9Y2ggb2RoYWRvdi4gTmV3ZXktV2VzdCBiZXJpZSBkbyDDunZhaHkgxI1hc292w7ogesOhdmlzbG9zxaUsIHphdGlhxL4gxI1vIEhDMyBsZW4gaGV0ZXJvc2tlZGFzdGljaXR1LiBWw71zbGVka3kgdGFrIHBvc2t5dHVqw7ogc3BvxL5haGxpdmVqxaFpZSBpbmZlcmVuY2llLgoKCgojIyBQcmVkaWtjaWUgYSB2aXp1YWxpesOhY2lhCmBgYHtyIHVsb2hhOC1wcmVkaWtjaWV9CmRmJHByZWRpY3RlZCA8LSBwcmVkaWN0KG1vZGVsLCBuZXdkYXRhID0gZGYpCmRmJHJlc2lkdWFscyA8LSByZXNpZHVhbHMobW9kZWwpCmdncGxvdChkZiwgYWVzKHggPSBkYXRlKSkgKwogIGdlb21fbGluZShhZXMoeSA9IG1lYW50ZW1wKSwgc2l6ZSA9IDAuOCwgY29sb3IgPSAiZ3JlZW4iKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gcHJlZGljdGVkKSwgbGluZXR5cGUgPSAiZGFzaGVkIiwgc2l6ZSA9IDAuOCwgY29sb3IgPSAib3JhbmdlIikgKwogIGxhYnModGl0bGUgPSAiU2t1dG/EjW7DoSB2cyBwcmVkaWtvdmFuw6EgbWVhbiB0ZW1wZXJhdHVyZSIsCiAgICAgICB5ID0gIk1lYW50ZW1wICjCsEMpIiwgeCA9ICJEw6F0dW0iKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYApOYSBncmFmZSB2aWTDrW1lLCDFvmUgcHJlZGlrY2llIHNsZWR1asO6IHNrdXRvxI1uw7ogdGVwbG90dSBwb21lcm5lIGRvYnJlLiBPZGNow71sa3kgc8O6IG1hbMOpIGEgcmV6w61kdcOhIG5ldnlrYXp1asO6IHN5c3RlbWF0aWNrw70gdHJlbmQuIE1vZGVsIHRlZGEgZG9rw6HFvmUgemFjaHl0acWlIHrDoWtsYWRuw70gdnrFpWFoIG1lZHppIHRlcGxvdG91IGEgb3N0YXRuw71taSBwcmVtZW5uw71taS4gUHJlc25vc8WlIGJ5IHNhIGRhbGEgenbDvcWhacWlIHByaWRhbsOtbSBzZXrDs25ueWNoIGVmZWt0b3YgYWxlYm8gbmVsaW5lw6FybnljaCBwcnZrb3YuIE5hcHJpZWsgdG9tdSBtw6EgbW9kZWwgZG9icsO6IHZ5c3ZldMS+b3ZhY2l1IHNjaG9wbm9zxaUuCgoKCiMjICBEaWFnbm9zdGlrYSByZXrDrWR1w60KYGBge3IgdWxvaGE5LXJlemlkdWF9CmdncGxvdChkYXRhLmZyYW1lKHJlc2lkID0gZGYkcmVzaWR1YWxzKSwgYWVzKHggPSByZXNpZCkpICsKICBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSwgYmlucyA9IDMwKSArCiAgc3RhdF9mdW5jdGlvbihmdW4gPSBkbm9ybSwgYXJncyA9IGxpc3QobWVhbiA9IG1lYW4oZGYkcmVzaWR1YWxzLCBuYS5ybT1UUlVFKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZCAgID0gc2QoZGYkcmVzaWR1YWxzLCBuYS5ybT1UUlVFKSkpICsKICBsYWJzKHRpdGxlID0gIkhpc3RvZ3JhbSByZXrDrWR1w60gKHMgbm9ybcOhbG5vdSBrcml2a291KSIpCnFxbm9ybShkZiRyZXNpZHVhbHMpOyBxcWxpbmUoZGYkcmVzaWR1YWxzKQpgYGAKSGlzdG9ncmFtIHVrYXp1amUsIMW+ZSByZXrDrWR1w6Egc8O6IHByaWJsacW+bmUgc3ltZXRyaWNrw6kgYSBjZW50csOhbG5lIG9rb2xvIG51bHkuIFFRLXBsb3QgcG90dnJkenVqZSwgxb5lIG9kY2jDvWxreSBvZCBub3Jtw6Fsbm9zdGkgc8O6IGxlbiBtaWVybmUuIE1vZGVsIHRlZGEgc3DEusWIYSBwcmVkcG9rbGFkIG5vcm1hbGl0eSByZXrDrWR1w60uIEFrIGJ5IGJvbGkgb2RjaMO9bGt5IHbDpMSNxaFpZSwgbW9obGkgYnkgc21lIHNrw7pzacWlIGxvZ2FyaXRtaWNrw7ogdHJhbnNmb3Jtw6FjaXUgYWxlYm8gcm9idXN0bmVqxaHDrSBtb2RlbC4gViB0b210byBwcsOtcGFkZSBzw7ogdsO9c2xlZGt5IHByaWphdGXEvm7DqS4KCiMjIEFSSU1BIG1vZGVsIHByZSB0ZXBsb3R1CmBgYHtyIHVsb2hhMTAtYXJpbWF9CnRzX21lYW50ZW1wIDwtIHRzKGRmJG1lYW50ZW1wLCBzdGFydCA9IGMoeWVhcihtaW4oZGYkZGF0ZSkpLCB5ZGF5KG1pbihkZiRkYXRlKSkpLCBmcmVxdWVuY3kgPSAzNjUpCmdndHNkaXNwbGF5KG5hLm9taXQodHNfbWVhbnRlbXApLCBtYWluID0gIkFDRi9QQUNGIHByZSBtZWFudGVtcCIpCmFyaW1hX2ZpdCA8LSBhdXRvLmFyaW1hKG5hLm9taXQodHNfbWVhbnRlbXApLCBzZWFzb25hbCA9IEZBTFNFKQpzdW1tYXJ5KGFyaW1hX2ZpdCkKY2hlY2tyZXNpZHVhbHMoYXJpbWFfZml0KQpgYGAKQVJJTUEgbW9kZWwgc2vDum1hIMSNYXNvdsO6IHrDoXZpc2xvc8WlIHRlcGxvdHkuIEF1dG9tYXRpY2vDvSB2w71iZXIgcGFyYW1ldHJvdiB6dnnEjWFqbmUgenZvbMOtIG7DrXprZSBob2Rub3R5IHAgYSBxLCDEjW8gbmF6bmHEjXVqZSBqZWRub2R1Y2jDuiDFoXRydWt0w7pydSBwcm9jZXN1LiBSZXrDrWR1w6EgeiBBUklNQSBtb2RlbHUgYnkgbWFsaSBiecWlIG7DoWhvZG7DqSBiZXogdHJlbmR1LiBBayBzw7ogYXV0b2tvcmVsw6FjaWUgbWFsw6ksIG1vZGVsIGRvYnJlIHZ5c3RpaHVqZSBwcmllYmVoIGTDoXQuIFRlbnRvIHByw61zdHVwIGplIHZob2Ruw70gYWogcHJlIHByZWRwb3ZlZGUgYnVkw7pjaWNoIGhvZG7DtHQuCg==