knitr::opts_chunk$set(
  echo = TRUE,
  message = FALSE,
  warning = FALSE
)

Import údajov z otv. databáz

  1. Mendeley Data Tuto sa dostaneme z Mendeley Data, kde si údaje viete voľne stiahnuť. Údaje sa vzťahujú k už publikovaným článkom vo vydavateľstve Elsevier. Výber sa dá urobiť jednoducho zadaním kľúčových slov.

  2. Kaggle Data Tuto sa dostaneme z Kaggle Datasets, kde si údaje viete voľne stiahnuť. Údaje sa vzťahujú k projektom podporovaným Kaggle. Výber sa dá urobiť jednoducho zadaním kľúčových slov.

  3. Databázy knižníc R – .library(datasets). alebo .library(wooldridge).ale aj iné – stačí si dať príkaz data().

library(datasets)

# dostupné datasety v 'datasets' balíku
ds <- as.data.frame(utils::data(package = "datasets")$results)[, c("Item","Title")]
knitr::kable(head(ds, 20), col.names = c("Dataset", "Title"))
Dataset Title
AirPassengers Monthly Airline Passenger Numbers 1949-1960
BJsales Sales Data with Leading Indicator
BJsales.lead (BJsales) Sales Data with Leading Indicator
BOD Biochemical Oxygen Demand
CO2 Carbon Dioxide Uptake in Grass Plants
ChickWeight Weight versus age of chicks on different diets
DNase Elisa assay of DNase
EuStockMarkets Daily Closing Prices of Major European Stock Indices, 1991-1998
Formaldehyde Determination of Formaldehyde
HairEyeColor Hair and Eye Color of Statistics Students
Harman23.cor Harman Example 2.3
Harman74.cor Harman Example 7.4
Indometh Pharmacokinetics of Indomethacin
InsectSprays Effectiveness of Insect Sprays
JohnsonJohnson Quarterly Earnings per Johnson & Johnson Share
LakeHuron Level of Lake Huron 1875-1972
LifeCycleSavings Intercountry Life-Cycle Savings Data
Loblolly Growth of Loblolly Pine Trees
Nile Flow of the River Nile
Orange Growth of Orange Trees

# príklad vstavaného datasetu
head(CO2)

Môžeme použiť aj databázu určenú pre ekonometriu – balík wooldridge:

# install.packages("wooldridge")
library(wooldridge)
ds <- as.data.frame(utils::data(package = "wooldridge")$results)[, c("Item","Title")]
knitr::kable(head(ds, 20), col.names = c("Dataset", "Title")) %>%
  kable_styling(
    bootstrap_options = c("striped", "hover", "condensed", "responsive"),
    full_width = FALSE,
    position = "center"
  )
Error in knitr::kable(head(ds, 20), col.names = c("Dataset", "Title")) %>%  : 
  could not find function "%>%"

Import údajov z .csv (vlastná databáza dopravných nehôd)

V tejto práci použijem databázu o dopravných nehách, uloženú v súbore premavka.csv.csv. Dataset obsahuje informácie o dátume a čase nehody, type ovládania dopravy (semafór, značky), poveternostných podmienkach, osvetlení (deň / noc), type zrážky, stave vozovky, počte zúčastnených vozidiel a počte zranených osôb.

Súbor mám uložený v tom istom priečinku ako tento RMarkdown. Importujem ho pomocou funkcie read.csv(), pričom oddeľovač položiek je čiarka a desatinným oddeľovačom je bodka.

# Import vlastnej CSV databázy dopravných nehôd
udaje <- read.csv("premavka.csv.csv",
                  header = TRUE,
                  sep = ",",
                  dec = ".")

# náhľad na prvé riadky
head(udaje)

# názvy premenných
colnames(udaje)
 [1] "crash_date"                    "traffic_control_device"       
 [3] "weather_condition"             "lighting_condition"           
 [5] "first_crash_type"              "trafficway_type"              
 [7] "alignment"                     "roadway_surface_cond"         
 [9] "road_defect"                   "crash_type"                   
[11] "intersection_related_i"        "damage"                       
[13] "prim_contributory_cause"       "num_units"                    
[15] "most_severe_injury"            "injuries_total"               
[17] "injuries_fatal"                "injuries_incapacitating"      
[19] "injuries_non_incapacitating"   "injuries_reported_not_evident"
[21] "injuries_no_indication"        "crash_hour"                   
[23] "crash_day_of_week"             "crash_month"                  

Pre ďalšiu analýzu budem pracovať hlavne s premennými:

  • weather_condition – poveternostné podmienky (CLEAR, RAIN, SNOW, FOG/SMOKE/HAZE, atď.)
  • lighting_condition – osvetlenie (DAYLIGHT, DARKNESS, LIGHTED ROAD, atď.)
  • num_units – počet vozidiel / jednotiek zúčastnených na nehode
  • injuries_total – celkový počet zranených pri nehode
  • crash_hour – hodina nehody
  • crash_day_of_week – deň v týždni (1–7)
  • crash_month – mesiac (1–12)

Grafy

ggplot2 – knižnica pre grafy

Najprv si vyberiem podmnožinu nehôd, napríklad iba nehody, ktoré sa stali cez deň (DAYLIGHT), aby som vedela lepšie vizualizovať vzťahy medzi premennými.

library(dplyr)
library(ggplot2)

udaje.daylight <- udaje %>%
  filter(lighting_condition == "DAYLIGHT") %>%
  select(num_units, injuries_total, crash_hour, crash_month, weather_condition)

head(udaje.daylight)

Knižnica ggplot2 je v súčasnosti najčastejšie používaná grafická knižnica v R. Predpripravené príklady grafov sú uvedené napríklad v R Graph Gallery.

Basic Scatter plot: Počet zranení podľa hodiny dňa (nehody cez deň)

ggplot(udaje.daylight, aes(x = crash_hour, y = injuries_total)) +
  geom_point(alpha = 0.6) +
  theme_minimal() +
  labs(
    title = "Počet zranení podľa hodiny dňa (nehody cez deň)",
    x = "Hodina nehody",
    y = "Celkový počet zranených"
  )

Tento graf ukazuje, ako sa počet zranených pri nehodách mení v priebehu dňa. Väčšina nehôd má nulový alebo nízky počet zranených bez ohľadu na hodinu, ale skupiny bodov naznačujú, že k závažnejším nehodám (viac zranených) môže dochádzať v popoludňajších a večerných hodinách, keď je na cestách viac áut a premávka je hustejšia.

Boxplot: Počet zranení podľa počasia

# Bar plot with grouping
library(ggplot2)

library(ggplot2)


ggplot(udaje, aes(x = factor(weather_condition), y = injuries_total)) +
  geom_boxplot(fill = "lightblue", color = "darkblue") +
  labs(
    title = "Počet zranení podľa poveternostných podmienok",
    x = "Počasie",
    y = "Celkový počet zranených"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

Boxplot porovnáva rozdelenie počtu zranených pri rôznych typoch počasia. Vidno, že pri jasnom počasí (CLEAR) je medián počtu zranených nízky, ale vzhľadom na veľký počet nehôd sa občas vyskytnú aj prípady s vyšším počtom zranených. Pri daždi (RAIN), snehu (SNOW) či námraze sa rozdelenie mierne posúva, čo môže naznačovať vyššie riziko zranení pri zhoršených podmienkach, aj keď väčšina nehôd má stále 0 alebo 1 zranenú osobu.

Základné štatistiky

knitr – jednoduchá tabuľka

V tejto časti spočítam základné štatistiky pre premennú injuries_total podľa poveternostných podmienok (weather_condition). Zaujíma ma, ako sa líši počet zranených pri rôznych typoch počasia.

library(knitr)

injury.stats <- udaje %>%
  group_by(weather_condition) %>%
  summarise(
    n      = n(),
    mean   = mean(injuries_total, na.rm = TRUE),
    sd     = sd(injuries_total, na.rm = TRUE),
    min    = min(injuries_total, na.rm = TRUE),
    q25    = quantile(injuries_total, 0.25, na.rm = TRUE),
    median = median(injuries_total, na.rm = TRUE),
    q75    = quantile(injuries_total, 0.75, na.rm = TRUE),
    max    = max(injuries_total, na.rm = TRUE),
    .groups = "drop"
  )

kable(injury.stats, digits = 2,
      caption = "Základné štatistiky počtu zranených podľa poveternostných podmienok")
Základné štatistiky počtu zranených podľa poveternostných podmienok
weather_condition n mean sd min q25 median q75 max
BLOWING SAND, SOIL, DIRT 1 0.00 NA 0 0 0 0 0
BLOWING SNOW 127 0.31 0.75 0 0 0 0 4
CLEAR 164700 0.39 0.81 0 0 0 1 21
CLOUDY/OVERCAST 7533 0.37 0.75 0 0 0 1 11
FOG/SMOKE/HAZE 360 0.44 0.83 0 0 0 1 5
FREEZING RAIN/DRIZZLE 510 0.46 0.86 0 0 0 1 6
OTHER 627 0.48 0.82 0 0 0 1 5
RAIN 21703 0.41 0.81 0 0 0 1 15
SEVERE CROSS WIND GATE 32 0.25 0.76 0 0 0 0 4
SLEET/HAIL 308 0.46 0.85 0 0 0 1 6
SNOW 6871 0.31 0.70 0 0 0 0 7
UNKNOWN 6534 0.17 0.49 0 0 0 0 6

Tabuľka ukazuje, ako sa priemerný počet zranených a rozptyl zranení líšia medzi jednotlivými typmi počasia. Napríklad pri počasí RAIN a FREEZING RAIN/DRIZZLE sú priemerné hodnoty injuries_total mierne vyššie ako pri CLEAR, čo je v súlade s očakávaním, že zhoršené podmienky zvyšujú riziko zranení. Zároveň vidno, že pri kategórii UNKNOWN sú priemery veľmi nízke, čo môže súvisieť aj s nekvalitným alebo neúplným záznamom údajov.

library(dplyr)
library(knitr)
library(kableExtra)

# Základné štatistiky počtu zranených podľa poveternostných podmienok
injury.stats <- udaje %>%
  group_by(weather_condition) %>%
  summarise(
    n      = n(),
    mean   = mean(injuries_total, na.rm = TRUE),
    sd     = sd(injuries_total, na.rm = TRUE),
    min    = min(injuries_total, na.rm = TRUE),
    q25    = quantile(injuries_total, 0.25, na.rm = TRUE),
    median = median(injuries_total, na.rm = TRUE),
    q75    = quantile(injuries_total, 0.75, na.rm = TRUE),
    max    = max(injuries_total, na.rm = TRUE),
    .groups = "drop"
  )

# krajšia tabuľka s kableExtra
injury.stats %>%
  kable(
    digits  = 2,
    caption = "Základné štatistiky počtu zranených podľa poveternostných podmienok"
  ) %>%
  kable_styling(
    full_width        = FALSE,
    bootstrap_options = c("striped", "hover", "condensed")
  ) %>%
  column_spec(1, bold = TRUE) %>%                             # prvý stĺpec (počasie) tučne
  row_spec(0, bold = TRUE, background = "#f2f2f2") %>%        # hlavička tučná so sivým pozadím
  add_header_above(c(" " = 1, "Štatistiky počtu zranených" = 8))
Základné štatistiky počtu zranených podľa poveternostných podmienok
Štatistiky počtu zranených
weather_condition n mean sd min q25 median q75 max
BLOWING SAND, SOIL, DIRT 1 0.00 NA 0 0 0 0 0
BLOWING SNOW 127 0.31 0.75 0 0 0 0 4
CLEAR 164700 0.39 0.81 0 0 0 1 21
CLOUDY/OVERCAST 7533 0.37 0.75 0 0 0 1 11
FOG/SMOKE/HAZE 360 0.44 0.83 0 0 0 1 5
FREEZING RAIN/DRIZZLE 510 0.46 0.86 0 0 0 1 6
OTHER 627 0.48 0.82 0 0 0 1 5
RAIN 21703 0.41 0.81 0 0 0 1 15
SEVERE CROSS WIND GATE 32 0.25 0.76 0 0 0 0 4
SLEET/HAIL 308 0.46 0.85 0 0 0 1 6
SNOW 6871 0.31 0.70 0 0 0 0 7
UNKNOWN 6534 0.17 0.49 0 0 0 0 6
NA
## Korelačná matica a heatmapa

install.packages("reshape2")
Error in install.packages : Updating loaded packages
library(dplyr)
library(ggplot2)
library(reshape2)

# výber numerických premenných, ktoré dávajú zmysel
num_data <- udaje %>%
  select(
    num_units,
    injuries_total,
    injuries_fatal,
    injuries_incapacitating,
    injuries_non_incapacitating,
    injuries_reported_not_evident,
    injuries_no_indication,
    crash_hour,
    crash_day_of_week,
    crash_month
  )

# korelačná matica (Pearsonova korelácia)
cor_mat <- cor(num_data, use = "pairwise.complete.obs")

# pre ggplot potrebujeme "dlhý" tvar
cor_df <- melt(cor_mat, varnames = c("Var1", "Var2"), value.name = "correlation")

ggplot(cor_df, aes(x = Var1, y = Var2, fill = correlation)) +
  geom_tile() +
  scale_fill_gradient2(
    limits = c(-1, 1),
    na.value = "white",
    name = "Korelácia"
  ) +
  theme_minimal() +
  theme(
    axis.text.x = element_text(angle = 45, hjust = 1),
    axis.title.x = element_blank(),
    axis.title.y = element_blank()
  ) +
  labs(
    title = "Heatmapa korelačnej matice numerických premenných"
  )

Heatmapa ukazuje, ako spolu súvisia jednotlivé číselné premenné v databáze nehôd. Vidno, že počet zranených rastie najmä vtedy, keď je do nehody zapojených viac vozidiel a keď sú zranenia vážnejšie. Časové premenné ako hodina, deň alebo mesiac majú len slabý vzťah k počtu zranených, takže čas nehody nezohráva až takú dôležitú úlohu ako samotný rozsah a závažnosť nehody.

Testovanie hypotéz

t-test: Porovnanie počtu zranených – Deň vs. Noc

Porovnám priemerný počet zranených pri nehodách, ktoré sa stali:

  • cez deň (DAYLIGHT)
  • v tme na osvetlenej ceste (DARKNESS, LIGHTED ROAD)

Použijem dvojvýberový t-test.

t.test.result <- t.test(
  udaje$injuries_total[udaje$lighting_condition == "DAYLIGHT"],
  udaje$injuries_total[udaje$lighting_condition == "DARKNESS, LIGHTED ROAD"]
)

print(t.test.result)

    Welch Two Sample t-test

data:  udaje$injuries_total[udaje$lighting_condition == "DAYLIGHT"] and udaje$injuries_total[udaje$lighting_condition == "DARKNESS, LIGHTED ROAD"]
t = -22.186, df = 85327, p-value < 2.2e-16
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 -0.10675978 -0.08942783
sample estimates:
mean of x mean of y 
0.3593793 0.4574731 

Výsledok t-testu ukazuje, že priemerný počet zranených pri nehodách cez deň (DAYLIGHT) a v tme na osvetlenej ceste (DARKNESS, LIGHTED ROAD) sa štatisticky významne líši (p-hodnota je prakticky nulová). Priemerný počet zranených je vyšší pri nehodách v tme na osvetlenej ceste, čo naznačuje, že aj pri umelom osvetlení sú nočné podmienky rizikovejšie ako denné.

ANOVA: Vplyv poveternostných podmienok na počet zranených

Teraz ma zaujíma, či sa priemerný počet zranených líši medzi rôznymi typmi počasia (weather_condition). Na to použijem jednofaktorovú ANOVA.

anova.result <- aov(injuries_total ~ weather_condition, data = udaje)
summary(anova.result)
                      Df Sum Sq Mean Sq F value Pr(>F)    
weather_condition     11    388   35.24   55.25 <2e-16 ***
Residuals         209294 133474    0.64                   
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

ANOVA testuje, či je priemerný počet zranených rovnaký pre všetky typy poveternostných podmienok. Výsledky ukazujú veľmi nízku p-hodnotu (p < 0.001), čo znamená, že počasie má štatisticky významný vplyv na priemerný počet zranených. Nehovorí nám to však, ktoré konkrétne kategórie sa líšia medzi sebou – iba to, že aspoň jedna skupina (napríklad RAIN alebo SNOW) sa od ostatných významne odlišuje.

Lineárna regresia: faktory ovplyvňujúce počet zranených

V tejto časti vytvorím lineárny regresný model, ktorý sa pokúsi vysvetliť počet zranených (injuries_total) pomocou:

  • num_units – počet zúčastnených vozidiel,
  • crash_hour – hodina nehody,
  • crash_day_of_week – deň v týždni,
  • crash_month – mesiac nehody.
model <- lm(injuries_total ~ num_units + crash_hour + crash_day_of_week + crash_month,
            data = udaje)

summary(model)

Call:
lm(formula = injuries_total ~ num_units + crash_hour + crash_day_of_week + 
    crash_month, data = udaje)

Residuals:
    Min      1Q  Median      3Q     Max 
-2.9330 -0.3719 -0.3518  0.3171 20.9325 

Coefficients:
                    Estimate Std. Error t value Pr(>|t|)    
(Intercept)       -0.2478411  0.0110628 -22.403  < 2e-16 ***
num_units          0.3233581  0.0043563  74.227  < 2e-16 ***
crash_hour        -0.0024418  0.0003085  -7.916 2.46e-15 ***
crash_day_of_week -0.0059035  0.0008787  -6.718 1.84e-11 ***
crash_month        0.0030261  0.0005033   6.013 1.82e-09 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.7892 on 209301 degrees of freedom
Multiple R-squared:  0.02626,   Adjusted R-squared:  0.02624 
F-statistic:  1411 on 4 and 209301 DF,  p-value: < 2.2e-16

Regresný model ukazuje, ako spolu súvisí počet zranených s počtom zúčastnených jednotiek a časom nehody. Premenná num_units má kladný a štatisticky významny koeficient, čo znamená, že s rastúcim počtom vozidiel v nehode rastie aj očakávaný počet zranených. Časové premenné (hodina, deň v týždni, mesiac) síce vychádzajú štatisticky významne, ale ich koeficienty sú malé, takže zmena času má v priemere oveľa menší vplyv ako samotný rozsah nehody (počet vozidiel).

Pekná tabuľka koeficientov (broom + kableExtra)

# install.packages(c("broom", "kableExtra", "dplyr", "stringr"))
library(broom)
library(stringr)

coef.tbl <- tidy(model, conf.int = TRUE) %>%
  mutate(
    term = dplyr::recode(term,
      "(Intercept)"        = "Intercept",
      "num_units"          = "Number of units",
      "crash_hour"         = "Crash hour",
      "crash_day_of_week"  = "Day of week",
      "crash_month"        = "Month"
    ),
    stars = dplyr::case_when(
      p.value < 0.001 ~ "***",
      p.value < 0.01  ~ "**",
      p.value < 0.05  ~ "*",
      p.value < 0.1   ~ "·",
      TRUE            ~ ""
    )
  ) %>%
  transmute(
    Term = term,
    Estimate = estimate,
    `Std. Error` = std.error,
    `t value` = statistic,
    `p value` = p.value,
    `95% CI` = str_c("[", round(conf.low, 3), ", ", round(conf.high, 3), "]"),
    Sig = stars
  )

coef.tbl %>%
  kable(
    digits = 3,
    caption = "OLS Regression Coefficients (injuries_total ~ num_units + crash_hour + crash_day_of_week + crash_month)"
  ) %>%
  kable_styling(full_width = FALSE,
                bootstrap_options = c("striped", "hover", "condensed")) %>%
  column_spec(1, bold = TRUE) %>%
  row_spec(0, bold = TRUE, background = "#f2f2f2") %>%
  footnote(
    general = "Signif. codes: *** p<0.001, ** p<0.01, * p<0.05, · p<0.1.",
    threeparttable = TRUE
  )
OLS Regression Coefficients (injuries_total ~ num_units + crash_hour + crash_day_of_week + crash_month)
Term Estimate Std. Error t value p value 95% CI Sig
Intercept -0.248 0.011 -22.403 0 [-0.27, -0.226] ***
Number of units 0.323 0.004 74.227 0 [0.315, 0.332] ***
Crash hour -0.002 0.000 -7.916 0 [-0.003, -0.002] ***
Day of week -0.006 0.001 -6.718 0 [-0.008, -0.004] ***
Month 0.003 0.001 6.013 0 [0.002, 0.004] ***
Note:
Signif. codes: *** p<0.001, ** p<0.01, * p<0.05, · p<0.1.

Štatistiky prispôsobenia modelu


library(broom)
library(dplyr)
library(knitr)
library(kableExtra)

fit.tbl <- glance(model) %>%
  transmute(
    `R-squared`      = r.squared,
    `Adj. R-squared` = adj.r.squared,
    `F-statistic`    = statistic,
    `F p-value`      = p.value,
    `AIC`            = AIC,
    `BIC`            = BIC,
    `Num. obs.`      = nobs
  )

fit.tbl %>%
  kable(digits = 3, caption = "Model Fit Statistics") %>%
  kable_styling(full_width = FALSE,
                bootstrap_options = c("condensed"))
Model Fit Statistics
R-squared Adj. R-squared F-statistic F p-value AIC BIC Num. obs.
0.026 0.026 1410.997 0 494869.2 494930.7 209306

Štatistiky prispôsobenia modelu ukazujú, že hodnota R-squared je síce pomerne nízka (okolo 0.026), čo znamená, že model vysvetľuje iba malú časť variability v počte zranených. Napriek tomu sú všetky testované premenné štatisticky významne, takže model je vhodný ako jednoduchý ilustračný nástroj na pochopenie trendov – no na presné predikcie by bolo treba zahrnúť aj ďalšie faktory (napr. typ križovatky, rýchlosť, typ vozidla a pod.).

LS0tCnRpdGxlOiAiUHLDoWNhIHMgZGF0YWLDoXpvdSAtIGltcG9ydCDDumRham92LCBncmFmeSwgxaF0YXRpc3Rpa3kiCmF1dGhvcjogIkJhcmJvcmEgS29wcmRvdmEgPGJyPgoocyB2eXXFvml0w61tIHZlcmVqbmUgZG9zdHVwbsO9Y2gga8OzZG92IGEgQ2hhdEdQVCkiCmRhdGU6ICJOb3ZlbWJlciAyMDI1IgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgdGhlbWU6IHVuaXRlZAogICAgaGlnaGxpZ2h0OiB0YW5nbwplZGl0b3Jfb3B0aW9uczogCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKICAgIAotLS0KYGBge3J9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBlY2hvID0gVFJVRSwKICBtZXNzYWdlID0gRkFMU0UsCiAgd2FybmluZyA9IEZBTFNFCikKYGBgCgojIEltcG9ydCDDumRham92IHogb3R2LiBkYXRhYsOhegoKMS4gKk1lbmRlbGV5IERhdGEqIFR1dG8gc2EgZG9zdGFuZW1lIHogW01lbmRlbGV5IERhdGFdKGh0dHBzOi8vZGF0YS5tZW5kZWxleS5jb20vKXt0YXJnZXQ9Ii5ibGFuayIgcmVsPSJub29wZW5lciJ9LCBrZGUgc2kgw7pkYWplIHZpZXRlIHZvxL5uZSBzdGlhaG51xaUuIMOaZGFqZSBzYSB2esWlYWh1asO6IGsgdcW+IHB1Ymxpa292YW7DvW0gxI1sw6Fua29tIHZvIHZ5ZGF2YXRlxL5zdHZlIEVsc2V2aWVyLiBWw71iZXIgc2EgZMOhIHVyb2JpxaUgamVkbm9kdWNobyB6YWRhbsOtbSBrxL7DusSNb3bDvWNoIHNsb3YuICAKCjIuICpLYWdnbGUgRGF0YSogVHV0byBzYSBkb3N0YW5lbWUgeiBbS2FnZ2xlIERhdGFzZXRzXShodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzKXt0YXJnZXQ9Ii5ibGFuayIgcmVsPSJub29wZW5lciJ9LCBrZGUgc2kgw7pkYWplIHZpZXRlIHZvxL5uZSBzdGlhaG51xaUuIMOaZGFqZSBzYSB2esWlYWh1asO6IGsgcHJvamVrdG9tIHBvZHBvcm92YW7DvW0gS2FnZ2xlLiBWw71iZXIgc2EgZMOhIHVyb2JpxaUgamVkbm9kdWNobyB6YWRhbsOtbSBrxL7DusSNb3bDvWNoIHNsb3YuCgozLiBEYXRhYsOhenkga25pxb5uw61jIFIg4oCTIC5saWJyYXJ5KGRhdGFzZXRzKS4gYWxlYm8gLmxpYnJhcnkod29vbGRyaWRnZSkuYWxlIGFqIGluw6kg4oCTIHN0YcSNw60gc2kgZGHFpSBwcsOta2F6IGRhdGEoKS4KCmBgYHtyfQpsaWJyYXJ5KGRhdGFzZXRzKQoKIyBkb3N0dXBuw6kgZGF0YXNldHkgdiAnZGF0YXNldHMnIGJhbMOta3UKZHMgPC0gYXMuZGF0YS5mcmFtZSh1dGlsczo6ZGF0YShwYWNrYWdlID0gImRhdGFzZXRzIikkcmVzdWx0cylbLCBjKCJJdGVtIiwiVGl0bGUiKV0Ka25pdHI6OmthYmxlKGhlYWQoZHMsIDIwKSwgY29sLm5hbWVzID0gYygiRGF0YXNldCIsICJUaXRsZSIpKQoKIyBwcsOta2xhZCB2c3RhdmFuw6lobyBkYXRhc2V0dQpoZWFkKENPMikKYGBgCgpNw7TFvmVtZSBwb3XFvmnFpSBhaiBkYXRhYsOhenUgdXLEjWVuw7ogcHJlIGVrb25vbWV0cml1IOKAkyBiYWzDrWsgKip3b29sZHJpZGdlKio6CgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKCJ3b29sZHJpZGdlIikKbGlicmFyeSh3b29sZHJpZGdlKQpkcyA8LSBhcy5kYXRhLmZyYW1lKHV0aWxzOjpkYXRhKHBhY2thZ2UgPSAid29vbGRyaWRnZSIpJHJlc3VsdHMpWywgYygiSXRlbSIsIlRpdGxlIildCmtuaXRyOjprYWJsZShoZWFkKGRzLCAyMCksIGNvbC5uYW1lcyA9IGMoIkRhdGFzZXQiLCAiVGl0bGUiKSkgJT4lCiAga2FibGVfc3R5bGluZygKICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiLCAicmVzcG9uc2l2ZSIpLAogICAgZnVsbF93aWR0aCA9IEZBTFNFLAogICAgcG9zaXRpb24gPSAiY2VudGVyIgogICkKYGBgCgojIyBJbXBvcnQgw7pkYWpvdiB6IC5jc3YgKHZsYXN0bsOhIGRhdGFiw6F6YSBkb3ByYXZuw71jaCBuZWjDtGQpCgpWIHRlanRvIHByw6FjaSBwb3XFvmlqZW0gZGF0YWLDoXp1IG8gZG9wcmF2bsO9Y2ggbmVow6FjaCwgdWxvxb5lbsO6IHYgc8O6Ym9yZSBgcHJlbWF2a2EuY3N2LmNzdmAuIERhdGFzZXQgb2JzYWh1amUgaW5mb3Jtw6FjaWUgbyBkw6F0dW1lIGEgxI1hc2UgbmVob2R5LCB0eXBlIG92bMOhZGFuaWEgZG9wcmF2eSAoc2VtYWbDs3IsIHpuYcSNa3kpLCBwb3ZldGVybm9zdG7DvWNoIHBvZG1pZW5rYWNoLCBvc3ZldGxlbsOtIChkZcWIIC8gbm9jKSwgdHlwZSB6csOhxb5reSwgc3RhdmUgdm96b3ZreSwgcG/EjXRlIHrDusSNYXN0bmVuw71jaCB2b3ppZGllbCBhIHBvxI10ZSB6cmFuZW7DvWNoIG9zw7RiLiAgCgpTw7pib3IgbcOhbSB1bG/FvmVuw70gdiB0b20gaXN0b20gcHJpZcSNaW5rdSBha28gdGVudG8gUk1hcmtkb3duLiBJbXBvcnR1amVtIGhvIHBvbW9jb3UgZnVua2NpZSBgcmVhZC5jc3YoKWAsIHByacSNb20gb2RkZcS+b3ZhxI0gcG9sb8W+aWVrIGplIMSNaWFya2EgYSBkZXNhdGlubsO9bSBvZGRlxL5vdmHEjW9tIGplIGJvZGthLgoKYGBge3J9CiMgSW1wb3J0IHZsYXN0bmVqIENTViBkYXRhYsOhenkgZG9wcmF2bsO9Y2ggbmVow7RkCnVkYWplIDwtIHJlYWQuY3N2KCJwcmVtYXZrYS5jc3YuY3N2IiwKICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVFJVRSwKICAgICAgICAgICAgICAgICAgc2VwID0gIiwiLAogICAgICAgICAgICAgICAgICBkZWMgPSAiLiIpCgojIG7DoWjEvmFkIG5hIHBydsOpIHJpYWRreQpoZWFkKHVkYWplKQoKIyBuw6F6dnkgcHJlbWVubsO9Y2gKY29sbmFtZXModWRhamUpCmBgYAoKUHJlIMSPYWzFoWl1IGFuYWzDvXp1IGJ1ZGVtIHByYWNvdmHFpSBobGF2bmUgcyBwcmVtZW5uw71taToKCi0gYHdlYXRoZXJfY29uZGl0aW9uYCDigJMgcG92ZXRlcm5vc3Ruw6kgcG9kbWllbmt5IChDTEVBUiwgUkFJTiwgU05PVywgRk9HL1NNT0tFL0hBWkUsIGF0xI8uKQotIGBsaWdodGluZ19jb25kaXRpb25gIOKAkyBvc3ZldGxlbmllIChEQVlMSUdIVCwgREFSS05FU1MsIExJR0hURUQgUk9BRCwgYXTEjy4pCi0gYG51bV91bml0c2Ag4oCTIHBvxI1ldCB2b3ppZGllbCAvIGplZG5vdGllayB6w7rEjWFzdG5lbsO9Y2ggbmEgbmVob2RlCi0gYGluanVyaWVzX3RvdGFsYCDigJMgY2Vsa292w70gcG/EjWV0IHpyYW5lbsO9Y2ggcHJpIG5laG9kZQotIGBjcmFzaF9ob3VyYCDigJMgaG9kaW5hIG5laG9keQotIGBjcmFzaF9kYXlfb2Zfd2Vla2Ag4oCTIGRlxYggdiB0w73FvmRuaSAoMeKAkzcpCi0gYGNyYXNoX21vbnRoYCDigJMgbWVzaWFjICgx4oCTMTIpCgojIEdyYWZ5CgojIyMgZ2dwbG90MiDigJMga25pxb5uaWNhIHByZSBncmFmeQoKTmFqcHJ2IHNpIHZ5YmVyaWVtIHBvZG1ub8W+aW51IG5laMO0ZCwgbmFwcsOta2xhZCBpYmEgbmVob2R5LCBrdG9yw6kgc2Egc3RhbGkgY2V6IGRlxYggKERBWUxJR0hUKSwgYWJ5IHNvbSB2ZWRlbGEgbGVwxaFpZSB2aXp1YWxpem92YcWlIHZ6xaVhaHkgbWVkemkgcHJlbWVubsO9bWkuCgpgYGB7cn0KbGlicmFyeShkcGx5cikKbGlicmFyeShnZ3Bsb3QyKQoKdWRhamUuZGF5bGlnaHQgPC0gdWRhamUgJT4lCiAgZmlsdGVyKGxpZ2h0aW5nX2NvbmRpdGlvbiA9PSAiREFZTElHSFQiKSAlPiUKICBzZWxlY3QobnVtX3VuaXRzLCBpbmp1cmllc190b3RhbCwgY3Jhc2hfaG91ciwgY3Jhc2hfbW9udGgsIHdlYXRoZXJfY29uZGl0aW9uKQoKaGVhZCh1ZGFqZS5kYXlsaWdodCkKYGBgCgpLbmnFvm5pY2EgKipnZ3Bsb3QyKiogamUgdiBzw7rEjWFzbm9zdGkgbmFqxI1hc3RlasWhaWUgcG91xb7DrXZhbsOhIGdyYWZpY2vDoSBrbmnFvm5pY2EgdiBSLiBQcmVkcHJpcHJhdmVuw6kgcHLDrWtsYWR5IGdyYWZvdiBzw7ogdXZlZGVuw6kgbmFwcsOta2xhZCB2IFtSIEdyYXBoIEdhbGxlcnldKGh0dHBzOi8vci1ncmFwaC1nYWxsZXJ5LmNvbS8pLgoKIyMjIEJhc2ljIFNjYXR0ZXIgcGxvdDogUG/EjWV0IHpyYW5lbsOtIHBvZMS+YSBob2RpbnkgZMWIYSAobmVob2R5IGNleiBkZcWIKQoKYGBge3J9CmdncGxvdCh1ZGFqZS5kYXlsaWdodCwgYWVzKHggPSBjcmFzaF9ob3VyLCB5ID0gaW5qdXJpZXNfdG90YWwpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgbGFicygKICAgIHRpdGxlID0gIlBvxI1ldCB6cmFuZW7DrSBwb2TEvmEgaG9kaW55IGTFiGEgKG5laG9keSBjZXogZGXFiCkiLAogICAgeCA9ICJIb2RpbmEgbmVob2R5IiwKICAgIHkgPSAiQ2Vsa292w70gcG/EjWV0IHpyYW5lbsO9Y2giCiAgKQpgYGAKVGVudG8gZ3JhZiB1a2F6dWplLCBha28gc2EgcG/EjWV0IHpyYW5lbsO9Y2ggcHJpIG5laG9kw6FjaCBtZW7DrSB2IHByaWViZWh1IGTFiGEuIFbDpMSNxaFpbmEgbmVow7RkIG3DoSBudWxvdsO9IGFsZWJvIG7DrXpreSBwb8SNZXQgenJhbmVuw71jaCBiZXogb2jEvmFkdSBuYSBob2RpbnUsIGFsZSBza3VwaW55IGJvZG92IG5hem5hxI11asO6LCDFvmUgayB6w6F2YcW+bmVqxaHDrW0gbmVob2TDoW0gKHZpYWMgenJhbmVuw71jaCkgbcO0xb5lIGRvY2jDoWR6YcWlIHYgcG9wb2x1ZMWIYWrFocOtY2ggYSB2ZcSNZXJuw71jaCBob2RpbsOhY2gsIGtlxI8gamUgbmEgY2VzdMOhY2ggdmlhYyDDoXV0IGEgcHJlbcOhdmthIGplIGh1c3RlasWhaWEuCgoKIyMjIEJveHBsb3Q6IFBvxI1ldCB6cmFuZW7DrSBwb2TEvmEgcG/EjWFzaWEKCmBgYHtyfQojIEJhciBwbG90IHdpdGggZ3JvdXBpbmcKbGlicmFyeShnZ3Bsb3QyKQoKbGlicmFyeShnZ3Bsb3QyKQoKCmdncGxvdCh1ZGFqZSwgYWVzKHggPSBmYWN0b3Iod2VhdGhlcl9jb25kaXRpb24pLCB5ID0gaW5qdXJpZXNfdG90YWwpKSArCiAgZ2VvbV9ib3hwbG90KGZpbGwgPSAibGlnaHRibHVlIiwgY29sb3IgPSAiZGFya2JsdWUiKSArCiAgbGFicygKICAgIHRpdGxlID0gIlBvxI1ldCB6cmFuZW7DrSBwb2TEvmEgcG92ZXRlcm5vc3Ruw71jaCBwb2RtaWVub2siLAogICAgeCA9ICJQb8SNYXNpZSIsCiAgICB5ID0gIkNlbGtvdsO9IHBvxI1ldCB6cmFuZW7DvWNoIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKYGBgCkJveHBsb3QgcG9yb3Zuw6F2YSByb3pkZWxlbmllIHBvxI10dSB6cmFuZW7DvWNoIHByaSByw7R6bnljaCB0eXBvY2ggcG/EjWFzaWEuIFZpZG5vLCDFvmUgcHJpIGphc25vbSBwb8SNYXPDrSAoQ0xFQVIpIGplIG1lZGnDoW4gcG/EjXR1IHpyYW5lbsO9Y2ggbsOtemt5LCBhbGUgdnpoxL5hZG9tIG5hIHZlxL5rw70gcG/EjWV0IG5laMO0ZCBzYSBvYsSNYXMgdnlza3l0bsO6IGFqIHByw61wYWR5IHMgdnnFocWhw61tIHBvxI10b20genJhbmVuw71jaC4gUHJpIGRhxb5kaSAoUkFJTiksIHNuZWh1IChTTk9XKSDEjWkgbsOhbXJhemUgc2Egcm96ZGVsZW5pZSBtaWVybmUgcG9zw7p2YSwgxI1vIG3DtMW+ZSBuYXpuYcSNb3ZhxaUgdnnFocWhaWUgcml6aWtvIHpyYW5lbsOtIHByaSB6aG9yxaFlbsO9Y2ggcG9kbWllbmthY2gsIGFqIGtlxI8gdsOkxI3FoWluYSBuZWjDtGQgbcOhIHN0w6FsZSAwIGFsZWJvIDEgenJhbmVuw7ogb3NvYnUuCgoKIyBaw6FrbGFkbsOpIMWhdGF0aXN0aWt5CgojIyBrbml0ciDigJMgamVkbm9kdWNow6EgdGFidcS+a2EKClYgdGVqdG8gxI1hc3RpIHNwb8SNw610YW0gesOha2xhZG7DqSDFoXRhdGlzdGlreSBwcmUgcHJlbWVubsO6IGBpbmp1cmllc190b3RhbGAgcG9kxL5hIHBvdmV0ZXJub3N0bsO9Y2ggcG9kbWllbm9rIChgd2VhdGhlcl9jb25kaXRpb25gKS4gWmF1asOtbWEgbWEsIGFrbyBzYSBsw63FoWkgcG/EjWV0IHpyYW5lbsO9Y2ggcHJpIHLDtHpueWNoIHR5cG9jaCBwb8SNYXNpYS4KCmBgYHtyfQpsaWJyYXJ5KGtuaXRyKQoKaW5qdXJ5LnN0YXRzIDwtIHVkYWplICU+JQogIGdyb3VwX2J5KHdlYXRoZXJfY29uZGl0aW9uKSAlPiUKICBzdW1tYXJpc2UoCiAgICBuICAgICAgPSBuKCksCiAgICBtZWFuICAgPSBtZWFuKGluanVyaWVzX3RvdGFsLCBuYS5ybSA9IFRSVUUpLAogICAgc2QgICAgID0gc2QoaW5qdXJpZXNfdG90YWwsIG5hLnJtID0gVFJVRSksCiAgICBtaW4gICAgPSBtaW4oaW5qdXJpZXNfdG90YWwsIG5hLnJtID0gVFJVRSksCiAgICBxMjUgICAgPSBxdWFudGlsZShpbmp1cmllc190b3RhbCwgMC4yNSwgbmEucm0gPSBUUlVFKSwKICAgIG1lZGlhbiA9IG1lZGlhbihpbmp1cmllc190b3RhbCwgbmEucm0gPSBUUlVFKSwKICAgIHE3NSAgICA9IHF1YW50aWxlKGluanVyaWVzX3RvdGFsLCAwLjc1LCBuYS5ybSA9IFRSVUUpLAogICAgbWF4ICAgID0gbWF4KGluanVyaWVzX3RvdGFsLCBuYS5ybSA9IFRSVUUpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkKCmthYmxlKGluanVyeS5zdGF0cywgZGlnaXRzID0gMiwKICAgICAgY2FwdGlvbiA9ICJaw6FrbGFkbsOpIMWhdGF0aXN0aWt5IHBvxI10dSB6cmFuZW7DvWNoIHBvZMS+YSBwb3ZldGVybm9zdG7DvWNoIHBvZG1pZW5vayIpCmBgYApUYWJ1xL5rYSB1a2F6dWplLCBha28gc2EgcHJpZW1lcm7DvSBwb8SNZXQgenJhbmVuw71jaCBhIHJvenB0eWwgenJhbmVuw60gbMOtxaFpYSBtZWR6aSBqZWRub3RsaXbDvW1pIHR5cG1pIHBvxI1hc2lhLiBOYXByw61rbGFkIHByaSBwb8SNYXPDrSBSQUlOIGEgRlJFRVpJTkcgUkFJTi9EUklaWkxFIHPDuiBwcmllbWVybsOpIGhvZG5vdHkgaW5qdXJpZXNfdG90YWwgbWllcm5lIHZ5xaHFoWllIGFrbyBwcmkgQ0xFQVIsIMSNbyBqZSB2IHPDumxhZGUgcyBvxI1ha8OhdmFuw61tLCDFvmUgemhvcsWhZW7DqSBwb2RtaWVua3kgenZ5xaF1asO6IHJpemlrbyB6cmFuZW7DrS4gWsOhcm92ZcWIIHZpZG5vLCDFvmUgcHJpIGthdGVnw7NyaWkgVU5LTk9XTiBzw7ogcHJpZW1lcnkgdmXEvm1pIG7DrXprZSwgxI1vIG3DtMW+ZSBzw7p2aXNpZcWlIGFqIHMgbmVrdmFsaXRuw71tIGFsZWJvIG5lw7pwbG7DvW0gesOhem5hbW9tIMO6ZGFqb3YuCgoKYGBge3J9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKCiMgWsOha2xhZG7DqSDFoXRhdGlzdGlreSBwb8SNdHUgenJhbmVuw71jaCBwb2TEvmEgcG92ZXRlcm5vc3Ruw71jaCBwb2RtaWVub2sKaW5qdXJ5LnN0YXRzIDwtIHVkYWplICU+JQogIGdyb3VwX2J5KHdlYXRoZXJfY29uZGl0aW9uKSAlPiUKICBzdW1tYXJpc2UoCiAgICBuICAgICAgPSBuKCksCiAgICBtZWFuICAgPSBtZWFuKGluanVyaWVzX3RvdGFsLCBuYS5ybSA9IFRSVUUpLAogICAgc2QgICAgID0gc2QoaW5qdXJpZXNfdG90YWwsIG5hLnJtID0gVFJVRSksCiAgICBtaW4gICAgPSBtaW4oaW5qdXJpZXNfdG90YWwsIG5hLnJtID0gVFJVRSksCiAgICBxMjUgICAgPSBxdWFudGlsZShpbmp1cmllc190b3RhbCwgMC4yNSwgbmEucm0gPSBUUlVFKSwKICAgIG1lZGlhbiA9IG1lZGlhbihpbmp1cmllc190b3RhbCwgbmEucm0gPSBUUlVFKSwKICAgIHE3NSAgICA9IHF1YW50aWxlKGluanVyaWVzX3RvdGFsLCAwLjc1LCBuYS5ybSA9IFRSVUUpLAogICAgbWF4ICAgID0gbWF4KGluanVyaWVzX3RvdGFsLCBuYS5ybSA9IFRSVUUpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkKCiMga3JhasWhaWEgdGFidcS+a2EgcyBrYWJsZUV4dHJhCmluanVyeS5zdGF0cyAlPiUKICBrYWJsZSgKICAgIGRpZ2l0cyAgPSAyLAogICAgY2FwdGlvbiA9ICJaw6FrbGFkbsOpIMWhdGF0aXN0aWt5IHBvxI10dSB6cmFuZW7DvWNoIHBvZMS+YSBwb3ZldGVybm9zdG7DvWNoIHBvZG1pZW5vayIKICApICU+JQogIGthYmxlX3N0eWxpbmcoCiAgICBmdWxsX3dpZHRoICAgICAgICA9IEZBTFNFLAogICAgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpCiAgKSAlPiUKICBjb2x1bW5fc3BlYygxLCBib2xkID0gVFJVRSkgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHBydsO9IHN0xLpwZWMgKHBvxI1hc2llKSB0dcSNbmUKICByb3dfc3BlYygwLCBib2xkID0gVFJVRSwgYmFja2dyb3VuZCA9ICIjZjJmMmYyIikgJT4lICAgICAgICAjIGhsYXZpxI1rYSB0dcSNbsOhIHNvIHNpdsO9bSBwb3phZMOtbQogIGFkZF9oZWFkZXJfYWJvdmUoYygiICIgPSAxLCAixaB0YXRpc3Rpa3kgcG/EjXR1IHpyYW5lbsO9Y2giID0gOCkpCgpgYGAKCmBgYHtyfQojIyBLb3JlbGHEjW7DoSBtYXRpY2EgYSBoZWF0bWFwYQoKaW5zdGFsbC5wYWNrYWdlcygicmVzaGFwZTIiKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkocmVzaGFwZTIpCgojIHbDvWJlciBudW1lcmlja8O9Y2ggcHJlbWVubsO9Y2gsIGt0b3LDqSBkw6F2YWrDuiB6bXlzZWwKbnVtX2RhdGEgPC0gdWRhamUgJT4lCiAgc2VsZWN0KAogICAgbnVtX3VuaXRzLAogICAgaW5qdXJpZXNfdG90YWwsCiAgICBpbmp1cmllc19mYXRhbCwKICAgIGluanVyaWVzX2luY2FwYWNpdGF0aW5nLAogICAgaW5qdXJpZXNfbm9uX2luY2FwYWNpdGF0aW5nLAogICAgaW5qdXJpZXNfcmVwb3J0ZWRfbm90X2V2aWRlbnQsCiAgICBpbmp1cmllc19ub19pbmRpY2F0aW9uLAogICAgY3Jhc2hfaG91ciwKICAgIGNyYXNoX2RheV9vZl93ZWVrLAogICAgY3Jhc2hfbW9udGgKICApCgojIGtvcmVsYcSNbsOhIG1hdGljYSAoUGVhcnNvbm92YSBrb3JlbMOhY2lhKQpjb3JfbWF0IDwtIGNvcihudW1fZGF0YSwgdXNlID0gInBhaXJ3aXNlLmNvbXBsZXRlLm9icyIpCgojIHByZSBnZ3Bsb3QgcG90cmVidWplbWUgImRsaMO9IiB0dmFyCmNvcl9kZiA8LSBtZWx0KGNvcl9tYXQsIHZhcm5hbWVzID0gYygiVmFyMSIsICJWYXIyIiksIHZhbHVlLm5hbWUgPSAiY29ycmVsYXRpb24iKQoKZ2dwbG90KGNvcl9kZiwgYWVzKHggPSBWYXIxLCB5ID0gVmFyMiwgZmlsbCA9IGNvcnJlbGF0aW9uKSkgKwogIGdlb21fdGlsZSgpICsKICBzY2FsZV9maWxsX2dyYWRpZW50MigKICAgIGxpbWl0cyA9IGMoLTEsIDEpLAogICAgbmEudmFsdWUgPSAid2hpdGUiLAogICAgbmFtZSA9ICJLb3JlbMOhY2lhIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpCiAgKSArCiAgbGFicygKICAgIHRpdGxlID0gIkhlYXRtYXBhIGtvcmVsYcSNbmVqIG1hdGljZSBudW1lcmlja8O9Y2ggcHJlbWVubsO9Y2giCiAgKQpgYGAKSGVhdG1hcGEgdWthenVqZSwgYWtvIHNwb2x1IHPDunZpc2lhIGplZG5vdGxpdsOpIMSNw61zZWxuw6kgcHJlbWVubsOpIHYgZGF0YWLDoXplIG5laMO0ZC4gVmlkbm8sIMW+ZSBwb8SNZXQgenJhbmVuw71jaCByYXN0aWUgbmFqbcOkIHZ0ZWR5LCBrZcSPIGplIGRvIG5laG9keSB6YXBvamVuw71jaCB2aWFjIHZvemlkaWVsIGEga2XEjyBzw7ogenJhbmVuaWEgdsOhxb5uZWrFoWllLiDEjGFzb3bDqSBwcmVtZW5uw6kgYWtvIGhvZGluYSwgZGXFiCBhbGVibyBtZXNpYWMgbWFqw7ogbGVuIHNsYWLDvSB2esWlYWggayBwb8SNdHUgenJhbmVuw71jaCwgdGFrxb5lIMSNYXMgbmVob2R5IG5lem9ocsOhdmEgYcW+IHRha8O6IGTDtGxlxb5pdMO6IMO6bG9odSBha28gc2Ftb3Ruw70gcm96c2FoIGEgesOhdmHFvm5vc8WlIG5laG9keS4KCiMgVGVzdG92YW5pZSBoeXBvdMOpegoKIyMgdC10ZXN0OiBQb3Jvdm5hbmllIHBvxI10dSB6cmFuZW7DvWNoIOKAkyBEZcWIIHZzLiBOb2MKClBvcm92bsOhbSBwcmllbWVybsO9IHBvxI1ldCB6cmFuZW7DvWNoIHByaSBuZWhvZMOhY2gsIGt0b3LDqSBzYSBzdGFsaToKCi0gY2V6IGRlxYggKGBEQVlMSUdIVGApCi0gdiB0bWUgbmEgb3N2ZXRsZW5laiBjZXN0ZSAoYERBUktORVNTLCBMSUdIVEVEIFJPQURgKQoKUG91xb5pamVtIGR2b2p2w71iZXJvdsO9IHQtdGVzdC4KCmBgYHtyfQp0LnRlc3QucmVzdWx0IDwtIHQudGVzdCgKICB1ZGFqZSRpbmp1cmllc190b3RhbFt1ZGFqZSRsaWdodGluZ19jb25kaXRpb24gPT0gIkRBWUxJR0hUIl0sCiAgdWRhamUkaW5qdXJpZXNfdG90YWxbdWRhamUkbGlnaHRpbmdfY29uZGl0aW9uID09ICJEQVJLTkVTUywgTElHSFRFRCBST0FEIl0KKQoKcHJpbnQodC50ZXN0LnJlc3VsdCkKCmBgYAoKVsO9c2xlZG9rIHQtdGVzdHUgdWthenVqZSwgxb5lIHByaWVtZXJuw70gcG/EjWV0IHpyYW5lbsO9Y2ggcHJpIG5laG9kw6FjaCBjZXogZGXFiCAoREFZTElHSFQpIGEgdiB0bWUgbmEgb3N2ZXRsZW5laiBjZXN0ZSAoREFSS05FU1MsIExJR0hURUQgUk9BRCkgc2EgxaF0YXRpc3RpY2t5IHbDvXpuYW1uZSBsw63FoWkgKHAtaG9kbm90YSBqZSBwcmFrdGlja3kgbnVsb3bDoSkuIFByaWVtZXJuw70gcG/EjWV0IHpyYW5lbsO9Y2ggamUgdnnFocWhw60gcHJpIG5laG9kw6FjaCB2IHRtZSBuYSBvc3ZldGxlbmVqIGNlc3RlLCDEjW8gbmF6bmHEjXVqZSwgxb5lIGFqIHByaSB1bWVsb20gb3N2ZXRsZW7DrSBzw7ogbm/EjW7DqSBwb2RtaWVua3kgcml6aWtvdmVqxaFpZSBha28gZGVubsOpLgoKCiMjIEFOT1ZBOiBWcGx5diBwb3ZldGVybm9zdG7DvWNoIHBvZG1pZW5vayBuYSBwb8SNZXQgenJhbmVuw71jaAoKVGVyYXogbWEgemF1asOtbWEsIMSNaSBzYSBwcmllbWVybsO9IHBvxI1ldCB6cmFuZW7DvWNoIGzDrcWhaSBtZWR6aSByw7R6bnltaSB0eXBtaSBwb8SNYXNpYSAoYHdlYXRoZXJfY29uZGl0aW9uYCkuIE5hIHRvIHBvdcW+aWplbSBqZWRub2Zha3Rvcm92w7ogQU5PVkEuCgpgYGB7cn0KYW5vdmEucmVzdWx0IDwtIGFvdihpbmp1cmllc190b3RhbCB+IHdlYXRoZXJfY29uZGl0aW9uLCBkYXRhID0gdWRhamUpCnN1bW1hcnkoYW5vdmEucmVzdWx0KQpgYGAKCkFOT1ZBIHRlc3R1amUsIMSNaSBqZSBwcmllbWVybsO9IHBvxI1ldCB6cmFuZW7DvWNoIHJvdm5ha8O9IHByZSB2xaFldGt5IHR5cHkgcG92ZXRlcm5vc3Ruw71jaCBwb2RtaWVub2suIFbDvXNsZWRreSB1a2F6dWrDuiB2ZcS+bWkgbsOtemt1IHAtaG9kbm90dSAocCA8IDAuMDAxKSwgxI1vIHpuYW1lbsOhLCDFvmUgcG/EjWFzaWUgbcOhIMWhdGF0aXN0aWNreSB2w716bmFtbsO9IHZwbHl2IG5hIHByaWVtZXJuw70gcG/EjWV0IHpyYW5lbsO9Y2guIE5laG92b3LDrSBuw6FtIHRvIHbFoWFrLCBrdG9yw6kga29ua3LDqXRuZSBrYXRlZ8OzcmllIHNhIGzDrcWhaWEgbWVkemkgc2Vib3Ug4oCTIGliYSB0bywgxb5lIGFzcG/FiCBqZWRuYSBza3VwaW5hIChuYXByw61rbGFkIFJBSU4gYWxlYm8gU05PVykgc2Egb2Qgb3N0YXRuw71jaCB2w716bmFtbmUgb2RsacWhdWplLgoKIyBMaW5lw6FybmEgcmVncmVzaWE6IGZha3Rvcnkgb3ZwbHl2xYh1asO6Y2UgcG/EjWV0IHpyYW5lbsO9Y2gKClYgdGVqdG8gxI1hc3RpIHZ5dHZvcsOtbSBsaW5lw6FybnkgcmVncmVzbsO9IG1vZGVsLCBrdG9yw70gc2EgcG9rw7pzaSB2eXN2ZXRsacWlIHBvxI1ldCB6cmFuZW7DvWNoIChgaW5qdXJpZXNfdG90YWxgKSBwb21vY291OgoKLSBgbnVtX3VuaXRzYCDigJMgcG/EjWV0IHrDusSNYXN0bmVuw71jaCB2b3ppZGllbCwKLSBgY3Jhc2hfaG91cmAg4oCTIGhvZGluYSBuZWhvZHksCi0gYGNyYXNoX2RheV9vZl93ZWVrYCDigJMgZGXFiCB2IHTDvcW+ZG5pLAotIGBjcmFzaF9tb250aGAg4oCTIG1lc2lhYyBuZWhvZHkuCgpgYGB7cn0KbW9kZWwgPC0gbG0oaW5qdXJpZXNfdG90YWwgfiBudW1fdW5pdHMgKyBjcmFzaF9ob3VyICsgY3Jhc2hfZGF5X29mX3dlZWsgKyBjcmFzaF9tb250aCwKICAgICAgICAgICAgZGF0YSA9IHVkYWplKQoKc3VtbWFyeShtb2RlbCkKYGBgCgpSZWdyZXNuw70gbW9kZWwgdWthenVqZSwgYWtvIHNwb2x1IHPDunZpc8OtIHBvxI1ldCB6cmFuZW7DvWNoIHMgcG/EjXRvbSB6w7rEjWFzdG5lbsO9Y2ggamVkbm90aWVrIGEgxI1hc29tIG5laG9keS4gUHJlbWVubsOhIG51bV91bml0cyBtw6Ega2xhZG7DvSBhIMWhdGF0aXN0aWNreSB2w716bmFtbnkga29lZmljaWVudCwgxI1vIHpuYW1lbsOhLCDFvmUgcyByYXN0w7pjaW0gcG/EjXRvbSB2b3ppZGllbCB2IG5laG9kZSByYXN0aWUgYWogb8SNYWvDoXZhbsO9IHBvxI1ldCB6cmFuZW7DvWNoLiDEjGFzb3bDqSBwcmVtZW5uw6kgKGhvZGluYSwgZGXFiCB2IHTDvcW+ZG5pLCBtZXNpYWMpIHPDrWNlIHZ5Y2jDoWR6YWrDuiDFoXRhdGlzdGlja3kgdsO9em5hbW5lLCBhbGUgaWNoIGtvZWZpY2llbnR5IHPDuiBtYWzDqSwgdGFrxb5lIHptZW5hIMSNYXN1IG3DoSB2IHByaWVtZXJlIG92ZcS+YSBtZW7FocOtIHZwbHl2IGFrbyBzYW1vdG7DvSByb3pzYWggbmVob2R5IChwb8SNZXQgdm96aWRpZWwpLgoKCiMjIFBla27DoSB0YWJ1xL5rYSBrb2VmaWNpZW50b3YgKGJyb29tICsga2FibGVFeHRyYSkKCmBgYHtyfQojIGluc3RhbGwucGFja2FnZXMoYygiYnJvb20iLCAia2FibGVFeHRyYSIsICJkcGx5ciIsICJzdHJpbmdyIikpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkoc3RyaW5ncikKCmNvZWYudGJsIDwtIHRpZHkobW9kZWwsIGNvbmYuaW50ID0gVFJVRSkgJT4lCiAgbXV0YXRlKAogICAgdGVybSA9IGRwbHlyOjpyZWNvZGUodGVybSwKICAgICAgIihJbnRlcmNlcHQpIiAgICAgICAgPSAiSW50ZXJjZXB0IiwKICAgICAgIm51bV91bml0cyIgICAgICAgICAgPSAiTnVtYmVyIG9mIHVuaXRzIiwKICAgICAgImNyYXNoX2hvdXIiICAgICAgICAgPSAiQ3Jhc2ggaG91ciIsCiAgICAgICJjcmFzaF9kYXlfb2Zfd2VlayIgID0gIkRheSBvZiB3ZWVrIiwKICAgICAgImNyYXNoX21vbnRoIiAgICAgICAgPSAiTW9udGgiCiAgICApLAogICAgc3RhcnMgPSBkcGx5cjo6Y2FzZV93aGVuKAogICAgICBwLnZhbHVlIDwgMC4wMDEgfiAiKioqIiwKICAgICAgcC52YWx1ZSA8IDAuMDEgIH4gIioqIiwKICAgICAgcC52YWx1ZSA8IDAuMDUgIH4gIioiLAogICAgICBwLnZhbHVlIDwgMC4xICAgfiAiwrciLAogICAgICBUUlVFICAgICAgICAgICAgfiAiIgogICAgKQogICkgJT4lCiAgdHJhbnNtdXRlKAogICAgVGVybSA9IHRlcm0sCiAgICBFc3RpbWF0ZSA9IGVzdGltYXRlLAogICAgYFN0ZC4gRXJyb3JgID0gc3RkLmVycm9yLAogICAgYHQgdmFsdWVgID0gc3RhdGlzdGljLAogICAgYHAgdmFsdWVgID0gcC52YWx1ZSwKICAgIGA5NSUgQ0lgID0gc3RyX2MoIlsiLCByb3VuZChjb25mLmxvdywgMyksICIsICIsIHJvdW5kKGNvbmYuaGlnaCwgMyksICJdIiksCiAgICBTaWcgPSBzdGFycwogICkKCmNvZWYudGJsICU+JQogIGthYmxlKAogICAgZGlnaXRzID0gMywKICAgIGNhcHRpb24gPSAiT0xTIFJlZ3Jlc3Npb24gQ29lZmZpY2llbnRzIChpbmp1cmllc190b3RhbCB+IG51bV91bml0cyArIGNyYXNoX2hvdXIgKyBjcmFzaF9kYXlfb2Zfd2VlayArIGNyYXNoX21vbnRoKSIKICApICU+JQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFLAogICAgICAgICAgICAgICAgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAlPiUKICBjb2x1bW5fc3BlYygxLCBib2xkID0gVFJVRSkgJT4lCiAgcm93X3NwZWMoMCwgYm9sZCA9IFRSVUUsIGJhY2tncm91bmQgPSAiI2YyZjJmMiIpICU+JQogIGZvb3Rub3RlKAogICAgZ2VuZXJhbCA9ICJTaWduaWYuIGNvZGVzOiAqKiogcDwwLjAwMSwgKiogcDwwLjAxLCAqIHA8MC4wNSwgwrcgcDwwLjEuIiwKICAgIHRocmVlcGFydHRhYmxlID0gVFJVRQogICkKYGBgCgojIyDFoHRhdGlzdGlreSBwcmlzcMO0c29iZW5pYSBtb2RlbHUKCmBgYHtyfQoKbGlicmFyeShicm9vbSkKbGlicmFyeShkcGx5cikKbGlicmFyeShrbml0cikKbGlicmFyeShrYWJsZUV4dHJhKQoKZml0LnRibCA8LSBnbGFuY2UobW9kZWwpICU+JQogIHRyYW5zbXV0ZSgKICAgIGBSLXNxdWFyZWRgICAgICAgPSByLnNxdWFyZWQsCiAgICBgQWRqLiBSLXNxdWFyZWRgID0gYWRqLnIuc3F1YXJlZCwKICAgIGBGLXN0YXRpc3RpY2AgICAgPSBzdGF0aXN0aWMsCiAgICBgRiBwLXZhbHVlYCAgICAgID0gcC52YWx1ZSwKICAgIGBBSUNgICAgICAgICAgICAgPSBBSUMsCiAgICBgQklDYCAgICAgICAgICAgID0gQklDLAogICAgYE51bS4gb2JzLmAgICAgICA9IG5vYnMKICApCgpmaXQudGJsICU+JQogIGthYmxlKGRpZ2l0cyA9IDMsIGNhcHRpb24gPSAiTW9kZWwgRml0IFN0YXRpc3RpY3MiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSwKICAgICAgICAgICAgICAgIGJvb3RzdHJhcF9vcHRpb25zID0gYygiY29uZGVuc2VkIikpCmBgYAoKxaB0YXRpc3Rpa3kgcHJpc3DDtHNvYmVuaWEgbW9kZWx1IHVrYXp1asO6LCDFvmUgaG9kbm90YSBSLXNxdWFyZWQgamUgc8OtY2UgcG9tZXJuZSBuw616a2EgKG9rb2xvIDAuMDI2KSwgxI1vIHpuYW1lbsOhLCDFvmUgbW9kZWwgdnlzdmV0xL51amUgaWJhIG1hbMO6IMSNYXPFpSB2YXJpYWJpbGl0eSB2IHBvxI10ZSB6cmFuZW7DvWNoLiBOYXByaWVrIHRvbXUgc8O6IHbFoWV0a3kgdGVzdG92YW7DqSBwcmVtZW5uw6kgxaF0YXRpc3RpY2t5IHbDvXpuYW1uZSwgdGFrxb5lIG1vZGVsIGplIHZob2Ruw70gYWtvIGplZG5vZHVjaMO9IGlsdXN0cmHEjW7DvSBuw6FzdHJvaiBuYSBwb2Nob3BlbmllIHRyZW5kb3Yg4oCTIG5vIG5hIHByZXNuw6kgcHJlZGlrY2llIGJ5IGJvbG8gdHJlYmEgemFocm7DusWlIGFqIMSPYWzFoWllIGZha3RvcnkgKG5hcHIuIHR5cCBrcmnFvm92YXRreSwgcsO9Y2hsb3PFpSwgdHlwIHZvemlkbGEgYSBwb2QuKS4=