Introduction

We will explore logistic regression by examining the classic mtcars data set.

Preliminaries

library(car)
library(broom)
library(mosaic)
library(caret)
package ‘caret’ was built under R version 3.5.2

Explore data

mtcars
str(mtcars)
'data.frame':   32 obs. of  11 variables:
 $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
 $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
 $ disp: num  160 160 108 258 360 ...
 $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
 $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
 $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
 $ qsec: num  16.5 17 18.6 19.4 17 ...
 $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
 $ am  : num  1 1 1 0 0 0 0 0 0 0 ...
 $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
 $ carb: num  4 4 1 1 2 1 4 2 2 4 ...

Consider the am variable that indicates automatic or manual transmission.

tally(~ am, data = mtcars)
am
 0  1 
19 13 

Note that the variable is not coded as a factor variable. We would normally rectify this situation by using the factor command, but we will delay that step for the time being. The reason is that we’re going to create scatterplots below, and that requires variables to be recorded numerically.

Let’s try to predict am (the transmission type—automatic or manual) from mpg. (Remember, this was 1974!) What about linear regression?

ggplot(mtcars, aes(y = am, x = mpg)) +
    geom_jitter(height = 0) +
    geom_smooth(method =  "lm")

This clearly doesn’t work.

The plot below uses a loess curve to try to “fit” the data.

ggplot(mtcars, aes(y = am, x = mpg)) +
    geom_jitter(height = 0) +
    geom_smooth()

Finally, we fit a “logistic regression” curve.

ggplot(mtcars, aes(y = am, x = mpg)) +
    geom_jitter(height = 0) +
    geom_smooth(method = "glm", method.args = list(family = binomial))

Model assumptions

Logistic regression doesn’t have too many assumptions (unlike standard linear regression). Obviously, the response variable must be binary and the observations in the data must be independent. Other than that, there’s not much else! One exception is a “non-separability” rule that states that there must be some “overlap” in the outcomes relative to the explanatory variable. As an example, here is some fake data:

fake_data <- data.frame(y = c(rep(0, 10), rep(1, 10)), x = 1:20)
ggplot(fake_data, aes(y = y, x = x)) + geom_point()

We can see that there is a boundary between 10 and 11 for which everything to the left is classified as “0” and everything to the right is “1”. This will break logistic regression:

glm(y ~ x, data = fake_data, family = binomial)
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred

Call:  glm(formula = y ~ x, family = binomial, data = fake_data)

Coefficients:
(Intercept)            x  
    -433.39        41.28  

Degrees of Freedom: 19 Total (i.e. Null);  18 Residual
Null Deviance:      27.73 
Residual Deviance: 4.357e-09    AIC: 4

The glm function boldly tried to calculate the regression anyway (with a warning), but the answer makes no sense.

Model

For this section, we will go ahead and change am to a factor variable.

am <- factor(mtcars$am, levels = c(0, 1), labels = c("Automatic", "Manual"))
mtcars2 <- data.frame(am, mpg = mtcars$mpg)

Running the model…

am_mpg <- glm(am ~ mpg, data = mtcars2, family = binomial)
am_mpg_tidy <- tidy(am_mpg)
am_mpg_tidy

Mathematically, the model is

\[\log\left(\frac{\hat{p}}{1 - \hat{p}}\right) = -6.6 + 0.3 mpg,\] or equivalently,

\[\hat{p} = \frac{1}{1 + e^{-(-6.6 + 0.3 mpg)}}\]

Another mathematically equivalent way to express it:

\[\hat{p} = \frac{e^{-6.6 + 0.3 mpg}}{1 + e^{-6.6 + 0.3 mpg}}\]

Interpret coefficients

The intercept -6.6035267 is the log odds of having manual transmission for a car with 0 mpg. This clearly makes no sense.

The coefficient of mpg, 0.3070282, is the predicted change in the log odds corresponding to an increase of one mpg. (But how easy it is to understand this?)

Exponentiating the coefficients

An argument to the tidy command will give the coefficients in the much more convenient exponentiated form:

am_mpg_tidy_exp <- tidy(am_mpg, exponentiate = TRUE)
am_mpg_tidy_exp

Mathematically, we have exponentiated both sides, giving us the odds instead of the log odds.

\[\frac{\hat{p}}{1 - \hat{p}} = e^{-6.6 + 0.3 mpg} = e^{-6.6} e^{0.3 mpg}.\]

Note that \(e^{-6.6}\) is 0.0013556 and \(e^{0.3}\) is 1.3593793.

If we substitute these exponentiated values into the above expression, we get

\[\frac{\hat{p}}{1 - \hat{p}} = 0.001 \left(1.36^{mpg}\right).\]

The exponentiated intercept is the odds of a car having manual transmission if mpg is zero. Again, this makes no sense. (However, it does make sense that this is what the model would predict since the logistic function will be asymptotically close to zero that far to the left of the data.)

The exponentiated coefficient tells us what happens to the odds if we increase the mpg by one unit. Here’s why. Suppose \(\hat{p}'\) is the probability corresponding to adding one to mpg:

\[\frac{\hat{p}'}{1 - \hat{p}'} = 0.001 \left(1.36^{(mpg + 1)}\right).\]

The right hand side is equal to

\[0.001 \left(1.36^{mpg}(1.36)\right).\]

and this is just 1.36 times the odds for the original mpg value. In other words, adding one to mpg simply multiplies the odds by 1.36 (the value of the exponentiated coefficient).

Mean centering the predictor

Mean centering can help by giving an interpretable intercept.

mtcars2 <- mtcars2 %>%
    mutate(mpg_cent = mpg - mean(mpg))
am_mpg_mc <- glm(am ~ mpg_cent, data = mtcars2, family = binomial)
am_mpg_mc_tidy <- tidy(am_mpg_mc)
am_mpg_mc_tidy

The coefficient for mpg has not changed, but the intercept now has a reasonable interpretation. When the mean-centered mpg is zero—in other words, when a car gets 20.090625 mpg—the predicted log odds of having manual transmission are -0.4351385, or, alternatively, the odds are 0.647175 (\(e^{-0.4}\)).

Interpreting on the probability scale

The coefficient of an explanatory variable has a convenient (if a bit difficult to interpret) interpretation on the log-odds scale or the odds scale. (In the former case, one unit of increase in the explanatory variable predicts an additive effect on the log-odds; in the latter case, one unit of increase in the explanatory variable predicts a multiplicative effect on the odds.) However, no such interpretation exists for the probability of success.

For example, let’s see what happens to the probability of having manual transmission when we go from 10 mpg to 11 mpg:

This will give us the log odds at 10 mpg:

log_odds_10 <- predict(am_mpg, data.frame(mpg = 10))
log_odds_10
        1 
-3.533245 

We convert it to a probability by applying the logistic function. There are two forms that are mathematically equivalent:

\[\frac{1}{1 + e^{-x}} = \frac{e^{x}}{1 + e^{x}}.\] We’ll use the first one since we only have to put in the value of \(x\) in one spot.

1/(1 + exp(-log_odds_10))
         1 
0.02838097 

This appears in R as plogis.

prob_10 <- plogis(log_odds_10)
prob_10
         1 
0.02838097 

An easier way to do this is to use an optional argument to the predict function:

prob_10 <- predict(am_mpg, data.frame(mpg = 10), type = "response")
prob_10
         1 
0.02838097 

We do the same for 11 mpg:

prob_11 <- predict(am_mpg, data.frame(mpg = 11), type = "response")
prob_11
         1 
0.03819098 

The change in probability is

prob_11 - prob_10
          1 
0.009810004 

Okay, now look at a change from 20 mpg to 21 mpg:

prob_20 <- predict(am_mpg, data.frame(mpg = 20), type = "response")
prob_21 <- predict(am_mpg, data.frame(mpg = 21), type = "response")
prob_21 - prob_20
         1 
0.07481195 

This is a much larger change. That’s because the logistic function is much steeper toward the middle of the graph than at the edges.

If you do happen to need a prediction near the middle of the graph, though, it is nice that the graph is approximately linear through a fairly wide range of values (here, maybe from 15 to 25 or so). The \(\beta/4\) rule tells us that the slope of the straight line approximation near the middle of the graph is \(\beta/4\) where \(\beta\) is the coefficient of the explanatory variable in the model.1 Let’s calculate that here:

am_mpg_tidy$estimate[2]/4
[1] 0.07675705

That’s pretty close to the answer we got when predicting the difference in probability between 20 and 21 mpg.

Model accuracy

Since the goal of logistic regression is to make predictions of a binary response variable, we can evaluate the model by comparing the predictions made by the model to the actual values of the response variable. For example, a sensible thing to do would be to say that if the model predicts a probability of greater than 50% (log odds greater than 0), then the car is more likely to have manual transmission, whereas if the probability is less than 50% (log odds less than 0), then the car is more likely to have automatic transmission.

Another way to look at it is to find the mpg value where the model crosses the 50% probability mark. Since 50% probability is the same as log odds of zero, all we need to do is set log odds equal to zero in the model:

\[0 = -6.6 + 0.3 mpg\] Solving this precisely, we get

-am_mpg_tidy$estimate[1]/am_mpg_tidy$estimate[2]
[1] 21.50788

You can check the graph above to see that the 50% mark does, indeed, correspond to about 21 or 22 mpg. Therefore, any car that gets less than 21.5 mpg will be predicted to have automatic transmission, while any car that gets greater than 21.5 mpg will be predicted to have manual transmission.

We can easily make a variable that is “Automatic” when the predicted log odds are negative and “Manual” when they are positive.

am_pred <- factor(ifelse(predict(am_mpg) < 0, "Automatic", "Manual"))
am_pred
        1         2         3         4         5         6         7         8         9        10        11 
Automatic Automatic    Manual Automatic Automatic Automatic Automatic    Manual    Manual Automatic Automatic 
       12        13        14        15        16        17        18        19        20        21        22 
Automatic Automatic Automatic Automatic Automatic Automatic    Manual    Manual    Manual Automatic Automatic 
       23        24        25        26        27        28        29        30        31        32 
Automatic Automatic Automatic    Manual    Manual    Manual Automatic Automatic Automatic Automatic 
Levels: Automatic Manual

Compare this to the actual values of am from the original data:

mtcars2$am
 [1] Manual    Manual    Manual    Automatic Automatic Automatic Automatic Automatic Automatic Automatic
[11] Automatic Automatic Automatic Automatic Automatic Automatic Automatic Manual    Manual    Manual   
[21] Automatic Automatic Automatic Automatic Automatic Manual    Manual    Manual    Manual    Manual   
[31] Manual    Manual   
Levels: Automatic Manual

The caret package has a convenient function called confusionMatrix that creates a table of predicted values versus actual (reference) values.

confusionMatrix(am_pred, mtcars2$am)
Confusion Matrix and Statistics

           Reference
Prediction  Automatic Manual
  Automatic        17      6
  Manual            2      7
                                         
               Accuracy : 0.75           
                 95% CI : (0.566, 0.8854)
    No Information Rate : 0.5938         
    P-Value [Acc > NIR] : 0.04978        
                                         
                  Kappa : 0.4553         
                                         
 Mcnemar's Test P-Value : 0.28884        
                                         
            Sensitivity : 0.8947         
            Specificity : 0.5385         
         Pos Pred Value : 0.7391         
         Neg Pred Value : 0.7778         
             Prevalence : 0.5938         
         Detection Rate : 0.5312         
   Detection Prevalence : 0.7188         
      Balanced Accuracy : 0.7166         
                                         
       'Positive' Class : Automatic      
                                         

Therefore, the logistic regression model has a 75% accuracy rate in classifying cars as automatic or manual based solely on mpg. Since most of the cars are automatics (19/32, or about 60%), a completely naive thing to do would be just to say all the cars have automatic transmission, with accuracy 60%. This is called the “No Information Rate” or NIR. But using mpg as a predictor improves the accuracy to 75%, and that’s a statistically significant improvement!

Multiple logistic regression

We can use more than one predictor variable. Let’s try predicting am from mpg_cent and vs. (Note that vs is a dichotomous categorical variable indicating the engine type, “V” vs “Straight”.) First, we’ll include vs in our mtcars data frame.

mtcars2 <- mtcars2 %>%
    mutate(vs = mtcars$vs)
str(mtcars2)
'data.frame':   32 obs. of  4 variables:
 $ am      : Factor w/ 2 levels "Automatic","Manual": 2 2 2 1 1 1 1 1 1 1 ...
 $ mpg     : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
 $ mpg_cent: num  0.909 0.909 2.709 1.309 -1.391 ...
 $ vs      : num  0 0 1 1 0 1 0 1 1 1 ...
am_mpg_vs <- glm(am ~ mpg_cent + vs, data = mtcars2, family = binomial)
am_mpg_vs_tidy <- tidy(am_mpg_vs)
am_mpg_vs_tidy
am_mpg_vs_tidy_exp <- tidy(am_mpg_vs, exponentiate = TRUE)
am_mpg_vs_tidy_exp

The interpretations of the intercept and coefficients do not change except that now we must take care—as in any multiple regression setting—to mention that they predict changes assuming all other variables are held constant.

Note that the coefficient of vs is not statistically significant.

Here is the confusion matrix for this model:

am_pred2 <- factor(ifelse(predict(am_mpg_vs) < 0, "Automatic", "Manual"))
confusionMatrix(am_pred2, mtcars2$am)
Confusion Matrix and Statistics

           Reference
Prediction  Automatic Manual
  Automatic        16      4
  Manual            3      9
                                          
               Accuracy : 0.7812          
                 95% CI : (0.6003, 0.9072)
    No Information Rate : 0.5938          
    P-Value [Acc > NIR] : 0.02102         
                                          
                  Kappa : 0.541           
                                          
 Mcnemar's Test P-Value : 1.00000         
                                          
            Sensitivity : 0.8421          
            Specificity : 0.6923          
         Pos Pred Value : 0.8000          
         Neg Pred Value : 0.7500          
             Prevalence : 0.5938          
         Detection Rate : 0.5000          
   Detection Prevalence : 0.6250          
      Balanced Accuracy : 0.7672          
                                          
       'Positive' Class : Automatic       
                                          

The accuracy has improved a little to about 78%, but that’s not much higher than 75%. Due to the small sample size, this actually means that only one additional car was predicted correctly. (In fact, one fewer automatic was classified correctly by this model, but because two more manuals were correctly classified, the net effect is a slightly better accuracy score.)

As with other forms of regression, ANOVA can tell us if the addition of predictors is significant.

# This is Type II ANOVA
Anova(am_mpg_vs)
Analysis of Deviance Table (Type II tests)

Response: am
         LR Chisq Df Pr(>Chisq)    
mpg_cent  17.3788  1  3.062e-05 ***
vs         4.7314  1    0.02962 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Although the coefficient of vs was not significant, the ANOVA table tells us that the addition of vs to the model adds some predictive power.


  1. For those with some calculus background, \(\beta/4\) is the derivative of the logistic function evaluated at the point where the function is equal to 0.5.

LS0tCnRpdGxlOiAiTG9naXN0aWMgcmVncmVzc2lvbiAyIgpvdXRwdXQ6CiAgICBodG1sX25vdGVib29rOgogICAgICAgIHRvYzogeWVzCiAgICAgICAgdG9jX2Zsb2F0OiB5ZXMKLS0tCgoKIyMgSW50cm9kdWN0aW9uCgpXZSB3aWxsIGV4cGxvcmUgbG9naXN0aWMgcmVncmVzc2lvbiBieSBleGFtaW5pbmcgdGhlIGNsYXNzaWMgYG10Y2Fyc2AgZGF0YSBzZXQuCgoKIyMgUHJlbGltaW5hcmllcwoKYGBge3IsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeShjYXIpCmxpYnJhcnkoYnJvb20pCmxpYnJhcnkobW9zYWljKQpsaWJyYXJ5KGNhcmV0KQpgYGAKCgojIyBFeHBsb3JlIGRhdGEKCmBgYHtyfQptdGNhcnMKYGBgCgoKYGBge3J9CnN0cihtdGNhcnMpCmBgYAoKQ29uc2lkZXIgdGhlIGBhbWAgdmFyaWFibGUgdGhhdCBpbmRpY2F0ZXMgYXV0b21hdGljIG9yIG1hbnVhbCB0cmFuc21pc3Npb24uCgpgYGB7cn0KdGFsbHkofiBhbSwgZGF0YSA9IG10Y2FycykKYGBgCgpOb3RlIHRoYXQgdGhlIHZhcmlhYmxlIGlzIG5vdCBjb2RlZCBhcyBhIGZhY3RvciB2YXJpYWJsZS4gV2Ugd291bGQgbm9ybWFsbHkgcmVjdGlmeSB0aGlzIHNpdHVhdGlvbiBieSB1c2luZyB0aGUgYGZhY3RvcmAgY29tbWFuZCwgYnV0IHdlIHdpbGwgZGVsYXkgdGhhdCBzdGVwIGZvciB0aGUgdGltZSBiZWluZy4gVGhlIHJlYXNvbiBpcyB0aGF0IHdlJ3JlIGdvaW5nIHRvIGNyZWF0ZSBzY2F0dGVycGxvdHMgYmVsb3csIGFuZCB0aGF0IHJlcXVpcmVzIHZhcmlhYmxlcyB0byBiZSByZWNvcmRlZCBudW1lcmljYWxseS4KCkxldCdzIHRyeSB0byBwcmVkaWN0IGBhbWAgKHRoZSB0cmFuc21pc3Npb24gdHlwZS0tLWF1dG9tYXRpYyBvciBtYW51YWwpIGZyb20gYG1wZ2AuIChSZW1lbWJlciwgdGhpcyB3YXMgMTk3NCEpIFdoYXQgYWJvdXQgbGluZWFyIHJlZ3Jlc3Npb24/CgpgYGB7cn0KZ2dwbG90KG10Y2FycywgYWVzKHkgPSBhbSwgeCA9IG1wZykpICsKICAgIGdlb21faml0dGVyKGhlaWdodCA9IDApICsKICAgIGdlb21fc21vb3RoKG1ldGhvZCA9ICAibG0iKQpgYGAKClRoaXMgY2xlYXJseSBkb2Vzbid0IHdvcmsuCgpUaGUgcGxvdCBiZWxvdyB1c2VzIGEgbG9lc3MgY3VydmUgdG8gdHJ5IHRvICJmaXQiIHRoZSBkYXRhLgoKYGBge3J9CmdncGxvdChtdGNhcnMsIGFlcyh5ID0gYW0sIHggPSBtcGcpKSArCiAgICBnZW9tX2ppdHRlcihoZWlnaHQgPSAwKSArCiAgICBnZW9tX3Ntb290aCgpCmBgYAoKRmluYWxseSwgd2UgZml0IGEgImxvZ2lzdGljIHJlZ3Jlc3Npb24iIGN1cnZlLgoKYGBge3J9CmdncGxvdChtdGNhcnMsIGFlcyh5ID0gYW0sIHggPSBtcGcpKSArCiAgICBnZW9tX2ppdHRlcihoZWlnaHQgPSAwKSArCiAgICBnZW9tX3Ntb290aChtZXRob2QgPSAiZ2xtIiwgbWV0aG9kLmFyZ3MgPSBsaXN0KGZhbWlseSA9IGJpbm9taWFsKSkKYGBgCgoKCiMjIE1vZGVsIGFzc3VtcHRpb25zCgpMb2dpc3RpYyByZWdyZXNzaW9uIGRvZXNuJ3QgaGF2ZSB0b28gbWFueSBhc3N1bXB0aW9ucyAodW5saWtlIHN0YW5kYXJkIGxpbmVhciByZWdyZXNzaW9uKS4gT2J2aW91c2x5LCB0aGUgcmVzcG9uc2UgdmFyaWFibGUgbXVzdCBiZSBiaW5hcnkgYW5kIHRoZSBvYnNlcnZhdGlvbnMgaW4gdGhlIGRhdGEgbXVzdCBiZSBpbmRlcGVuZGVudC4gT3RoZXIgdGhhbiB0aGF0LCB0aGVyZSdzIG5vdCBtdWNoIGVsc2UhIE9uZSBleGNlcHRpb24gaXMgYSAibm9uLXNlcGFyYWJpbGl0eSIgcnVsZSB0aGF0IHN0YXRlcyB0aGF0IHRoZXJlIG11c3QgYmUgc29tZSAib3ZlcmxhcCIgaW4gdGhlIG91dGNvbWVzIHJlbGF0aXZlIHRvIHRoZSBleHBsYW5hdG9yeSB2YXJpYWJsZS4gQXMgYW4gZXhhbXBsZSwgaGVyZSBpcyBzb21lIGZha2UgZGF0YToKCmBgYHtyfQpmYWtlX2RhdGEgPC0gZGF0YS5mcmFtZSh5ID0gYyhyZXAoMCwgMTApLCByZXAoMSwgMTApKSwgeCA9IDE6MjApCmdncGxvdChmYWtlX2RhdGEsIGFlcyh5ID0geSwgeCA9IHgpKSArIGdlb21fcG9pbnQoKQpgYGAKCldlIGNhbiBzZWUgdGhhdCB0aGVyZSBpcyBhIGJvdW5kYXJ5IGJldHdlZW4gMTAgYW5kIDExIGZvciB3aGljaCBldmVyeXRoaW5nIHRvIHRoZSBsZWZ0IGlzIGNsYXNzaWZpZWQgYXMgIjAiIGFuZCBldmVyeXRoaW5nIHRvIHRoZSByaWdodCBpcyAiMSIuIFRoaXMgd2lsbCBicmVhayBsb2dpc3RpYyByZWdyZXNzaW9uOgoKYGBge3IsIGVycm9yID0gVFJVRX0KZ2xtKHkgfiB4LCBkYXRhID0gZmFrZV9kYXRhLCBmYW1pbHkgPSBiaW5vbWlhbCkKYGBgCgpUaGUgYGdsbWAgZnVuY3Rpb24gYm9sZGx5IHRyaWVkIHRvIGNhbGN1bGF0ZSB0aGUgcmVncmVzc2lvbiBhbnl3YXkgKHdpdGggYSB3YXJuaW5nKSwgYnV0IHRoZSBhbnN3ZXIgbWFrZXMgbm8gc2Vuc2UuCgoKIyMgTW9kZWwKCkZvciB0aGlzIHNlY3Rpb24sIHdlIHdpbGwgZ28gYWhlYWQgYW5kIGNoYW5nZSBgYW1gIHRvIGEgZmFjdG9yIHZhcmlhYmxlLgoKYGBge3J9CmFtIDwtIGZhY3RvcihtdGNhcnMkYW0sIGxldmVscyA9IGMoMCwgMSksIGxhYmVscyA9IGMoIkF1dG9tYXRpYyIsICJNYW51YWwiKSkKbXRjYXJzMiA8LSBkYXRhLmZyYW1lKGFtLCBtcGcgPSBtdGNhcnMkbXBnKQpgYGAKClJ1bm5pbmcgdGhlIG1vZGVsLi4uCgpgYGB7cn0KYW1fbXBnIDwtIGdsbShhbSB+IG1wZywgZGF0YSA9IG10Y2FyczIsIGZhbWlseSA9IGJpbm9taWFsKQphbV9tcGdfdGlkeSA8LSB0aWR5KGFtX21wZykKYW1fbXBnX3RpZHkKYGBgCgpNYXRoZW1hdGljYWxseSwgdGhlIG1vZGVsIGlzCgokJFxsb2dcbGVmdChcZnJhY3tcaGF0e3B9fXsxIC0gXGhhdHtwfX1ccmlnaHQpID0gLTYuNiArIDAuMyBtcGcsJCQKb3IgZXF1aXZhbGVudGx5LAoKJCRcaGF0e3B9ID0gXGZyYWN7MX17MSArIGVeey0oLTYuNiArIDAuMyBtcGcpfX0kJAoKQW5vdGhlciBtYXRoZW1hdGljYWxseSBlcXVpdmFsZW50IHdheSB0byBleHByZXNzIGl0OgoKJCRcaGF0e3B9ID0gXGZyYWN7ZV57LTYuNiArIDAuMyBtcGd9fXsxICsgZV57LTYuNiArIDAuMyBtcGd9fSQkCgoKIyMgSW50ZXJwcmV0IGNvZWZmaWNpZW50cwoKVGhlIGludGVyY2VwdCBgciBhbV9tcGdfdGlkeSRlc3RpbWF0ZVsxXWAgaXMgdGhlIGxvZyBvZGRzIG9mIGhhdmluZyBtYW51YWwgdHJhbnNtaXNzaW9uIGZvciBhIGNhciB3aXRoIDAgbXBnLiBUaGlzIGNsZWFybHkgbWFrZXMgbm8gc2Vuc2UuCgpUaGUgY29lZmZpY2llbnQgb2YgYG1wZ2AsIGByIGFtX21wZ190aWR5JGVzdGltYXRlWzJdYCwgaXMgdGhlIHByZWRpY3RlZCBjaGFuZ2UgaW4gdGhlIGxvZyBvZGRzIGNvcnJlc3BvbmRpbmcgdG8gYW4gaW5jcmVhc2Ugb2Ygb25lIG1wZy4gKEJ1dCBob3cgZWFzeSBpdCBpcyB0byB1bmRlcnN0YW5kIHRoaXM/KQoKCiMjIEV4cG9uZW50aWF0aW5nIHRoZSBjb2VmZmljaWVudHMKCkFuIGFyZ3VtZW50IHRvIHRoZSBgdGlkeWAgY29tbWFuZCB3aWxsIGdpdmUgdGhlIGNvZWZmaWNpZW50cyBpbiB0aGUgbXVjaCBtb3JlIGNvbnZlbmllbnQgZXhwb25lbnRpYXRlZCBmb3JtOgoKYGBge3J9CmFtX21wZ190aWR5X2V4cCA8LSB0aWR5KGFtX21wZywgZXhwb25lbnRpYXRlID0gVFJVRSkKYW1fbXBnX3RpZHlfZXhwCmBgYAoKTWF0aGVtYXRpY2FsbHksIHdlIGhhdmUgZXhwb25lbnRpYXRlZCBib3RoIHNpZGVzLCBnaXZpbmcgdXMgdGhlIG9kZHMgaW5zdGVhZCBvZiB0aGUgbG9nIG9kZHMuCgokJFxmcmFje1xoYXR7cH19ezEgLSBcaGF0e3B9fSA9IGVeey02LjYgKyAwLjMgbXBnfSA9IGVeey02LjZ9IGVeezAuMyBtcGd9LiQkCgpOb3RlIHRoYXQgJGVeey02LjZ9JCBpcyBgciBleHAoYW1fbXBnX3RpZHkkZXN0aW1hdGVbMV0pYCBhbmQgJGVeezAuM30kIGlzIGByIGV4cChhbV9tcGdfdGlkeSRlc3RpbWF0ZVsyXSlgLgoKSWYgd2Ugc3Vic3RpdHV0ZSB0aGVzZSBleHBvbmVudGlhdGVkIHZhbHVlcyBpbnRvIHRoZSBhYm92ZSBleHByZXNzaW9uLCB3ZSBnZXQKCiQkXGZyYWN7XGhhdHtwfX17MSAtIFxoYXR7cH19ID0gMC4wMDEgXGxlZnQoMS4zNl57bXBnfVxyaWdodCkuJCQKClRoZSBleHBvbmVudGlhdGVkIGludGVyY2VwdCBpcyB0aGUgb2RkcyBvZiBhIGNhciBoYXZpbmcgbWFudWFsIHRyYW5zbWlzc2lvbiBpZiBgbXBnYCBpcyB6ZXJvLiBBZ2FpbiwgdGhpcyBtYWtlcyBubyBzZW5zZS4gKEhvd2V2ZXIsIGl0IGRvZXMgbWFrZSBzZW5zZSB0aGF0IHRoaXMgaXMgd2hhdCB0aGUgbW9kZWwgd291bGQgcHJlZGljdCBzaW5jZSB0aGUgbG9naXN0aWMgZnVuY3Rpb24gd2lsbCBiZSBhc3ltcHRvdGljYWxseSBjbG9zZSB0byB6ZXJvIHRoYXQgZmFyIHRvIHRoZSBsZWZ0IG9mIHRoZSBkYXRhLikKClRoZSBleHBvbmVudGlhdGVkIGNvZWZmaWNpZW50IHRlbGxzIHVzIHdoYXQgaGFwcGVucyB0byB0aGUgb2RkcyBpZiB3ZSBpbmNyZWFzZSB0aGUgYG1wZ2AgYnkgb25lIHVuaXQuIEhlcmUncyB3aHkuIFN1cHBvc2UgJFxoYXR7cH0nJCBpcyB0aGUgcHJvYmFiaWxpdHkgY29ycmVzcG9uZGluZyB0byBhZGRpbmcgb25lIHRvIG1wZzoKCiQkXGZyYWN7XGhhdHtwfSd9ezEgLSBcaGF0e3B9J30gPSAwLjAwMSBcbGVmdCgxLjM2XnsobXBnICsgMSl9XHJpZ2h0KS4kJAoKVGhlIHJpZ2h0IGhhbmQgc2lkZSBpcyBlcXVhbCB0bwoKJCQwLjAwMSBcbGVmdCgxLjM2XnttcGd9KDEuMzYpXHJpZ2h0KS4kJAoKYW5kIHRoaXMgaXMganVzdCAxLjM2IHRpbWVzIHRoZSBvZGRzIGZvciB0aGUgb3JpZ2luYWwgbXBnIHZhbHVlLiBJbiBvdGhlciB3b3JkcywgYWRkaW5nIG9uZSB0byBtcGcgc2ltcGx5IG11bHRpcGxpZXMgdGhlIG9kZHMgYnkgMS4zNiAodGhlIHZhbHVlIG9mIHRoZSBleHBvbmVudGlhdGVkIGNvZWZmaWNpZW50KS4KCgojIyBNZWFuIGNlbnRlcmluZyB0aGUgcHJlZGljdG9yCgpNZWFuIGNlbnRlcmluZyBjYW4gaGVscCBieSBnaXZpbmcgYW4gaW50ZXJwcmV0YWJsZSBpbnRlcmNlcHQuCgpgYGB7cn0KbXRjYXJzMiA8LSBtdGNhcnMyICU+JQogICAgbXV0YXRlKG1wZ19jZW50ID0gbXBnIC0gbWVhbihtcGcpKQphbV9tcGdfbWMgPC0gZ2xtKGFtIH4gbXBnX2NlbnQsIGRhdGEgPSBtdGNhcnMyLCBmYW1pbHkgPSBiaW5vbWlhbCkKYW1fbXBnX21jX3RpZHkgPC0gdGlkeShhbV9tcGdfbWMpCmFtX21wZ19tY190aWR5CmBgYAoKVGhlIGNvZWZmaWNpZW50IGZvciBgbXBnYCBoYXMgbm90IGNoYW5nZWQsIGJ1dCB0aGUgaW50ZXJjZXB0IG5vdyBoYXMgYSByZWFzb25hYmxlIGludGVycHJldGF0aW9uLiBXaGVuIHRoZSBtZWFuLWNlbnRlcmVkIG1wZyBpcyB6ZXJvLS0taW4gb3RoZXIgd29yZHMsIHdoZW4gYSBjYXIgZ2V0cyBgciBtZWFuKG10Y2FycyRtcGcpYCBgbXBnYC0tLXRoZSBwcmVkaWN0ZWQgbG9nIG9kZHMgb2YgaGF2aW5nIG1hbnVhbCB0cmFuc21pc3Npb24gYXJlIGByIGFtX21wZ19tY190aWR5JGVzdGltYXRlWzFdYCwgb3IsIGFsdGVybmF0aXZlbHksIHRoZSBvZGRzIGFyZSBgciBleHAoYW1fbXBnX21jX3RpZHkkZXN0aW1hdGVbMV0pYCAoJGVeey0wLjR9JCkuCgoKIyMgSW50ZXJwcmV0aW5nIG9uIHRoZSBwcm9iYWJpbGl0eSBzY2FsZQoKVGhlIGNvZWZmaWNpZW50IG9mIGFuIGV4cGxhbmF0b3J5IHZhcmlhYmxlIGhhcyBhIGNvbnZlbmllbnQgKGlmIGEgYml0IGRpZmZpY3VsdCB0byBpbnRlcnByZXQpIGludGVycHJldGF0aW9uIG9uIHRoZSBsb2ctb2RkcyBzY2FsZSBvciB0aGUgb2RkcyBzY2FsZS4gKEluIHRoZSBmb3JtZXIgY2FzZSwgb25lIHVuaXQgb2YgaW5jcmVhc2UgaW4gdGhlIGV4cGxhbmF0b3J5IHZhcmlhYmxlIHByZWRpY3RzIGFuIGFkZGl0aXZlIGVmZmVjdCBvbiB0aGUgbG9nLW9kZHM7IGluIHRoZSBsYXR0ZXIgY2FzZSwgb25lIHVuaXQgb2YgaW5jcmVhc2UgaW4gdGhlIGV4cGxhbmF0b3J5IHZhcmlhYmxlIHByZWRpY3RzIGEgbXVsdGlwbGljYXRpdmUgZWZmZWN0IG9uIHRoZSBvZGRzLikgSG93ZXZlciwgbm8gc3VjaCBpbnRlcnByZXRhdGlvbiBleGlzdHMgZm9yIHRoZSAqcHJvYmFiaWxpdHkqIG9mIHN1Y2Nlc3MuCgpGb3IgZXhhbXBsZSwgbGV0J3Mgc2VlIHdoYXQgaGFwcGVucyB0byB0aGUgcHJvYmFiaWxpdHkgb2YgaGF2aW5nIG1hbnVhbCB0cmFuc21pc3Npb24gd2hlbiB3ZSBnbyBmcm9tIDEwIG1wZyB0byAxMSBtcGc6CgpUaGlzIHdpbGwgZ2l2ZSB1cyB0aGUgbG9nIG9kZHMgYXQgMTAgbXBnOgoKYGBge3J9CmxvZ19vZGRzXzEwIDwtIHByZWRpY3QoYW1fbXBnLCBkYXRhLmZyYW1lKG1wZyA9IDEwKSkKbG9nX29kZHNfMTAKYGBgCgpXZSBjb252ZXJ0IGl0IHRvIGEgcHJvYmFiaWxpdHkgYnkgYXBwbHlpbmcgdGhlIGxvZ2lzdGljIGZ1bmN0aW9uLiBUaGVyZSBhcmUgdHdvIGZvcm1zIHRoYXQgYXJlIG1hdGhlbWF0aWNhbGx5IGVxdWl2YWxlbnQ6CgokJFxmcmFjezF9ezEgKyBlXnsteH19ID0gXGZyYWN7ZV57eH19ezEgKyBlXnt4fX0uJCQKV2UnbGwgdXNlIHRoZSBmaXJzdCBvbmUgc2luY2Ugd2Ugb25seSBoYXZlIHRvIHB1dCBpbiB0aGUgdmFsdWUgb2YgJHgkIGluIG9uZSBzcG90LgoKYGBge3J9CjEvKDEgKyBleHAoLWxvZ19vZGRzXzEwKSkKYGBgCgoKVGhpcyBhcHBlYXJzIGluIFIgYXMgYHBsb2dpc2AuCgpgYGB7cn0KcHJvYl8xMCA8LSBwbG9naXMobG9nX29kZHNfMTApCnByb2JfMTAKYGBgCgpBbiBlYXNpZXIgd2F5IHRvIGRvIHRoaXMgaXMgdG8gdXNlIGFuIG9wdGlvbmFsIGFyZ3VtZW50IHRvIHRoZSBgcHJlZGljdGAgZnVuY3Rpb246CgpgYGB7cn0KcHJvYl8xMCA8LSBwcmVkaWN0KGFtX21wZywgZGF0YS5mcmFtZShtcGcgPSAxMCksIHR5cGUgPSAicmVzcG9uc2UiKQpwcm9iXzEwCmBgYAoKV2UgZG8gdGhlIHNhbWUgZm9yIDExIG1wZzoKCmBgYHtyfQpwcm9iXzExIDwtIHByZWRpY3QoYW1fbXBnLCBkYXRhLmZyYW1lKG1wZyA9IDExKSwgdHlwZSA9ICJyZXNwb25zZSIpCnByb2JfMTEKYGBgCgpUaGUgY2hhbmdlIGluIHByb2JhYmlsaXR5IGlzCgpgYGB7cn0KcHJvYl8xMSAtIHByb2JfMTAKYGBgCgpPa2F5LCBub3cgbG9vayBhdCBhIGNoYW5nZSBmcm9tIDIwIG1wZyB0byAyMSBtcGc6CgpgYGB7cn0KcHJvYl8yMCA8LSBwcmVkaWN0KGFtX21wZywgZGF0YS5mcmFtZShtcGcgPSAyMCksIHR5cGUgPSAicmVzcG9uc2UiKQpwcm9iXzIxIDwtIHByZWRpY3QoYW1fbXBnLCBkYXRhLmZyYW1lKG1wZyA9IDIxKSwgdHlwZSA9ICJyZXNwb25zZSIpCnByb2JfMjEgLSBwcm9iXzIwCmBgYAoKVGhpcyBpcyBhIG11Y2ggbGFyZ2VyIGNoYW5nZS4gVGhhdCdzIGJlY2F1c2UgdGhlIGxvZ2lzdGljIGZ1bmN0aW9uIGlzIG11Y2ggc3RlZXBlciB0b3dhcmQgdGhlIG1pZGRsZSBvZiB0aGUgZ3JhcGggdGhhbiBhdCB0aGUgZWRnZXMuCgpJZiB5b3UgZG8gaGFwcGVuIHRvIG5lZWQgYSBwcmVkaWN0aW9uIG5lYXIgdGhlIG1pZGRsZSBvZiB0aGUgZ3JhcGgsIHRob3VnaCwgaXQgaXMgbmljZSB0aGF0IHRoZSBncmFwaCBpcyBhcHByb3hpbWF0ZWx5IGxpbmVhciB0aHJvdWdoIGEgZmFpcmx5IHdpZGUgcmFuZ2Ugb2YgdmFsdWVzIChoZXJlLCBtYXliZSBmcm9tIDE1IHRvIDI1IG9yIHNvKS4gVGhlICRcYmV0YS80JCBydWxlIHRlbGxzIHVzIHRoYXQgdGhlIHNsb3BlIG9mIHRoZSBzdHJhaWdodCBsaW5lIGFwcHJveGltYXRpb24gbmVhciB0aGUgbWlkZGxlIG9mIHRoZSBncmFwaCBpcyAkXGJldGEvNCQgd2hlcmUgJFxiZXRhJCBpcyB0aGUgY29lZmZpY2llbnQgb2YgdGhlIGV4cGxhbmF0b3J5IHZhcmlhYmxlIGluIHRoZSBtb2RlbC5eW0ZvciB0aG9zZSB3aXRoIHNvbWUgY2FsY3VsdXMgYmFja2dyb3VuZCwgJFxiZXRhLzQkIGlzIHRoZSBkZXJpdmF0aXZlIG9mIHRoZSBsb2dpc3RpYyBmdW5jdGlvbiBldmFsdWF0ZWQgYXQgdGhlIHBvaW50IHdoZXJlIHRoZSBmdW5jdGlvbiBpcyBlcXVhbCB0byAwLjUuXSBMZXQncyBjYWxjdWxhdGUgdGhhdCBoZXJlOgoKYGBge3J9CmFtX21wZ190aWR5JGVzdGltYXRlWzJdLzQKYGBgCgpUaGF0J3MgcHJldHR5IGNsb3NlIHRvIHRoZSBhbnN3ZXIgd2UgZ290IHdoZW4gcHJlZGljdGluZyB0aGUgZGlmZmVyZW5jZSBpbiBwcm9iYWJpbGl0eSBiZXR3ZWVuIDIwIGFuZCAyMSBtcGcuCgoKIyMgTW9kZWwgYWNjdXJhY3kKClNpbmNlIHRoZSBnb2FsIG9mIGxvZ2lzdGljIHJlZ3Jlc3Npb24gaXMgdG8gbWFrZSBwcmVkaWN0aW9ucyBvZiBhIGJpbmFyeSByZXNwb25zZSB2YXJpYWJsZSwgd2UgY2FuIGV2YWx1YXRlIHRoZSBtb2RlbCBieSBjb21wYXJpbmcgdGhlIHByZWRpY3Rpb25zIG1hZGUgYnkgdGhlIG1vZGVsIHRvIHRoZSBhY3R1YWwgdmFsdWVzIG9mIHRoZSByZXNwb25zZSB2YXJpYWJsZS4gRm9yIGV4YW1wbGUsIGEgc2Vuc2libGUgdGhpbmcgdG8gZG8gd291bGQgYmUgdG8gc2F5IHRoYXQgaWYgdGhlIG1vZGVsIHByZWRpY3RzIGEgcHJvYmFiaWxpdHkgb2YgZ3JlYXRlciB0aGFuIDUwJSAobG9nIG9kZHMgZ3JlYXRlciB0aGFuIDApLCB0aGVuIHRoZSBjYXIgaXMgbW9yZSBsaWtlbHkgdG8gaGF2ZSBtYW51YWwgdHJhbnNtaXNzaW9uLCB3aGVyZWFzIGlmIHRoZSBwcm9iYWJpbGl0eSBpcyBsZXNzIHRoYW4gNTAlIChsb2cgb2RkcyBsZXNzIHRoYW4gMCksIHRoZW4gdGhlIGNhciBpcyBtb3JlIGxpa2VseSB0byBoYXZlIGF1dG9tYXRpYyB0cmFuc21pc3Npb24uCgpBbm90aGVyIHdheSB0byBsb29rIGF0IGl0IGlzIHRvIGZpbmQgdGhlIG1wZyB2YWx1ZSB3aGVyZSB0aGUgbW9kZWwgY3Jvc3NlcyB0aGUgNTAlIHByb2JhYmlsaXR5IG1hcmsuIFNpbmNlIDUwJSBwcm9iYWJpbGl0eSBpcyB0aGUgc2FtZSBhcyBsb2cgb2RkcyBvZiB6ZXJvLCBhbGwgd2UgbmVlZCB0byBkbyBpcyBzZXQgbG9nIG9kZHMgZXF1YWwgdG8gemVybyBpbiB0aGUgbW9kZWw6CgokJDAgPSAtNi42ICsgMC4zIG1wZyQkClNvbHZpbmcgdGhpcyBwcmVjaXNlbHksIHdlIGdldAoKYGBge3J9Ci1hbV9tcGdfdGlkeSRlc3RpbWF0ZVsxXS9hbV9tcGdfdGlkeSRlc3RpbWF0ZVsyXQpgYGAKCllvdSBjYW4gY2hlY2sgdGhlIGdyYXBoIGFib3ZlIHRvIHNlZSB0aGF0IHRoZSA1MCUgbWFyayBkb2VzLCBpbmRlZWQsIGNvcnJlc3BvbmQgdG8gYWJvdXQgMjEgb3IgMjIgbXBnLiBUaGVyZWZvcmUsIGFueSBjYXIgdGhhdCBnZXRzIGxlc3MgdGhhbiAyMS41IG1wZyB3aWxsIGJlIHByZWRpY3RlZCB0byBoYXZlIGF1dG9tYXRpYyB0cmFuc21pc3Npb24sIHdoaWxlIGFueSBjYXIgdGhhdCBnZXRzIGdyZWF0ZXIgdGhhbiAyMS41IG1wZyB3aWxsIGJlIHByZWRpY3RlZCB0byBoYXZlIG1hbnVhbCB0cmFuc21pc3Npb24uCgpXZSBjYW4gZWFzaWx5IG1ha2UgYSB2YXJpYWJsZSB0aGF0IGlzICJBdXRvbWF0aWMiIHdoZW4gdGhlIHByZWRpY3RlZCBsb2cgb2RkcyBhcmUgbmVnYXRpdmUgYW5kICJNYW51YWwiIHdoZW4gdGhleSBhcmUgcG9zaXRpdmUuCgpgYGB7cn0KYW1fcHJlZCA8LSBmYWN0b3IoaWZlbHNlKHByZWRpY3QoYW1fbXBnKSA8IDAsICJBdXRvbWF0aWMiLCAiTWFudWFsIikpCmFtX3ByZWQKYGBgCgpDb21wYXJlIHRoaXMgdG8gdGhlIGFjdHVhbCB2YWx1ZXMgb2YgYGFtYCBmcm9tIHRoZSBvcmlnaW5hbCBkYXRhOgoKYGBge3J9Cm10Y2FyczIkYW0KYGBgCgpUaGUgYGNhcmV0YCBwYWNrYWdlIGhhcyBhIGNvbnZlbmllbnQgZnVuY3Rpb24gY2FsbGVkIGBjb25mdXNpb25NYXRyaXhgIHRoYXQgY3JlYXRlcyBhIHRhYmxlIG9mIHByZWRpY3RlZCB2YWx1ZXMgdmVyc3VzIGFjdHVhbCAocmVmZXJlbmNlKSB2YWx1ZXMuCgpgYGB7cn0KY29uZnVzaW9uTWF0cml4KGFtX3ByZWQsIG10Y2FyczIkYW0pCmBgYAoKVGhlcmVmb3JlLCB0aGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBoYXMgYSA3NSUgYWNjdXJhY3kgcmF0ZSBpbiBjbGFzc2lmeWluZyBjYXJzIGFzIGF1dG9tYXRpYyBvciBtYW51YWwgYmFzZWQgc29sZWx5IG9uIG1wZy4gU2luY2UgbW9zdCBvZiB0aGUgY2FycyBhcmUgYXV0b21hdGljcyAoMTkvMzIsIG9yIGFib3V0IDYwJSksIGEgY29tcGxldGVseSBuYWl2ZSB0aGluZyB0byBkbyB3b3VsZCBiZSBqdXN0IHRvIHNheSBhbGwgdGhlIGNhcnMgaGF2ZSBhdXRvbWF0aWMgdHJhbnNtaXNzaW9uLCB3aXRoIGFjY3VyYWN5IDYwJS4gVGhpcyBpcyBjYWxsZWQgdGhlICJObyBJbmZvcm1hdGlvbiBSYXRlIiBvciBOSVIuIEJ1dCB1c2luZyBgbXBnYCBhcyBhIHByZWRpY3RvciBpbXByb3ZlcyB0aGUgYWNjdXJhY3kgdG8gNzUlLCBhbmQgdGhhdCdzIGEgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBpbXByb3ZlbWVudCEKCgojIyBNdWx0aXBsZSBsb2dpc3RpYyByZWdyZXNzaW9uCgpXZSBjYW4gdXNlIG1vcmUgdGhhbiBvbmUgcHJlZGljdG9yIHZhcmlhYmxlLiBMZXQncyB0cnkgcHJlZGljdGluZyBgYW1gIGZyb20gYG1wZ19jZW50YCBhbmQgYHZzYC4gKE5vdGUgdGhhdCBgdnNgIGlzIGEgZGljaG90b21vdXMgY2F0ZWdvcmljYWwgdmFyaWFibGUgaW5kaWNhdGluZyB0aGUgZW5naW5lIHR5cGUsICJWIiB2cyAiU3RyYWlnaHQiLikgRmlyc3QsIHdlJ2xsIGluY2x1ZGUgYHZzYCBpbiBvdXIgYG10Y2Fyc2AgZGF0YSBmcmFtZS4KCmBgYHtyfQptdGNhcnMyIDwtIG10Y2FyczIgJT4lCiAgICBtdXRhdGUodnMgPSBtdGNhcnMkdnMpCnN0cihtdGNhcnMyKQpgYGAKCgpgYGB7cn0KYW1fbXBnX3ZzIDwtIGdsbShhbSB+IG1wZ19jZW50ICsgdnMsIGRhdGEgPSBtdGNhcnMyLCBmYW1pbHkgPSBiaW5vbWlhbCkKYW1fbXBnX3ZzX3RpZHkgPC0gdGlkeShhbV9tcGdfdnMpCmFtX21wZ192c190aWR5CmBgYAoKCmBgYHtyfQphbV9tcGdfdnNfdGlkeV9leHAgPC0gdGlkeShhbV9tcGdfdnMsIGV4cG9uZW50aWF0ZSA9IFRSVUUpCmFtX21wZ192c190aWR5X2V4cApgYGAKClRoZSBpbnRlcnByZXRhdGlvbnMgb2YgdGhlIGludGVyY2VwdCBhbmQgY29lZmZpY2llbnRzIGRvIG5vdCBjaGFuZ2UgZXhjZXB0IHRoYXQgbm93IHdlIG11c3QgdGFrZSBjYXJlLS0tYXMgaW4gYW55IG11bHRpcGxlIHJlZ3Jlc3Npb24gc2V0dGluZy0tLXRvIG1lbnRpb24gdGhhdCB0aGV5IHByZWRpY3QgY2hhbmdlcyBhc3N1bWluZyBhbGwgb3RoZXIgdmFyaWFibGVzIGFyZSBoZWxkIGNvbnN0YW50LgoKTm90ZSB0aGF0IHRoZSBjb2VmZmljaWVudCBvZiBgdnNgIGlzIG5vdCBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50LgoKSGVyZSBpcyB0aGUgY29uZnVzaW9uIG1hdHJpeCBmb3IgdGhpcyBtb2RlbDoKCmBgYHtyfQphbV9wcmVkMiA8LSBmYWN0b3IoaWZlbHNlKHByZWRpY3QoYW1fbXBnX3ZzKSA8IDAsICJBdXRvbWF0aWMiLCAiTWFudWFsIikpCmNvbmZ1c2lvbk1hdHJpeChhbV9wcmVkMiwgbXRjYXJzMiRhbSkKYGBgCgpUaGUgYWNjdXJhY3kgaGFzIGltcHJvdmVkIGEgbGl0dGxlIHRvIGFib3V0IDc4JSwgYnV0IHRoYXQncyBub3QgbXVjaCBoaWdoZXIgdGhhbiA3NSUuIER1ZSB0byB0aGUgc21hbGwgc2FtcGxlIHNpemUsIHRoaXMgYWN0dWFsbHkgbWVhbnMgdGhhdCBvbmx5IG9uZSBhZGRpdGlvbmFsIGNhciB3YXMgcHJlZGljdGVkIGNvcnJlY3RseS4gKEluIGZhY3QsIG9uZSBmZXdlciBhdXRvbWF0aWMgd2FzIGNsYXNzaWZpZWQgY29ycmVjdGx5IGJ5IHRoaXMgbW9kZWwsIGJ1dCBiZWNhdXNlIHR3byBtb3JlIG1hbnVhbHMgd2VyZSBjb3JyZWN0bHkgY2xhc3NpZmllZCwgdGhlIG5ldCBlZmZlY3QgaXMgYSBzbGlnaHRseSBiZXR0ZXIgYWNjdXJhY3kgc2NvcmUuKQoKQXMgd2l0aCBvdGhlciBmb3JtcyBvZiByZWdyZXNzaW9uLCBBTk9WQSBjYW4gdGVsbCB1cyBpZiB0aGUgYWRkaXRpb24gb2YgcHJlZGljdG9ycyBpcyBzaWduaWZpY2FudC4KCmBgYHtyfQojIFRoaXMgaXMgVHlwZSBJSSBBTk9WQQpBbm92YShhbV9tcGdfdnMpCmBgYAoKQWx0aG91Z2ggdGhlIGNvZWZmaWNpZW50IG9mIGB2c2Agd2FzIG5vdCBzaWduaWZpY2FudCwgdGhlIEFOT1ZBIHRhYmxlIHRlbGxzIHVzIHRoYXQgdGhlIGFkZGl0aW9uIG9mIGB2c2AgdG8gdGhlIG1vZGVsIGFkZHMgc29tZSBwcmVkaWN0aXZlIHBvd2VyLgo=