Set-up

Load the tidyverse:

library(tidyverse)

Acknowledgements

This notebook is based on Chapter 7 in R for Data Science (Grolemund and Wickham 2020). It does a nice treatment of the diamonds data set. So we will look at some other examples.

1 Model building

Models are about compressing information: take many data points and compress them into a summary. Throw out information to gain information!

The estimator (i.e. model) for the unconditional population mean is a form of compression:

\[ E[X] = \frac{1}{n} \sum_{i=1}^n x_i \]

Take \(n\) observations and compress them into one summarization of central tendency.

The same principle applies if we run a fancier estimator (e.g., regression, estimator for the conditional population mean). Models produce “insight” because they compress information.

1.1 Building up to building models

Before we can build models we first need to figure out what should go in them.

Say we have some outcome of interest \(y\) (e.g., number of website visitors). We think the number of unique visitors is a function of some variables:

\[ y = f(\cdot) \]

and if we new a) what those variables were and b) how they influence the outcome, we would have a good idea how to increase readership.

This is exploratory data analysis (EDA).

At a shallow level EDA boils down to calculating sumamry statistics and making plots.

But the bigger picture behind EDA is figuring out what goes inside \(f(\cdot)\) so you can build (and estimate the parameters of) a model.

1.2 Types of models

Remember there are roughly two types of models:

  • models that predict (“what” will people buy?")
  • models that infer (“why” will people buy it?")

Prediction is the domain of traditional machine learning. You don’t necessarily care about why – you just want to generate the most accurate prediction possible.

Inference is about hypothesis testing: the “how” or “why” some variables affect others.

In this notebook we will focus on inference. Specifically, we will use EDA to generate testable hypotheses.

2 Height

Let’s start with a trivial example: variation in height across males and females.

Re-load the nhanes survey data:

nhanes = read_csv("https://raw.githubusercontent.com/lrdegeest/r_for_data_science/main/data/nhanes.csv")

We have basic medical data of over 10,000 randomly surveyed individuals (the survey was carried out by the CDC). One of those variables is height.

How is height distributed in our sample? Let’s plot a histogram

ggplot(data = nhanes, aes(x = height)) + # fill this in!
  geom_histogram() 

Height is clearly normally distributed around it’s mean:

nhanes %>% 
  summarise(mean(height))

So if we were to randomly draw somebody from the sample using slice_sample (another of the slice family of operators):

# try playing this chunk many times!!
nhanes %>% # take the data
  slice_sample(n=1) %>% # draw one random person
  select(height) # view their height

more often than not we will draw somebody whose height is close to the mean height.

2.1 Height by sex

But what if we were to randomly draw two people: one male, one female? Play this chunk a few times:

# try playing this chunk many times!!
nhanes %>% # take the data
  group_by(sex) %>% # group observations by sex
  slice_sample(n=1) %>% # draw one random person (from each sex)
  select(height) # view their height

More often than not it seems like we are drawing a male taller than the female. This would suggest that men are systematically taller than women.

We could plot the distributions of height by sex (in one panel, using fill):

ggplot(data = nhanes, aes(x = height, fill = sex)) + 
  geom_histogram()

OK, looks like there is a clear difference in the distributions. But this plot covers up the right half of the female distribution. Can we clean it up?

Let’s use a 75% transparency (alpha = 0.75) and position the histograms “side-by-side” (position = "identity"):

ggplot(data = nhanes, aes(x = height, fill = sex)) + 
  geom_histogram(alpha = 0.75, position = "identity")

2.2 Generating a hypothesis

So yes, it looks like the distributions are different. We could calculate the averages by sex:

nhanes %>% 
  group_by(sex) %>% 
  summarise(mean(height))

and now think about a hypothesis: on average, men are taller than women – not just in this sample, but in the population from which this sample was drawn.

More specifically we can formulate a null and alternative hypothesis:

  • Null: no relationship between sex and height (hence “null”)
  • Alternative: some relationship between sex and height

We want to see if our data provides evidence to support the alternative hypothesis. This is inference in a nutshell.

2.3 Testing the hypothesis

We can test our hypothese with a model where height is a function of sex plus some random error (\(\epsilon\)):

\[ \text{height} = f(\text{sex}) + \epsilon \]

Now we have to choose the functional form of \(f()\). The simplest is a line:

\[ \begin{aligned} \text{height} &= f(\text{sex}) + \epsilon \\ &= \beta_0 + \beta_1(\text{sex}) + \epsilon \end{aligned} \] and use lm() (for “linear model”) to estimate the parameters \(\beta_0\) (the intercept) and more importantly \(\beta_1\) (the relationship between sex and height) and then summary() to view the results (coefficient estimates and inference):

lm(formula = height ~ sex, data = nhanes) %>% # estimate the model
  summary() # view the coefficients AND hypothesis tests

Call:
lm(formula = height ~ sex, data = nhanes)

Residuals:
     Min       1Q   Median       3Q      Max 
-29.9451  -4.4451  -0.0403   4.6559  27.3587 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 161.23933    0.09374 1719.99   <2e-16 ***
sexMale      13.50274    0.13604   99.25   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 6.912 on 10349 degrees of freedom
Multiple R-squared:  0.4877,    Adjusted R-squared:  0.4876 
F-statistic:  9851 on 1 and 10349 DF,  p-value: < 2.2e-16

Ample evidence to support the alternative hypothesis. On average, men are taller than women.

2.4 lm()

In another notebook we’ll talk more about linear models. For now, just note that lm() takes two arguments:

  • formula =: the model formula. y ~ x means \(y = f(x)\).
  • data =: the data that house the variables used in formula

3 Avocados

Let’s consider a more interesting example: demand for organic avocados.

Here is some data on avocados Kaggle thanks to the Hass Avocado Board:

avocados = read_csv("https://raw.githubusercontent.com/lrdegeest/r_for_data_science/main/data/avocados.csv")

3.1 Checkpoint

Plot a histogram of avocado prices (AveragePrice).

ggplot(data = avocados, aes(x = AveragePrice)) + 
  geom_histogram()

3.2 Checkpoint

Now plot a histogram of avocado prices by organice/not organic (type). Make sure to handle overlapping distributions.

ggplot(data = avocados, aes(x = AveragePrice, fill=type)) + 
  geom_histogram(alpha = 0.75, position = "identity")

3.3 Checkpoint

Use lm() and summary() to estimate a model of avocado prices as a linear function of avocado type

lm(formula = AveragePrice ~ type, data = avocados) %>% 
  summary()

Call:
lm(formula = AveragePrice ~ type, data = avocados)

Residuals:
     Min       1Q   Median       3Q      Max 
-1.21400 -0.20400 -0.02804  0.18600  1.59600 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 1.158040   0.003321   348.7   <2e-16 ***
typeorganic 0.495959   0.004697   105.6   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.3173 on 18247 degrees of freedom
Multiple R-squared:  0.3793,    Adjusted R-squared:  0.3792 
F-statistic: 1.115e+04 on 1 and 18247 DF,  p-value: < 2.2e-16

3.4 Demand shifts

OK, but does this price shift lead to a demand shift?

If so, we should the demand curve shift down for organic avocados.

So let’s look at sales of avocados and see if there is a relationship between sales and avocado type.

3.4.1 Renaming columns

Unfortunately some of the column names have spaces in them:

colnames(avocados)
 [1] "X1"           "Date"         "AveragePrice" "Total Volume" "4046"        
 [6] "4225"         "4770"         "Total Bags"   "Small Bags"   "Large Bags"  
[11] "XLarge Bags"  "type"         "year"         "region"      

including the column on sales, “Total Volume”. R will throw an error if we try to plot its distribution because it will think “Total” and “Volume” are separate objects:

ggplot(data = avocados, aes(x = Total Volume)) + 
Error: unexpected symbol in "ggplot(data = avocados, aes(x = Total Volume"

We can use rename() to rename that column:

avocados = avocados %>% # make sure you reassign the data!
  rename(TotalVolume = Total Volume)
Error: unexpected symbol in:
"avocados = avocados %>% # make sure you reassign the data!
  rename(TotalVolume = Total Volume"

Argh. Why won’t this work? Now you see why spaces are so annoying!

(You might say it’s the “pits”…https://tinyurl.com/yyawvnwn)

To “escape” the space we have to wrap the old name in quotes:

avocados = avocados %>% # make sure you reassign the data!
  rename(TotalVolume = "Total Volume")

3.5 Demand shifts (continued)

OK, now we’re in business:

ggplot(data = avocados, aes(x = TotalVolume)) + 
  geom_histogram()

Sales are highly skewed. That means the average is not representative. So if we build a model about the conditional average (e.g., average sales conditional on avocado type), it won’t be useful.

Fortunately we know that we can transform data onto a new scale without changing it’s underlying information (we saw this when we did z-scores).

A common method to transform a non-normal variable into a normal variable is the log transformation. (Just make sure you don’t have negative or zero values! To see why, try typing log(0) and log(-1) in your console.)

3.5.1 Checkpoint

Use mutate() and log() to create a new variable called “log_sales” that log transforms TotalVolume.

avocados = avocados %>% 
  mutate(log_sales = log(TotalVolume))

3.5.2 Checkpoint

Plot the distribution of log_sales:

ggplot(data = avocados, aes(x = log_sales)) + 
  geom_histogram()

(More) normally distributed!

3.5.3 Checkpoint

Make a scatter plot of log sales as a function of price. Use a transparency (try different values of alpha!) Hint: alpha can go inside geom_point(), just like we did with geom_histogram().

ggplot(data = avocados, aes(x = AveragePrice, y = log_sales)) + 
  geom_point(alpha = 0.20)

3.5.4 Checkpoint

Now re-do the plot and this time add geom_smooth(method = "lm"). This will add a regression line to the plot.

ggplot(data = avocados, aes(x = AveragePrice, y = log_sales)) + 
  geom_point(alpha = 0.20) + 
  geom_smooth(method = "lm")

3.5.5 Checkpoint

Estimate the parameters of that line with lm() and summary():

lm(formula = log_sales ~ AveragePrice, data = avocados) %>% 
  summary()

Call:
lm(formula = log_sales ~ AveragePrice, data = avocados)

Residuals:
    Min      1Q  Median      3Q     Max 
-6.2673 -1.2907  0.0552  1.1642  6.5046 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept)  15.98827    0.05008  319.23   <2e-16 ***
AveragePrice -3.32293    0.03425  -97.03   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.863 on 18247 degrees of freedom
Multiple R-squared:  0.3404,    Adjusted R-squared:  0.3403 
F-statistic:  9415 on 1 and 18247 DF,  p-value: < 2.2e-16

3.5.6 Checkpoint

Now, re-plot the scatterplot of log sales and avocado type with a regression line, but this time use facet_wrap() to make separate panels for conventional and organic avocados:

ggplot(data = avocados, aes(x = AveragePrice, y = log_sales)) + 
  geom_point(alpha = 0.20) +
  geom_smooth(method = "lm") + 
  facet_wrap(~type) 

3.6 Demand shifts: bringing it all together

Notice two key things in that last plot:

  1. the intercept of the regression line for organic avocados shifted down relative to the intercept for conventional avocados
  2. the slope of the regression lines look different; the relationship between price and sales varies across avocado types

Now we have a good idea of a model we can build that summarizes the demand for avocados. The model should:

  • allow the intercept to vary by avocado type, and;
  • allow the slope to vary by avocado type

Here is such a model that includes a dummy variable (to capture the changing intercept) and an interaction effect (to capture the changing slope):

\[ \text{log(sales)} = \beta_0 + \beta_1(\text{price}) + \beta_2(\text{organic}) + \beta_3(\text{price} \times \text{organic}) + \epsilon \]

Let’s estimate the parameters with lm():

lm(formula = log_sales ~ AveragePrice + type + AveragePrice*type, data = avocados) %>% 
  summary()

Call:
lm(formula = log_sales ~ AveragePrice + type + AveragePrice * 
    type, data = avocados)

Residuals:
    Min      1Q  Median      3Q     Max 
-5.0958 -0.9735 -0.1847  0.7429  4.8303 

Coefficients:
                         Estimate Std. Error t value Pr(>|t|)    
(Intercept)              14.49652    0.06420  225.81  < 2e-16 ***
AveragePrice             -1.16907    0.05406  -21.62  < 2e-16 ***
typeorganic              -3.87157    0.09226  -41.96  < 2e-16 ***
AveragePrice:typeorganic  0.48246    0.06673    7.23 5.03e-13 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.358 on 18245 degrees of freedom
Multiple R-squared:  0.6493,    Adjusted R-squared:  0.6492 
F-statistic: 1.126e+04 on 3 and 18245 DF,  p-value: < 2.2e-16

What does this model say?

  • The intercept for conventional avocados is 14.49652
  • The shift in the intercept from conventional to organic is -1.16907
  • The effect of price on sales (the slope) for coventional is -1.16907
  • The shift in the slope from conventional to organic is 0.48246

So it seems that people who buy organic avocados are less sensitive to changes in price!

Of course, take this analysis with a grain of salt.

There are probably several confounders to address, like variation in prices over time and across regions.

Also, who is buying these avocados? This might explain why we see a right shift in volume for conventional avos. Is it folks like us at the store? Restaurants buying in bulk? We don’t know.

4 Blood pressure

Let’s go back to nhanes and consider a more interesting example: variation in blood pressure over time and by sex.

When your heart beats, it creates pressure to circulated oxygenated blood throughout your body. That pressure has two parts, systolic and diastolic.

That is why when they take your blood pressure they say “120 over 80” (units of millimeters of mercury, or mmHg), meaning 120 systolic over 80 diastolic. 120/80 is considered a normal blood pressure. Anything above 130 systolic is considered high blood pressure.

The harder your blood travels the more it deteriorates your cardiovascular system. This excess force is hypertension or high blood pressure. Over time hypertension can lead to a number of health issues.

In nhanes the variable bpsystol captures systolic blood pressure.

4.1 Checkpoint

Calculate avereage systolic blood pressure by sex:

nhanes %>% 
  group_by(sex) %>% 
  summarise(mean(bpsystol))

4.2 Checkpoint

Plot the distribution of systolic blood pressure by sex:

ggplot(data = nhanes, aes(x = bpsystol, fill = sex)) + 
  geom_histogram(alpha = 0.75, position = "identity")

4.3 Blood pressure over time

It doesn’t look like there is much a of difference in the unconditional average blood pressure between men and women.

But what if we need to look at the conditional average? For instance, what if blood pressure varies over time (age)?

4.3.1 Checkpoint

First plot the bpystol as a function of age. Include a regression line.

ggplot(data = nhanes, aes(x = age, y = bpsystol)) + 
  geom_point() + 
  geom_smooth(method = "lm")

4.3.2 Checkpoint

That plot is a bit messy. Let’s simplify it. Now plot average blood pressure by age. Include a regression line. To do this, first use summarise and create a variable called “mean_bpsystol” by age, then pipe (%>%) the output to ggplot. (You might find it helpful to first build the summary table). Don’t include a regression line.

nhanes %>% 
  group_by(age) %>% 
  summarise(mean_bpsystol = mean(bpsystol)) %>% 
  ggplot(., aes(age, mean_bpsystol)) + 
  geom_point() + 
  geom_line() 

4.3.3 Checkpoint

Does this relationship vary by sex?

Re-do that plot, but this time by age and sex. Instead of fill= use color= to color the plot by sex:

nhanes %>% 
  group_by(age, sex) %>% 
  summarise(mean_bpsystol = mean(bpsystol)) %>% 
  ggplot(., aes(age, mean_bpsystol, color = sex)) + 
  geom_point() + 
  geom_line() 

4.4 Modeling the relationship

When we plot average systolic blood pressure by age for men and women, we see an upward trend in both sexes, but not necessarily the same trend. The lines do not look parallel.

Another way to think about is that the slope of the lines for age are different for men and women. In the graph it looks like the effect of age is more pronounced – the slope is steeper – among women.

Based on our exploratory analysis we might consider the following model:

\[ \text{blood pressure} = \beta_0 + \beta_1 \text{age} + \beta_2 \text{sex} + \beta_3 (\text{sex} \times \text{age}) + \epsilon \]

4.4.1 Checkpoint

Modify the model we estimated for avocado sales to estimate the model above. Recall that inside lm() we can interact two variables x1 and x2 by typeing x1*x2.

lm(formula = bpsystol ~ sex + age + sex*age, data = nhanes) %>% 
  summary()

Call:
lm(formula = bpsystol ~ sex + age + sex * age, data = nhanes)

Residuals:
    Min      1Q  Median      3Q     Max 
-65.990 -13.214  -2.101  10.606 156.826 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 90.11098    0.80336  112.17   <2e-16 ***
sexMale     20.45813    1.16526   17.56   <2e-16 ***
age          0.81635    0.01583   51.56   <2e-16 ***
sexMale:age -0.34573    0.02304  -15.01   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 20.14 on 10347 degrees of freedom
Multiple R-squared:  0.2551,    Adjusted R-squared:  0.2548 
F-statistic:  1181 on 3 and 10347 DF,  p-value: < 2.2e-16

Interestingly, the effect of age on blood pressure among men, sexMale:age, is negative.

Meaning that as men age, their blood pressure goes up (because the average effect of age is positive), but slower than for women (because the interaction effect is negative).

The general takeaway is that a patient should be compared against the right benchmark. For instance, we can’t just compare a male patient’s blood pressure against the overall average. They need to be compared to the average male at the same age.

5 Big picture

Don’t worry if you haven’t followed every step, especially the stuff with lm(). We’ll do more of that later.

For now, just notice how we were able to use summary statistics and plots to generate hypotheses, build models, and then estimate those models to compress the data and create insights.

LS0tCnRpdGxlOiAiRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyAoQ29tcGxldGVkIE5vdGVib29rKSIKc3VidGl0bGU6ICJSIGZvciBEYXRhIFNjaWVuY2UiCmF1dGhvcjogIkxERyIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICB0aGVtZTogcmVhZGFibGUKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiAKICAgICAgY29sbGFwc2VkOiB5ZXMgICAgICAKLS0tCgojIFNldC11cCB7LX0KICAKTG9hZCB0aGUgYHRpZHl2ZXJzZWA6CgpgYGB7ciBsb2FkIHRpZHl2ZXJzZSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKIyMgQWNrbm93bGVkZ2VtZW50cyB7LX0KClRoaXMgbm90ZWJvb2sgaXMgYmFzZWQgb24gQ2hhcHRlciA3IGluICpSIGZvciBEYXRhIFNjaWVuY2UqIChHcm9sZW11bmQgYW5kIFdpY2toYW0gMjAyMCkuIEl0IGRvZXMgYSBuaWNlIHRyZWF0bWVudCBvZiB0aGUgYGRpYW1vbmRzYCBkYXRhIHNldC4gU28gd2Ugd2lsbCBsb29rIGF0IHNvbWUgb3RoZXIgZXhhbXBsZXMuCgojIE1vZGVsIGJ1aWxkaW5nCgpNb2RlbHMgYXJlIGFib3V0ICoqY29tcHJlc3NpbmcqKiBpbmZvcm1hdGlvbjogdGFrZSBtYW55IGRhdGEgcG9pbnRzIGFuZCBjb21wcmVzcyB0aGVtIGludG8gYSBzdW1tYXJ5LiBUaHJvdyBvdXQgaW5mb3JtYXRpb24gdG8gZ2FpbiBpbmZvcm1hdGlvbiEKClRoZSBlc3RpbWF0b3IgKGkuZS4gbW9kZWwpIGZvciB0aGUgKip1bmNvbmRpdGlvbmFsKiogcG9wdWxhdGlvbiBtZWFuIGlzIGEgZm9ybSBvZiBjb21wcmVzc2lvbjoKCiQkCkVbWF0gPSBcZnJhY3sxfXtufSBcc3VtX3tpPTF9Xm4geF9pCiQkCgpUYWtlICRuJCBvYnNlcnZhdGlvbnMgYW5kIGNvbXByZXNzIHRoZW0gaW50byBvbmUgc3VtbWFyaXphdGlvbiBvZiBjZW50cmFsIHRlbmRlbmN5LiAKClRoZSBzYW1lIHByaW5jaXBsZSBhcHBsaWVzIGlmIHdlIHJ1biBhIGZhbmNpZXIgZXN0aW1hdG9yIChlLmcuLCByZWdyZXNzaW9uLCBlc3RpbWF0b3IgZm9yIHRoZSAqKmNvbmRpdGlvbmFsKiogcG9wdWxhdGlvbiBtZWFuKS4gTW9kZWxzIHByb2R1Y2UgImluc2lnaHQiIGJlY2F1c2UgdGhleSBjb21wcmVzcyBpbmZvcm1hdGlvbi4gCgojIyBCdWlsZGluZyB1cCB0byBidWlsZGluZyBtb2RlbHMKCkJlZm9yZSB3ZSBjYW4gYnVpbGQgbW9kZWxzIHdlIGZpcnN0IG5lZWQgdG8gZmlndXJlIG91dCB3aGF0IHNob3VsZCBnbyBpbiB0aGVtLgoKU2F5IHdlIGhhdmUgc29tZSBvdXRjb21lIG9mIGludGVyZXN0ICR5JCAoZS5nLiwgbnVtYmVyIG9mIHdlYnNpdGUgdmlzaXRvcnMpLiBXZSB0aGluayB0aGUgbnVtYmVyIG9mIHVuaXF1ZSB2aXNpdG9ycyBpcyBhICoqZnVuY3Rpb24gb2YqKiBzb21lIHZhcmlhYmxlczoKCiQkCnkgPSBmKFxjZG90KQokJAoKYW5kIGlmIHdlIG5ldyBhKSB3aGF0IHRob3NlIHZhcmlhYmxlcyB3ZXJlIGFuZCBiKSAqKmhvdyoqIHRoZXkgaW5mbHVlbmNlIHRoZSBvdXRjb21lLCB3ZSB3b3VsZCBoYXZlIGEgZ29vZCBpZGVhIGhvdyB0byBpbmNyZWFzZSByZWFkZXJzaGlwLiAKClRoaXMgaXMgKipleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIChFREEpLioqIAoKQXQgYSBzaGFsbG93IGxldmVsIEVEQSBib2lscyBkb3duIHRvIGNhbGN1bGF0aW5nIHN1bWFtcnkgc3RhdGlzdGljcyBhbmQgbWFraW5nIHBsb3RzLgoKQnV0IHRoZSBiaWdnZXIgcGljdHVyZSBiZWhpbmQgRURBIGlzIGZpZ3VyaW5nIG91dCB3aGF0IGdvZXMgaW5zaWRlICRmKFxjZG90KSQgc28geW91IGNhbiBidWlsZCAoYW5kIGVzdGltYXRlIHRoZSBwYXJhbWV0ZXJzIG9mKSBhIG1vZGVsLgoKIyMgVHlwZXMgb2YgbW9kZWxzCgpSZW1lbWJlciB0aGVyZSBhcmUgcm91Z2hseSB0d28gdHlwZXMgb2YgbW9kZWxzOgoKKiBtb2RlbHMgdGhhdCAqKnByZWRpY3QqKiAoIip3aGF0KiIgd2lsbCBwZW9wbGUgYnV5PyIpCiogbW9kZWxzIHRoYXQgKippbmZlcioqICgiKndoeSoiIHdpbGwgcGVvcGxlIGJ1eSBpdD8iKQoKKipQcmVkaWN0aW9uKiogaXMgdGhlIGRvbWFpbiBvZiB0cmFkaXRpb25hbCBtYWNoaW5lIGxlYXJuaW5nLiBZb3UgZG9uJ3QgbmVjZXNzYXJpbHkgY2FyZSBhYm91dCAqKndoeSoqIC0tIHlvdSBqdXN0IHdhbnQgdG8gZ2VuZXJhdGUgdGhlIG1vc3QgYWNjdXJhdGUgcHJlZGljdGlvbiBwb3NzaWJsZS4gCgoqKkluZmVyZW5jZSoqIGlzIGFib3V0ICoqaHlwb3RoZXNpcyB0ZXN0aW5nKio6IHRoZSAiaG93IiBvciAid2h5IiBzb21lIHZhcmlhYmxlcyBhZmZlY3Qgb3RoZXJzLiAKCkluIHRoaXMgbm90ZWJvb2sgd2Ugd2lsbCBmb2N1cyBvbiBpbmZlcmVuY2UuIFNwZWNpZmljYWxseSwgd2Ugd2lsbCB1c2UgRURBIHRvIGdlbmVyYXRlIHRlc3RhYmxlIGh5cG90aGVzZXMuCgojIEhlaWdodAoKTGV0J3Mgc3RhcnQgd2l0aCBhIHRyaXZpYWwgZXhhbXBsZTogdmFyaWF0aW9uIGluIGhlaWdodCBhY3Jvc3MgbWFsZXMgYW5kIGZlbWFsZXMuIAoKUmUtbG9hZCB0aGUgYG5oYW5lc2Agc3VydmV5IGRhdGE6CgpgYGB7ciBsb2FkIG5oYW5lcywgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlPUZBTFNFfQpuaGFuZXMgPSByZWFkX2NzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2xyZGVnZWVzdC9yX2Zvcl9kYXRhX3NjaWVuY2UvbWFpbi9kYXRhL25oYW5lcy5jc3YiKQpgYGAKCldlIGhhdmUgYmFzaWMgbWVkaWNhbCBkYXRhIG9mIG92ZXIgMTAsMDAwIHJhbmRvbWx5IHN1cnZleWVkIGluZGl2aWR1YWxzICh0aGUgc3VydmV5IHdhcyBjYXJyaWVkIG91dCBieSB0aGUgQ0RDKS4gT25lIG9mIHRob3NlIHZhcmlhYmxlcyBpcyBgaGVpZ2h0YC4gCgpIb3cgaXMgaGVpZ2h0IGRpc3RyaWJ1dGVkIGluIG91ciBzYW1wbGU/IExldCdzIHBsb3QgYSBoaXN0b2dyYW0KCmBgYHtyIGhpc3RvZ3JhbSBoZWlnaHR9CmdncGxvdChkYXRhID0gbmhhbmVzLCBhZXMoeCA9IGhlaWdodCkpICsgIyBmaWxsIHRoaXMgaW4hCiAgZ2VvbV9oaXN0b2dyYW0oKSAKYGBgCgpIZWlnaHQgaXMgY2xlYXJseSBub3JtYWxseSBkaXN0cmlidXRlZCBhcm91bmQgaXQncyBtZWFuOiAKCmBgYHtyIG1lYW4gaGVpZ2h0fQpuaGFuZXMgJT4lIAogIHN1bW1hcmlzZShtZWFuKGhlaWdodCkpCmBgYAoKClNvIGlmIHdlIHdlcmUgdG8gcmFuZG9tbHkgZHJhdyBzb21lYm9keSBmcm9tIHRoZSBzYW1wbGUgdXNpbmcgYHNsaWNlX3NhbXBsZWAgKGFub3RoZXIgb2YgdGhlIGBzbGljZWAgZmFtaWx5IG9mIG9wZXJhdG9ycyk6CgpgYGB7ciBzbGljZV9zYW1wbGV9CiMgdHJ5IHBsYXlpbmcgdGhpcyBjaHVuayBtYW55IHRpbWVzISEKbmhhbmVzICU+JSAjIHRha2UgdGhlIGRhdGEKICBzbGljZV9zYW1wbGUobj0xKSAlPiUgIyBkcmF3IG9uZSByYW5kb20gcGVyc29uCiAgc2VsZWN0KGhlaWdodCkgIyB2aWV3IHRoZWlyIGhlaWdodApgYGAKCm1vcmUgb2Z0ZW4gdGhhbiBub3Qgd2Ugd2lsbCBkcmF3IHNvbWVib2R5IHdob3NlIGhlaWdodCBpcyBjbG9zZSB0byB0aGUgbWVhbiBoZWlnaHQuIAoKIyMgSGVpZ2h0IGJ5IHNleAoKQnV0IHdoYXQgaWYgd2Ugd2VyZSB0byByYW5kb21seSBkcmF3IHR3byBwZW9wbGU6IG9uZSBtYWxlLCBvbmUgZmVtYWxlPyBQbGF5IHRoaXMgY2h1bmsgYSBmZXcgdGltZXM6CgpgYGB7ciBzbGljZV9zYW1wbGUgYnkgc2V4LCBtZXNzYWdlPUZBTFNFfQojIHRyeSBwbGF5aW5nIHRoaXMgY2h1bmsgbWFueSB0aW1lcyEhCm5oYW5lcyAlPiUgIyB0YWtlIHRoZSBkYXRhCiAgZ3JvdXBfYnkoc2V4KSAlPiUgIyBncm91cCBvYnNlcnZhdGlvbnMgYnkgc2V4CiAgc2xpY2Vfc2FtcGxlKG49MSkgJT4lICMgZHJhdyBvbmUgcmFuZG9tIHBlcnNvbiAoZnJvbSBlYWNoIHNleCkKICBzZWxlY3QoaGVpZ2h0KSAjIHZpZXcgdGhlaXIgaGVpZ2h0CmBgYAoKTW9yZSBvZnRlbiB0aGFuIG5vdCBpdCBzZWVtcyBsaWtlIHdlIGFyZSBkcmF3aW5nIGEgbWFsZSB0YWxsZXIgdGhhbiB0aGUgZmVtYWxlLiBUaGlzIHdvdWxkIHN1Z2dlc3QgdGhhdCBtZW4gYXJlICoqc3lzdGVtYXRpY2FsbHkqKiB0YWxsZXIgdGhhbiB3b21lbi4gCgpXZSBjb3VsZCBwbG90IHRoZSBkaXN0cmlidXRpb25zIG9mIGhlaWdodCBieSBzZXggKGluIG9uZSBwYW5lbCwgdXNpbmcgYGZpbGxgKToKCmBgYHtyIGhpc3RvZ3JhbSBoZWlnaHQgYnkgc2V4fQpnZ3Bsb3QoZGF0YSA9IG5oYW5lcywgYWVzKHggPSBoZWlnaHQsIGZpbGwgPSBzZXgpKSArIAogIGdlb21faGlzdG9ncmFtKCkKYGBgCgpPSywgbG9va3MgbGlrZSB0aGVyZSBpcyBhIGNsZWFyIGRpZmZlcmVuY2UgaW4gdGhlIGRpc3RyaWJ1dGlvbnMuIEJ1dCB0aGlzIHBsb3QgY292ZXJzIHVwIHRoZSByaWdodCBoYWxmIG9mIHRoZSBmZW1hbGUgZGlzdHJpYnV0aW9uLiBDYW4gd2UgY2xlYW4gaXQgdXA/IAoKTGV0J3MgdXNlIGEgNzUlIHRyYW5zcGFyZW5jeSAoYGFscGhhID0gMC43NWApIGFuZCBwb3NpdGlvbiB0aGUgaGlzdG9ncmFtcyAic2lkZS1ieS1zaWRlIiAoYHBvc2l0aW9uID0gImlkZW50aXR5ImApOgoKYGBge3IgYmV0dGVyIGhpc3RvZ3JhbSBoZWlnaHQgYnkgc2V4fQpnZ3Bsb3QoZGF0YSA9IG5oYW5lcywgYWVzKHggPSBoZWlnaHQsIGZpbGwgPSBzZXgpKSArIAogIGdlb21faGlzdG9ncmFtKGFscGhhID0gMC43NSwgcG9zaXRpb24gPSAiaWRlbnRpdHkiKQpgYGAKCiMjIEdlbmVyYXRpbmcgYSBoeXBvdGhlc2lzCgpTbyB5ZXMsIGl0IGxvb2tzIGxpa2UgdGhlIGRpc3RyaWJ1dGlvbnMgYXJlIGRpZmZlcmVudC4gV2UgY291bGQgY2FsY3VsYXRlIHRoZSBhdmVyYWdlcyBieSBzZXg6CgpgYGB7ciBhdmVyYWdlIGhlaWdodCBieSBzZXgsIG1lc3NhZ2U9RkFMU0V9Cm5oYW5lcyAlPiUgCiAgZ3JvdXBfYnkoc2V4KSAlPiUgCiAgc3VtbWFyaXNlKG1lYW4oaGVpZ2h0KSkKYGBgCgphbmQgbm93IHRoaW5rIGFib3V0IGEgaHlwb3RoZXNpczogb24gYXZlcmFnZSwgbWVuIGFyZSB0YWxsZXIgdGhhbiB3b21lbiAtLSBub3QganVzdCBpbiB0aGlzIHNhbXBsZSwgYnV0IGluIHRoZSAqKnBvcHVsYXRpb24gZnJvbSB3aGljaCB0aGlzIHNhbXBsZSB3YXMgZHJhd24uKiogCgpNb3JlIHNwZWNpZmljYWxseSB3ZSBjYW4gZm9ybXVsYXRlIGEgKipudWxsKiogYW5kICoqYWx0ZXJuYXRpdmUqKiBoeXBvdGhlc2lzOgoKKiBOdWxsOiBubyByZWxhdGlvbnNoaXAgYmV0d2VlbiBzZXggYW5kIGhlaWdodCAoaGVuY2UgIm51bGwiKQoqIEFsdGVybmF0aXZlOiBzb21lIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHNleCBhbmQgaGVpZ2h0IAoKV2Ugd2FudCB0byBzZWUgaWYgb3VyIGRhdGEgcHJvdmlkZXMgZXZpZGVuY2UgdG8gc3VwcG9ydCB0aGUgYWx0ZXJuYXRpdmUgaHlwb3RoZXNpcy4gVGhpcyBpcyAqKmluZmVyZW5jZSoqIGluIGEgbnV0c2hlbGwuCgojIyBUZXN0aW5nIHRoZSBoeXBvdGhlc2lzCgpXZSBjYW4gdGVzdCBvdXIgaHlwb3RoZXNlIHdpdGggYSBtb2RlbCB3aGVyZSBoZWlnaHQgaXMgYSBmdW5jdGlvbiBvZiBzZXggcGx1cyBzb21lIHJhbmRvbSBlcnJvciAoJFxlcHNpbG9uJCk6CgokJApcdGV4dHtoZWlnaHR9ID0gZihcdGV4dHtzZXh9KSArIFxlcHNpbG9uIAokJAoKTm93IHdlIGhhdmUgdG8gY2hvb3NlIHRoZSAqKmZ1bmN0aW9uYWwgZm9ybSoqIG9mICRmKCkkLiBUaGUgc2ltcGxlc3QgaXMgYSBsaW5lOgoKJCQKXGJlZ2lue2FsaWduZWR9Clx0ZXh0e2hlaWdodH0gICAmPSBmKFx0ZXh0e3NleH0pICsgXGVwc2lsb24gXFwKICAgICAgICAgICAgICAgICY9IFxiZXRhXzAgKyBcYmV0YV8xKFx0ZXh0e3NleH0pICsgXGVwc2lsb24gClxlbmR7YWxpZ25lZH0KJCQKYW5kIHVzZSBgbG0oKWAgKGZvciAibGluZWFyIG1vZGVsIikgdG8gZXN0aW1hdGUgdGhlIHBhcmFtZXRlcnMgJFxiZXRhXzAkICh0aGUgaW50ZXJjZXB0KSBhbmQgbW9yZSBpbXBvcnRhbnRseSAkXGJldGFfMSQgKHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBzZXggYW5kIGhlaWdodCkgYW5kIHRoZW4gYHN1bW1hcnkoKWAgdG8gdmlldyB0aGUgcmVzdWx0cyAoY29lZmZpY2llbnQgZXN0aW1hdGVzIGFuZCBpbmZlcmVuY2UpOgoKYGBge3J9CmxtKGZvcm11bGEgPSBoZWlnaHQgfiBzZXgsIGRhdGEgPSBuaGFuZXMpICU+JSAjIGVzdGltYXRlIHRoZSBtb2RlbAogIHN1bW1hcnkoKSAjIHZpZXcgdGhlIGNvZWZmaWNpZW50cyBBTkQgaHlwb3RoZXNpcyB0ZXN0cwpgYGAKCkFtcGxlIGV2aWRlbmNlIHRvIHN1cHBvcnQgdGhlIGFsdGVybmF0aXZlIGh5cG90aGVzaXMuIE9uIGF2ZXJhZ2UsIG1lbiBhcmUgdGFsbGVyIHRoYW4gd29tZW4uCgojIyBgbG0oKWAKCkluIGFub3RoZXIgbm90ZWJvb2sgd2UnbGwgdGFsayBtb3JlIGFib3V0IGxpbmVhciBtb2RlbHMuIEZvciBub3csIGp1c3Qgbm90ZSB0aGF0IGBsbSgpYCB0YWtlcyB0d28gYXJndW1lbnRzOgoKKiBgZm9ybXVsYSA9IGA6IHRoZSBtb2RlbCBmb3JtdWxhLiBgeSB+IHhgIG1lYW5zICR5ID0gZih4KSQuCiogYGRhdGEgPSBgOiB0aGUgZGF0YSB0aGF0IGhvdXNlIHRoZSB2YXJpYWJsZXMgdXNlZCBpbiBgZm9ybXVsYWAKCiMgQXZvY2Fkb3MKCkxldCdzIGNvbnNpZGVyIGEgbW9yZSBpbnRlcmVzdGluZyBleGFtcGxlOiBkZW1hbmQgZm9yIG9yZ2FuaWMgYXZvY2Fkb3MuCgpIZXJlIGlzIHNvbWUgZGF0YSBvbiBhdm9jYWRvcyBbS2FnZ2xlXShodHRwczovL3d3dy5rYWdnbGUuY29tL25ldXJvbXVzaWMvYXZvY2Fkby1wcmljZXMvKSB0aGFua3MgdG8gdGhlIFtIYXNzIEF2b2NhZG8gQm9hcmRdKGh0dHBzOi8vaGFzc2F2b2NhZG9ib2FyZC5jb20vKToKCmBgYHtyIGxvYWQgYXZvY2Fkb3MsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmF2b2NhZG9zID0gcmVhZF9jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9scmRlZ2Vlc3Qvcl9mb3JfZGF0YV9zY2llbmNlL21haW4vZGF0YS9hdm9jYWRvcy5jc3YiKQpgYGAKCiMjIENoZWNrcG9pbnQgCgpQbG90IGEgaGlzdG9ncmFtIG9mIGF2b2NhZG8gcHJpY2VzIChgQXZlcmFnZVByaWNlYCkuCgpgYGB7ciBjaGVja3BvaW50IGF2b2NhZG8gaGlzdG9ncmFtc30KZ2dwbG90KGRhdGEgPSBhdm9jYWRvcywgYWVzKHggPSBBdmVyYWdlUHJpY2UpKSArIAogIGdlb21faGlzdG9ncmFtKCkKYGBgCgojIyBDaGVja3BvaW50IAoKTm93IHBsb3QgYSBoaXN0b2dyYW0gb2YgYXZvY2FkbyBwcmljZXMgYnkgb3JnYW5pY2Uvbm90IG9yZ2FuaWMgKGB0eXBlYCkuIE1ha2Ugc3VyZSB0byBoYW5kbGUgb3ZlcmxhcHBpbmcgZGlzdHJpYnV0aW9ucy4KCmBgYHtyIGNoZWNrcG9pbnQgYXZvY2FkbyBoaXN0b2dyYW1zIGJ5IHR5cGV9CmdncGxvdChkYXRhID0gYXZvY2Fkb3MsIGFlcyh4ID0gQXZlcmFnZVByaWNlLCBmaWxsPXR5cGUpKSArIAogIGdlb21faGlzdG9ncmFtKGFscGhhID0gMC43NSwgcG9zaXRpb24gPSAiaWRlbnRpdHkiKQpgYGAKCiMjIENoZWNrcG9pbnQKClVzZSBgbG0oKWAgYW5kIGBzdW1tYXJ5KClgIHRvIGVzdGltYXRlIGEgbW9kZWwgb2YgYXZvY2FkbyBwcmljZXMgYXMgYSBsaW5lYXIgZnVuY3Rpb24gb2YgYXZvY2FkbyB0eXBlCgpgYGB7ciBjaGVja3BvaW50IGxtIGF2b2NhZG8gdHlwZX0KbG0oZm9ybXVsYSA9IEF2ZXJhZ2VQcmljZSB+IHR5cGUsIGRhdGEgPSBhdm9jYWRvcykgJT4lIAogIHN1bW1hcnkoKQpgYGAKCiMjIERlbWFuZCBzaGlmdHMKCk9LLCBidXQgZG9lcyB0aGlzIHByaWNlIHNoaWZ0IGxlYWQgdG8gYSBkZW1hbmQgc2hpZnQ/CgpJZiBzbywgd2Ugc2hvdWxkIHRoZSBkZW1hbmQgY3VydmUgc2hpZnQgZG93biBmb3Igb3JnYW5pYyBhdm9jYWRvcy4gCgpTbyBsZXQncyBsb29rIGF0IHNhbGVzIG9mIGF2b2NhZG9zIGFuZCBzZWUgaWYgdGhlcmUgaXMgYSByZWxhdGlvbnNoaXAgYmV0d2VlbiBzYWxlcyBhbmQgYXZvY2FkbyB0eXBlLiAKCiMjIyBSZW5hbWluZyBjb2x1bW5zCgpVbmZvcnR1bmF0ZWx5IHNvbWUgb2YgdGhlIGNvbHVtbiBuYW1lcyBoYXZlIHNwYWNlcyBpbiB0aGVtOgoKYGBge3IgY29sbmFtZXN9CmNvbG5hbWVzKGF2b2NhZG9zKQpgYGAKCmluY2x1ZGluZyB0aGUgY29sdW1uIG9uIHNhbGVzLCAiVG90YWwgVm9sdW1lIi4gUiB3aWxsIHRocm93IGFuIGVycm9yIGlmIHdlIHRyeSB0byBwbG90IGl0cyBkaXN0cmlidXRpb24gYmVjYXVzZSBpdCB3aWxsIHRoaW5rICJUb3RhbCIgYW5kICJWb2x1bWUiIGFyZSBzZXBhcmF0ZSBvYmplY3RzOgoKYGBge3IgaGlzdG9ncmFtIGJhZCBjb2x1bW4gbmFtZX0KZ2dwbG90KGRhdGEgPSBhdm9jYWRvcywgYWVzKHggPSBUb3RhbCBWb2x1bWUpKSArIAogIGdlb21faGlzdG9ncmFtKCkKYGBgCgpXZSBjYW4gdXNlIGByZW5hbWUoKWAgdG8gcmVuYW1lIHRoYXQgY29sdW1uOgoKYGBge3IgcmVuYW1lIHdyb25nfQphdm9jYWRvcyA9IGF2b2NhZG9zICU+JSAjIG1ha2Ugc3VyZSB5b3UgcmVhc3NpZ24gdGhlIGRhdGEhCiAgcmVuYW1lKFRvdGFsVm9sdW1lID0gVG90YWwgVm9sdW1lKQpgYGAKCkFyZ2guIFdoeSB3b24ndCB0aGlzIHdvcms/IE5vdyB5b3Ugc2VlIHdoeSBzcGFjZXMgYXJlIHNvIGFubm95aW5nIQoKKFlvdSBtaWdodCBzYXkgaXQncyB0aGUgInBpdHMiLi4uaHR0cHM6Ly90aW55dXJsLmNvbS95eWF3dm53bikKClRvICJlc2NhcGUiIHRoZSBzcGFjZSB3ZSBoYXZlIHRvIHdyYXAgdGhlIG9sZCBuYW1lIGluIHF1b3RlczoKCmBgYHtyIHJlbmFtZSByaWdodH0KYXZvY2Fkb3MgPSBhdm9jYWRvcyAlPiUgIyBtYWtlIHN1cmUgeW91IHJlYXNzaWduIHRoZSBkYXRhIQogIHJlbmFtZShUb3RhbFZvbHVtZSA9ICJUb3RhbCBWb2x1bWUiKQpgYGAKCiMjIERlbWFuZCBzaGlmdHMgKGNvbnRpbnVlZCkKCk9LLCBub3cgd2UncmUgaW4gYnVzaW5lc3M6IAoKYGBge3IgaGlzdG9ncmFtIHNhbGVzfQpnZ3Bsb3QoZGF0YSA9IGF2b2NhZG9zLCBhZXMoeCA9IFRvdGFsVm9sdW1lKSkgKyAKICBnZW9tX2hpc3RvZ3JhbSgpCmBgYAoKU2FsZXMgYXJlIGhpZ2hseSBza2V3ZWQuIFRoYXQgbWVhbnMgdGhlIGF2ZXJhZ2UgaXMgbm90IHJlcHJlc2VudGF0aXZlLiBTbyBpZiB3ZSBidWlsZCBhIG1vZGVsIGFib3V0IHRoZSAqKmNvbmRpdGlvbmFsKiogYXZlcmFnZSAoZS5nLiwgYXZlcmFnZSBzYWxlcyBjb25kaXRpb25hbCBvbiBhdm9jYWRvIHR5cGUpLCBpdCB3b24ndCBiZSB1c2VmdWwuIAoKRm9ydHVuYXRlbHkgd2Uga25vdyB0aGF0IHdlIGNhbiAqKnRyYW5zZm9ybSoqIGRhdGEgb250byBhIG5ldyBzY2FsZSB3aXRob3V0IGNoYW5naW5nIGl0J3MgdW5kZXJseWluZyBpbmZvcm1hdGlvbiAod2Ugc2F3IHRoaXMgd2hlbiB3ZSBkaWQgei1zY29yZXMpLgoKQSBjb21tb24gbWV0aG9kIHRvIHRyYW5zZm9ybSBhIG5vbi1ub3JtYWwgdmFyaWFibGUgaW50byBhIG5vcm1hbCB2YXJpYWJsZSBpcyB0aGUgKipsb2cgdHJhbnNmb3JtYXRpb24uKiogKEp1c3QgbWFrZSBzdXJlIHlvdSBkb24ndCBoYXZlIG5lZ2F0aXZlIG9yIHplcm8gdmFsdWVzISBUbyBzZWUgd2h5LCB0cnkgdHlwaW5nIGBsb2coMClgIGFuZCBgbG9nKC0xKWAgaW4geW91ciBjb25zb2xlLikKCiMjIyBDaGVja3BvaW50CgpVc2UgYG11dGF0ZSgpYCBhbmQgYGxvZygpYCB0byBjcmVhdGUgYSBuZXcgdmFyaWFibGUgY2FsbGVkICJsb2dfc2FsZXMiIHRoYXQgbG9nIHRyYW5zZm9ybXMgYFRvdGFsVm9sdW1lYC4KCmBgYHtyIGNoZWNrcG9pbnQgbG9nIHRyYW5zZm9ybX0KYXZvY2Fkb3MgPSBhdm9jYWRvcyAlPiUgCiAgbXV0YXRlKGxvZ19zYWxlcyA9IGxvZyhUb3RhbFZvbHVtZSkpCmBgYAoKIyMjIENoZWNrcG9pbnQKClBsb3QgdGhlIGRpc3RyaWJ1dGlvbiBvZiBgbG9nX3NhbGVzYDoKCmBgYHtyIGNoZWNrcG9pbnQgbG9nIHNhbGVzIGRpc3RyaWJ1dGlvbn0KZ2dwbG90KGRhdGEgPSBhdm9jYWRvcywgYWVzKHggPSBsb2dfc2FsZXMpKSArIAogIGdlb21faGlzdG9ncmFtKCkKYGBgCgooTW9yZSkgbm9ybWFsbHkgZGlzdHJpYnV0ZWQhCgojIyMgQ2hlY2twb2ludAoKTWFrZSBhIHNjYXR0ZXIgcGxvdCBvZiBsb2cgc2FsZXMgYXMgYSBmdW5jdGlvbiBvZiBwcmljZS4gVXNlIGEgdHJhbnNwYXJlbmN5ICh0cnkgZGlmZmVyZW50IHZhbHVlcyBvZiBgYWxwaGFgISkgSGludDogYGFscGhhYCBjYW4gZ28gaW5zaWRlIGBnZW9tX3BvaW50KClgLCBqdXN0IGxpa2Ugd2UgZGlkIHdpdGggYGdlb21faGlzdG9ncmFtKClgLgoKYGBge3Igc2FsZXMgb3ZlciBwcmljZXN9CmdncGxvdChkYXRhID0gYXZvY2Fkb3MsIGFlcyh4ID0gQXZlcmFnZVByaWNlLCB5ID0gbG9nX3NhbGVzKSkgKyAKICBnZW9tX3BvaW50KGFscGhhID0gMC4yMCkKYGBgCgojIyMgQ2hlY2twb2ludAoKTm93IHJlLWRvIHRoZSBwbG90IGFuZCB0aGlzIHRpbWUgYWRkIGBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKWAuIFRoaXMgd2lsbCBhZGQgYSByZWdyZXNzaW9uIGxpbmUgdG8gdGhlIHBsb3QuCgpgYGB7ciBzYWxlcyBvdmVyIHByaWNlcyB3aXRoIGxtfQpnZ3Bsb3QoZGF0YSA9IGF2b2NhZG9zLCBhZXMoeCA9IEF2ZXJhZ2VQcmljZSwgeSA9IGxvZ19zYWxlcykpICsgCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMjApICsgCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikKYGBgCgojIyMgQ2hlY2twb2ludAoKRXN0aW1hdGUgdGhlIHBhcmFtZXRlcnMgb2YgdGhhdCBsaW5lIHdpdGggYGxtKClgIGFuZCBgc3VtbWFyeSgpYDoKCmBgYHtyIGNoZWNrcG9pbnQgbG0gcHJpY2Ugc2FsZXN9CmxtKGZvcm11bGEgPSBsb2dfc2FsZXMgfiBBdmVyYWdlUHJpY2UsIGRhdGEgPSBhdm9jYWRvcykgJT4lIAogIHN1bW1hcnkoKQpgYGAKCiMjIyBDaGVja3BvaW50CgpOb3csIHJlLXBsb3QgdGhlIHNjYXR0ZXJwbG90IG9mIGxvZyBzYWxlcyBhbmQgYXZvY2FkbyB0eXBlIHdpdGggYSByZWdyZXNzaW9uIGxpbmUsIGJ1dCB0aGlzIHRpbWUgdXNlIGBmYWNldF93cmFwKClgIHRvIG1ha2Ugc2VwYXJhdGUgcGFuZWxzIGZvciBjb252ZW50aW9uYWwgYW5kIG9yZ2FuaWMgYXZvY2Fkb3M6CgpgYGB7ciBzYWxlcyBvdmVyIHByaWNlcyB3aXRoIGxtIGFuZCBmYWNldH0KZ2dwbG90KGRhdGEgPSBhdm9jYWRvcywgYWVzKHggPSBBdmVyYWdlUHJpY2UsIHkgPSBsb2dfc2FsZXMpKSArIAogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIwKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikgKyAKICBmYWNldF93cmFwKH50eXBlKSAKYGBgCgojIyBEZW1hbmQgc2hpZnRzOiBicmluZ2luZyBpdCBhbGwgdG9nZXRoZXIKCk5vdGljZSB0d28ga2V5IHRoaW5ncyBpbiB0aGF0IGxhc3QgcGxvdDoKCjEuIHRoZSAqKmludGVyY2VwdCoqIG9mIHRoZSByZWdyZXNzaW9uIGxpbmUgZm9yIG9yZ2FuaWMgYXZvY2Fkb3Mgc2hpZnRlZCBkb3duIHJlbGF0aXZlIHRvIHRoZSBpbnRlcmNlcHQgZm9yIGNvbnZlbnRpb25hbCBhdm9jYWRvcwoyLiB0aGUgKipzbG9wZSoqIG9mIHRoZSByZWdyZXNzaW9uIGxpbmVzIGxvb2sgZGlmZmVyZW50OyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gcHJpY2UgYW5kIHNhbGVzIHZhcmllcyBhY3Jvc3MgYXZvY2FkbyB0eXBlcwoKTm93IHdlIGhhdmUgYSBnb29kIGlkZWEgb2YgYSBtb2RlbCB3ZSBjYW4gYnVpbGQgdGhhdCBzdW1tYXJpemVzIHRoZSBkZW1hbmQgZm9yIGF2b2NhZG9zLiBUaGUgbW9kZWwgc2hvdWxkOgoKKiBhbGxvdyB0aGUgaW50ZXJjZXB0IHRvIHZhcnkgYnkgYXZvY2FkbyB0eXBlLCBhbmQ7CiogYWxsb3cgdGhlIHNsb3BlIHRvIHZhcnkgYnkgYXZvY2FkbyB0eXBlCgpIZXJlIGlzIHN1Y2ggYSBtb2RlbCB0aGF0IGluY2x1ZGVzIGEgKipkdW1teSB2YXJpYWJsZSoqICh0byBjYXB0dXJlIHRoZSBjaGFuZ2luZyBpbnRlcmNlcHQpIGFuZCBhbiAqKmludGVyYWN0aW9uIGVmZmVjdCoqICh0byBjYXB0dXJlIHRoZSBjaGFuZ2luZyBzbG9wZSk6CgokJApcdGV4dHtsb2coc2FsZXMpfSA9IFxiZXRhXzAgKyBcYmV0YV8xKFx0ZXh0e3ByaWNlfSkgKyBcYmV0YV8yKFx0ZXh0e29yZ2FuaWN9KSArIFxiZXRhXzMoXHRleHR7cHJpY2V9IFx0aW1lcyBcdGV4dHtvcmdhbmljfSkgKyBcZXBzaWxvbgokJAoKTGV0J3MgZXN0aW1hdGUgdGhlIHBhcmFtZXRlcnMgd2l0aCBgbG0oKWA6CgpgYGB7ciBhdm9jYWRvIG1vZGVsfQpsbShmb3JtdWxhID0gbG9nX3NhbGVzIH4gQXZlcmFnZVByaWNlICsgdHlwZSArIEF2ZXJhZ2VQcmljZSp0eXBlLCBkYXRhID0gYXZvY2Fkb3MpICU+JSAKICBzdW1tYXJ5KCkKYGBgCgpXaGF0IGRvZXMgdGhpcyBtb2RlbCBzYXk/CgoqIFRoZSBpbnRlcmNlcHQgZm9yIGNvbnZlbnRpb25hbCBhdm9jYWRvcyBpcyAxNC40OTY1MgoqIFRoZSBzaGlmdCBpbiB0aGUgaW50ZXJjZXB0IGZyb20gY29udmVudGlvbmFsIHRvIG9yZ2FuaWMgaXMgLTEuMTY5MDcKKiBUaGUgZWZmZWN0IG9mIHByaWNlIG9uIHNhbGVzICh0aGUgc2xvcGUpIGZvciBjb3ZlbnRpb25hbCBpcyAtMS4xNjkwNwoqIFRoZSBzaGlmdCBpbiB0aGUgc2xvcGUgZnJvbSBjb252ZW50aW9uYWwgdG8gb3JnYW5pYyBpcyAwLjQ4MjQ2CgpTbyBpdCBzZWVtcyB0aGF0IHBlb3BsZSB3aG8gYnV5IG9yZ2FuaWMgYXZvY2Fkb3MgYXJlIGxlc3Mgc2Vuc2l0aXZlIHRvIGNoYW5nZXMgaW4gcHJpY2UhCgpPZiBjb3Vyc2UsIHRha2UgdGhpcyBhbmFseXNpcyB3aXRoIGEgZ3JhaW4gb2Ygc2FsdC4gCgpUaGVyZSBhcmUgcHJvYmFibHkgc2V2ZXJhbCBjb25mb3VuZGVycyB0byBhZGRyZXNzLCBsaWtlIHZhcmlhdGlvbiBpbiBwcmljZXMgb3ZlciB0aW1lIGFuZCBhY3Jvc3MgcmVnaW9ucy4gCgpBbHNvLCB3aG8gaXMgYnV5aW5nIHRoZXNlIGF2b2NhZG9zPyBUaGlzIG1pZ2h0IGV4cGxhaW4gd2h5IHdlIHNlZSBhIHJpZ2h0IHNoaWZ0IGluIHZvbHVtZSBmb3IgY29udmVudGlvbmFsIGF2b3MuIElzIGl0IGZvbGtzIGxpa2UgdXMgYXQgdGhlIHN0b3JlPyBSZXN0YXVyYW50cyBidXlpbmcgaW4gYnVsaz8gV2UgZG9uJ3Qga25vdy4gCgojIEJsb29kIHByZXNzdXJlCgpMZXQncyBnbyBiYWNrIHRvIGBuaGFuZXNgIGFuZCBjb25zaWRlciBhIG1vcmUgaW50ZXJlc3RpbmcgZXhhbXBsZTogdmFyaWF0aW9uIGluIGJsb29kIHByZXNzdXJlICoqb3ZlciB0aW1lKiogYW5kICoqYnkgc2V4KiouIAoKV2hlbiB5b3VyIGhlYXJ0IGJlYXRzLCBpdCBjcmVhdGVzIHByZXNzdXJlIHRvIGNpcmN1bGF0ZWQgb3h5Z2VuYXRlZCBibG9vZCB0aHJvdWdob3V0IHlvdXIgYm9keS4gVGhhdCBwcmVzc3VyZSBoYXMgdHdvIHBhcnRzLCAqKnN5c3RvbGljKiogYW5kICoqZGlhc3RvbGljKiouCgpUaGF0IGlzIHdoeSB3aGVuIHRoZXkgdGFrZSB5b3VyIGJsb29kIHByZXNzdXJlIHRoZXkgc2F5ICIxMjAgb3ZlciA4MCIgKHVuaXRzIG9mIG1pbGxpbWV0ZXJzIG9mIG1lcmN1cnksIG9yIG1tSGcpLCBtZWFuaW5nIDEyMCBzeXN0b2xpYyBvdmVyIDgwIGRpYXN0b2xpYy4gMTIwLzgwIGlzIGNvbnNpZGVyZWQgYSBub3JtYWwgYmxvb2QgcHJlc3N1cmUuIEFueXRoaW5nIGFib3ZlIDEzMCBzeXN0b2xpYyBpcyBjb25zaWRlcmVkIGhpZ2ggYmxvb2QgcHJlc3N1cmUuCgpUaGUgaGFyZGVyIHlvdXIgYmxvb2QgdHJhdmVscyB0aGUgbW9yZSBpdCBkZXRlcmlvcmF0ZXMgeW91ciBjYXJkaW92YXNjdWxhciBzeXN0ZW0uIFRoaXMgZXhjZXNzIGZvcmNlIGlzIFsqKmh5cGVydGVuc2lvbioqXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9IeXBlcnRlbnNpb24pIG9yIGhpZ2ggYmxvb2QgcHJlc3N1cmUuIE92ZXIgdGltZSBoeXBlcnRlbnNpb24gY2FuIGxlYWQgdG8gYSBudW1iZXIgb2YgaGVhbHRoIGlzc3Vlcy4KCkluIGBuaGFuZXNgIHRoZSB2YXJpYWJsZSBgYnBzeXN0b2xgIGNhcHR1cmVzIHN5c3RvbGljIGJsb29kIHByZXNzdXJlLgoKIyMgQ2hlY2twb2ludCAKCkNhbGN1bGF0ZSBhdmVyZWFnZSBzeXN0b2xpYyBibG9vZCBwcmVzc3VyZSBieSBzZXg6CgpgYGB7ciBjaGVja3BvaW50IGF2ZXJhZ2Ugc3lzdG9saWMgYnkgc2V4LCBtZXNzYWdlPUZBTFNFfQpuaGFuZXMgJT4lIAogIGdyb3VwX2J5KHNleCkgJT4lIAogIHN1bW1hcmlzZShtZWFuKGJwc3lzdG9sKSkKYGBgCgojIyBDaGVja3BvaW50IAoKUGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHN5c3RvbGljIGJsb29kIHByZXNzdXJlIGJ5IHNleDoKCmBgYHtyIGNoZWNrcG9pbnQgZGlzdHJpYnV0aW9uIHN5c3RvbGljIGJ5IHNleH0KZ2dwbG90KGRhdGEgPSBuaGFuZXMsIGFlcyh4ID0gYnBzeXN0b2wsIGZpbGwgPSBzZXgpKSArIAogIGdlb21faGlzdG9ncmFtKGFscGhhID0gMC43NSwgcG9zaXRpb24gPSAiaWRlbnRpdHkiKQpgYGAKCiMjIEJsb29kIHByZXNzdXJlIG92ZXIgdGltZQoKSXQgZG9lc24ndCBsb29rIGxpa2UgdGhlcmUgaXMgbXVjaCBhIG9mIGRpZmZlcmVuY2UgaW4gdGhlICoqdW5jb25kaXRpb25hbCBhdmVyYWdlKiogYmxvb2QgcHJlc3N1cmUgYmV0d2VlbiBtZW4gYW5kIHdvbWVuLiAKCkJ1dCB3aGF0IGlmIHdlIG5lZWQgdG8gbG9vayBhdCB0aGUgKipjb25kaXRpb25hbCBhdmVyYWdlKio/IEZvciBpbnN0YW5jZSwgd2hhdCBpZiBibG9vZCBwcmVzc3VyZSB2YXJpZXMgb3ZlciB0aW1lIChgYWdlYCk/CgojIyMgQ2hlY2twb2ludAoKRmlyc3QgcGxvdCB0aGUgYGJweXN0b2xgIGFzIGEgZnVuY3Rpb24gb2YgYGFnZWAuIEluY2x1ZGUgYSByZWdyZXNzaW9uIGxpbmUuCgpgYGB7ciBjaGVja3BvaW50IGJweXN0b2wgb3ZlciBhZ2V9CmdncGxvdChkYXRhID0gbmhhbmVzLCBhZXMoeCA9IGFnZSwgeSA9IGJwc3lzdG9sKSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKQpgYGAKCiMjIyBDaGVja3BvaW50CgpUaGF0IHBsb3QgaXMgYSBiaXQgbWVzc3kuIExldCdzIHNpbXBsaWZ5IGl0LiBOb3cgcGxvdCAqKmF2ZXJhZ2UqKiBibG9vZCBwcmVzc3VyZSBieSBhZ2UuIEluY2x1ZGUgYSByZWdyZXNzaW9uIGxpbmUuIFRvIGRvIHRoaXMsIGZpcnN0IHVzZSBgc3VtbWFyaXNlYCBhbmQgY3JlYXRlIGEgdmFyaWFibGUgY2FsbGVkICJtZWFuX2Jwc3lzdG9sIiAqKmJ5KiogYGFnZWAsIHRoZW4gcGlwZSAoYCU+JWApIHRoZSBvdXRwdXQgdG8gYGdncGxvdGAuIChZb3UgbWlnaHQgZmluZCBpdCBoZWxwZnVsIHRvIGZpcnN0IGJ1aWxkIHRoZSBzdW1tYXJ5IHRhYmxlKS4gRG9uJ3QgaW5jbHVkZSBhIHJlZ3Jlc3Npb24gbGluZS4KCmBgYHtyIGNoZWNrcG9pbnQgYXZlcmFnZSBicHlzdG9sIG92ZXIgYWdlLCBtZXNzYWdlPUZBTFNFfQpuaGFuZXMgJT4lIAogIGdyb3VwX2J5KGFnZSkgJT4lIAogIHN1bW1hcmlzZShtZWFuX2Jwc3lzdG9sID0gbWVhbihicHN5c3RvbCkpICU+JSAKICBnZ3Bsb3QoLiwgYWVzKGFnZSwgbWVhbl9icHN5c3RvbCkpICsgCiAgZ2VvbV9wb2ludCgpICsgCiAgZ2VvbV9saW5lKCkgCmBgYAoKIyMjIENoZWNrcG9pbnQKCkRvZXMgdGhpcyByZWxhdGlvbnNoaXAgdmFyeSBieSBzZXg/IAoKUmUtZG8gdGhhdCBwbG90LCBidXQgdGhpcyB0aW1lIGJ5IGFnZSAqKmFuZCoqIHNleC4gSW5zdGVhZCBvZiBgZmlsbD1gIHVzZSBgY29sb3I9YCB0byBjb2xvciB0aGUgcGxvdCBieSBgc2V4YDoKCmBgYHtyIGNoZWNrcG9pbnQgYXZlcmFnZSBicHlzdG9sIG92ZXIgYWdlIGFuZCBzZXgsIG1lc3NhZ2U9RkFMU0V9Cm5oYW5lcyAlPiUgCiAgZ3JvdXBfYnkoYWdlLCBzZXgpICU+JSAKICBzdW1tYXJpc2UobWVhbl9icHN5c3RvbCA9IG1lYW4oYnBzeXN0b2wpKSAlPiUgCiAgZ2dwbG90KC4sIGFlcyhhZ2UsIG1lYW5fYnBzeXN0b2wsIGNvbG9yID0gc2V4KSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBnZW9tX2xpbmUoKSAKYGBgCgojIyBNb2RlbGluZyB0aGUgcmVsYXRpb25zaGlwCgpXaGVuIHdlIHBsb3QgYXZlcmFnZSBzeXN0b2xpYyBibG9vZCBwcmVzc3VyZSBieSBhZ2UgZm9yIG1lbiBhbmQgd29tZW4sIHdlIHNlZSBhbiB1cHdhcmQgdHJlbmQgaW4gYm90aCBzZXhlcywgYnV0IG5vdCBuZWNlc3NhcmlseSB0aGUgc2FtZSB0cmVuZC4gVGhlIGxpbmVzIGRvIG5vdCBsb29rIHBhcmFsbGVsLiAKCkFub3RoZXIgd2F5IHRvIHRoaW5rIGFib3V0IGlzIHRoYXQgdGhlIHNsb3BlIG9mIHRoZSBsaW5lcyBmb3IgYWdlIGFyZSBkaWZmZXJlbnQgZm9yIG1lbiBhbmQgd29tZW4uIEluIHRoZSBncmFwaCBpdCBsb29rcyBsaWtlIHRoZSBlZmZlY3Qgb2YgYWdlIGlzIG1vcmUgcHJvbm91bmNlZCAtLSB0aGUgc2xvcGUgaXMgc3RlZXBlciAtLSBhbW9uZyB3b21lbi4gCgpCYXNlZCBvbiBvdXIgZXhwbG9yYXRvcnkgYW5hbHlzaXMgd2UgbWlnaHQgY29uc2lkZXIgdGhlIGZvbGxvd2luZyBtb2RlbDoKCiQkClx0ZXh0e2Jsb29kIHByZXNzdXJlfSA9IFxiZXRhXzAgKyBcYmV0YV8xIFx0ZXh0e2FnZX0gKyBcYmV0YV8yIFx0ZXh0e3NleH0gKyBcYmV0YV8zIChcdGV4dHtzZXh9IFx0aW1lcyBcdGV4dHthZ2V9KSArIFxlcHNpbG9uCiQkCgojIyMgQ2hlY2twb2ludAoKTW9kaWZ5IHRoZSBtb2RlbCB3ZSBlc3RpbWF0ZWQgZm9yIGF2b2NhZG8gc2FsZXMgdG8gZXN0aW1hdGUgdGhlIG1vZGVsIGFib3ZlLiBSZWNhbGwgdGhhdCBpbnNpZGUgYGxtKClgIHdlIGNhbiBpbnRlcmFjdCB0d28gdmFyaWFibGVzIGB4MWAgYW5kIGB4MmAgYnkgdHlwZWluZyBgeDEqeDJgLgoKYGBge3IgY2hlY2twb2ludCBtb2RlbCBicHN5c3RvbH0KbG0oZm9ybXVsYSA9IGJwc3lzdG9sIH4gc2V4ICsgYWdlICsgc2V4KmFnZSwgZGF0YSA9IG5oYW5lcykgJT4lIAogIHN1bW1hcnkoKQpgYGAKCkludGVyZXN0aW5nbHksIHRoZSBlZmZlY3Qgb2YgYWdlIG9uIGJsb29kIHByZXNzdXJlIGFtb25nIG1lbiwgYHNleE1hbGU6YWdlYCwgaXMgKipuZWdhdGl2ZSoqLiAKCk1lYW5pbmcgdGhhdCBhcyBtZW4gYWdlLCB0aGVpciBibG9vZCBwcmVzc3VyZSBnb2VzIHVwIChiZWNhdXNlIHRoZSBhdmVyYWdlIGVmZmVjdCBvZiBhZ2UgaXMgcG9zaXRpdmUpLCBidXQgc2xvd2VyIHRoYW4gZm9yIHdvbWVuIChiZWNhdXNlIHRoZSBpbnRlcmFjdGlvbiBlZmZlY3QgaXMgbmVnYXRpdmUpLgoKVGhlIGdlbmVyYWwgdGFrZWF3YXkgaXMgdGhhdCBhIHBhdGllbnQgc2hvdWxkIGJlIGNvbXBhcmVkIGFnYWluc3QgdGhlIHJpZ2h0IGJlbmNobWFyay4gRm9yIGluc3RhbmNlLCB3ZSBjYW4ndCBqdXN0IGNvbXBhcmUgYSBtYWxlIHBhdGllbnQncyBibG9vZCBwcmVzc3VyZSBhZ2FpbnN0IHRoZSBvdmVyYWxsIGF2ZXJhZ2UuIFRoZXkgbmVlZCB0byBiZSBjb21wYXJlZCB0byB0aGUgYXZlcmFnZSBtYWxlIGF0IHRoZSBzYW1lIGFnZS4KCiMgQmlnIHBpY3R1cmUKCkRvbid0IHdvcnJ5IGlmIHlvdSBoYXZlbid0IGZvbGxvd2VkIGV2ZXJ5IHN0ZXAsIGVzcGVjaWFsbHkgdGhlIHN0dWZmIHdpdGggYGxtKClgLiBXZSdsbCBkbyBtb3JlIG9mIHRoYXQgbGF0ZXIuIAoKRm9yIG5vdywganVzdCBub3RpY2UgaG93IHdlIHdlcmUgYWJsZSB0byB1c2Ugc3VtbWFyeSBzdGF0aXN0aWNzIGFuZCBwbG90cyB0byBnZW5lcmF0ZSBoeXBvdGhlc2VzLCBidWlsZCBtb2RlbHMsIGFuZCB0aGVuIGVzdGltYXRlIHRob3NlIG1vZGVscyB0byBjb21wcmVzcyB0aGUgZGF0YSBhbmQgY3JlYXRlIGluc2lnaHRzLiA=