Overview

This is a reproduction of the workflow for model basics, which comes from Chapter 23 of R for Data Science. The purpose was to learn the concepts. I skipped the simple model section and instead concentrate of visualizing models, which adds functionality from the modelr package. I deviate a little from the book because focusing on the new modelr package was what helped in my learning. Just note that what is here has some differences than the chapter, mostly extending it for my learning benefit.


Prerequisites

library(tidyverse) # ggplot2, purrr, dplyr, tidyr, readr, tibble
library(modelr)
options(na.action = na.warn)

Visualizing Models

Use modelr::data_grid() to make evenly spaced grid for modelling. This is a little unintuitive, but examples help. I’ll step through the modelr::sim1 data set.

modelr::sim1 # sim1 data set

Visualize the data set:

sim1 %>% 
    ggplot(aes(x, y)) +
    geom_point()

Create a model:

sim1_mod <- lm(y ~ x, data = sim1)

Data grid

Initially this was confusing, but after exploring the documentation I realized data_grid() allows us to:

  1. Create an evenly spaced frame for modelling
  2. Expand the grid for use with multiple variables

This capability is important because often the data is bunched up (typically with continuous variables), but modeling the x-axis is much better when the x-points are evenly distributed.

As it turns out, sim1 is evenly spaced, so the data grid we create is not gaining us much (it does reduce the repetition in x-points), but it becomes very important when the data is not uniformly spaced.

We want to model as a function of x, so we setup our grid along the range of x:

grid <- sim1 %>%
    data_grid(x)
grid

Add Predictions

Next, use add_predictions() to apply the model sim1_mod predictions to the grid.

grid <- grid %>%
    add_predictions(sim1_mod)
grid

Visualize the predictions by applying both the sim1 data and the grid data to a ggplot:

ggplot(data = sim1) +
    geom_point(aes(x = x, y = y)) +
    # Overlay grid predictions on ggplot
    geom_line(aes(x = x, y = pred), data = grid, color = 'red', size = 1)

Add Residuals

The predictions let us know what pattern the model captured, and the residuals let us know what the model has missed. The key is to look for pattern in the residuals. Use add_residuals() to add residuals to the original sim data. Note that it’s important to add residuals to the initial data frame (and not grid!) because the residuals show how far the original data points are from the model. The data grid is for visualizing the model predictions, while the original data is for assessing the residuals.

sim1 <- sim1 %>%
    add_residuals(sim1_mod) 
sim1

Visualize the residuals:

ggplot(sim1, aes(x, resid)) + 
    geom_ref_line(h = 0) +
    geom_point() +
    scale_y_continuous(limits = c(-5,5))

The randomness of the residuals means the model, sim1_mod, did a good job of capturing the pattern.

Exercises

Exercise 1

Instead of using lm() to fit a straight line, you can use loess() to fit a smooth curve. Repeat the process of model fitting, grid generation, predictions, and visualization on sim1 using loess() instead of lm(). How does the result compare to geom_smooth()?

Generate the model and the data grid:

# loess model
sim1_mod_loess <- loess(y ~ x, data=sim1)
# Generate grid
grid <- sim1 %>%
    data_grid(x) %>%
    add_predictions(sim1_mod_loess) 

Visualize the predictions and compare with geom_smooth():

ggplot(sim1, aes(x = x)) +
    geom_point(aes(y = y)) +
    geom_smooth(aes(y = y)) +
    geom_line(aes(y = pred), data = grid, colour = "red", size = .5)

The loess model compares very closely with the line generated by loess_smooth().

Exercise 3

What does geom_ref_line() do? What package does it come from? Why is displaying a reference line in plots showing residuals useful and important?

We need to visualize the residuals. First, add the residuals to sim1. Note that this replaces the previous resid column unless you rename (i.e. add_residuals(sim1_mod_loess, "some_new_name_for_loess_residuals")).

sim1 <- sim1 %>%
    add_residuals(sim1_mod_loess)
sim1

Now, we can visualize the residuals using the geom_ref_line().

ggplot(sim1, aes(x, resid)) + 
    geom_ref_line(h = 0, size = 2, colour = 'steelblue') +
    geom_point() +
    scale_y_continuous(limits = c(-5,5))

We can see that a reference line has been added at y = 0. This helps us visualize the pattern around the reference line.


Formulas and Model Families

Note that I skip the model_matrix() function, which converts a data set and a function (e.g. y ~ x) to a model matrix that describes what R is modelling under the hood. If interested, you should definitely check the documentation.

Categorical Variables

We analyze modelr::sim2 data set, which contains categorical variables for x (a, b, c, d).

sim2

Let’s visualize:

ggplot(sim2, aes(x, y)) +
    geom_point()

Next, fit a model and add predictions:

mod2 <- lm(y ~ x, data = sim2)
grid <- sim2 %>%
    data_grid(x) %>%
    add_predictions(mod2)
grid

Visualize the data with the predictions. Remember, grid contains the predictions at each uniquely spaced interval, which for sim2 is the categories.

ggplot(sim2, aes(x = x)) +
    geom_point(aes(y = y)) +
    geom_point(data = grid, aes(y = pred), color = "red", size = 4)

Interactions (Continuous and Categorical Variables)

Data set sim3 contains both continuous and categorical variables to include in model:

sim3

We want to model y using both x1 and x2. First, let’s visualize using a scatter plot for continuous y vs x1 and using a color for the categorical x2:

ggplot(sim3, aes(x1, y, color = x2)) +
    geom_point()

Two ways to fit the data:

mod1 <- lm(y ~ x1 + x2, data = sim3)
mod2 <- lm(y ~ x1 * x2, data = sim3)

To visualize these models we need two new tricks:

  1. We have two predictors, so we need to give data_grid() both variables. It finds all the unique values of x1 and x2 and then generates all combinations.

  2. To generate predictions from both models simultaneously, we can use gather_predictions() which adds each prediction as a row. The complement of gather_predictions() is spread_predictions() which adds each prediction to a new column.

Using gather_predictions():

grid <- sim3 %>%
    data_grid(x1, x2) %>%
    gather_predictions(mod1, mod2)
grid

Using spread_predictions():

grid_spread <- sim3 %>%
    data_grid(x1, x2) %>%
    spread_predictions(mod1, mod2)
grid_spread

Note that the only difference between gather and spread is the layout of the predictions. Gather returns predictions in a tidy format (good for plotting) whereas spread returns in a more intuitive format with predictions in model columns. We’ll continue with the gather format.

We can visualize the results using a facet_wrap():

ggplot(data = sim3, aes(x = x1, y = y, color = x2)) + 
    geom_point() +
    geom_line(data = grid, aes(y = pred)) + # Overlay predictions
    facet_wrap(~ model) # facet on model variable

We can also take this one step further using facet_grid, which really shows off the difference in model performance:

ggplot(data = sim3, aes(x = x1, y = y, color = x2)) + 
    geom_point() +
    geom_line(data = grid, aes(y = pred)) + # Overlay predictions
    facet_grid(model ~ x2) # facet on both x2 and model variable

Finally, we can take a look at the residuals to see what pattern is leftover:

sim3 <- sim3 %>%
    gather_residuals(mod1, mod2)
ggplot(sim3, aes(x1, resid, color = x2)) +
    geom_ref_line(h = 0) +
    geom_point() + 
    facet_grid(model ~ x2)

We can qualitatively see that mod2 does a much better job of removing the pattern versus mod1.

Interactions (two continuous)

Let’s take a look at sim4:

sim4

We want to use x1 and x2 to predict y, but now both predictors are categorical. We create two models, one without interactions and one with:

mod1 <- lm(y ~ x1 + x2, data = sim4)
mod2 <- lm(y ~ x1 * x2, data = sim4)

Quick digression on combining data_grid() with seq_range()

Before we jump into predicting using models, it’s important to understand the data. Let’s take a look at x1 first:

sim4$x1 %>% unique()
 [1] -1.0000000 -0.7777778 -0.5555556 -0.3333333 -0.1111111  0.1111111  0.3333333  0.5555556
 [9]  0.7777778  1.0000000

x1 has ten uniformly spaced numeric values. A convenient way to model numeric data is to use data_grid() in conjunction with seq_range() to control the grid size. seq_range() takes the range of the values (in our case x1) and generates a uniform sequence over a specified interval number.

sim4$x1 %>% seq_range(n = 5)
[1] -1.0 -0.5  0.0  0.5  1.0

Important arguments to seq_range() (besides n):

  • pretty = TRUE: Generates a pretty sequence with values rounded.
seq_range(c(0.0123, 0.923423), n = 5)
[1] 0.0123000 0.2400808 0.4678615 0.6956423 0.9234230
seq_range(c(0.0123, 0.923423), n = 5, pretty = TRUE)
[1] 0.0 0.2 0.4 0.6 0.8 1.0
  • trim = 0.2: Trims off 20% of the tail values (i.e. -1 becomes -0.8)
sim4$x1 %>% seq_range(n = 5, trim = 0.2) %>% round(2)
[1] -0.8 -0.4  0.0  0.4  0.8
  • expand = 0.2: Expands the range by 20%.
sim4$x1 %>% seq_range(n = 5, expand = 0.2)
[1] -1.2 -0.6  0.0  0.6  1.2

Back to modelling using data_grid() and seq_range()

What we want to do is use the models to generate predictions for visualization. We combine data_grid() with seq_range() to create a 5 x 5 grid for model 1 and a 5 x 5 grid for model 2:

grid <- sim4 %>%
    data_grid(
        x1 = seq_range(x1, n = 5),
        x2 = seq_range(x2, n = 5)
    ) %>%
    gather_predictions(mod1, mod2)
grid

Visualizing is a challenge with multiple predictors, but we can accomplish using a combination of color and faceting:

ggplot(data = sim4, 
       aes(x = x1, 
           y = y, 
           color = x2 %>% round(2) %>% factor(),
           group = x2
           )
       ) + 
    geom_point() + 
    geom_line(data = grid, 
              aes(x = x1,
                  y = pred,
                  color = x2 %>% round(2) %>% factor(),
                  group = x2
                  )
              ) +
    labs(color = "x2") +
    facet_wrap(~ model, ncol = 1)

The interaction allows the model to consider both the x1 and x2 simultaneously.

Transformations

Transformations can be used to approximate non-linear functions.

Create a non-linear data set:

set.seed(150)
sim5 <- tibble(
    x = seq(0, 3.5 * pi, length.out = 50),
    y = 4 * sin(x) + rnorm(length(x))
)
sim5

Visualize the data set:

ggplot(sim5, aes(x, y)) + 
    geom_point()

Fit models to the data using natural splines:

library(splines)
mod1 <- lm(y ~ ns(x, 1), data = sim5)
mod2 <- lm(y ~ ns(x, 2), data = sim5)
mod3 <- lm(y ~ ns(x, 3), data = sim5)
mod4 <- lm(y ~ ns(x, 4), data = sim5)
mod5 <- lm(y ~ ns(x, 5), data = sim5)

Create the data grid:

grid <- sim5 %>%
    data_grid(x = seq_range(x, n = 50, expand = 0.1)) %>%
    gather_predictions(mod1, mod2, mod3, mod4, mod5)
grid

Visualize the predictions:

ggplot(sim5, aes(x, y)) +
    geom_point() +
    geom_line(data = grid, aes(y = pred), color = "red", size = 2) +
    facet_wrap(~ model)


Missing Values

R defaults to silently drop NA’s. To get a warning, set options(na.action = na.warn).

options(na.action  = na.warn)
df <- tribble(
    ~x, ~y,
    1, 2.2,
    2, NA,
    3, 3.5,
    4, 8.3,
    NA, 10
)
mod <- lm(y ~ x, data = df)
Dropping 2 rows with missing values

Other Model Families

In addition, the Caret Package (caret), is a set of functions that streamlines the process for creating predictive models.

LS0tDQp0aXRsZTogJ0NoYXB0ZXIgMjM6IE1vZGVsIEJhc2ljcycNCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0aGVtZTogZmxhdGx5DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDINCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0aGVtZTogZmxhdGx5DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDINCi0tLQ0KDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0KbGlicmFyeShrbml0cikNCm9wdHNfY2h1bmskc2V0KGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTMsIGZpZy5hbGlnbj0nY2VudGVyJywNCiAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFKQ0KYGBgDQoNCiMgT3ZlcnZpZXcNCg0KVGhpcyBpcyBhIHJlcHJvZHVjdGlvbiBvZiB0aGUgd29ya2Zsb3cgZm9yIG1vZGVsIGJhc2ljcywgd2hpY2ggY29tZXMgZnJvbSBbQ2hhcHRlciAyMyBvZiBfUiBmb3IgRGF0YSBTY2llbmNlX10oaHR0cDovL3I0ZHMuaGFkLmNvLm56L21hbnktbW9kZWxzLmh0bWwpLiBUaGUgcHVycG9zZSB3YXMgdG8gbGVhcm4gdGhlIGNvbmNlcHRzLiBJIHNraXBwZWQgdGhlIHNpbXBsZSBtb2RlbCBzZWN0aW9uIGFuZCBpbnN0ZWFkIGNvbmNlbnRyYXRlIG9mIHZpc3VhbGl6aW5nIG1vZGVscywgd2hpY2ggYWRkcyBmdW5jdGlvbmFsaXR5IGZyb20gdGhlIGBtb2RlbHJgIHBhY2thZ2UuIEkgZGV2aWF0ZSBhIGxpdHRsZSBmcm9tIHRoZSBib29rIGJlY2F1c2UgZm9jdXNpbmcgb24gdGhlIG5ldyBgbW9kZWxyYCBwYWNrYWdlIHdhcyB3aGF0IGhlbHBlZCBpbiBteSBsZWFybmluZy4gSnVzdCBub3RlIHRoYXQgd2hhdCBpcyBoZXJlIGhhcyBzb21lIGRpZmZlcmVuY2VzIHRoYW4gdGhlIGNoYXB0ZXIsIG1vc3RseSBleHRlbmRpbmcgaXQgZm9yIG15IGxlYXJuaW5nIGJlbmVmaXQuDQoNCi0tLS0NCg0KIyBQcmVyZXF1aXNpdGVzDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpICMgZ2dwbG90MiwgcHVycnIsIGRwbHlyLCB0aWR5ciwgcmVhZHIsIHRpYmJsZQ0KbGlicmFyeShtb2RlbHIpDQpvcHRpb25zKG5hLmFjdGlvbiA9IG5hLndhcm4pDQpgYGANCg0KLS0tLQ0KDQojIFZpc3VhbGl6aW5nIE1vZGVscw0KDQpVc2UgYG1vZGVscjo6ZGF0YV9ncmlkKClgIHRvIG1ha2UgZXZlbmx5IHNwYWNlZCBncmlkIGZvciBtb2RlbGxpbmcuIFRoaXMgaXMgYSBsaXR0bGUgdW5pbnR1aXRpdmUsIGJ1dCBleGFtcGxlcyBoZWxwLiBJJ2xsIHN0ZXAgdGhyb3VnaCB0aGUgYG1vZGVscjo6c2ltMWAgZGF0YSBzZXQuDQoNCmBgYHtyfQ0KbW9kZWxyOjpzaW0xICMgc2ltMSBkYXRhIHNldA0KYGBgDQoNClZpc3VhbGl6ZSB0aGUgZGF0YSBzZXQ6DQoNCmBgYHtyfQ0Kc2ltMSAlPiUgDQogICAgZ2dwbG90KGFlcyh4LCB5KSkgKw0KICAgIGdlb21fcG9pbnQoKQ0KYGBgDQoNCkNyZWF0ZSBhIG1vZGVsOg0KDQpgYGB7cn0NCnNpbTFfbW9kIDwtIGxtKHkgfiB4LCBkYXRhID0gc2ltMSkNCmBgYA0KDQoNCiMjIERhdGEgZ3JpZA0KDQpJbml0aWFsbHkgdGhpcyB3YXMgY29uZnVzaW5nLCBidXQgYWZ0ZXIgZXhwbG9yaW5nIHRoZSBkb2N1bWVudGF0aW9uIEkgcmVhbGl6ZWQgYGRhdGFfZ3JpZCgpYCBhbGxvd3MgdXMgdG86DQoNCjEuIENyZWF0ZSBhbiBldmVubHkgc3BhY2VkIGZyYW1lIGZvciBtb2RlbGxpbmcNCjIuIEV4cGFuZCB0aGUgZ3JpZCBmb3IgdXNlIHdpdGggbXVsdGlwbGUgdmFyaWFibGVzDQoNClRoaXMgY2FwYWJpbGl0eSBpcyBpbXBvcnRhbnQgYmVjYXVzZSBvZnRlbiB0aGUgZGF0YSBpcyBidW5jaGVkIHVwICh0eXBpY2FsbHkgd2l0aCBjb250aW51b3VzIHZhcmlhYmxlcyksIGJ1dCBtb2RlbGluZyB0aGUgeC1heGlzIGlzIG11Y2ggYmV0dGVyIHdoZW4gdGhlIHgtcG9pbnRzIGFyZSBldmVubHkgZGlzdHJpYnV0ZWQuICANCg0KQXMgaXQgdHVybnMgb3V0LCBgc2ltMWAgaXMgZXZlbmx5IHNwYWNlZCwgc28gdGhlIGRhdGEgZ3JpZCB3ZSBjcmVhdGUgaXMgbm90IGdhaW5pbmcgdXMgbXVjaCAoaXQgZG9lcyByZWR1Y2UgdGhlIHJlcGV0aXRpb24gaW4geC1wb2ludHMpLCBidXQgaXQgYmVjb21lcyB2ZXJ5IGltcG9ydGFudCB3aGVuIHRoZSBkYXRhIGlzIG5vdCB1bmlmb3JtbHkgc3BhY2VkLg0KDQpXZSB3YW50IHRvIG1vZGVsIGFzIGEgZnVuY3Rpb24gb2YgYHhgLCBzbyB3ZSBzZXR1cCBvdXIgZ3JpZCBhbG9uZyB0aGUgcmFuZ2Ugb2YgYHhgOg0KDQpgYGB7cn0NCmdyaWQgPC0gc2ltMSAlPiUNCiAgICBkYXRhX2dyaWQoeCkNCmdyaWQNCmBgYA0KDQojIyBBZGQgUHJlZGljdGlvbnMNCg0KTmV4dCwgdXNlIGBhZGRfcHJlZGljdGlvbnMoKWAgdG8gYXBwbHkgdGhlIG1vZGVsIGBzaW0xX21vZGAgcHJlZGljdGlvbnMgdG8gdGhlIGdyaWQuDQoNCmBgYHtyfQ0KZ3JpZCA8LSBncmlkICU+JQ0KICAgIGFkZF9wcmVkaWN0aW9ucyhzaW0xX21vZCkNCmdyaWQNCmBgYA0KDQpWaXN1YWxpemUgdGhlIHByZWRpY3Rpb25zIGJ5IGFwcGx5aW5nIGJvdGggdGhlIGBzaW0xYCBkYXRhIGFuZCB0aGUgYGdyaWRgIGRhdGEgdG8gYSBnZ3Bsb3Q6DQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBzaW0xKSArDQogICAgZ2VvbV9wb2ludChhZXMoeCA9IHgsIHkgPSB5KSkgKw0KICAgICMgT3ZlcmxheSBncmlkIHByZWRpY3Rpb25zIG9uIGdncGxvdA0KICAgIGdlb21fbGluZShhZXMoeCA9IHgsIHkgPSBwcmVkKSwgZGF0YSA9IGdyaWQsIGNvbG9yID0gJ3JlZCcsIHNpemUgPSAxKQ0KYGBgDQoNCiMjIEFkZCBSZXNpZHVhbHMNCg0KVGhlIF9fcHJlZGljdGlvbnNfXyBsZXQgdXMga25vdyB3aGF0IHBhdHRlcm4gdGhlIG1vZGVsIGNhcHR1cmVkLCBhbmQgdGhlIF9fcmVzaWR1YWxzX18gbGV0IHVzIGtub3cgd2hhdCB0aGUgbW9kZWwgaGFzIG1pc3NlZC4gVGhlIGtleSBpcyB0byBsb29rIGZvciBwYXR0ZXJuIGluIHRoZSByZXNpZHVhbHMuIFVzZSBgYWRkX3Jlc2lkdWFscygpYCB0byBhZGQgcmVzaWR1YWxzIHRvIHRoZSBvcmlnaW5hbCBgc2ltYCBkYXRhLiBOb3RlIHRoYXQgaXQncyBpbXBvcnRhbnQgdG8gYWRkIHJlc2lkdWFscyB0byB0aGUgaW5pdGlhbCBkYXRhIGZyYW1lIChhbmQgbm90IGBncmlkYCEpIGJlY2F1c2UgdGhlIHJlc2lkdWFscyBzaG93IGhvdyBmYXIgdGhlIG9yaWdpbmFsIGRhdGEgcG9pbnRzIGFyZSBmcm9tIHRoZSBtb2RlbC4gVGhlIGRhdGEgZ3JpZCBpcyBmb3IgdmlzdWFsaXppbmcgdGhlIG1vZGVsIHByZWRpY3Rpb25zLCB3aGlsZSB0aGUgb3JpZ2luYWwgZGF0YSBpcyBmb3IgYXNzZXNzaW5nIHRoZSByZXNpZHVhbHMuDQoNCmBgYHtyfQ0Kc2ltMSA8LSBzaW0xICU+JQ0KICAgIGFkZF9yZXNpZHVhbHMoc2ltMV9tb2QpIA0Kc2ltMQ0KYGBgDQoNClZpc3VhbGl6ZSB0aGUgcmVzaWR1YWxzOg0KDQpgYGB7cn0NCmdncGxvdChzaW0xLCBhZXMoeCwgcmVzaWQpKSArIA0KICAgIGdlb21fcmVmX2xpbmUoaCA9IDApICsNCiAgICBnZW9tX3BvaW50KCkgKw0KICAgIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKC01LDUpKQ0KYGBgDQoNClRoZSByYW5kb21uZXNzIG9mIHRoZSByZXNpZHVhbHMgbWVhbnMgdGhlIG1vZGVsLCBgc2ltMV9tb2RgLCBkaWQgYSBnb29kIGpvYiBvZiBjYXB0dXJpbmcgdGhlIHBhdHRlcm4uDQoNCiMjIyBFeGVyY2lzZXMNCg0KIyMjIyBFeGVyY2lzZSAxDQoNCl9JbnN0ZWFkIG9mIHVzaW5nIGxtKCkgdG8gZml0IGEgc3RyYWlnaHQgbGluZSwgeW91IGNhbiB1c2UgbG9lc3MoKSB0byBmaXQgYSBzbW9vdGggY3VydmUuIFJlcGVhdCB0aGUgcHJvY2VzcyBvZiBtb2RlbCBmaXR0aW5nLCBncmlkIGdlbmVyYXRpb24sIHByZWRpY3Rpb25zLCBhbmQgdmlzdWFsaXphdGlvbiBvbiBzaW0xIHVzaW5nIGxvZXNzKCkgaW5zdGVhZCBvZiBsbSgpLiBIb3cgZG9lcyB0aGUgcmVzdWx0IGNvbXBhcmUgdG8gZ2VvbV9zbW9vdGgoKT9fDQoNCkdlbmVyYXRlIHRoZSBtb2RlbCBhbmQgdGhlIGRhdGEgZ3JpZDoNCg0KYGBge3J9DQojIGxvZXNzIG1vZGVsDQpzaW0xX21vZF9sb2VzcyA8LSBsb2Vzcyh5IH4geCwgZGF0YT1zaW0xKQ0KIyBHZW5lcmF0ZSBncmlkDQpncmlkIDwtIHNpbTEgJT4lDQogICAgZGF0YV9ncmlkKHgpICU+JQ0KICAgIGFkZF9wcmVkaWN0aW9ucyhzaW0xX21vZF9sb2VzcykgDQpgYGANCg0KVmlzdWFsaXplIHRoZSBwcmVkaWN0aW9ucyBhbmQgY29tcGFyZSB3aXRoIGBnZW9tX3Ntb290aCgpYDoNCg0KYGBge3J9DQpnZ3Bsb3Qoc2ltMSwgYWVzKHggPSB4KSkgKw0KICAgIGdlb21fcG9pbnQoYWVzKHkgPSB5KSkgKw0KICAgIGdlb21fc21vb3RoKGFlcyh5ID0geSkpICsNCiAgICBnZW9tX2xpbmUoYWVzKHkgPSBwcmVkKSwgZGF0YSA9IGdyaWQsIGNvbG91ciA9ICJyZWQiLCBzaXplID0gLjUpDQpgYGANCg0KVGhlIGxvZXNzIG1vZGVsIGNvbXBhcmVzIHZlcnkgY2xvc2VseSB3aXRoIHRoZSBsaW5lIGdlbmVyYXRlZCBieSBgbG9lc3Nfc21vb3RoKClgLg0KDQojIyMjIEV4ZXJjaXNlIDMNCg0KX1doYXQgZG9lcyBgZ2VvbV9yZWZfbGluZSgpYCBkbz8gV2hhdCBwYWNrYWdlIGRvZXMgaXQgY29tZSBmcm9tPyBXaHkgaXMgZGlzcGxheWluZyBhIHJlZmVyZW5jZSBsaW5lIGluIHBsb3RzIHNob3dpbmcgcmVzaWR1YWxzIHVzZWZ1bCBhbmQgaW1wb3J0YW50P18NCg0KV2UgbmVlZCB0byB2aXN1YWxpemUgdGhlIHJlc2lkdWFscy4gRmlyc3QsIGFkZCB0aGUgcmVzaWR1YWxzIHRvIGBzaW0xYC4gTm90ZSB0aGF0IHRoaXMgcmVwbGFjZXMgdGhlIHByZXZpb3VzIGByZXNpZGAgY29sdW1uIHVubGVzcyB5b3UgcmVuYW1lIChpLmUuIGBhZGRfcmVzaWR1YWxzKHNpbTFfbW9kX2xvZXNzLCAic29tZV9uZXdfbmFtZV9mb3JfbG9lc3NfcmVzaWR1YWxzIilgKS4NCg0KYGBge3J9DQpzaW0xIDwtIHNpbTEgJT4lDQogICAgYWRkX3Jlc2lkdWFscyhzaW0xX21vZF9sb2VzcykNCnNpbTENCmBgYA0KDQpOb3csIHdlIGNhbiB2aXN1YWxpemUgdGhlIHJlc2lkdWFscyB1c2luZyB0aGUgYGdlb21fcmVmX2xpbmUoKWAuDQoNCmBgYHtyfQ0KZ2dwbG90KHNpbTEsIGFlcyh4LCByZXNpZCkpICsgDQogICAgZ2VvbV9yZWZfbGluZShoID0gMCwgc2l6ZSA9IDIsIGNvbG91ciA9ICdzdGVlbGJsdWUnKSArDQogICAgZ2VvbV9wb2ludCgpICsNCiAgICBzY2FsZV95X2NvbnRpbnVvdXMobGltaXRzID0gYygtNSw1KSkNCmBgYA0KDQpXZSBjYW4gc2VlIHRoYXQgYSByZWZlcmVuY2UgbGluZSBoYXMgYmVlbiBhZGRlZCBhdCB5ID0gMC4gVGhpcyBoZWxwcyB1cyB2aXN1YWxpemUgdGhlIHBhdHRlcm4gYXJvdW5kIHRoZSByZWZlcmVuY2UgbGluZS4NCg0KLS0tLQ0KDQojIEZvcm11bGFzIGFuZCBNb2RlbCBGYW1pbGllcw0KDQpOb3RlIHRoYXQgSSBza2lwIHRoZSBgbW9kZWxfbWF0cml4KClgIGZ1bmN0aW9uLCB3aGljaCBjb252ZXJ0cyBhIGRhdGEgc2V0IGFuZCBhIGZ1bmN0aW9uIChlLmcuIHkgfiB4KSB0byBhIG1vZGVsIG1hdHJpeCB0aGF0IGRlc2NyaWJlcyB3aGF0IFIgaXMgbW9kZWxsaW5nIHVuZGVyIHRoZSBob29kLiBJZiBpbnRlcmVzdGVkLCB5b3Ugc2hvdWxkIGRlZmluaXRlbHkgY2hlY2sgdGhlIGRvY3VtZW50YXRpb24uDQoNCiMjIENhdGVnb3JpY2FsIFZhcmlhYmxlcw0KDQpXZSBhbmFseXplIGBtb2RlbHI6OnNpbTJgIGRhdGEgc2V0LCB3aGljaCBjb250YWlucyBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgZm9yIGB4YCAoYSwgYiwgYywgZCkuDQoNCmBgYHtyfQ0Kc2ltMg0KYGBgDQoNCkxldCdzIHZpc3VhbGl6ZToNCg0KYGBge3J9DQpnZ3Bsb3Qoc2ltMiwgYWVzKHgsIHkpKSArDQogICAgZ2VvbV9wb2ludCgpDQpgYGANCg0KTmV4dCwgZml0IGEgbW9kZWwgYW5kIGFkZCBwcmVkaWN0aW9uczoNCg0KYGBge3J9DQptb2QyIDwtIGxtKHkgfiB4LCBkYXRhID0gc2ltMikNCmdyaWQgPC0gc2ltMiAlPiUNCiAgICBkYXRhX2dyaWQoeCkgJT4lDQogICAgYWRkX3ByZWRpY3Rpb25zKG1vZDIpDQpncmlkDQpgYGANCg0KVmlzdWFsaXplIHRoZSBkYXRhIHdpdGggdGhlIHByZWRpY3Rpb25zLiBSZW1lbWJlciwgYGdyaWRgIGNvbnRhaW5zIHRoZSBwcmVkaWN0aW9ucyBhdCBlYWNoIHVuaXF1ZWx5IHNwYWNlZCBpbnRlcnZhbCwgd2hpY2ggZm9yIGBzaW0yYCBpcyB0aGUgY2F0ZWdvcmllcy4NCg0KYGBge3J9DQpnZ3Bsb3Qoc2ltMiwgYWVzKHggPSB4KSkgKw0KICAgIGdlb21fcG9pbnQoYWVzKHkgPSB5KSkgKw0KICAgIGdlb21fcG9pbnQoZGF0YSA9IGdyaWQsIGFlcyh5ID0gcHJlZCksIGNvbG9yID0gInJlZCIsIHNpemUgPSA0KQ0KYGBgDQoNCiMjIEludGVyYWN0aW9ucyAoQ29udGludW91cyBhbmQgQ2F0ZWdvcmljYWwgVmFyaWFibGVzKQ0KDQpEYXRhIHNldCBgc2ltM2AgY29udGFpbnMgYm90aCBjb250aW51b3VzIGFuZCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgdG8gaW5jbHVkZSBpbiBtb2RlbDoNCg0KYGBge3J9DQpzaW0zDQpgYGANCg0KV2Ugd2FudCB0byBtb2RlbCBgeWAgdXNpbmcgYm90aCBgeDFgIGFuZCBgeDJgLiBGaXJzdCwgbGV0J3MgdmlzdWFsaXplIHVzaW5nIGEgc2NhdHRlciBwbG90IGZvciBjb250aW51b3VzIGB5YCB2cyBgeDFgIGFuZCB1c2luZyBhIGNvbG9yIGZvciB0aGUgY2F0ZWdvcmljYWwgYHgyYDoNCg0KYGBge3J9DQpnZ3Bsb3Qoc2ltMywgYWVzKHgxLCB5LCBjb2xvciA9IHgyKSkgKw0KICAgIGdlb21fcG9pbnQoKQ0KYGBgDQoNClR3byB3YXlzIHRvIGZpdCB0aGUgZGF0YToNCg0KYGBge3J9DQptb2QxIDwtIGxtKHkgfiB4MSArIHgyLCBkYXRhID0gc2ltMykNCm1vZDIgPC0gbG0oeSB+IHgxICogeDIsIGRhdGEgPSBzaW0zKQ0KYGBgDQoNClRvIHZpc3VhbGl6ZSB0aGVzZSBtb2RlbHMgd2UgbmVlZCB0d28gbmV3IHRyaWNrczoNCg0KMS4gV2UgaGF2ZSB0d28gcHJlZGljdG9ycywgc28gd2UgbmVlZCB0byBnaXZlIGBkYXRhX2dyaWQoKWAgYm90aCB2YXJpYWJsZXMuIEl0IGZpbmRzIGFsbCB0aGUgdW5pcXVlIHZhbHVlcyBvZiBgeDFgIGFuZCBgeDJgIGFuZCB0aGVuIGdlbmVyYXRlcyBhbGwgY29tYmluYXRpb25zLg0KDQoyLiBUbyBnZW5lcmF0ZSBwcmVkaWN0aW9ucyBmcm9tIGJvdGggbW9kZWxzIHNpbXVsdGFuZW91c2x5LCB3ZSBjYW4gdXNlIGBnYXRoZXJfcHJlZGljdGlvbnMoKWAgd2hpY2ggYWRkcyBlYWNoIHByZWRpY3Rpb24gYXMgYSByb3cuIFRoZSBjb21wbGVtZW50IG9mIGBnYXRoZXJfcHJlZGljdGlvbnMoKWAgaXMgYHNwcmVhZF9wcmVkaWN0aW9ucygpYCB3aGljaCBhZGRzIGVhY2ggcHJlZGljdGlvbiB0byBhIG5ldyBjb2x1bW4uDQoNClVzaW5nIGBnYXRoZXJfcHJlZGljdGlvbnMoKWA6DQoNCmBgYHtyfQ0KZ3JpZCA8LSBzaW0zICU+JQ0KICAgIGRhdGFfZ3JpZCh4MSwgeDIpICU+JQ0KICAgIGdhdGhlcl9wcmVkaWN0aW9ucyhtb2QxLCBtb2QyKQ0KZ3JpZA0KYGBgDQoNClVzaW5nIGBzcHJlYWRfcHJlZGljdGlvbnMoKWA6DQoNCmBgYHtyfQ0KZ3JpZF9zcHJlYWQgPC0gc2ltMyAlPiUNCiAgICBkYXRhX2dyaWQoeDEsIHgyKSAlPiUNCiAgICBzcHJlYWRfcHJlZGljdGlvbnMobW9kMSwgbW9kMikNCmdyaWRfc3ByZWFkDQpgYGANCg0KTm90ZSB0aGF0IHRoZSBvbmx5IGRpZmZlcmVuY2UgYmV0d2VlbiBnYXRoZXIgYW5kIHNwcmVhZCBpcyB0aGUgbGF5b3V0IG9mIHRoZSBwcmVkaWN0aW9ucy4gR2F0aGVyIHJldHVybnMgcHJlZGljdGlvbnMgaW4gYSB0aWR5IGZvcm1hdCAoZ29vZCBmb3IgcGxvdHRpbmcpIHdoZXJlYXMgc3ByZWFkIHJldHVybnMgaW4gYSBtb3JlIGludHVpdGl2ZSBmb3JtYXQgd2l0aCBwcmVkaWN0aW9ucyBpbiBtb2RlbCBjb2x1bW5zLiBXZSdsbCBjb250aW51ZSB3aXRoIHRoZSBnYXRoZXIgZm9ybWF0Lg0KDQpXZSBjYW4gdmlzdWFsaXplIHRoZSByZXN1bHRzIHVzaW5nIGEgYGZhY2V0X3dyYXAoKWA6DQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBzaW0zLCBhZXMoeCA9IHgxLCB5ID0geSwgY29sb3IgPSB4MikpICsgDQogICAgZ2VvbV9wb2ludCgpICsNCiAgICBnZW9tX2xpbmUoZGF0YSA9IGdyaWQsIGFlcyh5ID0gcHJlZCkpICsgIyBPdmVybGF5IHByZWRpY3Rpb25zDQogICAgZmFjZXRfd3JhcCh+IG1vZGVsKSAjIGZhY2V0IG9uIG1vZGVsIHZhcmlhYmxlDQpgYGANCg0KV2UgY2FuIGFsc28gdGFrZSB0aGlzIG9uZSBzdGVwIGZ1cnRoZXIgdXNpbmcgYGZhY2V0X2dyaWRgLCB3aGljaCByZWFsbHkgc2hvd3Mgb2ZmIHRoZSBkaWZmZXJlbmNlIGluIG1vZGVsIHBlcmZvcm1hbmNlOg0KDQpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTB9DQpnZ3Bsb3QoZGF0YSA9IHNpbTMsIGFlcyh4ID0geDEsIHkgPSB5LCBjb2xvciA9IHgyKSkgKyANCiAgICBnZW9tX3BvaW50KCkgKw0KICAgIGdlb21fbGluZShkYXRhID0gZ3JpZCwgYWVzKHkgPSBwcmVkKSkgKyAjIE92ZXJsYXkgcHJlZGljdGlvbnMNCiAgICBmYWNldF9ncmlkKG1vZGVsIH4geDIpICMgZmFjZXQgb24gYm90aCB4MiBhbmQgbW9kZWwgdmFyaWFibGUNCmBgYA0KDQpGaW5hbGx5LCB3ZSBjYW4gdGFrZSBhIGxvb2sgYXQgdGhlIHJlc2lkdWFscyB0byBzZWUgd2hhdCBwYXR0ZXJuIGlzIGxlZnRvdmVyOg0KDQpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTB9DQpzaW0zIDwtIHNpbTMgJT4lDQogICAgZ2F0aGVyX3Jlc2lkdWFscyhtb2QxLCBtb2QyKQ0KZ2dwbG90KHNpbTMsIGFlcyh4MSwgcmVzaWQsIGNvbG9yID0geDIpKSArDQogICAgZ2VvbV9yZWZfbGluZShoID0gMCkgKw0KICAgIGdlb21fcG9pbnQoKSArIA0KICAgIGZhY2V0X2dyaWQobW9kZWwgfiB4MikNCmBgYA0KDQpXZSBjYW4gcXVhbGl0YXRpdmVseSBzZWUgdGhhdCBgbW9kMmAgZG9lcyBhIG11Y2ggYmV0dGVyIGpvYiBvZiByZW1vdmluZyB0aGUgcGF0dGVybiB2ZXJzdXMgYG1vZDFgLiANCg0KIyMgSW50ZXJhY3Rpb25zICh0d28gY29udGludW91cykNCg0KTGV0J3MgdGFrZSBhIGxvb2sgYXQgYHNpbTRgOg0KDQpgYGB7cn0NCnNpbTQNCmBgYA0KDQpXZSB3YW50IHRvIHVzZSBgeDFgIGFuZCBgeDJgIHRvIHByZWRpY3QgYHlgLCBidXQgbm93IGJvdGggcHJlZGljdG9ycyBhcmUgY2F0ZWdvcmljYWwuIFdlIGNyZWF0ZSB0d28gbW9kZWxzLCBvbmUgd2l0aG91dCBpbnRlcmFjdGlvbnMgYW5kIG9uZSB3aXRoOg0KDQpgYGB7cn0NCm1vZDEgPC0gbG0oeSB+IHgxICsgeDIsIGRhdGEgPSBzaW00KQ0KbW9kMiA8LSBsbSh5IH4geDEgKiB4MiwgZGF0YSA9IHNpbTQpDQpgYGANCg0KIyMjIFF1aWNrIGRpZ3Jlc3Npb24gb24gY29tYmluaW5nIGRhdGFfZ3JpZCgpIHdpdGggc2VxX3JhbmdlKCkNCg0KQmVmb3JlIHdlIGp1bXAgaW50byBwcmVkaWN0aW5nIHVzaW5nIG1vZGVscywgaXQncyBpbXBvcnRhbnQgdG8gdW5kZXJzdGFuZCB0aGUgZGF0YS4gTGV0J3MgdGFrZSBhIGxvb2sgYXQgYHgxYCBmaXJzdDoNCg0KYGBge3J9DQpzaW00JHgxICU+JSB1bmlxdWUoKQ0KYGBgDQoNCmB4MWAgaGFzIHRlbiB1bmlmb3JtbHkgc3BhY2VkIG51bWVyaWMgdmFsdWVzLiBBIGNvbnZlbmllbnQgd2F5IHRvIG1vZGVsIG51bWVyaWMgZGF0YSBpcyB0byB1c2UgYGRhdGFfZ3JpZCgpYCBpbiBjb25qdW5jdGlvbiB3aXRoIGBzZXFfcmFuZ2UoKWAgdG8gY29udHJvbCB0aGUgZ3JpZCBzaXplLiBgc2VxX3JhbmdlKClgIHRha2VzIHRoZSByYW5nZSBvZiB0aGUgdmFsdWVzIChpbiBvdXIgY2FzZSBgeDFgKSBhbmQgZ2VuZXJhdGVzIGEgdW5pZm9ybSBzZXF1ZW5jZSBvdmVyIGEgc3BlY2lmaWVkIGludGVydmFsIG51bWJlci4NCg0KYGBge3J9DQpzaW00JHgxICU+JSBzZXFfcmFuZ2UobiA9IDUpDQpgYGANCg0KSW1wb3J0YW50IGFyZ3VtZW50cyB0byBgc2VxX3JhbmdlKClgIChiZXNpZGVzIGBuYCk6DQoNCiogYHByZXR0eSA9IFRSVUVgOiBHZW5lcmF0ZXMgYSBwcmV0dHkgc2VxdWVuY2Ugd2l0aCB2YWx1ZXMgcm91bmRlZC4NCg0KYGBge3J9DQpzZXFfcmFuZ2UoYygwLjAxMjMsIDAuOTIzNDIzKSwgbiA9IDUpDQpzZXFfcmFuZ2UoYygwLjAxMjMsIDAuOTIzNDIzKSwgbiA9IDUsIHByZXR0eSA9IFRSVUUpDQpgYGANCg0KKiBgdHJpbSA9IDAuMmA6IFRyaW1zIG9mZiAyMCUgb2YgdGhlIHRhaWwgdmFsdWVzIChpLmUuIC0xIGJlY29tZXMgLTAuOCkNCg0KYGBge3J9DQpzaW00JHgxICU+JSBzZXFfcmFuZ2UobiA9IDUsIHRyaW0gPSAwLjIpICU+JSByb3VuZCgyKQ0KYGBgDQoNCiogYGV4cGFuZCA9IDAuMmA6IEV4cGFuZHMgdGhlIHJhbmdlIGJ5IDIwJS4NCg0KYGBge3J9DQpzaW00JHgxICU+JSBzZXFfcmFuZ2UobiA9IDUsIGV4cGFuZCA9IDAuMikNCmBgYA0KDQojIyMgQmFjayB0byBtb2RlbGxpbmcgdXNpbmcgZGF0YV9ncmlkKCkgYW5kIHNlcV9yYW5nZSgpDQoNCldoYXQgd2Ugd2FudCB0byBkbyBpcyB1c2UgdGhlIG1vZGVscyB0byBnZW5lcmF0ZSBwcmVkaWN0aW9ucyBmb3IgdmlzdWFsaXphdGlvbi4gV2UgY29tYmluZSBgZGF0YV9ncmlkKClgIHdpdGggYHNlcV9yYW5nZSgpYCB0byBjcmVhdGUgYSA1IHggNSBncmlkIGZvciBtb2RlbCAxIGFuZCBhIDUgeCA1IGdyaWQgZm9yIG1vZGVsIDI6DQoNCmBgYHtyfQ0KZ3JpZCA8LSBzaW00ICU+JQ0KICAgIGRhdGFfZ3JpZCgNCiAgICAgICAgeDEgPSBzZXFfcmFuZ2UoeDEsIG4gPSA1KSwNCiAgICAgICAgeDIgPSBzZXFfcmFuZ2UoeDIsIG4gPSA1KQ0KICAgICkgJT4lDQogICAgZ2F0aGVyX3ByZWRpY3Rpb25zKG1vZDEsIG1vZDIpDQpncmlkDQpgYGANCg0KVmlzdWFsaXppbmcgaXMgYSBjaGFsbGVuZ2Ugd2l0aCBtdWx0aXBsZSBwcmVkaWN0b3JzLCBidXQgd2UgY2FuIGFjY29tcGxpc2ggdXNpbmcgYSBjb21iaW5hdGlvbiBvZiBjb2xvciBhbmQgZmFjZXRpbmc6DQoNCmBgYHtyLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD04fQ0KZ2dwbG90KGRhdGEgPSBzaW00LCANCiAgICAgICBhZXMoeCA9IHgxLCANCiAgICAgICAgICAgeSA9IHksIA0KICAgICAgICAgICBjb2xvciA9IHgyICU+JSByb3VuZCgyKSAlPiUgZmFjdG9yKCksDQogICAgICAgICAgIGdyb3VwID0geDINCiAgICAgICAgICAgKQ0KICAgICAgICkgKyANCiAgICBnZW9tX3BvaW50KCkgKyANCiAgICBnZW9tX2xpbmUoZGF0YSA9IGdyaWQsIA0KICAgICAgICAgICAgICBhZXMoeCA9IHgxLA0KICAgICAgICAgICAgICAgICAgeSA9IHByZWQsDQogICAgICAgICAgICAgICAgICBjb2xvciA9IHgyICU+JSByb3VuZCgyKSAlPiUgZmFjdG9yKCksDQogICAgICAgICAgICAgICAgICBncm91cCA9IHgyDQogICAgICAgICAgICAgICAgICApDQogICAgICAgICAgICAgICkgKw0KICAgIGxhYnMoY29sb3IgPSAieDIiKSArDQogICAgZmFjZXRfd3JhcCh+IG1vZGVsLCBuY29sID0gMSkNCmBgYA0KDQpUaGUgaW50ZXJhY3Rpb24gYWxsb3dzIHRoZSBtb2RlbCB0byBjb25zaWRlciBib3RoIHRoZSBgeDFgIGFuZCBgeDJgIHNpbXVsdGFuZW91c2x5LiANCg0KDQojIyBUcmFuc2Zvcm1hdGlvbnMgDQoNClRyYW5zZm9ybWF0aW9ucyBjYW4gYmUgdXNlZCB0byBhcHByb3hpbWF0ZSBub24tbGluZWFyIGZ1bmN0aW9ucy4gDQoNCkNyZWF0ZSBhIG5vbi1saW5lYXIgZGF0YSBzZXQ6DQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTUwKQ0Kc2ltNSA8LSB0aWJibGUoDQogICAgeCA9IHNlcSgwLCAzLjUgKiBwaSwgbGVuZ3RoLm91dCA9IDUwKSwNCiAgICB5ID0gNCAqIHNpbih4KSArIHJub3JtKGxlbmd0aCh4KSkNCikNCnNpbTUNCmBgYA0KDQpWaXN1YWxpemUgdGhlIGRhdGEgc2V0Og0KDQpgYGB7cn0NCmdncGxvdChzaW01LCBhZXMoeCwgeSkpICsgDQogICAgZ2VvbV9wb2ludCgpDQpgYGANCg0KRml0IG1vZGVscyB0byB0aGUgZGF0YSB1c2luZyBuYXR1cmFsIHNwbGluZXM6DQoNCmBgYHtyfQ0KbGlicmFyeShzcGxpbmVzKQ0KbW9kMSA8LSBsbSh5IH4gbnMoeCwgMSksIGRhdGEgPSBzaW01KQ0KbW9kMiA8LSBsbSh5IH4gbnMoeCwgMiksIGRhdGEgPSBzaW01KQ0KbW9kMyA8LSBsbSh5IH4gbnMoeCwgMyksIGRhdGEgPSBzaW01KQ0KbW9kNCA8LSBsbSh5IH4gbnMoeCwgNCksIGRhdGEgPSBzaW01KQ0KbW9kNSA8LSBsbSh5IH4gbnMoeCwgNSksIGRhdGEgPSBzaW01KQ0KYGBgDQoNCkNyZWF0ZSB0aGUgZGF0YSBncmlkOg0KDQpgYGB7cn0NCmdyaWQgPC0gc2ltNSAlPiUNCiAgICBkYXRhX2dyaWQoeCA9IHNlcV9yYW5nZSh4LCBuID0gNTAsIGV4cGFuZCA9IDAuMSkpICU+JQ0KICAgIGdhdGhlcl9wcmVkaWN0aW9ucyhtb2QxLCBtb2QyLCBtb2QzLCBtb2Q0LCBtb2Q1KQ0KZ3JpZA0KYGBgDQoNClZpc3VhbGl6ZSB0aGUgcHJlZGljdGlvbnM6DQoNCmBgYHtyfQ0KZ2dwbG90KHNpbTUsIGFlcyh4LCB5KSkgKw0KICAgIGdlb21fcG9pbnQoKSArDQogICAgZ2VvbV9saW5lKGRhdGEgPSBncmlkLCBhZXMoeSA9IHByZWQpLCBjb2xvciA9ICJyZWQiLCBzaXplID0gMikgKw0KICAgIGZhY2V0X3dyYXAofiBtb2RlbCkNCmBgYA0KDQotLS0tDQoNCiMgTWlzc2luZyBWYWx1ZXMNCg0KUiBkZWZhdWx0cyB0byBzaWxlbnRseSBkcm9wIGBOQWAncy4gVG8gZ2V0IGEgd2FybmluZywgc2V0IGBvcHRpb25zKG5hLmFjdGlvbiAgPSBuYS53YXJuKWAuDQoNCmBgYHtyLCB3YXJuaW5nPVRSVUV9DQpvcHRpb25zKG5hLmFjdGlvbiAgPSBuYS53YXJuKQ0KZGYgPC0gdHJpYmJsZSgNCiAgICB+eCwgfnksDQogICAgMSwgMi4yLA0KICAgIDIsIE5BLA0KICAgIDMsIDMuNSwNCiAgICA0LCA4LjMsDQogICAgTkEsIDEwDQopDQoNCm1vZCA8LSBsbSh5IH4geCwgZGF0YSA9IGRmKQ0KYGBgDQoNCi0tLS0NCg0KIyBPdGhlciBNb2RlbCBGYW1pbGllcw0KDQoNCiogX19HZW5lcmFsaXplZCBsaW5lYXIgbW9kZWxzX18sIGUuZy4gYHN0YXRzOjpnbG0oKWAuIExpbmVhciBtb2RlbHMgYXNzdW1lIHRoYXQgdGhlIHJlc3BvbnNlIGlzIGNvbnRpbnVvdXMgYW5kIHRoZSBlcnJvciBoYXMgYSBub3JtYWwgZGlzdHJpYnV0aW9uLiBHZW5lcmFsaXplZCBsaW5lYXIgbW9kZWxzIGV4dGVuZCBsaW5lYXIgbW9kZWxzIHRvIGluY2x1ZGUgbm9uLWNvbnRpbnVvdXMgcmVzcG9uc2VzIChlLmcuIGJpbmFyeSBkYXRhIG9yIGNvdW50cykuIFRoZXkgd29yayBieSBkZWZpbmluZyBhIGRpc3RhbmNlIG1ldHJpYyBiYXNlZCBvbiB0aGUgc3RhdGlzdGljYWwgaWRlYSBvZiBsaWtlbGlob29kLg0KDQoqIF9fR2VuZXJhbGl6ZWQgYWRkaXRpdmUgbW9kZWxzX18sIGUuZy4gYG1nY3Y6OmdhbSgpYCwgZXh0ZW5kIGdlbmVyYWxpemVkIGxpbmVhciBtb2RlbHMgdG8gaW5jb3Jwb3JhdGUgYXJiaXRyYXJ5IHNtb290aCBmdW5jdGlvbnMuIFRoYXQgbWVhbnMgeW91IGNhbiB3cml0ZSBhIGZvcm11bGEgbGlrZSBgeSB+IHMoeClgIHdoaWNoIGJlY29tZXMgYW4gZXF1YXRpb24gbGlrZSBgeSA9IGYoeClgIGFuZCBsZXQgYGdhbSgpYCBlc3RpbWF0ZSB3aGF0IHRoYXQgZnVuY3Rpb24gaXMgKHN1YmplY3QgdG8gc29tZSBzbW9vdGhuZXNzIGNvbnN0cmFpbnRzIHRvIG1ha2UgdGhlIHByb2JsZW0gdHJhY3RhYmxlKS4NCg0KKiBfX1BlbmFsaXplZCBsaW5lYXIgbW9kZWxzX18sIGUuZy4gYGdsbW5ldDo6Z2xtbmV0KClgLCBhZGQgYSBwZW5hbHR5IHRlcm0gdG8gdGhlIGRpc3RhbmNlIHRoYXQgcGVuYWxpemVzIGNvbXBsZXggbW9kZWxzIChhcyBkZWZpbmVkIGJ5IHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBwYXJhbWV0ZXIgdmVjdG9yIGFuZCB0aGUgb3JpZ2luKS4gVGhpcyB0ZW5kcyB0byBtYWtlIG1vZGVscyB0aGF0IGdlbmVyYWxpemUgYmV0dGVyIHRvIG5ldyBkYXRhIHNldHMgZnJvbSB0aGUgc2FtZSBwb3B1bGF0aW9uLg0KDQoqIF9fUm9idXN0IGxpbmVhciBtb2RlbHNfXywgZS5nLiBgTUFTUzpybG0oKWAsIHR3ZWFrIHRoZSBkaXN0YW5jZSB0byBkb3duIHdlaWdodCBwb2ludHMgdGhhdCBhcmUgdmVyeSBmYXIgYXdheS4gVGhpcyBtYWtlcyB0aGVtIGxlc3Mgc2Vuc2l0aXZlIHRvIHRoZSBwcmVzZW5jZSBvZiBvdXRsaWVycywgYXQgdGhlIGNvc3Qgb2YgYmVpbmcgbm90IHF1aXRlIGFzIGdvb2Qgd2hlbiB0aGVyZSBhcmUgbm8gb3V0bGllcnMuDQoNCiogX19UcmVlc19fLCBlLmcuIGBycGFydDo6cnBhcnQoKWAsIGF0dGFjayB0aGUgcHJvYmxlbSBpbiBhIGNvbXBsZXRlbHkgZGlmZmVyZW50IHdheSB0aGFuIGxpbmVhciBtb2RlbHMuIFRoZXkgZml0IGEgcGllY2Utd2lzZSBjb25zdGFudCBtb2RlbCwgc3BsaXR0aW5nIHRoZSBkYXRhIGludG8gcHJvZ3Jlc3NpdmVseSBzbWFsbGVyIGFuZCBzbWFsbGVyIHBpZWNlcy4gVHJlZXMgYXJlbuKAmXQgdGVycmlibHkgZWZmZWN0aXZlIGJ5IHRoZW1zZWx2ZXMsIGJ1dCB0aGV5IGFyZSB2ZXJ5IHBvd2VyZnVsIHdoZW4gdXNlZCBpbiBhZ2dyZWdhdGUgYnkgbW9kZWxzIGxpa2UgcmFuZG9tIGZvcmVzdHMgKGUuZy4gYHJhbmRvbUZvcmVzdDo6cmFuZG9tRm9yZXN0KClgKSBvciBncmFkaWVudCBib29zdGluZyBtYWNoaW5lcyAoZS5nLiBgeGdib29zdDo6eGdib29zdGAuKQ0KDQpJbiBhZGRpdGlvbiwgdGhlIF9fW0NhcmV0IFBhY2thZ2VdKGh0dHA6Ly90b3BlcG8uZ2l0aHViLmlvL2NhcmV0L2luZGV4Lmh0bWwpX18gKGBjYXJldGApLCBpcyBhIHNldCBvZiBmdW5jdGlvbnMgdGhhdCBzdHJlYW1saW5lcyB0aGUgcHJvY2VzcyBmb3IgY3JlYXRpbmcgcHJlZGljdGl2ZSBtb2RlbHMuICANCg==