In this tutorial, we will learn how to import, tidy, wrangle and visualize data. We will work with one specific dataset;

1 The NHANES dataset

The National Health and Nutrition Examination Survey (NHANES) contains data that has been collected since 1960. For this tutorial, we will make use of the data that were collected between 2009 and 2012, for 10.000 U.S. civilians. The dataset contains a large number of physical, demographic, nutritional and life-style-related parameters.

However, before we can actually start working with data, we will need to learn how to import the required datasets into our Rstudio environment.

2 Data Import with the readr R package

library(readr)

Let’s try reading in some data. We will begin by reading in the NHANES.csv dataset.

NHANES <- read_csv("https://raw.githubusercontent.com/GTPB/PSLS20/master/data/NHANES.csv")
## Parsed with column specification:
## cols(
##   .default = col_double(),
##   SurveyYr = col_character(),
##   Gender = col_character(),
##   AgeDecade = col_character(),
##   Race1 = col_character(),
##   Race3 = col_logical(),
##   Education = col_character(),
##   MaritalStatus = col_character(),
##   HHIncome = col_character(),
##   HomeOwn = col_character(),
##   Work = col_character(),
##   BMICatUnder20yrs = col_logical(),
##   BMI_WHO = col_character(),
##   Testosterone = col_logical(),
##   Diabetes = col_character(),
##   HealthGen = col_character(),
##   LittleInterest = col_character(),
##   Depressed = col_character(),
##   SleepTrouble = col_character(),
##   PhysActive = col_character(),
##   TVHrsDay = col_logical()
##   # ... with 12 more columns
## )
## See spec(...) for full column specifications.
## Warning: 20122 parsing failures.
##  row              col           expected     actual                                                                   file
## 5001 Race3            1/0/T/F/TRUE/FALSE Asian      'https://raw.githubusercontent.com/GTPB/PSLS20/master/data/NHANES.csv'
## 5001 BMICatUnder20yrs 1/0/T/F/TRUE/FALSE NormWeight 'https://raw.githubusercontent.com/GTPB/PSLS20/master/data/NHANES.csv'
## 5001 Testosterone     1/0/T/F/TRUE/FALSE 274.95     'https://raw.githubusercontent.com/GTPB/PSLS20/master/data/NHANES.csv'
## 5001 TVHrsDay         1/0/T/F/TRUE/FALSE 2_hr       'https://raw.githubusercontent.com/GTPB/PSLS20/master/data/NHANES.csv'
## 5001 CompHrsDay       1/0/T/F/TRUE/FALSE 3_hr       'https://raw.githubusercontent.com/GTPB/PSLS20/master/data/NHANES.csv'
## .... ................ .................. .......... ......................................................................
## See problems(...) for more details.
head(NHANES) ## take a look at the first rows of the dataset
## # A tibble: 6 x 76
##      ID SurveyYr Gender   Age AgeDecade AgeMonths Race1 Race3 Education
##   <dbl> <chr>    <chr>  <dbl> <chr>         <dbl> <chr> <lgl> <chr>    
## 1 51624 2009_10  male      34 30-39           409 White NA    High Sch…
## 2 51624 2009_10  male      34 30-39           409 White NA    High Sch…
## 3 51624 2009_10  male      34 30-39           409 White NA    High Sch…
## 4 51625 2009_10  male       4 0-9              49 Other NA    <NA>     
## 5 51630 2009_10  female    49 40-49           596 White NA    Some Col…
## 6 51638 2009_10  male       9 0-9             115 White NA    <NA>     
## # … with 67 more variables: MaritalStatus <chr>, HHIncome <chr>,
## #   HHIncomeMid <dbl>, Poverty <dbl>, HomeRooms <dbl>, HomeOwn <chr>,
## #   Work <chr>, Weight <dbl>, Length <dbl>, HeadCirc <dbl>, Height <dbl>,
## #   BMI <dbl>, BMICatUnder20yrs <lgl>, BMI_WHO <chr>, Pulse <dbl>,
## #   BPSysAve <dbl>, BPDiaAve <dbl>, BPSys1 <dbl>, BPDia1 <dbl>, BPSys2 <dbl>,
## #   BPDia2 <dbl>, BPSys3 <dbl>, BPDia3 <dbl>, Testosterone <lgl>,
## #   DirectChol <dbl>, TotChol <dbl>, UrineVol1 <dbl>, UrineFlow1 <dbl>,
## #   UrineVol2 <dbl>, UrineFlow2 <dbl>, Diabetes <chr>, DiabetesAge <dbl>,
## #   HealthGen <chr>, DaysPhysHlthBad <dbl>, DaysMentHlthBad <dbl>,
## #   LittleInterest <chr>, Depressed <chr>, nPregnancies <dbl>, nBabies <dbl>,
## #   Age1stBaby <dbl>, SleepHrsNight <dbl>, SleepTrouble <chr>,
## #   PhysActive <chr>, PhysActiveDays <dbl>, TVHrsDay <lgl>, CompHrsDay <lgl>,
## #   TVHrsDayChild <dbl>, CompHrsDayChild <dbl>, Alcohol12PlusYr <chr>,
## #   AlcoholDay <dbl>, AlcoholYear <dbl>, SmokeNow <chr>, Smoke100 <chr>,
## #   Smoke100n <chr>, SmokeAge <dbl>, Marijuana <chr>, AgeFirstMarij <dbl>,
## #   RegularMarij <chr>, AgeRegMarij <dbl>, HardDrugs <chr>, SexEver <chr>,
## #   SexAge <dbl>, SexNumPartnLife <dbl>, SexNumPartYear <dbl>, SameSex <chr>,
## #   SexOrientation <chr>, PregnantNow <chr>
tail(NHANES) ## take a look at the last rows of the dataset
## # A tibble: 6 x 76
##      ID SurveyYr Gender   Age AgeDecade AgeMonths Race1 Race3 Education
##   <dbl> <chr>    <chr>  <dbl> <chr>         <dbl> <chr> <lgl> <chr>    
## 1 71909 2011_12  male      28 20-29            NA Mexi… NA    9 - 11th…
## 2 71909 2011_12  male      28 20-29            NA Mexi… NA    9 - 11th…
## 3 71910 2011_12  female     0 0-9               5 White NA    <NA>     
## 4 71911 2011_12  male      27 20-29            NA Mexi… NA    College …
## 5 71915 2011_12  male      60 60-69            NA White NA    College …
## 6 71915 2011_12  male      60 60-69            NA White NA    College …
## # … with 67 more variables: MaritalStatus <chr>, HHIncome <chr>,
## #   HHIncomeMid <dbl>, Poverty <dbl>, HomeRooms <dbl>, HomeOwn <chr>,
## #   Work <chr>, Weight <dbl>, Length <dbl>, HeadCirc <dbl>, Height <dbl>,
## #   BMI <dbl>, BMICatUnder20yrs <lgl>, BMI_WHO <chr>, Pulse <dbl>,
## #   BPSysAve <dbl>, BPDiaAve <dbl>, BPSys1 <dbl>, BPDia1 <dbl>, BPSys2 <dbl>,
## #   BPDia2 <dbl>, BPSys3 <dbl>, BPDia3 <dbl>, Testosterone <lgl>,
## #   DirectChol <dbl>, TotChol <dbl>, UrineVol1 <dbl>, UrineFlow1 <dbl>,
## #   UrineVol2 <dbl>, UrineFlow2 <dbl>, Diabetes <chr>, DiabetesAge <dbl>,
## #   HealthGen <chr>, DaysPhysHlthBad <dbl>, DaysMentHlthBad <dbl>,
## #   LittleInterest <chr>, Depressed <chr>, nPregnancies <dbl>, nBabies <dbl>,
## #   Age1stBaby <dbl>, SleepHrsNight <dbl>, SleepTrouble <chr>,
## #   PhysActive <chr>, PhysActiveDays <dbl>, TVHrsDay <lgl>, CompHrsDay <lgl>,
## #   TVHrsDayChild <dbl>, CompHrsDayChild <dbl>, Alcohol12PlusYr <chr>,
## #   AlcoholDay <dbl>, AlcoholYear <dbl>, SmokeNow <chr>, Smoke100 <chr>,
## #   Smoke100n <chr>, SmokeAge <dbl>, Marijuana <chr>, AgeFirstMarij <dbl>,
## #   RegularMarij <chr>, AgeRegMarij <dbl>, HardDrugs <chr>, SexEver <chr>,
## #   SexAge <dbl>, SexNumPartnLife <dbl>, SexNumPartYear <dbl>, SameSex <chr>,
## #   SexOrientation <chr>, PregnantNow <chr>
#knitr::kable(NHANES[c(1,4,5,6,7,8),c(1,3,4,7,17,20,21,25)],format = "markdown")
NHANES[c(1,4,5,6,7,8),c(1,3,4,7,17,20,21,25)] ## take a look at a subset of the dataset
## # A tibble: 6 x 8
##      ID Gender   Age Race1 Weight Height   BMI BPSysAve
##   <dbl> <chr>  <dbl> <chr>  <dbl>  <dbl> <dbl>    <dbl>
## 1 51624 male      34 White   87.4   165.  32.2      113
## 2 51625 male       4 Other   17     105.  15.3       NA
## 3 51630 female    49 White   86.7   168.  30.6      112
## 4 51638 male       9 White   29.8   133.  16.8       86
## 5 51646 male       8 White   35.2   131.  20.6      107
## 6 51647 female    45 White   75.7   167.  27.2      118

2.1 Take a glimpse() at your data

The glimpse function allows us to (obviously) take a first, informative glimpse at our data. The function is part of the dplyr, which we will explore in much more detail below!

dplyr::glimpse(NHANES[,1:10])
## Observations: 10,000
## Variables: 10
## $ ID            <dbl> 51624, 51624, 51624, 51625, 51630, 51638, 51646, 51647,…
## $ SurveyYr      <chr> "2009_10", "2009_10", "2009_10", "2009_10", "2009_10", …
## $ Gender        <chr> "male", "male", "male", "male", "female", "male", "male…
## $ Age           <dbl> 34, 34, 34, 4, 49, 9, 8, 45, 45, 45, 66, 58, 54, 10, 58…
## $ AgeDecade     <chr> "30-39", "30-39", "30-39", "0-9", "40-49", "0-9", "0-9"…
## $ AgeMonths     <dbl> 409, 409, 409, 49, 596, 115, 101, 541, 541, 541, 795, 7…
## $ Race1         <chr> "White", "White", "White", "Other", "White", "White", "…
## $ Race3         <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
## $ Education     <chr> "High School", "High School", "High School", NA, "Some …
## $ MaritalStatus <chr> "Married", "Married", "Married", NA, "LivePartner", NA,…
# or glimpse(NHANES) to see all the variables in the dataset

3 Data Tidying

library(tidyverse)
## ── Attaching packages ────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
## ✓ ggplot2 3.2.1     ✓ dplyr   0.8.3
## ✓ tibble  2.1.3     ✓ stringr 1.4.0
## ✓ tidyr   1.0.0     ✓ forcats 0.4.0
## ✓ purrr   0.3.3
## ── Conflicts ───────────────────────────────────────────────────────────── tidyverse_conflicts() ──
## x dplyr::filter() masks stats::filter()
## x dplyr::lag()    masks stats::lag()

** Important ** If you are not familiar yet with the concepts of tidy data, have a look at the preliminary_tidyverse.Rmd file!

If we consider our NHANES dataframe, we see it is already in a tidy format, as;

  • Each variable forms a column.
  • Each observation forms a row.
  • Each type of observational unit forms a table.

Each row contains all of the information on a single subject (US civilian) in the study.

In the next tutorial, we will work with a dataset on the effects of a certain drug, captopril, on the systolic and diastolic blood pressure of patients. This will not be a tidy dataset. As such, the details of tidying data with tidyverse will be described there

4 Data wrangling with dplyr

library(dplyr)

The dplyr package provides us with a large set of functions for handling our data. We refer to the preliminary_tidyverse.Rmd file for a more detailed description of these functions.

The most important dplyr functions to remember are:

dplyr verbs Description
select() select columns
filter() filter rows
arrange() re-order or arrange rows
mutate() create new columns
summarize() summarize values
group_by() allows for group operations in the “split-apply-combine” concept

In addition, dplyr uses the pipe operator %>% to use the output of one function as an input to the next function. Instead of nesting functions (reading from the inside to the outside), the idea of of piping is to read the functions from left to right.

4.1 Select and filter

Here we will demonstrate the dplyr functionalities with a couple of small examples.

Let’s say we want to investigate how many subjects are 1. men that 2. are older than 18 years old, 3. are taller than 150 cm, and 4. have weight of less than 80kg

NHANES %>% 
  select(c("Gender","Age","Weight","Height")) %>% ## select the columns of interest
  filter(Gender == "male", 
         Age > 18,
         Height > 150,
         Weight < 80) %>% ## filter observations (rows) based on the required values
  nrow()
## [1] 1286

Filtering the dataset based on thee three filtering criteria retain 3.601 subjects. Note that the select step was not strictly necessary, but since we could answer the question based on only these 4 columns, there is no need to retain the the rest of the data (if this is the only question).

4.2 Arrange

Next question; Within the Race1 category of Hispanics, select men that are not married and display the first five by descending height.

Hint: use the desc() function inside of arrange() to order rows in a descending order. Use the base R function head to display the first five.

NHANES %>%
  select(c("Race1", "Gender","MaritalStatus","Height")) %>%
  filter(MaritalStatus != "Married", Race1 == "Hispanic", Gender == "male") %>% 
  arrange(desc(Height)) %>% 
  head(n=5)
## # A tibble: 5 x 4
##   Race1    Gender MaritalStatus Height
##   <chr>    <chr>  <chr>          <dbl>
## 1 Hispanic male   NeverMarried    197.
## 2 Hispanic male   NeverMarried    190.
## 3 Hispanic male   NeverMarried    182.
## 4 Hispanic male   NeverMarried    181.
## 5 Hispanic male   NeverMarried    180.

We have successfully combined three of the main dplyr functionalities. Let’s try to explore another one!

4.3 Mutate

Assume that we don’t trust the BMI values in our dataset and we decide to calculate them ourselves. BMI is typically calculated by taking a person’s weight (in kg) and dividing it by the its height (in m) squared. Based on this rule, generate a new column, BMI_self, for the subset of the NHANES dataset that we obtained from the previous question. To create new columns, we will use the mutate() function in dplyr.

NHANES %>%
  select(c("Race1", "Gender","MaritalStatus","Height","Weight","BMI")) %>%
  filter(MaritalStatus != "Married", Race1 == "Hispanic", Gender == "male") %>% 
  mutate(BMI_self = Weight/(Height/100)**2)
## # A tibble: 107 x 7
##    Race1    Gender MaritalStatus Height Weight   BMI BMI_self
##    <chr>    <chr>  <chr>          <dbl>  <dbl> <dbl>    <dbl>
##  1 Hispanic male   Divorced        177.  114.   36.3     36.3
##  2 Hispanic male   Divorced        177.  114.   36.3     36.3
##  3 Hispanic male   NeverMarried    190.  125.   34.7     34.7
##  4 Hispanic male   NeverMarried    176.  161.   52.1     52.1
##  5 Hispanic male   LivePartner     164.   88.2  32.8     32.8
##  6 Hispanic male   LivePartner     164.   88.2  32.8     32.8
##  7 Hispanic male   NeverMarried    162.   78.5  30.0     30.0
##  8 Hispanic male   Divorced        173.   95.4  31.8     31.8
##  9 Hispanic male   LivePartner     179.   92.8  28.9     28.9
## 10 Hispanic male   LivePartner     179.   92.8  28.9     28.9
## # … with 97 more rows

Note that now we need to include the Weight and BMI columns in the select statement, because we need that input for the mutate function. Good news: It turns out that the BMI column in the original dataset was computed correctly after all! But now we are interested in the mean value of the BMI_Self column. This requires a fifth dplyr function: summarize().

4.4 summarize

Given the filtering of the question above, compute the mean value of the BMI_self column.

NHANES %>%
  select(c("Race1", "Gender","MaritalStatus","Height","Weight","BMI")) %>%
  filter(MaritalStatus != "Married", Race1 == "Hispanic", Gender == "male") %>% 
  mutate(BMI_self = Weight/(Height/100)**2) %>%
  summarize(avg_BMI_self = mean(BMI_self, na.rm = TRUE))
## # A tibble: 1 x 1
##   avg_BMI_self
##          <dbl>
## 1         28.8
## the addition argument na.rm = TRUE makes sure to remove missing
## values for the purpose of calculating the mean

For this particular subset of the data, we find an average BMI value of 28.59 kg/m**2.

There are many other summary statistics you could consider such sd(), min(), median(), max(), sum(), n() (returns the length of vector), first() (returns first value in vector), last() (returns last value in vector) and n_distinct() (number of distinct values in vector). We will elaborate on these functions later.

Note that choosing the most informative summary statistic is very important! This can be shown with the following example;

** change location to github location ** ![/Users/jg/Desktop/PhD/Teaching/Lisbon_2020/psls20/data/Figure_partners.png){width=60%}

In this study, men and woman were asked about their “ideal number of partners desired over 30 years”. While almost all subjects desired a number between 0 and 50, three male subjects selected a number above 100. These three outliers in the data can have a large impact on the data analysis, especially when we work with summary statistics that are sensitive to these outliers.

When we look at the mean, for instance, we see that on average woman desire 2.8 partners, while men desire 64.3 partners on average, suggesting a large discrepancy between male and female desires.

However, if look at a more robust summary statistic such as the median, we see that the result is 1 for both men and woman. It is clear that the mean value was completely distorted by the three outliers in the data.

Another example of a more robust summary statistic is the geometric mean.

4.5 Group

We have already combined 5 very important functions. Here we will add a final one: group_by().

The group_by() verb is and incredibly powerful function in dplyr. It allows us, for example, to calculate summary statistics for different groups of observations.

If we take our example from above, let’s say we want to split the data frame by some variable (e.g. MaritalStatus), apply a function (mean) to a column (e.g. BMI_self) of the individual
data frames and then combine the output back into a summary data frame.

NHANES %>% 
  select(c("Race1", "Gender","MaritalStatus","Height","Weight","BMI")) %>%
  filter(MaritalStatus != "Married", Race1 == "Hispanic", Gender == "male") %>%
  mutate(BMI_self = Weight/(Height/100)**2) %>%
  group_by(MaritalStatus) %>% ##  group the subjects by their marital status
  summarize(avg_BMI_self = mean(BMI_self, na.rm = TRUE)) ## calculate the mean BMI_self value of each group
## # A tibble: 5 x 2
##   MaritalStatus avg_BMI_self
##   <chr>                <dbl>
## 1 Divorced              28.4
## 2 LivePartner           29.0
## 3 NeverMarried          28.1
## 4 Separated             31.4
## 5 Widowed               30.6

We have successfully combined six of the most important dplyr functionalities!

Now that we have all the required functions for importing, tidying and wrangling data in place, we will learn how to visualize our data with the ggplot2 package.

5 Data Visualization

library(ggplot2)

As you might have have already seen, there are many functions available in base R that can create plots (e.g. plot(), boxplot()). Others include: hist(), qqplot(), etc. These functions are great because they come with a basic installation of R and can be quite powerful when you need a quick visualization of something when you are exploring data.

We are choosing to introduce ggplot2 because, in our opinion, it’s one of the most simple ways for beginners to create relatively complicated plots that are intuitive and aesthetically pleasing.

5.1 Univariate statistics

In univariate statistics, we focus on a single variable of interest. Here, we will show different ways to visualize the BMI variable from the NHANES dataset. Importantly, different types of visualizations will provide us with different types of information!

Here we will visualize the same data using a dotplot, a histogram and a boxplot.

5.1.1 Dotplot

NHANES %>%
  filter(!is.na(BMI)) %>%
  head(NHANES, n=100) %>% ## to make the visualization more clear, we take only the first 100 subjects
  ggplot(aes(x=BMI)) +
  geom_dotplot(method = "histodot") 
## `stat_bindot()` using `bins = 30`. Pick better value with `binwidth`.

In stead of making a visualization “on the fly” as we did in the code chunk above, we may also store the visualization in an object. This allows us to: - Call upon the object again later - Add extra layers to the plot.

## store the plot from above in the "p" object
p <- NHANES %>%
  filter(!is.na(BMI)) %>%
  head(NHANES, n=100) %>%
  ggplot(aes(x=BMI)) +
  geom_dotplot(method = "histodot") 

## Add additional info to the plot
p <- p + xlab("BMI (kg/m2)") + 
  theme_bw()

## Display the plot
p
## `stat_bindot()` using `bins = 30`. Pick better value with `binwidth`.

Note that if we are sure we do not want to store the plot for later retrieval, we shouldn’t: it would simple be a waste of storage space!

In the dotplot, the BMI value for each subject (in this case, the first 100 subjects of the NHANES study), is plotted on the x-axis. The y-axis allows us to count how many subjects have a BMI value that falls within a certain counting bin. For instance, in the BMI interval [29.5, 30.[ we observe 4 subjects.

Note, however, that the dotplot does not provide us with information on the mean/median values of the data, which are typically features of high interest.

5.1.2 Histogram

NHANES %>%
  filter(!is.na(BMI)) %>%
  head(NHANES, n=100) %>%
  ggplot(aes(x=BMI)) +
  geom_histogram()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

The information that can be obtained from this histogram is similar to that of the dotplot. Here, we can read immediately (i.e. without counting) that the BMI interval [29.5, 30.0[ contains 4 subjects. Again, the histogram does not provide us with information on the mean/median values of the data, but only on its distribution.

5.1.3 Boxplot

set.seed(2) ## to make the horizontal position of the jitter non-random

give.n <- function(x){
  return(c(y = 50, label = length(x))) 
}

NHANES %>%
  filter(!is.na(BMI)) %>%
  head(NHANES, n=100) %>%
  ggplot(aes(x="", y=BMI)) +
  geom_boxplot(outlier.shape=NA) + 
  geom_jitter(width=0.2) +
  stat_summary(geom="text", fun.y=quantile,
               aes(label=sprintf("%1.1f", ..y..)),
               position=position_nudge(x=0.5), size=4.5) +
  annotate("text", x = c(1.5,1.5,1.5,1.5,1.5,1.05,1.05), y = c(12.5,19,24.5,28.5,45,18,35), label = c("(minimum)","(25%)","(median)","(75%)","(maximum)","whisker","whisker"),size=3) +
  stat_summary(fun.data = give.n, geom = "text") 

Arguably, the boxplot is the most informative default visualization strategy. First, it provides us with similar insights to the shape of the distribution as the histogram. Second, It clearly displays several useful summary statistics such as the median, the interquartile range, whiskers and outliers. Third, with the geom_jitter functionality we can also project each individual value of the dataset on the plot.

The only disadvantage of the boxplot as compared to the dotplot or histogram is that we cannot see (from the figure) how many subjects have a BMI value within a certain interval. However, this feature usually is not of primary interest.

5.2 Bivariate statistics

In bivariate statistics, the goal is to study two variables, including the relationship between both variables.

In terms of visualizations, the scatterplot is the baseline method for displaying two variables.

5.2.1 Create scatter plots using geom_point()

For the NHANES dataset, we can for instance look at the relationship between a person’s height and weight values.

NHANES %>%
  ggplot(aes(x = Height, y = Weight)) +
  geom_point() + 
  xlab("Height (cm)") + 
  ylab("Weight (kg)")
## Warning: Removed 366 rows containing missing values (geom_point).

We used the xlab() and ylab() functions in ggplot2 to specify the x-axis and y-axis labels. Note that NA values were automatically remove by the geom_point function.

ggplot2 also has a very broad panel of aesthetic features for improving your plot. One very basic feature is that we can give colors to the ggplot object. For instance, we can give different colors to the dots in the previous scatterplot based on a subject’s gender.

NHANES %>%
  ggplot(aes(x = Height, y = Weight, color = Gender)) +
  geom_point() + 
  xlab("Height (cm)") + 
  ylab("Weight (kg)")
## Warning: Removed 366 rows containing missing values (geom_point).

5.3 Combining dplyr and ggplot2

Note that the previous functions from the dplyr package can be easily combined with ggplot through the concept of pipes %>%.

For instance, we could make the same scatterplot as above, but only for white, married adults.

In addition, we here also show some convenient ggplot features; 1. Setting the colors manually 2. Set to have a white background 3. Set a (main) title for the plot 4. Pick a different shape for the dots in the scatterplot 5. Manually set the limits of the x- and y-axes

Note that this a only the tip of the iceberg of the the ggplot functionalities!

NHANES %>%
  filter(Age >= 18, Race1 == "White", MaritalStatus == "Married") %>% ## select the required data
  ggplot(aes(x = Height, y = Weight, color = Gender)) +
  geom_point(shape=17, size = 1) + ## set to a different shape (triangle) and size
  ggtitle("Height versus Weight") + ## for the main title
  xlab("Height (cm)") + 
  ylab("Weight (kg)") +
  xlim(0,220) + ## set limit of x-axis
  ylim(0,220) + ## set limit of y-axis
  scale_color_manual(values = c("red","blue")) + ## manually set colors
  theme_bw() ## set white background
## Warning: Removed 22 rows containing missing values (geom_point).

Next to scatterplots, we have a large number of other types of plots at our disposal. Importantly, some plots will be more informative than others, depending on the research question. Therefore, choosing the plot that is most informative is crucial. We show this with a more elaborate example later (Data_exploration_captopril.Rmd).

5.4 Final example

5.4.1 Goal

Set up a reference interval for the systolic blood pressure in the NHANES dataset.

5.4.2 Background

The captopril dataset, which we will explore and analyse later, holds information on 15 patients that have increased blood pressure values.

Before we may conduct such an experiment, we first need to know which values should be considered increased and which ones should be considered normal. To find an interval for values that are normal, we can set up a reference interval. To set up this interval, we will use the NHANES dataset. The BPSysAve column holds data on the systolic blood pressure. To select healthy subjects, we will need to subset the data.

5.4.3 Analysis

First, we will plot the data for all subjects for which we have all the required data and that are between 40 and 65 years old.

Note, that we calculate the standard deviation on the average blood pressure measurements. If you have the same number of “technical repeats” on the same person you are allowed to average them first. Note, however, that if there are some technical measurements missing, averaging is no longer allowed because then the averaged values will be heteroscedastic: some averages are based on more technical repeats than others so they will have a different standard deviation.

## histogram of BPSysAve for subjects wit age between 40 and 65 years old
NHANES %>%
  filter(!is.na(Race1), !is.na(Smoke100n), !is.na(BMI_WHO), !is.na(Age), !is.na(HardDrugs), !is.na(HealthGen), !is.na(Gender), !is.na(AlcoholYear), !is.na(BPSys1), !is.na(BPSys2), !is.na(BPSys3), !is.na(SleepTrouble)) %>% ## retains 4660 subjects with the required data
  filter(between(Age,40,65)) %>% ## filter the subjects on age, retains 2522 subjects
  distinct(ID,.keep_all=TRUE) %>% ## removes duplicated IDs, retains 1467 subjects
  ggplot(aes(x=BPSysAve)) +
  geom_histogram()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

An important requirement of calculating reference intervals is that the data are normally distributed. this is clearly not the case; the data has a long right tail, which is quite common in biological data (in this easier to have extreme values on the right-hand side than on the left-hand size, which is additionally bounded by zero for most biological variables).

By selecting only healthy subjects, we expect the data to be distributed more normally. We define healthy as being a non-smoker, without a history of diabetes, hard drugs or sleeping trouble, with a general health that is not considered poor and that has a BMI between 18.5 and 29.9.

## histogram of BPSysAve for HEALTHY male subjects with age between 40 and 65 years old
NHANES %>%
  filter(!is.na(Race1), !is.na(Smoke100n), !is.na(BMI_WHO), !is.na(Age), !is.na(HardDrugs), !is.na(HealthGen), !is.na(Gender), !is.na(AlcoholYear), !is.na(BPSys1), !is.na(BPSys2), !is.na(BPSys3), !is.na(SleepTrouble)) %>% ## retains 4660 subjects with the required data
  filter(between(Age,40,65)) %>% ## filter the subjects on age, retains 2522 subjects
  distinct(ID,.keep_all=TRUE) %>% ## removes duplicated IDs, retains 1467 subjects
  filter(Smoke100n == "Non-Smoker", Diabetes == "No", HardDrugs == "No", HealthGen != "Poor", SleepTrouble == "No", BMI_WHO%in%c("18.5_to_24.9","25.0_to_29.9")) %>% ## filter to have only healthy subjects, based on multiple health criteria. Retains 275 subjects.
  ggplot(aes(x=BPSysAve)) +
  geom_histogram()
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

We will evaluate if the distribution is normally distributed using a QQ-plot. In this plot we compare the quantiles in the data with the quantiles of the normal distribution. If the data are normally distributed then the data in the QQ-plot approximately follows a straight line. Deviations from the straight line indicate deviations of normality.

NHANES %>%
  filter(!is.na(Race1), !is.na(Smoke100n), !is.na(BMI_WHO), !is.na(Age), !is.na(HardDrugs), !is.na(HealthGen), !is.na(Gender), !is.na(AlcoholYear), !is.na(BPSys1)&!is.na(BPSys2)&!is.na(BPSys3), !is.na(SleepTrouble)) %>%
  filter(between(Age,40,65)) %>% 
  distinct(ID,.keep_all=TRUE) %>% 
  filter(Smoke100n == "Non-Smoker", Diabetes == "No", HardDrugs == "No", HealthGen != "Poor", SleepTrouble == "No", BMI_WHO%in%c("18.5_to_24.9","25.0_to_29.9")) %>% 
  ggplot(aes(sample=BPSysAve))+
  geom_qq()+
  geom_qq_line()

We observe that the distribution is slightly skewed too the right. We will therefore log transform the bloodpressure measurements. We use the log2 transformation: a difference of 1 on the log scale corresponds to doubling the blood pressure on the original scale: \(log2(B)-log2(A)=log2(B/A)\) \(2^{1}=2\).

NHANES %>%
  filter(!is.na(Race1), !is.na(Smoke100n), !is.na(BMI_WHO), !is.na(Age), !is.na(HardDrugs), !is.na(HealthGen), !is.na(Gender), !is.na(AlcoholYear), !is.na(BPSys1)&!is.na(BPSys2)&!is.na(BPSys3), !is.na(SleepTrouble)) %>%
  filter(between(Age,40,65)) %>% 
  distinct(ID,.keep_all=TRUE) %>% 
  filter(Smoke100n == "Non-Smoker", Diabetes == "No", HardDrugs == "No", HealthGen != "Poor", SleepTrouble == "No", BMI_WHO%in%c("18.5_to_24.9","25.0_to_29.9")) %>% 
  ggplot(aes(sample=BPSysAve %>% log2))+
  geom_qq()+
  geom_qq_line()

We see that the data is normally distributed upon log transformation. We can now use the normal distribution to construct a 95% interval at log scale that we will backtransform to the original scale.

## to get the 95% reference interval for the healthy group;

summary_NHANES <- NHANES %>%
  filter(!is.na(Race1), !is.na(Smoke100n), !is.na(BMI_WHO), !is.na(Age), !is.na(HardDrugs), !is.na(HealthGen), !is.na(Gender), !is.na(AlcoholYear), !is.na(BPSys1), !is.na(BPSys2), !is.na(BPSys3), !is.na(SleepTrouble)) %>% ## retains 4660 subjects with the required data
  filter(between(Age,40,65)) %>% ## filter the subjects on age, retains 2522 subjects
  distinct(ID,.keep_all=TRUE) %>% ## removes duplicated IDs, retains 1467 subjects
  filter(Smoke100n == "Non-Smoker", Diabetes == "No", HardDrugs == "No", HealthGen != "Poor", SleepTrouble == "No", BMI_WHO%in%c("18.5_to_24.9","25.0_to_29.9")) %>% 
  mutate(BPSysAveLog=BPSysAve %>% log2) %>%
  summarize_at("BPSysAveLog",
               list(mean=~mean(.,na.rm=TRUE),
                    sd=~sd(.,na.rm=TRUE),
                    n=function(x) x%>%is.na%>%`!`%>%sum)) %>%
  mutate(se=sd/sqrt(n))

paste("Mean value (log2):", summary_NHANES$mean)
## [1] "Mean value (log2): 6.88522582539534"
paste("Geometric mean value (log2):", 2^summary_NHANES$mean)
## [1] "Geometric mean value (log2): 118.211437989542"
paste("Standard deviation (log2):", summary_NHANES$sd)
## [1] "Standard deviation (log2): 0.168454138831745"
paste0("Reference interval (log2): [", summary_NHANES$mean - 2*summary_NHANES$sd, ";", summary_NHANES$mean + 2*summary_NHANES$sd, "]")
## [1] "Reference interval (log2): [6.54831754773185;7.22213410305883]"
paste0("Reference interval (log2): [", 2^(summary_NHANES$mean - 2*summary_NHANES$sd), ";", 2^(summary_NHANES$mean + 2*summary_NHANES$sd), "]")
## [1] "Reference interval (log2): [93.5922747749331;149.306597207509]"

The log2 systolic blood pressure for healthy subjects is approximately normally distributed. We have calculated the mean and standard deviation of blood pressure values in this healthy subset at the log2 scale, which allows us to set up the (95%) reference interval, for what we can consider to be normal blood pressure values. Any of the later patients who’s values do not fall within this interval, we can consider abnormal.

The log2 mean blood pressure value of the healthy subset is 6.89, with a standard deviation of 0.17. When we replace the population average and population standard deviation with these estimates, we obtain a 95% reference interval of [6.55;7.22] on log scale and [93.59,149.31] mmHg on the original scale.

Note, that in the literature a value 140 mmHg for the systolic blood pressure is typically considered to be the upper limit of normality.


LS0tCnRpdGxlOiAiVHV0b3JpYWwgNC4xOiBFeHBsb3JpbmcgdGhlIE5IQU5FUyBkYXRhc2V0IiAgIApvdXRwdXQ6CiAgICBodG1sX2RvY3VtZW50OgogICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlICAgIAogICAgICB0aGVtZTogY29zbW8KICAgICAgdG9jOiB0cnVlCiAgICAgIHRvY19mbG9hdDogdHJ1ZQogICAgICBoaWdobGlnaHQ6IHRhbmdvCiAgICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQotLS0KCkluIHRoaXMgdHV0b3JpYWwsIHdlIHdpbGwgbGVhcm4gaG93IHRvIGltcG9ydCwgdGlkeSwgd3JhbmdsZSBhbmQgCnZpc3VhbGl6ZSBkYXRhLiBXZSB3aWxsIHdvcmsgd2l0aCBvbmUgc3BlY2lmaWMgZGF0YXNldDsKCiMgVGhlIE5IQU5FUyBkYXRhc2V0CgpUaGUgTmF0aW9uYWwgSGVhbHRoIGFuZCBOdXRyaXRpb24gRXhhbWluYXRpb24gU3VydmV5IChOSEFORVMpCmNvbnRhaW5zIGRhdGEgdGhhdCBoYXMgYmVlbiBjb2xsZWN0ZWQgc2luY2UgMTk2MC4gRm9yIHRoaXMgdHV0b3JpYWwsCndlIHdpbGwgbWFrZSB1c2Ugb2YgdGhlIGRhdGEgdGhhdCB3ZXJlIGNvbGxlY3RlZCBiZXR3ZWVuIDIwMDkgYW5kIAoyMDEyLCBmb3IgMTAuMDAwIFUuUy4gY2l2aWxpYW5zLiBUaGUgZGF0YXNldCBjb250YWlucyBhIGxhcmdlIG51bWJlciBvZgpwaHlzaWNhbCwgZGVtb2dyYXBoaWMsIG51dHJpdGlvbmFsIGFuZCBsaWZlLXN0eWxlLXJlbGF0ZWQgcGFyYW1ldGVycy4KCkhvd2V2ZXIsIGJlZm9yZSB3ZSBjYW4gYWN0dWFsbHkgc3RhcnQgd29ya2luZyB3aXRoIGRhdGEsIHdlIHdpbGwgbmVlZCB0byAKbGVhcm4gaG93IHRvIGltcG9ydCB0aGUgcmVxdWlyZWQgZGF0YXNldHMgaW50byBvdXIgUnN0dWRpbyBlbnZpcm9ubWVudC4KCiMgRGF0YSBJbXBvcnQgIHdpdGggdGhlIGByZWFkcmAgUiBwYWNrYWdlICAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHJlYWRyKQpgYGAKCkxldCdzIHRyeSByZWFkaW5nIGluIHNvbWUgZGF0YS4gV2Ugd2lsbCBiZWdpbiBieQpyZWFkaW5nIGluIHRoZSBgTkhBTkVTLmNzdmAgZGF0YXNldC4gCgpgYGB7ciBuaGFuZXNEYXRFeHBsfQpOSEFORVMgPC0gcmVhZF9jc3YoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9HVFBCL1BTTFMyMC9tYXN0ZXIvZGF0YS9OSEFORVMuY3N2IikKYGBgCgpgYGB7cn0KaGVhZChOSEFORVMpICMjIHRha2UgYSBsb29rIGF0IHRoZSBmaXJzdCByb3dzIG9mIHRoZSBkYXRhc2V0CmBgYAoKYGBge3J9CnRhaWwoTkhBTkVTKSAjIyB0YWtlIGEgbG9vayBhdCB0aGUgbGFzdCByb3dzIG9mIHRoZSBkYXRhc2V0CmBgYAoKYGBge3J9CiNrbml0cjo6a2FibGUoTkhBTkVTW2MoMSw0LDUsNiw3LDgpLGMoMSwzLDQsNywxNywyMCwyMSwyNSldLGZvcm1hdCA9ICJtYXJrZG93biIpCk5IQU5FU1tjKDEsNCw1LDYsNyw4KSxjKDEsMyw0LDcsMTcsMjAsMjEsMjUpXSAjIyB0YWtlIGEgbG9vayBhdCBhIHN1YnNldCBvZiB0aGUgZGF0YXNldApgYGAKCiMjIFRha2UgYSBgZ2xpbXBzZSgpYCBhdCB5b3VyIGRhdGEKClRoZSBnbGltcHNlIGZ1bmN0aW9uIGFsbG93cyB1cyB0byAob2J2aW91c2x5KSB0YWtlCmEgZmlyc3QsIGluZm9ybWF0aXZlIGdsaW1wc2UgYXQgb3VyIGRhdGEuIFRoZSBmdW5jdGlvbgppcyBwYXJ0IG9mIHRoZSBgZHBseXJgLCB3aGljaCB3ZSB3aWxsIGV4cGxvcmUgaW4gbXVjaAptb3JlIGRldGFpbCBiZWxvdyEKCmBgYHtyfQpkcGx5cjo6Z2xpbXBzZShOSEFORVNbLDE6MTBdKQojIG9yIGdsaW1wc2UoTkhBTkVTKSB0byBzZWUgYWxsIHRoZSB2YXJpYWJsZXMgaW4gdGhlIGRhdGFzZXQKYGBgCgoKIyBEYXRhIFRpZHlpbmcKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgoKKiogSW1wb3J0YW50ICoqIApJZiB5b3UgYXJlIG5vdCBmYW1pbGlhciB5ZXQgd2l0aCB0aGUgY29uY2VwdHMgb2YgdGlkeSBkYXRhLApoYXZlIGEgbG9vayBhdCB0aGUgcHJlbGltaW5hcnlfdGlkeXZlcnNlLlJtZCBmaWxlIQoKSWYgd2UgY29uc2lkZXIgb3VyIGBOSEFORVNgIGRhdGFmcmFtZSwgd2Ugc2VlIGl0IGlzIGFscmVhZHkgaW4gCmEgdGlkeSBmb3JtYXQsIGFzOwoKKiBFYWNoIHZhcmlhYmxlIGZvcm1zIGEgY29sdW1uLgoqIEVhY2ggb2JzZXJ2YXRpb24gZm9ybXMgYSByb3cuCiogRWFjaCB0eXBlIG9mIG9ic2VydmF0aW9uYWwgdW5pdCBmb3JtcyBhIHRhYmxlLgoKRWFjaCByb3cgY29udGFpbnMgYWxsIG9mIHRoZSBpbmZvcm1hdGlvbiBvbiAKYSBzaW5nbGUgc3ViamVjdCAoVVMgY2l2aWxpYW4pIGluIHRoZSBzdHVkeS4gCgpJbiB0aGUgbmV4dCB0dXRvcmlhbCwgd2Ugd2lsbCB3b3JrIHdpdGggYSBkYXRhc2V0IG9uIHRoZSAKZWZmZWN0cyBvZiBhIGNlcnRhaW4gZHJ1ZywgX2NhcHRvcHJpbF8sIG9uIHRoZSBzeXN0b2xpYwphbmQgZGlhc3RvbGljIGJsb29kIHByZXNzdXJlIG9mIHBhdGllbnRzLiBUaGlzIHdpbGwgbm90IGJlIAphIF90aWR5XyBkYXRhc2V0LiBBcyBzdWNoLCB0aGUgZGV0YWlscyBvZiB0aWR5aW5nIGRhdGEgCndpdGggdGlkeXZlcnNlIHdpbGwgYmUgZGVzY3JpYmVkIHRoZXJlCgoKIyBEYXRhIHdyYW5nbGluZyB3aXRoIGBkcGx5cmAKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpgYGAKCgpUaGUgYGRwbHlyYCBwYWNrYWdlIHByb3ZpZGVzIHVzIHdpdGggYSBsYXJnZSBzZXQgb2YgCmZ1bmN0aW9ucyBmb3IgaGFuZGxpbmcgb3VyIGRhdGEuIFdlIHJlZmVyIHRvIHRoZSAKcHJlbGltaW5hcnlfdGlkeXZlcnNlLlJtZCBmaWxlIGZvciBhIG1vcmUgZGV0YWlsZWQKZGVzY3JpcHRpb24gb2YgdGhlc2UgZnVuY3Rpb25zLgoKVGhlIG1vc3QgaW1wb3J0YW50IGBkcGx5cmAgZnVuY3Rpb25zIHRvIHJlbWVtYmVyIGFyZTogCgpgZHBseXJgIHZlcmJzIHwgRGVzY3JpcHRpb24gfAotLS0gfCAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0gfApgc2VsZWN0KClgIHwgc2VsZWN0IGNvbHVtbnMgIHwKYGZpbHRlcigpYCB8IGZpbHRlciByb3dzIHwKYGFycmFuZ2UoKWAgfCByZS1vcmRlciBvciBhcnJhbmdlIHJvd3MgfApgbXV0YXRlKClgIHwgY3JlYXRlIG5ldyBjb2x1bW5zIHwKYHN1bW1hcml6ZSgpYCB8IHN1bW1hcml6ZSB2YWx1ZXMgfApgZ3JvdXBfYnkoKWAgfCBhbGxvd3MgZm9yIGdyb3VwIG9wZXJhdGlvbnMgaW4gdGhlICJzcGxpdC1hcHBseS1jb21iaW5lIiBjb25jZXB0IHwKCkluIGFkZGl0aW9uLCBgZHBseXJgIHVzZXMgdGhlIHBpcGUgb3BlcmF0b3IgYCU+JWAgdG8gCnVzZSB0aGUgb3V0cHV0IG9mIG9uZSBmdW5jdGlvbiBhcyBhbiBpbnB1dCB0byB0aGUgbmV4dApmdW5jdGlvbi4gSW5zdGVhZCBvZiBuZXN0aW5nIGZ1bmN0aW9ucyAocmVhZGluZyBmcm9tIHRoZSAKaW5zaWRlIHRvIHRoZSBvdXRzaWRlKSwgdGhlIGlkZWEgb2Ygb2YgcGlwaW5nIGlzIHRvIApyZWFkIHRoZSBmdW5jdGlvbnMgZnJvbSBsZWZ0IHRvIHJpZ2h0LiAKCiMjIFNlbGVjdCBhbmQgZmlsdGVyCgpIZXJlIHdlIHdpbGwgZGVtb25zdHJhdGUgdGhlIGBkcGx5cmAgZnVuY3Rpb25hbGl0aWVzIHdpdGgKYSBjb3VwbGUgb2Ygc21hbGwgZXhhbXBsZXMuCgpMZXQncyBzYXkgd2Ugd2FudCB0byBpbnZlc3RpZ2F0ZSBob3cgbWFueSBzdWJqZWN0cyBhcmUKMS4gbWVuIHRoYXQKMi4gYXJlIG9sZGVyIHRoYW4gMTggeWVhcnMgb2xkLAozLiBhcmUgdGFsbGVyIHRoYW4gMTUwIGNtLCBhbmQKNC4gaGF2ZSB3ZWlnaHQgb2YgbGVzcyB0aGFuIDgwa2cKCmBgYHtyIHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0KTkhBTkVTICU+JSAKICBzZWxlY3QoYygiR2VuZGVyIiwiQWdlIiwiV2VpZ2h0IiwiSGVpZ2h0IikpICU+JSAjIyBzZWxlY3QgdGhlIGNvbHVtbnMgb2YgaW50ZXJlc3QKICBmaWx0ZXIoR2VuZGVyID09ICJtYWxlIiwgCiAgICAgICAgIEFnZSA+IDE4LAogICAgICAgICBIZWlnaHQgPiAxNTAsCiAgICAgICAgIFdlaWdodCA8IDgwKSAlPiUgIyMgZmlsdGVyIG9ic2VydmF0aW9ucyAocm93cykgYmFzZWQgb24gdGhlIHJlcXVpcmVkIHZhbHVlcwogIG5yb3coKQpgYGAKCkZpbHRlcmluZyB0aGUgZGF0YXNldCBiYXNlZCBvbiB0aGVlIHRocmVlIGZpbHRlcmluZyBjcml0ZXJpYQpyZXRhaW4gMy42MDEgc3ViamVjdHMuIE5vdGUgdGhhdCB0aGUgYHNlbGVjdGAgc3RlcCB3YXMgbm90CnN0cmljdGx5IG5lY2Vzc2FyeSwgYnV0IHNpbmNlIHdlIGNvdWxkIGFuc3dlciB0aGUgcXVlc3Rpb24KYmFzZWQgb24gb25seSB0aGVzZSA0IGNvbHVtbnMsIHRoZXJlIGlzIG5vIG5lZWQgdG8gcmV0YWluIHRoZQp0aGUgcmVzdCBvZiB0aGUgZGF0YSAoaWYgdGhpcyBpcyB0aGUgb25seSBxdWVzdGlvbikuCgojIyBBcnJhbmdlCgpOZXh0IHF1ZXN0aW9uOyBXaXRoaW4gdGhlIFJhY2UxIGNhdGVnb3J5IG9mIEhpc3Bhbmljcywgc2VsZWN0Cm1lbiB0aGF0IGFyZSBub3QgbWFycmllZCBhbmQgZGlzcGxheSB0aGUgZmlyc3QgZml2ZSBieSAKZGVzY2VuZGluZyBoZWlnaHQuCgoqKkhpbnQqKjogdXNlIHRoZSBgZGVzYygpYCBmdW5jdGlvbiBpbnNpZGUgb2YKYGFycmFuZ2UoKWAgdG8gb3JkZXIgcm93cyBpbiBhIGRlc2NlbmRpbmcgb3JkZXIuIApVc2UgdGhlIGJhc2UgUiBmdW5jdGlvbiBgaGVhZGAgdG8gZGlzcGxheSB0aGUgZmlyc3QKZml2ZS4KCmBgYHtyIHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0KTkhBTkVTICU+JQogIHNlbGVjdChjKCJSYWNlMSIsICJHZW5kZXIiLCJNYXJpdGFsU3RhdHVzIiwiSGVpZ2h0IikpICU+JQogIGZpbHRlcihNYXJpdGFsU3RhdHVzICE9ICJNYXJyaWVkIiwgUmFjZTEgPT0gIkhpc3BhbmljIiwgR2VuZGVyID09ICJtYWxlIikgJT4lIAogIGFycmFuZ2UoZGVzYyhIZWlnaHQpKSAlPiUgCiAgaGVhZChuPTUpCmBgYAoKV2UgaGF2ZSBzdWNjZXNzZnVsbHkgY29tYmluZWQgdGhyZWUgb2YgdGhlIG1haW4gYGRwbHlyYApmdW5jdGlvbmFsaXRpZXMuIExldCdzIHRyeSB0byBleHBsb3JlIGFub3RoZXIgb25lIQoKIyMgTXV0YXRlCgpBc3N1bWUgdGhhdCB3ZSBkb24ndCB0cnVzdCB0aGUgQk1JIHZhbHVlcyBpbiBvdXIgZGF0YXNldAphbmQgd2UgZGVjaWRlIHRvIGNhbGN1bGF0ZSB0aGVtIG91cnNlbHZlcy4gQk1JIGlzIHR5cGljYWxseSAKY2FsY3VsYXRlZCBieSB0YWtpbmcgYSBwZXJzb24ncyB3ZWlnaHQgKGluIGtnKSBhbmQgZGl2aWRpbmcKaXQgYnkgdGhlIGl0cyBoZWlnaHQgKGluIG0pIHNxdWFyZWQuIEJhc2VkIG9uIHRoaXMgcnVsZSwKZ2VuZXJhdGUgYSBuZXcgY29sdW1uLCBCTUlfc2VsZiwgZm9yIHRoZSBzdWJzZXQgb2YgdGhlCk5IQU5FUyBkYXRhc2V0IHRoYXQgd2Ugb2J0YWluZWQgZnJvbSB0aGUgcHJldmlvdXMgcXVlc3Rpb24uClRvIGNyZWF0ZSBuZXcgY29sdW1ucywgd2Ugd2lsbCB1c2UgdGhlIGBtdXRhdGUoKWAgZnVuY3Rpb24gCmluIGBkcGx5cmAuIAoKYGBge3J9Ck5IQU5FUyAlPiUKICBzZWxlY3QoYygiUmFjZTEiLCAiR2VuZGVyIiwiTWFyaXRhbFN0YXR1cyIsIkhlaWdodCIsIldlaWdodCIsIkJNSSIpKSAlPiUKICBmaWx0ZXIoTWFyaXRhbFN0YXR1cyAhPSAiTWFycmllZCIsIFJhY2UxID09ICJIaXNwYW5pYyIsIEdlbmRlciA9PSAibWFsZSIpICU+JSAKICBtdXRhdGUoQk1JX3NlbGYgPSBXZWlnaHQvKEhlaWdodC8xMDApKioyKQpgYGAKCk5vdGUgdGhhdCBub3cgd2UgbmVlZCB0byBpbmNsdWRlIHRoZSBfV2VpZ2h0XyBhbmQgX0JNSV8KY29sdW1ucyBpbiB0aGUgYHNlbGVjdGAgc3RhdGVtZW50LCBiZWNhdXNlIHdlIG5lZWQgdGhhdAppbnB1dCBmb3IgdGhlIGBtdXRhdGVgIGZ1bmN0aW9uLiBHb29kIG5ld3M6IEl0IHR1cm5zIG91dCAKdGhhdCB0aGUgQk1JIGNvbHVtbiBpbiB0aGUgb3JpZ2luYWwgZGF0YXNldCB3YXMgY29tcHV0ZWQKY29ycmVjdGx5IGFmdGVyIGFsbCEgQnV0IG5vdyB3ZSBhcmUgaW50ZXJlc3RlZCBpbiB0aGUKbWVhbiB2YWx1ZSBvZiB0aGUgQk1JX1NlbGYgY29sdW1uLiBUaGlzIHJlcXVpcmVzIGEgZmlmdGgKYGRwbHlyYCBmdW5jdGlvbjogYHN1bW1hcml6ZSgpYC4KCgojIyBzdW1tYXJpemUKCkdpdmVuIHRoZSBmaWx0ZXJpbmcgb2YgdGhlIHF1ZXN0aW9uIGFib3ZlLCBjb21wdXRlIHRoZSBtZWFuCnZhbHVlIG9mIHRoZSBCTUlfc2VsZiBjb2x1bW4uCgpgYGB7cn0KTkhBTkVTICU+JQogIHNlbGVjdChjKCJSYWNlMSIsICJHZW5kZXIiLCJNYXJpdGFsU3RhdHVzIiwiSGVpZ2h0IiwiV2VpZ2h0IiwiQk1JIikpICU+JQogIGZpbHRlcihNYXJpdGFsU3RhdHVzICE9ICJNYXJyaWVkIiwgUmFjZTEgPT0gIkhpc3BhbmljIiwgR2VuZGVyID09ICJtYWxlIikgJT4lIAogIG11dGF0ZShCTUlfc2VsZiA9IFdlaWdodC8oSGVpZ2h0LzEwMCkqKjIpICU+JQogIHN1bW1hcml6ZShhdmdfQk1JX3NlbGYgPSBtZWFuKEJNSV9zZWxmLCBuYS5ybSA9IFRSVUUpKQojIyB0aGUgYWRkaXRpb24gYXJndW1lbnQgbmEucm0gPSBUUlVFIG1ha2VzIHN1cmUgdG8gcmVtb3ZlIG1pc3NpbmcKIyMgdmFsdWVzIGZvciB0aGUgcHVycG9zZSBvZiBjYWxjdWxhdGluZyB0aGUgbWVhbgpgYGAKCkZvciB0aGlzIHBhcnRpY3VsYXIgc3Vic2V0IG9mIHRoZSBkYXRhLCB3ZSBmaW5kIGFuCmF2ZXJhZ2UgQk1JIHZhbHVlIG9mIDI4LjU5IGtnL20qKjIuCgpUaGVyZSBhcmUgbWFueSBvdGhlciBzdW1tYXJ5IHN0YXRpc3RpY3MgeW91IApjb3VsZCBjb25zaWRlciBzdWNoIGBzZCgpYCwgYG1pbigpYCwgYG1lZGlhbigpYCwgCmBtYXgoKWAsIGBzdW0oKWAsIGBuKClgIChyZXR1cm5zIHRoZSBsZW5ndGggb2YgdmVjdG9yKSwgCmBmaXJzdCgpYCAocmV0dXJucyBmaXJzdCB2YWx1ZSBpbiB2ZWN0b3IpLCAKYGxhc3QoKWAgKHJldHVybnMgbGFzdCB2YWx1ZSBpbiB2ZWN0b3IpIGFuZCAKYG5fZGlzdGluY3QoKWAgKG51bWJlciBvZiBkaXN0aW5jdCB2YWx1ZXMgaW4gdmVjdG9yKS4gCldlIHdpbGwgZWxhYm9yYXRlIG9uIHRoZXNlIGZ1bmN0aW9ucyBsYXRlci4KCk5vdGUgdGhhdCBjaG9vc2luZyB0aGUgbW9zdCBpbmZvcm1hdGl2ZSBzdW1tYXJ5IHN0YXRpc3RpYwppcyB2ZXJ5IGltcG9ydGFudCEgVGhpcyBjYW4gYmUgc2hvd24gd2l0aCB0aGUgZm9sbG93aW5nIGV4YW1wbGU7CgoqKiBjaGFuZ2UgbG9jYXRpb24gdG8gZ2l0aHViIGxvY2F0aW9uICoqCiFbL1VzZXJzL2pnL0Rlc2t0b3AvUGhEL1RlYWNoaW5nL0xpc2Jvbl8yMDIwL3BzbHMyMC9kYXRhL0ZpZ3VyZV9wYXJ0bmVycy5wbmcpe3dpZHRoPTYwJX0KCkluIHRoaXMgc3R1ZHksIG1lbiBhbmQgd29tYW4gd2VyZSBhc2tlZCBhYm91dCB0aGVpciAKImlkZWFsIG51bWJlciBvZiBwYXJ0bmVycyBkZXNpcmVkIG92ZXIgMzAgeWVhcnMiLgpXaGlsZSBhbG1vc3QgYWxsIHN1YmplY3RzIGRlc2lyZWQgYSBudW1iZXIgYmV0d2VlbiAwIGFuZCA1MCwKdGhyZWUgbWFsZSBzdWJqZWN0cyBzZWxlY3RlZCBhIG51bWJlciBhYm92ZSAxMDAuClRoZXNlIHRocmVlIF9vdXRsaWVyc18gaW4gdGhlIGRhdGEgY2FuIGhhdmUgYSBsYXJnZSBpbXBhY3Qgb24KdGhlIGRhdGEgYW5hbHlzaXMsIGVzcGVjaWFsbHkgd2hlbiB3ZSB3b3JrIHdpdGggc3VtbWFyeSAKc3RhdGlzdGljcyB0aGF0IGFyZSBzZW5zaXRpdmUgdG8gdGhlc2Ugb3V0bGllcnMuIAoKV2hlbiB3ZSBsb29rIGF0IHRoZSBfbWVhbl8sIGZvciBpbnN0YW5jZSwgd2Ugc2VlIHRoYXQgb24gYXZlcmFnZSAKd29tYW4gZGVzaXJlIDIuOCBwYXJ0bmVycywgd2hpbGUgbWVuIGRlc2lyZSA2NC4zIHBhcnRuZXJzIG9uIGF2ZXJhZ2UsIApzdWdnZXN0aW5nIGEgbGFyZ2UgZGlzY3JlcGFuY3kgYmV0d2VlbiBtYWxlIGFuZCBmZW1hbGUgZGVzaXJlcy4KCkhvd2V2ZXIsIGlmIGxvb2sgYXQgYSBtb3JlIF9yb2J1c3RfIHN1bW1hcnkgc3RhdGlzdGljIHN1Y2ggYXMKdGhlIF9tZWRpYW5fLCB3ZSBzZWUgdGhhdCB0aGUgcmVzdWx0IGlzIDEgZm9yIGJvdGggbWVuIGFuZCB3b21hbi4KSXQgaXMgY2xlYXIgdGhhdCB0aGUgbWVhbiB2YWx1ZSB3YXMgY29tcGxldGVseSBkaXN0b3J0ZWQgYnkgdGhlCnRocmVlIG91dGxpZXJzIGluIHRoZSBkYXRhLgoKQW5vdGhlciBleGFtcGxlIG9mIGEgbW9yZSByb2J1c3Qgc3VtbWFyeSBzdGF0aXN0aWMgaXMgCnRoZSBfZ2VvbWV0cmljIG1lYW5fLgoKCiMjIEdyb3VwCgpXZSBoYXZlIGFscmVhZHkgY29tYmluZWQgNSB2ZXJ5IGltcG9ydGFudCBmdW5jdGlvbnMuIEhlcmUKd2Ugd2lsbCBhZGQgYSBmaW5hbCBvbmU6IGBncm91cF9ieSgpYC4KClRoZSBgZ3JvdXBfYnkoKWAgdmVyYiBpcyBhbmQgaW5jcmVkaWJseSBwb3dlcmZ1bApmdW5jdGlvbiBpbiBgZHBseXJgLiBJdCBhbGxvd3MgdXMsIGZvciBleGFtcGxlLAp0byBjYWxjdWxhdGUgc3VtbWFyeSBzdGF0aXN0aWNzIGZvciBkaWZmZXJlbnQKZ3JvdXBzIG9mIG9ic2VydmF0aW9ucy4KCklmIHdlIHRha2Ugb3VyIGV4YW1wbGUgZnJvbSBhYm92ZSwgbGV0J3Mgc2F5IAp3ZSB3YW50IHRvIHNwbGl0IHRoZSBkYXRhIGZyYW1lIGJ5IHNvbWUgdmFyaWFibGUgCihlLmcuIGBNYXJpdGFsU3RhdHVzYCksIGFwcGx5IGEgZnVuY3Rpb24gKGBtZWFuYCkgCnRvIGEgY29sdW1uIChlLmcuIEJNSV9zZWxmKSBvZiB0aGUgaW5kaXZpZHVhbCAgCmRhdGEgZnJhbWVzICBhbmQgdGhlbiBjb21iaW5lIHRoZSBvdXRwdXQgYmFjayBpbnRvIAphIHN1bW1hcnkgZGF0YSBmcmFtZS4gCgpgYGB7cn0KTkhBTkVTICU+JSAKICBzZWxlY3QoYygiUmFjZTEiLCAiR2VuZGVyIiwiTWFyaXRhbFN0YXR1cyIsIkhlaWdodCIsIldlaWdodCIsIkJNSSIpKSAlPiUKICBmaWx0ZXIoTWFyaXRhbFN0YXR1cyAhPSAiTWFycmllZCIsIFJhY2UxID09ICJIaXNwYW5pYyIsIEdlbmRlciA9PSAibWFsZSIpICU+JQogIG11dGF0ZShCTUlfc2VsZiA9IFdlaWdodC8oSGVpZ2h0LzEwMCkqKjIpICU+JQogIGdyb3VwX2J5KE1hcml0YWxTdGF0dXMpICU+JSAjIyAgZ3JvdXAgdGhlIHN1YmplY3RzIGJ5IHRoZWlyIG1hcml0YWwgc3RhdHVzCiAgc3VtbWFyaXplKGF2Z19CTUlfc2VsZiA9IG1lYW4oQk1JX3NlbGYsIG5hLnJtID0gVFJVRSkpICMjIGNhbGN1bGF0ZSB0aGUgbWVhbiBCTUlfc2VsZiB2YWx1ZSBvZiBlYWNoIGdyb3VwCmBgYAoKV2UgaGF2ZSBzdWNjZXNzZnVsbHkgY29tYmluZWQgc2l4IG9mIHRoZSBtb3N0IGltcG9ydGFudApgZHBseXJgIGZ1bmN0aW9uYWxpdGllcyEKCk5vdyB0aGF0IHdlIGhhdmUgYWxsIHRoZSByZXF1aXJlZCBmdW5jdGlvbnMgZm9yCmltcG9ydGluZywgdGlkeWluZyBhbmQgd3JhbmdsaW5nIGRhdGEgaW4gcGxhY2UsCndlIHdpbGwgbGVhcm4gaG93IHRvIHZpc3VhbGl6ZSBvdXIgZGF0YSB3aXRoIHRoZQpnZ3Bsb3QyIHBhY2thZ2UuCgojIERhdGEgVmlzdWFsaXphdGlvbgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKYGBgCgoKQXMgeW91IG1pZ2h0IGhhdmUgaGF2ZSBhbHJlYWR5IHNlZW4sIHRoZXJlIGFyZSBtYW55IGZ1bmN0aW9ucyAKYXZhaWxhYmxlIGluIGJhc2UgUiB0aGF0IGNhbiBjcmVhdGUgcGxvdHMgKGUuZy4gYHBsb3QoKWAsIGBib3hwbG90KClgKS4gCk90aGVycyBpbmNsdWRlOiBgaGlzdCgpYCwgYHFxcGxvdCgpYCwgZXRjLgpUaGVzZSBmdW5jdGlvbnMgYXJlIGdyZWF0IGJlY2F1c2UgdGhleSBjb21lIHdpdGggYSBiYXNpYyBpbnN0YWxsYXRpb24gCm9mIFIgYW5kIGNhbiBiZSBxdWl0ZSBwb3dlcmZ1bCB3aGVuIHlvdSBuZWVkIGEgcXVpY2sgdmlzdWFsaXphdGlvbiAKb2Ygc29tZXRoaW5nIHdoZW4geW91IGFyZSBleHBsb3JpbmcgZGF0YS4gCgpXZSBhcmUgY2hvb3NpbmcgdG8gaW50cm9kdWNlIGBnZ3Bsb3QyYCBiZWNhdXNlLCBpbiBvdXIgCm9waW5pb24sIGl0J3Mgb25lIG9mIHRoZSBtb3N0IHNpbXBsZSB3YXlzIGZvciBiZWdpbm5lcnMgdG8gCmNyZWF0ZSByZWxhdGl2ZWx5IGNvbXBsaWNhdGVkIHBsb3RzIHRoYXQgYXJlIGludHVpdGl2ZSAKYW5kIGFlc3RoZXRpY2FsbHkgcGxlYXNpbmcuCgojIyBVbml2YXJpYXRlIHN0YXRpc3RpY3MKCkluIHVuaXZhcmlhdGUgc3RhdGlzdGljcywgd2UgZm9jdXMgb24gYSBzaW5nbGUgdmFyaWFibGUgCm9mIGludGVyZXN0LiBIZXJlLCB3ZSB3aWxsIHNob3cgZGlmZmVyZW50IHdheXMgdG8gdmlzdWFsaXplIAp0aGUgYEJNSWAgdmFyaWFibGUgZnJvbSB0aGUgTkhBTkVTIGRhdGFzZXQuIEltcG9ydGFudGx5LApkaWZmZXJlbnQgdHlwZXMgb2YgdmlzdWFsaXphdGlvbnMgd2lsbCBwcm92aWRlIHVzIHdpdGggCmRpZmZlcmVudCB0eXBlcyBvZiBpbmZvcm1hdGlvbiEKCkhlcmUgd2Ugd2lsbCB2aXN1YWxpemUgdGhlIHNhbWUgZGF0YSB1c2luZyBhIGRvdHBsb3QsCmEgaGlzdG9ncmFtIGFuZCBhIGJveHBsb3QuCgojIyMgRG90cGxvdAoKYGBge3J9Ck5IQU5FUyAlPiUKICBmaWx0ZXIoIWlzLm5hKEJNSSkpICU+JQogIGhlYWQoTkhBTkVTLCBuPTEwMCkgJT4lICMjIHRvIG1ha2UgdGhlIHZpc3VhbGl6YXRpb24gbW9yZSBjbGVhciwgd2UgdGFrZSBvbmx5IHRoZSBmaXJzdCAxMDAgc3ViamVjdHMKICBnZ3Bsb3QoYWVzKHg9Qk1JKSkgKwogIGdlb21fZG90cGxvdChtZXRob2QgPSAiaGlzdG9kb3QiKSAKYGBgCgpJbiBzdGVhZCBvZiBtYWtpbmcgYSB2aXN1YWxpemF0aW9uICJvbiB0aGUgZmx5IgphcyB3ZSBkaWQgaW4gdGhlIGNvZGUgY2h1bmsgYWJvdmUsIHdlIG1heSBhbHNvIHN0b3JlCnRoZSB2aXN1YWxpemF0aW9uIGluIGFuIG9iamVjdC4gVGhpcyBhbGxvd3MgdXMgdG86Ci0gQ2FsbCB1cG9uIHRoZSBvYmplY3QgYWdhaW4gbGF0ZXIKLSBBZGQgZXh0cmEgbGF5ZXJzIHRvIHRoZSBwbG90LgoKYGBge3J9CiMjIHN0b3JlIHRoZSBwbG90IGZyb20gYWJvdmUgaW4gdGhlICJwIiBvYmplY3QKcCA8LSBOSEFORVMgJT4lCiAgZmlsdGVyKCFpcy5uYShCTUkpKSAlPiUKICBoZWFkKE5IQU5FUywgbj0xMDApICU+JQogIGdncGxvdChhZXMoeD1CTUkpKSArCiAgZ2VvbV9kb3RwbG90KG1ldGhvZCA9ICJoaXN0b2RvdCIpIAoKIyMgQWRkIGFkZGl0aW9uYWwgaW5mbyB0byB0aGUgcGxvdApwIDwtIHAgKyB4bGFiKCJCTUkgKGtnL20yKSIpICsgCiAgdGhlbWVfYncoKQoKIyMgRGlzcGxheSB0aGUgcGxvdApwCmBgYAoKTm90ZSB0aGF0IGlmIHdlIGFyZSBzdXJlIHdlIGRvIG5vdCB3YW50IHRvIHN0b3JlCnRoZSBwbG90IGZvciBsYXRlciByZXRyaWV2YWwsIHdlIHNob3VsZG4ndDogaXQgd291bGQKc2ltcGxlIGJlIGEgd2FzdGUgb2Ygc3RvcmFnZSBzcGFjZSEKCkluIHRoZSBkb3RwbG90LCB0aGUgQk1JIHZhbHVlIGZvciBlYWNoIHN1YmplY3QgKGluIHRoaXMgY2FzZSwKdGhlIGZpcnN0IDEwMCBzdWJqZWN0cyBvZiB0aGUgTkhBTkVTIHN0dWR5KSwgaXMgcGxvdHRlZCBvbiB0aGUKeC1heGlzLiBUaGUgeS1heGlzIGFsbG93cyB1cyB0byBjb3VudCBob3cgbWFueSBzdWJqZWN0cyBoYXZlIAphIEJNSSB2YWx1ZSB0aGF0IGZhbGxzIHdpdGhpbiBhIGNlcnRhaW4gY291bnRpbmcgYmluLgpGb3IgaW5zdGFuY2UsIGluIHRoZSBCTUkgaW50ZXJ2YWwgWzI5LjUsIDMwLlsgCndlIG9ic2VydmUgNCBzdWJqZWN0cy4KCk5vdGUsIGhvd2V2ZXIsIHRoYXQgdGhlIGRvdHBsb3QgZG9lcyBub3QgcHJvdmlkZSB1cyB3aXRoIGluZm9ybWF0aW9uCm9uIHRoZSBtZWFuL21lZGlhbiB2YWx1ZXMgb2YgdGhlIGRhdGEsIHdoaWNoIGFyZSB0eXBpY2FsbHkgZmVhdHVyZXMKb2YgaGlnaCBpbnRlcmVzdC4KCiMjIyBIaXN0b2dyYW0KCmBgYHtyfQpOSEFORVMgJT4lCiAgZmlsdGVyKCFpcy5uYShCTUkpKSAlPiUKICBoZWFkKE5IQU5FUywgbj0xMDApICU+JQogIGdncGxvdChhZXMoeD1CTUkpKSArCiAgZ2VvbV9oaXN0b2dyYW0oKQpgYGAKClRoZSBpbmZvcm1hdGlvbiB0aGF0IGNhbiBiZSBvYnRhaW5lZCBmcm9tIHRoaXMgaGlzdG9ncmFtCmlzIHNpbWlsYXIgdG8gdGhhdCBvZiB0aGUgZG90cGxvdC4gSGVyZSwgd2UgY2FuIHJlYWQgaW1tZWRpYXRlbHkKKGkuZS4gd2l0aG91dCBjb3VudGluZykgdGhhdCB0aGUgQk1JIGludGVydmFsIFsyOS41LCAzMC4wWyAKY29udGFpbnMgNCBzdWJqZWN0cy4gQWdhaW4sIHRoZSBoaXN0b2dyYW0gZG9lcyBub3QgcHJvdmlkZSB1cyB3aXRoIAppbmZvcm1hdGlvbiBvbiB0aGUgbWVhbi9tZWRpYW4gdmFsdWVzIG9mIHRoZSBkYXRhLCBidXQgb25seSBvbgppdHMgZGlzdHJpYnV0aW9uLgoKIyMjIEJveHBsb3QKCmBgYHtyfQpzZXQuc2VlZCgyKSAjIyB0byBtYWtlIHRoZSBob3Jpem9udGFsIHBvc2l0aW9uIG9mIHRoZSBqaXR0ZXIgbm9uLXJhbmRvbQoKZ2l2ZS5uIDwtIGZ1bmN0aW9uKHgpewogIHJldHVybihjKHkgPSA1MCwgbGFiZWwgPSBsZW5ndGgoeCkpKSAKfQoKTkhBTkVTICU+JQogIGZpbHRlcighaXMubmEoQk1JKSkgJT4lCiAgaGVhZChOSEFORVMsIG49MTAwKSAlPiUKICBnZ3Bsb3QoYWVzKHg9IiIsIHk9Qk1JKSkgKwogIGdlb21fYm94cGxvdChvdXRsaWVyLnNoYXBlPU5BKSArIAogIGdlb21faml0dGVyKHdpZHRoPTAuMikgKwogIHN0YXRfc3VtbWFyeShnZW9tPSJ0ZXh0IiwgZnVuLnk9cXVhbnRpbGUsCiAgICAgICAgICAgICAgIGFlcyhsYWJlbD1zcHJpbnRmKCIlMS4xZiIsIC4ueS4uKSksCiAgICAgICAgICAgICAgIHBvc2l0aW9uPXBvc2l0aW9uX251ZGdlKHg9MC41KSwgc2l6ZT00LjUpICsKICBhbm5vdGF0ZSgidGV4dCIsIHggPSBjKDEuNSwxLjUsMS41LDEuNSwxLjUsMS4wNSwxLjA1KSwgeSA9IGMoMTIuNSwxOSwyNC41LDI4LjUsNDUsMTgsMzUpLCBsYWJlbCA9IGMoIihtaW5pbXVtKSIsIigyNSUpIiwiKG1lZGlhbikiLCIoNzUlKSIsIihtYXhpbXVtKSIsIndoaXNrZXIiLCJ3aGlza2VyIiksc2l6ZT0zKSArCiAgc3RhdF9zdW1tYXJ5KGZ1bi5kYXRhID0gZ2l2ZS5uLCBnZW9tID0gInRleHQiKSAKYGBgCgpBcmd1YWJseSwgdGhlIGJveHBsb3QgaXMgdGhlIG1vc3QgaW5mb3JtYXRpdmUgZGVmYXVsdAp2aXN1YWxpemF0aW9uIHN0cmF0ZWd5LiBGaXJzdCwgaXQgcHJvdmlkZXMgdXMgd2l0aApzaW1pbGFyIGluc2lnaHRzIHRvIHRoZSBzaGFwZSBvZiB0aGUgZGlzdHJpYnV0aW9uIGFzCnRoZSBoaXN0b2dyYW0uIFNlY29uZCwgSXQgY2xlYXJseSBkaXNwbGF5cyBzZXZlcmFsIHVzZWZ1bApzdW1tYXJ5IHN0YXRpc3RpY3Mgc3VjaCBhcyB0aGUgbWVkaWFuLCB0aGUgaW50ZXJxdWFydGlsZQpyYW5nZSwgd2hpc2tlcnMgYW5kIG91dGxpZXJzLiBUaGlyZCwgd2l0aCB0aGUgZ2VvbV9qaXR0ZXIKZnVuY3Rpb25hbGl0eSB3ZSBjYW4gYWxzbyBwcm9qZWN0IGVhY2ggaW5kaXZpZHVhbCB2YWx1ZQpvZiB0aGUgZGF0YXNldCBvbiB0aGUgcGxvdC4KClRoZSBvbmx5IGRpc2FkdmFudGFnZSBvZiB0aGUgYm94cGxvdCBhcyBjb21wYXJlZCB0byB0aGUKZG90cGxvdCBvciBoaXN0b2dyYW0gaXMgdGhhdCB3ZSBjYW5ub3Qgc2VlIChmcm9tIHRoZSBmaWd1cmUpCmhvdyBtYW55IHN1YmplY3RzIGhhdmUgYSBCTUkgdmFsdWUgd2l0aGluIGEgY2VydGFpbiBpbnRlcnZhbC4KSG93ZXZlciwgdGhpcyBmZWF0dXJlIHVzdWFsbHkgaXMgbm90IG9mIHByaW1hcnkgaW50ZXJlc3QuCgojIyBCaXZhcmlhdGUgc3RhdGlzdGljcwoKSW4gYml2YXJpYXRlIHN0YXRpc3RpY3MsIHRoZSBnb2FsIGlzIHRvIHN0dWR5IHR3byB2YXJpYWJsZXMsCmluY2x1ZGluZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gYm90aCB2YXJpYWJsZXMuCgpJbiB0ZXJtcyBvZiB2aXN1YWxpemF0aW9ucywgdGhlIHNjYXR0ZXJwbG90IGlzIHRoZSBiYXNlbGluZQptZXRob2QgZm9yIGRpc3BsYXlpbmcgdHdvIHZhcmlhYmxlcy4KCiMjIyBDcmVhdGUgc2NhdHRlciBwbG90cyB1c2luZyBgZ2VvbV9wb2ludCgpYCAKCkZvciB0aGUgTkhBTkVTIGRhdGFzZXQsIHdlIGNhbiBmb3IgaW5zdGFuY2UgbG9vawphdCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gYSBwZXJzb24ncyBoZWlnaHQgYW5kIHdlaWdodAp2YWx1ZXMuCgpgYGB7cn0KTkhBTkVTICU+JQogIGdncGxvdChhZXMoeCA9IEhlaWdodCwgeSA9IFdlaWdodCkpICsKICBnZW9tX3BvaW50KCkgKyAKICB4bGFiKCJIZWlnaHQgKGNtKSIpICsgCiAgeWxhYigiV2VpZ2h0IChrZykiKQpgYGAKCldlIHVzZWQgdGhlIGB4bGFiKClgIGFuZCBgeWxhYigpYCBmdW5jdGlvbnMKaW4gYGdncGxvdDJgIHRvIHNwZWNpZnkgdGhlIHgtYXhpcyBhbmQgeS1heGlzCmxhYmVscy4gTm90ZSB0aGF0IE5BIHZhbHVlcyB3ZXJlIGF1dG9tYXRpY2FsbHkKcmVtb3ZlIGJ5IHRoZSBnZW9tX3BvaW50IGZ1bmN0aW9uLgoKZ2dwbG90MiBhbHNvIGhhcyBhIHZlcnkgYnJvYWQgcGFuZWwgb2YgYWVzdGhldGljIGZlYXR1cmVzCmZvciBpbXByb3ZpbmcgeW91ciBwbG90LiBPbmUgdmVyeSBiYXNpYyBmZWF0dXJlIGlzIHRoYXQgd2UKY2FuIGdpdmUgY29sb3JzIHRvIHRoZSBnZ3Bsb3Qgb2JqZWN0LiBGb3IgaW5zdGFuY2UsIHdlIGNhbgpnaXZlIGRpZmZlcmVudCBjb2xvcnMgdG8gdGhlIGRvdHMgaW4gdGhlIHByZXZpb3VzIApzY2F0dGVycGxvdCBiYXNlZCBvbiBhIHN1YmplY3QncyBnZW5kZXIuCgpgYGB7cn0KTkhBTkVTICU+JQogIGdncGxvdChhZXMoeCA9IEhlaWdodCwgeSA9IFdlaWdodCwgY29sb3IgPSBHZW5kZXIpKSArCiAgZ2VvbV9wb2ludCgpICsgCiAgeGxhYigiSGVpZ2h0IChjbSkiKSArIAogIHlsYWIoIldlaWdodCAoa2cpIikKYGBgCgojIyBDb21iaW5pbmcgZHBseXIgYW5kIGdncGxvdDIKCk5vdGUgdGhhdCB0aGUgcHJldmlvdXMgZnVuY3Rpb25zIGZyb20gdGhlIGRwbHlyCnBhY2thZ2UgY2FuIGJlIGVhc2lseSBjb21iaW5lZCB3aXRoIGdncGxvdCB0aHJvdWdoCnRoZSBjb25jZXB0IG9mIHBpcGVzICU+JS4KCkZvciBpbnN0YW5jZSwgd2UgY291bGQgbWFrZSB0aGUgc2FtZSBzY2F0dGVycGxvdCBhcyBhYm92ZSwKYnV0IG9ubHkgZm9yIHdoaXRlLCBtYXJyaWVkIGFkdWx0cy4KCkluIGFkZGl0aW9uLCB3ZSBoZXJlIGFsc28gc2hvdyBzb21lIGNvbnZlbmllbnQgZ2dwbG90IGZlYXR1cmVzOwoxLiBTZXR0aW5nIHRoZSBjb2xvcnMgbWFudWFsbHkKMi4gU2V0IHRvIGhhdmUgYSB3aGl0ZSBiYWNrZ3JvdW5kCjMuIFNldCBhIChtYWluKSB0aXRsZSBmb3IgdGhlIHBsb3QKNC4gUGljayBhIGRpZmZlcmVudCBzaGFwZSBmb3IgdGhlIGRvdHMgaW4gdGhlIHNjYXR0ZXJwbG90CjUuIE1hbnVhbGx5IHNldCB0aGUgbGltaXRzIG9mIHRoZSB4LSBhbmQgeS1heGVzCgpOb3RlIHRoYXQgdGhpcyBhIG9ubHkgdGhlIHRpcCBvZiB0aGUgaWNlYmVyZyBvZiB0aGUKdGhlIGdncGxvdCBmdW5jdGlvbmFsaXRpZXMhCgpgYGB7cn0KTkhBTkVTICU+JQogIGZpbHRlcihBZ2UgPj0gMTgsIFJhY2UxID09ICJXaGl0ZSIsIE1hcml0YWxTdGF0dXMgPT0gIk1hcnJpZWQiKSAlPiUgIyMgc2VsZWN0IHRoZSByZXF1aXJlZCBkYXRhCiAgZ2dwbG90KGFlcyh4ID0gSGVpZ2h0LCB5ID0gV2VpZ2h0LCBjb2xvciA9IEdlbmRlcikpICsKICBnZW9tX3BvaW50KHNoYXBlPTE3LCBzaXplID0gMSkgKyAjIyBzZXQgdG8gYSBkaWZmZXJlbnQgc2hhcGUgKHRyaWFuZ2xlKSBhbmQgc2l6ZQogIGdndGl0bGUoIkhlaWdodCB2ZXJzdXMgV2VpZ2h0IikgKyAjIyBmb3IgdGhlIG1haW4gdGl0bGUKICB4bGFiKCJIZWlnaHQgKGNtKSIpICsgCiAgeWxhYigiV2VpZ2h0IChrZykiKSArCiAgeGxpbSgwLDIyMCkgKyAjIyBzZXQgbGltaXQgb2YgeC1heGlzCiAgeWxpbSgwLDIyMCkgKyAjIyBzZXQgbGltaXQgb2YgeS1heGlzCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoInJlZCIsImJsdWUiKSkgKyAjIyBtYW51YWxseSBzZXQgY29sb3JzCiAgdGhlbWVfYncoKSAjIyBzZXQgd2hpdGUgYmFja2dyb3VuZApgYGAKCk5leHQgdG8gc2NhdHRlcnBsb3RzLCB3ZSBoYXZlIGEgbGFyZ2UgbnVtYmVyIG9mIG90aGVyCnR5cGVzIG9mIHBsb3RzIGF0IG91ciBkaXNwb3NhbC4gSW1wb3J0YW50bHksIHNvbWUgcGxvdHMgCndpbGwgYmUgbW9yZSBpbmZvcm1hdGl2ZSB0aGFuIG90aGVycywgZGVwZW5kaW5nIG9uIHRoZSAKcmVzZWFyY2ggcXVlc3Rpb24uIFRoZXJlZm9yZSwgY2hvb3NpbmcgdGhlIHBsb3QgdGhhdCBpcyAKbW9zdCBpbmZvcm1hdGl2ZSBpcyBjcnVjaWFsLiBXZSBzaG93IHRoaXMgd2l0aCBhIG1vcmUgCmVsYWJvcmF0ZSBleGFtcGxlIGxhdGVyIChEYXRhX2V4cGxvcmF0aW9uX2NhcHRvcHJpbC5SbWQpLgoKIyMgRmluYWwgZXhhbXBsZQoKIyMjIEdvYWwKClNldCB1cCBhIHJlZmVyZW5jZSBpbnRlcnZhbCBmb3IgdGhlIHN5c3RvbGljIGJsb29kIApwcmVzc3VyZSBpbiB0aGUgTkhBTkVTIGRhdGFzZXQuCgojIyMgQmFja2dyb3VuZAoKVGhlIGNhcHRvcHJpbCBkYXRhc2V0LCB3aGljaCB3ZSB3aWxsIGV4cGxvcmUgYW5kIGFuYWx5c2UgbGF0ZXIsCmhvbGRzIGluZm9ybWF0aW9uIG9uICAxNSBwYXRpZW50cyB0aGF0IGhhdmUgaW5jcmVhc2VkIGJsb29kIHByZXNzdXJlIAp2YWx1ZXMuIAoKQmVmb3JlIHdlIG1heSBjb25kdWN0IHN1Y2ggYW4gZXhwZXJpbWVudCwgd2UgZmlyc3QgbmVlZCB0byBrbm93IAp3aGljaCB2YWx1ZXMgc2hvdWxkIGJlIGNvbnNpZGVyZWQgX2luY3JlYXNlZF8gYW5kIHdoaWNoIG9uZXMgc2hvdWxkIGJlIApjb25zaWRlcmVkIF9ub3JtYWxfLiBUbyBmaW5kIGFuIGludGVydmFsIGZvciB2YWx1ZXMgdGhhdCBhcmUgbm9ybWFsLAp3ZSBjYW4gc2V0IHVwIGEgcmVmZXJlbmNlIGludGVydmFsLiBUbyBzZXQgdXAgdGhpcyBpbnRlcnZhbCwgd2Ugd2lsbAp1c2UgdGhlIE5IQU5FUyBkYXRhc2V0LiBUaGUgYEJQU3lzQXZlYCBjb2x1bW4gaG9sZHMgZGF0YSBvbiB0aGUgc3lzdG9saWMKYmxvb2QgcHJlc3N1cmUuIFRvIHNlbGVjdCBoZWFsdGh5IHN1YmplY3RzLCB3ZSB3aWxsIG5lZWQgdG8gc3Vic2V0IHRoZQpkYXRhLgoKIyMjIEFuYWx5c2lzCgpGaXJzdCwgd2Ugd2lsbCBwbG90IHRoZSBkYXRhIGZvciBhbGwgc3ViamVjdHMgZm9yIHdoaWNoIHdlIGhhdmUgYWxsCnRoZSByZXF1aXJlZCBkYXRhIGFuZCB0aGF0IGFyZSBiZXR3ZWVuIDQwIGFuZCA2NSB5ZWFycyBvbGQuCgpOb3RlLCB0aGF0IHdlICBjYWxjdWxhdGUgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvbiB0aGUgYXZlcmFnZSBibG9vZCBwcmVzc3VyZSBtZWFzdXJlbWVudHMuIElmIHlvdSBoYXZlIHRoZSBzYW1lIG51bWJlciBvZiAidGVjaG5pY2FsIHJlcGVhdHMiIG9uIHRoZSBzYW1lIHBlcnNvbiB5b3UgYXJlIGFsbG93ZWQgdG8gYXZlcmFnZSB0aGVtIGZpcnN0LiAKTm90ZSwgaG93ZXZlciwgdGhhdCBpZiB0aGVyZSBhcmUgc29tZSB0ZWNobmljYWwgbWVhc3VyZW1lbnRzIG1pc3NpbmcsIGF2ZXJhZ2luZyBpcyBubyBsb25nZXIgYWxsb3dlZCBiZWNhdXNlIHRoZW4gdGhlIGF2ZXJhZ2VkIHZhbHVlcyB3aWxsIGJlIGhldGVyb3NjZWRhc3RpYzogc29tZSBhdmVyYWdlcyBhcmUgYmFzZWQgb24gbW9yZSB0ZWNobmljYWwgcmVwZWF0cyB0aGFuIG90aGVycyBzbyB0aGV5IHdpbGwgaGF2ZSBhIGRpZmZlcmVudCBzdGFuZGFyZCBkZXZpYXRpb24uIAoKYGBge3J9CiMjIGhpc3RvZ3JhbSBvZiBCUFN5c0F2ZSBmb3Igc3ViamVjdHMgd2l0IGFnZSBiZXR3ZWVuIDQwIGFuZCA2NSB5ZWFycyBvbGQKTkhBTkVTICU+JQogIGZpbHRlcighaXMubmEoUmFjZTEpLCAhaXMubmEoU21va2UxMDBuKSwgIWlzLm5hKEJNSV9XSE8pLCAhaXMubmEoQWdlKSwgIWlzLm5hKEhhcmREcnVncyksICFpcy5uYShIZWFsdGhHZW4pLCAhaXMubmEoR2VuZGVyKSwgIWlzLm5hKEFsY29ob2xZZWFyKSwgIWlzLm5hKEJQU3lzMSksICFpcy5uYShCUFN5czIpLCAhaXMubmEoQlBTeXMzKSwgIWlzLm5hKFNsZWVwVHJvdWJsZSkpICU+JSAjIyByZXRhaW5zIDQ2NjAgc3ViamVjdHMgd2l0aCB0aGUgcmVxdWlyZWQgZGF0YQogIGZpbHRlcihiZXR3ZWVuKEFnZSw0MCw2NSkpICU+JSAjIyBmaWx0ZXIgdGhlIHN1YmplY3RzIG9uIGFnZSwgcmV0YWlucyAyNTIyIHN1YmplY3RzCiAgZGlzdGluY3QoSUQsLmtlZXBfYWxsPVRSVUUpICU+JSAjIyByZW1vdmVzIGR1cGxpY2F0ZWQgSURzLCByZXRhaW5zIDE0Njcgc3ViamVjdHMKICBnZ3Bsb3QoYWVzKHg9QlBTeXNBdmUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oKQpgYGAKCkFuIGltcG9ydGFudCByZXF1aXJlbWVudCBvZiBjYWxjdWxhdGluZyByZWZlcmVuY2UgaW50ZXJ2YWxzIGlzCnRoYXQgdGhlIGRhdGEgYXJlIG5vcm1hbGx5IGRpc3RyaWJ1dGVkLiB0aGlzIGlzIGNsZWFybHkgbm90IHRoZQpjYXNlOyB0aGUgZGF0YSBoYXMgYSBsb25nIHJpZ2h0IHRhaWwsIHdoaWNoIGlzIHF1aXRlIGNvbW1vbgppbiBiaW9sb2dpY2FsIGRhdGEgKGluIHRoaXMgZWFzaWVyIHRvIGhhdmUgZXh0cmVtZSB2YWx1ZXMgb24gdGhlCnJpZ2h0LWhhbmQgc2lkZSB0aGFuIG9uIHRoZSBsZWZ0LWhhbmQgc2l6ZSwgd2hpY2ggaXMgYWRkaXRpb25hbGx5CmJvdW5kZWQgYnkgemVybyBmb3IgbW9zdCBiaW9sb2dpY2FsIHZhcmlhYmxlcykuCgpCeSBzZWxlY3Rpbmcgb25seSBfaGVhbHRoeV8gc3ViamVjdHMsIHdlIGV4cGVjdCB0aGUgZGF0YSB0byBiZSAKZGlzdHJpYnV0ZWQgbW9yZSBub3JtYWxseS4gV2UgZGVmaW5lIF9oZWFsdGh5XyBhcyBiZWluZyBhIG5vbi1zbW9rZXIsCndpdGhvdXQgYSBoaXN0b3J5IG9mIGRpYWJldGVzLCBoYXJkIGRydWdzIG9yIHNsZWVwaW5nIHRyb3VibGUsIHdpdGggYQpnZW5lcmFsIGhlYWx0aCB0aGF0IGlzIG5vdCBjb25zaWRlcmVkIHBvb3IgYW5kIHRoYXQgaGFzIGEgQk1JIGJldHdlZW4KMTguNSBhbmQgMjkuOS4KCmBgYHtyfQojIyBoaXN0b2dyYW0gb2YgQlBTeXNBdmUgZm9yIEhFQUxUSFkgbWFsZSBzdWJqZWN0cyB3aXRoIGFnZSBiZXR3ZWVuIDQwIGFuZCA2NSB5ZWFycyBvbGQKTkhBTkVTICU+JQogIGZpbHRlcighaXMubmEoUmFjZTEpLCAhaXMubmEoU21va2UxMDBuKSwgIWlzLm5hKEJNSV9XSE8pLCAhaXMubmEoQWdlKSwgIWlzLm5hKEhhcmREcnVncyksICFpcy5uYShIZWFsdGhHZW4pLCAhaXMubmEoR2VuZGVyKSwgIWlzLm5hKEFsY29ob2xZZWFyKSwgIWlzLm5hKEJQU3lzMSksICFpcy5uYShCUFN5czIpLCAhaXMubmEoQlBTeXMzKSwgIWlzLm5hKFNsZWVwVHJvdWJsZSkpICU+JSAjIyByZXRhaW5zIDQ2NjAgc3ViamVjdHMgd2l0aCB0aGUgcmVxdWlyZWQgZGF0YQogIGZpbHRlcihiZXR3ZWVuKEFnZSw0MCw2NSkpICU+JSAjIyBmaWx0ZXIgdGhlIHN1YmplY3RzIG9uIGFnZSwgcmV0YWlucyAyNTIyIHN1YmplY3RzCiAgZGlzdGluY3QoSUQsLmtlZXBfYWxsPVRSVUUpICU+JSAjIyByZW1vdmVzIGR1cGxpY2F0ZWQgSURzLCByZXRhaW5zIDE0Njcgc3ViamVjdHMKICBmaWx0ZXIoU21va2UxMDBuID09ICJOb24tU21va2VyIiwgRGlhYmV0ZXMgPT0gIk5vIiwgSGFyZERydWdzID09ICJObyIsIEhlYWx0aEdlbiAhPSAiUG9vciIsIFNsZWVwVHJvdWJsZSA9PSAiTm8iLCBCTUlfV0hPJWluJWMoIjE4LjVfdG9fMjQuOSIsIjI1LjBfdG9fMjkuOSIpKSAlPiUgIyMgZmlsdGVyIHRvIGhhdmUgb25seSBoZWFsdGh5IHN1YmplY3RzLCBiYXNlZCBvbiBtdWx0aXBsZSBoZWFsdGggY3JpdGVyaWEuIFJldGFpbnMgMjc1IHN1YmplY3RzLgogIGdncGxvdChhZXMoeD1CUFN5c0F2ZSkpICsKICBnZW9tX2hpc3RvZ3JhbSgpCmBgYAoKV2Ugd2lsbCBldmFsdWF0ZSBpZiB0aGUgZGlzdHJpYnV0aW9uIGlzIG5vcm1hbGx5IGRpc3RyaWJ1dGVkIHVzaW5nIGEgUVEtcGxvdC4KSW4gdGhpcyBwbG90IHdlIGNvbXBhcmUgdGhlIHF1YW50aWxlcyBpbiB0aGUgZGF0YSB3aXRoIHRoZSBxdWFudGlsZXMgb2YgdGhlIG5vcm1hbCBkaXN0cmlidXRpb24uIApJZiB0aGUgZGF0YSBhcmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgdGhlbiB0aGUgZGF0YSBpbiB0aGUgUVEtcGxvdCBhcHByb3hpbWF0ZWx5IGZvbGxvd3MgYSBzdHJhaWdodCBsaW5lLiAKRGV2aWF0aW9ucyBmcm9tIHRoZSBzdHJhaWdodCBsaW5lIGluZGljYXRlIGRldmlhdGlvbnMgb2Ygbm9ybWFsaXR5LgoKYGBge3J9Ck5IQU5FUyAlPiUKICBmaWx0ZXIoIWlzLm5hKFJhY2UxKSwgIWlzLm5hKFNtb2tlMTAwbiksICFpcy5uYShCTUlfV0hPKSwgIWlzLm5hKEFnZSksICFpcy5uYShIYXJkRHJ1Z3MpLCAhaXMubmEoSGVhbHRoR2VuKSwgIWlzLm5hKEdlbmRlciksICFpcy5uYShBbGNvaG9sWWVhciksICFpcy5uYShCUFN5czEpJiFpcy5uYShCUFN5czIpJiFpcy5uYShCUFN5czMpLCAhaXMubmEoU2xlZXBUcm91YmxlKSkgJT4lCiAgZmlsdGVyKGJldHdlZW4oQWdlLDQwLDY1KSkgJT4lIAogIGRpc3RpbmN0KElELC5rZWVwX2FsbD1UUlVFKSAlPiUgCiAgZmlsdGVyKFNtb2tlMTAwbiA9PSAiTm9uLVNtb2tlciIsIERpYWJldGVzID09ICJObyIsIEhhcmREcnVncyA9PSAiTm8iLCBIZWFsdGhHZW4gIT0gIlBvb3IiLCBTbGVlcFRyb3VibGUgPT0gIk5vIiwgQk1JX1dITyVpbiVjKCIxOC41X3RvXzI0LjkiLCIyNS4wX3RvXzI5LjkiKSkgJT4lIAogIGdncGxvdChhZXMoc2FtcGxlPUJQU3lzQXZlKSkrCiAgZ2VvbV9xcSgpKwogIGdlb21fcXFfbGluZSgpCmBgYAoKV2Ugb2JzZXJ2ZSB0aGF0IHRoZSBkaXN0cmlidXRpb24gaXMgc2xpZ2h0bHkgc2tld2VkIHRvbyB0aGUgcmlnaHQuIApXZSB3aWxsIHRoZXJlZm9yZSBsb2cgdHJhbnNmb3JtIHRoZSBibG9vZHByZXNzdXJlIG1lYXN1cmVtZW50cy4gCldlIHVzZSB0aGUgbG9nMiB0cmFuc2Zvcm1hdGlvbjogYSBkaWZmZXJlbmNlIG9mIDEgb24gdGhlIGxvZyBzY2FsZSBjb3JyZXNwb25kcyB0byBkb3VibGluZyB0aGUgYmxvb2QgcHJlc3N1cmUgb24gdGhlIG9yaWdpbmFsIHNjYWxlOgokbG9nMihCKS1sb2cyKEEpPWxvZzIoQi9BKSQgXHJpZ2h0YXJyb3cgJDJeezF9PTIkLgoKYGBge3J9Ck5IQU5FUyAlPiUKICBmaWx0ZXIoIWlzLm5hKFJhY2UxKSwgIWlzLm5hKFNtb2tlMTAwbiksICFpcy5uYShCTUlfV0hPKSwgIWlzLm5hKEFnZSksICFpcy5uYShIYXJkRHJ1Z3MpLCAhaXMubmEoSGVhbHRoR2VuKSwgIWlzLm5hKEdlbmRlciksICFpcy5uYShBbGNvaG9sWWVhciksICFpcy5uYShCUFN5czEpJiFpcy5uYShCUFN5czIpJiFpcy5uYShCUFN5czMpLCAhaXMubmEoU2xlZXBUcm91YmxlKSkgJT4lCiAgZmlsdGVyKGJldHdlZW4oQWdlLDQwLDY1KSkgJT4lIAogIGRpc3RpbmN0KElELC5rZWVwX2FsbD1UUlVFKSAlPiUgCiAgZmlsdGVyKFNtb2tlMTAwbiA9PSAiTm9uLVNtb2tlciIsIERpYWJldGVzID09ICJObyIsIEhhcmREcnVncyA9PSAiTm8iLCBIZWFsdGhHZW4gIT0gIlBvb3IiLCBTbGVlcFRyb3VibGUgPT0gIk5vIiwgQk1JX1dITyVpbiVjKCIxOC41X3RvXzI0LjkiLCIyNS4wX3RvXzI5LjkiKSkgJT4lIAogIGdncGxvdChhZXMoc2FtcGxlPUJQU3lzQXZlICU+JSBsb2cyKSkrCiAgZ2VvbV9xcSgpKwogIGdlb21fcXFfbGluZSgpCmBgYAoKV2Ugc2VlIHRoYXQgdGhlIGRhdGEgaXMgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgdXBvbiBsb2cgdHJhbnNmb3JtYXRpb24uIApXZSBjYW4gbm93IHVzZSB0aGUgbm9ybWFsIGRpc3RyaWJ1dGlvbiB0byBjb25zdHJ1Y3QgYSA5NSUgaW50ZXJ2YWwgYXQgbG9nIHNjYWxlIHRoYXQgd2Ugd2lsbCBiYWNrdHJhbnNmb3JtIHRvIHRoZSBvcmlnaW5hbCBzY2FsZS4gCgoKYGBge3J9CiMjIHRvIGdldCB0aGUgOTUlIHJlZmVyZW5jZSBpbnRlcnZhbCBmb3IgdGhlIGhlYWx0aHkgZ3JvdXA7CgpzdW1tYXJ5X05IQU5FUyA8LSBOSEFORVMgJT4lCiAgZmlsdGVyKCFpcy5uYShSYWNlMSksICFpcy5uYShTbW9rZTEwMG4pLCAhaXMubmEoQk1JX1dITyksICFpcy5uYShBZ2UpLCAhaXMubmEoSGFyZERydWdzKSwgIWlzLm5hKEhlYWx0aEdlbiksICFpcy5uYShHZW5kZXIpLCAhaXMubmEoQWxjb2hvbFllYXIpLCAhaXMubmEoQlBTeXMxKSwgIWlzLm5hKEJQU3lzMiksICFpcy5uYShCUFN5czMpLCAhaXMubmEoU2xlZXBUcm91YmxlKSkgJT4lICMjIHJldGFpbnMgNDY2MCBzdWJqZWN0cyB3aXRoIHRoZSByZXF1aXJlZCBkYXRhCiAgZmlsdGVyKGJldHdlZW4oQWdlLDQwLDY1KSkgJT4lICMjIGZpbHRlciB0aGUgc3ViamVjdHMgb24gYWdlLCByZXRhaW5zIDI1MjIgc3ViamVjdHMKICBkaXN0aW5jdChJRCwua2VlcF9hbGw9VFJVRSkgJT4lICMjIHJlbW92ZXMgZHVwbGljYXRlZCBJRHMsIHJldGFpbnMgMTQ2NyBzdWJqZWN0cwogIGZpbHRlcihTbW9rZTEwMG4gPT0gIk5vbi1TbW9rZXIiLCBEaWFiZXRlcyA9PSAiTm8iLCBIYXJkRHJ1Z3MgPT0gIk5vIiwgSGVhbHRoR2VuICE9ICJQb29yIiwgU2xlZXBUcm91YmxlID09ICJObyIsIEJNSV9XSE8laW4lYygiMTguNV90b18yNC45IiwiMjUuMF90b18yOS45IikpICU+JSAKICBtdXRhdGUoQlBTeXNBdmVMb2c9QlBTeXNBdmUgJT4lIGxvZzIpICU+JQogIHN1bW1hcml6ZV9hdCgiQlBTeXNBdmVMb2ciLAogICAgICAgICAgICAgICBsaXN0KG1lYW49fm1lYW4oLixuYS5ybT1UUlVFKSwKICAgICAgICAgICAgICAgICAgICBzZD1+c2QoLixuYS5ybT1UUlVFKSwKICAgICAgICAgICAgICAgICAgICBuPWZ1bmN0aW9uKHgpIHglPiVpcy5uYSU+JWAhYCU+JXN1bSkpICU+JQogIG11dGF0ZShzZT1zZC9zcXJ0KG4pKQoKcGFzdGUoIk1lYW4gdmFsdWUgKGxvZzIpOiIsIHN1bW1hcnlfTkhBTkVTJG1lYW4pCnBhc3RlKCJHZW9tZXRyaWMgbWVhbiB2YWx1ZSAobG9nMik6IiwgMl5zdW1tYXJ5X05IQU5FUyRtZWFuKQpwYXN0ZSgiU3RhbmRhcmQgZGV2aWF0aW9uIChsb2cyKToiLCBzdW1tYXJ5X05IQU5FUyRzZCkKcGFzdGUwKCJSZWZlcmVuY2UgaW50ZXJ2YWwgKGxvZzIpOiBbIiwgc3VtbWFyeV9OSEFORVMkbWVhbiAtIDIqc3VtbWFyeV9OSEFORVMkc2QsICI7Iiwgc3VtbWFyeV9OSEFORVMkbWVhbiArIDIqc3VtbWFyeV9OSEFORVMkc2QsICJdIikKcGFzdGUwKCJSZWZlcmVuY2UgaW50ZXJ2YWwgKGxvZzIpOiBbIiwgMl4oc3VtbWFyeV9OSEFORVMkbWVhbiAtIDIqc3VtbWFyeV9OSEFORVMkc2QpLCAiOyIsIDJeKHN1bW1hcnlfTkhBTkVTJG1lYW4gKyAyKnN1bW1hcnlfTkhBTkVTJHNkKSwgIl0iKQpgYGAKClRoZSBsb2cyIHN5c3RvbGljIGJsb29kIHByZXNzdXJlIGZvciBoZWFsdGh5IHN1YmplY3RzIGlzIGFwcHJveGltYXRlbHkgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuIFdlIGhhdmUgY2FsY3VsYXRlZCB0aGUKbWVhbiBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIGJsb29kIHByZXNzdXJlIHZhbHVlcyBpbiB0aGlzIGhlYWx0aHkKc3Vic2V0IGF0IHRoZSBsb2cyIHNjYWxlLCB3aGljaCBhbGxvd3MgdXMgdG8gc2V0IHVwIHRoZSAoOTUlKSByZWZlcmVuY2UgaW50ZXJ2YWwsIGZvcgp3aGF0IHdlIGNhbiBjb25zaWRlciB0byBiZSBfbm9ybWFsXyBibG9vZCBwcmVzc3VyZSB2YWx1ZXMuIEFueSBvZiB0aGUKbGF0ZXIgcGF0aWVudHMgd2hvJ3MgdmFsdWVzIGRvIG5vdCBmYWxsIHdpdGhpbiB0aGlzIGludGVydmFsLCB3ZSBjYW4KY29uc2lkZXIgX2Fibm9ybWFsXy4gCgpUaGUgbG9nMiBtZWFuIGJsb29kIHByZXNzdXJlIHZhbHVlIG9mIHRoZSBoZWFsdGh5IHN1YnNldCBpcyBgciByb3VuZChzdW1tYXJ5X05IQU5FUyRtZWFuLDIpYCwgd2l0aAphIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBgciByb3VuZChzdW1tYXJ5X05IQU5FUyRzZCwyKWAuIFdoZW4gd2UgcmVwbGFjZSB0aGUgcG9wdWxhdGlvbiBhdmVyYWdlCmFuZCBwb3B1bGF0aW9uIHN0YW5kYXJkIGRldmlhdGlvbiB3aXRoIHRoZXNlIGVzdGltYXRlcywgd2Ugb2J0YWluIGEgCjk1JSByZWZlcmVuY2UgaW50ZXJ2YWwgb2YgW2ByIHJvdW5kKHN1bW1hcnlfTkhBTkVTJG1lYW4gLSAyKnN1bW1hcnlfTkhBTkVTJHNkLDIpYDtgciByb3VuZChzdW1tYXJ5X05IQU5FUyRtZWFuICsgMipzdW1tYXJ5X05IQU5FUyRzZCwyKWBdIG9uIGxvZyBzY2FsZSBhbmQgW2ByIHJvdW5kKDJeKHN1bW1hcnlfTkhBTkVTJG1lYW4gLSAyKnN1bW1hcnlfTkhBTkVTJHNkKSwyKWAsYHIgcm91bmQoMl4oc3VtbWFyeV9OSEFORVMkbWVhbiArIDIqc3VtbWFyeV9OSEFORVMkc2QpLDIpYF0gbW1IZyBvbiB0aGUgb3JpZ2luYWwgc2NhbGUuIAoKTm90ZSwgdGhhdCBpbiB0aGUgbGl0ZXJhdHVyZSBhIHZhbHVlIDE0MCBtbUhnIGZvciB0aGUgc3lzdG9saWMgYmxvb2QgCnByZXNzdXJlIGlzIHR5cGljYWxseSBjb25zaWRlcmVkIHRvIGJlIHRoZSB1cHBlciBsaW1pdCBvZiBfbm9ybWFsaXR5Xy4gCgotLS0KCiMgW0hvbWVdKGh0dHBzOi8vZ3RwYi5naXRodWIuaW8vUFNMUzIwLykgey19CgoK