library(tidyverse) #for plotting and summarizing
library(ggridges) #for ridge plots
library(ggmosaic) #NEW! for mosaic plots
library(moderndive) #for nice model output
library(broom) #for nice model output 
library(Ecdat) #for data
theme_set(theme_minimal()) #changes the theme of ggplots to theme_minimal, my personal favorite

GOAL:

By the end of these notes and activities, you should be able to perform the following tasks.

  • Create appropriate plots to explore the relationship between categorical or quantitative predictors and a binary response.
  • Know when a logistic regression model is appropriate.
  • Fit a logistic regression model using glm(). (Don’t forget family = binomial(link = "logit").)
  • Interpret coefficients from a logistic regression model for both categorical and quantitative predictors.
  • Find the predicted probability of “success” for a new observation.
  • Plot the logistic model in simple cases.

So far all the modeling we have done in this course has used a quantitative response variable. Now, we are going to talk about how to model a different type of response variable, one with a binary response: yes/no, true/false, dead/alive, etc.

Before we start, I would like to take some time to discuss some real-life examples. Can you think of any places where this might be used?

To introduce this concept, we will use the Hmda dataset from the Ecdat library. It contains data on mortgage application denials in Boston. The data are from 1997-98. You can learn more about the data by typing Hmda into help or ?Hmda in the Console. The response variable is called deny and is a yes if the applicant was denied, and a no otherwise.

We will filter out a couple outliers, so use Hmda2 from now on.

Hmda2 <- 
  Hmda %>% 
  filter(hir < 1, dir < 1, ) %>% 
  mutate(deny_quant = ifelse(deny == "yes", 1, 0))

Exploratory analysis

We will learn some new techniques to examine relationships between quantitative variables and the binary response. One option, shown below, is to use boxplots. I also added the original data.

Hmda2 %>% 
  ggplot(aes(y = deny, x = dir)) +
  geom_jitter(width = .1, size = .2, alpha = .5, color = "darkgray") +
  geom_boxplot(outlier.shape = NA, # doesn't indicate outliers
               varwidth = TRUE, # width of boxplot changes according to number of observations
               alpha = .5)

Or we could use ridge plots.

Hmda2 %>% 
  ggplot(aes(x= dir, y = deny, fill = deny)) +
  geom_density_ridges(alpha = .5)
## Picking joint bandwidth of 0.0185

To explore the relationship between two categorical variables, we can use barplots. Below, I construct them in three different ways. When would you use each of them? What are the different things each of them show well?

ggplot(data=Hmda2) +
  geom_bar(aes(x=single, fill=deny))

ggplot(data=Hmda2) +
  geom_bar(aes(x=single, fill=deny),
           position = "dodge")

ggplot(data=Hmda2) +
  geom_bar(aes(x=single, fill=deny), 
           position = "fill") +
  labs(y = "proportion")

A mosaic plot is an even more informative graph than the last plot from above. The widths reflect the proportion in each level of the x-axis variable, in this case single. This function is a bit tedious because you have to do some labeling on your own.

Hmda2 %>% 
  ggplot() +
  geom_mosaic(aes(x = product(single), 
                  fill = deny)) +
  labs(x = "Single", y = "Deny") +
  guides(fill = "none")

YOUR TURN!

Explore some other potential predictor variables on your own. Do any variables seem to be good predictors of deny?

Try to apply regular regression techniques … (Typically a bad idea)

Since we know how to model data with a quantitative response, we might think of turning the deny variable into a quantitative variable. I did that when I created Hmda2; deny_quant is a 1 if the applicant was denied and 0 otherwise.

Now, we could use this as our response variable in a linear model, same as we have been doing all semester. Let’s use dir and single as explanatory variables. Interpret each of the coefficients in the model below. Do you see an issue? (Hint, try predicting the response for non-single applicants with low debt payments to total income ratio, dir).

lm_deny_WRONG <- lm(deny_quant ~ dir + single, data = Hmda2)
tidy(lm_deny_WRONG)

It might also help to look at the plot.

augment(lm_deny_WRONG) %>% 
  ggplot(aes(x=dir, y=deny_quant, color=single)) +
  geom_jitter(height = .05, size = .2, alpha = .5) +
  geom_line(aes(y = .fitted))

How do we solve this problem? We want to guarantee that the model values are between 0 and 1. We are going to use something called a link function. We can think about this in two steps:

  1. Fit a linear model the way we are used to, adding terms times their coefficients. (Note that the method isn’t exactly the same … I’ll talk about that later). Call this output y. This value is not a probability.

  2. Use a link function to translate the value y to a scale between 0 and 1, p (which stands for probability). The function that will be used is called the “logit link” or the logistic transformation and it is defined as

\[ p = \frac{e^y}{(1 + e^y)} \]

Fitting the logistic regression model with glm()

Let’s investigate how to do this in R. We can use a function called glm() (generalized linear model) to fit this model. Before explaining how the model is fit, let’s fit it and talk about interpreting the coefficients and predicting new values.

glm_deny <- glm(deny ~ dir + single, 
                data = Hmda2,
                family = binomial(link = "logit") #new!
                ) 

#NOTE: get_regression_table() doesn't work for glm's
tidy(glm_deny) 

Explanation

Remember, the value that comes out of the linear model portion is y and y is the result of a linear equation. So,

\[ \hat{y} = \hat{\beta}_0 + \hat{\beta}_1 x_1 +\hat{\beta}_2 x_2 + ... + \hat{\beta}_p x_p \]

Let’s also solve for \(y\) in

\[ \hat{p} = \frac{e^{\hat{y}}}{(1 + e^{\hat{y}})}, \]

which, after a bit of math, gives: \[ \hat{y} = log\Big(\frac{\hat{p}}{1-\hat{p}}\Big) \]

or \[ e^{\hat{y}} = \frac{\hat{p}}{1-\hat{p}}. \]

Combining these, we have

\[ log\Big(\frac{\hat{p}}{1-\hat{p}}\Big) = \hat{\beta}_0 + \hat{\beta}_1 x_1 +\hat{\beta}_2 x_2 + ... + \hat{\beta}_p x_p \]

or

\[ \frac{\hat{p}}{1-\hat{p}} = e^{\hat{\beta}_0}e^{\hat{\beta}_1x_1}e^{\hat{\beta}_2 x_2} \cdots e^{\hat{\beta}_p x_p} \]

These equations give us nice ways to interpret the coefficients.

The quantity \(\frac{\hat{p}}{1-\hat{p}}\) is called the odds.

Tangent: What are odds?

Before we move on to interpreting the results of our models, let’s first make sure we have an understanding of odds.

Let x be some event (ie. getting a heads, winning the lottery, buying a shirt, passing this class, …). Then the odds of x, \(odds(x)\), is defined as:

\[ odds(x) = \frac{p(x)}{1 - p(x)}, \]

the probability of x divided by 1 minus the probability of x (which is the probability of of not x).

YOUR TURN!

  1. Assume the probability of flipping heads on a coin is .5. Find the odds of flipping a head.
  2. In a regular 52 card deck of cards, the odds of choosing a card that is hearts.
  3. If the probability of survival is \(p=.80\), what are the odds of survival?
  4. In the previous question, what are the log odds of survival?
  5. Is it easy to think about the log odds scale? Or even the odds scale?

Back to explanation

Recall the model we were just looking at. I also added a column of the exponentiated coefficients (\(e^{coefficient}\)).

tidy(glm_deny) %>% 
  select(term, estimate) %>% 
  mutate(exp_est = exp(estimate))

We should keep in mind:

  1. The coefficients that are in the output are on the log(odds) scale - YUCK! We don’t usually want to interpret things on that scale.
  2. The exponentiated equation is multiplicative on the odds scale. That’s a better way to interpret our results

YOUR TURN!

Interpret/find the following:

  1. \(\hat{\beta}_0\), the intercept.
  2. \(e^{\hat{\beta}_0}\), the exponentiated intercept.
  3. \(\hat{\beta}_1\), the coefficient for dir. Why might this be a useless interpretation? In what units might we want to interpret this?
  4. \(e^{\hat{\beta}_1}\).
  5. \(\hat{\beta}_2\),
  6. \(e^{\hat{\beta}_2}\) Hint: compare the odds of denial for single and non-single with the same dir.
  7. The probability of a denied mortgage application for a single applicant who has a debt payments to total income ratio of .2.
  8. The probability of an accepted mortgage for a single applicant who has a debt payments to total income ratio of .2.

Prediction

We can use the augment() function to predict new values, similar to how we used it before. See augment.glm in the help for more details.

Let’s show how to do that using problem 7 from above. What is .fitted?

augment(glm_deny, 
        newdata = tibble(single = "yes", 
                         dir = .2))

Let’s put this in terms of probability:

augment(glm_deny, 
        newdata = tibble(single = "yes", 
                         dir = .2),
        type.predict = "response")

Just like with linear regression, in simple cases, we can plot the model values (the probabilities). This is one of those cases. How does this plot look different from when we used linear regression to model deny?

augment(glm_deny, 
        data = Hmda2,
        type.predict="response") %>% 
  #I need to plot deny as a 0/1
  ggplot(aes(x=dir, y=deny_quant, color=single)) +
  geom_jitter(height = .05, size = .2, alpha = .5) +
  geom_line(aes(y = .fitted))

Fitting the model

We learned that linear models are fit by finding the coefficients that minimize the sum of the squared residuals. Coefficients in logistic regression maximize the likelihood function. In the case of logistic regression, the likelihood function is:

\[ \prod_{i=1}^n p_i^{y_i}(1-p_i)^{1-y_i}. \]

This formula may look complicated but it’s not too bad if we break apart the pieces.

First, the big pi,\(\prod_{i=1}^n\) means multiplication.

The \(y_i\) is the observed value for the \(i^{th}\) observation in the dataset. So, it is either 0 or 1.

Notice that when \(y_i = 1\),

\[ p_i^{y_i}(1-p_i)^{1-y_i} = p_i \]

and when \(y_i = 0\),

\[ p_i^{y_i}(1-p_i)^{1-y_i} = 1 - p_i. \]

So, this is a product of either the predicted probabilities (for cases when \(y_i = 1\)) or one minus the predicted probabilities (for cases when \(y_i = 0\)).

The largest this value can be is \(1\), which would happen if all the 1’s had a predicted probability of 1 and all the 0’s would have a predicted probability of 0.

This will never happen in real life, but in general, a “good” model would be one where the 1’s have predicted probabilities that are close to 1 and 0’s have predicted probabilities that are close to 0.

Recap!

The logistic model

The response variable \(y\) takes two values, 1 or 0. If it is not coded that way, we (or R) will code it that way.

Let \(p(X) =\) probability that \(y=1\) for predictors \(X\) (for multiple logistic, this can mean multiple predictors \(x_1, x_2, ..., x_k\)).

We use the logit link function to construct a model that is linear in the log-odds scale:

\[ log \Bigg(\frac{p(X)}{1-p(X)} \Bigg) = \beta_0 + \beta_1x_1 + \beta_2x_2 + ...\beta_k x_k \]

Equivalently,

\[ p(X) = \frac{e^{\beta_0 + \beta_1x_1 + \beta_2x_2 + ...\beta_k x_k}}{1 + e^{\beta_0 + \beta_1x_1 + \beta_2x_2 + ...\beta_k x_k}} \]

and

\[ \frac{p(X)}{1-p(X)} = odds(X) = e^{\beta_0}e^{\beta_1x_1}e^{\beta_2x_2}...e^{\beta_k x_k} \]

This last equation gives us a nice way to interpret the exponentiated coefficients, for both categorical and quantitative predictors. If x_i is quantitative, then the interpretation is that with all other variables held fixed, a one unit change in x_i corresponds to multiplying the odds by \(e^{\beta_i}\). If x_i is an indicator variable created from a categorical variable (assume it is a 1 if category = L), then the exponentiated coefficient is an odds ratio. So, with all other variables held fixed, the odds for category L are \(e^{\beta_i}\) times the odds for the reference category.

IMPORTANT: The odds are always the odds of a 1, so be sure you know which level is coded as a 1 in the response variable.

R code

We use the glm function in R to fit these models. This function works in much the same way as lm, but we need to add additional arguments that tell it to do logistic regression and what link function to use. The notation is

glm(y ~ x1 + x2 + ... + xk, 
    data=dataname, 
    family = binomial(link = "logit"))

Note that if the response variable is not already coded as 0’s and 1’s, R will do that part. It will code the value that is lower alphabetically as 0 and the other as 1. This is important to know as it affects the interpretation of the model.

The tidy function gives the estimated coefficients, along with their standard errors and p-values.

The augment function can either give the log-odds (default) or it can give the probabilities, by adding type.predict = "response", like below.

augment(model, type.predict = "response")

It can also be used to find predicted probabilities for a new data set or a single new observation by using the newdata argument. Either provide a new dataset that contains all the predictor variables or add a tiny data.frame with values for all the variables in the model.

LS0tCnRpdGxlOiAiTG9naXN0aWMgUmVncmVzc2lvbiIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKSAjZm9yIHBsb3R0aW5nIGFuZCBzdW1tYXJpemluZwpsaWJyYXJ5KGdncmlkZ2VzKSAjZm9yIHJpZGdlIHBsb3RzCmxpYnJhcnkoZ2dtb3NhaWMpICNORVchIGZvciBtb3NhaWMgcGxvdHMKbGlicmFyeShtb2Rlcm5kaXZlKSAjZm9yIG5pY2UgbW9kZWwgb3V0cHV0CmxpYnJhcnkoYnJvb20pICNmb3IgbmljZSBtb2RlbCBvdXRwdXQgCmxpYnJhcnkoRWNkYXQpICNmb3IgZGF0YQp0aGVtZV9zZXQodGhlbWVfbWluaW1hbCgpKSAjY2hhbmdlcyB0aGUgdGhlbWUgb2YgZ2dwbG90cyB0byB0aGVtZV9taW5pbWFsLCBteSBwZXJzb25hbCBmYXZvcml0ZQpgYGAKCjxkaXYgY2xhc3M9ImFsZXJ0IGFsZXJ0LXN1Y2Nlc3MiPgogIDxzdHJvbmc+R09BTDo8L3N0cm9uZz4KCkJ5IHRoZSBlbmQgb2YgdGhlc2Ugbm90ZXMgYW5kIGFjdGl2aXRpZXMsIHlvdSBzaG91bGQgYmUgYWJsZSB0byBwZXJmb3JtIHRoZSBmb2xsb3dpbmcgdGFza3MuCgoqIENyZWF0ZSBhcHByb3ByaWF0ZSBwbG90cyB0byBleHBsb3JlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBjYXRlZ29yaWNhbCBvciBxdWFudGl0YXRpdmUgcHJlZGljdG9ycyBhbmQgYSBiaW5hcnkgcmVzcG9uc2UuICAKKiBLbm93IHdoZW4gYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGlzIGFwcHJvcHJpYXRlLiAgCiogRml0IGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCB1c2luZyBgZ2xtKClgLiAoRG9uJ3QgZm9yZ2V0IGBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IilgLikgIAoqIEludGVycHJldCBjb2VmZmljaWVudHMgZnJvbSBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgZm9yIGJvdGggY2F0ZWdvcmljYWwgYW5kIHF1YW50aXRhdGl2ZSBwcmVkaWN0b3JzLiAgCiogRmluZCB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXR5IG9mICJzdWNjZXNzIiBmb3IgYSBuZXcgb2JzZXJ2YXRpb24uICAKKiBQbG90IHRoZSBsb2dpc3RpYyBtb2RlbCBpbiBzaW1wbGUgY2FzZXMuCgo8L2Rpdj4KClNvIGZhciBhbGwgdGhlIG1vZGVsaW5nIHdlIGhhdmUgZG9uZSBpbiB0aGlzIGNvdXJzZSBoYXMgdXNlZCBhIHF1YW50aXRhdGl2ZSByZXNwb25zZSB2YXJpYWJsZS4gTm93LCB3ZSBhcmUgZ29pbmcgdG8gdGFsayBhYm91dCBob3cgdG8gbW9kZWwgYSBkaWZmZXJlbnQgdHlwZSBvZiByZXNwb25zZSB2YXJpYWJsZSwgb25lIHdpdGggYSBiaW5hcnkgcmVzcG9uc2U6IHllcy9ubywgdHJ1ZS9mYWxzZSwgZGVhZC9hbGl2ZSwgZXRjLiAKCkJlZm9yZSB3ZSBzdGFydCwgSSB3b3VsZCBsaWtlIHRvIHRha2Ugc29tZSB0aW1lIHRvIGRpc2N1c3Mgc29tZSByZWFsLWxpZmUgZXhhbXBsZXMuICoqQ2FuIHlvdSB0aGluayBvZiBhbnkgcGxhY2VzIHdoZXJlIHRoaXMgbWlnaHQgYmUgdXNlZD8qKgoKVG8gaW50cm9kdWNlIHRoaXMgY29uY2VwdCwgd2Ugd2lsbCB1c2UgdGhlIGBIbWRhYCBkYXRhc2V0IGZyb20gdGhlIGBFY2RhdGAgbGlicmFyeS4gSXQgY29udGFpbnMgZGF0YSBvbiBtb3J0Z2FnZSBhcHBsaWNhdGlvbiBkZW5pYWxzIGluIEJvc3Rvbi4gVGhlIGRhdGEgYXJlIGZyb20gMTk5Ny05OC4gWW91IGNhbiBsZWFybiBtb3JlIGFib3V0IHRoZSBkYXRhIGJ5IHR5cGluZyBgSG1kYWAgaW50byBoZWxwIG9yIGA/SG1kYWAgaW4gdGhlIENvbnNvbGUuIFRoZSByZXNwb25zZSB2YXJpYWJsZSBpcyBjYWxsZWQgYGRlbnlgIGFuZCBpcyBhIGB5ZXNgIGlmIHRoZSBhcHBsaWNhbnQgd2FzIGRlbmllZCwgYW5kIGEgYG5vYCBvdGhlcndpc2UuCgpXZSB3aWxsIGZpbHRlciBvdXQgYSBjb3VwbGUgb3V0bGllcnMsIHNvIHVzZSBgSG1kYTJgIGZyb20gbm93IG9uLgoKYGBge3J9CkhtZGEyIDwtIAogIEhtZGEgJT4lIAogIGZpbHRlcihoaXIgPCAxLCBkaXIgPCAxLCApICU+JSAKICBtdXRhdGUoZGVueV9xdWFudCA9IGlmZWxzZShkZW55ID09ICJ5ZXMiLCAxLCAwKSkKYGBgCgoKIyBFeHBsb3JhdG9yeSBhbmFseXNpcwoKV2Ugd2lsbCBsZWFybiBzb21lIG5ldyB0ZWNobmlxdWVzIHRvIGV4YW1pbmUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHF1YW50aXRhdGl2ZSB2YXJpYWJsZXMgYW5kIHRoZSBiaW5hcnkgcmVzcG9uc2UuIE9uZSBvcHRpb24sIHNob3duIGJlbG93LCBpcyB0byB1c2UgYm94cGxvdHMuIEkgYWxzbyBhZGRlZCB0aGUgb3JpZ2luYWwgZGF0YS4KCmBgYHtyfQpIbWRhMiAlPiUgCiAgZ2dwbG90KGFlcyh5ID0gZGVueSwgeCA9IGRpcikpICsKICBnZW9tX2ppdHRlcih3aWR0aCA9IC4xLCBzaXplID0gLjIsIGFscGhhID0gLjUsIGNvbG9yID0gImRhcmtncmF5IikgKwogIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlID0gTkEsICMgZG9lc24ndCBpbmRpY2F0ZSBvdXRsaWVycwogICAgICAgICAgICAgICB2YXJ3aWR0aCA9IFRSVUUsICMgd2lkdGggb2YgYm94cGxvdCBjaGFuZ2VzIGFjY29yZGluZyB0byBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zCiAgICAgICAgICAgICAgIGFscGhhID0gLjUpCmBgYAoKT3Igd2UgY291bGQgdXNlIHJpZGdlIHBsb3RzLiAKCmBgYHtyfQpIbWRhMiAlPiUgCiAgZ2dwbG90KGFlcyh4PSBkaXIsIHkgPSBkZW55LCBmaWxsID0gZGVueSkpICsKICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGFscGhhID0gLjUpCmBgYAoKVG8gZXhwbG9yZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdHdvIGNhdGVnb3JpY2FsIHZhcmlhYmxlcywgd2UgY2FuIHVzZSBiYXJwbG90cy4gQmVsb3csIEkgY29uc3RydWN0IHRoZW0gaW4gdGhyZWUgZGlmZmVyZW50IHdheXMuICpXaGVuIHdvdWxkIHlvdSB1c2UgZWFjaCBvZiB0aGVtPyBXaGF0IGFyZSB0aGUgZGlmZmVyZW50IHRoaW5ncyBlYWNoIG9mIHRoZW0gc2hvdyB3ZWxsPyoKCmBgYHtyfQpnZ3Bsb3QoZGF0YT1IbWRhMikgKwogIGdlb21fYmFyKGFlcyh4PXNpbmdsZSwgZmlsbD1kZW55KSkKYGBgCgpgYGB7cn0KZ2dwbG90KGRhdGE9SG1kYTIpICsKICBnZW9tX2JhcihhZXMoeD1zaW5nbGUsIGZpbGw9ZGVueSksCiAgICAgICAgICAgcG9zaXRpb24gPSAiZG9kZ2UiKQpgYGAKCmBgYHtyfQpnZ3Bsb3QoZGF0YT1IbWRhMikgKwogIGdlb21fYmFyKGFlcyh4PXNpbmdsZSwgZmlsbD1kZW55KSwgCiAgICAgICAgICAgcG9zaXRpb24gPSAiZmlsbCIpICsKICBsYWJzKHkgPSAicHJvcG9ydGlvbiIpCmBgYAoKQSBtb3NhaWMgcGxvdCBpcyBhbiBldmVuIG1vcmUgaW5mb3JtYXRpdmUgZ3JhcGggdGhhbiB0aGUgbGFzdCBwbG90IGZyb20gYWJvdmUuIFRoZSB3aWR0aHMgcmVmbGVjdCB0aGUgcHJvcG9ydGlvbiBpbiBlYWNoIGxldmVsIG9mIHRoZSB4LWF4aXMgdmFyaWFibGUsIGluIHRoaXMgY2FzZSBgc2luZ2xlYC4gVGhpcyBmdW5jdGlvbiBpcyBhIGJpdCB0ZWRpb3VzIGJlY2F1c2UgeW91IGhhdmUgdG8gZG8gc29tZSBsYWJlbGluZyBvbiB5b3VyIG93bi4KCmBgYHtyfQpIbWRhMiAlPiUgCiAgZ2dwbG90KCkgKwogIGdlb21fbW9zYWljKGFlcyh4ID0gcHJvZHVjdChzaW5nbGUpLCAKICAgICAgICAgICAgICAgICAgZmlsbCA9IGRlbnkpKSArCiAgbGFicyh4ID0gIlNpbmdsZSIsIHkgPSAiRGVueSIpICsKICBndWlkZXMoZmlsbCA9ICJub25lIikKCmBgYAoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+CiAgPHN0cm9uZz5ZT1VSIFRVUk4hPC9zdHJvbmc+CgpFeHBsb3JlIHNvbWUgb3RoZXIgcG90ZW50aWFsIHByZWRpY3RvciB2YXJpYWJsZXMgb24geW91ciBvd24uIERvIGFueSB2YXJpYWJsZXMgc2VlbSB0byBiZSBnb29kIHByZWRpY3RvcnMgb2YgYGRlbnlgPyAKCjwvZGl2PgoKIyBUcnkgdG8gYXBwbHkgcmVndWxhciByZWdyZXNzaW9uIHRlY2huaXF1ZXMgLi4uIChUeXBpY2FsbHkgYSBiYWQgaWRlYSkKClNpbmNlIHdlIGtub3cgaG93IHRvIG1vZGVsIGRhdGEgd2l0aCBhIHF1YW50aXRhdGl2ZSByZXNwb25zZSwgd2UgbWlnaHQgdGhpbmsgb2YgdHVybmluZyB0aGUgYGRlbnlgIHZhcmlhYmxlIGludG8gYSBxdWFudGl0YXRpdmUgdmFyaWFibGUuIEkgZGlkIHRoYXQgd2hlbiBJIGNyZWF0ZWQgYEhtZGEyYDsgYGRlbnlfcXVhbnRgIGlzIGEgMSBpZiB0aGUgYXBwbGljYW50IHdhcyBkZW5pZWQgYW5kIDAgb3RoZXJ3aXNlLgoKTm93LCB3ZSBjb3VsZCB1c2UgdGhpcyBhcyBvdXIgcmVzcG9uc2UgdmFyaWFibGUgaW4gYSBsaW5lYXIgbW9kZWwsIHNhbWUgYXMgd2UgaGF2ZSBiZWVuIGRvaW5nIGFsbCBzZW1lc3Rlci4gTGV0J3MgdXNlIGBkaXJgIGFuZCBgc2luZ2xlYCBhcyBleHBsYW5hdG9yeSB2YXJpYWJsZXMuICoqSW50ZXJwcmV0IGVhY2ggb2YgdGhlIGNvZWZmaWNpZW50cyBpbiB0aGUgbW9kZWwgYmVsb3cuIERvIHlvdSBzZWUgYW4gaXNzdWU/IChIaW50LCB0cnkgcHJlZGljdGluZyB0aGUgcmVzcG9uc2UgZm9yIG5vbi1zaW5nbGUgYXBwbGljYW50cyB3aXRoIGxvdyBkZWJ0IHBheW1lbnRzIHRvIHRvdGFsIGluY29tZSByYXRpbywgYGRpcmApLioqCgpgYGB7cn0KbG1fZGVueV9XUk9ORyA8LSBsbShkZW55X3F1YW50IH4gZGlyICsgc2luZ2xlLCBkYXRhID0gSG1kYTIpCnRpZHkobG1fZGVueV9XUk9ORykKYGBgCgpJdCBtaWdodCBhbHNvIGhlbHAgdG8gbG9vayBhdCB0aGUgcGxvdC4KCmBgYHtyfQphdWdtZW50KGxtX2RlbnlfV1JPTkcpICU+JSAKICBnZ3Bsb3QoYWVzKHg9ZGlyLCB5PWRlbnlfcXVhbnQsIGNvbG9yPXNpbmdsZSkpICsKICBnZW9tX2ppdHRlcihoZWlnaHQgPSAuMDUsIHNpemUgPSAuMiwgYWxwaGEgPSAuNSkgKwogIGdlb21fbGluZShhZXMoeSA9IC5maXR0ZWQpKQpgYGAKCgpIb3cgZG8gd2Ugc29sdmUgdGhpcyBwcm9ibGVtPyBXZSB3YW50IHRvIGd1YXJhbnRlZSB0aGF0IHRoZSBtb2RlbCB2YWx1ZXMgYXJlIGJldHdlZW4gMCBhbmQgMS4gV2UgYXJlIGdvaW5nIHRvIHVzZSBzb21ldGhpbmcgY2FsbGVkIGEgKmxpbmsgZnVuY3Rpb24qLiBXZSBjYW4gdGhpbmsgYWJvdXQgdGhpcyBpbiB0d28gc3RlcHM6CgoxLiBGaXQgYSBsaW5lYXIgbW9kZWwgdGhlIHdheSB3ZSBhcmUgdXNlZCB0bywgYWRkaW5nIHRlcm1zIHRpbWVzIHRoZWlyIGNvZWZmaWNpZW50cy4gKE5vdGUgdGhhdCB0aGUgbWV0aG9kIGlzbid0IGV4YWN0bHkgdGhlIHNhbWUgLi4uIEknbGwgdGFsayBhYm91dCB0aGF0IGxhdGVyKS4gQ2FsbCB0aGlzIG91dHB1dCAqeSouIFRoaXMgdmFsdWUgaXMgbm90IGEgcHJvYmFiaWxpdHkuCgoyLiBVc2UgYSBsaW5rIGZ1bmN0aW9uIHRvIHRyYW5zbGF0ZSB0aGUgdmFsdWUgKnkqIHRvIGEgc2NhbGUgYmV0d2VlbiAwIGFuZCAxLCAqcCogKHdoaWNoIHN0YW5kcyBmb3IgcHJvYmFiaWxpdHkpLiBUaGUgZnVuY3Rpb24gdGhhdCB3aWxsIGJlIHVzZWQgaXMgY2FsbGVkIHRoZSAibG9naXQgbGluayIgb3IgdGhlIGxvZ2lzdGljIHRyYW5zZm9ybWF0aW9uIGFuZCBpdCBpcyBkZWZpbmVkIGFzIAoKJCQKcCA9IFxmcmFje2VeeX17KDEgKyBlXnkpfQokJAoKIyBGaXR0aW5nIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHdpdGggYGdsbSgpYAoKTGV0J3MgaW52ZXN0aWdhdGUgaG93IHRvIGRvIHRoaXMgaW4gUi4gV2UgY2FuIHVzZSBhIGZ1bmN0aW9uIGNhbGxlZCBgZ2xtKClgIChnZW5lcmFsaXplZCBsaW5lYXIgbW9kZWwpIHRvIGZpdCB0aGlzIG1vZGVsLiBCZWZvcmUgZXhwbGFpbmluZyBob3cgdGhlIG1vZGVsIGlzIGZpdCwgbGV0J3MgZml0IGl0IGFuZCB0YWxrIGFib3V0IGludGVycHJldGluZyB0aGUgY29lZmZpY2llbnRzIGFuZCBwcmVkaWN0aW5nIG5ldyB2YWx1ZXMuIAoKCmBgYHtyfQoKZ2xtX2RlbnkgPC0gZ2xtKGRlbnkgfiBkaXIgKyBzaW5nbGUsIAogICAgICAgICAgICAgICAgZGF0YSA9IEhtZGEyLAogICAgICAgICAgICAgICAgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpICNuZXchCiAgICAgICAgICAgICAgICApIAoKI05PVEU6IGdldF9yZWdyZXNzaW9uX3RhYmxlKCkgZG9lc24ndCB3b3JrIGZvciBnbG0ncwp0aWR5KGdsbV9kZW55KSAKYGBgCgojIyBFeHBsYW5hdGlvbgoKUmVtZW1iZXIsIHRoZSB2YWx1ZSB0aGF0IGNvbWVzIG91dCBvZiB0aGUgbGluZWFyIG1vZGVsIHBvcnRpb24gaXMgKnkqIGFuZCAqeSogaXMgdGhlIHJlc3VsdCBvZiBhIGxpbmVhciBlcXVhdGlvbi4gU28sIAoKJCQKXGhhdHt5fSA9IFxoYXR7XGJldGF9XzAgKyBcaGF0e1xiZXRhfV8xIHhfMSArXGhhdHtcYmV0YX1fMiB4XzIgKyAuLi4gKyBcaGF0e1xiZXRhfV9wIHhfcAokJAoKTGV0J3MgYWxzbyBzb2x2ZSBmb3IgJHkkIGluIAoKJCQgClxoYXR7cH0gPSBcZnJhY3tlXntcaGF0e3l9fX17KDEgKyBlXntcaGF0e3l9fSl9LAokJAoKd2hpY2gsIGFmdGVyIGEgYml0IG9mIG1hdGgsIGdpdmVzOgokJApcaGF0e3l9ID0gbG9nXEJpZyhcZnJhY3tcaGF0e3B9fXsxLVxoYXR7cH19XEJpZykKJCQgCgpvciAKJCQKZV57XGhhdHt5fX0gPSBcZnJhY3tcaGF0e3B9fXsxLVxoYXR7cH19LgokJAoKQ29tYmluaW5nIHRoZXNlLCB3ZSBoYXZlIAoKJCQKbG9nXEJpZyhcZnJhY3tcaGF0e3B9fXsxLVxoYXR7cH19XEJpZykgPSBcaGF0e1xiZXRhfV8wICsgXGhhdHtcYmV0YX1fMSB4XzEgK1xoYXR7XGJldGF9XzIgeF8yICsgLi4uICsgXGhhdHtcYmV0YX1fcCB4X3AKJCQgCgpvciAKCiQkClxmcmFje1xoYXR7cH19ezEtXGhhdHtwfX0gPSBlXntcaGF0e1xiZXRhfV8wfWVee1xoYXR7XGJldGF9XzF4XzF9ZV57XGhhdHtcYmV0YX1fMiB4XzJ9IFxjZG90cyBlXntcaGF0e1xiZXRhfV9wIHhfcH0KJCQKClRoZXNlIGVxdWF0aW9ucyBnaXZlIHVzIG5pY2Ugd2F5cyB0byBpbnRlcnByZXQgdGhlIGNvZWZmaWNpZW50cy4gCgpUaGUgcXVhbnRpdHkgJFxmcmFje1xoYXR7cH19ezEtXGhhdHtwfX0kIGlzIGNhbGxlZCB0aGUgKm9kZHMqLiAKCiMjIyBUYW5nZW50OiBXaGF0IGFyZSBvZGRzPwoKQmVmb3JlIHdlIG1vdmUgb24gdG8gaW50ZXJwcmV0aW5nIHRoZSByZXN1bHRzIG9mIG91ciBtb2RlbHMsIGxldCdzIGZpcnN0IG1ha2Ugc3VyZSB3ZSBoYXZlIGFuIHVuZGVyc3RhbmRpbmcgb2Ygb2Rkcy4gCgpMZXQgeCBiZSBzb21lIGV2ZW50IChpZS4gZ2V0dGluZyBhIGhlYWRzLCB3aW5uaW5nIHRoZSBsb3R0ZXJ5LCBidXlpbmcgYSBzaGlydCwgcGFzc2luZyB0aGlzIGNsYXNzLCAuLi4pLiBUaGVuIHRoZSBvZGRzIG9mIHgsICRvZGRzKHgpJCwgaXMgZGVmaW5lZCBhczoKCiQkCm9kZHMoeCkgPSBcZnJhY3twKHgpfXsxIC0gcCh4KX0sCiQkCgp0aGUgcHJvYmFiaWxpdHkgb2YgeCBkaXZpZGVkIGJ5IDEgbWludXMgdGhlIHByb2JhYmlsaXR5IG9mIHggKHdoaWNoIGlzIHRoZSBwcm9iYWJpbGl0eSBvZiBvZiBub3QgeCkuCgo8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1pbmZvIj4KICA8c3Ryb25nPllPVVIgVFVSTiE8L3N0cm9uZz4KICAKMS4gQXNzdW1lIHRoZSBwcm9iYWJpbGl0eSBvZiBmbGlwcGluZyBoZWFkcyBvbiBhIGNvaW4gaXMgLjUuIEZpbmQgdGhlIG9kZHMgb2YgZmxpcHBpbmcgYSBoZWFkLiAgIAoyLiBJbiBhIHJlZ3VsYXIgNTIgY2FyZCBkZWNrIG9mIGNhcmRzLCB0aGUgb2RkcyBvZiBjaG9vc2luZyBhIGNhcmQgdGhhdCBpcyBoZWFydHMuICAKMy4gSWYgdGhlIHByb2JhYmlsaXR5IG9mIHN1cnZpdmFsIGlzICRwPS44MCQsIHdoYXQgYXJlIHRoZSBvZGRzIG9mIHN1cnZpdmFsPyAgCjQuIEluIHRoZSBwcmV2aW91cyBxdWVzdGlvbiwgd2hhdCBhcmUgdGhlIGxvZyBvZGRzIG9mIHN1cnZpdmFsPyAgCjUuIElzIGl0IGVhc3kgdG8gdGhpbmsgYWJvdXQgdGhlIGxvZyBvZGRzIHNjYWxlPyBPciBldmVuIHRoZSBvZGRzIHNjYWxlPwoKPC9kaXY+CgojIyBCYWNrIHRvIGV4cGxhbmF0aW9uCgpSZWNhbGwgdGhlIG1vZGVsIHdlIHdlcmUganVzdCBsb29raW5nIGF0LiBJIGFsc28gYWRkZWQgYSBjb2x1bW4gb2YgdGhlIGV4cG9uZW50aWF0ZWQgY29lZmZpY2llbnRzICgkZV57Y29lZmZpY2llbnR9JCkuCgpgYGB7cn0KdGlkeShnbG1fZGVueSkgJT4lIAogIHNlbGVjdCh0ZXJtLCBlc3RpbWF0ZSkgJT4lIAogIG11dGF0ZShleHBfZXN0ID0gZXhwKGVzdGltYXRlKSkKYGBgCgpXZSBzaG91bGQga2VlcCBpbiBtaW5kOgoKMS4gVGhlIGNvZWZmaWNpZW50cyB0aGF0IGFyZSBpbiB0aGUgb3V0cHV0IGFyZSBvbiB0aGUgYGxvZyhvZGRzKWAgc2NhbGUgLSBZVUNLISBXZSBkb24ndCB1c3VhbGx5IHdhbnQgdG8gaW50ZXJwcmV0IHRoaW5ncyBvbiB0aGF0IHNjYWxlLiAgCjIuIFRoZSBleHBvbmVudGlhdGVkIGVxdWF0aW9uIGlzIG11bHRpcGxpY2F0aXZlIG9uIHRoZSBvZGRzIHNjYWxlLiBUaGF0J3MgYSBiZXR0ZXIgd2F5IHRvIGludGVycHJldCBvdXIgcmVzdWx0cwoKPGRpdiBjbGFzcz0iYWxlcnQgYWxlcnQtaW5mbyI+CiAgPHN0cm9uZz5ZT1VSIFRVUk4hPC9zdHJvbmc+CgpJbnRlcnByZXQvZmluZCB0aGUgZm9sbG93aW5nOgoKMS4gJFxoYXR7XGJldGF9XzAkLCB0aGUgaW50ZXJjZXB0LgoyLiAkZV57XGhhdHtcYmV0YX1fMH0kLCB0aGUgZXhwb25lbnRpYXRlZCBpbnRlcmNlcHQuCjMuICRcaGF0e1xiZXRhfV8xJCwgdGhlIGNvZWZmaWNpZW50IGZvciBgZGlyYC4gV2h5IG1pZ2h0IHRoaXMgYmUgYSB1c2VsZXNzIGludGVycHJldGF0aW9uPyBJbiB3aGF0IHVuaXRzIG1pZ2h0IHdlIHdhbnQgdG8gaW50ZXJwcmV0IHRoaXM/CjQuICRlXntcaGF0e1xiZXRhfV8xfSQuIAo1LiAkXGhhdHtcYmV0YX1fMiQsIAo2LiAkZV57XGhhdHtcYmV0YX1fMn0kIEhpbnQ6IGNvbXBhcmUgdGhlIG9kZHMgb2YgZGVuaWFsIGZvciBzaW5nbGUgYW5kIG5vbi1zaW5nbGUgd2l0aCB0aGUgc2FtZSBgZGlyYC4gIAo3LiBUaGUgcHJvYmFiaWxpdHkgb2YgYSBkZW5pZWQgbW9ydGdhZ2UgYXBwbGljYXRpb24gZm9yIGEgc2luZ2xlIGFwcGxpY2FudCB3aG8gaGFzIGEgZGVidCBwYXltZW50cyB0byB0b3RhbCBpbmNvbWUgcmF0aW8gb2YgLjIuICAKOC4gVGhlIHByb2JhYmlsaXR5IG9mIGFuIGFjY2VwdGVkIG1vcnRnYWdlIGZvciBhIHNpbmdsZSBhcHBsaWNhbnQgd2hvIGhhcyBhIGRlYnQgcGF5bWVudHMgdG8gdG90YWwgaW5jb21lIHJhdGlvIG9mIC4yLiAKCjwvZGl2PgoKIyMgUHJlZGljdGlvbgoKV2UgY2FuIHVzZSB0aGUgYGF1Z21lbnQoKWAgZnVuY3Rpb24gdG8gcHJlZGljdCBuZXcgdmFsdWVzLCBzaW1pbGFyIHRvIGhvdyB3ZSB1c2VkIGl0IGJlZm9yZS4gU2VlIGBhdWdtZW50LmdsbWAgaW4gdGhlIGhlbHAgZm9yIG1vcmUgZGV0YWlscy4KCkxldCdzIHNob3cgaG93IHRvIGRvIHRoYXQgdXNpbmcgcHJvYmxlbSA3IGZyb20gYWJvdmUuICoqV2hhdCBpcyAuZml0dGVkPyoqCgpgYGB7cn0KYXVnbWVudChnbG1fZGVueSwgCiAgICAgICAgbmV3ZGF0YSA9IHRpYmJsZShzaW5nbGUgPSAieWVzIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICBkaXIgPSAuMikpCmBgYAoKTGV0J3MgcHV0IHRoaXMgaW4gdGVybXMgb2YgcHJvYmFiaWxpdHk6CgpgYGB7cn0KYXVnbWVudChnbG1fZGVueSwgCiAgICAgICAgbmV3ZGF0YSA9IHRpYmJsZShzaW5nbGUgPSAieWVzIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICBkaXIgPSAuMiksCiAgICAgICAgdHlwZS5wcmVkaWN0ID0gInJlc3BvbnNlIikKYGBgCgpKdXN0IGxpa2Ugd2l0aCBsaW5lYXIgcmVncmVzc2lvbiwgaW4gc2ltcGxlIGNhc2VzLCB3ZSBjYW4gcGxvdCB0aGUgbW9kZWwgdmFsdWVzICh0aGUgcHJvYmFiaWxpdGllcykuIFRoaXMgaXMgb25lIG9mIHRob3NlIGNhc2VzLiAqKkhvdyBkb2VzIHRoaXMgcGxvdCBsb29rIGRpZmZlcmVudCBmcm9tIHdoZW4gd2UgdXNlZCBsaW5lYXIgcmVncmVzc2lvbiB0byBtb2RlbCBgZGVueWA/KioKCmBgYHtyfQphdWdtZW50KGdsbV9kZW55LCAKICAgICAgICBkYXRhID0gSG1kYTIsCiAgICAgICAgdHlwZS5wcmVkaWN0PSJyZXNwb25zZSIpICU+JSAKICAjSSBuZWVkIHRvIHBsb3QgZGVueSBhcyBhIDAvMQogIGdncGxvdChhZXMoeD1kaXIsIHk9ZGVueV9xdWFudCwgY29sb3I9c2luZ2xlKSkgKwogIGdlb21faml0dGVyKGhlaWdodCA9IC4wNSwgc2l6ZSA9IC4yLCBhbHBoYSA9IC41KSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gLmZpdHRlZCkpCmBgYAoKCiMjIEZpdHRpbmcgdGhlIG1vZGVsCgpXZSBsZWFybmVkIHRoYXQgbGluZWFyIG1vZGVscyBhcmUgZml0IGJ5IGZpbmRpbmcgdGhlIGNvZWZmaWNpZW50cyB0aGF0IG1pbmltaXplIHRoZSBzdW0gb2YgdGhlIHNxdWFyZWQgcmVzaWR1YWxzLiBDb2VmZmljaWVudHMgaW4gbG9naXN0aWMgcmVncmVzc2lvbiBtYXhpbWl6ZSB0aGUgbGlrZWxpaG9vZCBmdW5jdGlvbi4gSW4gdGhlIGNhc2Ugb2YgbG9naXN0aWMgcmVncmVzc2lvbiwgdGhlIGxpa2VsaWhvb2QgZnVuY3Rpb24gaXM6IAoKJCQKXHByb2Rfe2k9MX1ebiBwX2lee3lfaX0oMS1wX2kpXnsxLXlfaX0uCiQkCgpUaGlzIGZvcm11bGEgbWF5IGxvb2sgY29tcGxpY2F0ZWQgYnV0IGl0J3Mgbm90IHRvbyBiYWQgaWYgd2UgYnJlYWsgYXBhcnQgdGhlIHBpZWNlcy4gCgpGaXJzdCwgdGhlIGJpZyBwaSwkXHByb2Rfe2k9MX1ebiQgbWVhbnMgbXVsdGlwbGljYXRpb24uCgpUaGUgJHlfaSQgaXMgdGhlIG9ic2VydmVkIHZhbHVlIGZvciB0aGUgJGlee3RofSQgb2JzZXJ2YXRpb24gaW4gdGhlIGRhdGFzZXQuIFNvLCBpdCBpcyBlaXRoZXIgMCBvciAxLiAKCk5vdGljZSB0aGF0IHdoZW4gJHlfaSA9IDEkLCAKCiQkCnBfaV57eV9pfSgxLXBfaSleezEteV9pfSA9IHBfaQokJCAKCmFuZCB3aGVuICR5X2kgPSAwJCwgCgokJApwX2lee3lfaX0oMS1wX2kpXnsxLXlfaX0gPSAxIC0gcF9pLgokJCAKClNvLCB0aGlzIGlzIGEgcHJvZHVjdCBvZiBlaXRoZXIgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIChmb3IgY2FzZXMgd2hlbiAkeV9pID0gMSQpIG9yIG9uZSBtaW51cyB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgKGZvciBjYXNlcyB3aGVuICR5X2kgPSAwJCkuIAoKVGhlIGxhcmdlc3QgdGhpcyB2YWx1ZSBjYW4gYmUgaXMgJDEkLCB3aGljaCB3b3VsZCBoYXBwZW4gaWYgYWxsIHRoZSAxJ3MgaGFkIGEgcHJlZGljdGVkIHByb2JhYmlsaXR5IG9mIDEgYW5kIGFsbCB0aGUgMCdzIHdvdWxkIGhhdmUgYSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgb2YgMC4gCgpUaGlzIHdpbGwgbmV2ZXIgaGFwcGVuIGluIHJlYWwgbGlmZSwgYnV0IGluIGdlbmVyYWwsIGEgImdvb2QiIG1vZGVsIHdvdWxkIGJlIG9uZSB3aGVyZSB0aGUgMSdzIGhhdmUgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgdGhhdCBhcmUgY2xvc2UgdG8gMSBhbmQgMCdzIGhhdmUgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgdGhhdCBhcmUgY2xvc2UgdG8gMC4gCgoKIyBSZWNhcCEKCiMjIFRoZSBsb2dpc3RpYyBtb2RlbAoKVGhlIHJlc3BvbnNlIHZhcmlhYmxlICR5JCB0YWtlcyB0d28gdmFsdWVzLCAxIG9yIDAuIElmIGl0IGlzIG5vdCBjb2RlZCB0aGF0IHdheSwgd2UgKG9yIFIpIHdpbGwgY29kZSBpdCB0aGF0IHdheS4KCkxldCAkcChYKSA9JCBwcm9iYWJpbGl0eSB0aGF0ICR5PTEkIGZvciBwcmVkaWN0b3JzICRYJCAoZm9yIG11bHRpcGxlIGxvZ2lzdGljLCB0aGlzIGNhbiBtZWFuIG11bHRpcGxlIHByZWRpY3RvcnMgJHhfMSwgeF8yLCAuLi4sIHhfayQpLgoKV2UgdXNlIHRoZSBsb2dpdCBsaW5rIGZ1bmN0aW9uIHRvIGNvbnN0cnVjdCBhIG1vZGVsIHRoYXQgaXMgbGluZWFyIGluIHRoZSBsb2ctb2RkcyBzY2FsZToKCiQkCmxvZyBcQmlnZyhcZnJhY3twKFgpfXsxLXAoWCl9ICBcQmlnZykgPSBcYmV0YV8wICsgXGJldGFfMXhfMSArIFxiZXRhXzJ4XzIgKyAuLi5cYmV0YV9rIHhfawokJAoKRXF1aXZhbGVudGx5LAoKJCQKcChYKSA9IFxmcmFje2Vee1xiZXRhXzAgKyBcYmV0YV8xeF8xICsgXGJldGFfMnhfMiArIC4uLlxiZXRhX2sgeF9rfX17MSArIGVee1xiZXRhXzAgKyBcYmV0YV8xeF8xICsgXGJldGFfMnhfMiArIC4uLlxiZXRhX2sgeF9rfX0KJCQKCmFuZCAKCiQkClxmcmFje3AoWCl9ezEtcChYKX0gPSBvZGRzKFgpID0gZV57XGJldGFfMH1lXntcYmV0YV8xeF8xfWVee1xiZXRhXzJ4XzJ9Li4uZV57XGJldGFfayB4X2t9CiQkCgpUaGlzIGxhc3QgZXF1YXRpb24gZ2l2ZXMgdXMgYSBuaWNlIHdheSB0byBpbnRlcnByZXQgdGhlIGV4cG9uZW50aWF0ZWQgY29lZmZpY2llbnRzLCBmb3IgYm90aCBjYXRlZ29yaWNhbCBhbmQgcXVhbnRpdGF0aXZlIHByZWRpY3RvcnMuIElmICp4X2kqIGlzIHF1YW50aXRhdGl2ZSwgdGhlbiB0aGUgaW50ZXJwcmV0YXRpb24gaXMgdGhhdCB3aXRoIGFsbCBvdGhlciB2YXJpYWJsZXMgaGVsZCBmaXhlZCwgYSBvbmUgdW5pdCBjaGFuZ2UgaW4gKnhfaSogY29ycmVzcG9uZHMgdG8gbXVsdGlwbHlpbmcgdGhlIG9kZHMgYnkgJGVee1xiZXRhX2l9JC4gSWYgKnhfaSogaXMgYW4gaW5kaWNhdG9yIHZhcmlhYmxlIGNyZWF0ZWQgZnJvbSBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlIChhc3N1bWUgaXQgaXMgYSAxIGlmIGNhdGVnb3J5ID0gTCksIHRoZW4gdGhlIGV4cG9uZW50aWF0ZWQgY29lZmZpY2llbnQgaXMgYW4gb2RkcyByYXRpby4gU28sIHdpdGggYWxsIG90aGVyIHZhcmlhYmxlcyBoZWxkIGZpeGVkLCB0aGUgb2RkcyBmb3IgY2F0ZWdvcnkgTCBhcmUgJGVee1xiZXRhX2l9JCB0aW1lcyB0aGUgb2RkcyBmb3IgdGhlIHJlZmVyZW5jZSBjYXRlZ29yeS4gCgpJTVBPUlRBTlQ6IFRoZSBvZGRzIGFyZSBhbHdheXMgdGhlIG9kZHMgb2YgYSAxLCBzbyBiZSBzdXJlIHlvdSBrbm93IHdoaWNoIGxldmVsIGlzIGNvZGVkIGFzIGEgMSBpbiB0aGUgcmVzcG9uc2UgdmFyaWFibGUuIAoKIyMgUiBjb2RlCgpXZSB1c2UgdGhlIGBnbG1gIGZ1bmN0aW9uIGluIFIgdG8gZml0IHRoZXNlIG1vZGVscy4gVGhpcyBmdW5jdGlvbiB3b3JrcyBpbiBtdWNoIHRoZSBzYW1lIHdheSBhcyBgbG1gLCBidXQgd2UgbmVlZCB0byBhZGQgYWRkaXRpb25hbCBhcmd1bWVudHMgdGhhdCB0ZWxsIGl0IHRvIGRvIGxvZ2lzdGljIHJlZ3Jlc3Npb24gYW5kIHdoYXQgbGluayBmdW5jdGlvbiB0byB1c2UuIFRoZSBub3RhdGlvbiBpcwoKYGBgCmdsbSh5IH4geDEgKyB4MiArIC4uLiArIHhrLCAKICAgIGRhdGE9ZGF0YW5hbWUsIAogICAgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJsb2dpdCIpKQpgYGAKCk5vdGUgdGhhdCBpZiB0aGUgcmVzcG9uc2UgdmFyaWFibGUgaXMgbm90IGFscmVhZHkgY29kZWQgYXMgMCdzIGFuZCAxJ3MsIFIgd2lsbCBkbyB0aGF0IHBhcnQuIEl0IHdpbGwgY29kZSB0aGUgdmFsdWUgdGhhdCBpcyBsb3dlciBhbHBoYWJldGljYWxseSBhcyAwIGFuZCB0aGUgb3RoZXIgYXMgMS4gVGhpcyBpcyBpbXBvcnRhbnQgdG8ga25vdyBhcyBpdCBhZmZlY3RzIHRoZSBpbnRlcnByZXRhdGlvbiBvZiB0aGUgbW9kZWwuCgpUaGUgYHRpZHlgIGZ1bmN0aW9uIGdpdmVzIHRoZSBlc3RpbWF0ZWQgY29lZmZpY2llbnRzLCBhbG9uZyB3aXRoIHRoZWlyIHN0YW5kYXJkIGVycm9ycyBhbmQgcC12YWx1ZXMuIAoKVGhlIGBhdWdtZW50YCBmdW5jdGlvbiBjYW4gZWl0aGVyIGdpdmUgdGhlIGxvZy1vZGRzIChkZWZhdWx0KSBvciBpdCBjYW4gZ2l2ZSB0aGUgcHJvYmFiaWxpdGllcywgYnkgYWRkaW5nIGB0eXBlLnByZWRpY3QgPSAicmVzcG9uc2UiYCwgbGlrZSBiZWxvdy4KCmBgYAphdWdtZW50KG1vZGVsLCB0eXBlLnByZWRpY3QgPSAicmVzcG9uc2UiKQpgYGAKCkl0IGNhbiBhbHNvIGJlIHVzZWQgdG8gZmluZCBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyBmb3IgYSBuZXcgZGF0YSBzZXQgb3IgYSBzaW5nbGUgbmV3IG9ic2VydmF0aW9uIGJ5IHVzaW5nIHRoZSBgbmV3ZGF0YWAgYXJndW1lbnQuIEVpdGhlciBwcm92aWRlIGEgbmV3IGRhdGFzZXQgdGhhdCBjb250YWlucyBhbGwgdGhlIHByZWRpY3RvciB2YXJpYWJsZXMgb3IgYWRkIGEgdGlueSBkYXRhLmZyYW1lIHdpdGggdmFsdWVzIGZvciBhbGwgdGhlIHZhcmlhYmxlcyBpbiB0aGUgbW9kZWwuIAoKCgoKCgoKCg==