1.Intro

Fixed-site public bicycles, while providing convenience to citizens on one hand, also face a shortage of vehicles at the docks or a surplus of vehicles. In this spatial analysis, we use open data from Philadelphia’s IndegoBike to predict the demand for bicycle sharing at all docking points to ensure that bicycles or docking points are available when needed.

2.Model Construction

2.1 Data Selection

As a cycling friendly city, Philadelphia’s busy public transportation makes the best sample for studying public bike scheduling. The data for this study uses IndegoBike open data from May and June, and from these data 20-24 weeks were selected for subsequent analysis.

library(tidyverse)
library(sf)
library(lubridate)
library(tigris)
library(tidycensus)
library(viridis)
library(riem)
library(gridExtra)
library(knitr)
library(kableExtra)
library(RSocrata)
library(ggplot2)
library(osmdata)
library(gganimate)
library(FNN)
library(grid)
library(rjson)
library(gifski)

options(tigris_class = "sf")
root.dir =
paste0("https://raw.githubusercontent.com/urbanSpatial",
"/Public-Policy-Analytics-Landing/master/DATA/")
source(
paste0("https://raw.githubusercontent.com/urbanSpatial/",
"Public-Policy-Analytics-Landing/master/functions.r"))

palette5 <- c("#f9b294","#f2727f","#c06c86","#6d5c7e","#315d7f")
palette6 <- c("#eff3ff","#bdd7e7","#6baed6","#3182bd","#08519c")
palette4 <- c("#f9b294","#f2727f","#c06c86","#6d5c7e")
palette2 <- c("#f9b294","#f2727f")
palette1_main <- "#F2727F"
palette1_assist <- '#F9B294'

mapTheme <- function(base_size = 35, title_size = 45) {
  theme(
    text = element_text( color = "black"),
    plot.title = element_text(hjust = 0.5, size = title_size, colour = "black",face = "bold"), 
    plot.subtitle = element_text(hjust = 0.5,size=base_size,face="italic"),
    plot.caption = element_text(size=base_size,hjust=0),
    axis.ticks = element_blank(),
    panel.spacing = unit(6, 'lines'),
    panel.background = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_blank(),
    strip.background = element_rect(fill = "grey80", color = "white"),
    strip.text = element_text(size=base_size),
    axis.title = element_text(face = "bold",size=base_size),
    axis.text = element_blank(),
    plot.background = element_blank(),
    legend.background = element_blank(),
    legend.title = element_text(size=base_size,colour = "black", face = "italic"),
    legend.text = element_text(size=base_size,colour = "black", face = "italic"),
    strip.text.x = element_text(size = base_size,face = "bold")
  )
}

plotTheme <- function(base_size = 35, title_size = 45) {
  theme(
    text = element_text( color = "black"),
    plot.title = element_text(hjust = 0.5, size = title_size, colour = "black",face = "bold"), 
    plot.subtitle = element_text(hjust = 0.5,size=base_size,face="italic"),
    plot.caption = element_text(size=base_size,hjust=0),
    axis.ticks = element_blank(),
    panel.spacing = unit(6, 'lines'),
    panel.background = element_blank(),
    panel.grid.major = element_line("grey80", size = 0.1),
    panel.grid.minor = element_blank(),
    panel.border = element_rect(colour = "black", fill=NA, size=6),
    strip.background = element_rect(fill = "grey80", color = "white"),
    strip.text = element_text(size=base_size),
    axis.title = element_text(face = "bold",size=base_size),
    axis.text = element_text(size=base_size),
    plot.background = element_blank(),
    legend.background = element_blank(),
    legend.title = element_text(size=base_size,colour = "black", face = "italic"),
    legend.text = element_text(size=base_size,colour = "black", face = "italic"),
    strip.text.x = element_text(size = base_size,face = "bold")
  )
}

Download the data of season 2 from the IndegoBike website, we can use the substr function to extract the first 12 characters of the string “started_at”. For example, “5-01-2022 21:08:07” will be transfer to “5-01-2022 21”. Then we can use the mdy_h function to caculate the week and the time.

rideRaw <- read.csv("D:/Upenn/Upenn Lec/05-MUSA-508/Assign-06/HW/indego-trips-2022-q2/indego-trips-2022-q2.csv")
ride <- rideRaw %>%
  
  mutate(interval_hour =mdy_h(substr(start_time, 1, 12)),
         week = week(interval_hour),
         dayotw = wday(interval_hour, label=TRUE),
         start_station_name = as.character(start_station),
         end_station_name = as.character(end_station))%>%
  na.omit() %>%
  filter(week %in% c(20:24))
census_api_key("155cc525674a0d27c98afbb7030d0802e9bb5543", overwrite = TRUE)
PHICensus <- 
  get_acs(geography = "tract", 
          variables = c("B01003_001", "B19013_001", 
                        "B02001_002", "B08013_001",
                        "B08012_001", "B08301_001", 
                        "B08301_010", "B01002_001"), 
          year = 2017, 
          state = "PA", 
          geometry = TRUE, 
          county=c("Philadelphia"),
          output = "wide") %>%
  rename(Total_Pop =  B01003_001E,
         Med_Inc = B19013_001E,
         Med_Age = B01002_001E,
         White_Pop = B02001_002E,
         Travel_Time = B08013_001E,
         Num_Commuters = B08012_001E,
         Means_of_Transport = B08301_001E,
         Total_Public_Trans = B08301_010E) %>%
  select(Total_Pop, Med_Inc, White_Pop, Travel_Time,
         Means_of_Transport, Total_Public_Trans,
         Med_Age,
         GEOID, geometry) %>%
  mutate(Percent_White = White_Pop / Total_Pop,
         Mean_Commute_Time = Travel_Time / Total_Public_Trans,
         Percent_Taking_Public_Trans = Total_Public_Trans / Means_of_Transport)

PHITracts <- 
  PHICensus %>%
  as.data.frame() %>%
  distinct(GEOID, .keep_all = TRUE) %>%
  select(GEOID, geometry) %>% 
  st_sf

CensusData <- 
  get_acs(geography = "tract", 
          variables = c("B01003_001E"), 
          year=2019, state="PA", county="Philadelphia",geometry = T, output="wide") %>%
  st_transform(st_crs(PHITracts)) %>%
  rename(Census_TotalPop = B01003_001E) %>%
  dplyr::select(-NAME,-starts_with("B")) %>%
  mutate(Census_areasqm = as.numeric(st_area(.)),
         Census_PopDensity = Census_TotalPop/Census_areasqm) %>%
  dplyr::select(-Census_TotalPop,-Census_areasqm)%>%
  replace(is.na(.),0)

##st_write(CensusData, "CensusData.geojson")
ride_rsch <- ride%>%
  group_by(start_station_name)%>%
  summarize(lat=first(start_lat),lng=first(start_lon))

PHITracts <-
  tigris::tracts(state = "PA", county = "Philadelphia") %>%
  dplyr::select(GEOID)%>%
  filter(GEOID!="36061000100")


ride2_sf = st_as_sf(ride_rsch, coords = c("lng", "lat"),
                 crs = st_crs(PHITracts), agr = "constant")

ride_station <- st_join(ride2_sf,PHITracts,left=TRUE)%>%na.omit()
ride_tract <- st_join(PHITracts,ride2_sf,left=TRUE)%>%na.omit()

station_list <- ride_station[["start_station_name"]]

ride3 <- ride %>%
  filter(start_station_name %in% station_list,end_station_name %in% station_list)

Using the data obtained from the above steps, we can get the location information of all Indego stations in the Philadelphia.

ggplot()+
  geom_sf(data=st_union(PHITracts),color="black",size=15,fill = "transparent")+
  geom_sf(data=PHITracts,color="black",size=0.5,linetype ="dashed",fill = "transparent")+
  geom_sf(data=ride_station,size=4,color=palette1_main)+
  labs(title = "Map of Share Bike Stations, Philadelphia", 
       subtitle = "Red dots are stations")+
  mapTheme()

study.panel <-expand.grid(
  interval_hour = unique(ride3$interval_hour),
  start_station_name =
    unique(ride3$start_station_name))

ride3.panel <-ride3 %>%
  mutate(Trip_Counter = 1) %>%
  group_by(interval_hour, start_station_name) %>%
  summarize(Trip_Count = sum(Trip_Counter, na.rm=T)) %>%
  right_join(study.panel) %>%
  replace(is.na(.),0) %>%
  left_join(ride_station,by = "start_station_name") %>%
  mutate(week = week(interval_hour),dayotw = wday(interval_hour, label = TRUE)) %>%
  st_sf()
weather.Data <-
  riem_measures(station = "PHL", date_start = "2022-05-01",
                date_end = "2022-07-01")

weather.Panel <-
  weather.Data %>%
  mutate_if(is.character,
            list(~replace(as.character(.), is.na(.), "0"))) %>%
  replace(is.na(.), 0) %>%
  mutate(interval_hour = ymd_h(substr(valid, 1, 13))) %>%
  mutate(week = week(interval_hour),
         dayotw = wday(interval_hour, label=TRUE)) %>%
  filter(week %in% c(20:24))%>%
  group_by(interval_hour,week,dayotw) %>%
  summarize(Temp = max(tmpf),
            Precip = sum(p01i),
            Wind = max(sknt)) %>%
  mutate(Temp = ifelse(Temp == 0, 42, Temp))%>%
  replace(is.na(.),0)

2.2 Weather Feature

After importing the bicycle data, we can continue to introduce some features related to bicycle usage for analysis. The first is the weather feature, which we can easily understand from life experience that people use bicycles less frequently in rainy days due to safety concerns. The usage rate is also lower in low temperatures. Therefore, we introduce three weather features: precipitation, wind and temperature, to analyze the correlation between weather and bicycle use.

grid.arrange(
ggplot(weather.Panel, aes(interval_hour,Precip)) + 
  geom_line(color=palette1_main,size=2)+
  labs(x="",
       title = "Weather Data - PHI - Week 20 - Week 24, 2022")+
  plotTheme(),
ggplot(weather.Panel, aes(interval_hour,Wind)) + 
  geom_line(color=palette1_main,size=2)+
  labs(x="",
       title = "")+
  plotTheme(),
ggplot(weather.Panel, aes(interval_hour,Temp)) + 
  geom_line(color=palette1_main,size=2)+
  labs(x="Time",
       title = "")+
  plotTheme())

2.3 Population Feature

Similarly, the population base is also related to the proximity of public bike stations to a significant degree. Densely populated neighborhoods may require more frequent dispatching, which should also be taken into account.

ggplot()+
  geom_sf(data=st_union(PHITracts),color="black",size=10,fill = "transparent")+
  geom_sf(data=CensusData,aes(fill=q5(Census_PopDensity)))+
  geom_sf(data=ride_station,color="red")+
  scale_fill_manual(values = palette5,
                    labels = qBr(CensusData, "Census_PopDensity"),
                    name = "Per1000sqm")+
  labs(title = "Map of Population Density, Philadelphia", 
       subtitle = "Population Per 1000sqm")+
  mapTheme()

ride4 <-ride3.panel%>%
  left_join(weather.Panel%>%dplyr::select(-dayotw), by = "interval_hour")%>%
  rename("week"="week.x")%>%
  dplyr::select(-week.y)

3. Data Exploration

In this section, we will analyze the data for the previously screened five weeks.

3.1 Splitting data sets

Here, we use the data of the first three weeks (20-24) as the data of the training set and the data of the last two weeks (20-24) as the data of the test set.

ride5 <-
  ride4 %>%
  arrange(start_station_name, interval_hour) %>%
  group_by(start_station_name) %>%
  mutate(lagHour = dplyr::lag(Trip_Count,1),
         lag2Hours = dplyr::lag(Trip_Count,2),
         lag3Hours = dplyr::lag(Trip_Count,3),
         lag4Hours = dplyr::lag(Trip_Count,4),
         lag12Hours = dplyr::lag(Trip_Count,12),
         lag1day = dplyr::lag(Trip_Count,24)) %>%
ungroup()
ride5 <-ride5%>%
  left_join(CensusData%>%st_drop_geometry()%>%dplyr::select(GEOID,Census_PopDensity), by = "GEOID")%>%
  mutate(isPrecip = ifelse(Precip > 0,"Rain/Snow", "None"))
ride.Train <- filter(ride5, week < 23)
ride.Test <- filter(ride5, week >= 23)
ride.cross <- ride5

3.2 Time Lag

Time Lag implies that the cycling situation at some point in the future is correlated with the cycling situation at some time in the past. From the results, most weeks show very comparable time series patterns with consistent peaks and valleys. This indicates the existence of serial correlation.

Fridays <-
  mutate(ride5,friday = ifelse(dayotw == "Fri" & hour(interval_hour) == 1,interval_hour, 0)) %>%
  filter(friday != 0)
rbind(
  mutate(ride.Train, Legend = "Training"),
  mutate(ride.Test, Legend = "Testing")) %>%
  group_by(Legend, interval_hour) %>%
  summarize(Trip_Count = sum(Trip_Count)) %>%
  ungroup() %>%
ggplot(aes(interval_hour, Trip_Count, colour = Legend)) +
  geom_line(size=2) +
  scale_colour_manual(values = palette2) +
  geom_vline(data = Fridays, aes(xintercept = interval_hour),linetype = "dashed",size=1) +
  labs(
    title="Rideshare trips by week: week20- week24",
    subtitle="Dashed lines for every Friday",
    x="Day", y="Trip Count") +
  plotTheme()+ theme(panel.grid.major = element_blank())

3.3 Spatial Autocorrelation

Spatial autocorrelation means that the bicycle situation at a point is correlated with the surrounding bicycle situation. In this figure, we can see constant spatial clustering on a weekly basis. The higher frequency of use occurs in the center city area, an area that contains relatively high population density, which is also consistent with our common sense.

ride5_sf = st_as_sf(ride5, sf_column_name = "geometry" ,
                 crs = st_crs(NYCTracts), agr = "constant")

group_by(ride5_sf, week, start_station_name) %>%
  summarize(Sum_Trip_Count = sum(Trip_Count)) %>%
  ungroup() %>%
  ggplot() + 
  geom_sf(data=PHITracts,color="grey50",size=0.5,fill = "transparent")+
  geom_sf(aes(color = q5(Sum_Trip_Count)),size=2) +
  geom_sf(data=st_union(PHITracts),color="black",size=3,fill = "transparent")+
  facet_wrap(~week, ncol = 5) +
  scale_color_manual(values = palette5,
                     labels = c("10","196","529","826","1283"),
                    name = "Trip_Count") +
  labs(title="Sum of rideshare trips by tract and week") +
  mapTheme(25,35) + 
  theme(legend.position = "bottom",
        panel.border = element_rect(color = "black",fill = "transparent", size = 6))

test <- group_by(ride5_sf,dayotw,start_station_name) %>%
  summarize(Sum_Trip_Count = sum(Trip_Count)) %>%
  ungroup()

ggplot() + 
  geom_sf(data=PHITracts,color="grey50",size=0.5,fill = "transparent")+
  geom_sf(data=test,aes(color = q5(Sum_Trip_Count)),size=1.25) +
  geom_sf(data=st_union(PHITracts),color="black",size=2,fill = "transparent")+
  facet_wrap(~dayotw, ncol = 7) +
  scale_color_manual(values = palette5,
                     labels = c("12","140","375","596","916"),
                    name = "Trip_Count") +
  labs(title="Sum of rideshare trips by tract and week") +
  mapTheme(25,35) + 
  theme(legend.position = "bottom",
        panel.border = element_rect(color = "black",fill = "transparent", size = 6))

3.4 Spatial & Seriel Correlation

Below is a graphical representation of the evolution of bicycle trips versus time using animation. The trips are divided into 5 categories for each station. From the evolving graph we can see that the impact of Time Lag is quite evident during the peak hours of the city.

ride.animation.data <- ride5_sf%>%
  filter(week == 20)


ride.animation.data2 <-ride.animation.data%>%
  mutate(Trips =
           case_when(
             Trip_Count == 0 ~ "0 trips",
             Trip_Count > 0 & Trip_Count <= 3 ~ "1-3 trips",
             Trip_Count > 3 & Trip_Count <= 11 ~ "4-11 trips",
             Trip_Count > 11 & Trip_Count <= 28 ~ "12-28 trips",
             Trip_Count > 28 ~ "28+ trips")) %>%
  mutate(Trips = fct_relevel(Trips,"0 trips","1-3 trips","4-11 trips","12-28 trips","28+ trips"))
mapTheme2 <- function(base_size = 12, title_size = 15) {
  theme(
    text = element_text( color = "black"),
    plot.title = element_text(hjust = 0.5, size = title_size, colour = "black",face = "bold"), 
    plot.subtitle = element_text(hjust = 0.5,size=base_size,face="italic"),
    plot.caption = element_text(size=base_size,hjust=0),
    axis.ticks = element_blank(),
    panel.spacing = unit(6, 'lines'),
    panel.background = element_blank(),
    panel.grid.major = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_blank(),
    strip.background = element_rect(fill = "grey80", color = "white"),
    strip.text = element_text(size=base_size),
    axis.title = element_text(face = "bold",size=base_size),
    axis.text = element_blank(),
    plot.background = element_blank(),
    legend.background = element_blank(),
    legend.title = element_text(size=base_size,colour = "black", face = "italic"),
    legend.text = element_text(size=base_size,colour = "black", face = "italic"),
    strip.text.x = element_text(size = base_size,face = "bold")
  )
}

rideshare_animation <-ggplot() +
  geom_sf(data=PHITracts,color="grey80",size=0.01,fill = "transparent")+
  geom_sf(data = ride.animation.data2, aes(color = Trips),size=1.32) +
  geom_sf(data=st_union(PHITracts),color="black",size=1.2,fill = "transparent")+
  scale_color_manual(values = palette5) +
  labs(title ="Rideshare pickups for week 36, Philadelphia",
       subtitle = "One hour intervals: {current_frame}") +
  transition_manual(interval_hour) +
  mapTheme2() + 
  theme(legend.position = "bottom",
        panel.border = element_rect(color = "black",fill = "transparent", size = 1))

animate(rideshare_animation, duration=10,renderer = gifski_renderer()) %>% knitr::include_graphics ()

4. Regression

4.1 Regression Models

In this section, five different Linear regressions are estimated with different fixed effects:

rer 0 focus on just additional features

reg1 focuses on just time, including additional features

reg2 focuses on just space fixed effects, including additional features

reg3 focuses on time, space fixed effects and additional features

reg4 adds the time lag features.

Time features like hour is set as a continuous feature, the interpretation is that a 1 hour increase is associated with an estimated change in Trip_Count. Spatial fixed effects for start_station_name are also included to account for the across tract differences, like amenities, access to transit, distance to the Loop, etc.

Ordinary least squares (OLS) is chosen, and the assumptions to the OLS Regression are met here.

#no time, no spatial
reg0 <- lm(Trip_Count ~dayotw + Temp + isPrecip + Wind,data=ride.Train)
#yes time, no spatial
reg1 <- lm(Trip_Count ~hour(interval_hour) + dayotw + Temp + isPrecip + Wind ,data=ride.Train)
#no time, yes spatial
reg2 <- lm(Trip_Count ~start_station_name + dayotw + Temp + isPrecip + Wind ,data=ride.Train)
#yes time, yes spatial
reg3 <- lm(Trip_Count ~start_station_name + hour(interval_hour) + dayotw + Temp + isPrecip + Wind ,data=ride.Train)
#yes time&lag, yes spatial
reg4 <- lm(Trip_Count ~start_station_name + hour(interval_hour) + dayotw + Temp + isPrecip + Wind  + lagHour + lag2Hours + lag3Hours + lag4Hours +lag12Hours + lag1day,
           data=ride.Train)

ride.Test.weekNest <-as.data.frame(ride.Test) %>%nest(-week)

model_pred <- function(dat, fit){pred <- predict(fit, newdata = dat)}

week_predictions <-ride.Test.weekNest %>%
  mutate(
    A_Baseline = map(.x = data, fit = reg0, .f = model_pred),
    B_Time_FE = map(.x = data, fit = reg1, .f = model_pred),
    C_Space_FE = map(.x = data, fit = reg2, .f = model_pred),
    D_Space_Time_FE = map(.x = data, fit = reg3,.f = model_pred),
    E_Space_Time_Lags = map(.x = data, fit = reg4,.f = model_pred))

First, we calculated the (MAE) for the five models. This data provides a visual measure of the accuracy of the models. From the icons, we can see that as my chosen model becomes more and more complex, the more predictors are added, the better the accuracy of the model. The addition of time lag significantly improves the accuracy of the model. The time and space effects also have a positive impact on the prediction of the model.

week_predictions <- week_predictions %>%
  gather(Regression, Prediction, -data, -week) %>%
  mutate(Observed = map(data, pull, Trip_Count),
         Absolute_Error = map2(Observed, Prediction,~ abs(.x - .y)),
         MAE = map_dbl(Absolute_Error, mean),
         sd_AE = map_dbl(Absolute_Error, sd))

week_predictions2 <- week_predictions
week_predictions2$week <- as.character(week_predictions2$week)

week_predictions2 %>%
  dplyr::select(week, Regression, MAE) %>%
  gather(Variable, MAE, -Regression, -week) %>%
  ggplot(aes(week, MAE)) +
  geom_bar(aes(fill = Regression),
           position = "dodge", stat="identity") +
  scale_fill_manual(values = palette5) +
  labs(title = "Mean Absolute Errors by model and week") +
  plotTheme()+
  theme(legend.position = "bottom")

4.2 Validation by time

Trips are used as vertical coordinates. As the model becomes more complex, the prediction results become closer to the actual results, and the addition of the time lag makes the model closer to the actual situation for each hour of the day.

week_predictions2 %>%
  mutate(interval_hour = map(data, pull, interval_hour),
         start_station_name =map(data, pull, start_station_name)) %>%
  dplyr::select(interval_hour, start_station_name,
                Observed, Prediction, Regression) %>%
  unnest() %>%
  gather(Variable, Value, -Regression, -interval_hour,-start_station_name) %>%
  group_by(Regression, Variable, interval_hour) %>%
  summarize(Value = mean(Value)) %>%
  ggplot(aes(interval_hour, Value, colour=Variable)) +
  geom_line(size = 3) +
  facet_wrap(~Regression, ncol=1) +
  scale_colour_manual(values = palette2) +
  labs(
    title = "Mean Predicted/Observed rideshare by hour",
    x = "Hour", y= "Rideshare Trips") +
  plotTheme()+
  theme(legend.position = "bottom")

4.3 Validation by space

So how do these models perform in predicting bike use at each site? We calculated the MAE for each station using the data from weeks 23 and 24 as the test set. It is easy to see from the results that for all models, the accuracy is more accurate for the edge areas compared to the central city. However, the addition of the space feature of time lag, etc., makes the prediction of the peripheral range of Philadelphia more accurate. center city’s case still has a higher MAE as the model becomes more complex.

error.byWeek <-
  filter(week_predictions2, Regression != "A_Baseline") %>%
  unnest() %>% 
  st_sf() %>%
  dplyr::select(start_station_name, Absolute_Error,week, geometry,Regression) %>%
  gather(Variable, Value, -start_station_name,-week, -geometry,-Regression) %>%
  group_by(Variable, start_station_name, week,Regression) %>%
  summarize(MAE = mean(Value))
## Warning: `cols` is now required when using unnest().
## Please use `cols = c(data, Prediction, Observed, Absolute_Error)`
## `summarise()` has grouped output by 'Variable', 'start_station_name', 'week'.
## You can override using the `.groups` argument.
q4 <- function(variable) {as.factor(ntile(variable, 4))}
ggplot() +
  geom_sf(data=st_union(PHITracts),color="black",size=3,fill = "transparent")+
  geom_sf(data=PHITracts,color="black",size=0.5,linetype ="dashed",fill = "transparent")+
  geom_sf(data = error.byWeek, aes(color = q4(MAE)), size = 2) +
  scale_color_manual(values=palette5,
                     labels = c("0","1","2","3"),
                     name = "MAE") +
  facet_wrap(week~Regression, ncol = 4)+
  labs(title = 'Mean absolute error by station and by week') +
  mapTheme(25,35)+ 
  theme(
    legend.position = "bottom",
    panel.border = element_rect(color = "black",fill = "transparent", size = 6))

error.byHour <-
  filter(week_predictions2, Regression == "E_Space_Time_Lags" & week==24) %>%
  unnest() %>%
  filter(dayotw == "Mon")%>%
  st_sf() %>%
  dplyr::select(start_station_name, Absolute_Error,
                geometry, interval_hour) %>%
  gather(Variable, Value, -interval_hour,-start_station_name, -geometry) %>%
  group_by(hour = hour(interval_hour), start_station_name) %>%
  summarize(MAE = mean(Value))

The figure below shows the predicted MAE for each station for each hour. it can be seen from the figure that the MAE for each station increases as the morning and evening peaks approach. For example, at 17 and 19 pm, more dark dots appear on the map, representing that the model will be more inaccurate for the peak predictions.

ggplot() +
  geom_sf(data=st_union(PHITracts),color="black",size=2,fill = "transparent")+
  geom_sf(data=PHITracts,color="grey70",size=1,linetype ="dashed",fill = "transparent")+
  geom_sf(data = error.byHour, aes(color = q4(MAE)), size = 2) +
  scale_color_manual(values=palette4,
                     labels = c("0","1","2","2+"),
                     name = "MAE") +
  facet_wrap(~hour, ncol = 8)+
  labs(title = 'Mean absolute error by station and by hour') +
  mapTheme()+ 
  theme(
    legend.position = "bottom",
    panel.border = element_rect(color = "black",fill = "transparent", size = 4))

4.4 Cross Validation by Time

To further validate the generalizability of the model, cross-validate the E_Space_Time_Lags model by time hour (with features, time, spatial fixed effects, additional features and time lags) has been made. Each hour takes turns to be the test set: the model is trained on the other hours of the day and tested on this hour.

crossValidate <- function(dataset, id, dependentVariable, indVariables) {
  
  allPredictions <- data.frame()
  cvID_list <- unique(dataset[[id]])
  
  for (i in cvID_list) {
    
    thisFold <- i
    cat("This hold out fold is", thisFold, "\n")
    
    fold.train <- filter(dataset, dataset[[id]] != thisFold) %>% as.data.frame() %>% 
      dplyr::select(id, geometry,interval_hour, indVariables, dependentVariable)
    
    fold.test  <- filter(dataset, dataset[[id]] == thisFold) %>% as.data.frame() %>% 
      dplyr::select(id, geometry,interval_hour, indVariables, dependentVariable)
    
    regression <- lm(paste0(dependentVariable,"~."), 
    data = fold.train %>% dplyr::select(-geometry))
    
    thisPrediction <- 
      mutate(fold.test, Prediction = predict(regression, fold.test, type = "response"))
    
    allPredictions <-
      rbind(allPredictions, thisPrediction)
    
    gc()
  }
  return(st_sf(allPredictions))
}

ride.cross <- ride5

ride.cross <- ride.cross%>%mutate(hour=hour(interval_hour))
reg.logo_vars <- c("hour","start_station_name","dayotw","Temp","isPrecip","Wind","lagHour","lag2Hours", "lag3Hours","lag4Hours","lag12Hours","lag1day")

reg.logo_hour <- crossValidate(
  dataset = ride.cross,
  id = "hour",
  dependentVariable = "Trip_Count",
  indVariables = reg.logo_vars)

From the table we can see that the MAE is concentrated in 1, 2, 3. For the case of lower number of rentals we tend to have higher prediction results, while for the case of higher number of rentals we have lower prediction results, and we have better prediction results when the number of rentals is around 7.5.

reg.logo_hour <- reg.logo_hour%>%
  mutate(MAE = abs(Prediction - Trip_Count))%>%
  na.omit()

grid.arrange(ncol=2,
reg.logo_hour %>%
  ggplot(aes(MAE)) +
  geom_histogram(bins = 40, colour="white", fill=palette1_main) +
  scale_x_continuous(breaks = seq(0, 3, by = 1)) +
  labs(title="Distribution of MAE, LOGO-CV",
       x="Mean Absolute Error", y="Trip Count") +
  plotTheme()+
  theme(legend.position="bottom",
    axis.text = element_text(size=25)),
st_drop_geometry(reg.logo_hour) %>%
  mutate(Count_Decile = ntile(Trip_Count, 10)) %>%
  group_by(Count_Decile) %>%
  summarize(meanObserved = mean(Trip_Count, na.rm=T),
            meanPrediction = mean(Prediction, na.rm=T)) %>%
  gather(Variable, Value, -Count_Decile) %>%
  ggplot(aes(Count_Decile, Value, shape = Variable)) +
  geom_path(aes(group = Count_Decile), size=2,colour = palette1_assist) +
  geom_point(size = 6,color=palette1_main) +
  scale_shape_manual(values = c(2, 17)) +
  xlim(0,10) +
  ylim(0,10)+
  labs(title = "Predicted and observed") +
  plotTheme()+
  theme(legend.position = c(.3,.8)))

reg.logo_hour %>%
  st_drop_geometry()%>%
  dplyr::select(interval_hour, start_station_name,
                Trip_Count, Prediction) %>%
  gather(Variable, Value, -interval_hour,-start_station_name) %>%
  group_by(Variable, interval_hour) %>%
  summarize(Value = mean(Value))%>%
  ggplot(aes(interval_hour, Value, colour=Variable)) +
  geom_line(size = 2) +
  scale_colour_manual(values = palette2) +
  labs(
    title = "Mean Predicted/Observed rideshare by time",
    x = "Time", y= "Rideshare Trips") +
  plotTheme()+
  theme(legend.position = "bottom")

Presenting the MAE on the map by hour, we can see that the predictions are more accurate in the practice periods with lower usage in the early morning hours. Spatially, the MAE is higher in the central city than in the surrounding areas.

LOGO_SPD <- reg.logo_hour %>%
  st_drop_geometry()%>%
  filter(dayotw == "Mon")%>%
  dplyr::select(hour, start_station_name,MAE) %>%
  gather(Variable, Value, -hour,-start_station_name) %>%
  group_by(Variable, hour,start_station_name,) %>%
  summarize(Value = mean(Value))%>%
  left_join(ride_station%>%dplyr::select(-GEOID),by="start_station_name")%>%
  st_sf()

ggplot() +
  geom_sf(data=st_union(PHITracts),color="black",size=2,fill = "transparent")+
  geom_sf(data=PHITracts,color="grey80",size=0.5,fill = "transparent")+
  geom_sf(data = LOGO_SPD, aes(color = q4(Value)), size = 2) +
  scale_color_manual(values=palette4,
                     labels = c("0","1","2","2+"),
                     name = "MAE") +
  facet_wrap(~hour, ncol = 8)+
  labs(title = 'Mean absolute error by station and by hour - LOGO') +
  mapTheme()+ 
  theme(
    legend.position = "bottom",
    panel.border = element_rect(color = "black",fill = "transparent", size = 3))

5.Conclusion

This time we analyzed the predicted bicycle rental usage using data from IndegoBike in Philadelphia. First using five different regression model algorithms, we can conclude that the prediction accuracy of the model continues to improve with the addition of spatial features and time lag. However, the model itself still has a tendency to predict higher for lower usage cases and lower for higher usage cases. In general, the model supports advance bicycle dispatching. We can pre-deploy bikes more in advance of the prediction results. For example, we can increase the supply of bicycles in the central city before the high time period, so as to better meet the travel demand of users.

LS0tDQp0aXRsZTogIlJpZGUgQmlrZSBTaGFyZSBQcmVkaWN0aW9uIg0KYXV0aG9yOiAiWmlsZSBXdSINCmRhdGU6ICIyMDIyLTExLTE3Ig0Kb3V0cHV0OiANCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIg0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KIyMgMS5JbnRybw0KRml4ZWQtc2l0ZSBwdWJsaWMgYmljeWNsZXMsIHdoaWxlIHByb3ZpZGluZyBjb252ZW5pZW5jZSB0byBjaXRpemVucyBvbiBvbmUgaGFuZCwgYWxzbyBmYWNlIGEgc2hvcnRhZ2Ugb2YgdmVoaWNsZXMgYXQgdGhlIGRvY2tzIG9yIGEgc3VycGx1cyBvZiB2ZWhpY2xlcy4gSW4gdGhpcyBzcGF0aWFsIGFuYWx5c2lzLCB3ZSB1c2Ugb3BlbiBkYXRhIGZyb20gUGhpbGFkZWxwaGlhJ3MgSW5kZWdvQmlrZSB0byBwcmVkaWN0IHRoZSBkZW1hbmQgZm9yIGJpY3ljbGUgc2hhcmluZyBhdCBhbGwgZG9ja2luZyBwb2ludHMgdG8gZW5zdXJlIHRoYXQgYmljeWNsZXMgb3IgZG9ja2luZyBwb2ludHMgYXJlIGF2YWlsYWJsZSB3aGVuIG5lZWRlZC4NCg0KIyMgMi5Nb2RlbCBDb25zdHJ1Y3Rpb24NCg0KIyMjIDIuMSBEYXRhIFNlbGVjdGlvbg0KQXMgYSBjeWNsaW5nIGZyaWVuZGx5IGNpdHksIFBoaWxhZGVscGhpYSdzIGJ1c3kgcHVibGljIHRyYW5zcG9ydGF0aW9uIG1ha2VzIHRoZSBiZXN0IHNhbXBsZSBmb3Igc3R1ZHlpbmcgcHVibGljIGJpa2Ugc2NoZWR1bGluZy4gVGhlIGRhdGEgZm9yIHRoaXMgc3R1ZHkgdXNlcyBJbmRlZ29CaWtlIG9wZW4gZGF0YSBmcm9tIE1heSBhbmQgSnVuZSwgYW5kIGZyb20gdGhlc2UgZGF0YSAyMC0yNCB3ZWVrcyB3ZXJlIHNlbGVjdGVkIGZvciBzdWJzZXF1ZW50IGFuYWx5c2lzLg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0UscmVzdWx0cz0iaGlkZSIsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCmBgYHtyIHNldHVwXzEzLCBjYWNoZT1UUlVFLCBtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShzZikNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeSh0aWdyaXMpDQpsaWJyYXJ5KHRpZHljZW5zdXMpDQpsaWJyYXJ5KHZpcmlkaXMpDQpsaWJyYXJ5KHJpZW0pDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmxpYnJhcnkoa25pdHIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KFJTb2NyYXRhKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShvc21kYXRhKQ0KbGlicmFyeShnZ2FuaW1hdGUpDQpsaWJyYXJ5KEZOTikNCmxpYnJhcnkoZ3JpZCkNCmxpYnJhcnkocmpzb24pDQpsaWJyYXJ5KGdpZnNraSkNCg0Kb3B0aW9ucyh0aWdyaXNfY2xhc3MgPSAic2YiKQ0Kcm9vdC5kaXIgPQ0KcGFzdGUwKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vdXJiYW5TcGF0aWFsIiwNCiIvUHVibGljLVBvbGljeS1BbmFseXRpY3MtTGFuZGluZy9tYXN0ZXIvREFUQS8iKQ0Kc291cmNlKA0KcGFzdGUwKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vdXJiYW5TcGF0aWFsLyIsDQoiUHVibGljLVBvbGljeS1BbmFseXRpY3MtTGFuZGluZy9tYXN0ZXIvZnVuY3Rpb25zLnIiKSkNCg0KcGFsZXR0ZTUgPC0gYygiI2Y5YjI5NCIsIiNmMjcyN2YiLCIjYzA2Yzg2IiwiIzZkNWM3ZSIsIiMzMTVkN2YiKQ0KcGFsZXR0ZTYgPC0gYygiI2VmZjNmZiIsIiNiZGQ3ZTciLCIjNmJhZWQ2IiwiIzMxODJiZCIsIiMwODUxOWMiKQ0KcGFsZXR0ZTQgPC0gYygiI2Y5YjI5NCIsIiNmMjcyN2YiLCIjYzA2Yzg2IiwiIzZkNWM3ZSIpDQpwYWxldHRlMiA8LSBjKCIjZjliMjk0IiwiI2YyNzI3ZiIpDQpwYWxldHRlMV9tYWluIDwtICIjRjI3MjdGIg0KcGFsZXR0ZTFfYXNzaXN0IDwtICcjRjlCMjk0Jw0KDQptYXBUaGVtZSA8LSBmdW5jdGlvbihiYXNlX3NpemUgPSAzNSwgdGl0bGVfc2l6ZSA9IDQ1KSB7DQogIHRoZW1lKA0KICAgIHRleHQgPSBlbGVtZW50X3RleHQoIGNvbG9yID0gImJsYWNrIiksDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZSA9IHRpdGxlX3NpemUsIGNvbG91ciA9ICJibGFjayIsZmFjZSA9ICJib2xkIiksIA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsc2l6ZT1iYXNlX3NpemUsZmFjZT0iaXRhbGljIiksDQogICAgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KHNpemU9YmFzZV9zaXplLGhqdXN0PTApLA0KICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksDQogICAgcGFuZWwuc3BhY2luZyA9IHVuaXQoNiwgJ2xpbmVzJyksDQogICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLA0KICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksDQogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLA0KICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJncmV5ODAiLCBjb2xvciA9ICJ3aGl0ZSIpLA0KICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT1iYXNlX3NpemUpLA0KICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIixzaXplPWJhc2Vfc2l6ZSksDQogICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLA0KICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT1iYXNlX3NpemUsY29sb3VyID0gImJsYWNrIiwgZmFjZSA9ICJpdGFsaWMiKSwNCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPWJhc2Vfc2l6ZSxjb2xvdXIgPSAiYmxhY2siLCBmYWNlID0gIml0YWxpYyIpLA0KICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gYmFzZV9zaXplLGZhY2UgPSAiYm9sZCIpDQogICkNCn0NCg0KcGxvdFRoZW1lIDwtIGZ1bmN0aW9uKGJhc2Vfc2l6ZSA9IDM1LCB0aXRsZV9zaXplID0gNDUpIHsNCiAgdGhlbWUoDQogICAgdGV4dCA9IGVsZW1lbnRfdGV4dCggY29sb3IgPSAiYmxhY2siKSwNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplID0gdGl0bGVfc2l6ZSwgY29sb3VyID0gImJsYWNrIixmYWNlID0gImJvbGQiKSwgDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSxzaXplPWJhc2Vfc2l6ZSxmYWNlPSJpdGFsaWMiKSwNCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoc2l6ZT1iYXNlX3NpemUsaGp1c3Q9MCksDQogICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBwYW5lbC5zcGFjaW5nID0gdW5pdCg2LCAnbGluZXMnKSwNCiAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLA0KICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2xpbmUoImdyZXk4MCIsIHNpemUgPSAwLjEpLA0KICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCksDQogICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG91ciA9ICJibGFjayIsIGZpbGw9TkEsIHNpemU9NiksDQogICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gImdyZXk4MCIsIGNvbG9yID0gIndoaXRlIiksDQogICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPWJhc2Vfc2l6ZSksDQogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLHNpemU9YmFzZV9zaXplKSwNCiAgICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZT1iYXNlX3NpemUpLA0KICAgIHBsb3QuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT1iYXNlX3NpemUsY29sb3VyID0gImJsYWNrIiwgZmFjZSA9ICJpdGFsaWMiKSwNCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPWJhc2Vfc2l6ZSxjb2xvdXIgPSAiYmxhY2siLCBmYWNlID0gIml0YWxpYyIpLA0KICAgIHN0cmlwLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gYmFzZV9zaXplLGZhY2UgPSAiYm9sZCIpDQogICkNCn0NCmBgYA0KDQpEb3dubG9hZCB0aGUgZGF0YSBvZiBzZWFzb24gMiBmcm9tIHRoZSBJbmRlZ29CaWtlIHdlYnNpdGUsIHdlIGNhbiB1c2UgdGhlIHN1YnN0ciBmdW5jdGlvbiB0byBleHRyYWN0IHRoZSBmaXJzdCAxMiBjaGFyYWN0ZXJzIG9mIHRoZSBzdHJpbmcgInN0YXJ0ZWRfYXQiLiBGb3IgZXhhbXBsZSwgIjUtMDEtMjAyMiAyMTowODowNyIgd2lsbCBiZSB0cmFuc2ZlciB0byAiNS0wMS0yMDIyIDIxIi4gVGhlbiB3ZSBjYW4gdXNlIHRoZSBtZHlfaCBmdW5jdGlvbiB0byBjYWN1bGF0ZSB0aGUgd2VlayBhbmQgdGhlIHRpbWUuDQpgYGB7ciBpbXB1dCBkYXRhLHJlc3VsdHM9ImhpZGUiLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCg0KcmlkZVJhdyA8LSByZWFkLmNzdigiRDovVXBlbm4vVXBlbm4gTGVjLzA1LU1VU0EtNTA4L0Fzc2lnbi0wNi9IVy9pbmRlZ28tdHJpcHMtMjAyMi1xMi9pbmRlZ28tdHJpcHMtMjAyMi1xMi5jc3YiKQ0KcmlkZSA8LSByaWRlUmF3ICU+JQ0KICANCiAgbXV0YXRlKGludGVydmFsX2hvdXIgPW1keV9oKHN1YnN0cihzdGFydF90aW1lLCAxLCAxMikpLA0KICAgICAgICAgd2VlayA9IHdlZWsoaW50ZXJ2YWxfaG91ciksDQogICAgICAgICBkYXlvdHcgPSB3ZGF5KGludGVydmFsX2hvdXIsIGxhYmVsPVRSVUUpLA0KICAgICAgICAgc3RhcnRfc3RhdGlvbl9uYW1lID0gYXMuY2hhcmFjdGVyKHN0YXJ0X3N0YXRpb24pLA0KICAgICAgICAgZW5kX3N0YXRpb25fbmFtZSA9IGFzLmNoYXJhY3RlcihlbmRfc3RhdGlvbikpJT4lDQogIG5hLm9taXQoKSAlPiUNCiAgZmlsdGVyKHdlZWsgJWluJSBjKDIwOjI0KSkNCg0KDQpgYGANCg0KYGBge3IgbG9hZF9hcGlfa2V5LCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLHJlc3VsdHM9ImhpZGUifQ0KY2Vuc3VzX2FwaV9rZXkoIjE1NWNjNTI1Njc0YTBkMjdjOThhZmJiNzAzMGQwODAyZTliYjU1NDMiLCBvdmVyd3JpdGUgPSBUUlVFKQ0KYGBgDQoNCmBgYHtyIGltcG9ydCB0aGUgY2Vuc3VzIGRhdGEscmVzdWx0cz0iaGlkZSIsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KUEhJQ2Vuc3VzIDwtIA0KICBnZXRfYWNzKGdlb2dyYXBoeSA9ICJ0cmFjdCIsIA0KICAgICAgICAgIHZhcmlhYmxlcyA9IGMoIkIwMTAwM18wMDEiLCAiQjE5MDEzXzAwMSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIkIwMjAwMV8wMDIiLCAiQjA4MDEzXzAwMSIsDQogICAgICAgICAgICAgICAgICAgICAgICAiQjA4MDEyXzAwMSIsICJCMDgzMDFfMDAxIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAiQjA4MzAxXzAxMCIsICJCMDEwMDJfMDAxIiksIA0KICAgICAgICAgIHllYXIgPSAyMDE3LCANCiAgICAgICAgICBzdGF0ZSA9ICJQQSIsIA0KICAgICAgICAgIGdlb21ldHJ5ID0gVFJVRSwgDQogICAgICAgICAgY291bnR5PWMoIlBoaWxhZGVscGhpYSIpLA0KICAgICAgICAgIG91dHB1dCA9ICJ3aWRlIikgJT4lDQogIHJlbmFtZShUb3RhbF9Qb3AgPSAgQjAxMDAzXzAwMUUsDQogICAgICAgICBNZWRfSW5jID0gQjE5MDEzXzAwMUUsDQogICAgICAgICBNZWRfQWdlID0gQjAxMDAyXzAwMUUsDQogICAgICAgICBXaGl0ZV9Qb3AgPSBCMDIwMDFfMDAyRSwNCiAgICAgICAgIFRyYXZlbF9UaW1lID0gQjA4MDEzXzAwMUUsDQogICAgICAgICBOdW1fQ29tbXV0ZXJzID0gQjA4MDEyXzAwMUUsDQogICAgICAgICBNZWFuc19vZl9UcmFuc3BvcnQgPSBCMDgzMDFfMDAxRSwNCiAgICAgICAgIFRvdGFsX1B1YmxpY19UcmFucyA9IEIwODMwMV8wMTBFKSAlPiUNCiAgc2VsZWN0KFRvdGFsX1BvcCwgTWVkX0luYywgV2hpdGVfUG9wLCBUcmF2ZWxfVGltZSwNCiAgICAgICAgIE1lYW5zX29mX1RyYW5zcG9ydCwgVG90YWxfUHVibGljX1RyYW5zLA0KICAgICAgICAgTWVkX0FnZSwNCiAgICAgICAgIEdFT0lELCBnZW9tZXRyeSkgJT4lDQogIG11dGF0ZShQZXJjZW50X1doaXRlID0gV2hpdGVfUG9wIC8gVG90YWxfUG9wLA0KICAgICAgICAgTWVhbl9Db21tdXRlX1RpbWUgPSBUcmF2ZWxfVGltZSAvIFRvdGFsX1B1YmxpY19UcmFucywNCiAgICAgICAgIFBlcmNlbnRfVGFraW5nX1B1YmxpY19UcmFucyA9IFRvdGFsX1B1YmxpY19UcmFucyAvIE1lYW5zX29mX1RyYW5zcG9ydCkNCg0KUEhJVHJhY3RzIDwtIA0KICBQSElDZW5zdXMgJT4lDQogIGFzLmRhdGEuZnJhbWUoKSAlPiUNCiAgZGlzdGluY3QoR0VPSUQsIC5rZWVwX2FsbCA9IFRSVUUpICU+JQ0KICBzZWxlY3QoR0VPSUQsIGdlb21ldHJ5KSAlPiUgDQogIHN0X3NmDQoNCkNlbnN1c0RhdGEgPC0gDQogIGdldF9hY3MoZ2VvZ3JhcGh5ID0gInRyYWN0IiwgDQogICAgICAgICAgdmFyaWFibGVzID0gYygiQjAxMDAzXzAwMUUiKSwgDQogICAgICAgICAgeWVhcj0yMDE5LCBzdGF0ZT0iUEEiLCBjb3VudHk9IlBoaWxhZGVscGhpYSIsZ2VvbWV0cnkgPSBULCBvdXRwdXQ9IndpZGUiKSAlPiUNCiAgc3RfdHJhbnNmb3JtKHN0X2NycyhQSElUcmFjdHMpKSAlPiUNCiAgcmVuYW1lKENlbnN1c19Ub3RhbFBvcCA9IEIwMTAwM18wMDFFKSAlPiUNCiAgZHBseXI6OnNlbGVjdCgtTkFNRSwtc3RhcnRzX3dpdGgoIkIiKSkgJT4lDQogIG11dGF0ZShDZW5zdXNfYXJlYXNxbSA9IGFzLm51bWVyaWMoc3RfYXJlYSguKSksDQogICAgICAgICBDZW5zdXNfUG9wRGVuc2l0eSA9IENlbnN1c19Ub3RhbFBvcC9DZW5zdXNfYXJlYXNxbSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoLUNlbnN1c19Ub3RhbFBvcCwtQ2Vuc3VzX2FyZWFzcW0pJT4lDQogIHJlcGxhY2UoaXMubmEoLiksMCkNCg0KIyNzdF93cml0ZShDZW5zdXNEYXRhLCAiQ2Vuc3VzRGF0YS5nZW9qc29uIikNCg0KYGBgDQoNCmBgYHtyLHJlc3VsdHM9ImhpZGUiLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCnJpZGVfcnNjaCA8LSByaWRlJT4lDQogIGdyb3VwX2J5KHN0YXJ0X3N0YXRpb25fbmFtZSklPiUNCiAgc3VtbWFyaXplKGxhdD1maXJzdChzdGFydF9sYXQpLGxuZz1maXJzdChzdGFydF9sb24pKQ0KDQpQSElUcmFjdHMgPC0NCiAgdGlncmlzOjp0cmFjdHMoc3RhdGUgPSAiUEEiLCBjb3VudHkgPSAiUGhpbGFkZWxwaGlhIikgJT4lDQogIGRwbHlyOjpzZWxlY3QoR0VPSUQpJT4lDQogIGZpbHRlcihHRU9JRCE9IjM2MDYxMDAwMTAwIikNCg0KDQpyaWRlMl9zZiA9IHN0X2FzX3NmKHJpZGVfcnNjaCwgY29vcmRzID0gYygibG5nIiwgImxhdCIpLA0KICAgICAgICAgICAgICAgICBjcnMgPSBzdF9jcnMoUEhJVHJhY3RzKSwgYWdyID0gImNvbnN0YW50IikNCg0KcmlkZV9zdGF0aW9uIDwtIHN0X2pvaW4ocmlkZTJfc2YsUEhJVHJhY3RzLGxlZnQ9VFJVRSklPiVuYS5vbWl0KCkNCnJpZGVfdHJhY3QgPC0gc3Rfam9pbihQSElUcmFjdHMscmlkZTJfc2YsbGVmdD1UUlVFKSU+JW5hLm9taXQoKQ0KDQpzdGF0aW9uX2xpc3QgPC0gcmlkZV9zdGF0aW9uW1sic3RhcnRfc3RhdGlvbl9uYW1lIl1dDQoNCnJpZGUzIDwtIHJpZGUgJT4lDQogIGZpbHRlcihzdGFydF9zdGF0aW9uX25hbWUgJWluJSBzdGF0aW9uX2xpc3QsZW5kX3N0YXRpb25fbmFtZSAlaW4lIHN0YXRpb25fbGlzdCkNCmBgYA0KDQpVc2luZyB0aGUgZGF0YSBvYnRhaW5lZCBmcm9tIHRoZSBhYm92ZSBzdGVwcywgd2UgY2FuIGdldCB0aGUgbG9jYXRpb24gaW5mb3JtYXRpb24gb2YgYWxsIEluZGVnbyBzdGF0aW9ucyBpbiB0aGUgUGhpbGFkZWxwaGlhLg0KYGBge3IgbWFwcGluZyB0aGUgcmVzZWFyY2ggYXJlYSwgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoPTE1LHJlc3VsdHM9ImhpZGUiLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGE9c3RfdW5pb24oUEhJVHJhY3RzKSxjb2xvcj0iYmxhY2siLHNpemU9MTUsZmlsbCA9ICJ0cmFuc3BhcmVudCIpKw0KICBnZW9tX3NmKGRhdGE9UEhJVHJhY3RzLGNvbG9yPSJibGFjayIsc2l6ZT0wLjUsbGluZXR5cGUgPSJkYXNoZWQiLGZpbGwgPSAidHJhbnNwYXJlbnQiKSsNCiAgZ2VvbV9zZihkYXRhPXJpZGVfc3RhdGlvbixzaXplPTQsY29sb3I9cGFsZXR0ZTFfbWFpbikrDQogIGxhYnModGl0bGUgPSAiTWFwIG9mIFNoYXJlIEJpa2UgU3RhdGlvbnMsIFBoaWxhZGVscGhpYSIsIA0KICAgICAgIHN1YnRpdGxlID0gIlJlZCBkb3RzIGFyZSBzdGF0aW9ucyIpKw0KICBtYXBUaGVtZSgpDQpgYGANCg0KYGBge3IscmVzdWx0cz0iaGlkZSIsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0Kc3R1ZHkucGFuZWwgPC1leHBhbmQuZ3JpZCgNCiAgaW50ZXJ2YWxfaG91ciA9IHVuaXF1ZShyaWRlMyRpbnRlcnZhbF9ob3VyKSwNCiAgc3RhcnRfc3RhdGlvbl9uYW1lID0NCiAgICB1bmlxdWUocmlkZTMkc3RhcnRfc3RhdGlvbl9uYW1lKSkNCg0KcmlkZTMucGFuZWwgPC1yaWRlMyAlPiUNCiAgbXV0YXRlKFRyaXBfQ291bnRlciA9IDEpICU+JQ0KICBncm91cF9ieShpbnRlcnZhbF9ob3VyLCBzdGFydF9zdGF0aW9uX25hbWUpICU+JQ0KICBzdW1tYXJpemUoVHJpcF9Db3VudCA9IHN1bShUcmlwX0NvdW50ZXIsIG5hLnJtPVQpKSAlPiUNCiAgcmlnaHRfam9pbihzdHVkeS5wYW5lbCkgJT4lDQogIHJlcGxhY2UoaXMubmEoLiksMCkgJT4lDQogIGxlZnRfam9pbihyaWRlX3N0YXRpb24sYnkgPSAic3RhcnRfc3RhdGlvbl9uYW1lIikgJT4lDQogIG11dGF0ZSh3ZWVrID0gd2VlayhpbnRlcnZhbF9ob3VyKSxkYXlvdHcgPSB3ZGF5KGludGVydmFsX2hvdXIsIGxhYmVsID0gVFJVRSkpICU+JQ0KICBzdF9zZigpDQpgYGANCg0KYGBge3IscmVzdWx0cz0iaGlkZSIsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0Kd2VhdGhlci5EYXRhIDwtDQogIHJpZW1fbWVhc3VyZXMoc3RhdGlvbiA9ICJQSEwiLCBkYXRlX3N0YXJ0ID0gIjIwMjItMDUtMDEiLA0KICAgICAgICAgICAgICAgIGRhdGVfZW5kID0gIjIwMjItMDctMDEiKQ0KDQp3ZWF0aGVyLlBhbmVsIDwtDQogIHdlYXRoZXIuRGF0YSAlPiUNCiAgbXV0YXRlX2lmKGlzLmNoYXJhY3RlciwNCiAgICAgICAgICAgIGxpc3QofnJlcGxhY2UoYXMuY2hhcmFjdGVyKC4pLCBpcy5uYSguKSwgIjAiKSkpICU+JQ0KICByZXBsYWNlKGlzLm5hKC4pLCAwKSAlPiUNCiAgbXV0YXRlKGludGVydmFsX2hvdXIgPSB5bWRfaChzdWJzdHIodmFsaWQsIDEsIDEzKSkpICU+JQ0KICBtdXRhdGUod2VlayA9IHdlZWsoaW50ZXJ2YWxfaG91ciksDQogICAgICAgICBkYXlvdHcgPSB3ZGF5KGludGVydmFsX2hvdXIsIGxhYmVsPVRSVUUpKSAlPiUNCiAgZmlsdGVyKHdlZWsgJWluJSBjKDIwOjI0KSklPiUNCiAgZ3JvdXBfYnkoaW50ZXJ2YWxfaG91cix3ZWVrLGRheW90dykgJT4lDQogIHN1bW1hcml6ZShUZW1wID0gbWF4KHRtcGYpLA0KICAgICAgICAgICAgUHJlY2lwID0gc3VtKHAwMWkpLA0KICAgICAgICAgICAgV2luZCA9IG1heChza250KSkgJT4lDQogIG11dGF0ZShUZW1wID0gaWZlbHNlKFRlbXAgPT0gMCwgNDIsIFRlbXApKSU+JQ0KICByZXBsYWNlKGlzLm5hKC4pLDApDQpgYGANCg0KIyMjIDIuMiBXZWF0aGVyIEZlYXR1cmUNCkFmdGVyIGltcG9ydGluZyB0aGUgYmljeWNsZSBkYXRhLCB3ZSBjYW4gY29udGludWUgdG8gaW50cm9kdWNlIHNvbWUgZmVhdHVyZXMgcmVsYXRlZCB0byBiaWN5Y2xlIHVzYWdlIGZvciBhbmFseXNpcy4gVGhlIGZpcnN0IGlzIHRoZSB3ZWF0aGVyIGZlYXR1cmUsIHdoaWNoIHdlIGNhbiBlYXNpbHkgdW5kZXJzdGFuZCBmcm9tIGxpZmUgZXhwZXJpZW5jZSB0aGF0IHBlb3BsZSB1c2UgYmljeWNsZXMgbGVzcyBmcmVxdWVudGx5IGluIHJhaW55IGRheXMgZHVlIHRvIHNhZmV0eSBjb25jZXJucy4gVGhlIHVzYWdlIHJhdGUgaXMgYWxzbyBsb3dlciBpbiBsb3cgdGVtcGVyYXR1cmVzLiBUaGVyZWZvcmUsIHdlIGludHJvZHVjZSB0aHJlZSB3ZWF0aGVyIGZlYXR1cmVzOiBwcmVjaXBpdGF0aW9uLCB3aW5kIGFuZCB0ZW1wZXJhdHVyZSwgdG8gYW5hbHl6ZSB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB3ZWF0aGVyIGFuZCBiaWN5Y2xlIHVzZS4NCmBgYHtyLCBmaWcuaGVpZ2h0PSAyMCxmaWcud2lkdGg9MjAsIHdhcm5pbmc9RkFMU0UscmVzdWx0cz0iaGlkZSIsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KZ3JpZC5hcnJhbmdlKA0KZ2dwbG90KHdlYXRoZXIuUGFuZWwsIGFlcyhpbnRlcnZhbF9ob3VyLFByZWNpcCkpICsgDQogIGdlb21fbGluZShjb2xvcj1wYWxldHRlMV9tYWluLHNpemU9MikrDQogIGxhYnMoeD0iIiwNCiAgICAgICB0aXRsZSA9ICJXZWF0aGVyIERhdGEgLSBQSEkgLSBXZWVrIDIwIC0gV2VlayAyNCwgMjAyMiIpKw0KICBwbG90VGhlbWUoKSwNCmdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWxfaG91cixXaW5kKSkgKyANCiAgZ2VvbV9saW5lKGNvbG9yPXBhbGV0dGUxX21haW4sc2l6ZT0yKSsNCiAgbGFicyh4PSIiLA0KICAgICAgIHRpdGxlID0gIiIpKw0KICBwbG90VGhlbWUoKSwNCmdncGxvdCh3ZWF0aGVyLlBhbmVsLCBhZXMoaW50ZXJ2YWxfaG91cixUZW1wKSkgKyANCiAgZ2VvbV9saW5lKGNvbG9yPXBhbGV0dGUxX21haW4sc2l6ZT0yKSsNCiAgbGFicyh4PSJUaW1lIiwNCiAgICAgICB0aXRsZSA9ICIiKSsNCiAgcGxvdFRoZW1lKCkpDQpgYGANCg0KIyMjIDIuMyBQb3B1bGF0aW9uIEZlYXR1cmUNClNpbWlsYXJseSwgdGhlIHBvcHVsYXRpb24gYmFzZSBpcyBhbHNvIHJlbGF0ZWQgdG8gdGhlIHByb3hpbWl0eSBvZiBwdWJsaWMgYmlrZSBzdGF0aW9ucyB0byBhIHNpZ25pZmljYW50IGRlZ3JlZS4gRGVuc2VseSBwb3B1bGF0ZWQgbmVpZ2hib3Job29kcyBtYXkgcmVxdWlyZSBtb3JlIGZyZXF1ZW50IGRpc3BhdGNoaW5nLCB3aGljaCBzaG91bGQgYWxzbyBiZSB0YWtlbiBpbnRvIGFjY291bnQuDQpgYGB7ciBwbG90IGNlbnN1cywgZmlnLmhlaWdodD0gMTUsIGZpZy53aWR0aCA9IDE1LHJlc3VsdHM9ImhpZGUiLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCmdncGxvdCgpKw0KICBnZW9tX3NmKGRhdGE9c3RfdW5pb24oUEhJVHJhY3RzKSxjb2xvcj0iYmxhY2siLHNpemU9MTAsZmlsbCA9ICJ0cmFuc3BhcmVudCIpKw0KICBnZW9tX3NmKGRhdGE9Q2Vuc3VzRGF0YSxhZXMoZmlsbD1xNShDZW5zdXNfUG9wRGVuc2l0eSkpKSsNCiAgZ2VvbV9zZihkYXRhPXJpZGVfc3RhdGlvbixjb2xvcj0icmVkIikrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU1LA0KICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBxQnIoQ2Vuc3VzRGF0YSwgIkNlbnN1c19Qb3BEZW5zaXR5IiksDQogICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiUGVyMTAwMHNxbSIpKw0KICBsYWJzKHRpdGxlID0gIk1hcCBvZiBQb3B1bGF0aW9uIERlbnNpdHksIFBoaWxhZGVscGhpYSIsIA0KICAgICAgIHN1YnRpdGxlID0gIlBvcHVsYXRpb24gUGVyIDEwMDBzcW0iKSsNCiAgbWFwVGhlbWUoKQ0KYGBgDQoNCmBgYHtyLHJlc3VsdHM9ImhpZGUiLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCnJpZGU0IDwtcmlkZTMucGFuZWwlPiUNCiAgbGVmdF9qb2luKHdlYXRoZXIuUGFuZWwlPiVkcGx5cjo6c2VsZWN0KC1kYXlvdHcpLCBieSA9ICJpbnRlcnZhbF9ob3VyIiklPiUNCiAgcmVuYW1lKCJ3ZWVrIj0id2Vlay54IiklPiUNCiAgZHBseXI6OnNlbGVjdCgtd2Vlay55KQ0KYGBgDQoNCiMjIDMuIERhdGEgRXhwbG9yYXRpb24NCkluIHRoaXMgc2VjdGlvbiwgd2Ugd2lsbCBhbmFseXplIHRoZSBkYXRhIGZvciB0aGUgcHJldmlvdXNseSBzY3JlZW5lZCBmaXZlIHdlZWtzLg0KDQojIyMgMy4xIFNwbGl0dGluZyBkYXRhIHNldHMNCkhlcmUsIHdlIHVzZSB0aGUgZGF0YSBvZiB0aGUgZmlyc3QgdGhyZWUgd2Vla3MgKDIwLTI0KSBhcyB0aGUgZGF0YSBvZiB0aGUgdHJhaW5pbmcgc2V0IGFuZCB0aGUgZGF0YSBvZiB0aGUgbGFzdCB0d28gd2Vla3MgKDIwLTI0KSBhcyB0aGUgZGF0YSBvZiB0aGUgdGVzdCBzZXQuDQpgYGB7cixyZXN1bHRzPSJoaWRlIix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpyaWRlNSA8LQ0KICByaWRlNCAlPiUNCiAgYXJyYW5nZShzdGFydF9zdGF0aW9uX25hbWUsIGludGVydmFsX2hvdXIpICU+JQ0KICBncm91cF9ieShzdGFydF9zdGF0aW9uX25hbWUpICU+JQ0KICBtdXRhdGUobGFnSG91ciA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwxKSwNCiAgICAgICAgIGxhZzJIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwyKSwNCiAgICAgICAgIGxhZzNIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwzKSwNCiAgICAgICAgIGxhZzRIb3VycyA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCw0KSwNCiAgICAgICAgIGxhZzEySG91cnMgPSBkcGx5cjo6bGFnKFRyaXBfQ291bnQsMTIpLA0KICAgICAgICAgbGFnMWRheSA9IGRwbHlyOjpsYWcoVHJpcF9Db3VudCwyNCkpICU+JQ0KdW5ncm91cCgpDQpgYGANCg0KYGBge3IscmVzdWx0cz0iaGlkZSIsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KcmlkZTUgPC1yaWRlNSU+JQ0KICBsZWZ0X2pvaW4oQ2Vuc3VzRGF0YSU+JXN0X2Ryb3BfZ2VvbWV0cnkoKSU+JWRwbHlyOjpzZWxlY3QoR0VPSUQsQ2Vuc3VzX1BvcERlbnNpdHkpLCBieSA9ICJHRU9JRCIpJT4lDQogIG11dGF0ZShpc1ByZWNpcCA9IGlmZWxzZShQcmVjaXAgPiAwLCJSYWluL1Nub3ciLCAiTm9uZSIpKQ0KcmlkZS5UcmFpbiA8LSBmaWx0ZXIocmlkZTUsIHdlZWsgPCAyMykNCnJpZGUuVGVzdCA8LSBmaWx0ZXIocmlkZTUsIHdlZWsgPj0gMjMpDQpyaWRlLmNyb3NzIDwtIHJpZGU1DQpgYGANCg0KIyMjIDMuMiBUaW1lIExhZw0KVGltZSBMYWcgaW1wbGllcyB0aGF0IHRoZSBjeWNsaW5nIHNpdHVhdGlvbiBhdCBzb21lIHBvaW50IGluIHRoZSBmdXR1cmUgaXMgY29ycmVsYXRlZCB3aXRoIHRoZSBjeWNsaW5nIHNpdHVhdGlvbiBhdCBzb21lIHRpbWUgaW4gdGhlIHBhc3QuIEZyb20gdGhlIHJlc3VsdHMsIG1vc3Qgd2Vla3Mgc2hvdyB2ZXJ5IGNvbXBhcmFibGUgdGltZSBzZXJpZXMgcGF0dGVybnMgd2l0aCBjb25zaXN0ZW50IHBlYWtzIGFuZCB2YWxsZXlzLiBUaGlzIGluZGljYXRlcyB0aGUgZXhpc3RlbmNlIG9mIHNlcmlhbCBjb3JyZWxhdGlvbi4NCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0gMzAgLHJlc3VsdHM9ImhpZGUiLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCkZyaWRheXMgPC0NCiAgbXV0YXRlKHJpZGU1LGZyaWRheSA9IGlmZWxzZShkYXlvdHcgPT0gIkZyaSIgJiBob3VyKGludGVydmFsX2hvdXIpID09IDEsaW50ZXJ2YWxfaG91ciwgMCkpICU+JQ0KICBmaWx0ZXIoZnJpZGF5ICE9IDApDQpyYmluZCgNCiAgbXV0YXRlKHJpZGUuVHJhaW4sIExlZ2VuZCA9ICJUcmFpbmluZyIpLA0KICBtdXRhdGUocmlkZS5UZXN0LCBMZWdlbmQgPSAiVGVzdGluZyIpKSAlPiUNCiAgZ3JvdXBfYnkoTGVnZW5kLCBpbnRlcnZhbF9ob3VyKSAlPiUNCiAgc3VtbWFyaXplKFRyaXBfQ291bnQgPSBzdW0oVHJpcF9Db3VudCkpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQpnZ3Bsb3QoYWVzKGludGVydmFsX2hvdXIsIFRyaXBfQ291bnQsIGNvbG91ciA9IExlZ2VuZCkpICsNCiAgZ2VvbV9saW5lKHNpemU9MikgKw0KICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGUyKSArDQogIGdlb21fdmxpbmUoZGF0YSA9IEZyaWRheXMsIGFlcyh4aW50ZXJjZXB0ID0gaW50ZXJ2YWxfaG91ciksbGluZXR5cGUgPSAiZGFzaGVkIixzaXplPTEpICsNCiAgbGFicygNCiAgICB0aXRsZT0iUmlkZXNoYXJlIHRyaXBzIGJ5IHdlZWs6IHdlZWsyMC0gd2VlazI0IiwNCiAgICBzdWJ0aXRsZT0iRGFzaGVkIGxpbmVzIGZvciBldmVyeSBGcmlkYXkiLA0KICAgIHg9IkRheSIsIHk9IlRyaXAgQ291bnQiKSArDQogIHBsb3RUaGVtZSgpKyB0aGVtZShwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQoNCiMjIyAzLjMgU3BhdGlhbCBBdXRvY29ycmVsYXRpb24gDQpTcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiBtZWFucyB0aGF0IHRoZSBiaWN5Y2xlIHNpdHVhdGlvbiBhdCBhIHBvaW50IGlzIGNvcnJlbGF0ZWQgd2l0aCB0aGUgc3Vycm91bmRpbmcgYmljeWNsZSBzaXR1YXRpb24uIEluIHRoaXMgZmlndXJlLCB3ZSBjYW4gc2VlIGNvbnN0YW50IHNwYXRpYWwgY2x1c3RlcmluZyBvbiBhIHdlZWtseSBiYXNpcy4gVGhlIGhpZ2hlciBmcmVxdWVuY3kgb2YgdXNlIG9jY3VycyBpbiB0aGUgY2VudGVyIGNpdHkgYXJlYSwgYW4gYXJlYSB0aGF0IGNvbnRhaW5zIHJlbGF0aXZlbHkgaGlnaCBwb3B1bGF0aW9uIGRlbnNpdHksIHdoaWNoIGlzIGFsc28gY29uc2lzdGVudCB3aXRoIG91ciBjb21tb24gc2Vuc2UuDQpgYGB7ciBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbiwgZmlnLmhlaWdodD0gMTAgLCBmaWcud2lkdGg9IDMwLHJlc3VsdHM9ImhpZGUiLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCnJpZGU1X3NmID0gc3RfYXNfc2YocmlkZTUsIHNmX2NvbHVtbl9uYW1lID0gImdlb21ldHJ5IiAsDQogICAgICAgICAgICAgICAgIGNycyA9IHN0X2NycyhOWUNUcmFjdHMpLCBhZ3IgPSAiY29uc3RhbnQiKQ0KDQpncm91cF9ieShyaWRlNV9zZiwgd2Vlaywgc3RhcnRfc3RhdGlvbl9uYW1lKSAlPiUNCiAgc3VtbWFyaXplKFN1bV9UcmlwX0NvdW50ID0gc3VtKFRyaXBfQ291bnQpKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBnZ3Bsb3QoKSArIA0KICBnZW9tX3NmKGRhdGE9UEhJVHJhY3RzLGNvbG9yPSJncmV5NTAiLHNpemU9MC41LGZpbGwgPSAidHJhbnNwYXJlbnQiKSsNCiAgZ2VvbV9zZihhZXMoY29sb3IgPSBxNShTdW1fVHJpcF9Db3VudCkpLHNpemU9MikgKw0KICBnZW9tX3NmKGRhdGE9c3RfdW5pb24oUEhJVHJhY3RzKSxjb2xvcj0iYmxhY2siLHNpemU9MyxmaWxsID0gInRyYW5zcGFyZW50IikrDQogIGZhY2V0X3dyYXAofndlZWssIG5jb2wgPSA1KSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBwYWxldHRlNSwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIjEwIiwiMTk2IiwiNTI5IiwiODI2IiwiMTI4MyIpLA0KICAgICAgICAgICAgICAgICAgICBuYW1lID0gIlRyaXBfQ291bnQiKSArDQogIGxhYnModGl0bGU9IlN1bSBvZiByaWRlc2hhcmUgdHJpcHMgYnkgdHJhY3QgYW5kIHdlZWsiKSArDQogIG1hcFRoZW1lKDI1LDM1KSArIA0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwNCiAgICAgICAgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG9yID0gImJsYWNrIixmaWxsID0gInRyYW5zcGFyZW50Iiwgc2l6ZSA9IDYpKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PSAxMCAsIGZpZy53aWR0aD0gMzAscmVzdWx0cz0iaGlkZSIsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KdGVzdCA8LSBncm91cF9ieShyaWRlNV9zZixkYXlvdHcsc3RhcnRfc3RhdGlvbl9uYW1lKSAlPiUNCiAgc3VtbWFyaXplKFN1bV9UcmlwX0NvdW50ID0gc3VtKFRyaXBfQ291bnQpKSAlPiUNCiAgdW5ncm91cCgpDQoNCmdncGxvdCgpICsgDQogIGdlb21fc2YoZGF0YT1QSElUcmFjdHMsY29sb3I9ImdyZXk1MCIsc2l6ZT0wLjUsZmlsbCA9ICJ0cmFuc3BhcmVudCIpKw0KICBnZW9tX3NmKGRhdGE9dGVzdCxhZXMoY29sb3IgPSBxNShTdW1fVHJpcF9Db3VudCkpLHNpemU9MS4yNSkgKw0KICBnZW9tX3NmKGRhdGE9c3RfdW5pb24oUEhJVHJhY3RzKSxjb2xvcj0iYmxhY2siLHNpemU9MixmaWxsID0gInRyYW5zcGFyZW50IikrDQogIGZhY2V0X3dyYXAofmRheW90dywgbmNvbCA9IDcpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGU1LA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiMTIiLCIxNDAiLCIzNzUiLCI1OTYiLCI5MTYiKSwNCiAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJUcmlwX0NvdW50IikgKw0KICBsYWJzKHRpdGxlPSJTdW0gb2YgcmlkZXNoYXJlIHRyaXBzIGJ5IHRyYWN0IGFuZCB3ZWVrIikgKw0KICBtYXBUaGVtZSgyNSwzNSkgKyANCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsDQogICAgICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvciA9ICJibGFjayIsZmlsbCA9ICJ0cmFuc3BhcmVudCIsIHNpemUgPSA2KSkNCmBgYA0KDQojIyMgMy40IFNwYXRpYWwgJiBTZXJpZWwgQ29ycmVsYXRpb24NCkJlbG93IGlzIGEgZ3JhcGhpY2FsIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBldm9sdXRpb24gb2YgYmljeWNsZSB0cmlwcyB2ZXJzdXMgdGltZSB1c2luZyBhbmltYXRpb24uIFRoZSB0cmlwcyBhcmUgZGl2aWRlZCBpbnRvIDUgY2F0ZWdvcmllcyBmb3IgZWFjaCBzdGF0aW9uLiBGcm9tIHRoZSBldm9sdmluZyBncmFwaCB3ZSBjYW4gc2VlIHRoYXQgdGhlIGltcGFjdCBvZiBUaW1lIExhZyBpcyBxdWl0ZSBldmlkZW50IGR1cmluZyB0aGUgcGVhayBob3VycyBvZiB0aGUgY2l0eS4NCmBgYHtyIHJlcXVpcmVkIGFuaW1hdGlvbiwgZmlnLmhlaWdodD0zMCAsIGZpZy53aWR0aD0yMCxyZXN1bHRzPSJoaWRlIix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpyaWRlLmFuaW1hdGlvbi5kYXRhIDwtIHJpZGU1X3NmJT4lDQogIGZpbHRlcih3ZWVrID09IDIwKQ0KDQoNCnJpZGUuYW5pbWF0aW9uLmRhdGEyIDwtcmlkZS5hbmltYXRpb24uZGF0YSU+JQ0KICBtdXRhdGUoVHJpcHMgPQ0KICAgICAgICAgICBjYXNlX3doZW4oDQogICAgICAgICAgICAgVHJpcF9Db3VudCA9PSAwIH4gIjAgdHJpcHMiLA0KICAgICAgICAgICAgIFRyaXBfQ291bnQgPiAwICYgVHJpcF9Db3VudCA8PSAzIH4gIjEtMyB0cmlwcyIsDQogICAgICAgICAgICAgVHJpcF9Db3VudCA+IDMgJiBUcmlwX0NvdW50IDw9IDExIH4gIjQtMTEgdHJpcHMiLA0KICAgICAgICAgICAgIFRyaXBfQ291bnQgPiAxMSAmIFRyaXBfQ291bnQgPD0gMjggfiAiMTItMjggdHJpcHMiLA0KICAgICAgICAgICAgIFRyaXBfQ291bnQgPiAyOCB+ICIyOCsgdHJpcHMiKSkgJT4lDQogIG11dGF0ZShUcmlwcyA9IGZjdF9yZWxldmVsKFRyaXBzLCIwIHRyaXBzIiwiMS0zIHRyaXBzIiwiNC0xMSB0cmlwcyIsIjEyLTI4IHRyaXBzIiwiMjgrIHRyaXBzIikpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9MzAgLCBmaWcud2lkdGg9MjAscmVzdWx0cz0iaGlkZSIsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KbWFwVGhlbWUyIDwtIGZ1bmN0aW9uKGJhc2Vfc2l6ZSA9IDEyLCB0aXRsZV9zaXplID0gMTUpIHsNCiAgdGhlbWUoDQogICAgdGV4dCA9IGVsZW1lbnRfdGV4dCggY29sb3IgPSAiYmxhY2siKSwNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41LCBzaXplID0gdGl0bGVfc2l6ZSwgY29sb3VyID0gImJsYWNrIixmYWNlID0gImJvbGQiKSwgDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSxzaXplPWJhc2Vfc2l6ZSxmYWNlPSJpdGFsaWMiKSwNCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoc2l6ZT1iYXNlX3NpemUsaGp1c3Q9MCksDQogICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBwYW5lbC5zcGFjaW5nID0gdW5pdCg2LCAnbGluZXMnKSwNCiAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLA0KICAgIHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksDQogICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksDQogICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gImdyZXk4MCIsIGNvbG9yID0gIndoaXRlIiksDQogICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChzaXplPWJhc2Vfc2l6ZSksDQogICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLHNpemU9YmFzZV9zaXplKSwNCiAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCksDQogICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLA0KICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLA0KICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPWJhc2Vfc2l6ZSxjb2xvdXIgPSAiYmxhY2siLCBmYWNlID0gIml0YWxpYyIpLA0KICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9YmFzZV9zaXplLGNvbG91ciA9ICJibGFjayIsIGZhY2UgPSAiaXRhbGljIiksDQogICAgc3RyaXAudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSBiYXNlX3NpemUsZmFjZSA9ICJib2xkIikNCiAgKQ0KfQ0KDQpyaWRlc2hhcmVfYW5pbWF0aW9uIDwtZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGE9UEhJVHJhY3RzLGNvbG9yPSJncmV5ODAiLHNpemU9MC4wMSxmaWxsID0gInRyYW5zcGFyZW50IikrDQogIGdlb21fc2YoZGF0YSA9IHJpZGUuYW5pbWF0aW9uLmRhdGEyLCBhZXMoY29sb3IgPSBUcmlwcyksc2l6ZT0xLjMyKSArDQogIGdlb21fc2YoZGF0YT1zdF91bmlvbihQSElUcmFjdHMpLGNvbG9yPSJibGFjayIsc2l6ZT0xLjIsZmlsbCA9ICJ0cmFuc3BhcmVudCIpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTUpICsNCiAgbGFicyh0aXRsZSA9IlJpZGVzaGFyZSBwaWNrdXBzIGZvciB3ZWVrIDM2LCBQaGlsYWRlbHBoaWEiLA0KICAgICAgIHN1YnRpdGxlID0gIk9uZSBob3VyIGludGVydmFsczoge2N1cnJlbnRfZnJhbWV9IikgKw0KICB0cmFuc2l0aW9uX21hbnVhbChpbnRlcnZhbF9ob3VyKSArDQogIG1hcFRoZW1lMigpICsgDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLA0KICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiYmxhY2siLGZpbGwgPSAidHJhbnNwYXJlbnQiLCBzaXplID0gMSkpDQoNCmFuaW1hdGUocmlkZXNoYXJlX2FuaW1hdGlvbiwgZHVyYXRpb249MTAscmVuZGVyZXIgPSBnaWZza2lfcmVuZGVyZXIoKSkgJT4lIGtuaXRyOjppbmNsdWRlX2dyYXBoaWNzICgpDQpgYGANCg0KIyMgNC4gUmVncmVzc2lvbg0KIyMjIDQuMSBSZWdyZXNzaW9uIE1vZGVscw0KSW4gdGhpcyBzZWN0aW9uLCBmaXZlIGRpZmZlcmVudCBMaW5lYXIgcmVncmVzc2lvbnMgYXJlIGVzdGltYXRlZCB3aXRoIGRpZmZlcmVudCBmaXhlZCBlZmZlY3RzOg0KDQo+IHJlciAwIGZvY3VzIG9uIGp1c3QgYWRkaXRpb25hbCBmZWF0dXJlcw0KDQo+IHJlZzEgZm9jdXNlcyBvbiBqdXN0IHRpbWUsIGluY2x1ZGluZyBhZGRpdGlvbmFsIGZlYXR1cmVzDQoNCj4gcmVnMiBmb2N1c2VzIG9uIGp1c3Qgc3BhY2UgZml4ZWQgZWZmZWN0cywgaW5jbHVkaW5nIGFkZGl0aW9uYWwgZmVhdHVyZXMNCg0KPiByZWczIGZvY3VzZXMgb24gdGltZSwgc3BhY2UgZml4ZWQgZWZmZWN0cyBhbmQgYWRkaXRpb25hbCBmZWF0dXJlcw0KDQo+IHJlZzQgYWRkcyB0aGUgdGltZSBsYWcgZmVhdHVyZXMuDQoNClRpbWUgZmVhdHVyZXMgbGlrZSBob3VyIGlzIHNldCBhcyBhIGNvbnRpbnVvdXMgZmVhdHVyZSwgdGhlIGludGVycHJldGF0aW9uIGlzIHRoYXQgYSAxIGhvdXIgaW5jcmVhc2UgaXMgYXNzb2NpYXRlZCB3aXRoIGFuIGVzdGltYXRlZCBjaGFuZ2UgaW4gVHJpcF9Db3VudC4gU3BhdGlhbCBmaXhlZCBlZmZlY3RzIGZvciBzdGFydF9zdGF0aW9uX25hbWUgYXJlIGFsc28gaW5jbHVkZWQgdG8gYWNjb3VudCBmb3IgdGhlIGFjcm9zcyB0cmFjdCBkaWZmZXJlbmNlcywgbGlrZSBhbWVuaXRpZXMsIGFjY2VzcyB0byB0cmFuc2l0LCBkaXN0YW5jZSB0byB0aGUgTG9vcCwgZXRjLg0KDQpPcmRpbmFyeSBsZWFzdCBzcXVhcmVzIChPTFMpIGlzIGNob3NlbiwgYW5kIHRoZSBhc3N1bXB0aW9ucyB0byB0aGUgT0xTIFJlZ3Jlc3Npb24gYXJlIG1ldCBoZXJlLg0KYGBge3IscmVzdWx0cz0iaGlkZSIsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KI25vIHRpbWUsIG5vIHNwYXRpYWwNCnJlZzAgPC0gbG0oVHJpcF9Db3VudCB+ZGF5b3R3ICsgVGVtcCArIGlzUHJlY2lwICsgV2luZCxkYXRhPXJpZGUuVHJhaW4pDQojeWVzIHRpbWUsIG5vIHNwYXRpYWwNCnJlZzEgPC0gbG0oVHJpcF9Db3VudCB+aG91cihpbnRlcnZhbF9ob3VyKSArIGRheW90dyArIFRlbXAgKyBpc1ByZWNpcCArIFdpbmQgLGRhdGE9cmlkZS5UcmFpbikNCiNubyB0aW1lLCB5ZXMgc3BhdGlhbA0KcmVnMiA8LSBsbShUcmlwX0NvdW50IH5zdGFydF9zdGF0aW9uX25hbWUgKyBkYXlvdHcgKyBUZW1wICsgaXNQcmVjaXAgKyBXaW5kICxkYXRhPXJpZGUuVHJhaW4pDQojeWVzIHRpbWUsIHllcyBzcGF0aWFsDQpyZWczIDwtIGxtKFRyaXBfQ291bnQgfnN0YXJ0X3N0YXRpb25fbmFtZSArIGhvdXIoaW50ZXJ2YWxfaG91cikgKyBkYXlvdHcgKyBUZW1wICsgaXNQcmVjaXAgKyBXaW5kICxkYXRhPXJpZGUuVHJhaW4pDQojeWVzIHRpbWUmbGFnLCB5ZXMgc3BhdGlhbA0KcmVnNCA8LSBsbShUcmlwX0NvdW50IH5zdGFydF9zdGF0aW9uX25hbWUgKyBob3VyKGludGVydmFsX2hvdXIpICsgZGF5b3R3ICsgVGVtcCArIGlzUHJlY2lwICsgV2luZCAgKyBsYWdIb3VyICsgbGFnMkhvdXJzICsgbGFnM0hvdXJzICsgbGFnNEhvdXJzICtsYWcxMkhvdXJzICsgbGFnMWRheSwNCiAgICAgICAgICAgZGF0YT1yaWRlLlRyYWluKQ0KDQpyaWRlLlRlc3Qud2Vla05lc3QgPC1hcy5kYXRhLmZyYW1lKHJpZGUuVGVzdCkgJT4lbmVzdCgtd2VlaykNCg0KbW9kZWxfcHJlZCA8LSBmdW5jdGlvbihkYXQsIGZpdCl7cHJlZCA8LSBwcmVkaWN0KGZpdCwgbmV3ZGF0YSA9IGRhdCl9DQoNCndlZWtfcHJlZGljdGlvbnMgPC1yaWRlLlRlc3Qud2Vla05lc3QgJT4lDQogIG11dGF0ZSgNCiAgICBBX0Jhc2VsaW5lID0gbWFwKC54ID0gZGF0YSwgZml0ID0gcmVnMCwgLmYgPSBtb2RlbF9wcmVkKSwNCiAgICBCX1RpbWVfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcxLCAuZiA9IG1vZGVsX3ByZWQpLA0KICAgIENfU3BhY2VfRkUgPSBtYXAoLnggPSBkYXRhLCBmaXQgPSByZWcyLCAuZiA9IG1vZGVsX3ByZWQpLA0KICAgIERfU3BhY2VfVGltZV9GRSA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzMsLmYgPSBtb2RlbF9wcmVkKSwNCiAgICBFX1NwYWNlX1RpbWVfTGFncyA9IG1hcCgueCA9IGRhdGEsIGZpdCA9IHJlZzQsLmYgPSBtb2RlbF9wcmVkKSkNCmBgYA0KRmlyc3QsIHdlIGNhbGN1bGF0ZWQgdGhlIChNQUUpIGZvciB0aGUgZml2ZSBtb2RlbHMuIFRoaXMgZGF0YSBwcm92aWRlcyBhIHZpc3VhbCBtZWFzdXJlIG9mIHRoZSBhY2N1cmFjeSBvZiB0aGUgbW9kZWxzLiBGcm9tIHRoZSBpY29ucywgd2UgY2FuIHNlZSB0aGF0IGFzIG15IGNob3NlbiBtb2RlbCBiZWNvbWVzIG1vcmUgYW5kIG1vcmUgY29tcGxleCwgdGhlIG1vcmUgcHJlZGljdG9ycyBhcmUgYWRkZWQsIHRoZSBiZXR0ZXIgdGhlIGFjY3VyYWN5IG9mIHRoZSBtb2RlbC4gVGhlIGFkZGl0aW9uIG9mIHRpbWUgbGFnIHNpZ25pZmljYW50bHkgaW1wcm92ZXMgdGhlIGFjY3VyYWN5IG9mIHRoZSBtb2RlbC4gVGhlIHRpbWUgYW5kIHNwYWNlIGVmZmVjdHMgYWxzbyBoYXZlIGEgcG9zaXRpdmUgaW1wYWN0IG9uIHRoZSBwcmVkaWN0aW9uIG9mIHRoZSBtb2RlbC4NCmBgYHtyLCBmaWcuaGVpZ2h0PTE1LCBmaWcud2lkdGg9MjUscmVzdWx0cz0iaGlkZSIsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0Kd2Vla19wcmVkaWN0aW9ucyA8LSB3ZWVrX3ByZWRpY3Rpb25zICU+JQ0KICBnYXRoZXIoUmVncmVzc2lvbiwgUHJlZGljdGlvbiwgLWRhdGEsIC13ZWVrKSAlPiUNCiAgbXV0YXRlKE9ic2VydmVkID0gbWFwKGRhdGEsIHB1bGwsIFRyaXBfQ291bnQpLA0KICAgICAgICAgQWJzb2x1dGVfRXJyb3IgPSBtYXAyKE9ic2VydmVkLCBQcmVkaWN0aW9uLH4gYWJzKC54IC0gLnkpKSwNCiAgICAgICAgIE1BRSA9IG1hcF9kYmwoQWJzb2x1dGVfRXJyb3IsIG1lYW4pLA0KICAgICAgICAgc2RfQUUgPSBtYXBfZGJsKEFic29sdXRlX0Vycm9yLCBzZCkpDQoNCndlZWtfcHJlZGljdGlvbnMyIDwtIHdlZWtfcHJlZGljdGlvbnMNCndlZWtfcHJlZGljdGlvbnMyJHdlZWsgPC0gYXMuY2hhcmFjdGVyKHdlZWtfcHJlZGljdGlvbnMyJHdlZWspDQoNCndlZWtfcHJlZGljdGlvbnMyICU+JQ0KICBkcGx5cjo6c2VsZWN0KHdlZWssIFJlZ3Jlc3Npb24sIE1BRSkgJT4lDQogIGdhdGhlcihWYXJpYWJsZSwgTUFFLCAtUmVncmVzc2lvbiwgLXdlZWspICU+JQ0KICBnZ3Bsb3QoYWVzKHdlZWssIE1BRSkpICsNCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSBSZWdyZXNzaW9uKSwNCiAgICAgICAgICAgcG9zaXRpb24gPSAiZG9kZ2UiLCBzdGF0PSJpZGVudGl0eSIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTUpICsNCiAgbGFicyh0aXRsZSA9ICJNZWFuIEFic29sdXRlIEVycm9ycyBieSBtb2RlbCBhbmQgd2VlayIpICsNCiAgcGxvdFRoZW1lKCkrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0KYGBgDQoNCiMjIyA0LjIgVmFsaWRhdGlvbiBieSB0aW1lDQpUcmlwcyBhcmUgdXNlZCBhcyB2ZXJ0aWNhbCBjb29yZGluYXRlcy4gQXMgdGhlIG1vZGVsIGJlY29tZXMgbW9yZSBjb21wbGV4LCB0aGUgcHJlZGljdGlvbiByZXN1bHRzIGJlY29tZSBjbG9zZXIgdG8gdGhlIGFjdHVhbCByZXN1bHRzLCBhbmQgdGhlIGFkZGl0aW9uIG9mIHRoZSB0aW1lIGxhZyBtYWtlcyB0aGUgbW9kZWwgY2xvc2VyIHRvIHRoZSBhY3R1YWwgc2l0dWF0aW9uIGZvciBlYWNoIGhvdXIgb2YgdGhlIGRheS4NCmBgYHtyLCBmaWcuaGVpZ2h0PTMwLCBmaWcud2lkdGg9MzAscmVzdWx0cz0iaGlkZSIsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0Kd2Vla19wcmVkaWN0aW9uczIgJT4lDQogIG11dGF0ZShpbnRlcnZhbF9ob3VyID0gbWFwKGRhdGEsIHB1bGwsIGludGVydmFsX2hvdXIpLA0KICAgICAgICAgc3RhcnRfc3RhdGlvbl9uYW1lID1tYXAoZGF0YSwgcHVsbCwgc3RhcnRfc3RhdGlvbl9uYW1lKSkgJT4lDQogIGRwbHlyOjpzZWxlY3QoaW50ZXJ2YWxfaG91ciwgc3RhcnRfc3RhdGlvbl9uYW1lLA0KICAgICAgICAgICAgICAgIE9ic2VydmVkLCBQcmVkaWN0aW9uLCBSZWdyZXNzaW9uKSAlPiUNCiAgdW5uZXN0KCkgJT4lDQogIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1SZWdyZXNzaW9uLCAtaW50ZXJ2YWxfaG91ciwtc3RhcnRfc3RhdGlvbl9uYW1lKSAlPiUNCiAgZ3JvdXBfYnkoUmVncmVzc2lvbiwgVmFyaWFibGUsIGludGVydmFsX2hvdXIpICU+JQ0KICBzdW1tYXJpemUoVmFsdWUgPSBtZWFuKFZhbHVlKSkgJT4lDQogIGdncGxvdChhZXMoaW50ZXJ2YWxfaG91ciwgVmFsdWUsIGNvbG91cj1WYXJpYWJsZSkpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAzKSArDQogIGZhY2V0X3dyYXAoflJlZ3Jlc3Npb24sIG5jb2w9MSkgKw0KICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IHBhbGV0dGUyKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiTWVhbiBQcmVkaWN0ZWQvT2JzZXJ2ZWQgcmlkZXNoYXJlIGJ5IGhvdXIiLA0KICAgIHggPSAiSG91ciIsIHk9ICJSaWRlc2hhcmUgVHJpcHMiKSArDQogIHBsb3RUaGVtZSgpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCmBgYA0KDQojIyMgNC4zIFZhbGlkYXRpb24gYnkgc3BhY2UNClNvIGhvdyBkbyB0aGVzZSBtb2RlbHMgcGVyZm9ybSBpbiBwcmVkaWN0aW5nIGJpa2UgdXNlIGF0IGVhY2ggc2l0ZT8gV2UgY2FsY3VsYXRlZCB0aGUgTUFFIGZvciBlYWNoIHN0YXRpb24gdXNpbmcgdGhlIGRhdGEgZnJvbSB3ZWVrcyAyMyBhbmQgMjQgYXMgdGhlIHRlc3Qgc2V0Lg0KSXQgaXMgZWFzeSB0byBzZWUgZnJvbSB0aGUgcmVzdWx0cyB0aGF0IGZvciBhbGwgbW9kZWxzLCB0aGUgYWNjdXJhY3kgaXMgbW9yZSBhY2N1cmF0ZSBmb3IgdGhlIGVkZ2UgYXJlYXMgY29tcGFyZWQgdG8gdGhlIGNlbnRyYWwgY2l0eS4gSG93ZXZlciwgdGhlIGFkZGl0aW9uIG9mIHRoZSBzcGFjZSBmZWF0dXJlIG9mIHRpbWUgbGFnLCBldGMuLCBtYWtlcyB0aGUgcHJlZGljdGlvbiBvZiB0aGUgcGVyaXBoZXJhbCByYW5nZSBvZiBQaGlsYWRlbHBoaWEgbW9yZSBhY2N1cmF0ZS4gY2VudGVyIGNpdHkncyBjYXNlIHN0aWxsIGhhcyBhIGhpZ2hlciBNQUUgYXMgdGhlIG1vZGVsIGJlY29tZXMgbW9yZSBjb21wbGV4Lg0KYGBge3IscmVzdWx0cz0iaGlkZSJ9DQplcnJvci5ieVdlZWsgPC0NCiAgZmlsdGVyKHdlZWtfcHJlZGljdGlvbnMyLCBSZWdyZXNzaW9uICE9ICJBX0Jhc2VsaW5lIikgJT4lDQogIHVubmVzdCgpICU+JSANCiAgc3Rfc2YoKSAlPiUNCiAgZHBseXI6OnNlbGVjdChzdGFydF9zdGF0aW9uX25hbWUsIEFic29sdXRlX0Vycm9yLHdlZWssIGdlb21ldHJ5LFJlZ3Jlc3Npb24pICU+JQ0KICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtc3RhcnRfc3RhdGlvbl9uYW1lLC13ZWVrLCAtZ2VvbWV0cnksLVJlZ3Jlc3Npb24pICU+JQ0KICBncm91cF9ieShWYXJpYWJsZSwgc3RhcnRfc3RhdGlvbl9uYW1lLCB3ZWVrLFJlZ3Jlc3Npb24pICU+JQ0KICBzdW1tYXJpemUoTUFFID0gbWVhbihWYWx1ZSkpDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9MzAsIGZpZy53aWR0aD0zMCxyZXN1bHRzPSJoaWRlIix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpxNCA8LSBmdW5jdGlvbih2YXJpYWJsZSkge2FzLmZhY3RvcihudGlsZSh2YXJpYWJsZSwgNCkpfQ0KZ2dwbG90KCkgKw0KICBnZW9tX3NmKGRhdGE9c3RfdW5pb24oUEhJVHJhY3RzKSxjb2xvcj0iYmxhY2siLHNpemU9MyxmaWxsID0gInRyYW5zcGFyZW50IikrDQogIGdlb21fc2YoZGF0YT1QSElUcmFjdHMsY29sb3I9ImJsYWNrIixzaXplPTAuNSxsaW5ldHlwZSA9ImRhc2hlZCIsZmlsbCA9ICJ0cmFuc3BhcmVudCIpKw0KICBnZW9tX3NmKGRhdGEgPSBlcnJvci5ieVdlZWssIGFlcyhjb2xvciA9IHE0KE1BRSkpLCBzaXplID0gMikgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPXBhbGV0dGU1LA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiMCIsIjEiLCIyIiwiMyIpLA0KICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJNQUUiKSArDQogIGZhY2V0X3dyYXAod2Vla35SZWdyZXNzaW9uLCBuY29sID0gNCkrDQogIGxhYnModGl0bGUgPSAnTWVhbiBhYnNvbHV0ZSBlcnJvciBieSBzdGF0aW9uIGFuZCBieSB3ZWVrJykgKw0KICBtYXBUaGVtZSgyNSwzNSkrIA0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwNCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiYmxhY2siLGZpbGwgPSAidHJhbnNwYXJlbnQiLCBzaXplID0gNikpDQpgYGANCg0KYGBge3IscmVzdWx0cz0iaGlkZSIsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0KZXJyb3IuYnlIb3VyIDwtDQogIGZpbHRlcih3ZWVrX3ByZWRpY3Rpb25zMiwgUmVncmVzc2lvbiA9PSAiRV9TcGFjZV9UaW1lX0xhZ3MiICYgd2Vlaz09MjQpICU+JQ0KICB1bm5lc3QoKSAlPiUNCiAgZmlsdGVyKGRheW90dyA9PSAiTW9uIiklPiUNCiAgc3Rfc2YoKSAlPiUNCiAgZHBseXI6OnNlbGVjdChzdGFydF9zdGF0aW9uX25hbWUsIEFic29sdXRlX0Vycm9yLA0KICAgICAgICAgICAgICAgIGdlb21ldHJ5LCBpbnRlcnZhbF9ob3VyKSAlPiUNCiAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWludGVydmFsX2hvdXIsLXN0YXJ0X3N0YXRpb25fbmFtZSwgLWdlb21ldHJ5KSAlPiUNCiAgZ3JvdXBfYnkoaG91ciA9IGhvdXIoaW50ZXJ2YWxfaG91ciksIHN0YXJ0X3N0YXRpb25fbmFtZSkgJT4lDQogIHN1bW1hcml6ZShNQUUgPSBtZWFuKFZhbHVlKSkNCmBgYA0KVGhlIGZpZ3VyZSBiZWxvdyBzaG93cyB0aGUgcHJlZGljdGVkIE1BRSBmb3IgZWFjaCBzdGF0aW9uIGZvciBlYWNoIGhvdXIuIGl0IGNhbiBiZSBzZWVuIGZyb20gdGhlIGZpZ3VyZSB0aGF0IHRoZSBNQUUgZm9yIGVhY2ggc3RhdGlvbiBpbmNyZWFzZXMgYXMgdGhlIG1vcm5pbmcgYW5kIGV2ZW5pbmcgcGVha3MgYXBwcm9hY2guIEZvciBleGFtcGxlLCBhdCAxNyBhbmQgMTkgcG0sIG1vcmUgZGFyayBkb3RzIGFwcGVhciBvbiB0aGUgbWFwLCByZXByZXNlbnRpbmcgdGhhdCB0aGUgbW9kZWwgd2lsbCBiZSBtb3JlIGluYWNjdXJhdGUgZm9yIHRoZSBwZWFrIHByZWRpY3Rpb25zLg0KYGBge3IsIGZpZy5oZWlnaHQ9MTUsIGZpZy53aWR0aD0zMCx3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YT1zdF91bmlvbihQSElUcmFjdHMpLGNvbG9yPSJibGFjayIsc2l6ZT0yLGZpbGwgPSAidHJhbnNwYXJlbnQiKSsNCiAgZ2VvbV9zZihkYXRhPVBISVRyYWN0cyxjb2xvcj0iZ3JleTcwIixzaXplPTEsbGluZXR5cGUgPSJkYXNoZWQiLGZpbGwgPSAidHJhbnNwYXJlbnQiKSsNCiAgZ2VvbV9zZihkYXRhID0gZXJyb3IuYnlIb3VyLCBhZXMoY29sb3IgPSBxNChNQUUpKSwgc2l6ZSA9IDIpICsNCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcz1wYWxldHRlNCwNCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIjAiLCIxIiwiMiIsIjIrIiksDQogICAgICAgICAgICAgICAgICAgICBuYW1lID0gIk1BRSIpICsNCiAgZmFjZXRfd3JhcCh+aG91ciwgbmNvbCA9IDgpKw0KICBsYWJzKHRpdGxlID0gJ01lYW4gYWJzb2x1dGUgZXJyb3IgYnkgc3RhdGlvbiBhbmQgYnkgaG91cicpICsNCiAgbWFwVGhlbWUoKSsgDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLA0KICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvciA9ICJibGFjayIsZmlsbCA9ICJ0cmFuc3BhcmVudCIsIHNpemUgPSA0KSkNCmBgYA0KDQojIyMgNC40IENyb3NzIFZhbGlkYXRpb24gYnkgVGltZQ0KVG8gZnVydGhlciB2YWxpZGF0ZSB0aGUgZ2VuZXJhbGl6YWJpbGl0eSBvZiB0aGUgbW9kZWwsIGNyb3NzLXZhbGlkYXRlIHRoZSBFX1NwYWNlX1RpbWVfTGFncyBtb2RlbCBieSB0aW1lIGhvdXIgKHdpdGggZmVhdHVyZXMsIHRpbWUsIHNwYXRpYWwgZml4ZWQgZWZmZWN0cywgYWRkaXRpb25hbCBmZWF0dXJlcyBhbmQgdGltZSBsYWdzKSBoYXMgYmVlbiBtYWRlLiBFYWNoIGhvdXIgdGFrZXMgdHVybnMgdG8gYmUgdGhlIHRlc3Qgc2V0OiB0aGUgbW9kZWwgaXMgdHJhaW5lZCBvbiB0aGUgb3RoZXIgaG91cnMgb2YgdGhlIGRheSBhbmQgdGVzdGVkIG9uIHRoaXMgaG91ci4NCmBgYHtyLHJlc3VsdHM9ImhpZGUiLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCmNyb3NzVmFsaWRhdGUgPC0gZnVuY3Rpb24oZGF0YXNldCwgaWQsIGRlcGVuZGVudFZhcmlhYmxlLCBpbmRWYXJpYWJsZXMpIHsNCiAgDQogIGFsbFByZWRpY3Rpb25zIDwtIGRhdGEuZnJhbWUoKQ0KICBjdklEX2xpc3QgPC0gdW5pcXVlKGRhdGFzZXRbW2lkXV0pDQogIA0KICBmb3IgKGkgaW4gY3ZJRF9saXN0KSB7DQogICAgDQogICAgdGhpc0ZvbGQgPC0gaQ0KICAgIGNhdCgiVGhpcyBob2xkIG91dCBmb2xkIGlzIiwgdGhpc0ZvbGQsICJcbiIpDQogICAgDQogICAgZm9sZC50cmFpbiA8LSBmaWx0ZXIoZGF0YXNldCwgZGF0YXNldFtbaWRdXSAhPSB0aGlzRm9sZCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQogICAgICBkcGx5cjo6c2VsZWN0KGlkLCBnZW9tZXRyeSxpbnRlcnZhbF9ob3VyLCBpbmRWYXJpYWJsZXMsIGRlcGVuZGVudFZhcmlhYmxlKQ0KICAgIA0KICAgIGZvbGQudGVzdCAgPC0gZmlsdGVyKGRhdGFzZXQsIGRhdGFzZXRbW2lkXV0gPT0gdGhpc0ZvbGQpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIA0KICAgICAgZHBseXI6OnNlbGVjdChpZCwgZ2VvbWV0cnksaW50ZXJ2YWxfaG91ciwgaW5kVmFyaWFibGVzLCBkZXBlbmRlbnRWYXJpYWJsZSkNCiAgICANCiAgICByZWdyZXNzaW9uIDwtIGxtKHBhc3RlMChkZXBlbmRlbnRWYXJpYWJsZSwifi4iKSwgDQogICAgZGF0YSA9IGZvbGQudHJhaW4gJT4lIGRwbHlyOjpzZWxlY3QoLWdlb21ldHJ5KSkNCiAgICANCiAgICB0aGlzUHJlZGljdGlvbiA8LSANCiAgICAgIG11dGF0ZShmb2xkLnRlc3QsIFByZWRpY3Rpb24gPSBwcmVkaWN0KHJlZ3Jlc3Npb24sIGZvbGQudGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpKQ0KICAgIA0KICAgIGFsbFByZWRpY3Rpb25zIDwtDQogICAgICByYmluZChhbGxQcmVkaWN0aW9ucywgdGhpc1ByZWRpY3Rpb24pDQogICAgDQogICAgZ2MoKQ0KICB9DQogIHJldHVybihzdF9zZihhbGxQcmVkaWN0aW9ucykpDQp9DQoNCnJpZGUuY3Jvc3MgPC0gcmlkZTUNCg0KcmlkZS5jcm9zcyA8LSByaWRlLmNyb3NzJT4lbXV0YXRlKGhvdXI9aG91cihpbnRlcnZhbF9ob3VyKSkNCnJlZy5sb2dvX3ZhcnMgPC0gYygiaG91ciIsInN0YXJ0X3N0YXRpb25fbmFtZSIsImRheW90dyIsIlRlbXAiLCJpc1ByZWNpcCIsIldpbmQiLCJsYWdIb3VyIiwibGFnMkhvdXJzIiwgImxhZzNIb3VycyIsImxhZzRIb3VycyIsImxhZzEySG91cnMiLCJsYWcxZGF5IikNCg0KcmVnLmxvZ29faG91ciA8LSBjcm9zc1ZhbGlkYXRlKA0KICBkYXRhc2V0ID0gcmlkZS5jcm9zcywNCiAgaWQgPSAiaG91ciIsDQogIGRlcGVuZGVudFZhcmlhYmxlID0gIlRyaXBfQ291bnQiLA0KICBpbmRWYXJpYWJsZXMgPSByZWcubG9nb192YXJzKQ0KYGBgDQoNCmBgYHtyLCBmaWcuaGVpZ2h0PTEwLCBmaWcud2lkdGg9MjAsaW5jbHVkZT1GQUxTRSxyZXN1bHRzPSJoaWRlIix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpyZWcubG9nb19ob3VyIDwtIHJlZy5sb2dvX2hvdXIlPiUNCiAgbXV0YXRlKE1BRSA9IGFicyhQcmVkaWN0aW9uIC0gVHJpcF9Db3VudCkpJT4lDQogIG5hLm9taXQoKQ0KDQpyZWcubG9nb19ob3VyICU+JQ0KICBnZ3Bsb3QoYWVzKE1BRSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDQwLCBjb2xvdXI9IndoaXRlIiwgZmlsbD1wYWxldHRlMV9tYWluKSArDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMCwgMTAsIGJ5ID0gMSkpICsNCiAgbGFicyh0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIE1BRSAtIExPR08tQ1YiLA0KICAgICAgIHg9Ik1lYW4gQWJzb2x1dGUgRXJyb3IiLCB5PSJUcmlwIENvdW50IikgKw0KICBwbG90VGhlbWUoKQ0KDQpzdF9kcm9wX2dlb21ldHJ5KHJlZy5sb2dvX2hvdXIpICU+JQ0KICBtdXRhdGUoQ291bnRfRGVjaWxlID0gbnRpbGUoVHJpcF9Db3VudCwgMTApKSAlPiUNCiAgZ3JvdXBfYnkoQ291bnRfRGVjaWxlKSAlPiUNCiAgc3VtbWFyaXplKG1lYW5PYnNlcnZlZCA9IG1lYW4oVHJpcF9Db3VudCwgbmEucm09VCksDQogICAgICAgICAgICBtZWFuUHJlZGljdGlvbiA9IG1lYW4oUHJlZGljdGlvbiwgbmEucm09VCkpICU+JQ0KICBnYXRoZXIoVmFyaWFibGUsIFZhbHVlLCAtQ291bnRfRGVjaWxlKSAlPiUNCiAgZ2dwbG90KGFlcyhDb3VudF9EZWNpbGUsIFZhbHVlLCBzaGFwZSA9IFZhcmlhYmxlKSkgKw0KICBnZW9tX3BhdGgoYWVzKGdyb3VwID0gQ291bnRfRGVjaWxlKSwgc2l6ZT0yLGNvbG91ciA9IHBhbGV0dGUxX2Fzc2lzdCkgKw0KICBnZW9tX3BvaW50KHNpemUgPSA2LGNvbG9yPXBhbGV0dGUxX21haW4pICsNCiAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcyA9IGMoMiwgMTcpKSArDQogIHhsaW0oMCwxMCkgKw0KICB5bGltKDAsMTApKw0KICBsYWJzKHRpdGxlID0gIlByZWRpY3RlZCBhbmQgb2JzZXJ2ZWQgY291bnQgYnkgZGVjaWxlIikgKw0KICBwbG90VGhlbWUoKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJib3R0b20iKQ0KDQpzdF9kcm9wX2dlb21ldHJ5KHJlZy5sb2dvX2hvdXIpICU+JQ0KICBzdW1tYXJpemUoTWVhbl9NQUUgPSByb3VuZChtZWFuKE1BRSksIDIpLA0KICAgICAgICAgICAgU0RfTUFFID0gcm91bmQoc2QoTUFFKSwgMikpICU+JQ0KICBrYWJsZShjYXB0aW9uID0gJzxjZW50ZXI+VGFibGUuIE1BRSAmIFNELSBMT0dPLUNWICcsIGFsaWduPXJlcCgnYycsIDIpKSAlPiUNCiAga2FibGVfY2xhc3NpYyhmdWxsX3dpZHRoID0gVCwgaHRtbF9mb250ID0gIkNhbWJyaWEiKQ0KYGBgDQpGcm9tIHRoZSB0YWJsZSB3ZSBjYW4gc2VlIHRoYXQgdGhlIE1BRSBpcyBjb25jZW50cmF0ZWQgaW4gMSwgMiwgMy4gRm9yIHRoZSBjYXNlIG9mIGxvd2VyIG51bWJlciBvZiByZW50YWxzIHdlIHRlbmQgdG8gaGF2ZSBoaWdoZXIgcHJlZGljdGlvbiByZXN1bHRzLCB3aGlsZSBmb3IgdGhlIGNhc2Ugb2YgaGlnaGVyIG51bWJlciBvZiByZW50YWxzIHdlIGhhdmUgbG93ZXIgcHJlZGljdGlvbiByZXN1bHRzLCBhbmQgd2UgaGF2ZSBiZXR0ZXIgcHJlZGljdGlvbiByZXN1bHRzIHdoZW4gdGhlIG51bWJlciBvZiByZW50YWxzIGlzIGFyb3VuZCA3LjUuDQpgYGB7ciwgZmlnLmhlaWdodD0xMCxmaWcud2lkdGg9MjAscmVzdWx0cz0iaGlkZSJ9DQpyZWcubG9nb19ob3VyIDwtIHJlZy5sb2dvX2hvdXIlPiUNCiAgbXV0YXRlKE1BRSA9IGFicyhQcmVkaWN0aW9uIC0gVHJpcF9Db3VudCkpJT4lDQogIG5hLm9taXQoKQ0KDQpncmlkLmFycmFuZ2UobmNvbD0yLA0KcmVnLmxvZ29faG91ciAlPiUNCiAgZ2dwbG90KGFlcyhNQUUpKSArDQogIGdlb21faGlzdG9ncmFtKGJpbnMgPSA0MCwgY29sb3VyPSJ3aGl0ZSIsIGZpbGw9cGFsZXR0ZTFfbWFpbikgKw0KICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDMsIGJ5ID0gMSkpICsNCiAgbGFicyh0aXRsZT0iRGlzdHJpYnV0aW9uIG9mIE1BRSwgTE9HTy1DViIsDQogICAgICAgeD0iTWVhbiBBYnNvbHV0ZSBFcnJvciIsIHk9IlRyaXAgQ291bnQiKSArDQogIHBsb3RUaGVtZSgpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb249ImJvdHRvbSIsDQogICAgYXhpcy50ZXh0ID0gZWxlbWVudF90ZXh0KHNpemU9MjUpKSwNCnN0X2Ryb3BfZ2VvbWV0cnkocmVnLmxvZ29faG91cikgJT4lDQogIG11dGF0ZShDb3VudF9EZWNpbGUgPSBudGlsZShUcmlwX0NvdW50LCAxMCkpICU+JQ0KICBncm91cF9ieShDb3VudF9EZWNpbGUpICU+JQ0KICBzdW1tYXJpemUobWVhbk9ic2VydmVkID0gbWVhbihUcmlwX0NvdW50LCBuYS5ybT1UKSwNCiAgICAgICAgICAgIG1lYW5QcmVkaWN0aW9uID0gbWVhbihQcmVkaWN0aW9uLCBuYS5ybT1UKSkgJT4lDQogIGdhdGhlcihWYXJpYWJsZSwgVmFsdWUsIC1Db3VudF9EZWNpbGUpICU+JQ0KICBnZ3Bsb3QoYWVzKENvdW50X0RlY2lsZSwgVmFsdWUsIHNoYXBlID0gVmFyaWFibGUpKSArDQogIGdlb21fcGF0aChhZXMoZ3JvdXAgPSBDb3VudF9EZWNpbGUpLCBzaXplPTIsY29sb3VyID0gcGFsZXR0ZTFfYXNzaXN0KSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDYsY29sb3I9cGFsZXR0ZTFfbWFpbikgKw0KICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gYygyLCAxNykpICsNCiAgeGxpbSgwLDEwKSArDQogIHlsaW0oMCwxMCkrDQogIGxhYnModGl0bGUgPSAiUHJlZGljdGVkIGFuZCBvYnNlcnZlZCIpICsNCiAgcGxvdFRoZW1lKCkrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9IGMoLjMsLjgpKSkNCg0KYGBgDQoNCmBgYHtyLGluY2x1ZGU9RkFMU0Usd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQ0Kc3RfZHJvcF9nZW9tZXRyeShyZWcubG9nb19ob3VyKSAlPiUNCiAgc3VtbWFyaXplKE1lYW5fTUFFID0gcm91bmQobWVhbihNQUUpLCAyKSwNCiAgICAgICAgICAgIFNEX01BRSA9IHJvdW5kKHNkKE1BRSksIDIpKSAlPiUNCiAga2FibGUoY2FwdGlvbiA9ICc8Y2VudGVyPlRhYmxlLiBNQUUgJiBTRC0gTE9HTy1DViAnLCBhbGlnbj1yZXAoJ2MnLCAyKSkgJT4lDQogIGthYmxlX2NsYXNzaWMoZnVsbF93aWR0aCA9IFQsIGh0bWxfZm9udCA9ICJDYW1icmlhIikNCmBgYA0KDQoNCg0KYGBge3IsIGZpZy53aWR0aD0zMCxmaWcuaGVpZ2h0PTEwLHJlc3VsdHM9ImhpZGUiLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0NCnJlZy5sb2dvX2hvdXIgJT4lDQogIHN0X2Ryb3BfZ2VvbWV0cnkoKSU+JQ0KICBkcGx5cjo6c2VsZWN0KGludGVydmFsX2hvdXIsIHN0YXJ0X3N0YXRpb25fbmFtZSwNCiAgICAgICAgICAgICAgICBUcmlwX0NvdW50LCBQcmVkaWN0aW9uKSAlPiUNCiAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWludGVydmFsX2hvdXIsLXN0YXJ0X3N0YXRpb25fbmFtZSkgJT4lDQogIGdyb3VwX2J5KFZhcmlhYmxlLCBpbnRlcnZhbF9ob3VyKSAlPiUNCiAgc3VtbWFyaXplKFZhbHVlID0gbWVhbihWYWx1ZSkpJT4lDQogIGdncGxvdChhZXMoaW50ZXJ2YWxfaG91ciwgVmFsdWUsIGNvbG91cj1WYXJpYWJsZSkpICsNCiAgZ2VvbV9saW5lKHNpemUgPSAyKSArDQogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gcGFsZXR0ZTIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJNZWFuIFByZWRpY3RlZC9PYnNlcnZlZCByaWRlc2hhcmUgYnkgdGltZSIsDQogICAgeCA9ICJUaW1lIiwgeT0gIlJpZGVzaGFyZSBUcmlwcyIpICsNCiAgcGxvdFRoZW1lKCkrDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQ0KYGBgDQpQcmVzZW50aW5nIHRoZSBNQUUgb24gdGhlIG1hcCBieSBob3VyLCB3ZSBjYW4gc2VlIHRoYXQgdGhlIHByZWRpY3Rpb25zIGFyZSBtb3JlIGFjY3VyYXRlIGluIHRoZSBwcmFjdGljZSBwZXJpb2RzIHdpdGggbG93ZXIgdXNhZ2UgaW4gdGhlIGVhcmx5IG1vcm5pbmcgaG91cnMuIFNwYXRpYWxseSwgdGhlIE1BRSBpcyBoaWdoZXIgaW4gdGhlIGNlbnRyYWwgY2l0eSB0aGFuIGluIHRoZSBzdXJyb3VuZGluZyBhcmVhcy4NCmBgYHtyLCBmaWcuaGVpZ2h0PTE1LGZpZy53aWR0aD0zMCxyZXN1bHRzPSJoaWRlIix3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9DQpMT0dPX1NQRCA8LSByZWcubG9nb19ob3VyICU+JQ0KICBzdF9kcm9wX2dlb21ldHJ5KCklPiUNCiAgZmlsdGVyKGRheW90dyA9PSAiTW9uIiklPiUNCiAgZHBseXI6OnNlbGVjdChob3VyLCBzdGFydF9zdGF0aW9uX25hbWUsTUFFKSAlPiUNCiAgZ2F0aGVyKFZhcmlhYmxlLCBWYWx1ZSwgLWhvdXIsLXN0YXJ0X3N0YXRpb25fbmFtZSkgJT4lDQogIGdyb3VwX2J5KFZhcmlhYmxlLCBob3VyLHN0YXJ0X3N0YXRpb25fbmFtZSwpICU+JQ0KICBzdW1tYXJpemUoVmFsdWUgPSBtZWFuKFZhbHVlKSklPiUNCiAgbGVmdF9qb2luKHJpZGVfc3RhdGlvbiU+JWRwbHlyOjpzZWxlY3QoLUdFT0lEKSxieT0ic3RhcnRfc3RhdGlvbl9uYW1lIiklPiUNCiAgc3Rfc2YoKQ0KDQpnZ3Bsb3QoKSArDQogIGdlb21fc2YoZGF0YT1zdF91bmlvbihQSElUcmFjdHMpLGNvbG9yPSJibGFjayIsc2l6ZT0yLGZpbGwgPSAidHJhbnNwYXJlbnQiKSsNCiAgZ2VvbV9zZihkYXRhPVBISVRyYWN0cyxjb2xvcj0iZ3JleTgwIixzaXplPTAuNSxmaWxsID0gInRyYW5zcGFyZW50IikrDQogIGdlb21fc2YoZGF0YSA9IExPR09fU1BELCBhZXMoY29sb3IgPSBxNChWYWx1ZSkpLCBzaXplID0gMikgKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzPXBhbGV0dGU0LA0KICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiMCIsIjEiLCIyIiwiMisiKSwNCiAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAiTUFFIikgKw0KICBmYWNldF93cmFwKH5ob3VyLCBuY29sID0gOCkrDQogIGxhYnModGl0bGUgPSAnTWVhbiBhYnNvbHV0ZSBlcnJvciBieSBzdGF0aW9uIGFuZCBieSBob3VyIC0gTE9HTycpICsNCiAgbWFwVGhlbWUoKSsgDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLA0KICAgIHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvciA9ICJibGFjayIsZmlsbCA9ICJ0cmFuc3BhcmVudCIsIHNpemUgPSAzKSkNCmBgYA0KDQojIyA1LkNvbmNsdXNpb24NCg0KVGhpcyB0aW1lIHdlIGFuYWx5emVkIHRoZSBwcmVkaWN0ZWQgYmljeWNsZSByZW50YWwgdXNhZ2UgdXNpbmcgZGF0YSBmcm9tIEluZGVnb0Jpa2UgaW4gUGhpbGFkZWxwaGlhLiBGaXJzdCB1c2luZyBmaXZlIGRpZmZlcmVudCByZWdyZXNzaW9uIG1vZGVsIGFsZ29yaXRobXMsIHdlIGNhbiBjb25jbHVkZSB0aGF0IHRoZSBwcmVkaWN0aW9uIGFjY3VyYWN5IG9mIHRoZSBtb2RlbCBjb250aW51ZXMgdG8gaW1wcm92ZSB3aXRoIHRoZSBhZGRpdGlvbiBvZiBzcGF0aWFsIGZlYXR1cmVzIGFuZCB0aW1lIGxhZy4gSG93ZXZlciwgdGhlIG1vZGVsIGl0c2VsZiBzdGlsbCBoYXMgYSB0ZW5kZW5jeSB0byBwcmVkaWN0IGhpZ2hlciBmb3IgbG93ZXIgdXNhZ2UgY2FzZXMgYW5kIGxvd2VyIGZvciBoaWdoZXIgdXNhZ2UgY2FzZXMuIEluIGdlbmVyYWwsIHRoZSBtb2RlbCBzdXBwb3J0cyBhZHZhbmNlIGJpY3ljbGUgZGlzcGF0Y2hpbmcuIFdlIGNhbiBwcmUtZGVwbG95IGJpa2VzIG1vcmUgaW4gYWR2YW5jZSBvZiB0aGUgcHJlZGljdGlvbiByZXN1bHRzLiBGb3IgZXhhbXBsZSwgd2UgY2FuIGluY3JlYXNlIHRoZSBzdXBwbHkgb2YgYmljeWNsZXMgaW4gdGhlIGNlbnRyYWwgY2l0eSBiZWZvcmUgdGhlIGhpZ2ggdGltZSBwZXJpb2QsIHNvIGFzIHRvIGJldHRlciBtZWV0IHRoZSB0cmF2ZWwgZGVtYW5kIG9mIHVzZXJzLg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=