Úvod do problému

Cieľom tohto cvičenia je analyzovať klimatické údaje z oblasti Dillí. Dataset obsahuje denné hodnoty teploty, vlhkosti, rýchlosti vetra a tlaku vzduchu. Naším cieľom je pochopiť vzťahy medzi premennými, overiť štatistické predpoklady a vytvoriť vhodný model na vysvetlenie vývoja teploty. Budeme pracovať s lineárnym modelom a jeho diagnostikou, ako aj s časovými radmi. Výsledky budeme priebežne interpretovať.

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 ...
head(df)
summary(df)
     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
start(ts_xts)
[1] "2017-01-01"
end(ts_xts)
[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
dwtest(model)

    Durbin-Watson test

data:  model
DW = 0.46142, p-value < 2.2e-16
alternative hypothesis: true autocorrelation is greater than 0
bptest(model)

    studentized Breusch-Pagan test

data:  model
BP = 6.4475, df = 3, p-value = 0.09175
vif(model)
    humidity   wind_speed meanpressure 
    1.134886     1.143445     1.020645 
outlierTest(model)

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==