Session 14: Introduction to Estimation Theory. Multiple Linear Regression. Model diganostics. The role of part correlation in this model. Dummy coding of categorical variables in R. Nested models.

Feedback should be send to goran.milovanovic@datakolektiv.com. These notebooks accompany the Intro to Data Science: Non-Technical Background course 2020/21.


What do we want to do today?

In today’s session we need to go beyond the discussion of a simple relationship between two variables: one predictor and one criterion. While Simple Linear Regression is extremely useful in a didactic perspective - to introduce statistical learning methods as such - is is only seldom used in practice. The real world is complex way beyond exploring only simple relationships, and mathematical models in practice almost necessarily deal with many predictors and the existing mutual information among them in an attempt to predict the state of the outcome variable. We will introduce the Multiple Linear Regression model in this session and discuss a more complex scenario involving several predictors. We will also introduce the method of dummy coding when categorical predictors are present in the Multiple Linear Regression scenario. We are also laying ground for even more complex Generalized Linear Models that can handle categorization problems beyond regression. Finally: comparing nested regression models.

0. Prerequisits

Install:

install.packages('QuantPsyc')
install.packages('lattice')

Setup:

dataDir <- paste0(getwd(), "/_data/")
library(tidyverse)
library(Hmisc)
library(ppcor)
library(car)
library(datasets)
library(broom)
library(QuantPsyc)
library(lattice)

1. Multiple Linear Regression: the exposition of the problem

1.1 Iris, again

The `iris’ dataset again offers everything that we need to introduce a new statistical model. One might wonder, but that small and (manifestly!) simple dataset can be used as well to introduce even more complex statistical models than Multiple Linear Regression!

data(iris)
str(iris)
'data.frame':   150 obs. of  5 variables:
 $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
 $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
 $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
 $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
 $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

And back to the problem with Petal.Length ~ Sepal.Length regression that we have already discussed:

ggplot(data = iris,
       aes(x = Sepal.Length, 
           y = Petal.Length)) +
  geom_point(size = 2, colour = "blue") +
  geom_smooth(method='lm', size = .25) +
  ggtitle("Sepal Length vs Petal Length") +
  xlab("Sepal Length") + ylab("Petal Length") + 
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = .5))

… where everything looks nice until…

ggplot(data = iris,
       aes(x = Sepal.Length, 
           y = Petal.Length, 
           color = Species, 
           group = Species)) +
  geom_point(size = 2) +
  geom_smooth(method='lm', size = .25) +
  ggtitle("Sepal Length vs Petal Length") +
  xlab("Sepal Length") + ylab("Petal Length") + 
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = .5))

Not to mention the model assumptions that we have discussed in the previous Session.

Let’s study the problem a bit.

plot(iris[ , c(1,3,5)],
     main = 
       "Inspect: Sepal vs. Petal Length \nfollowing the discovery of the Species...",
     cex.main = .75,
     cex = .6)

Did we ever mention {lattice}, the hero of data visualization in R before {ggplot2}?

# - {latice} xyplot
xyplot(Petal.Length ~ Sepal.Length | Species,
       data = iris,
       xlab = "Sepal Length", 
       ylab = "Petal Length"
)

Consider the conditional densities of Petal.Length given Species (using {lattice} again):

# - {latice} densityplot
densityplot(~ Petal.Length | Species,
            data = iris,
            plot.points = FALSE,
            xlab = "Petal Length", 
            ylab = "Density",
            main = "P(Petal Length|Species)",
            col.line = 'red'
)

And now consider the conditional densities of Sepal.Length given Species:

# - {latice} densityplot
densityplot(~ Sepal.Length | Species,
            data = iris,
            plot.points=FALSE,
            xlab = "Sepal Length", ylab = "Density",
            main = "P(Sepal Length|Species)",
            col.line = 'blue'
)

I have and idea: why not run a series of separate Simple Linear Regressions in the subgroups defined by Species and inspect the results? Let’s do it:

# - setosa
species <- unique(iris$Species)
w1 <- which(iris$Species == species[1])
reg <- lm(Petal.Length ~ Sepal.Length, 
          data = iris[w1,]) 
tidy(reg)
# - versicolor
w2 <- which(iris$Species == species[2])
reg <- lm(Petal.Length ~ Sepal.Length, data = iris[w2,]) 
tidy(reg)
# - virginica
w3 <- which(iris$Species == species[3])
reg <- lm(Petal.Length ~ Sepal.Length, data = iris[w3,]) 
tidy(reg)

I have used broom::tidy to tidy up the model summaries. The {broom} package offers many useful functions to deal with potentially messy outputs of R’s modeling functions such as lm().

So, Species obviously has some effect on Petal.Length, and that effect possibly goes even beyond the effect of Sepal.Length. How do we incorporate another predictor into the regression model?

1.2 The predictor is categorical: Dummy Coding

We will now try to predict Petal.Length from Species alone in a Simple Linear Regression model. First:

is.factor(iris$Species)
[1] TRUE

Ok, and the levels?

levels(iris$Species)
[1] "setosa"     "versicolor" "virginica" 

Regression with one categorical predictor:

reg <- lm(Petal.Length ~ Species, 
          data = iris) 
tidy(reg)

What effects are present? Let’s see: Speciesversicolor, Speciesvirginica, ok, but what happened to Speciessetosa..? It is our baseline, see:

levels(iris$Species)
[1] "setosa"     "versicolor" "virginica" 

The broom::glance() function is similar to summary() but gives us the model overview all tidy:

broom::glance(reg)

Never forget what the regression coefficient of a dummy variable means: it tells us about the effect of moving from the baseline towards the respective reference level. Here: baseline = setosa (cmp. levels(iris$Species) vs. the output of tidy(reg)). Hence: always look after the order of levels in linear models!

For example, we can change the baseline in Species to versicolor:

# - Levels: setosa versicolor virginica
levels(iris$Species)
[1] "setosa"     "versicolor" "virginica" 
iris$Species <- factor(iris$Species, 
                       levels = c("versicolor", 
                                  "virginica",
                                  "setosa")
                       )
levels(iris$Species)
[1] "versicolor" "virginica"  "setosa"    

Regression again:

# - baseline is now: versicolor
reg <- lm(Petal.Length ~ Species, 
          data = iris) 
tidy(reg)

1.3 Understanding dummy coding

Here is another way to perform dummy coding of categorical variables in R:

# - ...just to fix the order of Species back to default
rm(iris); data(iris)
levels(iris$Species)
[1] "setosa"     "versicolor" "virginica" 

In order to understand what dummy coding really is:

contr.treatment(3, base = 1)
  2 3
1 0 0
2 1 0
3 0 1

And then specifically applied to iris$Species:

contrasts(iris$Species) <- contr.treatment(3, base = 1)
contrasts(iris$Species)
           2 3
setosa     0 0
versicolor 1 0
virginica  0 1

Do not forget that:

class(iris$Species)
[1] "factor"

Now let’s play with level ordering:

iris$Species <- factor(iris$Species, 
                       levels = c ("virginica", 
                                   "versicolor", 
                                   "setosa"))
levels(iris$Species)
[1] "virginica"  "versicolor" "setosa"    
contrasts(iris$Species) = contr.treatment(3, base = 1)
# - baseline is now: virginica
contrasts(iris$Species)
           2 3
virginica  0 0
versicolor 1 0
setosa     0 1
# - consider carefully what you need to do
levels(iris$Species)
[1] "virginica"  "versicolor" "setosa"    

2. Multiple Linear Regression: the problem solved

2.1 One categorical + one continuous predictor

Now we run a multiple linear regression model with Sepal.Length and Species (dummy coded) as predictors of Petal.Length:

# - Petal.Length ~ Species (Dummy Coding) + Sepal.Length 
rm(iris); data(iris) # ...just to fix the order of Species back to default
reg <- lm(Petal.Length ~ Species + Sepal.Length, 
          data = iris)
tidy(reg)
glance(reg)

N.B. Since is.factor (iris$Species) == T - R does the dummy coding in lm() internally for us!

Let’s now compare these results with the simple linear regression model:

reg <- lm(Petal.Length ~ Sepal.Length, data=iris) 
tidy(reg)
glance(reg)

2.2 Nested models

We will now specify two regression models: reg1 defined as Petal.Length ~ Sepal.Length and reg2 defined as Petal.Length ~ Species + Sepal.Length. Obviously, reg2 encompasses reg1 in some way, right? Of course: the predictors in one model are a subset of predictors in another. Such models are called nested models. In this terminological game, reg2 would also be called a full model: a terminology will be used quite often in Binary Logistic Regression, the first Generalized Linear Model that we will meet in our next session.

Note on nested models: There is always a set of coefficients for the nested model (e.g. reg1) such that it can be expressed in terms of the full model (reg2). Can you figure it out?

# - reg1 is nested under reg2
reg1 <- lm(Petal.Length ~ Sepal.Length, 
           data = iris)
reg2 <- lm(Petal.Length ~ Species + Sepal.Length, 
           data = iris)

We can use the partial F-test to compare nested models:

anova(reg1, reg2) # partial F-test; Species certainly has an effect beyond Sepal.Length
Analysis of Variance Table

Model 1: Petal.Length ~ Sepal.Length
Model 2: Petal.Length ~ Species + Sepal.Length
  Res.Df     RSS Df Sum of Sq      F    Pr(>F)    
1    148 111.459                                  
2    146  11.657  2    99.802 624.99 < 2.2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

2.3 Model diagnostics

We can use the same kind of Influence Plot to search for influential cases in Multiple Linear Regression as we did in the case of Simple Linear Regression (except for this time the computation of the relevant indicators is way more complicated):

infReg <- as.data.frame(influence.measures(reg)$infmat)
head(infReg)

This time we will use broom:augment() to obtain the influence measures:

regFrame <- broom::augment(reg2)
head(regFrame)

Produce the Influence Chart:

plotFrame <- data.frame(residual = regFrame$.std.resid,
                        leverage = regFrame$.hat,
                        cookD = regFrame$.cooksd)
ggplot(plotFrame,
       aes(y = residual,
           x = leverage)) +
  geom_point(size = plotFrame$cookD * 100, 
             shape = 1, color = "blue") +
  ylab("Standardized Residual") + 
  xlab("Leverage") +
  ggtitle("Influence Plot\nSize of the circle corresponds to Cook's distance") +
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(size = 8, 
                                  face = "bold", 
                                  hjust = .5))

3. Several continuous predictors

3.1 The stackloss problem

The following example is a modification of the multiple-linear-regression section from R Tutorial.

data(stackloss)
str(stackloss)
'data.frame':   21 obs. of  4 variables:
 $ Air.Flow  : num  80 80 75 62 62 62 62 62 58 58 ...
 $ Water.Temp: num  27 27 25 24 22 23 24 24 23 18 ...
 $ Acid.Conc.: num  89 88 90 87 87 87 93 93 87 80 ...
 $ stack.loss: num  42 37 37 28 18 18 19 20 15 14 ...

The description of the stackloss dataset is found in the documentation:

  • Water Temp is the temperature of cooling water circulated through coils in the absorption tower;
  • Air Flow is the flow of cooling air;
  • Acid Conc. is the concentration of the acid circulating;
  • stack.loss (the outcome variable) is 10 times the percentage of the ingoing ammonia to the plant that escapes from the absorption column unabsorbed; that is, an (inverse) measure of the overall efficiency of the plant.
stacklossModel = lm(stack.loss ~ Air.Flow + Water.Temp + Acid.Conc., 
                    data = stackloss)
summary(stacklossModel)

Call:
lm(formula = stack.loss ~ Air.Flow + Water.Temp + Acid.Conc., 
    data = stackloss)

Residuals:
    Min      1Q  Median      3Q     Max 
-7.2377 -1.7117 -0.4551  2.3614  5.6978 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -39.9197    11.8960  -3.356  0.00375 ** 
Air.Flow      0.7156     0.1349   5.307  5.8e-05 ***
Water.Temp    1.2953     0.3680   3.520  0.00263 ** 
Acid.Conc.   -0.1521     0.1563  -0.973  0.34405    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.243 on 17 degrees of freedom
Multiple R-squared:  0.9136,    Adjusted R-squared:  0.8983 
F-statistic:  59.9 on 3 and 17 DF,  p-value: 3.016e-09
glance(stacklossModel)
tidy(stacklossModel)

Prediction for one single new data point:

# predict new data
obs = data.frame(Air.Flow = 72, 
                 Water.Temp = 20, 
                 Acid.Conc. = 85)
predict(stacklossModel, obs)
       1 
24.58173 

The confint() functions works as usual, for 95% CI…

confint(stacklossModel, level = .95) # 95% CI
                  2.5 %      97.5 %
(Intercept) -65.0180339 -14.8213150
Air.Flow      0.4311143   1.0001661
Water.Temp    0.5188228   2.0717495
Acid.Conc.   -0.4818741   0.1776291

… as well as for %99 CI:

confint(stacklossModel, level = .99) # 99% CI
                  0.5 %     99.5 %
(Intercept) -74.3970156 -5.4423333
Air.Flow      0.3247901  1.1064903
Water.Temp    0.2286670  2.3619053
Acid.Conc.   -0.6050987  0.3008536

3.2 Multicolinearity in Multiple Regression

That crazy thing with multiple regression: if the predictors are not correlated at all, why not run a series of simple linear regressions? On the other hand, if the predictors are highly correlated, problems with the estimates arise… John Fox’s {car} package allows us to compute the Variance Inflation Factor quite easily:

VIF <- vif(stacklossModel)
VIF
  Air.Flow Water.Temp Acid.Conc. 
  2.906484   2.572632   1.333587 

The Variance Inflation Factor (VIF) measures the increase in the variance of a regression coefficient due to colinearity. It’s square root (sqrt(VIF)) tells us how much larger a standard error of a regression coefficient is compared to a hypothetical situation where there were no correlations with any other predictors in the model. NOTE: The lower bound of VIF is 1; there is no upper bound, but VIF > 2 typically indicates that one should be concerned.

sqrt(VIF)
  Air.Flow Water.Temp Acid.Conc. 
  1.704841   1.603943   1.154811 

3.3 Part correlation in multiple regression

In multiple regression, it is the semi-partial (or part) correlation that you need to inspect:

  • assume a model with X1, X2, X3 as predictors, and Y as a criterion;

  • you need a semi-partial of X1 and Y following the removal of X2 and X3 from Y;

  • it goes like this: in Step 1, you perform a multiple regression Y ~ X2 + X3;

  • In Step 2, you take the residuals of Y, call them RY;

  • in Step 3, you regress (correlate) RY ~ X1: the correlation coefficient that you get from Step 3 is the part correlation that you’re looking for!

Recall our model…

stacklossModel = lm(stack.loss ~ Air.Flow + Water.Temp + Acid.Conc.,
                    data = stackloss)
summary(stacklossModel)

Call:
lm(formula = stack.loss ~ Air.Flow + Water.Temp + Acid.Conc., 
    data = stackloss)

Residuals:
    Min      1Q  Median      3Q     Max 
-7.2377 -1.7117 -0.4551  2.3614  5.6978 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -39.9197    11.8960  -3.356  0.00375 ** 
Air.Flow      0.7156     0.1349   5.307  5.8e-05 ***
Water.Temp    1.2953     0.3680   3.520  0.00263 ** 
Acid.Conc.   -0.1521     0.1563  -0.973  0.34405    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.243 on 17 degrees of freedom
Multiple R-squared:  0.9136,    Adjusted R-squared:  0.8983 
F-statistic:  59.9 on 3 and 17 DF,  p-value: 3.016e-09

What is the semi-partial (part correlation) of stack.loss and Air.Flow? Remember the {pcorr} package?

spartCor1 <- spcor.test(x = stackloss$Air.Flow, 
                        y = stackloss$stack.loss,
                        z = stackloss[ , c("Water.Temp", "Acid.Conc.")],
                        method = "pearson")
print(spartCor1)

The unique contribution of Air.Flow is then:

spartCor1$estimate
[1] 0.4631864
spartCor1$statistic
[1] 2.154858
spartCor1$p.value
[1] 0.04580372

R Markdown


Goran S. Milovanović

DataKolektiv, 2020/21

contact:


License: GPLv3 This Notebook is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This Notebook is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this Notebook. If not, see http://www.gnu.org/licenses/.


LS0tDQp0aXRsZTogSW50cm8gdG8gRGF0YSBTY2llbmNlIChOb24tVGVjaG5pY2FsIEJhY2tncm91bmQsIFIpIC0gU2Vzc2lvbjE0DQphdXRob3I6DQotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhywgUGhEDQogIGFmZmlsaWF0aW9uOiBEYXRhS29sZWt0aXYsIENoaWVmIFNjaWVudGlzdCAmIE93bmVyOyBEYXRhIFNjaWVudGlzdCBmb3IgV2lraWRhdGEsIFdNREUNCmFic3RyYWN0OiANCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgdG9jX2RlcHRoOiA1DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDUNCi0tLQ0KDQohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpDQoNCioqKg0KIyBTZXNzaW9uIDE0OiBJbnRyb2R1Y3Rpb24gdG8gRXN0aW1hdGlvbiBUaGVvcnkuIE11bHRpcGxlIExpbmVhciBSZWdyZXNzaW9uLiBNb2RlbCBkaWdhbm9zdGljcy4gVGhlIHJvbGUgb2YgcGFydCBjb3JyZWxhdGlvbiBpbiB0aGlzIG1vZGVsLiBEdW1teSBjb2Rpbmcgb2YgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGluIFIuIE5lc3RlZCBtb2RlbHMuDQoNCg0KKipGZWVkYmFjayoqIHNob3VsZCBiZSBzZW5kIHRvIGBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tYC4gDQpUaGVzZSBub3RlYm9va3MgYWNjb21wYW55IHRoZSBJbnRybyB0byBEYXRhIFNjaWVuY2U6IE5vbi1UZWNobmljYWwgQmFja2dyb3VuZCBjb3Vyc2UgMjAyMC8yMS4NCg0KKioqDQoNCiMjIyBXaGF0IGRvIHdlIHdhbnQgdG8gZG8gdG9kYXk/DQoNCkluIHRvZGF5J3Mgc2Vzc2lvbiB3ZSBuZWVkIHRvIGdvIGJleW9uZCB0aGUgZGlzY3Vzc2lvbiBvZiBhIHNpbXBsZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0d28gdmFyaWFibGVzOiBvbmUgKnByZWRpY3RvciogYW5kIG9uZSAqY3JpdGVyaW9uKi4gV2hpbGUgU2ltcGxlIExpbmVhciBSZWdyZXNzaW9uIGlzIGV4dHJlbWVseSB1c2VmdWwgaW4gYSBkaWRhY3RpYyBwZXJzcGVjdGl2ZSAtIHRvIGludHJvZHVjZSBzdGF0aXN0aWNhbCBsZWFybmluZyBtZXRob2RzIGFzIHN1Y2ggLSBpcyBpcyBvbmx5IHNlbGRvbSB1c2VkIGluIHByYWN0aWNlLiBUaGUgcmVhbCB3b3JsZCBpcyBjb21wbGV4IHdheSBiZXlvbmQgZXhwbG9yaW5nIG9ubHkgc2ltcGxlIHJlbGF0aW9uc2hpcHMsIGFuZCBtYXRoZW1hdGljYWwgbW9kZWxzIGluIHByYWN0aWNlIGFsbW9zdCBuZWNlc3NhcmlseSBkZWFsIHdpdGggKm1hbnkgcHJlZGljdG9ycyogYW5kIHRoZSBleGlzdGluZyBtdXR1YWwgaW5mb3JtYXRpb24gYW1vbmcgdGhlbSBpbiBhbiBhdHRlbXB0IHRvIHByZWRpY3QgdGhlIHN0YXRlIG9mIHRoZSBvdXRjb21lIHZhcmlhYmxlLiBXZSB3aWxsIGludHJvZHVjZSB0aGUgTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb24gbW9kZWwgaW4gdGhpcyBzZXNzaW9uIGFuZCBkaXNjdXNzIGEgbW9yZSBjb21wbGV4IHNjZW5hcmlvIGludm9sdmluZyBzZXZlcmFsIHByZWRpY3RvcnMuIFdlIHdpbGwgYWxzbyBpbnRyb2R1Y2UgdGhlIG1ldGhvZCBvZiAqZHVtbXkgY29kaW5nKiB3aGVuIGNhdGVnb3JpY2FsIHByZWRpY3RvcnMgYXJlIHByZXNlbnQgaW4gdGhlIE11bHRpcGxlIExpbmVhciBSZWdyZXNzaW9uIHNjZW5hcmlvLiBXZSBhcmUgYWxzbyBsYXlpbmcgZ3JvdW5kIGZvciBldmVuIG1vcmUgY29tcGxleCAqR2VuZXJhbGl6ZWQgTGluZWFyIE1vZGVscyogdGhhdCBjYW4gaGFuZGxlIGNhdGVnb3JpemF0aW9uIHByb2JsZW1zIGJleW9uZCByZWdyZXNzaW9uLiBGaW5hbGx5OiBjb21wYXJpbmcgbmVzdGVkIHJlZ3Jlc3Npb24gbW9kZWxzLg0KDQojIyMgMC4gUHJlcmVxdWlzaXRzDQoNCkluc3RhbGw6DQoNCmBgYHtyIGVjaG8gPSBULCBldmFsID0gRn0NCmluc3RhbGwucGFja2FnZXMoJ1F1YW50UHN5YycpDQppbnN0YWxsLnBhY2thZ2VzKCdsYXR0aWNlJykNCmBgYA0KDQpTZXR1cDoNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0NCmRhdGFEaXIgPC0gcGFzdGUwKGdldHdkKCksICIvX2RhdGEvIikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShIbWlzYykNCmxpYnJhcnkocHBjb3IpDQpsaWJyYXJ5KGNhcikNCmxpYnJhcnkoZGF0YXNldHMpDQpsaWJyYXJ5KGJyb29tKQ0KbGlicmFyeShRdWFudFBzeWMpDQpsaWJyYXJ5KGxhdHRpY2UpDQpgYGANCg0KDQojIyMgMS4gTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb246IHRoZSBleHBvc2l0aW9uIG9mIHRoZSBwcm9ibGVtDQoNCiMjIyMgMS4xIElyaXMsIGFnYWluDQoNClRoZSBgaXJpcycgZGF0YXNldCBhZ2FpbiBvZmZlcnMgZXZlcnl0aGluZyB0aGF0IHdlIG5lZWQgdG8gaW50cm9kdWNlIGEgbmV3IHN0YXRpc3RpY2FsIG1vZGVsLiBPbmUgbWlnaHQgd29uZGVyLCBidXQgdGhhdCBzbWFsbCBhbmQgKG1hbmlmZXN0bHkhKSBzaW1wbGUgZGF0YXNldCBjYW4gYmUgdXNlZCBhcyB3ZWxsIHRvIGludHJvZHVjZSBldmVuIG1vcmUgY29tcGxleCBzdGF0aXN0aWNhbCBtb2RlbHMgdGhhbiBNdWx0aXBsZSBMaW5lYXIgUmVncmVzc2lvbiENCg0KYGBgIHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0NCmRhdGEoaXJpcykNCnN0cihpcmlzKQ0KYGBgDQoNCkFuZCBiYWNrIHRvIHRoZSBwcm9ibGVtIHdpdGggYFBldGFsLkxlbmd0aCB+IFNlcGFsLkxlbmd0aGAgcmVncmVzc2lvbiB0aGF0IHdlIGhhdmUgYWxyZWFkeSBkaXNjdXNzZWQ6DQoNCmBgYCB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9DQpnZ3Bsb3QoZGF0YSA9IGlyaXMsDQogICAgICAgYWVzKHggPSBTZXBhbC5MZW5ndGgsIA0KICAgICAgICAgICB5ID0gUGV0YWwuTGVuZ3RoKSkgKw0KICBnZW9tX3BvaW50KHNpemUgPSAyLCBjb2xvdXIgPSAiYmx1ZSIpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScsIHNpemUgPSAuMjUpICsNCiAgZ2d0aXRsZSgiU2VwYWwgTGVuZ3RoIHZzIFBldGFsIExlbmd0aCIpICsNCiAgeGxhYigiU2VwYWwgTGVuZ3RoIikgKyB5bGFiKCJQZXRhbCBMZW5ndGgiKSArIA0KICB0aGVtZV9idygpICsgDQogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IC41KSkNCmBgYA0KLi4uIHdoZXJlIGV2ZXJ5dGhpbmcgKmxvb2tzKiBuaWNlIHVudGlsLi4uDQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0NCmdncGxvdChkYXRhID0gaXJpcywNCiAgICAgICBhZXMoeCA9IFNlcGFsLkxlbmd0aCwgDQogICAgICAgICAgIHkgPSBQZXRhbC5MZW5ndGgsIA0KICAgICAgICAgICBjb2xvciA9IFNwZWNpZXMsIA0KICAgICAgICAgICBncm91cCA9IFNwZWNpZXMpKSArDQogIGdlb21fcG9pbnQoc2l6ZSA9IDIpICsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kPSdsbScsIHNpemUgPSAuMjUpICsNCiAgZ2d0aXRsZSgiU2VwYWwgTGVuZ3RoIHZzIFBldGFsIExlbmd0aCIpICsNCiAgeGxhYigiU2VwYWwgTGVuZ3RoIikgKyB5bGFiKCJQZXRhbCBMZW5ndGgiKSArIA0KICB0aGVtZV9idygpICsgDQogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IC41KSkNCmBgYA0KTm90IHRvIG1lbnRpb24gdGhlIG1vZGVsIGFzc3VtcHRpb25zIHRoYXQgd2UgaGF2ZSBkaXNjdXNzZWQgaW4gdGhlIHByZXZpb3VzIFNlc3Npb24uIA0KDQpMZXQncyBzdHVkeSB0aGUgcHJvYmxlbSBhIGJpdC4NCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQ0KcGxvdChpcmlzWyAsIGMoMSwzLDUpXSwNCiAgICAgbWFpbiA9IA0KICAgICAgICJJbnNwZWN0OiBTZXBhbCB2cy4gUGV0YWwgTGVuZ3RoIFxuZm9sbG93aW5nIHRoZSBkaXNjb3Zlcnkgb2YgdGhlIFNwZWNpZXMuLi4iLA0KICAgICBjZXgubWFpbiA9IC43NSwNCiAgICAgY2V4ID0gLjYpDQpgYGANCkRpZCB3ZSBldmVyIG1lbnRpb24gYHtsYXR0aWNlfWAsIHRoZSBoZXJvIG9mIGRhdGEgdmlzdWFsaXphdGlvbiBpbiBSIGJlZm9yZSBge2dncGxvdDJ9YD8gDQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0NCiMgLSB7bGF0aWNlfSB4eXBsb3QNCnh5cGxvdChQZXRhbC5MZW5ndGggfiBTZXBhbC5MZW5ndGggfCBTcGVjaWVzLA0KICAgICAgIGRhdGEgPSBpcmlzLA0KICAgICAgIHhsYWIgPSAiU2VwYWwgTGVuZ3RoIiwgDQogICAgICAgeWxhYiA9ICJQZXRhbCBMZW5ndGgiDQopDQpgYGANCkNvbnNpZGVyIHRoZSAqY29uZGl0aW9uYWwgZGVuc2l0aWVzKiBvZiBgUGV0YWwuTGVuZ3RoYCBnaXZlbiBgU3BlY2llc2AgKHVzaW5nIGB7bGF0dGljZX1gIGFnYWluKToNCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQ0KIyAtIHtsYXRpY2V9IGRlbnNpdHlwbG90DQpkZW5zaXR5cGxvdCh+IFBldGFsLkxlbmd0aCB8IFNwZWNpZXMsDQogICAgICAgICAgICBkYXRhID0gaXJpcywNCiAgICAgICAgICAgIHBsb3QucG9pbnRzID0gRkFMU0UsDQogICAgICAgICAgICB4bGFiID0gIlBldGFsIExlbmd0aCIsIA0KICAgICAgICAgICAgeWxhYiA9ICJEZW5zaXR5IiwNCiAgICAgICAgICAgIG1haW4gPSAiUChQZXRhbCBMZW5ndGh8U3BlY2llcykiLA0KICAgICAgICAgICAgY29sLmxpbmUgPSAncmVkJw0KKQ0KYGBgDQpBbmQgbm93IGNvbnNpZGVyIHRoZSAqY29uZGl0aW9uYWwgZGVuc2l0aWVzKiBvZiBgU2VwYWwuTGVuZ3RoYCBnaXZlbiBgU3BlY2llc2A6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0NCiMgLSB7bGF0aWNlfSBkZW5zaXR5cGxvdA0KZGVuc2l0eXBsb3QofiBTZXBhbC5MZW5ndGggfCBTcGVjaWVzLA0KICAgICAgICAgICAgZGF0YSA9IGlyaXMsDQogICAgICAgICAgICBwbG90LnBvaW50cz1GQUxTRSwNCiAgICAgICAgICAgIHhsYWIgPSAiU2VwYWwgTGVuZ3RoIiwgeWxhYiA9ICJEZW5zaXR5IiwNCiAgICAgICAgICAgIG1haW4gPSAiUChTZXBhbCBMZW5ndGh8U3BlY2llcykiLA0KICAgICAgICAgICAgY29sLmxpbmUgPSAnYmx1ZScNCikNCmBgYA0KSSBoYXZlIGFuZCBpZGVhOiB3aHkgbm90IHJ1biBhIHNlcmllcyBvZiBzZXBhcmF0ZSBTaW1wbGUgTGluZWFyIFJlZ3Jlc3Npb25zIGluIHRoZSBzdWJncm91cHMgZGVmaW5lZCBieSBgU3BlY2llc2AgYW5kIGluc3BlY3QgdGhlIHJlc3VsdHM/IExldCdzIGRvIGl0Og0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9DQojIC0gc2V0b3NhDQpzcGVjaWVzIDwtIHVuaXF1ZShpcmlzJFNwZWNpZXMpDQp3MSA8LSB3aGljaChpcmlzJFNwZWNpZXMgPT0gc3BlY2llc1sxXSkNCnJlZyA8LSBsbShQZXRhbC5MZW5ndGggfiBTZXBhbC5MZW5ndGgsIA0KICAgICAgICAgIGRhdGEgPSBpcmlzW3cxLF0pIA0KdGlkeShyZWcpDQpgYGANCg0KYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQ0KIyAtIHZlcnNpY29sb3INCncyIDwtIHdoaWNoKGlyaXMkU3BlY2llcyA9PSBzcGVjaWVzWzJdKQ0KcmVnIDwtIGxtKFBldGFsLkxlbmd0aCB+IFNlcGFsLkxlbmd0aCwgZGF0YSA9IGlyaXNbdzIsXSkgDQp0aWR5KHJlZykNCmBgYA0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9DQojIC0gdmlyZ2luaWNhDQp3MyA8LSB3aGljaChpcmlzJFNwZWNpZXMgPT0gc3BlY2llc1szXSkNCnJlZyA8LSBsbShQZXRhbC5MZW5ndGggfiBTZXBhbC5MZW5ndGgsIGRhdGEgPSBpcmlzW3czLF0pIA0KdGlkeShyZWcpDQpgYGANCg0KSSBoYXZlIHVzZWQgYGJyb29tOjp0aWR5YCB0byB0aWR5IHVwIHRoZSBtb2RlbCBzdW1tYXJpZXMuIFRoZSBbe2Jyb29tfV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2Jyb29tL3ZpZ25ldHRlcy9icm9vbS5odG1sKSBwYWNrYWdlIG9mZmVycyBtYW55IHVzZWZ1bCBmdW5jdGlvbnMgdG8gZGVhbCB3aXRoIHBvdGVudGlhbGx5IG1lc3N5IG91dHB1dHMgb2YgUidzIG1vZGVsaW5nIGZ1bmN0aW9ucyBzdWNoIGFzIGBsbSgpYC4gDQoNClNvLCBgU3BlY2llc2Agb2J2aW91c2x5IGhhcyBzb21lIGVmZmVjdCBvbiBgUGV0YWwuTGVuZ3RoYCwgYW5kIHRoYXQgZWZmZWN0IHBvc3NpYmx5IGdvZXMgZXZlbiBiZXlvbmQgdGhlIGVmZmVjdCBvZiBgU2VwYWwuTGVuZ3RoYC4gSG93IGRvIHdlIGluY29ycG9yYXRlIGFub3RoZXIgcHJlZGljdG9yIGludG8gdGhlIHJlZ3Jlc3Npb24gbW9kZWw/DQoNCg0KIyMjIyAxLjIgVGhlIHByZWRpY3RvciBpcyBjYXRlZ29yaWNhbDogRHVtbXkgQ29kaW5nDQoNCldlIHdpbGwgbm93IHRyeSB0byBwcmVkaWN0IGBQZXRhbC5MZW5ndGhgIGZyb20gYFNwZWNpZXNgIGFsb25lIGluIGEgU2ltcGxlIExpbmVhciBSZWdyZXNzaW9uIG1vZGVsLiBGaXJzdDoNCg0KYGBge3IgZWNobyA9IFR9DQppcy5mYWN0b3IoaXJpcyRTcGVjaWVzKQ0KYGBgDQpPaywgYW5kIHRoZSBsZXZlbHM/DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbGV2ZWxzKGlyaXMkU3BlY2llcykNCmBgYA0KUmVncmVzc2lvbiB3aXRoICpvbmUgY2F0ZWdvcmljYWwgcHJlZGljdG9yKjoNCg0KYGBge3IgZWNobyA9IFR9DQpyZWcgPC0gbG0oUGV0YWwuTGVuZ3RoIH4gU3BlY2llcywgDQogICAgICAgICAgZGF0YSA9IGlyaXMpIA0KdGlkeShyZWcpDQpgYGANCg0KV2hhdCBlZmZlY3RzIGFyZSBwcmVzZW50PyBMZXQncyBzZWU6IGBTcGVjaWVzdmVyc2ljb2xvcmAsIGBTcGVjaWVzdmlyZ2luaWNhYCwgb2ssIGJ1dCB3aGF0IGhhcHBlbmVkIHRvIGBTcGVjaWVzc2V0b3NhYC4uPyBJdCBpcyBvdXIgKipiYXNlbGluZSoqLCBzZWU6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbGV2ZWxzKGlyaXMkU3BlY2llcykNCmBgYA0KVGhlIGBicm9vbTo6Z2xhbmNlKClgIGZ1bmN0aW9uIGlzIHNpbWlsYXIgdG8gYHN1bW1hcnkoKWAgYnV0IGdpdmVzIHVzIHRoZSBtb2RlbCBvdmVydmlldyBhbGwgdGlkeToNCg0KYGBge3IgZWNobyA9IFR9DQpicm9vbTo6Z2xhbmNlKHJlZykNCmBgYA0KDQpOZXZlciBmb3JnZXQgd2hhdCB0aGUgcmVncmVzc2lvbiBjb2VmZmljaWVudCBvZiBhICoqZHVtbXkgdmFyaWFibGUqKiBtZWFuczogKml0IHRlbGxzIHVzIGFib3V0IHRoZSBlZmZlY3Qgb2YgbW92aW5nIGZyb20gdGhlIGJhc2VsaW5lIHRvd2FyZHMgdGhlIHJlc3BlY3RpdmUgcmVmZXJlbmNlIGxldmVsKi4gSGVyZTogYGJhc2VsaW5lID0gc2V0b3NhYCAoY21wLiBgbGV2ZWxzKGlyaXMkU3BlY2llcylgIHZzLiB0aGUgb3V0cHV0IG9mIGB0aWR5KHJlZylgKS4gKipIZW5jZToqKiBhbHdheXMgbG9vayBhZnRlciB0aGUgb3JkZXIgb2YgbGV2ZWxzIGluIGxpbmVhciBtb2RlbHMhDQoNCkZvciBleGFtcGxlLCB3ZSBjYW4gY2hhbmdlIHRoZSBiYXNlbGluZSBpbiBgU3BlY2llc2AgdG8gYHZlcnNpY29sb3JgOg0KDQpgYGB7ciBlY2hvID0gVH0NCiMgLSBMZXZlbHM6IHNldG9zYSB2ZXJzaWNvbG9yIHZpcmdpbmljYQ0KbGV2ZWxzKGlyaXMkU3BlY2llcykNCmBgYA0KDQpgYGB7ciBlY2hvID0gVH0NCmlyaXMkU3BlY2llcyA8LSBmYWN0b3IoaXJpcyRTcGVjaWVzLCANCiAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygidmVyc2ljb2xvciIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJ2aXJnaW5pY2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzZXRvc2EiKQ0KICAgICAgICAgICAgICAgICAgICAgICApDQpsZXZlbHMoaXJpcyRTcGVjaWVzKQ0KYGBgDQpSZWdyZXNzaW9uIGFnYWluOg0KDQpgYGB7ciBlY2hvID0gVH0NCiMgLSBiYXNlbGluZSBpcyBub3c6IHZlcnNpY29sb3INCnJlZyA8LSBsbShQZXRhbC5MZW5ndGggfiBTcGVjaWVzLCANCiAgICAgICAgICBkYXRhID0gaXJpcykgDQp0aWR5KHJlZykNCmBgYA0KDQojIyMjIDEuMyBVbmRlcnN0YW5kaW5nIGR1bW15IGNvZGluZw0KDQpIZXJlIGlzIGFub3RoZXIgd2F5IHRvIHBlcmZvcm0gZHVtbXkgY29kaW5nIG9mIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBpbiBSOg0KDQpgYGB7ciBlY2hvID0gVH0NCiMgLSAuLi5qdXN0IHRvIGZpeCB0aGUgb3JkZXIgb2YgU3BlY2llcyBiYWNrIHRvIGRlZmF1bHQNCnJtKGlyaXMpOyBkYXRhKGlyaXMpDQpsZXZlbHMoaXJpcyRTcGVjaWVzKQ0KYGBgDQpJbiBvcmRlciB0byB1bmRlcnN0YW5kIHdoYXQgZHVtbXkgY29kaW5nIHJlYWxseSBpczoNCg0KYGBge3IgZWNobyA9IFR9DQpjb250ci50cmVhdG1lbnQoMywgYmFzZSA9IDEpDQpgYGANCg0KQW5kIHRoZW4gc3BlY2lmaWNhbGx5IGFwcGxpZWQgdG8gYGlyaXMkU3BlY2llc2A6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KY29udHJhc3RzKGlyaXMkU3BlY2llcykgPC0gY29udHIudHJlYXRtZW50KDMsIGJhc2UgPSAxKQ0KY29udHJhc3RzKGlyaXMkU3BlY2llcykNCmBgYA0KRG8gbm90IGZvcmdldCB0aGF0Og0KDQpgYGB7ciBlY2hvID0gVH0NCmNsYXNzKGlyaXMkU3BlY2llcykNCmBgYA0KTm93IGxldCdzIHBsYXkgd2l0aCBsZXZlbCBvcmRlcmluZzoNCg0KYGBge3IgZWNobyA9IFR9DQppcmlzJFNwZWNpZXMgPC0gZmFjdG9yKGlyaXMkU3BlY2llcywgDQogICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMgKCJ2aXJnaW5pY2EiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInZlcnNpY29sb3IiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInNldG9zYSIpKQ0KbGV2ZWxzKGlyaXMkU3BlY2llcykNCmBgYA0KDQpgYGB7ciBlY2hvID0gVH0NCmNvbnRyYXN0cyhpcmlzJFNwZWNpZXMpID0gY29udHIudHJlYXRtZW50KDMsIGJhc2UgPSAxKQ0KIyAtIGJhc2VsaW5lIGlzIG5vdzogdmlyZ2luaWNhDQpjb250cmFzdHMoaXJpcyRTcGVjaWVzKQ0KIyAtIGNvbnNpZGVyIGNhcmVmdWxseSB3aGF0IHlvdSBuZWVkIHRvIGRvDQpgYGANCg0KYGBge3IgZWNobyA9IFR9DQpsZXZlbHMoaXJpcyRTcGVjaWVzKQ0KYGBgDQojIyMgMi4gTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb246IHRoZSBwcm9ibGVtIHNvbHZlZA0KDQojIyMjIDIuMSBPbmUgY2F0ZWdvcmljYWwgKyBvbmUgY29udGludW91cyBwcmVkaWN0b3INCg0KTm93IHdlIHJ1biBhIG11bHRpcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHdpdGggYFNlcGFsLkxlbmd0aGAgYW5kIGBTcGVjaWVzYCAoZHVtbXkgY29kZWQpIGFzIHByZWRpY3RvcnMgb2YgYFBldGFsLkxlbmd0aGA6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KIyAtIFBldGFsLkxlbmd0aCB+IFNwZWNpZXMgKER1bW15IENvZGluZykgKyBTZXBhbC5MZW5ndGggDQpybShpcmlzKTsgZGF0YShpcmlzKSAjIC4uLmp1c3QgdG8gZml4IHRoZSBvcmRlciBvZiBTcGVjaWVzIGJhY2sgdG8gZGVmYXVsdA0KcmVnIDwtIGxtKFBldGFsLkxlbmd0aCB+IFNwZWNpZXMgKyBTZXBhbC5MZW5ndGgsIA0KICAgICAgICAgIGRhdGEgPSBpcmlzKQ0KdGlkeShyZWcpDQpgYGANCg0KYGBge3IgZWNobyA9IFR9DQpnbGFuY2UocmVnKQ0KYGBgDQoNCioqTi5CLioqIFNpbmNlIGlzLmZhY3RvciBgKGlyaXMkU3BlY2llcykgPT0gVGAgLSBSIGRvZXMgdGhlIGR1bW15IGNvZGluZyBpbiBsbSgpIGludGVybmFsbHkgZm9yIHVzIQ0KDQpMZXQncyBub3cgY29tcGFyZSB0aGVzZSByZXN1bHRzIHdpdGggdGhlIHNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbDoNCg0KYGBge3IgZWNobyA9IFR9DQpyZWcgPC0gbG0oUGV0YWwuTGVuZ3RoIH4gU2VwYWwuTGVuZ3RoLCBkYXRhPWlyaXMpIA0KdGlkeShyZWcpDQpgYGANCg0KYGBge3IgZWNobyA9IFR9DQpnbGFuY2UocmVnKQ0KYGBgDQoNCiMjIyMgMi4yIE5lc3RlZCBtb2RlbHMNCg0KV2Ugd2lsbCBub3cgc3BlY2lmeSB0d28gcmVncmVzc2lvbiBtb2RlbHM6IGByZWcxYCBkZWZpbmVkIGFzIGBQZXRhbC5MZW5ndGggfiBTZXBhbC5MZW5ndGhgIGFuZCBgcmVnMmAgZGVmaW5lZCBhcyBgUGV0YWwuTGVuZ3RoIH4gU3BlY2llcyArIFNlcGFsLkxlbmd0aGAuIE9idmlvdXNseSwgYHJlZzJgIGVuY29tcGFzc2VzIGByZWcxYCBpbiBzb21lIHdheSwgcmlnaHQ/IE9mIGNvdXJzZTogdGhlIHByZWRpY3RvcnMgaW4gb25lIG1vZGVsIGFyZSBhIHN1YnNldCBvZiBwcmVkaWN0b3JzIGluIGFub3RoZXIuIFN1Y2ggbW9kZWxzIGFyZSBjYWxsZWQgKm5lc3RlZCBtb2RlbHMqLiBJbiB0aGlzIHRlcm1pbm9sb2dpY2FsIGdhbWUsIGByZWcyYCB3b3VsZCBhbHNvIGJlIGNhbGxlZCBhICoqZnVsbCBtb2RlbCoqOiBhIHRlcm1pbm9sb2d5IHdpbGwgYmUgdXNlZCBxdWl0ZSBvZnRlbiBpbiBCaW5hcnkgTG9naXN0aWMgUmVncmVzc2lvbiwgdGhlIGZpcnN0IEdlbmVyYWxpemVkIExpbmVhciBNb2RlbCB0aGF0IHdlIHdpbGwgbWVldCBpbiBvdXIgbmV4dCBzZXNzaW9uLg0KDQoqKk5vdGUgb24gbmVzdGVkIG1vZGVsczoqKiBUaGVyZSBpcyBhbHdheXMgYSBzZXQgb2YgY29lZmZpY2llbnRzIGZvciB0aGUgbmVzdGVkIG1vZGVsIChlLmcuIGByZWcxYCkgc3VjaCB0aGF0IGl0IGNhbiBiZSBleHByZXNzZWQgaW4gdGVybXMgb2YgdGhlIGZ1bGwgbW9kZWwgKGByZWcyYCkuIENhbiB5b3UgZmlndXJlIGl0IG91dD8NCg0KYGBge3IgZWNobyA9IFR9DQojIC0gcmVnMSBpcyBuZXN0ZWQgdW5kZXIgcmVnMg0KcmVnMSA8LSBsbShQZXRhbC5MZW5ndGggfiBTZXBhbC5MZW5ndGgsIA0KICAgICAgICAgICBkYXRhID0gaXJpcykNCnJlZzIgPC0gbG0oUGV0YWwuTGVuZ3RoIH4gU3BlY2llcyArIFNlcGFsLkxlbmd0aCwgDQogICAgICAgICAgIGRhdGEgPSBpcmlzKQ0KYGBgDQoNCldlIGNhbiB1c2UgdGhlIFtwYXJ0aWFsIEYtdGVzdF0oaHR0cDovL3BhZ2VzLnN0ZXJuLm55dS5lZHUvfmdzaW1vbi9COTAyMzAxUGFnZS9DTEFTUzAyXzI0RkVCMTAvUGFydGlhbEZ0ZXN0LnBkZikgdG8gY29tcGFyZSBuZXN0ZWQgbW9kZWxzOg0KDQpgYGB7ciBlY2hvID0gVH0NCmFub3ZhKHJlZzEsIHJlZzIpICMgcGFydGlhbCBGLXRlc3Q7IFNwZWNpZXMgY2VydGFpbmx5IGhhcyBhbiBlZmZlY3QgYmV5b25kIFNlcGFsLkxlbmd0aA0KYGBgDQoNCiMjIyMgMi4zIE1vZGVsIGRpYWdub3N0aWNzDQoNCldlIGNhbiB1c2UgdGhlIHNhbWUga2luZCBvZiBJbmZsdWVuY2UgUGxvdCB0byBzZWFyY2ggZm9yIGluZmx1ZW50aWFsIGNhc2VzIGluIE11bHRpcGxlIExpbmVhciBSZWdyZXNzaW9uIGFzIHdlIGRpZCBpbiB0aGUgY2FzZSBvZiBTaW1wbGUgTGluZWFyIFJlZ3Jlc3Npb24gKGV4Y2VwdCBmb3IgdGhpcyB0aW1lIHRoZSBjb21wdXRhdGlvbiBvZiB0aGUgcmVsZXZhbnQgaW5kaWNhdG9ycyBpcyB3YXkgbW9yZSBjb21wbGljYXRlZCk6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KaW5mUmVnIDwtIGFzLmRhdGEuZnJhbWUoaW5mbHVlbmNlLm1lYXN1cmVzKHJlZykkaW5mbWF0KQ0KaGVhZChpbmZSZWcpDQpgYGANCg0KVGhpcyB0aW1lIHdlIHdpbGwgdXNlIGBicm9vbTphdWdtZW50KClgIHRvIG9idGFpbiB0aGUgaW5mbHVlbmNlIG1lYXN1cmVzOg0KDQpgYGB7ciBlY2hvID0gVH0NCnJlZ0ZyYW1lIDwtIGJyb29tOjphdWdtZW50KHJlZzIpDQpoZWFkKHJlZ0ZyYW1lKQ0KYGBgDQoNClByb2R1Y2UgdGhlIEluZmx1ZW5jZSBDaGFydDoNCg0KYGBge3IgZWNobyA9IFR9DQpwbG90RnJhbWUgPC0gZGF0YS5mcmFtZShyZXNpZHVhbCA9IHJlZ0ZyYW1lJC5zdGQucmVzaWQsDQogICAgICAgICAgICAgICAgICAgICAgICBsZXZlcmFnZSA9IHJlZ0ZyYW1lJC5oYXQsDQogICAgICAgICAgICAgICAgICAgICAgICBjb29rRCA9IHJlZ0ZyYW1lJC5jb29rc2QpDQpnZ3Bsb3QocGxvdEZyYW1lLA0KICAgICAgIGFlcyh5ID0gcmVzaWR1YWwsDQogICAgICAgICAgIHggPSBsZXZlcmFnZSkpICsNCiAgZ2VvbV9wb2ludChzaXplID0gcGxvdEZyYW1lJGNvb2tEICogMTAwLCANCiAgICAgICAgICAgICBzaGFwZSA9IDEsIGNvbG9yID0gImJsdWUiKSArDQogIHlsYWIoIlN0YW5kYXJkaXplZCBSZXNpZHVhbCIpICsgDQogIHhsYWIoIkxldmVyYWdlIikgKw0KICBnZ3RpdGxlKCJJbmZsdWVuY2UgUGxvdFxuU2l6ZSBvZiB0aGUgY2lyY2xlIGNvcnJlc3BvbmRzIHRvIENvb2sncyBkaXN0YW5jZSIpICsNCiAgdGhlbWVfYncoKSArIA0KICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhY2UgPSAiYm9sZCIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhqdXN0ID0gLjUpKQ0KYGBgDQoNCiMjIyAzLiBTZXZlcmFsIGNvbnRpbnVvdXMgcHJlZGljdG9ycw0KDQojIyMjIDMuMSBUaGUgYHN0YWNrbG9zc2AgcHJvYmxlbQ0KDQpUaGUgZm9sbG93aW5nIGV4YW1wbGUgaXMgYSBtb2RpZmljYXRpb24gb2YgdGhlIFttdWx0aXBsZS1saW5lYXItcmVncmVzc2lvbiBzZWN0aW9uXShodHRwOi8vd3d3LnItdHV0b3IuY29tL2VsZW1lbnRhcnktc3RhdGlzdGljcy9tdWx0aXBsZS1saW5lYXItcmVncmVzc2lvbikgZnJvbSBbUiBUdXRvcmlhbF0oaHR0cDovL3d3dy5yLXR1dG9yLmNvbS8pLg0KDQoNCmBgYHtyIGVjaG8gPSBUfQ0KZGF0YShzdGFja2xvc3MpDQpzdHIoc3RhY2tsb3NzKQ0KYGBgDQoNClRoZSBkZXNjcmlwdGlvbiBvZiB0aGUgYHN0YWNrbG9zc2AgZGF0YXNldCBpcyBmb3VuZCBpbiB0aGUgW2RvY3VtZW50YXRpb25dKGh0dHBzOi8vc3RhdC5ldGh6LmNoL1ItbWFudWFsL1ItZGV2ZWwvbGlicmFyeS9kYXRhc2V0cy9odG1sL3N0YWNrbG9zcy5odG1sKToNCg0KLSBgV2F0ZXIgVGVtcGAgaXMgdGhlIHRlbXBlcmF0dXJlIG9mIGNvb2xpbmcgd2F0ZXIgY2lyY3VsYXRlZCB0aHJvdWdoIGNvaWxzIGluIHRoZSBhYnNvcnB0aW9uIHRvd2VyOyANCi0gYEFpciBGbG93YCBpcyB0aGUgZmxvdyBvZiBjb29saW5nIGFpcjsNCi0gYEFjaWQgQ29uYy5gIGlzIHRoZSBjb25jZW50cmF0aW9uIG9mIHRoZSBhY2lkIGNpcmN1bGF0aW5nOw0KLSBgc3RhY2subG9zc2AgKHRoZSBvdXRjb21lIHZhcmlhYmxlKSBpcyAxMCB0aW1lcyB0aGUgcGVyY2VudGFnZSBvZiB0aGUgaW5nb2luZyBhbW1vbmlhIHRvIHRoZSBwbGFudCB0aGF0IGVzY2FwZXMgZnJvbSB0aGUgYWJzb3JwdGlvbiBjb2x1bW4gdW5hYnNvcmJlZDsgdGhhdCBpcywgYW4gKGludmVyc2UpIG1lYXN1cmUgb2YgdGhlIG92ZXJhbGwgZWZmaWNpZW5jeSBvZiB0aGUgcGxhbnQuDQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3RhY2tsb3NzTW9kZWwgPSBsbShzdGFjay5sb3NzIH4gQWlyLkZsb3cgKyBXYXRlci5UZW1wICsgQWNpZC5Db25jLiwgDQogICAgICAgICAgICAgICAgICAgIGRhdGEgPSBzdGFja2xvc3MpDQpzdW1tYXJ5KHN0YWNrbG9zc01vZGVsKQ0KYGBgDQoNCmBgYHtyIGVjaG8gPSBUfQ0KZ2xhbmNlKHN0YWNrbG9zc01vZGVsKQ0KYGBgDQoNCmBgYHtyIGVjaG8gPSBUfQ0KdGlkeShzdGFja2xvc3NNb2RlbCkNCmBgYA0KUHJlZGljdGlvbiBmb3Igb25lIHNpbmdsZSBuZXcgZGF0YSBwb2ludDoNCg0KYGBge3IgZWNobyA9IFR9DQojIHByZWRpY3QgbmV3IGRhdGENCm9icyA9IGRhdGEuZnJhbWUoQWlyLkZsb3cgPSA3MiwgDQogICAgICAgICAgICAgICAgIFdhdGVyLlRlbXAgPSAyMCwgDQogICAgICAgICAgICAgICAgIEFjaWQuQ29uYy4gPSA4NSkNCnByZWRpY3Qoc3RhY2tsb3NzTW9kZWwsIG9icykNCmBgYA0KDQpUaGUgYGNvbmZpbnQoKWAgZnVuY3Rpb25zIHdvcmtzIGFzIHVzdWFsLCBmb3IgOTUlIENJLi4uDQoNCmBgYHtyIGVjaG8gPSBUfQ0KY29uZmludChzdGFja2xvc3NNb2RlbCwgbGV2ZWwgPSAuOTUpICMgOTUlIENJDQpgYGANCg0KLi4uIGFzIHdlbGwgYXMgZm9yICU5OSBDSToNCg0KYGBge3IgZWNobyA9IFR9DQpjb25maW50KHN0YWNrbG9zc01vZGVsLCBsZXZlbCA9IC45OSkgIyA5OSUgQ0kNCmBgYA0KDQojIyMjIDMuMiBNdWx0aWNvbGluZWFyaXR5IGluIE11bHRpcGxlIFJlZ3Jlc3Npb24NCg0KVGhhdCBjcmF6eSB0aGluZyB3aXRoIG11bHRpcGxlIHJlZ3Jlc3Npb246IGlmIHRoZSBwcmVkaWN0b3JzIGFyZSBub3QgY29ycmVsYXRlZCBhdCBhbGwsIHdoeSBub3QgcnVuIGEgc2VyaWVzIG9mIHNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbnM/IE9uIHRoZSBvdGhlciBoYW5kLCBpZiB0aGUgcHJlZGljdG9ycyBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQsIHByb2JsZW1zIHdpdGggdGhlIGVzdGltYXRlcyBhcmlzZS4uLiBKb2huIEZveCdzIGB7Y2FyfWAgcGFja2FnZSBhbGxvd3MgdXMgdG8gY29tcHV0ZSB0aGUgKlZhcmlhbmNlIEluZmxhdGlvbiBGYWN0b3IqIHF1aXRlIGVhc2lseToNCg0KYGBge3IgZWNobyA9IFR9DQpWSUYgPC0gdmlmKHN0YWNrbG9zc01vZGVsKQ0KVklGDQpgYGANCg0KVGhlIFZhcmlhbmNlIEluZmxhdGlvbiBGYWN0b3IgKFZJRikgbWVhc3VyZXMgdGhlIGluY3JlYXNlIGluIHRoZSAqdmFyaWFuY2UqIG9mIGEgcmVncmVzc2lvbiBjb2VmZmljaWVudCBkdWUgdG8gY29saW5lYXJpdHkuIEl0J3Mgc3F1YXJlIHJvb3QgKGBzcXJ0KFZJRilgKSB0ZWxscyB1cyBob3cgbXVjaCBsYXJnZXIgYSBzdGFuZGFyZCBlcnJvciBvZiBhIHJlZ3Jlc3Npb24gY29lZmZpY2llbnQgaXMgY29tcGFyZWQgdG8gYSBoeXBvdGhldGljYWwgc2l0dWF0aW9uIHdoZXJlIHRoZXJlIHdlcmUgbm8gY29ycmVsYXRpb25zIHdpdGggYW55IG90aGVyIHByZWRpY3RvcnMgaW4gdGhlIG1vZGVsLiAqKk5PVEU6KiogVGhlIGxvd2VyIGJvdW5kIG9mIFZJRiBpcyAxOyB0aGVyZSBpcyBubyB1cHBlciBib3VuZCwgYnV0IFZJRiA+IDIgdHlwaWNhbGx5IGluZGljYXRlcyB0aGF0IG9uZSBzaG91bGQgYmUgY29uY2VybmVkLg0KDQpgYGB7ciBlY2hvID0gVH0NCnNxcnQoVklGKQ0KYGBgDQoNCiMjIyMgMy4zIFBhcnQgY29ycmVsYXRpb24gaW4gbXVsdGlwbGUgcmVncmVzc2lvbg0KDQpJbiBtdWx0aXBsZSByZWdyZXNzaW9uLCBpdCBpcyB0aGUgKipzZW1pLXBhcnRpYWwgKG9yIHBhcnQpKiogY29ycmVsYXRpb24gdGhhdCB5b3UgbmVlZCB0byBpbnNwZWN0OiANCg0KLSBhc3N1bWUgYSBtb2RlbCB3aXRoIGBYMWAsIGBYMmAsIGBYM2AgYXMgcHJlZGljdG9ycywgYW5kIGBZYCBhcyBhIGNyaXRlcmlvbjsgDQoNCi0geW91IG5lZWQgYSBzZW1pLXBhcnRpYWwgb2YgYFgxYCBhbmQgYFlgIGZvbGxvd2luZyB0aGUgcmVtb3ZhbCBvZiBgWDJgIGFuZCBgWDNgIGZyb20gYFlgOw0KDQotIGl0IGdvZXMgbGlrZSB0aGlzOiBpbiBTdGVwIDEsIHlvdSBwZXJmb3JtIGEgbXVsdGlwbGUgcmVncmVzc2lvbiBgWSB+IFgyICsgWDNgOw0KDQotIEluIFN0ZXAgMiwgeW91IHRha2UgdGhlIHJlc2lkdWFscyBvZiBgWWAsIGNhbGwgdGhlbSBgUllgOyANCg0KLSBpbiBTdGVwIDMsIHlvdSByZWdyZXNzIChjb3JyZWxhdGUpIGBSWSB+IFgxYDogdGhlIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50IHRoYXQgeW91IGdldCBmcm9tIFN0ZXAgMyBpcyB0aGUgcGFydCBjb3JyZWxhdGlvbiB0aGF0IHlvdSdyZSBsb29raW5nIGZvciENCg0KUmVjYWxsIG91ciBtb2RlbC4uLg0KDQpgYGB7ciBlY2hvID0gVH0NCnN0YWNrbG9zc01vZGVsID0gbG0oc3RhY2subG9zcyB+IEFpci5GbG93ICsgV2F0ZXIuVGVtcCArIEFjaWQuQ29uYy4sDQogICAgICAgICAgICAgICAgICAgIGRhdGEgPSBzdGFja2xvc3MpDQpzdW1tYXJ5KHN0YWNrbG9zc01vZGVsKQ0KYGBgDQoNCldoYXQgaXMgdGhlIHNlbWktcGFydGlhbCAocGFydCBjb3JyZWxhdGlvbikgb2YgYHN0YWNrLmxvc3NgIGFuZCBgQWlyLkZsb3dgPyBSZW1lbWJlciB0aGUge3Bjb3JyfSBwYWNrYWdlPyANCg0KYGBge3IgZWNobyA9IFR9DQpzcGFydENvcjEgPC0gc3Bjb3IudGVzdCh4ID0gc3RhY2tsb3NzJEFpci5GbG93LCANCiAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBzdGFja2xvc3Mkc3RhY2subG9zcywNCiAgICAgICAgICAgICAgICAgICAgICAgIHogPSBzdGFja2xvc3NbICwgYygiV2F0ZXIuVGVtcCIsICJBY2lkLkNvbmMuIildLA0KICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInBlYXJzb24iKQ0KcHJpbnQoc3BhcnRDb3IxKQ0KYGBgDQoNClRoZSB1bmlxdWUgY29udHJpYnV0aW9uIG9mIGBBaXIuRmxvd2AgaXMgdGhlbjoNCg0KYGBge3IgZWNobyA9IFR9DQpzcGFydENvcjEkZXN0aW1hdGUNCmBgYA0KDQpgYGB7ciBlY2hvID0gVH0NCnNwYXJ0Q29yMSRzdGF0aXN0aWMNCmBgYA0KDQpgYGB7ciBlY2hvID0gVH0NCnNwYXJ0Q29yMSRwLnZhbHVlDQpgYGANCg0KKioqDQoNCiMjIyBGdXJ0aGVyIFJlYWRpbmdzDQoNCisgW1JlZ3Jlc3Npb24sIGJ5IERhdmlkIE0uIExhbmVdKGh0dHA6Ly9vbmxpbmVzdGF0Ym9vay5jb20vMi9yZWdyZXNzaW9uL3JlZ3Jlc3Npb24uaHRtbCkNCisgW3ticm9vbX0gcGFja2FnZTogVmlnbmV0dGVdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9icm9vbS92aWduZXR0ZXMvYnJvb20uaHRtbCkNCg0KDQojIyMgUiBNYXJrZG93bg0KDQorIFtSIE1hcmtkb3duXShodHRwczovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbS8pIGlzIHdoYXQgSSBoYXZlIHVzZWQgdG8gcHJvZHVjZSB0aGlzIGJlYXV0aWZ1bCBOb3RlYm9vay4gV2Ugd2lsbCBsZWFybiBtb3JlIGFib3V0IGl0IG5lYXIgdGhlIGVuZCBvZiB0aGUgY291cnNlLCBidXQgaWYgeW91IGFscmVhZHkgZmVlbCByZWFkeSB0byBkaXZlIGRlZXAsIGhlcmUncyBhIGJvb2s6IFtSIE1hcmtkb3duOiBUaGUgRGVmaW5pdGl2ZSBHdWlkZSwgWWlodWkgWGllLCBKLiBKLiBBbGxhaXJlLCBHYXJyZXR0IEdyb2xlbXVuZHMuXShodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ybWFya2Rvd24vKSANCg0KKioqDQpHb3JhbiBTLiBNaWxvdmFub3ZpxIcNCg0KRGF0YUtvbGVrdGl2LCAyMDIwLzIxDQoNCmNvbnRhY3Q6IGdvcmFuLm1pbG92YW5vdmljQGRhdGFrb2xla3Rpdi5jb20NCg0KIVtdKC4uL19pbWcvREtfTG9nb18xMDAucG5nKQ0KDQoqKioNCkxpY2Vuc2U6IFtHUEx2M10oaHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzL2dwbC0zLjAudHh0KQ0KVGhpcyBOb3RlYm9vayBpcyBmcmVlIHNvZnR3YXJlOiB5b3UgY2FuIHJlZGlzdHJpYnV0ZSBpdCBhbmQvb3IgbW9kaWZ5IGl0IHVuZGVyIHRoZSB0ZXJtcyBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgYXMgcHVibGlzaGVkIGJ5IHRoZSBGcmVlIFNvZnR3YXJlIEZvdW5kYXRpb24sIGVpdGhlciB2ZXJzaW9uIDMgb2YgdGhlIExpY2Vuc2UsIG9yIChhdCB5b3VyIG9wdGlvbikgYW55IGxhdGVyIHZlcnNpb24uDQpUaGlzIE5vdGVib29rIGlzIGRpc3RyaWJ1dGVkIGluIHRoZSBob3BlIHRoYXQgaXQgd2lsbCBiZSB1c2VmdWwsIGJ1dCBXSVRIT1VUIEFOWSBXQVJSQU5UWTsgd2l0aG91dCBldmVuIHRoZSBpbXBsaWVkIHdhcnJhbnR5IG9mIE1FUkNIQU5UQUJJTElUWSBvciBGSVRORVNTIEZPUiBBIFBBUlRJQ1VMQVIgUFVSUE9TRS4gIFNlZSB0aGUgR05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgZm9yIG1vcmUgZGV0YWlscy4NCllvdSBzaG91bGQgaGF2ZSByZWNlaXZlZCBhIGNvcHkgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFsb25nIHdpdGggdGhpcyBOb3RlYm9vay4gSWYgbm90LCBzZWUgPGh0dHA6Ly93d3cuZ251Lm9yZy9saWNlbnNlcy8+Lg0KDQoqKioNCg0K