Lab

In this lab, we will be looking at travel patterns in Netherlands, starting with Utrecht. We aim to perform statistical analysis on spatial ‘interaction’ data in R by creating models to predict how people commute.

First, we start with reading in the data.

Reading layer `OGRGeoJSON' from data source `/Users/arroyo/Desktop/stuff/Term 5/02221/Week 11/02221-Lab10/gem_2016.geojson' using driver `GeoJSON'
Simple feature collection with 479 features and 121 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 10425.16 ymin: 306846.2 xmax: 278026.1 ymax: 621876.3
epsg (SRID):    28992
proj4string:    +proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +towgs84=565.4171,50.3319,465.5524,-0.398957,0.343988,-1.87740,4.0725 +units=m +no_defs

We only care about the province of Utrecht for this part of the lab. So let’s select only those municipalitites that fall within this province.

provinces <- st_read("provinces.geojson") %>%
  st_transform(crs = 28992)
Reading layer `OGRGeoJSON' from data source `/Users/arroyo/Desktop/stuff/Term 5/02221/Week 11/02221-Lab10/provinces.geojson' using driver `GeoJSON'
Simple feature collection with 12 features and 3 fields
geometry type:  POLYGON
dimension:      XY
bbox:           xmin: 3.307938 ymin: 50.75037 xmax: 7.227498 ymax: 53.57642
epsg (SRID):    4326
proj4string:    +proj=longlat +datum=WGS84 +no_defs
utrecht <- provinces %>%
  filter(name == 'Utrecht')
munis <- munis %>% 
  filter(st_intersects(utrecht, 
                       st_centroid(munis),
                       sparse = F))

In the above step, we read in the geojson file of all the provinces in Netherlands. Next, we used filter to choose only the areas labelled as “Utrecht”. Finally, we find those municipalities where their centroid intersect the areas labelled as “Utrecht”

Now with the necessary municipalities, we can read in our other data.

commute <- read_csv("commuting.csv") %>%
  select(source, sink, weight) %>%
  rename(interaction = weight)
Parsed with column specification:
cols(
  source = col_integer(),
  sink = col_integer(),
  count = col_integer(),
  weight = col_integer()
)

Again, this set of data is for the entire country. Let’s filter it to only data in Utrecht

munis <- munis %>%
  mutate(id = as.numeric(str_replace(GM_CODE, "GM", "")))
commute <- commute %>% 
  filter(source %in% munis$id & sink %in% munis$id)

Now that we finally have all the data we need, we can do a plot to see how the interactions look like visually. First, we store all the centroid of the different municipalities. Then, we join both the source and the sink centroid to our commute table. Finally, we create lines between every pair of source and sink.

munis.centroid <- st_centroid(munis) %>%
  select(id)
commute <- commute %>%
  left_join(munis.centroid, by = c("source" = "id")) %>%
  left_join(munis.centroid, by = c("sink" = "id")) %>%
  rowwise() %>%
  mutate(geometry = st_combine(c(geometry.x, geometry.y)) %>%
         st_cast("MULTILINESTRING")) %>%
  select(-geometry.x, -geometry.y) %>%
  st_as_sf(crs = 28992)
ggplot(munis) + 
  geom_sf() + 
  geom_sf(data = commute) +
  theme_void() +
  ggtitle("Travel Patterns in Urecht")

The above map shows where people have been travelling to and travelling from. However, this does not show the volume of travel. We need to bind the width of each line to number of people commuting over that line. We will also filter out smaller connections and commuters that never leave their own municipality.

commute <- commute %>%
  filter(sink != source) %>%
  filter(interaction > 20) %>%
  mutate(lineWidth = interaction/max(interaction)*10)
ggplot(munis) + 
  geom_sf() + 
  geom_sf(data = commute, aes(size = lineWidth)) +
  scale_size_identity() + 
  theme_void() + 
  ggtitle("Travel Patterns in Urecht")

Now the plot shows more information. From the plot, it seems like it has one large employment centre with several smaller centres. Now that we can plot the actual number of commuters, let’s see if we can model the commuting relationship based the distance between municipalities, the number of residents and the number of jobs in each municipality.

residents <- read_csv("residents.csv") %>%
  select(id, weight)
Parsed with column specification:
cols(
  id = col_integer(),
  count = col_integer(),
  weight = col_integer()
)
jobs <- read_csv("jobs.csv") %>%
  select(id, weight)
Parsed with column specification:
cols(
  id = col_integer(),
  count = col_integer(),
  weight = col_integer()
)
dist <- st_distance(x = munis.centroid, y = munis.centroid)

The dist matrix is a bit hard to use right now. We need to convert it to a table with three columns (from, to, and distance between the two points)

rownames(dist) <- munis.centroid$id
colnames(dist) <- munis.centroid$id
dist <- list(
  source = rownames(dist)[row(dist)] %||% row(dist),
  sink = colnames(dist)[col(dist)] %||% col(dist),
  distance = dist
) %>%
  map_dfc(as.vector) %>%
  mutate(source = as.numeric(source)) %>%
  mutate(sink = as.numeric(sink))

Now that we have dist ready, we can join all the data together and plot it.

commute <- commute %>%
  left_join(dist) %>%
  left_join(residents, by = c('source' = 'id')) %>%
  rename(residents = weight) %>%
  left_join(jobs, by = c('sink' = 'id')) %>%
  rename(jobs = weight)
Joining, by = c("source", "sink")
ggplot(commute, aes(interaction, distance)) + 
  geom_point() + 
  ggtitle("Relationship Between Distance and Number of Interactions")

The above plot does not look linear. Let’s try using the log function.

ggplot(commute, aes(log(interaction), log(distance))) + 
  geom_point() +
  ggtitle("Logarithmic Relationships Between Distance and Number of Interactions")

Now we can create a naive linear model for it.

lm(data = commute, formula = interaction ~ residents + jobs + distance) %>% summary()

Call:
lm(formula = interaction ~ residents + jobs + distance, data = commute)

Residuals:
   Min     1Q Median     3Q    Max 
-15770  -1150   -309    922  40734 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)  2.553e+03  5.241e+02   4.872 1.47e-06 ***
residents    3.234e-02  3.423e-03   9.449  < 2e-16 ***
jobs         9.666e-02  4.960e-03  19.486  < 2e-16 ***
distance    -1.965e-01  2.255e-02  -8.713  < 2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 4632 on 515 degrees of freedom
Multiple R-squared:  0.5032,    Adjusted R-squared:  0.5003 
F-statistic: 173.9 on 3 and 515 DF,  p-value: < 2.2e-16

From the summary above, we can see that the R-square value is only around 0.5. That is really low. It seems that the linear model does not fit the data well. Perhaps we need to look at the distribution of commuters in order to choose a better model.

ggplot(commute, aes(interaction)) + 
  geom_histogram() + 
  ggtitle("Histogram of Number of Commuters Over Number of Interactions") + 
  xlab("Number of Interactions Between Two Municipality") + 
  ylab("Count")

As we can see from the above plot, the distribution of commuters is very right skewed, seemingly following a poisson distribution. We should try using a poison regression model instead.

model <- glm(data = commute, formula = interaction ~ log(residents) + log(jobs) + log(distance), family = poisson())
r2 <- function(empirical, fitted) {
  return(cor(empirical, fitted)^2)
}
r2(commute$interaction, fitted(model))
[1] 0.8639754

Looking at the R-squared value of our new model, it is fitting really well at 0.864.

tidy(model)

From the coefficients of our new model, we can see, an one percent increase in number of residents, the number of interactions would increase by 0.859/100. Similarly, an one percent increase in number of jobs would result in a 0.984/100 more interactions. On the other hand, an one percent increase in distance would result in a decrease of 1.5/100 interactions.

Now, let’s plot our fitted model and see how it looks.

commute <- commute %>% 
  mutate(fitted = fitted(model)) %>%
  mutate(residual = interaction - fitted) %>%
  mutate(residualSign = sign(residual))
commute <- commute %>% 
  mutate(lineWidth = fitted/ max(fitted) * 10)
  
ggplot(munis) + 
  geom_sf() +
  geom_sf(data = commute, aes(size = lineWidth)) +
  scale_size_identity() + 
  theme_void() + 
  ggtitle("Predicted Travel Patterns in Utrecht")

Comparing this with our earlier plot of the actual values, we can see that the model fits really well. The two plots look really similar.

Now let’s look at the residual to see the difference between the fitted model and the actual data.

commute <- commute %>% mutate(lineWidth = abs(residual) / max(residual) * 10)
ggplot(munis) +
  geom_sf() + 
  geom_sf(data = commute, aes(size = lineWidth, color = factor(residualSign))) +
  scale_size_identity() + 
  theme_void() +
  guides(color=guide_legend(title="Sign of Residual")) +
  ggtitle("Residual of Model")

NA

Assignment

Now, let’s extend our analysis to the entire of Netherlands. First, we read in the data again.

munis <- st_read("gem_2016.geojson", crs = 28992)
Reading layer `OGRGeoJSON' from data source `/Users/arroyo/Desktop/stuff/Term 5/02221/Week 11/02221-Lab10/gem_2016.geojson' using driver `GeoJSON'
st_crs<- : replacing crs does not reproject data; use st_transform for that
Simple feature collection with 479 features and 121 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: 10425.16 ymin: 306846.2 xmax: 278026.1 ymax: 621876.3
epsg (SRID):    28992
proj4string:    +proj=sterea +lat_0=52.15616055555555 +lon_0=5.38763888888889 +k=0.9999079 +x_0=155000 +y_0=463000 +ellps=bessel +towgs84=565.4171,50.3319,465.5524,-0.398957,0.343988,-1.87740,4.0725 +units=m +no_defs
munis <- munis %>% 
  filter(WATER == 'NEE') %>%
  select(GM_CODE, GM_NAAM)
commute <- read_csv("commuting.csv") %>%
  select(source, sink, weight) %>%
  rename(interaction = weight)
Parsed with column specification:
cols(
  source = col_integer(),
  sink = col_integer(),
  count = col_integer(),
  weight = col_integer()
)

Next, we filter and clean the data.

munis <- munis %>%
  mutate(id = as.numeric(str_replace(GM_CODE, "GM", "")))
commute <- commute %>% 
  filter(source %in% munis$id & sink %in% munis$id)

Now, we join the commute data to the municipalities, and create lines between every source, sink pair. While we are at it, let’s filter the data to show only the more significant interactions.

munis.centroid <- st_centroid(munis) %>%
  select(id)
commute <- commute %>%
  left_join(munis.centroid, by = c("source" = "id")) %>%
  left_join(munis.centroid, by = c("sink" = "id")) %>%
  rowwise() %>%
  mutate(geometry = st_combine(c(geometry.x, geometry.y)) %>%
         st_cast("MULTILINESTRING")) %>%
  select(-geometry.x, -geometry.y) %>%
  st_as_sf(crs = 28992)
commute <- commute %>%
  filter(sink != source) %>%
  filter(interaction > 750) %>%
  mutate(lineWidth = interaction/max(interaction)*10)

Now, let’s plot the data and see how it looks like.

ggplot(munis) + 
  geom_sf() + 
  geom_sf(data = commute, aes(size = lineWidth)) +
  scale_size_identity() + 
  theme_void() + 
  ggtitle("Travel Patterns in Netherlands")

The plot looks right. Right now, we could create a summary table to see which municipalities have the most number of people travelling there.

commute.summary <- commute %>% 
  group_by(sink) %>% 
  summarise(incoming = sum(interaction))  %>%
  as.data.frame() %>%
  select(c(sink, incoming)) %>%
  arrange(desc(incoming))
  
munis.name <- munis %>%
  as.data.frame() %>%
  select(c(GM_NAAM, id))
merge(commute.summary, munis.name, by.x = "sink", by.y = "id", sort = FALSE)

Wow~ The top three most popular destinations are Amsterdam, Rotterdam and Utrecht! This makes sense. Amsterdam is the capital of Netherlands, Rotterdam is Europe’s largest port and Utrecht is the fourth largest city in Netherlands.

Now, let’s try running a spatial interaction model for Netherlands. Again, we need the data for distance between municipalities, number of jobs and number of residents.

dist <- st_distance(x = munis.centroid, y = munis.centroid)
rownames(dist) <- munis.centroid$id
colnames(dist) <- munis.centroid$id
dist <- list(
  source = rownames(dist)[row(dist)] %||% row(dist),
  sink = colnames(dist)[col(dist)] %||% col(dist),
  distance = dist
) %>%
  map_dfc(as.vector) %>%
  mutate(source = as.numeric(source)) %>%
  mutate(sink = as.numeric(sink))
commute <- commute %>%
  left_join(dist) %>%
  left_join(residents, by = c('source' = 'id')) %>%
  rename(residents = weight) %>%
  left_join(jobs, by = c('sink' = 'id')) %>%
  rename(jobs = weight)
Joining, by = c("source", "sink")

With all the data we need, we can run the model.

model <- glm(data = commute, formula = interaction ~ log(residents) + log(jobs) + log(distance), family = poisson())
r2 <- function(empirical, fitted) {
  return(cor(empirical, fitted)^2)
}
r2(commute$interaction, fitted(model))
[1] 0.2824222

The R square value is really low. The model looks bad.

tidy(model)

Looking at the coefficients of this model, it suggests that, an one percent increase in number of residents, should cause a 0.49/100 increase in number of interactions. Similarly, an one percent increase in number of jobs would result in 0.644/100 more interactions. On the other hand, an one percent increase in distance would result in a decrease of 1.12/100 interactions.

It is hard to explain why the numbers are so different compared to the model for Utrecht by looking at the numbers alone. Perhaps, there are other significant factors that are in play here, which we are missing out in our model. For instance, it could be more difficult to move between cities as compared to moving within the same city. Factors like these could affect the accuracy of our model

Perhaps we could look at the residuals of the model and try and see what’s going on.

commute <- commute %>% 
  mutate(fitted = fitted(model)) %>%
  mutate(residual = interaction - fitted) %>%
  mutate(residualSign = sign(residual))
commute <- commute %>% 
  mutate(lineWidth = abs(residual)/ max(residual) * 10)
ggplot(munis) +
  geom_sf() + 
  geom_sf(data = commute, aes(size = lineWidth, color = factor(residualSign))) +
  scale_size_identity() + 
  theme_void() +
  guides(color=guide_legend(title="Sign of Residual")) +
  ggtitle("Residual of Model")

Looking at the residual plot, it seems that our model is really far from actual empirical data. Another possible reason why our model failed, could be because of how we are representing our data. In our model, we only take into account the distance between municipalities from their centroids. This suggests that all the people and jobs are located in the center of each municipalities. Across larger land masses, the discrepancies due to this estimation, become a lot more significant. In reality, jobs and people might be more spreaded out, or located closer to the edge of a municipality.

LS0tCnRpdGxlOiAiMDIyMjEgTGFiIDEwIgphdXRob3I6ICJSYXlzb24gTGltIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBMYWIKSW4gdGhpcyBsYWIsIHdlIHdpbGwgYmUgbG9va2luZyBhdCB0cmF2ZWwgcGF0dGVybnMgaW4gTmV0aGVybGFuZHMsIHN0YXJ0aW5nIHdpdGggVXRyZWNodC4gV2UgYWltIHRvIHBlcmZvcm0gc3RhdGlzdGljYWwgYW5hbHlzaXMgb24gc3BhdGlhbCAnaW50ZXJhY3Rpb24nIGRhdGEgaW4gUiBieSBjcmVhdGluZyBtb2RlbHMgdG8gcHJlZGljdCBob3cgcGVvcGxlIGNvbW11dGUuIAoKRmlyc3QsIHdlIHN0YXJ0IHdpdGggcmVhZGluZyBpbiB0aGUgZGF0YS4gCgpgYGB7ciwgZWNobyA9IEZBTFNFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHNmKQpsaWJyYXJ5KGRldnRvb2xzKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShicm9vbSkKbGlicmFyeShnZ3Bsb3QyKQoKbXVuaXMgPC0gc3RfcmVhZCgiZ2VtXzIwMTYuZ2VvanNvbiIsIGNycyA9IDI4OTkyKQptdW5pcyA8LSBtdW5pcyAlPiUgCiAgZmlsdGVyKFdBVEVSID09ICdORUUnKSAlPiUKICBzZWxlY3QoR01fQ09ERSwgR01fTkFBTSkKYGBgCgpXZSBvbmx5IGNhcmUgYWJvdXQgdGhlIHByb3ZpbmNlIG9mIFV0cmVjaHQgZm9yIHRoaXMgcGFydCBvZiB0aGUgbGFiLiBTbyBsZXQncyBzZWxlY3Qgb25seSB0aG9zZSBtdW5pY2lwYWxpdGl0ZXMgdGhhdCBmYWxsIHdpdGhpbiB0aGlzIHByb3ZpbmNlLiAKYGBge3J9CnByb3ZpbmNlcyA8LSBzdF9yZWFkKCJwcm92aW5jZXMuZ2VvanNvbiIpICU+JQogIHN0X3RyYW5zZm9ybShjcnMgPSAyODk5MikKdXRyZWNodCA8LSBwcm92aW5jZXMgJT4lCiAgZmlsdGVyKG5hbWUgPT0gJ1V0cmVjaHQnKQptdW5pcyA8LSBtdW5pcyAlPiUgCiAgZmlsdGVyKHN0X2ludGVyc2VjdHModXRyZWNodCwgCiAgICAgICAgICAgICAgICAgICAgICAgc3RfY2VudHJvaWQobXVuaXMpLAogICAgICAgICAgICAgICAgICAgICAgIHNwYXJzZSA9IEYpKQpgYGAKSW4gdGhlIGFib3ZlIHN0ZXAsIHdlIHJlYWQgaW4gdGhlIGdlb2pzb24gZmlsZSBvZiBhbGwgdGhlIHByb3ZpbmNlcyBpbiBOZXRoZXJsYW5kcy4gTmV4dCwgd2UgdXNlZCBmaWx0ZXIgdG8gY2hvb3NlIG9ubHkgdGhlIGFyZWFzIGxhYmVsbGVkIGFzICJVdHJlY2h0Ii4gRmluYWxseSwgd2UgZmluZCB0aG9zZSBtdW5pY2lwYWxpdGllcyB3aGVyZSB0aGVpciBjZW50cm9pZCBpbnRlcnNlY3QgdGhlIGFyZWFzIGxhYmVsbGVkIGFzICJVdHJlY2h0IgoKTm93IHdpdGggdGhlIG5lY2Vzc2FyeSBtdW5pY2lwYWxpdGllcywgd2UgY2FuIHJlYWQgaW4gb3VyIG90aGVyIGRhdGEuIApgYGB7cn0KY29tbXV0ZSA8LSByZWFkX2NzdigiY29tbXV0aW5nLmNzdiIpICU+JQogIHNlbGVjdChzb3VyY2UsIHNpbmssIHdlaWdodCkgJT4lCiAgcmVuYW1lKGludGVyYWN0aW9uID0gd2VpZ2h0KQpgYGAKQWdhaW4sIHRoaXMgc2V0IG9mIGRhdGEgaXMgZm9yIHRoZSBlbnRpcmUgY291bnRyeS4gTGV0J3MgZmlsdGVyIGl0IHRvIG9ubHkgZGF0YSBpbiBVdHJlY2h0CmBgYHtyfQptdW5pcyA8LSBtdW5pcyAlPiUKICBtdXRhdGUoaWQgPSBhcy5udW1lcmljKHN0cl9yZXBsYWNlKEdNX0NPREUsICJHTSIsICIiKSkpCmNvbW11dGUgPC0gY29tbXV0ZSAlPiUgCiAgZmlsdGVyKHNvdXJjZSAlaW4lIG11bmlzJGlkICYgc2luayAlaW4lIG11bmlzJGlkKQpgYGAKCk5vdyB0aGF0IHdlIGZpbmFsbHkgaGF2ZSBhbGwgdGhlIGRhdGEgd2UgbmVlZCwgd2UgY2FuIGRvIGEgcGxvdCB0byBzZWUgaG93IHRoZSBpbnRlcmFjdGlvbnMgbG9vayBsaWtlIHZpc3VhbGx5LiAKRmlyc3QsIHdlIHN0b3JlIGFsbCB0aGUgY2VudHJvaWQgb2YgdGhlIGRpZmZlcmVudCBtdW5pY2lwYWxpdGllcy4gVGhlbiwgd2Ugam9pbiBib3RoIHRoZSBzb3VyY2UgYW5kIHRoZSBzaW5rIGNlbnRyb2lkIHRvIG91ciBjb21tdXRlIHRhYmxlLiBGaW5hbGx5LCB3ZSBjcmVhdGUgbGluZXMgYmV0d2VlbiBldmVyeSBwYWlyIG9mIHNvdXJjZSBhbmQgc2luay4KYGBge3J9Cm11bmlzLmNlbnRyb2lkIDwtIHN0X2NlbnRyb2lkKG11bmlzKSAlPiUKICBzZWxlY3QoaWQpCgpjb21tdXRlIDwtIGNvbW11dGUgJT4lCiAgbGVmdF9qb2luKG11bmlzLmNlbnRyb2lkLCBieSA9IGMoInNvdXJjZSIgPSAiaWQiKSkgJT4lCiAgbGVmdF9qb2luKG11bmlzLmNlbnRyb2lkLCBieSA9IGMoInNpbmsiID0gImlkIikpICU+JQogIHJvd3dpc2UoKSAlPiUKICBtdXRhdGUoZ2VvbWV0cnkgPSBzdF9jb21iaW5lKGMoZ2VvbWV0cnkueCwgZ2VvbWV0cnkueSkpICU+JQogICAgICAgICBzdF9jYXN0KCJNVUxUSUxJTkVTVFJJTkciKSkgJT4lCiAgc2VsZWN0KC1nZW9tZXRyeS54LCAtZ2VvbWV0cnkueSkgJT4lCiAgc3RfYXNfc2YoY3JzID0gMjg5OTIpCgpnZ3Bsb3QobXVuaXMpICsgCiAgZ2VvbV9zZigpICsgCiAgZ2VvbV9zZihkYXRhID0gY29tbXV0ZSkgKwogIHRoZW1lX3ZvaWQoKSArCiAgZ2d0aXRsZSgiVHJhdmVsIFBhdHRlcm5zIGluIFVyZWNodCIpCmBgYApUaGUgYWJvdmUgbWFwIHNob3dzIHdoZXJlIHBlb3BsZSBoYXZlIGJlZW4gdHJhdmVsbGluZyB0byBhbmQgdHJhdmVsbGluZyBmcm9tLiBIb3dldmVyLCB0aGlzIGRvZXMgbm90IHNob3cgdGhlIHZvbHVtZSBvZiB0cmF2ZWwuIFdlIG5lZWQgdG8gYmluZCB0aGUgd2lkdGggb2YgZWFjaCBsaW5lIHRvIG51bWJlciBvZiBwZW9wbGUgY29tbXV0aW5nIG92ZXIgdGhhdCBsaW5lLiBXZSB3aWxsIGFsc28gZmlsdGVyIG91dCBzbWFsbGVyIGNvbm5lY3Rpb25zIGFuZCBjb21tdXRlcnMgdGhhdCBuZXZlciBsZWF2ZSB0aGVpciBvd24gbXVuaWNpcGFsaXR5LgoKYGBge3J9CmNvbW11dGUgPC0gY29tbXV0ZSAlPiUKICBmaWx0ZXIoc2luayAhPSBzb3VyY2UpICU+JQogIGZpbHRlcihpbnRlcmFjdGlvbiA+IDIwKSAlPiUKICBtdXRhdGUobGluZVdpZHRoID0gaW50ZXJhY3Rpb24vbWF4KGludGVyYWN0aW9uKSoxMCkKCmdncGxvdChtdW5pcykgKyAKICBnZW9tX3NmKCkgKyAKICBnZW9tX3NmKGRhdGEgPSBjb21tdXRlLCBhZXMoc2l6ZSA9IGxpbmVXaWR0aCkpICsKICBzY2FsZV9zaXplX2lkZW50aXR5KCkgKyAKICB0aGVtZV92b2lkKCkgKyAKICBnZ3RpdGxlKCJUcmF2ZWwgUGF0dGVybnMgaW4gVXJlY2h0IikKYGBgCk5vdyB0aGUgcGxvdCBzaG93cyBtb3JlIGluZm9ybWF0aW9uLiBGcm9tIHRoZSBwbG90LCBpdCBzZWVtcyBsaWtlIGl0IGhhcyBvbmUgbGFyZ2UgZW1wbG95bWVudCBjZW50cmUgd2l0aCBzZXZlcmFsIHNtYWxsZXIgY2VudHJlcy4gCk5vdyB0aGF0IHdlIGNhbiBwbG90IHRoZSBhY3R1YWwgbnVtYmVyIG9mIGNvbW11dGVycywgbGV0J3Mgc2VlIGlmIHdlIGNhbiBtb2RlbCB0aGUgY29tbXV0aW5nIHJlbGF0aW9uc2hpcCBiYXNlZCB0aGUgZGlzdGFuY2UgYmV0d2VlbiBtdW5pY2lwYWxpdGllcywgdGhlIG51bWJlciBvZiByZXNpZGVudHMgYW5kIHRoZSBudW1iZXIgb2Ygam9icyBpbiBlYWNoIG11bmljaXBhbGl0eS4gCmBgYHtyfQpyZXNpZGVudHMgPC0gcmVhZF9jc3YoInJlc2lkZW50cy5jc3YiKSAlPiUKICBzZWxlY3QoaWQsIHdlaWdodCkKam9icyA8LSByZWFkX2Nzdigiam9icy5jc3YiKSAlPiUKICBzZWxlY3QoaWQsIHdlaWdodCkKZGlzdCA8LSBzdF9kaXN0YW5jZSh4ID0gbXVuaXMuY2VudHJvaWQsIHkgPSBtdW5pcy5jZW50cm9pZCkKYGBgClRoZSBkaXN0IG1hdHJpeCBpcyBhIGJpdCBoYXJkIHRvIHVzZSByaWdodCBub3cuIFdlIG5lZWQgdG8gY29udmVydCBpdCB0byBhIHRhYmxlIHdpdGggdGhyZWUgY29sdW1ucyAoZnJvbSwgdG8sIGFuZCBkaXN0YW5jZSBiZXR3ZWVuIHRoZSB0d28gcG9pbnRzKQpgYGB7cn0Kcm93bmFtZXMoZGlzdCkgPC0gbXVuaXMuY2VudHJvaWQkaWQKY29sbmFtZXMoZGlzdCkgPC0gbXVuaXMuY2VudHJvaWQkaWQKZGlzdCA8LSBsaXN0KAogIHNvdXJjZSA9IHJvd25hbWVzKGRpc3QpW3JvdyhkaXN0KV0gJXx8JSByb3coZGlzdCksCiAgc2luayA9IGNvbG5hbWVzKGRpc3QpW2NvbChkaXN0KV0gJXx8JSBjb2woZGlzdCksCiAgZGlzdGFuY2UgPSBkaXN0CikgJT4lCiAgbWFwX2RmYyhhcy52ZWN0b3IpICU+JQogIG11dGF0ZShzb3VyY2UgPSBhcy5udW1lcmljKHNvdXJjZSkpICU+JQogIG11dGF0ZShzaW5rID0gYXMubnVtZXJpYyhzaW5rKSkKYGBgCgpOb3cgdGhhdCB3ZSBoYXZlIGRpc3QgcmVhZHksIHdlIGNhbiBqb2luIGFsbCB0aGUgZGF0YSB0b2dldGhlciBhbmQgcGxvdCBpdC4KCmBgYHtyfQpjb21tdXRlIDwtIGNvbW11dGUgJT4lCiAgbGVmdF9qb2luKGRpc3QpICU+JQogIGxlZnRfam9pbihyZXNpZGVudHMsIGJ5ID0gYygnc291cmNlJyA9ICdpZCcpKSAlPiUKICByZW5hbWUocmVzaWRlbnRzID0gd2VpZ2h0KSAlPiUKICBsZWZ0X2pvaW4oam9icywgYnkgPSBjKCdzaW5rJyA9ICdpZCcpKSAlPiUKICByZW5hbWUoam9icyA9IHdlaWdodCkKCmdncGxvdChjb21tdXRlLCBhZXMoaW50ZXJhY3Rpb24sIGRpc3RhbmNlKSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBnZ3RpdGxlKCJSZWxhdGlvbnNoaXAgQmV0d2VlbiBEaXN0YW5jZSBhbmQgTnVtYmVyIG9mIEludGVyYWN0aW9ucyIpCmBgYApUaGUgYWJvdmUgcGxvdCBkb2VzIG5vdCBsb29rIGxpbmVhci4gTGV0J3MgdHJ5IHVzaW5nIHRoZSBsb2cgZnVuY3Rpb24uIApgYGB7cn0KZ2dwbG90KGNvbW11dGUsIGFlcyhsb2coaW50ZXJhY3Rpb24pLCBsb2coZGlzdGFuY2UpKSkgKyAKICBnZW9tX3BvaW50KCkgKwogIGdndGl0bGUoIkxvZ2FyaXRobWljIFJlbGF0aW9uc2hpcHMgQmV0d2VlbiBEaXN0YW5jZSBhbmQgTnVtYmVyIG9mIEludGVyYWN0aW9ucyIpCmBgYAoKTm93IHdlIGNhbiBjcmVhdGUgYSBuYWl2ZSBsaW5lYXIgbW9kZWwgZm9yIGl0LiAKYGBge3J9CmxtKGRhdGEgPSBjb21tdXRlLCBmb3JtdWxhID0gaW50ZXJhY3Rpb24gfiByZXNpZGVudHMgKyBqb2JzICsgZGlzdGFuY2UpICU+JSBzdW1tYXJ5KCkKYGBgCkZyb20gdGhlIHN1bW1hcnkgYWJvdmUsIHdlIGNhbiBzZWUgdGhhdCB0aGUgUi1zcXVhcmUgdmFsdWUgaXMgb25seSBhcm91bmQgMC41LiBUaGF0IGlzIHJlYWxseSBsb3cuIEl0IHNlZW1zIHRoYXQgdGhlIGxpbmVhciBtb2RlbCBkb2VzIG5vdCBmaXQgdGhlIGRhdGEgd2VsbC4gUGVyaGFwcyB3ZSBuZWVkIHRvIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBjb21tdXRlcnMgaW4gb3JkZXIgdG8gY2hvb3NlIGEgYmV0dGVyIG1vZGVsLiAKCmBgYHtyfQpnZ3Bsb3QoY29tbXV0ZSwgYWVzKGludGVyYWN0aW9uKSkgKyAKICBnZW9tX2hpc3RvZ3JhbSgpICsgCiAgZ2d0aXRsZSgiSGlzdG9ncmFtIG9mIE51bWJlciBvZiBDb21tdXRlcnMgT3ZlciBOdW1iZXIgb2YgSW50ZXJhY3Rpb25zIikgKyAKICB4bGFiKCJOdW1iZXIgb2YgSW50ZXJhY3Rpb25zIEJldHdlZW4gVHdvIE11bmljaXBhbGl0eSIpICsgCiAgeWxhYigiQ291bnQiKQpgYGAKCkFzIHdlIGNhbiBzZWUgZnJvbSB0aGUgYWJvdmUgcGxvdCwgdGhlIGRpc3RyaWJ1dGlvbiBvZiBjb21tdXRlcnMgaXMgdmVyeSByaWdodCBza2V3ZWQsIHNlZW1pbmdseSBmb2xsb3dpbmcgYSBwb2lzc29uIGRpc3RyaWJ1dGlvbi4gV2Ugc2hvdWxkIHRyeSB1c2luZyBhIHBvaXNvbiByZWdyZXNzaW9uIG1vZGVsIGluc3RlYWQuIAoKYGBge3J9Cm1vZGVsIDwtIGdsbShkYXRhID0gY29tbXV0ZSwgZm9ybXVsYSA9IGludGVyYWN0aW9uIH4gbG9nKHJlc2lkZW50cykgKyBsb2coam9icykgKyBsb2coZGlzdGFuY2UpLCBmYW1pbHkgPSBwb2lzc29uKCkpCnIyIDwtIGZ1bmN0aW9uKGVtcGlyaWNhbCwgZml0dGVkKSB7CiAgcmV0dXJuKGNvcihlbXBpcmljYWwsIGZpdHRlZCleMikKfQpyMihjb21tdXRlJGludGVyYWN0aW9uLCBmaXR0ZWQobW9kZWwpKQpgYGAKCkxvb2tpbmcgYXQgdGhlIFItc3F1YXJlZCB2YWx1ZSBvZiBvdXIgbmV3IG1vZGVsLCBpdCBpcyBmaXR0aW5nIHJlYWxseSB3ZWxsIGF0IDAuODY0LiAKCmBgYHtyfQp0aWR5KG1vZGVsKQpgYGAKCkZyb20gdGhlIGNvZWZmaWNpZW50cyBvZiBvdXIgbmV3IG1vZGVsLCB3ZSBjYW4gc2VlLCBhbiBvbmUgcGVyY2VudCBpbmNyZWFzZSBpbiBudW1iZXIgb2YgcmVzaWRlbnRzLCB0aGUgbnVtYmVyIG9mIGludGVyYWN0aW9ucyB3b3VsZCBpbmNyZWFzZSBieSAwLjg1OS8xMDAuIFNpbWlsYXJseSwgYW4gb25lIHBlcmNlbnQgaW5jcmVhc2UgaW4gbnVtYmVyIG9mIGpvYnMgd291bGQgcmVzdWx0IGluIGEgMC45ODQvMTAwIG1vcmUgaW50ZXJhY3Rpb25zLiBPbiB0aGUgb3RoZXIgaGFuZCwgYW4gb25lIHBlcmNlbnQgaW5jcmVhc2UgaW4gZGlzdGFuY2Ugd291bGQgcmVzdWx0IGluIGEgZGVjcmVhc2Ugb2YgMS41LzEwMCBpbnRlcmFjdGlvbnMuIAoKTm93LCBsZXQncyBwbG90IG91ciBmaXR0ZWQgbW9kZWwgYW5kIHNlZSBob3cgaXQgbG9va3MuIApgYGB7cn0KY29tbXV0ZSA8LSBjb21tdXRlICU+JSAKICBtdXRhdGUoZml0dGVkID0gZml0dGVkKG1vZGVsKSkgJT4lCiAgbXV0YXRlKHJlc2lkdWFsID0gaW50ZXJhY3Rpb24gLSBmaXR0ZWQpICU+JQogIG11dGF0ZShyZXNpZHVhbFNpZ24gPSBzaWduKHJlc2lkdWFsKSkKCmNvbW11dGUgPC0gY29tbXV0ZSAlPiUgCiAgbXV0YXRlKGxpbmVXaWR0aCA9IGZpdHRlZC8gbWF4KGZpdHRlZCkgKiAxMCkKICAKZ2dwbG90KG11bmlzKSArIAogIGdlb21fc2YoKSArCiAgZ2VvbV9zZihkYXRhID0gY29tbXV0ZSwgYWVzKHNpemUgPSBsaW5lV2lkdGgpKSArCiAgc2NhbGVfc2l6ZV9pZGVudGl0eSgpICsgCiAgdGhlbWVfdm9pZCgpICsgCiAgZ2d0aXRsZSgiUHJlZGljdGVkIFRyYXZlbCBQYXR0ZXJucyBpbiBVdHJlY2h0IikKYGBgCkNvbXBhcmluZyB0aGlzIHdpdGggb3VyIGVhcmxpZXIgcGxvdCBvZiB0aGUgYWN0dWFsIHZhbHVlcywgd2UgY2FuIHNlZSB0aGF0IHRoZSBtb2RlbCBmaXRzIHJlYWxseSB3ZWxsLiBUaGUgdHdvIHBsb3RzIGxvb2sgcmVhbGx5IHNpbWlsYXIuIAoKTm93IGxldCdzIGxvb2sgYXQgdGhlIHJlc2lkdWFsIHRvIHNlZSB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBmaXR0ZWQgbW9kZWwgYW5kIHRoZSBhY3R1YWwgZGF0YS4KCmBgYHtyfQpjb21tdXRlIDwtIGNvbW11dGUgJT4lIG11dGF0ZShsaW5lV2lkdGggPSBhYnMocmVzaWR1YWwpIC8gbWF4KHJlc2lkdWFsKSAqIDEwKQpnZ3Bsb3QobXVuaXMpICsKICBnZW9tX3NmKCkgKyAKICBnZW9tX3NmKGRhdGEgPSBjb21tdXRlLCBhZXMoc2l6ZSA9IGxpbmVXaWR0aCwgY29sb3IgPSBmYWN0b3IocmVzaWR1YWxTaWduKSkpICsKICBzY2FsZV9zaXplX2lkZW50aXR5KCkgKyAKICB0aGVtZV92b2lkKCkgKwogIGd1aWRlcyhjb2xvcj1ndWlkZV9sZWdlbmQodGl0bGU9IlNpZ24gb2YgUmVzaWR1YWwiKSkgKwogIGdndGl0bGUoIlJlc2lkdWFsIG9mIE1vZGVsIikKICAKYGBgCgojIyBBc3NpZ25tZW50CgpOb3csIGxldCdzIGV4dGVuZCBvdXIgYW5hbHlzaXMgdG8gdGhlIGVudGlyZSBvZiBOZXRoZXJsYW5kcy4gRmlyc3QsIHdlIHJlYWQgaW4gdGhlIGRhdGEgYWdhaW4uIAoKYGBge3J9Cm11bmlzIDwtIHN0X3JlYWQoImdlbV8yMDE2Lmdlb2pzb24iLCBjcnMgPSAyODk5MikKbXVuaXMgPC0gbXVuaXMgJT4lIAogIGZpbHRlcihXQVRFUiA9PSAnTkVFJykgJT4lCiAgc2VsZWN0KEdNX0NPREUsIEdNX05BQU0pCgpjb21tdXRlIDwtIHJlYWRfY3N2KCJjb21tdXRpbmcuY3N2IikgJT4lCiAgc2VsZWN0KHNvdXJjZSwgc2luaywgd2VpZ2h0KSAlPiUKICByZW5hbWUoaW50ZXJhY3Rpb24gPSB3ZWlnaHQpCmBgYApOZXh0LCB3ZSBmaWx0ZXIgYW5kIGNsZWFuIHRoZSBkYXRhLiAKCmBgYHtyfQptdW5pcyA8LSBtdW5pcyAlPiUKICBtdXRhdGUoaWQgPSBhcy5udW1lcmljKHN0cl9yZXBsYWNlKEdNX0NPREUsICJHTSIsICIiKSkpCgpjb21tdXRlIDwtIGNvbW11dGUgJT4lIAogIGZpbHRlcihzb3VyY2UgJWluJSBtdW5pcyRpZCAmIHNpbmsgJWluJSBtdW5pcyRpZCkKYGBgCgpOb3csIHdlIGpvaW4gdGhlIGNvbW11dGUgZGF0YSB0byB0aGUgbXVuaWNpcGFsaXRpZXMsIGFuZCBjcmVhdGUgbGluZXMgYmV0d2VlbiBldmVyeSBzb3VyY2UsIHNpbmsgcGFpci4gV2hpbGUgd2UgYXJlIGF0IGl0LCBsZXQncyBmaWx0ZXIgdGhlIGRhdGEgdG8gc2hvdyBvbmx5IHRoZSBtb3JlIHNpZ25pZmljYW50IGludGVyYWN0aW9ucy4gCgpgYGB7cn0KbXVuaXMuY2VudHJvaWQgPC0gc3RfY2VudHJvaWQobXVuaXMpICU+JQogIHNlbGVjdChpZCkKCmNvbW11dGUgPC0gY29tbXV0ZSAlPiUKICBsZWZ0X2pvaW4obXVuaXMuY2VudHJvaWQsIGJ5ID0gYygic291cmNlIiA9ICJpZCIpKSAlPiUKICBsZWZ0X2pvaW4obXVuaXMuY2VudHJvaWQsIGJ5ID0gYygic2luayIgPSAiaWQiKSkgJT4lCiAgcm93d2lzZSgpICU+JQogIG11dGF0ZShnZW9tZXRyeSA9IHN0X2NvbWJpbmUoYyhnZW9tZXRyeS54LCBnZW9tZXRyeS55KSkgJT4lCiAgICAgICAgIHN0X2Nhc3QoIk1VTFRJTElORVNUUklORyIpKSAlPiUKICBzZWxlY3QoLWdlb21ldHJ5LngsIC1nZW9tZXRyeS55KSAlPiUKICBzdF9hc19zZihjcnMgPSAyODk5MikKCmNvbW11dGUgPC0gY29tbXV0ZSAlPiUKICBmaWx0ZXIoc2luayAhPSBzb3VyY2UpICU+JQogIGZpbHRlcihpbnRlcmFjdGlvbiA+IDc1MCkgJT4lCiAgbXV0YXRlKGxpbmVXaWR0aCA9IGludGVyYWN0aW9uL21heChpbnRlcmFjdGlvbikqMTApCmBgYAoKTm93LCBsZXQncyBwbG90IHRoZSBkYXRhIGFuZCBzZWUgaG93IGl0IGxvb2tzIGxpa2UuIAoKYGBge3J9CmdncGxvdChtdW5pcykgKyAKICBnZW9tX3NmKCkgKyAKICBnZW9tX3NmKGRhdGEgPSBjb21tdXRlLCBhZXMoc2l6ZSA9IGxpbmVXaWR0aCkpICsKICBzY2FsZV9zaXplX2lkZW50aXR5KCkgKyAKICB0aGVtZV92b2lkKCkgKyAKICBnZ3RpdGxlKCJUcmF2ZWwgUGF0dGVybnMgaW4gTmV0aGVybGFuZHMiKQpgYGAKClRoZSBwbG90IGxvb2tzIHJpZ2h0LiBSaWdodCBub3csIHdlIGNvdWxkIGNyZWF0ZSBhIHN1bW1hcnkgdGFibGUgdG8gc2VlIHdoaWNoIG11bmljaXBhbGl0aWVzIGhhdmUgdGhlIG1vc3QgbnVtYmVyIG9mIHBlb3BsZSB0cmF2ZWxsaW5nIHRoZXJlLiAKCmBgYHtyfQpjb21tdXRlLnN1bW1hcnkgPC0gY29tbXV0ZSAlPiUgCiAgZ3JvdXBfYnkoc2luaykgJT4lIAogIHN1bW1hcmlzZShpbmNvbWluZyA9IHN1bShpbnRlcmFjdGlvbikpICAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgc2VsZWN0KGMoc2luaywgaW5jb21pbmcpKSAlPiUKICBhcnJhbmdlKGRlc2MoaW5jb21pbmcpKQogIAptdW5pcy5uYW1lIDwtIG11bmlzICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBzZWxlY3QoYyhHTV9OQUFNLCBpZCkpCgptZXJnZShjb21tdXRlLnN1bW1hcnksIG11bmlzLm5hbWUsIGJ5LnggPSAic2luayIsIGJ5LnkgPSAiaWQiLCBzb3J0ID0gRkFMU0UpCmBgYAoKV293fiBUaGUgdG9wIHRocmVlIG1vc3QgcG9wdWxhciBkZXN0aW5hdGlvbnMgYXJlIEFtc3RlcmRhbSwgUm90dGVyZGFtIGFuZCBVdHJlY2h0ISBUaGlzIG1ha2VzIHNlbnNlLiBBbXN0ZXJkYW0gaXMgdGhlIGNhcGl0YWwgb2YgTmV0aGVybGFuZHMsIFJvdHRlcmRhbSBpcyBFdXJvcGUncyBsYXJnZXN0IHBvcnQgYW5kIFV0cmVjaHQgaXMgdGhlIGZvdXJ0aCBsYXJnZXN0IGNpdHkgaW4gTmV0aGVybGFuZHMuIAoKTm93LCBsZXQncyB0cnkgcnVubmluZyBhIHNwYXRpYWwgaW50ZXJhY3Rpb24gbW9kZWwgZm9yIE5ldGhlcmxhbmRzLiBBZ2Fpbiwgd2UgbmVlZCB0aGUgZGF0YSBmb3IgZGlzdGFuY2UgYmV0d2VlbiBtdW5pY2lwYWxpdGllcywgbnVtYmVyIG9mIGpvYnMgYW5kIG51bWJlciBvZiByZXNpZGVudHMuIAoKYGBge3J9CmRpc3QgPC0gc3RfZGlzdGFuY2UoeCA9IG11bmlzLmNlbnRyb2lkLCB5ID0gbXVuaXMuY2VudHJvaWQpCgpyb3duYW1lcyhkaXN0KSA8LSBtdW5pcy5jZW50cm9pZCRpZApjb2xuYW1lcyhkaXN0KSA8LSBtdW5pcy5jZW50cm9pZCRpZApkaXN0IDwtIGxpc3QoCiAgc291cmNlID0gcm93bmFtZXMoZGlzdClbcm93KGRpc3QpXSAlfHwlIHJvdyhkaXN0KSwKICBzaW5rID0gY29sbmFtZXMoZGlzdClbY29sKGRpc3QpXSAlfHwlIGNvbChkaXN0KSwKICBkaXN0YW5jZSA9IGRpc3QKKSAlPiUKICBtYXBfZGZjKGFzLnZlY3RvcikgJT4lCiAgbXV0YXRlKHNvdXJjZSA9IGFzLm51bWVyaWMoc291cmNlKSkgJT4lCiAgbXV0YXRlKHNpbmsgPSBhcy5udW1lcmljKHNpbmspKQoKY29tbXV0ZSA8LSBjb21tdXRlICU+JQogIGxlZnRfam9pbihkaXN0KSAlPiUKICBsZWZ0X2pvaW4ocmVzaWRlbnRzLCBieSA9IGMoJ3NvdXJjZScgPSAnaWQnKSkgJT4lCiAgcmVuYW1lKHJlc2lkZW50cyA9IHdlaWdodCkgJT4lCiAgbGVmdF9qb2luKGpvYnMsIGJ5ID0gYygnc2luaycgPSAnaWQnKSkgJT4lCiAgcmVuYW1lKGpvYnMgPSB3ZWlnaHQpCgpgYGAKCldpdGggYWxsIHRoZSBkYXRhIHdlIG5lZWQsIHdlIGNhbiBydW4gdGhlIG1vZGVsLiAKCmBgYHtyfQptb2RlbCA8LSBnbG0oZGF0YSA9IGNvbW11dGUsIGZvcm11bGEgPSBpbnRlcmFjdGlvbiB+IGxvZyhyZXNpZGVudHMpICsgbG9nKGpvYnMpICsgbG9nKGRpc3RhbmNlKSwgZmFtaWx5ID0gcG9pc3NvbigpKQpyMiA8LSBmdW5jdGlvbihlbXBpcmljYWwsIGZpdHRlZCkgewogIHJldHVybihjb3IoZW1waXJpY2FsLCBmaXR0ZWQpXjIpCn0KcjIoY29tbXV0ZSRpbnRlcmFjdGlvbiwgZml0dGVkKG1vZGVsKSkKCmBgYAoKVGhlIFIgc3F1YXJlIHZhbHVlIGlzIHJlYWxseSBsb3cuIFRoZSBtb2RlbCBsb29rcyBiYWQuIAoKYGBge3J9CnRpZHkobW9kZWwpCmBgYApMb29raW5nIGF0IHRoZSBjb2VmZmljaWVudHMgb2YgdGhpcyBtb2RlbCwgaXQgc3VnZ2VzdHMgdGhhdCwgYW4gb25lIHBlcmNlbnQgaW5jcmVhc2UgaW4gbnVtYmVyIG9mIHJlc2lkZW50cywgc2hvdWxkIGNhdXNlIGEgMC40OS8xMDAgaW5jcmVhc2UgaW4gbnVtYmVyIG9mIGludGVyYWN0aW9ucy4gU2ltaWxhcmx5LCBhbiBvbmUgcGVyY2VudCBpbmNyZWFzZSBpbiBudW1iZXIgb2Ygam9icyB3b3VsZCByZXN1bHQgaW4gMC42NDQvMTAwIG1vcmUgaW50ZXJhY3Rpb25zLiBPbiB0aGUgb3RoZXIgaGFuZCwgYW4gb25lIHBlcmNlbnQgaW5jcmVhc2UgaW4gZGlzdGFuY2Ugd291bGQgcmVzdWx0IGluIGEgZGVjcmVhc2Ugb2YgMS4xMi8xMDAgaW50ZXJhY3Rpb25zLiAKCkl0IGlzIGhhcmQgdG8gZXhwbGFpbiB3aHkgdGhlIG51bWJlcnMgYXJlIHNvIGRpZmZlcmVudCBjb21wYXJlZCB0byB0aGUgbW9kZWwgZm9yIFV0cmVjaHQgYnkgbG9va2luZyBhdCB0aGUgbnVtYmVycyBhbG9uZS4gUGVyaGFwcywgdGhlcmUgYXJlIG90aGVyIHNpZ25pZmljYW50IGZhY3RvcnMgdGhhdCBhcmUgaW4gcGxheSBoZXJlLCB3aGljaCB3ZSBhcmUgbWlzc2luZyBvdXQgaW4gb3VyIG1vZGVsLiBGb3IgaW5zdGFuY2UsIGl0IGNvdWxkIGJlIG1vcmUgZGlmZmljdWx0IHRvIG1vdmUgYmV0d2VlbiBjaXRpZXMgYXMgY29tcGFyZWQgdG8gbW92aW5nIHdpdGhpbiB0aGUgc2FtZSBjaXR5LiBGYWN0b3JzIGxpa2UgdGhlc2UgY291bGQgYWZmZWN0IHRoZSBhY2N1cmFjeSBvZiBvdXIgbW9kZWwKClBlcmhhcHMgd2UgY291bGQgbG9vayBhdCB0aGUgcmVzaWR1YWxzIG9mIHRoZSBtb2RlbCBhbmQgdHJ5IGFuZCBzZWUgd2hhdCdzIGdvaW5nIG9uLiAKYGBge3J9CmNvbW11dGUgPC0gY29tbXV0ZSAlPiUgCiAgbXV0YXRlKGZpdHRlZCA9IGZpdHRlZChtb2RlbCkpICU+JQogIG11dGF0ZShyZXNpZHVhbCA9IGludGVyYWN0aW9uIC0gZml0dGVkKSAlPiUKICBtdXRhdGUocmVzaWR1YWxTaWduID0gc2lnbihyZXNpZHVhbCkpCgpjb21tdXRlIDwtIGNvbW11dGUgJT4lIAogIG11dGF0ZShsaW5lV2lkdGggPSBhYnMocmVzaWR1YWwpLyBtYXgocmVzaWR1YWwpICogMTApCgpnZ3Bsb3QobXVuaXMpICsKICBnZW9tX3NmKCkgKyAKICBnZW9tX3NmKGRhdGEgPSBjb21tdXRlLCBhZXMoc2l6ZSA9IGxpbmVXaWR0aCwgY29sb3IgPSBmYWN0b3IocmVzaWR1YWxTaWduKSkpICsKICBzY2FsZV9zaXplX2lkZW50aXR5KCkgKyAKICB0aGVtZV92b2lkKCkgKwogIGd1aWRlcyhjb2xvcj1ndWlkZV9sZWdlbmQodGl0bGU9IlNpZ24gb2YgUmVzaWR1YWwiKSkgKwogIGdndGl0bGUoIlJlc2lkdWFsIG9mIE1vZGVsIikKYGBgCgpMb29raW5nIGF0IHRoZSByZXNpZHVhbCBwbG90LCBpdCBzZWVtcyB0aGF0IG91ciBtb2RlbCBpcyByZWFsbHkgZmFyIGZyb20gYWN0dWFsIGVtcGlyaWNhbCBkYXRhLiBBbm90aGVyIHBvc3NpYmxlIHJlYXNvbiB3aHkgb3VyIG1vZGVsIGZhaWxlZCwgY291bGQgYmUgYmVjYXVzZSBvZiBob3cgd2UgYXJlIHJlcHJlc2VudGluZyBvdXIgZGF0YS4gSW4gb3VyIG1vZGVsLCB3ZSBvbmx5IHRha2UgaW50byBhY2NvdW50IHRoZSBkaXN0YW5jZSBiZXR3ZWVuIG11bmljaXBhbGl0aWVzIGZyb20gdGhlaXIgY2VudHJvaWRzLiBUaGlzIHN1Z2dlc3RzIHRoYXQgYWxsIHRoZSBwZW9wbGUgYW5kIGpvYnMgYXJlIGxvY2F0ZWQgaW4gdGhlIGNlbnRlciBvZiBlYWNoIG11bmljaXBhbGl0aWVzLiBBY3Jvc3MgbGFyZ2VyIGxhbmQgbWFzc2VzLCB0aGUgZGlzY3JlcGFuY2llcyBkdWUgdG8gdGhpcyBlc3RpbWF0aW9uLCBiZWNvbWUgYSBsb3QgbW9yZSBzaWduaWZpY2FudC4gSW4gcmVhbGl0eSwgam9icyBhbmQgcGVvcGxlIG1pZ2h0IGJlIG1vcmUgc3ByZWFkZWQgb3V0LCBvciBsb2NhdGVkIGNsb3NlciB0byB0aGUgZWRnZSBvZiBhIG11bmljaXBhbGl0eS4gCgoK