Note that the directories used to store data are likely different on your computer, and such references will need to be changed before using any such code.
library(knitr)
library(kableExtra)
library(tidyverse)
library(broom)
html_df <- function(text, cols=NULL, col1=FALSE, full=F) {
if(!length(cols)) {
cols=colnames(text)
}
if(!col1) {
kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width=full)
} else {
kable(text,"html", col.names = cols, align = c("l",rep('c',length(cols)-1))) %>%
kable_styling(bootstrap_options = c("striped","hover"), full_width=full) %>%
column_spec(1,bold=T)
}
}
# Import Walmart data from Session_Project
library(lubridate)
preprocess_data <- function(df) {
# Merge the data together (Pulled from outside of function -- "scoping")
df <- inner_join(df, weekly.stores)
df <- inner_join(df, weekly.features[,1:11])
# Compress the weird markdown information to 1 variable
df$markdown <- 0
df[!is.na(df$MarkDown1),]$markdown <- df[!is.na(df$MarkDown1),]$MarkDown1
df[!is.na(df$MarkDown2),]$markdown <- df[!is.na(df$MarkDown2),]$MarkDown2
df[!is.na(df$MarkDown3),]$markdown <- df[!is.na(df$MarkDown3),]$MarkDown3
df[!is.na(df$MarkDown4),]$markdown <- df[!is.na(df$MarkDown4),]$MarkDown4
df[!is.na(df$MarkDown5),]$markdown <- df[!is.na(df$MarkDown5),]$MarkDown5
# Fix dates and add useful time variables
df$date <- as.Date(df$Date)
df$week <- week(df$date)
df$year <- year(df$date)
df$month <- month(df$date)
df
}
weekly <- read.csv("../../Data/WMT_train.csv", stringsAsFactors=FALSE)
weekly.features <- read.csv("../../Data/WMT_features.csv", stringsAsFactors=FALSE)
weekly.stores <- read.csv("../../Data/WMT_stores.csv", stringsAsFactors=FALSE)
df <- preprocess_data(weekly)
#df$id <- df$Store *10000 + df$week * 100 + df$Dept
df <- df %>%
group_by(Store, Dept) %>%
mutate(store_avg=mean(Weekly_Sales, rm.na=T)) %>%
ungroup()
# Create the binary variable from Walmart sales data
df$double <- ifelse(df$Weekly_Sales > df$store_avg*2,1,0)
fit <- glm(double ~ IsHoliday, data=df, family=binomial)
tidy(fit)
# Automating the above:
exp(coef(fit))
(Intercept) IsHolidayTRUE
0.03184725 1.71367497
c(-3.44, -2.9)
[1] -3.44 -2.90
a <- exp(-3.44)
b <- exp(-2.9)
c(a/(1+a), b/(1+b))
[1] 0.03106848 0.05215356
model2 <- glm(double ~ IsHoliday + Temperature + Fuel_Price, data=df, family=binomial)
summary(model2)
Call:
glm(formula = double ~ IsHoliday + Temperature + Fuel_Price,
family = binomial, data = df)
Deviance Residuals:
Min 1Q Median 3Q Max
-0.4113 -0.2738 -0.2464 -0.2213 2.8562
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -1.7764917 0.0673246 -26.39 <2e-16 ***
IsHolidayTRUE 0.3704298 0.0284395 13.03 <2e-16 ***
Temperature -0.0108268 0.0004698 -23.04 <2e-16 ***
Fuel_Price -0.3091950 0.0196234 -15.76 <2e-16 ***
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 120370 on 421569 degrees of freedom
Residual deviance: 119146 on 421566 degrees of freedom
AIC: 119154
Number of Fisher Scoring iterations: 6
# Odds
exp(coef(model2))
(Intercept) IsHolidayTRUE Temperature Fuel_Price
0.1692308 1.4483570 0.9892316 0.7340376
# Typical September days
hday_sep <- mean(predict(model2, filter(df, IsHoliday, month==9), type="response"))
no_hday_sep <- mean(predict(model2, filter(df, !IsHoliday, month==9), type="response"))
# Typical December days
hday_dec <- mean(predict(model2, filter(df, IsHoliday, month==12), type="response"))
no_hday_dec <- mean(predict(model2, filter(df, !IsHoliday, month==12), type="response"))
html_df(data.frame(Month=c(9,9,12,12),
IsHoliday=c(FALSE,TRUE,FALSE,TRUE),
Probability=c(no_hday_sep, hday_sep, no_hday_dec, hday_dec)))
Month |
IsHoliday |
Probability |
9 |
FALSE |
0.0266789 |
9 |
TRUE |
0.0374761 |
12 |
FALSE |
0.0398377 |
12 |
TRUE |
0.0586483 |
percent <- function(x) {
paste0(round(x*100,2),"%")
}
library(rlang)
# Another use for a quosure :)
# You could always just use quoted arguements here though
extract_margin <- function(m, x) {
x <- enquo(x)
percent(filter(summary(m), factor==quo_text(x))$AME)
}
# Calculate AME marginal effects
library(margins)
m <- margins(model2)
m
summary(m) %>%
html_df()
factor |
AME |
SE |
z |
p |
lower |
upper |
Fuel_Price |
-0.0096438 |
0.0006163 |
-15.64800 |
0 |
-0.0108517 |
-0.0084359 |
IsHoliday |
0.0133450 |
0.0011754 |
11.35372 |
0 |
0.0110413 |
0.0156487 |
Temperature |
-0.0003377 |
0.0000149 |
-22.71255 |
0 |
-0.0003668 |
-0.0003085 |
plot(m, which=summary(m)$factor)

margins(model2, at = list(IsHoliday = c(TRUE, FALSE)),
variables = c("Temperature", "Fuel_Price")) %>%
summary() %>%
html_df()
factor |
IsHoliday |
AME |
SE |
z |
p |
lower |
upper |
Fuel_Price |
FALSE |
-0.0093401 |
0.0005989 |
-15.59617 |
0 |
-0.0105139 |
-0.0081664 |
Fuel_Price |
TRUE |
-0.0131335 |
0.0008717 |
-15.06650 |
0 |
-0.0148420 |
-0.0114250 |
Temperature |
FALSE |
-0.0003271 |
0.0000146 |
-22.46024 |
0 |
-0.0003556 |
-0.0002985 |
Temperature |
TRUE |
-0.0004599 |
0.0000210 |
-21.92927 |
0 |
-0.0005010 |
-0.0004188 |
margins(model2, at = list(Temperature = c(0, 20, 40, 60, 80, 100)),
variables = c("IsHoliday")) %>%
summary() %>%
html_df()
factor |
Temperature |
AME |
SE |
z |
p |
lower |
upper |
IsHoliday |
0 |
0.0234484 |
0.0020168 |
11.62643 |
0 |
0.0194955 |
0.0274012 |
IsHoliday |
20 |
0.0194072 |
0.0016710 |
11.61387 |
0 |
0.0161320 |
0.0226824 |
IsHoliday |
40 |
0.0159819 |
0.0013885 |
11.51001 |
0 |
0.0132604 |
0.0187033 |
IsHoliday |
60 |
0.0131066 |
0.0011592 |
11.30623 |
0 |
0.0108345 |
0.0153786 |
IsHoliday |
80 |
0.0107120 |
0.0009732 |
11.00749 |
0 |
0.0088046 |
0.0126193 |
IsHoliday |
100 |
0.0087305 |
0.0008213 |
10.62977 |
0 |
0.0071207 |
0.0103402 |
# load data
df <- read.csv("../../Data/Shipping/shipping_dataset.csv", stringsAsFactors = F, na="NA")
df_ships <- read.csv("../../Data/Shipping/index.csv", stringsAsFactors = F, na="NA")
df_ports <- read.csv("../../Data/Shipping/port_dataset.csv", stringsAsFactors = F, na="NA")
typhoon <- read.csv("../../Data/Shipping/typhoon.csv", stringsAsFactors = F)
# rename lat and lon to avoid conflicts
df_ports <- df_ports %>% rename(port_lat=lat, port_lon=lon)
# Join in ships and ports
df <- left_join(df, df_ships)
Joining, by = "imo"
df <- df %>% filter(!imo == 9528029 | reported_destination == "AU ADL")
df <- left_join(df,df_ports, by=c("left_port"="port"))
df <- df %>% rename(left_lat=port_lat, left_lon=port_lon)
df <- left_join(df,df_ports, by=c("arrive_port"="port"))
df <- df %>% rename(arrive_lat=port_lat, arrive_lon=port_lon)
# fix dates
df$last_update = as.POSIXct(df$last_update, tz="UTC",origin="1970-01-01")
df$left_time = as.POSIXct(df$left_time, tz="UTC",origin="1970-01-01")
df$arrive_time = as.POSIXct(df$arrive_time, tz="UTC",origin="1970-01-01")
# Fix typhoon dates
typhoon$date <- as.POSIXct(paste(typhoon$date,typhoon$time), format="%Y%b%d %H%M%S", tz="UTC", origin="1970-01-01")
# Fix typhoon longitude -- they have lon * -1 in the data
typhoon$lon <- typhoon$lon * -1
typhoon_all <- typhoon
# filter to dates in sample
typhoon <- typhoon %>% filter(date > as.POSIXct("2018-08-30", tz="UTC"))
df <- df %>% rename(frame=run)
df_all <- df[df$frame != 32,]
df <- df %>% filter(last_update > as.POSIXct("2018-08-30", tz="UTC"))
library(plotly) # needed for the toRGB() function
geo <- list(
showland = TRUE,
showlakes = TRUE,
showcountries = TRUE,
showocean = TRUE,
countrywidth = 0.5,
landcolor = toRGB("grey90"),
lakecolor = toRGB("aliceblue"),
oceancolor = toRGB("aliceblue"),
projection = list(
type = 'orthographic', # detailed at https://plot.ly/r/reference/#layout-geo-projection
rotation = list(
lon = 100,
lat = 1,
roll = 0
)
),
lonaxis = list(
showgrid = TRUE,
gridcolor = toRGB("gray40"),
gridwidth = 0.5
),
lataxis = list(
showgrid = TRUE,
gridcolor = toRGB("gray40"),
gridwidth = 0.5
)
)
typhoon_Aug31 <- typhoon[typhoon$date > as.POSIXct("2018-08-31 00:00:00", tz="UTC") & typhoon$date < as.POSIXct("2018-09-01 00:00:00", tz="UTC"),]
typhoon_Jebi <- typhoon_all[typhoon_all$typhoon_name == "JEBI_Y",]
df_Aug31 <- df[df$frame == 1,]
library(plotly) # for plotting
library(RColorBrewer) # for colors
# plot with boats, ports, and typhoons
# Note: geo is defined in the appendix -- it controls layout
palette = brewer.pal(8, "Dark2")[c(1,8,3,2)]
p <- plot_geo(colors=palette) %>%
add_markers(data=df_ports, x = ~port_lon, y = ~port_lat, color = "Port") %>%
add_markers(data=df_Aug31, x = ~lon, y = ~lat, color = ~ship_type,
text=~paste('Ship name',shipname)) %>%
add_markers(data=typhoon_Aug31, x = ~lon, y = ~lat, color="TYPHOON",
text=~paste("Name", typhoon_name)) %>%
layout(showlegend = TRUE, geo = geo,
title = 'Singaporean owned container and tanker ships, August 31, 2018')
p
# plot with boats and typhoons
palette = brewer.pal(8, "Dark2")[c(1,3,2)]
p <- plot_geo(colors=palette) %>%
add_markers(data=df_all[df_all$frame == 14,], x = ~lon, y = ~lat, color = ~ship_type,
text=~paste('Ship name',shipname)) %>%
add_markers(data=typhoon_Jebi, x = ~lon, y = ~lat, color="Typhoon Jebi", text=~paste("Name", typhoon_name, "</br>Time: ", date)) %>%
layout(
showlegend = TRUE, geo = geo,
title = 'Singaporean container/tanker ships, September 4, 2018, evening')
p
library(leaflet)
library(leaflet.extras)
# typhoon icons
icons <- pulseIcons(color='red',
heartbeat = ifelse(typhoon_Jebi$intensity_vmax > 150/1.852, 0.8,
ifelse(typhoon$intensity_vmax < 118/1.852, 1.6, 1.2)),
iconSize=ifelse(typhoon_Jebi$intensity_vmax > 150/1.852, 12,
ifelse(typhoon_Jebi$intensity_vmax < 118/1.852, 3, 8)))
# ship icons
shipicons <- iconList(
ship = makeIcon("../Figures/ship.png", NULL, 18, 18)
)
leaflet() %>%
addTiles() %>%
setView(lng = 136, lat = 34, zoom=4) %>%
addPulseMarkers(data=typhoon_Jebi[seq(1,nrow(typhoon_Jebi),5),], lng=~lon,
lat=~lat, label=~date, icon=icons) %>%
addCircleMarkers(data=typhoon_Jebi[typhoon_Jebi$intensity_vmax > 150/1.852,],
lng=~lon, lat=~lat,stroke = TRUE, radius=12, color="red", label=~date) %>%
addCircleMarkers(data=typhoon_Jebi[typhoon_Jebi$intensity_vmax <= 150/1.852 &
typhoon_Jebi$intensity_vmax > 118/1.852,], lng=~lon, lat=~lat,
stroke = TRUE, radius=12, color="red", label=~date) %>%
addCircleMarkers(data=typhoon_Jebi[typhoon_Jebi$intensity_vmax <=118/1.852,],
lng=~lon, lat=~lat, stroke = TRUE, radius=12, color="red", label=~date) %>%
addMarkers(data=df_all[df_all$frame == 14,], lng=~lon, lat=~lat,
label=~shipname, icon=shipicons)
# Load datas
df3 <- read.csv("../../Data/Shipping/combined.csv", stringsAsFactors = F)
library(geosphere)
# Calculate distance to and from ports
df3$dist_toport <- distVincentyEllipsoid(as.matrix(df3[,c("lon","lat")]), as.matrix(df3[,c("arrive_lon","arrive_lat")]))
df3$dist_fromport <- distVincentyEllipsoid(as.matrix(df3[,c("lon","lat")]), as.matrix(df3[,c("left_lon","left_lat")]))
df3 <- df3 %>%
arrange(imo, last_update) %>%
group_by(imo) %>%
mutate(delayed=ifelse(difftime(arrive_time, lead(arrive_time), units="days") > 1/8 & arrive_time > last_update & left_port == lead(left_port),1,0)) %>%
ungroup()
library(geosphere)
x <- as.matrix(df3[,c("lon","lat")]) # ship location
y <- as.matrix(df3[,c("ty_lon","ty_lat")]) # typhoon location
df3$dist_typhoon <- distVincentyEllipsoid(x, y) / 1000
df3$typhoon_500 = ifelse(df3$dist_typhoon < 500 &
df3$dist_typhoon >= 0, 1, 0)
df3$typhoon_1000 = ifelse(df3$dist_typhoon < 1000 &
df3$dist_typhoon >= 500, 1, 0)
df3$typhoon_2000 = ifelse(df3$dist_typhoon < 2000 &
df3$dist_typhoon >= 1000, 1, 0)
fit1 <- glm(delayed ~ typhoon_500 + typhoon_1000 + typhoon_2000, data=df3,
family=binomial)
summary(fit1)
Call:
glm(formula = delayed ~ typhoon_500 + typhoon_1000 + typhoon_2000,
family = binomial, data = df3)
Deviance Residuals:
Min 1Q Median 3Q Max
-0.2502 -0.2261 -0.2261 -0.2261 2.7127
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -3.65377 0.02934 -124.547 <2e-16 ***
typhoon_500 0.14073 0.16311 0.863 0.3883
typhoon_1000 0.20539 0.12575 1.633 0.1024
typhoon_2000 0.16059 0.07106 2.260 0.0238 *
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 14329 on 59184 degrees of freedom
Residual deviance: 14322 on 59181 degrees of freedom
(3866 observations deleted due to missingness)
AIC: 14330
Number of Fisher Scoring iterations: 6
odds1 <- exp(coef(fit1))
odds1
(Intercept) typhoon_500 typhoon_1000 typhoon_2000
0.02589334 1.15111673 1.22800815 1.17420736
m1 <- margins(fit1)
summary(m1)
# Cut makes a categorical variable out of a numerical variable using specified bins
df3$Super <- ifelse(df3$intensity_vmax * 1.852 > 185, 1, 0)
df3$Moderate <- ifelse(df3$intensity_vmax * 1.852 >= 88 &
df3$intensity_vmax * 1.852 < 185, 1, 0)
df3$Weak <- ifelse(df3$intensity_vmax * 1.852 >= 41 &
df3$intensity_vmax * 1.852 < 88, 1, 0)
df3$HK_intensity <- cut(df3$intensity_vmax * 1.852 ,c(-1,41, 62, 87, 117, 149, 999))
table(df3$HK_intensity)
(-1,41] (41,62] (62,87] (87,117] (117,149] (149,999]
3398 12039 12615 11527 2255 21141
fit2 <- glm(delayed ~ (typhoon_500 + typhoon_1000 + typhoon_2000) :
(Weak + Moderate + Super), data=df3,
family=binomial)
tidy(fit2)
m2 <- margins(fit2)
summary(m2) %>%
html_df()
factor |
AME |
SE |
z |
p |
lower |
upper |
Moderate |
0.0007378 |
0.0006713 |
1.0990530 |
0.2717449 |
-0.0005779 |
0.0020535 |
Super |
-0.0050241 |
0.0860163 |
-0.0584087 |
0.9534231 |
-0.1736129 |
0.1635647 |
typhoon_1000 |
0.0035473 |
0.0036186 |
0.9802921 |
0.3269420 |
-0.0035450 |
0.0106396 |
typhoon_2000 |
0.0039224 |
0.0017841 |
2.1985908 |
0.0279070 |
0.0004257 |
0.0074191 |
typhoon_500 |
-0.0440484 |
0.6803640 |
-0.0647424 |
0.9483791 |
-1.3775373 |
1.2894405 |
Weak |
0.0009975 |
0.0005154 |
1.9353011 |
0.0529534 |
-0.0000127 |
0.0020077 |
margins(fit2, at = list(Weak = c(1)),
variables = c("typhoon_500", "typhoon_1000", "typhoon_2000")) %>%
summary() %>%
html_df()
factor |
Weak |
AME |
SE |
z |
p |
lower |
upper |
typhoon_1000 |
1 |
0.0073057 |
0.0053682 |
1.360938 |
0.1735332 |
-0.0032157 |
0.0178271 |
typhoon_2000 |
1 |
0.0067051 |
0.0031225 |
2.147328 |
0.0317671 |
0.0005850 |
0.0128251 |
typhoon_500 |
1 |
-0.0458116 |
0.7052501 |
-0.064958 |
0.9482075 |
-1.4280764 |
1.3364531 |
margins(fit2, at = list(Moderate = c(1)),
variables = c("typhoon_500", "typhoon_1000", "typhoon_2000")) %>%
summary() %>%
html_df()
factor |
Moderate |
AME |
SE |
z |
p |
lower |
upper |
typhoon_1000 |
1 |
0.0059332 |
0.0078245 |
0.7582856 |
0.4482800 |
-0.0094025 |
0.0212688 |
typhoon_2000 |
1 |
0.0044871 |
0.0039453 |
1.1373050 |
0.2554108 |
-0.0032457 |
0.0122198 |
typhoon_500 |
1 |
-0.0311946 |
0.6847130 |
-0.0455586 |
0.9636620 |
-1.3732074 |
1.3108182 |
margins(fit2, at = list(Super = c(1)),
variables = c("typhoon_500", "typhoon_1000", "typhoon_2000")) %>%
summary() %>%
html_df()
factor |
Super |
AME |
SE |
z |
p |
lower |
upper |
typhoon_1000 |
1 |
0.0030638 |
0.0111295 |
0.2752891 |
0.7830941 |
-0.0187495 |
0.0248772 |
typhoon_2000 |
1 |
0.0102513 |
0.0041568 |
2.4661549 |
0.0136572 |
0.0021041 |
0.0183985 |
typhoon_500 |
1 |
-0.2241250 |
3.1608062 |
-0.0709076 |
0.9434713 |
-6.4191913 |
5.9709413 |
LS0tDQp0aXRsZTogIkNvZGUgZm9yIFNlc3Npb24gNCINCmF1dGhvcjogIkRyLiBSaWNoYXJkIE0uIENyb3dsZXkiDQpkYXRlOiAiIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rDQotLS0NCg0KTm90ZSB0aGF0IHRoZSBkaXJlY3RvcmllcyB1c2VkIHRvIHN0b3JlIGRhdGEgYXJlIGxpa2VseSBkaWZmZXJlbnQgb24geW91ciBjb21wdXRlciwgYW5kIHN1Y2ggcmVmZXJlbmNlcyB3aWxsIG5lZWQgdG8gYmUgY2hhbmdlZCBiZWZvcmUgdXNpbmcgYW55IHN1Y2ggY29kZS4NCg0KYGBge3IgaGVscGVycywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GfQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShicm9vbSkNCmh0bWxfZGYgPC0gZnVuY3Rpb24odGV4dCwgY29scz1OVUxMLCBjb2wxPUZBTFNFLCBmdWxsPUYpIHsNCiAgaWYoIWxlbmd0aChjb2xzKSkgew0KICAgIGNvbHM9Y29sbmFtZXModGV4dCkNCiAgfQ0KICBpZighY29sMSkgew0KICAgIGthYmxlKHRleHQsImh0bWwiLCBjb2wubmFtZXMgPSBjb2xzLCBhbGlnbiA9IGMoImwiLHJlcCgnYycsbGVuZ3RoKGNvbHMpLTEpKSkgJT4lDQogICAgICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsImhvdmVyIiksIGZ1bGxfd2lkdGg9ZnVsbCkNCiAgfSBlbHNlIHsNCiAgICBrYWJsZSh0ZXh0LCJodG1sIiwgY29sLm5hbWVzID0gY29scywgYWxpZ24gPSBjKCJsIixyZXAoJ2MnLGxlbmd0aChjb2xzKS0xKSkpICU+JQ0KICAgICAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCJob3ZlciIpLCBmdWxsX3dpZHRoPWZ1bGwpICU+JQ0KICAgICAgY29sdW1uX3NwZWMoMSxib2xkPVQpDQogIH0NCn0NCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUZ9DQojIEltcG9ydCBXYWxtYXJ0IGRhdGEgZnJvbSBTZXNzaW9uX1Byb2plY3QNCmxpYnJhcnkobHVicmlkYXRlKQ0KcHJlcHJvY2Vzc19kYXRhIDwtIGZ1bmN0aW9uKGRmKSB7DQogICMgTWVyZ2UgdGhlIGRhdGEgdG9nZXRoZXIgKFB1bGxlZCBmcm9tIG91dHNpZGUgb2YgZnVuY3Rpb24gLS0gInNjb3BpbmciKQ0KICBkZiA8LSBpbm5lcl9qb2luKGRmLCB3ZWVrbHkuc3RvcmVzKQ0KICBkZiA8LSBpbm5lcl9qb2luKGRmLCB3ZWVrbHkuZmVhdHVyZXNbLDE6MTFdKQ0KICANCiAgIyBDb21wcmVzcyB0aGUgd2VpcmQgbWFya2Rvd24gaW5mb3JtYXRpb24gdG8gMSB2YXJpYWJsZQ0KICBkZiRtYXJrZG93biA8LSAwDQogIGRmWyFpcy5uYShkZiRNYXJrRG93bjEpLF0kbWFya2Rvd24gPC0gZGZbIWlzLm5hKGRmJE1hcmtEb3duMSksXSRNYXJrRG93bjENCiAgZGZbIWlzLm5hKGRmJE1hcmtEb3duMiksXSRtYXJrZG93biA8LSBkZlshaXMubmEoZGYkTWFya0Rvd24yKSxdJE1hcmtEb3duMg0KICBkZlshaXMubmEoZGYkTWFya0Rvd24zKSxdJG1hcmtkb3duIDwtIGRmWyFpcy5uYShkZiRNYXJrRG93bjMpLF0kTWFya0Rvd24zDQogIGRmWyFpcy5uYShkZiRNYXJrRG93bjQpLF0kbWFya2Rvd24gPC0gZGZbIWlzLm5hKGRmJE1hcmtEb3duNCksXSRNYXJrRG93bjQNCiAgZGZbIWlzLm5hKGRmJE1hcmtEb3duNSksXSRtYXJrZG93biA8LSBkZlshaXMubmEoZGYkTWFya0Rvd241KSxdJE1hcmtEb3duNQ0KICANCiAgIyBGaXggZGF0ZXMgYW5kIGFkZCB1c2VmdWwgdGltZSB2YXJpYWJsZXMNCiAgZGYkZGF0ZSA8LSBhcy5EYXRlKGRmJERhdGUpDQogIGRmJHdlZWsgPC0gd2VlayhkZiRkYXRlKQ0KICBkZiR5ZWFyIDwtIHllYXIoZGYkZGF0ZSkNCiAgZGYkbW9udGggPC0gbW9udGgoZGYkZGF0ZSkNCiAgDQogIGRmDQp9DQoNCndlZWtseSA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9XTVRfdHJhaW4uY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkNCndlZWtseS5mZWF0dXJlcyA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9XTVRfZmVhdHVyZXMuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSkNCndlZWtseS5zdG9yZXMgPC0gcmVhZC5jc3YoIi4uLy4uL0RhdGEvV01UX3N0b3Jlcy5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQ0KZGYgPC0gcHJlcHJvY2Vzc19kYXRhKHdlZWtseSkNCiNkZiRpZCA8LSBkZiRTdG9yZSAqMTAwMDAgKyBkZiR3ZWVrICogMTAwICsgZGYkRGVwdA0KZGYgPC0gZGYgJT4lDQogIGdyb3VwX2J5KFN0b3JlLCBEZXB0KSAlPiUgDQogIG11dGF0ZShzdG9yZV9hdmc9bWVhbihXZWVrbHlfU2FsZXMsIHJtLm5hPVQpKSAlPiUNCiAgdW5ncm91cCgpDQpgYGANCg0KYGBge3J9DQojIENyZWF0ZSB0aGUgYmluYXJ5IHZhcmlhYmxlIGZyb20gV2FsbWFydCBzYWxlcyBkYXRhDQpkZiRkb3VibGUgPC0gaWZlbHNlKGRmJFdlZWtseV9TYWxlcyA+IGRmJHN0b3JlX2F2ZyoyLDEsMCkNCmZpdCA8LSBnbG0oZG91YmxlIH4gSXNIb2xpZGF5LCBkYXRhPWRmLCBmYW1pbHk9Ymlub21pYWwpDQp0aWR5KGZpdCkNCmBgYA0KDQpgYGB7cn0NCiMgQXV0b21hdGluZyB0aGUgYWJvdmU6DQpleHAoY29lZihmaXQpKQ0KYGBgDQoNCmBgYHtyfQ0KYygtMy40NCwgLTIuOSkNCmBgYA0KDQpgYGB7cn0NCmEgPC0gZXhwKC0zLjQ0KQ0KYiA8LSBleHAoLTIuOSkNCmMoYS8oMSthKSwgYi8oMStiKSkNCmBgYA0KDQpgYGB7cn0NCm1vZGVsMiA8LSBnbG0oZG91YmxlIH4gSXNIb2xpZGF5ICsgVGVtcGVyYXR1cmUgKyBGdWVsX1ByaWNlLCBkYXRhPWRmLCBmYW1pbHk9Ymlub21pYWwpDQpzdW1tYXJ5KG1vZGVsMikNCmBgYA0KDQpgYGB7cn0NCiMgT2Rkcw0KZXhwKGNvZWYobW9kZWwyKSkNCmBgYA0KDQpgYGB7cn0NCiMgVHlwaWNhbCBTZXB0ZW1iZXIgZGF5cw0KaGRheV9zZXAgICAgPC0gbWVhbihwcmVkaWN0KG1vZGVsMiwgZmlsdGVyKGRmLCBJc0hvbGlkYXksIG1vbnRoPT05KSwgdHlwZT0icmVzcG9uc2UiKSkNCm5vX2hkYXlfc2VwIDwtICBtZWFuKHByZWRpY3QobW9kZWwyLCBmaWx0ZXIoZGYsICFJc0hvbGlkYXksIG1vbnRoPT05KSwgdHlwZT0icmVzcG9uc2UiKSkNCiMgVHlwaWNhbCBEZWNlbWJlciBkYXlzDQpoZGF5X2RlYyAgICA8LSBtZWFuKHByZWRpY3QobW9kZWwyLCBmaWx0ZXIoZGYsIElzSG9saWRheSwgbW9udGg9PTEyKSwgdHlwZT0icmVzcG9uc2UiKSkNCm5vX2hkYXlfZGVjIDwtICBtZWFuKHByZWRpY3QobW9kZWwyLCBmaWx0ZXIoZGYsICFJc0hvbGlkYXksIG1vbnRoPT0xMiksIHR5cGU9InJlc3BvbnNlIikpDQoNCmh0bWxfZGYoZGF0YS5mcmFtZShNb250aD1jKDksOSwxMiwxMiksDQogICAgICAgICAgICAgICAgICAgSXNIb2xpZGF5PWMoRkFMU0UsVFJVRSxGQUxTRSxUUlVFKSwNCiAgICAgICAgICAgICAgICAgICBQcm9iYWJpbGl0eT1jKG5vX2hkYXlfc2VwLCBoZGF5X3NlcCwgbm9faGRheV9kZWMsIGhkYXlfZGVjKSkpDQpgYGANCg0KYGBge3IsIG1lc3NhZ2U9Rn0NCnBlcmNlbnQgPC0gZnVuY3Rpb24oeCkgew0KICBwYXN0ZTAocm91bmQoeCoxMDAsMiksIiUiKQ0KfQ0KbGlicmFyeShybGFuZykNCiMgQW5vdGhlciB1c2UgZm9yIGEgcXVvc3VyZSA6KQ0KIyBZb3UgY291bGQgYWx3YXlzIGp1c3QgdXNlIHF1b3RlZCBhcmd1ZW1lbnRzIGhlcmUgdGhvdWdoDQpleHRyYWN0X21hcmdpbiA8LSBmdW5jdGlvbihtLCB4KSB7DQogIHggPC0gZW5xdW8oeCkNCiAgcGVyY2VudChmaWx0ZXIoc3VtbWFyeShtKSwgZmFjdG9yPT1xdW9fdGV4dCh4KSkkQU1FKQ0KfQ0KYGBgDQoNCmBgYHtyLCBtZXNzYWdlPUZ9DQojIENhbGN1bGF0ZSBBTUUgbWFyZ2luYWwgZWZmZWN0cw0KbGlicmFyeShtYXJnaW5zKQ0KbSA8LSBtYXJnaW5zKG1vZGVsMikNCm0NCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnkobSkgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD02fQ0KcGxvdChtLCB3aGljaD1zdW1tYXJ5KG0pJGZhY3RvcikNCmBgYA0KDQpgYGB7cn0NCm1hcmdpbnMobW9kZWwyLCBhdCA9IGxpc3QoSXNIb2xpZGF5ID0gYyhUUlVFLCBGQUxTRSkpLA0KICAgICAgICB2YXJpYWJsZXMgPSBjKCJUZW1wZXJhdHVyZSIsICJGdWVsX1ByaWNlIikpICU+JQ0KICBzdW1tYXJ5KCkgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyfQ0KbWFyZ2lucyhtb2RlbDIsIGF0ID0gbGlzdChUZW1wZXJhdHVyZSA9IGMoMCwgMjAsIDQwLCA2MCwgODAsIDEwMCkpLA0KICAgICAgICB2YXJpYWJsZXMgPSBjKCJJc0hvbGlkYXkiKSkgJT4lDQogIHN1bW1hcnkoKSAlPiUNCiAgaHRtbF9kZigpDQpgYGANCg0KYGBge3J9DQoNCiMgbG9hZCBkYXRhDQpkZiA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9TaGlwcGluZy9zaGlwcGluZ19kYXRhc2V0LmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGLCBuYT0iTkEiKQ0KZGZfc2hpcHMgPC0gcmVhZC5jc3YoIi4uLy4uL0RhdGEvU2hpcHBpbmcvaW5kZXguY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEYsIG5hPSJOQSIpDQpkZl9wb3J0cyA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9TaGlwcGluZy9wb3J0X2RhdGFzZXQuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEYsIG5hPSJOQSIpDQp0eXBob29uIDwtIHJlYWQuY3N2KCIuLi8uLi9EYXRhL1NoaXBwaW5nL3R5cGhvb24uY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQoNCiMgcmVuYW1lIGxhdCBhbmQgbG9uIHRvIGF2b2lkIGNvbmZsaWN0cw0KZGZfcG9ydHMgPC0gZGZfcG9ydHMgJT4lIHJlbmFtZShwb3J0X2xhdD1sYXQsIHBvcnRfbG9uPWxvbikNCg0KIyBKb2luIGluIHNoaXBzIGFuZCBwb3J0cw0KZGYgPC0gbGVmdF9qb2luKGRmLCBkZl9zaGlwcykNCmRmIDwtIGRmICU+JSBmaWx0ZXIoIWltbyA9PSA5NTI4MDI5IHwgcmVwb3J0ZWRfZGVzdGluYXRpb24gPT0gIkFVIEFETCIpDQpkZiA8LSBsZWZ0X2pvaW4oZGYsZGZfcG9ydHMsIGJ5PWMoImxlZnRfcG9ydCI9InBvcnQiKSkNCmRmIDwtIGRmICU+JSByZW5hbWUobGVmdF9sYXQ9cG9ydF9sYXQsIGxlZnRfbG9uPXBvcnRfbG9uKQ0KZGYgPC0gbGVmdF9qb2luKGRmLGRmX3BvcnRzLCBieT1jKCJhcnJpdmVfcG9ydCI9InBvcnQiKSkNCmRmIDwtIGRmICU+JSByZW5hbWUoYXJyaXZlX2xhdD1wb3J0X2xhdCwgYXJyaXZlX2xvbj1wb3J0X2xvbikNCg0KIyBmaXggZGF0ZXMNCmRmJGxhc3RfdXBkYXRlID0gYXMuUE9TSVhjdChkZiRsYXN0X3VwZGF0ZSwgdHo9IlVUQyIsb3JpZ2luPSIxOTcwLTAxLTAxIikNCmRmJGxlZnRfdGltZSA9IGFzLlBPU0lYY3QoZGYkbGVmdF90aW1lLCB0ej0iVVRDIixvcmlnaW49IjE5NzAtMDEtMDEiKQ0KZGYkYXJyaXZlX3RpbWUgPSBhcy5QT1NJWGN0KGRmJGFycml2ZV90aW1lLCB0ej0iVVRDIixvcmlnaW49IjE5NzAtMDEtMDEiKQ0KDQojIEZpeCB0eXBob29uIGRhdGVzDQp0eXBob29uJGRhdGUgPC0gYXMuUE9TSVhjdChwYXN0ZSh0eXBob29uJGRhdGUsdHlwaG9vbiR0aW1lKSwgZm9ybWF0PSIlWSViJWQgJUglTSVTIiwgdHo9IlVUQyIsIG9yaWdpbj0iMTk3MC0wMS0wMSIpDQojIEZpeCB0eXBob29uIGxvbmdpdHVkZSAtLSB0aGV5IGhhdmUgbG9uICogLTEgaW4gdGhlIGRhdGENCnR5cGhvb24kbG9uIDwtIHR5cGhvb24kbG9uICogLTENCg0KdHlwaG9vbl9hbGwgPC0gdHlwaG9vbg0KIyBmaWx0ZXIgdG8gZGF0ZXMgaW4gc2FtcGxlDQp0eXBob29uIDwtIHR5cGhvb24gJT4lIGZpbHRlcihkYXRlID4gYXMuUE9TSVhjdCgiMjAxOC0wOC0zMCIsIHR6PSJVVEMiKSkNCg0KZGYgPC0gZGYgJT4lIHJlbmFtZShmcmFtZT1ydW4pDQoNCmRmX2FsbCA8LSBkZltkZiRmcmFtZSAhPSAzMixdDQpkZiA8LSBkZiAlPiUgZmlsdGVyKGxhc3RfdXBkYXRlID4gYXMuUE9TSVhjdCgiMjAxOC0wOC0zMCIsIHR6PSJVVEMiKSkNCg0KbGlicmFyeShwbG90bHkpICAjIG5lZWRlZCBmb3IgdGhlIHRvUkdCKCkgZnVuY3Rpb24NCmdlbyA8LSBsaXN0KA0KICBzaG93bGFuZCA9IFRSVUUsDQogIHNob3dsYWtlcyA9IFRSVUUsDQogIHNob3djb3VudHJpZXMgPSBUUlVFLA0KICBzaG93b2NlYW4gPSBUUlVFLA0KICBjb3VudHJ5d2lkdGggPSAwLjUsDQogIGxhbmRjb2xvciA9IHRvUkdCKCJncmV5OTAiKSwNCiAgbGFrZWNvbG9yID0gdG9SR0IoImFsaWNlYmx1ZSIpLA0KICBvY2VhbmNvbG9yID0gdG9SR0IoImFsaWNlYmx1ZSIpLA0KICBwcm9qZWN0aW9uID0gbGlzdCgNCiAgICB0eXBlID0gJ29ydGhvZ3JhcGhpYycsICAjIGRldGFpbGVkIGF0IGh0dHBzOi8vcGxvdC5seS9yL3JlZmVyZW5jZS8jbGF5b3V0LWdlby1wcm9qZWN0aW9uDQogICAgcm90YXRpb24gPSBsaXN0KA0KICAgICAgbG9uID0gMTAwLA0KICAgICAgbGF0ID0gMSwNCiAgICAgIHJvbGwgPSAwDQogICAgKQ0KICApLA0KICBsb25heGlzID0gbGlzdCgNCiAgICBzaG93Z3JpZCA9IFRSVUUsDQogICAgZ3JpZGNvbG9yID0gdG9SR0IoImdyYXk0MCIpLA0KICAgIGdyaWR3aWR0aCA9IDAuNQ0KICApLA0KICBsYXRheGlzID0gbGlzdCgNCiAgICBzaG93Z3JpZCA9IFRSVUUsDQogICAgZ3JpZGNvbG9yID0gdG9SR0IoImdyYXk0MCIpLA0KICAgIGdyaWR3aWR0aCA9IDAuNQ0KICApDQopDQoNCnR5cGhvb25fQXVnMzEgPC0gdHlwaG9vblt0eXBob29uJGRhdGUgPiBhcy5QT1NJWGN0KCIyMDE4LTA4LTMxIDAwOjAwOjAwIiwgdHo9IlVUQyIpICYgdHlwaG9vbiRkYXRlIDwgYXMuUE9TSVhjdCgiMjAxOC0wOS0wMSAwMDowMDowMCIsIHR6PSJVVEMiKSxdDQp0eXBob29uX0plYmkgPC0gdHlwaG9vbl9hbGxbdHlwaG9vbl9hbGwkdHlwaG9vbl9uYW1lID09ICJKRUJJX1kiLF0NCmRmX0F1ZzMxIDwtIGRmW2RmJGZyYW1lID09IDEsXQ0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUYsIG1lc3NhZ2U9RiwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9OH0NCmxpYnJhcnkocGxvdGx5KSAgIyBmb3IgcGxvdHRpbmcNCmxpYnJhcnkoUkNvbG9yQnJld2VyKSAgIyBmb3IgY29sb3JzDQojIHBsb3Qgd2l0aCBib2F0cywgcG9ydHMsIGFuZCB0eXBob29ucw0KIyBOb3RlOiBnZW8gaXMgZGVmaW5lZCBpbiB0aGUgYXBwZW5kaXggLS0gaXQgY29udHJvbHMgbGF5b3V0DQpwYWxldHRlID0gYnJld2VyLnBhbCg4LCAiRGFyazIiKVtjKDEsOCwzLDIpXQ0KcCA8LSBwbG90X2dlbyhjb2xvcnM9cGFsZXR0ZSkgJT4lDQogIGFkZF9tYXJrZXJzKGRhdGE9ZGZfcG9ydHMsIHggPSB+cG9ydF9sb24sIHkgPSB+cG9ydF9sYXQsIGNvbG9yID0gIlBvcnQiKSAlPiUNCiAgYWRkX21hcmtlcnMoZGF0YT1kZl9BdWczMSwgeCA9IH5sb24sIHkgPSB+bGF0LCBjb2xvciA9IH5zaGlwX3R5cGUsDQogICAgICAgICAgICAgIHRleHQ9fnBhc3RlKCdTaGlwIG5hbWUnLHNoaXBuYW1lKSkgJT4lDQogIGFkZF9tYXJrZXJzKGRhdGE9dHlwaG9vbl9BdWczMSwgeCA9IH5sb24sIHkgPSB+bGF0LCBjb2xvcj0iVFlQSE9PTiIsDQogICAgICAgICAgICAgIHRleHQ9fnBhc3RlKCJOYW1lIiwgdHlwaG9vbl9uYW1lKSkgJT4lDQogICBsYXlvdXQoc2hvd2xlZ2VuZCA9IFRSVUUsIGdlbyA9IGdlbywNCiAgICB0aXRsZSA9ICdTaW5nYXBvcmVhbiBvd25lZCBjb250YWluZXIgYW5kIHRhbmtlciBzaGlwcywgQXVndXN0IDMxLCAyMDE4JykNCnANCmBgYA0KDQpgYGB7ciwgd2FybmluZz1GLCBtZXNzYWdlPUYsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTh9DQojIHBsb3Qgd2l0aCBib2F0cyBhbmQgdHlwaG9vbnMNCnBhbGV0dGUgPSBicmV3ZXIucGFsKDgsICJEYXJrMiIpW2MoMSwzLDIpXQ0KcCA8LSBwbG90X2dlbyhjb2xvcnM9cGFsZXR0ZSkgJT4lDQogIGFkZF9tYXJrZXJzKGRhdGE9ZGZfYWxsW2RmX2FsbCRmcmFtZSA9PSAxNCxdLCB4ID0gfmxvbiwgeSA9IH5sYXQsIGNvbG9yID0gfnNoaXBfdHlwZSwNCiAgICAgICAgICAgICAgdGV4dD1+cGFzdGUoJ1NoaXAgbmFtZScsc2hpcG5hbWUpKSAlPiUNCiAgYWRkX21hcmtlcnMoZGF0YT10eXBob29uX0plYmksIHggPSB+bG9uLCB5ID0gfmxhdCwgY29sb3I9IlR5cGhvb24gSmViaSIsIHRleHQ9fnBhc3RlKCJOYW1lIiwgdHlwaG9vbl9uYW1lLCAiPC9icj5UaW1lOiAiLCBkYXRlKSkgJT4lDQogICBsYXlvdXQoDQogICAgc2hvd2xlZ2VuZCA9IFRSVUUsIGdlbyA9IGdlbywNCiAgICB0aXRsZSA9ICdTaW5nYXBvcmVhbiBjb250YWluZXIvdGFua2VyIHNoaXBzLCBTZXB0ZW1iZXIgNCwgMjAxOCwgZXZlbmluZycpDQpwDQpgYGANCg0KYGBge3IsIHdhcm5pbmc9RiwgbWVzc2FnZT1GLCBmaWcuaGVpZ2h0PTUsIGZpZy53aWR0aD05fQ0KbGlicmFyeShsZWFmbGV0KQ0KbGlicmFyeShsZWFmbGV0LmV4dHJhcykNCg0KIyB0eXBob29uIGljb25zDQppY29ucyA8LSBwdWxzZUljb25zKGNvbG9yPSdyZWQnLA0KICBoZWFydGJlYXQgPSBpZmVsc2UodHlwaG9vbl9KZWJpJGludGVuc2l0eV92bWF4ID4gMTUwLzEuODUyLCAwLjgsDQogICAgaWZlbHNlKHR5cGhvb24kaW50ZW5zaXR5X3ZtYXggPCAxMTgvMS44NTIsIDEuNiwgMS4yKSksDQogIGljb25TaXplPWlmZWxzZSh0eXBob29uX0plYmkkaW50ZW5zaXR5X3ZtYXggPiAxNTAvMS44NTIsIDEyLA0KICAgIGlmZWxzZSh0eXBob29uX0plYmkkaW50ZW5zaXR5X3ZtYXggPCAxMTgvMS44NTIsIDMsIDgpKSkNCg0KIyBzaGlwIGljb25zDQpzaGlwaWNvbnMgPC0gaWNvbkxpc3QoDQogIHNoaXAgPSBtYWtlSWNvbigiLi4vRmlndXJlcy9zaGlwLnBuZyIsIE5VTEwsIDE4LCAxOCkNCikNCmxlYWZsZXQoKSAlPiUNCiAgYWRkVGlsZXMoKSAlPiUgDQogIHNldFZpZXcobG5nID0gMTM2LCBsYXQgPSAzNCwgem9vbT00KSAlPiUNCiAgYWRkUHVsc2VNYXJrZXJzKGRhdGE9dHlwaG9vbl9KZWJpW3NlcSgxLG5yb3codHlwaG9vbl9KZWJpKSw1KSxdLCBsbmc9fmxvbiwNCiAgICAgICAgICAgICAgICAgIGxhdD1+bGF0LCBsYWJlbD1+ZGF0ZSwgaWNvbj1pY29ucykgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YT10eXBob29uX0plYmlbdHlwaG9vbl9KZWJpJGludGVuc2l0eV92bWF4ID4gMTUwLzEuODUyLF0sDQogICAgbG5nPX5sb24sIGxhdD1+bGF0LHN0cm9rZSA9IFRSVUUsIHJhZGl1cz0xMiwgY29sb3I9InJlZCIsIGxhYmVsPX5kYXRlKSAlPiUNCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhPXR5cGhvb25fSmViaVt0eXBob29uX0plYmkkaW50ZW5zaXR5X3ZtYXggPD0gMTUwLzEuODUyICYNCiAgICB0eXBob29uX0plYmkkaW50ZW5zaXR5X3ZtYXggPiAxMTgvMS44NTIsXSwgbG5nPX5sb24sIGxhdD1+bGF0LA0KICAgIHN0cm9rZSA9IFRSVUUsIHJhZGl1cz0xMiwgY29sb3I9InJlZCIsIGxhYmVsPX5kYXRlKSAlPiUNCiAgYWRkQ2lyY2xlTWFya2VycyhkYXRhPXR5cGhvb25fSmViaVt0eXBob29uX0plYmkkaW50ZW5zaXR5X3ZtYXggPD0xMTgvMS44NTIsXSwNCiAgICBsbmc9fmxvbiwgbGF0PX5sYXQsIHN0cm9rZSA9IFRSVUUsIHJhZGl1cz0xMiwgY29sb3I9InJlZCIsIGxhYmVsPX5kYXRlKSAlPiUNCiAgYWRkTWFya2VycyhkYXRhPWRmX2FsbFtkZl9hbGwkZnJhbWUgPT0gMTQsXSwgbG5nPX5sb24sIGxhdD1+bGF0LA0KICAgICAgICAgICAgIGxhYmVsPX5zaGlwbmFtZSwgaWNvbj1zaGlwaWNvbnMpDQpgYGANCg0KYGBge3J9DQojIExvYWQgZGF0YXMNCmRmMyA8LSByZWFkLmNzdigiLi4vLi4vRGF0YS9TaGlwcGluZy9jb21iaW5lZC5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRikNCg0KbGlicmFyeShnZW9zcGhlcmUpDQoNCiMgQ2FsY3VsYXRlIGRpc3RhbmNlIHRvIGFuZCBmcm9tIHBvcnRzDQpkZjMkZGlzdF90b3BvcnQgPC0gZGlzdFZpbmNlbnR5RWxsaXBzb2lkKGFzLm1hdHJpeChkZjNbLGMoImxvbiIsImxhdCIpXSksIGFzLm1hdHJpeChkZjNbLGMoImFycml2ZV9sb24iLCJhcnJpdmVfbGF0IildKSkNCmRmMyRkaXN0X2Zyb21wb3J0IDwtIGRpc3RWaW5jZW50eUVsbGlwc29pZChhcy5tYXRyaXgoZGYzWyxjKCJsb24iLCJsYXQiKV0pLCBhcy5tYXRyaXgoZGYzWyxjKCJsZWZ0X2xvbiIsImxlZnRfbGF0IildKSkNCmRmMyA8LSBkZjMgJT4lDQogIGFycmFuZ2UoaW1vLCBsYXN0X3VwZGF0ZSkgJT4lDQogIGdyb3VwX2J5KGltbykgJT4lDQogIG11dGF0ZShkZWxheWVkPWlmZWxzZShkaWZmdGltZShhcnJpdmVfdGltZSwgbGVhZChhcnJpdmVfdGltZSksIHVuaXRzPSJkYXlzIikgPiAxLzggJiBhcnJpdmVfdGltZSA+IGxhc3RfdXBkYXRlICYgbGVmdF9wb3J0ID09IGxlYWQobGVmdF9wb3J0KSwxLDApKSAlPiUNCiAgdW5ncm91cCgpDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KGdlb3NwaGVyZSkNCnggPC0gYXMubWF0cml4KGRmM1ssYygibG9uIiwibGF0IildKSAgIyBzaGlwIGxvY2F0aW9uDQp5IDwtIGFzLm1hdHJpeChkZjNbLGMoInR5X2xvbiIsInR5X2xhdCIpXSkgIyB0eXBob29uIGxvY2F0aW9uDQoNCmRmMyRkaXN0X3R5cGhvb24gPC0gZGlzdFZpbmNlbnR5RWxsaXBzb2lkKHgsIHkpIC8gMTAwMA0KYGBgDQoNCmBgYHtyfQ0KZGYzJHR5cGhvb25fNTAwID0gaWZlbHNlKGRmMyRkaXN0X3R5cGhvb24gPCA1MDAgJiANCiAgICAgICAgICAgICAgICAgICAgICAgICBkZjMkZGlzdF90eXBob29uID49IDAsIDEsIDApDQpkZjMkdHlwaG9vbl8xMDAwID0gaWZlbHNlKGRmMyRkaXN0X3R5cGhvb24gPCAxMDAwICYNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZGYzJGRpc3RfdHlwaG9vbiA+PSA1MDAsIDEsIDApDQpkZjMkdHlwaG9vbl8yMDAwID0gaWZlbHNlKGRmMyRkaXN0X3R5cGhvb24gPCAyMDAwICYNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZGYzJGRpc3RfdHlwaG9vbiA+PSAxMDAwLCAxLCAwKQ0KYGBgDQoNCmBgYHtyfQ0KZml0MSA8LSBnbG0oZGVsYXllZCB+IHR5cGhvb25fNTAwICsgdHlwaG9vbl8xMDAwICsgdHlwaG9vbl8yMDAwLCBkYXRhPWRmMywNCiAgICAgICAgICAgIGZhbWlseT1iaW5vbWlhbCkNCnN1bW1hcnkoZml0MSkNCmBgYA0KDQpgYGB7cn0NCm9kZHMxIDwtIGV4cChjb2VmKGZpdDEpKQ0Kb2RkczENCmBgYA0KDQpgYGB7cn0NCm0xIDwtIG1hcmdpbnMoZml0MSkNCnN1bW1hcnkobTEpDQpgYGANCg0KYGBge3J9DQojIEN1dCBtYWtlcyBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlIG91dCBvZiBhIG51bWVyaWNhbCB2YXJpYWJsZSB1c2luZyBzcGVjaWZpZWQgYmlucw0KZGYzJFN1cGVyIDwtIGlmZWxzZShkZjMkaW50ZW5zaXR5X3ZtYXggKiAxLjg1MiA+IDE4NSwgMSwgMCkNCmRmMyRNb2RlcmF0ZSA8LSBpZmVsc2UoZGYzJGludGVuc2l0eV92bWF4ICogMS44NTIgPj0gODggJg0KICAgICAgICAgICAgICAgICAgICAgICAgIGRmMyRpbnRlbnNpdHlfdm1heCAqIDEuODUyIDwgMTg1LCAxLCAwKQ0KZGYzJFdlYWsgPC0gaWZlbHNlKGRmMyRpbnRlbnNpdHlfdm1heCAqIDEuODUyID49IDQxICYgDQogICAgICAgICAgICAgICAgICAgZGYzJGludGVuc2l0eV92bWF4ICogMS44NTIgPCA4OCwgMSwgMCkNCmRmMyRIS19pbnRlbnNpdHkgPC0gY3V0KGRmMyRpbnRlbnNpdHlfdm1heCAqIDEuODUyICxjKC0xLDQxLCA2MiwgODcsIDExNywgMTQ5LCA5OTkpKQ0KdGFibGUoZGYzJEhLX2ludGVuc2l0eSkNCmBgYA0KDQpgYGB7cn0NCmZpdDIgPC0gZ2xtKGRlbGF5ZWQgfiAodHlwaG9vbl81MDAgKyB0eXBob29uXzEwMDAgKyB0eXBob29uXzIwMDApIDogDQogICAgICAgICAgICAgIChXZWFrICsgTW9kZXJhdGUgKyBTdXBlciksIGRhdGE9ZGYzLA0KICAgICAgICAgICAgZmFtaWx5PWJpbm9taWFsKQ0KdGlkeShmaXQyKQ0KYGBgDQoNCmBgYHtyfQ0KbTIgPC0gbWFyZ2lucyhmaXQyKQ0Kc3VtbWFyeShtMikgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyfQ0KbWFyZ2lucyhmaXQyLCBhdCA9IGxpc3QoV2VhayA9IGMoMSkpLA0KICAgICAgICB2YXJpYWJsZXMgPSBjKCJ0eXBob29uXzUwMCIsICJ0eXBob29uXzEwMDAiLCAidHlwaG9vbl8yMDAwIikpICU+JQ0KICBzdW1tYXJ5KCkgJT4lDQogIGh0bWxfZGYoKQ0KYGBgDQoNCmBgYHtyfQ0KbWFyZ2lucyhmaXQyLCBhdCA9IGxpc3QoTW9kZXJhdGUgPSBjKDEpKSwNCiAgICAgICAgdmFyaWFibGVzID0gYygidHlwaG9vbl81MDAiLCAidHlwaG9vbl8xMDAwIiwgInR5cGhvb25fMjAwMCIpKSAlPiUNCiAgc3VtbWFyeSgpICU+JQ0KICBodG1sX2RmKCkNCmBgYA0KDQpgYGB7cn0NCm1hcmdpbnMoZml0MiwgYXQgPSBsaXN0KFN1cGVyID0gYygxKSksDQogICAgICAgIHZhcmlhYmxlcyA9IGMoInR5cGhvb25fNTAwIiwgInR5cGhvb25fMTAwMCIsICJ0eXBob29uXzIwMDAiKSkgJT4lDQogIHN1bW1hcnkoKSAlPiUNCiAgaHRtbF9kZigpDQpgYGANCg0K