1 Setup

Full repo: https://github.com/jrfarrer/stats701_hw1/

Published file: https://jrfarrer.github.io/stats701_hw1/

Begin by setting up the R session, creating a logger function, and loading packages.

# Set the seed for reproducibility
set.seed(44)
# Set the locale of the session so languages other than English can be used
invisible(Sys.setlocale("LC_ALL", "en_US.UTF-8"))
# Prevent printing in scientific notation
options(digits = 4, width = 220)
# Create a logger function
logger <- function(msg, level = "info", file = log_file) {
    cat(paste0("[", format(Sys.time(), "%Y-%m-%d %H:%M:%S.%OS"), "][", level, "] ", msg, "\n"), file = stdout())
}
# Set the project directory
base_dir <- ''
data_dir <- paste0(base_dir, "data/")
code_dir <- paste0(base_dir, "code/")
viz_dir <- paste0(base_dir, "viz/")
dir.create(data_dir, showWarnings = FALSE)
dir.create(code_dir, showWarnings = FALSE)
dir.create(viz_dir, showWarnings = FALSE)
# Create a function that will be used to load/install packages
fn_load_packages <- function(p) {
  if (!is.element(p, installed.packages()[,1]) || (p =="DT" && !(packageVersion(p) > "0.1"))) {
    if (p == "DT") {
      devtools::install_github('rstudio/DT')
    } else {
      install.packages(p, dep = TRUE, repos = 'http://cran.us.r-project.org')
    }
  }
  a <- suppressPackageStartupMessages(require(p, character.only = TRUE))
  if (a) {
    logger(paste0("Loaded package ", p, " version ", packageVersion(p)))
  } else {
    logger(paste0("Unable to load packages ", p))
  }
}
# Create a vector of packages
packages <- c('tidyverse','ggthemes','knitr','readxl','broom','forecast','stringr',
              'ISLR','GGally','gridExtra','leaps','extrafont','pander')
# Use function to load the required packages
invisible(lapply(packages, fn_load_packages))
[2016-10-16 23:35:18.18][info] Loaded package tidyverse version 1.0.0
[2016-10-16 23:35:18.18][info] Loaded package ggthemes version 3.2.0
[2016-10-16 23:35:18.18][info] Loaded package knitr version 1.14
[2016-10-16 23:35:18.18][info] Loaded package readxl version 0.1.1
[2016-10-16 23:35:18.18][info] Loaded package broom version 0.4.1
[2016-10-16 23:35:18.18][info] Loaded package forecast version 7.1
[2016-10-16 23:35:18.18][info] Loaded package stringr version 1.1.0
[2016-10-16 23:35:19.19][info] Loaded package ISLR version 1.0
[2016-10-16 23:35:19.19][info] Loaded package GGally version 1.2.0
[2016-10-16 23:35:19.19][info] Loaded package gridExtra version 2.2.1
[2016-10-16 23:35:19.19][info] Loaded package leaps version 2.9
[2016-10-16 23:35:19.19][info] Loaded package extrafont version 0.17
[2016-10-16 23:35:19.19][info] Loaded package pander version 0.6.0
# To the font second font, run the following two lines of code and add name of user to vector
# system(paste0("cp -r ",viz_dir,"fonts/. ~/Library/Fonts/")) # instantaneous
# font_import() # takes approximately 5-10 min
users_v <- c("Jordan")
# Create a color palette
pal538 <- ggthemes_data$fivethirtyeight
# Create a theme to use throughout the analysis
theme_jrf <- function(base_size = 8, base_family = ifelse(Sys.info()[['user']] %in% users_v, "DecimaMonoPro", "Helvetica")) {
    theme(
        plot.background = element_rect(fill = "#F0F0F0", colour = "#606063"), 
        panel.background = element_rect(fill = "#F0F0F0", colour = NA), 
        panel.border = element_blank(),
        panel.grid.major =   element_line(colour = "#D7D7D8"),
        panel.grid.minor =   element_line(colour = "#D7D7D8", size = 0.25),
        panel.margin =       unit(0.25, "lines"),
        panel.margin.x =     NULL,
        panel.margin.y =     NULL,
        axis.ticks.x = element_blank(), 
        axis.ticks.y = element_blank(),
        axis.title = element_text(colour = "#A0A0A3"),
        axis.text.x = element_text(vjust = 1, family = 'Helvetica', colour = '#3C3C3C'),
        axis.text.y = element_text(hjust = 1, family = 'Helvetica', colour = '#3C3C3C'),
        legend.background = element_blank(),
        legend.key = element_blank(), 
        plot.title = element_text(face = 'bold', colour = '#3C3C3C', hjust = 0),
        text = element_text(size = 9, family = ifelse(Sys.info()[['user']] %in% users_v,"DecimaMonoPro", "Helvetica")),
        title = element_text(family = ifelse(Sys.info()[['user']] %in% users_v,"DecimaMonoPro", "Helvetica"))
        
    )
}

2 Question 2

2.1 Data Loading

Let’s use Hadley’s readr package to load the dataset, using the col_name parameter to set the column names of the tibble.

# Load the csv with meaningful column names
survey_results <- read_csv(paste0(data_dir,'Survey_results_final.csv'), skip = 1,
                           col_names = c('hitid','hittypeid','title','description','keywords',
                            'reward','creationtime','maxassignments','requesterannotation',
                            'assignmentdurationinseconds','autoapprovaldelayinseconds',
                            'expiration','numberofsimilarhits','lifetimeinseconds',
                            'assignmentid','workerid','assignmentstatus','accepttime',
                            'submittime','autoapprovaltime','approvaltime','rejectiontime',
                            'requesterfeedback','worktime','lifetimeapprovalrate',
                            'last30daysapprovalrate','last7daysapprovalrate','age',
                            'education','gender','income','sirius','wharton','approve','reject'))
# Print a few records in the tibble
survey_results %>% 
    select(age, education, gender, income, sirius, wharton, worktime) 
# Put into a new tibble we'll use for cleaning (there will be a final later)
survey_results_cleaning <- survey_results

2.2 Data Cleaning

We’ll sequentially clean each of the primary variables of the dataset and create exploratory summaries.

2.2.1 Age

Let’s quickly summarize the age variable, noting that it is a character.

survey_results_cleaning %>% group_by(age) %>% summarise(cnt = n()) %>% arrange(age)

We correct some errant values, using our judgement as data analysts and plot a histogram.

survey_results_cleaning <-
    survey_results %>%
    mutate(
        age2 = ifelse(age == 'Eighteen (18)', "18", ifelse(age == 'female', NA, ifelse(age == "27`", "27", age)))
        , age2 = as.integer(age2)
    )
ggplot(survey_results_cleaning, aes(x = age2)) + 
    geom_point(aes(x = 4, y = 1), shape = 1, colour = pal538['red'], fill = NA, size = 6, stroke = 1) + 
    geom_point(aes(x = 223, y = 1), shape = 1, colour = pal538['red'], fill = NA, size = 6, stroke = 1) + 
    geom_histogram(binwidth = 1, fill = pal538['blue']) +
    theme_jrf() +
    scale_x_continuous(expand = c(0.05, 0.01)) + scale_y_continuous(expand = c(0.02, 0.01)) + 
    labs(title = "Age", y = "Count", x = "Age (years)")

It looks like we still missed some bad values.

sort(unique(survey_results_cleaning$age2))
 [1]   4  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40  41  42  43  44
[29]  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60  61  62  63  65  66  67  68  69  74  76 223

We fix those too and plot the histogram.

survey_results_cleaning <-
    survey_results_cleaning %>%
    mutate(
        age3 = ifelse(age2 %in% c(4, 223), NA, age2)
    )
ggplot(survey_results_cleaning, aes(x = age3)) + geom_histogram(binwidth = 1, fill = pal538['blue']) +
    theme_jrf() +
    scale_x_continuous(expand = c(0.05, 0.01)) + scale_y_continuous(expand = c(0.02, 0.01)) + 
    labs(title = "Age", y = "Count", x = "Age (years)")

2.2.2 Education

Let’s look at the unique values and counts.

survey_results_cleaning %>% group_by(education) %>% summarise(cnt = n()) %>% arrange(education)

It appears that 0 respondents left the survey on the default which read ‘select one’. We’ll update that to ‘Other’ and modify this variable to be a factor.

survey_results_cleaning <-
    survey_results_cleaning %>% 
    mutate(
        education2 = ifelse(education == "select one", "Other", education)
        , education2 = factor(education2, levels = c('Less than 12 years; no high school diploma'
                                                        , 'High school graduate (or equivalent)'
                                                        , 'Some college, no diploma; or Associate’s degree'
                                                        , 'Bachelor’s degree or other 4-year degree'
                                                        , 'Graduate or professional degree'
                                                        , 'Other'))
    )
survey_results_cleaning %>% group_by(education2) %>% summarise(cnt = n()) %>% arrange(education2)

2.2.3 Gender

We’ll summarize the gender variable.

survey_results_cleaning %>% group_by(gender) %>% summarise(cnt = n()) %>% arrange(gender)

We update this to be a factor.

survey_results_cleaning <-
    survey_results_cleaning %>%
    mutate(gender2 = as.factor(gender))
survey_results_cleaning %>% 
    group_by(gender2) %>% 
    summarise(cnt = n()) %>% 
    arrange(gender2) %>%
    mutate(prop = cnt / sum(cnt))

2.2.4 Income

survey_results_cleaning %>% group_by(income) %>% summarise(cnt = n()) %>% arrange(income)

Let’s convert this to a factor variable.

survey_results_cleaning <-
    survey_results_cleaning %>% 
    mutate(
        income2 = factor(income, levels = c('Less than $15,000'
                                            , '$15,000 - $30,000'
                                            , '$30,000 - $50,000'
                                            , '$50,000 - $75,000'
                                            , '$75,000 - $150,000'
                                            , 'Above $150,000'))
    )
survey_results_cleaning %>% group_by(income2) %>% summarise(cnt = n()) %>% arrange(income2)

2.2.5 Sirius and Wharton

survey_results_cleaning %>% group_by(sirius) %>% summarise(cnt = n()) %>% arrange(sirius)
survey_results_cleaning %>% group_by(wharton) %>% summarise(cnt = n()) %>% arrange(wharton)

Let’s convert these to factors for better analysis capabilities.

survey_results_cleaning <-
    survey_results_cleaning %>%
    mutate(
        sirius2 = factor(sirius, levels = c("Yes","No"))
        , wharton2 = factor(wharton, levels = c("Yes","No"))
    )
survey_results_cleaning %>% group_by(sirius2) %>% summarise(cnt = n()) %>% arrange(sirius2)
survey_results_cleaning %>% group_by(wharton2) %>% summarise(cnt = n()) %>% arrange(wharton2)

2.2.6 Worktime

ggplot(survey_results_cleaning, aes(x = worktime)) + geom_histogram(binwidth = 1, fill = pal538['blue']) + 
    theme_jrf() +
    scale_x_continuous(expand = c(0.05, 0.01)) + scale_y_continuous(expand = c(0.02, 0.01)) + 
    labs(title = "Worktime", y = "Count", x = "Worktime in Seconds")

2.2.7 Final Data Frame

We select and rename the columns.

survey_results_cleaning <- 
    survey_results_cleaning %>%
    select(age3, education2, gender2, income2, sirius2, wharton2, worktime) %>%
    rename(
        age = age3
        , education = education2
        , gender = gender2
        , income = income2
        , sirius = sirius2
        , wharton = wharton2
    )

Let’s review the records with missing data.

survey_results_cleaning[!complete.cases(survey_results_cleaning), ] %>% print(width = Inf)

We will remove the 7 records that have NAs for sirius or wharton. Without information about the response, we will have trouble making an estimate of p, the proportion of Sirius listeners who listened to Business Radio Powered by the Wharton School.

survey_results_cleaning %>%
    filter(is.na(sirius) | is.na(wharton))

We put together a final data frame.

survey_results_final <- 
    survey_results_cleaning %>%
    filter(!is.na(sirius) & !is.na(wharton))

2.3 Summary

We previously listened to the podcast Planet Money’s episode about Amazon’s Mechanical Turk program.

pander(summary(survey_results_final), missing = "", split.table = Inf)
age education gender income sirius wharton worktime
Min. :18.0 Less than 12 years; no high school diploma : 10 Female: 742 Less than $15,000 :209 Yes:1358 Yes: 70 Min. : 8.0
1st Qu.:23.0 High school graduate (or equivalent) :192 Male :1009 $15,000 - $30,000 :366 No : 399 No :1687 1st Qu.: 17.0
Median :28.0 Some college, no diploma; or Associate’s degree:743 NA’s : 6 $30,000 - $50,000 :428 Median : 21.0
Mean :30.4 Bachelor’s degree or other 4-year degree :613 $50,000 - $75,000 :376 Mean : 22.5
3rd Qu.:34.0 Graduate or professional degree :180 $75,000 - $150,000:327 3rd Qu.: 26.0
Max. :76.0 Other : 19 Above $150,000 : 47 Max. :108.0
NA’s :3 NA’s : 4
Variable Class Description
Age Integer The age in years of the survey respondent
Education Factor Level of education attain by the survey respondent
Gender Factor Gender indicated by the survey respondent (Male or Female)
Income Factor Income level provided by the survey respondent
Sirius Factor Response to “Have you ever listened to Sirius Radio?”
Wharton Factor Response to “Have you ever listened to Sirius Business Radio by Wharton?”
Worktime Integer Number of second spent completing the survey

2.4 Sample properties

2.4.1 (1)

On the surface, we have no reason to believe that the MTURK dataset could be representative of the US population. Knowledge of MTURK is not universal and attracts particular types of individuals willing to perform many small tasks for a minor reward (from Planet Money podcast).

First, we quickly see that the proportion of Sirius listeners is much higher than the given proportion. If the US population is 321.4 million, then the proportion of Sirius listeners is

\[\frac{51.6}{321.4} = 0.1605\]

However, we quickly see that in the survey data from MTURK, the proportion of Sirius listeners is much higher.

(sirius_prop <- survey_results_final %>% summarise(prop_sirius = sum(sirius == "Yes") / n()))
We see that of the survey respondents,

% say that have listened to Sirius.

Second, in order to answer the question “Does this appear to be a random sample from the US population?” empirically we can look at the four characteristics in our final dataset

  1. Age
  2. Gender
  3. Education
  4. Income

For age and gender, we download a table called “Population by Age” from US Census Bureau’s Current Population Survey in 2012.

census_age_gender <- read_csv(url("http://www.census.gov/population/age/data/files/2012/2012gender_table1.csv"), 
                              skip = 6, 
                              col_names = c("age", "all","all_percent","male","male_percent","female","female_percent"), 
                              col_types = cols(age = col_character(),
                                              all = col_number(),
                                              all_percent = col_number(),
                                              male = col_number(),
                                              male_percent = col_number(),
                                              female = col_number(),
                                              female_percent = col_number())
                              )
census_age_gender

We will need to bucket our MTURK dataset to match the categories of the Census Bureau’s. In doing so we remove the 109 records with an age 18-19 and without a listed gender.

actual <- 
    survey_results_final %>% 
        filter(age >= 20 & !is.na(gender)) %>%
        mutate(age_bucket = paste0(floor(age / 10), "0 to ", floor(age / 10),"9 years")) %>%
        group_by(age_bucket, gender) %>%
        summarise(
            n = n()
        ) %>%
        ungroup() %>%
        mutate(source = "Actual") %>%
        select(source, age_bucket, gender, n)
actual_size <- sum(actual$n)
actual

Next we clean the Census Bureau’s dataset and scale the expected number of individuals to our dataset size of 1645.

expected <- 
    census_age_gender %>%
        filter(row_number() <= 19) %>%
        select(age, male, female) %>%
        mutate(age = gsub("\\.","", age)) %>%
        filter(!(age %in% c('Under 5 years','All ages','5 to 9 years','10 to 14 years','15 to 19 years'))) %>%
        mutate(age_bucket = paste0(substring(age,1,1), "0 to ", substring(age,1,1),"9 years")) %>%
        mutate(age_bucket = ifelse(age_bucket == "80 to 89 years", "80 years plus", age_bucket)) %>%
        select(-age) %>%
        gather(gender, n, -age_bucket) %>%
        group_by(age_bucket, gender) %>%
        summarise(n = sum(n)) %>%
        ungroup() %>%
        mutate(gender = paste0(toupper(substring(gender,1,1)), substring(gender, 2, 999))) %>%
        mutate(percent = n / sum(n)) %>%
        mutate(Expected = actual_size * percent) %>%
        select(age_bucket, gender, Expected) %>%
        gather(source, n, -age_bucket, -gender) %>%
        select(source, age_bucket, gender, n)
expected

Then we can combine the two.

actual_expected <- 
    union(actual, expected) %>%
        mutate(
            source = factor(source, levels = c("Actual","Expected"))
            , gender = factor(gender, levels = c("Male","Female"))
        )

We find that the MTURK sample is younger and more male the US population. For example, in a sample 1645 we would expect to find 155.5733 males, 20 to 29 years old. However, in the MTURK sample there are 559 males, 20 to 29 years old, or 403.4267 more than expected. In addition, in the US population we would expect 2, 2, 852.4531, 51.8% females and 2, 1, 792.5469, 48.2% males. However, the MTUK sample has 1, 2, 715, 43.5% females and 1, 1, 930, 56.5% males.

actual_expected %>%    
    ggplot(aes(x = source, y = n, fill = source)) + 
    geom_bar(stat = "identity") + 
    coord_flip() + 
    facet_grid(age_bucket ~ gender, switch = "y") +
    theme_jrf() + 
    labs(title = "MTURK is younger and more male than US Population", 
         y = paste0("Respondents to Survey (", actual_size, ")"), x = NULL) +
    scale_fill_manual(values = c(Actual = pal538['blue'][[1]], Expected = pal538['red'][[1]])) +
    guides(fill = FALSE) +
    geom_text(aes(label = round(n, 0)), hjust = 0, family = "DecimaMonoPro") +
    scale_y_continuous(expand = c(0.02, 40)) +
    theme(strip.text.y = element_text(size = 6))

Looking at education, we download data the US Census Bureau’s Current Population Report that shows statistics on educational attainment. The data is by age and gender, but we aggregate the age section to the total population to compare to the MTURK sample. The table below shows expected vs actual proportions.

census_edu <- read_csv(url("https://www.census.gov/hhes/socdemo/education/data/cps/2015/Table%201-01.csv"), 
                              skip = 5)
edu_expected <-
    census_edu %>%
    select(1, 3:17) %>%
    filter(row_number() %in% c(2:15)) %>%
    select(-X1) %>%
    mutate(`Doctoral degree` = as.integer(gsub(",","",`Doctoral degree`))) %>%
    summarise_each(funs(sum(., na.rm =TRUE))) %>%
    gather(education, n) %>%
    mutate(
        education = ifelse(education == "None", "Other", 
                        ifelse(education %in% c("1st - 4th grade","5th - 6th grade","7th - 8th grade","9th grade",
                                                   "10th grade","11th grade /2"), "Less than 12 years; no high school diploma",
                        ifelse(education == "High school graduate", "High school graduate (or equivalent)",
                        ifelse(education %in% c("Some college, no degree","Associate's degree, occupational",
                                                "Associate's degree, academic"), 
                                                "Some college, no diploma; or Associate’s degree",
                        ifelse(education == "Bachelor's degree", "Bachelor’s degree or other 4-year degree", 
                        ifelse(education %in% c("Master's degree","Professional degree","Doctoral degree"), 
                               "Graduate or professional degree", NA))))))
        , education = factor(education, levels = c('Less than 12 years; no high school diploma'
                                                        , 'High school graduate (or equivalent)'
                                                        , 'Some college, no diploma; or Associate’s degree'
                                                        , 'Bachelor’s degree or other 4-year degree'
                                                        , 'Graduate or professional degree'
                                                        , 'Other'))
    ) %>%
    group_by(education) %>%
    summarise(expected_n = sum(n)) %>%
    ungroup() %>%
    mutate(expected = expected_n / sum(expected_n))
edu_actual <-
    survey_results_final %>%
    group_by(education) %>%
    summarise(actual_n = n()) %>%
    ungroup() %>%
    mutate(actual = actual_n / sum(actual_n))
    
comparison_tbl_edu <-
        inner_join(edu_expected, edu_actual, by = c("education")) %>%
        mutate(
            expected = paste0(round(100*expected,1), "%")
            , actual = paste0(round(100*actual,1), "%")
        ) %>%
        select(-actual_n, -expected_n)
comparison_tbl_edu

We find that the MTURK sample over indexes on people have been to college or graduated from college. Notably, in a sample of the US population we would expect to find 27.8% of people to have ‘Some college, no diploma; or Associate’s degree’, but in the MTURK sample 42.3% fit this category of educational attainment.

We use a proportions test to determine if the proportions are indeed different.

edu_matrix <- inner_join(edu_expected, edu_actual, by = c("education")) %>% select(expected_n, actual_n) %>% as.matrix
(edu_prop_test <- prop.test(edu_matrix))

    6-sample test for equality of proportions without continuity correction

data:  edu_matrix
X-squared = 760, df = 5, p-value <2e-16
alternative hypothesis: two.sided
sample estimates:
prop 1 prop 2 prop 3 prop 4 prop 5 prop 6 
0.9999 0.9991 0.9962 0.9955 0.9977 0.9926 

Using 6-sample test for equality of proportions without continuity correction we have strong evidence against the null hypothesis that the proportions in the education groups are the same. This provides further evidence that the MTURK sample does not represent the US population.

Looking at income, we download from the US Census Bureau statistics on personal income.

download.file("http://www2.census.gov/programs-surveys/cps/tables/pinc-01/2016/pinc01_1_1_1.xls",
              destfile = paste0(data_dir, 'pinc01_1_1_1.xls'), mode = "wb")
trying URL 'http://www2.census.gov/programs-surveys/cps/tables/pinc-01/2016/pinc01_1_1_1.xls'
Content type 'application/vnd.ms-excel' length 54784 bytes (53 KB)
==================================================
downloaded 53 KB
income <- read_excel(paste0(data_dir, 'pinc01_1_1_1.xls'), skip = 8)
income_expected <- 
    income[, c(4:44)] %>% 
    filter(row_number() == 2) %>%
    gather(income, n) %>%
    mutate(
        n = as.integer(n)
    ) %>%
    select(income, n) %>% 
    mutate(
        income = ifelse(row_number() <= 6, "Less than $15,000", 
                        ifelse(row_number() <= 12, "$15,000 - $30,000",
                               ifelse(row_number() <= 20, "$30,000 - $50,000",
                                      ifelse(row_number() <= 30, "$50,000 - $75,000",
                                             "Above $75,000"))))
        , income = factor(income, levels = c("Less than $15,000","$15,000 - $30,000", "$30,000 - $50,000",
                                             "$50,000 - $75,000", "Above $75,000"))
    ) %>%
    group_by(income) %>%
    summarise(
        n = sum(n)
    ) %>%
    ungroup() %>%
    mutate(expected = n / sum(n)) %>%
    mutate(expected_n = n) %>%
    select(income, expected_n, expected)
income_actual <- 
    survey_results_final %>%
    filter(!is.na(income)) %>%
    mutate(
        income = as.character(income)
        , income = ifelse(income %in% c("$75,000 - $150,000","Above $150,000"), "Above $75,000", income)
        , income = factor(income, levels = c("Less than $15,000","$15,000 - $30,000", "$30,000 - $50,000",
                                             "$50,000 - $75,000", "Above $75,000"))
    ) %>%
    group_by(income) %>%
    summarise(
        n = n()
    ) %>%
    ungroup() %>%
    mutate(actual = n / sum(n)) %>%
    mutate(actual_n = n) %>%
    select(income, actual_n, actual)
comparison_tbl_income <-
    inner_join(income_expected, income_actual, by = c("income")) %>%
        mutate(
            expected = paste0(round(100*expected,1), "%")
            , actual = paste0(round(100*actual,1), "%")
        ) %>%
        select(-actual_n, -expected_n)
comparison_tbl_income

Looking at the table above we see there is a smaller percentage of lower income respondents than expected (26.7% vs. 11.9%). In addition, there is larger percentage of high earning respondents than expected (15.7% vs. 21.3%).

We use a proportions test to determine if the proportions are indeed different.

income_matrix <- inner_join(income_expected, income_actual, by = c("income")) %>% select(expected_n, actual_n) %>% as.matrix
(income_prop_test <- prop.test(income_matrix))

    5-sample test for equality of proportions without continuity correction

data:  income_matrix
X-squared = 260, df = 4, p-value <2e-16
alternative hypothesis: two.sided
sample estimates:
prop 1 prop 2 prop 3 prop 4 prop 5 
0.9966 0.9930 0.9910 0.9883 0.9896 

Using 5-sample test for equality of proportions without continuity correction we have strong evidence against the null hypothesis that the proportions in the income groups are the same. This provides further evidence that the MTURK sample does not represent the US population.

2.4.2 (2)

Though we might be concerned that our sample does not represent the MTURK population as a whole we have no evidence to support this from the data provided. There should be concern that someone who opts to participate in a survey about satellite radio might be more likely to be a satellite radio listener (unless MTURK workers are much more likely to be Sirius listeners). However, we have no evidence to support this claim.

In thinking about this question we read the articles “Who are these people?” Evaluating the demographic characteristics and political preferences of MTurk survey respondents and “Demographics of Mechanical Turk”.

2.4.3 (3)

In order to estimate the number of Wharton listeners in the US we will create a 95% confidence interval of the proportion of Wharton listeners in the MTURK dataset and multiply this by the Sirius radio listeners (51.6 million).

\[\hat{p} \pm z \sqrt{\frac{\hat{p}(1-\hat{p})}{n}}\]

p_hat <- 
    survey_results_final %>% 
    filter(sirius == "Yes") %>% 
    summarise(p_hat = sum(wharton == "Yes") / n()) %>% 
    unlist()
ci2 <- c(p_hat - qt(0.975, nrow(survey_results_final)) * sqrt(p_hat*(1-p_hat) / nrow(survey_results_final))
        , p_hat + qt(0.975, nrow(survey_results_final)) * sqrt(p_hat*(1-p_hat) / nrow(survey_results_final)))
pop_p <- p_hat * 51.6
pop_ci <- round(ci2 * 51.6,2)

We estimate the sample proportion to be 0.0501 and the 95% confidence interval to be

\[(0.0399, 0.0603)\]

Thus we estimate the size of the Wharton listeners in the US to be 2.58 million and the 95% confidence interval to be (in millions)

\[(2.06, 3.11)\]

2.5 Brief Report

We have reviewed the survey of 1764 respondents of the MTURK survey. We have evidence that the survey respondents do not represent that population of the US based on the proportion of Sirius listeners (0.1605 vs

) and age, gender, income, and education characteristics. However, assuming that the sample represents the population, we estimate that there are between 2.06 and 3.11 million listeners of “Business Radio Powered by the Wharton School”.

3 Question 3

3.1 Part A

x <- seq(0, 1, length = 40)
y <- 1 + 1.2*x + rnorm(40, mean = 0, sd = 2)
ggplot(data_frame(x, y), aes(x = x, y = y)) + geom_point(colour = pal538['blue']) + 
    theme_jrf() +
    scale_x_continuous(expand = c(0.05, 0.01)) + scale_y_continuous(expand = c(0.02, 0.01)) + 
    labs(title = "Scatterplot of (x,y) pairs", y = "y", x = "x")

We use the the lm function to create a linear model.

fit1 <- lm(y ~ x)
tidy(fit1) %>% pander()
term estimate std.error statistic p.value
(Intercept) 0.002587 0.6573 0.003936 0.9969
x 2.963 1.131 2.619 0.01259

We find that \(\beta_0=0.4226\) and \(\beta_1=NA\). Next we overlay LS equation on the scatterplot.

ggplot(data = fit1$model, aes(x = x, y = y)) + geom_point(colour = pal538['blue']) + 
    geom_smooth(method="lm", se = TRUE, colour = pal538['red']) +
    theme_jrf() +
    scale_x_continuous(expand = c(0.05, 0.01)) + scale_y_continuous(expand = c(0.02, 0.01)) + 
    labs(title = "LS Equation", y = "y", x = "x")

The 95% confidence interval for \(\beta_1\) is \[NA \pm 1.96\times NA\] or \[(NA, NA)\]

This 95% confidence interval does indeed contain the true \(\beta_1\) which is \(1.2\).

The RSE is 0.027 which is very close to the true standard deviation of the error of \(\sigma = 2\).

3.2 Part B

We begin with the given simulation code chunk:

x <- seq(0, 1, length = 40)
n_sim <- 100
b1 <- numeric(n_sim) # nsim many LS estimates of beta1 (=1.2) 
upper_ci <- numeric(n_sim) # lower bound
lower_ci <- numeric(n_sim) # upper bound
t_star <- qt(0.975, 38)
# Carry out the simulation 
for (i in 1:n_sim){
    y <- 1 + 1.2 * x + rnorm(40, sd = 2) 
    lse <- lm(y ~ x)
    lse_out <- summary(lse)$coefficients 
    se <- lse_out[2, 2]
    b1[i] <- lse_out[2, 1] 
    upper_ci[i] <- b1[i] + t_star * se 
    lower_ci[i] <- b1[i] - t_star * se
}

We will summarize \(\beta_1\).

summary(b1) %>% pander()
Min. 1st Qu. Median Mean 3rd Qu. Max.
-0.931 0.682 1.22 1.29 1.86 3.94
ggplot(data = data_frame(b1 = b1), aes(x = b1)) + geom_histogram(binwidth = 0.2, fill = pal538['blue']) + 
    geom_vline(xintercept = 1.2, colour = pal538['red']) +
    geom_label(aes(x = 1.2, y = Inf, label = 'beta[1] == 1.2'), 
               vjust = "inward", hjust = "inward", parse = TRUE, colour = pal538['red']) +
    theme_jrf() +
    scale_x_continuous(expand = c(0.05, 0.01)) + scale_y_continuous(expand = c(0.02, 0.01)) + 
    labs(title = expression("Histogram of LS estimates "~b[1]~" of "~beta[1]), y = "Count", x = expression(b[1]))

The sampling distribution does agree with the theory as most of the LS estimate of \(\beta_1\) are close to 1.2.

ci <- data_frame(n = 1:100, b1 = b1 , lower_ci = lower_ci, upper_ci = upper_ci,
                 covers = factor(ifelse(lower_ci < 1.2 & upper_ci > 1.2, "Yes", "No"), levels = c("Yes", "No")))

We find that 97 out of 100 95% confidence intervals cover the true \(\beta_1\). We show this graphically below, where the red intervals do not cover the true \(\beta_1\) and the green intervals do cover the true \(\beta_1\).

ggplot(data = ci) + 
    geom_vline(xintercept = 1.2) +
    geom_segment(aes(x = lower_ci, xend = upper_ci, y = n, yend = n, colour = covers)) +
    labs(title = "100 Sample Confidence Intervals", y = NULL, x = expression(beta[1])) +
    geom_label(aes(x = 1.2, y = Inf, label = 'beta[1] == 1.2'), vjust = "inward", hjust = "inward", parse = TRUE) +
    guides(color = guide_legend(title = expression("Covers "~beta[1]~"?"))) +
    theme(legend.position = 'bottom') +
    theme_jrf() +
    scale_x_continuous(expand = c(0.05, 0.01)) + scale_y_continuous(expand = c(0.02, 0.01)) + 
    scale_colour_manual(values = c('Yes' = pal538['green'][[1]], 'No' = pal538['red'][[1]]))

4 Question 4

4.1 Summary

We begin by loading and tidying the ML Pay dataset.

# Read in the ML Pay dataset
ml_pay <- read_csv(paste0(data_dir, "MLPayData_Total.csv"))
# Let's tidy the dataset
ml_pay2 <- 
    ml_pay %>%
    rename(team = Team.name.2014) %>%
    gather(metric_raw, value, -payroll, -avgwin, -team) %>%
    mutate(
        year = as.factor(str_extract(metric_raw, "(\\d)+"))
        , metric = ifelse(substring(metric_raw,1,1) == "p", "payroll", 
                          ifelse(str_detect(metric_raw, ".pct"), "avgwin", "wins"))
    ) %>%
    select(team, year, metric, value, payroll, avgwin)
ml_pay2

Let’s do a few data quality checks, where we ensure there are 30 teams per year and there are no missing values.

# Show there are 30 unique teams per year
ml_pay2 %>%
    group_by(year) %>%
    summarise(
        n = n()
        , n_distinct = n_distinct(team)
    )
# Show that there are no missing values
ml_pay2 %>%
    summarise(
        na = sum(is.na(value))
        , nan = sum(is.nan(value))
    )

For the 17 years between 1998 and 2014, we summarize the payroll of the 30 teams.

ml_pay2 %>%
    filter(metric == "payroll") %>%
    group_by(year) %>%
    summarise(
          min = min(value)
        , p25 = quantile(value, .25)
        , p50 = quantile(value, .5)
        , mean = mean(value)
        , p75 = quantile(value, .75)
        , max = max(value)
    ) 

The box-plot below show there was a general growth in payroll spending over the 17 years in the MLB. The outlier at the high end of the payroll scale is the New York Yankees.

ml_pay2 %>%
    filter(metric == "payroll") %>%
    ggplot(aes(year, value)) + geom_boxplot(fill = pal538['blue']) +
    theme_jrf() +
    labs(title = "Payroll Growth", y = "Team Payroll ($m)", x = NULL)

Let’s identify what the year-over-year (yoy) growth in payroll has been by team.

ml_pay2 %>%
    filter(metric == "payroll") %>%
    arrange(team, year) %>%
    group_by(team) %>%
    mutate(
        yoy_growth = (value - lag(value)) / lag(value)
    ) %>%
    group_by(team) %>%
    summarise(
        avg_yoy_growth = mean(yoy_growth, na.rm = TRUE)
    ) %>% 
    ungroup() %>%
    arrange(desc(avg_yoy_growth)) %>%
    print(n = 30)

Let’s summarize this as the yoy growth overall.

avg_yoy_growth <- 
    ml_pay2 %>%
        filter(metric == "payroll") %>%
        arrange(team, year) %>%
        group_by(team) %>%
        mutate(
            yoy_growth = (value - lag(value)) / lag(value)
        ) %>%
        group_by(team) %>%
        summarise(
            avg_yoy_growth = mean(yoy_growth, na.rm = TRUE)
        ) %>% 
        ungroup() %>%
        summarise(
            avg_yoy_growth = mean(avg_yoy_growth)
        ) %>%
        unlist()
avg_yoy_growth
avg_yoy_growth 
        0.1042 

Next, we summarize the winning percentage for the 17 years. This is not particularly meaningful, but it is a way to identify any errant values.

ml_pay2 %>%
    filter(metric == "avgwin") %>%
    group_by(year) %>%
    summarise(
          min = min(value)
        , p25 = quantile(value, .25)
        , p50 = quantile(value, .5)
        , mean = mean(value)
        , p75 = quantile(value, .75)
        , max = max(value)
    )

The box-plot below shows the dispersion of winning percentage overtime. You can see the dot in 2003 is the Detroit Tigers who lost more games than any American League team in history (43-119).

ml_pay2 %>%
    filter(metric == "avgwin") %>%
    ggplot(aes(year, value)) + geom_boxplot(fill = pal538['blue']) +
    scale_y_continuous(labels = scales::percent) + 
    theme_jrf() +
    labs(title = "Winning Percentage", y = "Winning Percentage", x = NULL)

Let’s summarize the two variables across time to get an idea of where the values fall.

ml_pay2 %>%
    filter(metric %in% c("payroll","avgwin")) %>%
    group_by(metric) %>%
    summarise(
          min = min(value)
        , p25 = quantile(value, .25)
        , p50 = quantile(value, .5)
        , mean = mean(value)
        , p75 = quantile(value, .75)
        , max = max(value)
    ) %>%
    pander()
metric min p25 p50 mean p75 max
avgwin 0.2654 0.4444 0.50 0.5 0.5556 0.716
payroll 8.3170 51.3329 73.34 78.1 94.9997 235.295

Next, let’s look at scatter plots of payroll vs winning percentage over the 17 years. This plot helps highlight the fact that average payroll increases over time.

ml_pay2 %>%
    filter(metric %in% c("payroll","avgwin")) %>%
    select(-payroll, -avgwin) %>%
    spread(metric, value) %>%
    ggplot(aes(x = payroll, y = avgwin)) + facet_wrap(~ year) + 
    geom_point(colour = pal538['blue'], alpha = 0.75) + 
    geom_smooth(method = "lm", se = FALSE, colour = pal538['red']) +
    scale_y_continuous(labels = scales::percent) + 
    labs(title = "Payroll vs Winning Percentage", y = "Winning Percentage", x = "Payroll ($m)") + 
    theme_jrf() +
    geom_text(data =
                  . %>%
                  group_by(year) %>%
                  summarise(
                      cor = cor(payroll, avgwin)
                  ),
        aes(x = 170, y = .7, label = paste0("cor = ", round(cor, 3))),
            size = 3
        )

avg_person_cor <-
    ml_pay2 %>%
        filter(metric %in% c("payroll","avgwin")) %>%
        select(-payroll, -avgwin) %>%
        spread(metric, value) %>%
        group_by(year) %>%
        summarise(
            cor = cor(payroll, avgwin)    
        ) %>%
        ungroup() %>%
        summarise(
            cor = mean(cor)
        ) %>%
        unlist()
# Avg Person Correlation
avg_person_cor
   cor 
0.4402 

Let’s show the trends in the two variable by team.

ml_pay2 %>%
    filter(metric %in% c("payroll","avgwin")) %>%
    ggplot(aes(x = year, y = value, group = team)) + 
    facet_grid(metric ~ ., scales = "free_y", switch = "y", 
               labeller = ggplot2::labeller(metric = c(avgwin = "Winning Percentage", payroll = "Payroll"))) +
    geom_line(colour = pal538['blue']) + 
    guides(color = FALSE) +
    theme_jrf() +
    labs(title = "Payroll and Winning Percentage by Team", y = NULL, x = NULL)

Summary of Exploratory Analysis:

  1. Payroll has generally been increasing - the yoy average growth is 10.42%.
  2. There does appear to be a linear relationship between payroll and winning percentage, in a given year. The average Person correlation coefficient is 0.44.

4.2 Prediction

Let’s build a linear model to predict winning percentage for each of the 17 years. The best way to do this is using nested data frames (tidyr), purrr, and broom.

lm_by_year <- 
  ml_pay2 %>%
  filter(metric %in% c("payroll","avgwin")) %>%
  select(-payroll, -avgwin) %>%
  spread(metric, value) %>%
  group_by(year) %>% 
  nest() %>%
  mutate(
    model = purrr::map(data, ~ lm(avgwin ~ payroll, data = .))
  )

Below is a summary of each model. It appears that some of the models are significant at 95% confidence level, but a number of models are not, notably 2012, 2014, and 2000. If we look back at the Payroll vs Winning Percentage plot, we can see that correlation for these years are lower than others.

lm_by_year %>% 
    unnest(model %>% purrr::map(broom::glance)) %>%
    select(year, r.squared, adj.r.squared, sigma, statistic, p.value)

Below is the full summary of the model for 1998 and note that the results match the 1998 record above.

summary(lm_by_year$model[[1]])

Call:
lm(formula = avgwin ~ payroll, data = .)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.12418 -0.03151 -0.00042  0.02347  0.11439 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 0.350656   0.025803   13.59  7.5e-14 ***
payroll     0.003635   0.000579    6.27  8.8e-07 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.0546 on 28 degrees of freedom
Multiple R-squared:  0.584, Adjusted R-squared:  0.57 
F-statistic: 39.4 on 1 and 28 DF,  p-value: 8.78e-07

However, before we interpret these models let’s check our model assumptions, aside from linearity, we need to check (1) normality and (2) equal variance of the residuals.

The normal Q-Q plots below show that the residuals are approximately normal.

lm_by_year %>% 
    unnest(model %>% purrr::map(broom::augment)) %>%
    ggplot() +
    facet_wrap(~ year) +
    stat_qq(aes(sample = .std.resid), colour = pal538['blue']) +
    geom_abline(data =
        . %>% 
        group_by(year) %>%
        summarise(
            slope = diff(quantile(.std.resid, c(0.25, 0.75))) / diff(qnorm(c(0.25, 0.75)))
            , int = quantile(.std.resid, c(0.25, 0.75))[1L] - 
               (diff(quantile(.std.resid, c(0.25, 0.75))) / 
                    diff(qnorm(c(0.25, 0.75)))) * qnorm(c(0.25, 0.75))[1L]
        ),
        aes(slope = slope, intercept = int), alpha = 0.5
    ) +
    theme_jrf() +
    scale_x_continuous(labels = NULL) + 
    labs(title = "Normal Q-Q", y = "Standardized Residuals", x = "Theoretical Quantiles")

The fitted values vs residuals plots show approximately equal variance of the residuals (i.e. no heteroscedasticity).

lm_by_year %>% 
    unnest(model %>% purrr::map(broom::augment)) %>%
    ggplot(aes(x = .fitted, y = .resid)) +
    facet_wrap(~ year, scale = "free_x") +
    geom_point(colour = pal538['blue']) +
    geom_smooth(method = "loess", colour = pal538['red'], se = FALSE, size = .25, alpha = 0.5) + 
    geom_hline(yintercept = 0, alpha = 0.5, linetype = 'dashed', color = 'black') +
    theme_jrf() +
    scale_x_continuous(labels = NULL) + 
    labs(title = "Fitted Values vs Residuals", y = "Residuals", x = "Fitted Values")

Having checked the model assumptions, we can look at the \(\beta\) that have been estimated by the models. Below are the 34 coefficients (17 models with an intercept term and a coefficient for payroll). The p-values show that for many of the estimated coefficients we do not have enough evidence to reject the null hypothesis that the coefficients differ from 0.

lm_by_year %>% 
    unnest(model %>% purrr::map(broom::tidy)) %>%
    print(n = 34)

We find that in some years, payroll is a significant variable in predicting winning percentage while in others it is not. We might consider using previous years payroll to predict winning percentage.

4.3 Aggregated Information

Using the aggregated data provided in MLPayData_Total.csv we create linear regression to predict average winning percentage.

fit1 <- lm(avgwin ~ payroll, data = ml_pay2 %>% select(team, payroll, avgwin) %>% distinct())
summary(fit1)

Call:
lm(formula = avgwin ~ payroll, data = ml_pay2 %>% select(team, 
    payroll, avgwin) %>% distinct())

Residuals:
     Min       1Q   Median       3Q      Max 
-0.04003 -0.01749  0.00094  0.01095  0.07030 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   0.4226     0.0153   27.56  < 2e-16 ***
payroll       0.0614     0.0117    5.23  1.5e-05 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.027 on 28 degrees of freedom
Multiple R-squared:  0.494, Adjusted R-squared:  0.476 
F-statistic: 27.4 on 1 and 28 DF,  p-value: 1.47e-05

We find that model is significant with an F-statistic of 27.38.

4.3.1 Red Sox

red_sox <- ml_pay2 %>% select(team, payroll, avgwin) %>% distinct() %>% filter(team == "Boston Red Sox")
(red_sox_interval <- predict(fit1, red_sox, interval = "prediction", level = .95))
     fit    lwr    upr
1 0.5436 0.4848 0.6025

The 95% prediction interval for the Boston Red Sox is \((0.4848, 0.6025)\) and their winning percentage is \(0.5487\). In other words, the model does quite well in predicting the Boston Red Sox’s winning percentage over the 17 year period.

4.3.2 Oakland A’s

oakland <- ml_pay2 %>% select(team, payroll, avgwin) %>% distinct() %>% filter(team == "Oakland Athletics")
(oakland_interval <- predict(fit1, oakland, interval = "prediction", level = .95))
     fit    lwr    upr
1 0.4742 0.4172 0.5312

The 95% prediction interval for the Oakland Athletics is \((0.4172, 0.5312)\) and their winning percentage is \(0.5445\). In other words, the model under-predicting the Oakland Athletic’s winning percentage over the 17 year period as it’s outside the prediction interval. This was an expected result as Billy Beane was the general manager for the A’s during this period.

4.4 Best Model with Historicals

To build a model to best predict the winning percentage in 2014, we’ll use the payroll and winning percentage from previous years. We will use the last 5 years of winning percentages and payroll figures. We use our domain knowledge to assume that data more than 5 years back will not have an influence on the current season.

last_5years <- 
    ml_pay2 %>%
        filter(metric %in% c("payroll","avgwin")) %>%
        select(-payroll, -avgwin) %>%
        arrange(team, year) %>%
        spread(metric, value) %>%
        group_by(team) %>%
        mutate(
              payroll_lag1 = lag(payroll, 1)
            , payroll_lag2 = lag(payroll, 2)
            , payroll_lag3 = lag(payroll, 3)
            , payroll_lag4 = lag(payroll, 4)
            , payroll_lag5 = lag(payroll, 5)
            , avgwin_lag1 = lag(avgwin, 1)
            , avgwin_lag2 = lag(avgwin, 2)
            , avgwin_lag3 = lag(avgwin, 3)
            , avgwin_lag4 = lag(avgwin, 4)
            , avgwin_lag5 = lag(avgwin, 5)
        ) %>%
        ungroup() %>%
        filter(year == 2014) %>%
        select(-payroll, -year)
summary(lm(avgwin ~ . -team, data = last_5years))

Call:
lm(formula = avgwin ~ . - team, data = last_5years)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.09422 -0.01651  0.00883  0.02237  0.06299 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)   0.525603   0.115091    4.57  0.00021 ***
payroll_lag1  0.000450   0.000339    1.33  0.20019    
payroll_lag2  0.000828   0.000626    1.32  0.20133    
payroll_lag3 -0.000859   0.000772   -1.11  0.27985    
payroll_lag4  0.000035   0.000898    0.04  0.96933    
payroll_lag5  0.000340   0.000770    0.44  0.66390    
avgwin_lag1   0.088470   0.162141    0.55  0.59167    
avgwin_lag2   0.515698   0.179432    2.87  0.00972 ** 
avgwin_lag3  -0.528842   0.216923   -2.44  0.02477 *  
avgwin_lag4  -0.338486   0.190392   -1.78  0.09144 .  
avgwin_lag5   0.050099   0.214129    0.23  0.81751    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.0463 on 19 degrees of freedom
Multiple R-squared:   0.6,  Adjusted R-squared:  0.39 
F-statistic: 2.85 on 10 and 19 DF,  p-value: 0.0237

We will iteratively remove the explanatory variable that has the largest p-value for the coefficient estimate.

summary(lm(avgwin ~ . -team -payroll_lag4, data = last_5years))

Call:
lm(formula = avgwin ~ . - team - payroll_lag4, data = last_5years)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.09415 -0.01685  0.00889  0.02227  0.06232 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)   0.526261   0.110967    4.74  0.00012 ***
payroll_lag1  0.000449   0.000329    1.36  0.18831    
payroll_lag2  0.000832   0.000601    1.39  0.18127    
payroll_lag3 -0.000845   0.000669   -1.26  0.22096    
payroll_lag5  0.000363   0.000488    0.74  0.46594    
avgwin_lag1   0.089320   0.156605    0.57  0.57479    
avgwin_lag2   0.513650   0.167223    3.07  0.00602 ** 
avgwin_lag3  -0.530114   0.209032   -2.54  0.01966 *  
avgwin_lag4  -0.337122   0.182412   -1.85  0.07943 .  
avgwin_lag5   0.049045   0.207041    0.24  0.81516    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.0451 on 20 degrees of freedom
Multiple R-squared:   0.6,  Adjusted R-squared:  0.42 
F-statistic: 3.33 on 9 and 20 DF,  p-value: 0.0119
summary(lm(avgwin ~ . -team -payroll_lag4 -avgwin_lag5, data = last_5years))

Call:
lm(formula = avgwin ~ . - team - payroll_lag4 - avgwin_lag5, 
    data = last_5years)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.09380 -0.01754  0.00645  0.01984  0.06502 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)   0.538249   0.096510    5.58  1.6e-05 ***
payroll_lag1  0.000457   0.000320    1.43   0.1682    
payroll_lag2  0.000870   0.000566    1.54   0.1390    
payroll_lag3 -0.000837   0.000653   -1.28   0.2137    
payroll_lag5  0.000364   0.000477    0.76   0.4536    
avgwin_lag1   0.091051   0.152878    0.60   0.5578    
avgwin_lag2   0.506139   0.160457    3.15   0.0048 ** 
avgwin_lag3  -0.532122   0.204112   -2.61   0.0165 *  
avgwin_lag4  -0.315147   0.153493   -2.05   0.0527 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.0441 on 21 degrees of freedom
Multiple R-squared:  0.599, Adjusted R-squared:  0.446 
F-statistic: 3.92 on 8 and 21 DF,  p-value: 0.00566
summary(lm(avgwin ~ . -team -payroll_lag4 -avgwin_lag5 -avgwin_lag1, data = last_5years))

Call:
lm(formula = avgwin ~ . - team - payroll_lag4 - avgwin_lag5 - 
    avgwin_lag1, data = last_5years)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.09901 -0.01594  0.00531  0.02218  0.06506 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)   0.558474   0.089004    6.27  2.6e-06 ***
payroll_lag1  0.000509   0.000304    1.67   0.1082    
payroll_lag2  0.000883   0.000557    1.58   0.1272    
payroll_lag3 -0.000940   0.000621   -1.51   0.1442    
payroll_lag5  0.000390   0.000468    0.83   0.4136    
avgwin_lag2   0.540367   0.147599    3.66   0.0014 ** 
avgwin_lag3  -0.507848   0.197046   -2.58   0.0172 *  
avgwin_lag4  -0.321684   0.150838   -2.13   0.0444 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.0434 on 22 degrees of freedom
Multiple R-squared:  0.592, Adjusted R-squared:  0.462 
F-statistic: 4.56 on 7 and 22 DF,  p-value: 0.00282
summary(lm(avgwin ~ . -team -payroll_lag4 -avgwin_lag5 -avgwin_lag1 -payroll_lag5, data = last_5years))

Call:
lm(formula = avgwin ~ . - team - payroll_lag4 - avgwin_lag5 - 
    avgwin_lag1 - payroll_lag5, data = last_5years)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.10148 -0.01826  0.00261  0.02746  0.06409 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)   0.586540   0.081836    7.17  2.7e-07 ***
payroll_lag1  0.000552   0.000297    1.86   0.0764 .  
payroll_lag2  0.000748   0.000530    1.41   0.1711    
payroll_lag3 -0.000604   0.000469   -1.29   0.2104    
avgwin_lag2   0.504779   0.140343    3.60   0.0015 ** 
avgwin_lag3  -0.464948   0.188934   -2.46   0.0218 *  
avgwin_lag4  -0.361271   0.142207   -2.54   0.0183 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.0432 on 23 degrees of freedom
Multiple R-squared:  0.579, Adjusted R-squared:  0.47 
F-statistic: 5.28 on 6 and 23 DF,  p-value: 0.00151
summary(lm(avgwin ~ . -team -payroll_lag4 -avgwin_lag5 -avgwin_lag1 -payroll_lag5 -payroll_lag3, data = last_5years))

Call:
lm(formula = avgwin ~ . - team - payroll_lag4 - avgwin_lag5 - 
    avgwin_lag1 - payroll_lag5 - payroll_lag3, data = last_5years)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.10542 -0.01801  0.00232  0.02926  0.06465 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)   0.570319   0.081966    6.96  3.4e-07 ***
payroll_lag1  0.000392   0.000274    1.43   0.1653    
payroll_lag2  0.000244   0.000362    0.67   0.5063    
avgwin_lag2   0.519764   0.141771    3.67   0.0012 ** 
avgwin_lag3  -0.369298   0.176113   -2.10   0.0467 *  
avgwin_lag4  -0.419934   0.136562   -3.08   0.0052 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.0437 on 24 degrees of freedom
Multiple R-squared:  0.549, Adjusted R-squared:  0.455 
F-statistic: 5.84 on 5 and 24 DF,  p-value: 0.00115
summary(lm(avgwin ~ . -team -payroll_lag4 -avgwin_lag5 -avgwin_lag1 -payroll_lag5 -payroll_lag3 -payroll_lag2, data = last_5years))

Call:
lm(formula = avgwin ~ . - team - payroll_lag4 - avgwin_lag5 - 
    avgwin_lag1 - payroll_lag5 - payroll_lag3 - payroll_lag2, 
    data = last_5years)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.11094 -0.01508  0.00388  0.02677  0.07620 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)   0.565351   0.080740    7.00  2.4e-07 ***
payroll_lag1  0.000499   0.000221    2.25   0.0333 *  
avgwin_lag2   0.486773   0.131613    3.70   0.0011 ** 
avgwin_lag3  -0.324439   0.161292   -2.01   0.0552 .  
avgwin_lag4  -0.396055   0.130451   -3.04   0.0055 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.0433 on 25 degrees of freedom
Multiple R-squared:  0.54,  Adjusted R-squared:  0.467 
F-statistic: 7.35 on 4 and 25 DF,  p-value: 0.000467
summary(lm(avgwin ~ . -team -payroll_lag4 -avgwin_lag5 -avgwin_lag1 -payroll_lag5 -payroll_lag3 -payroll_lag2 -avgwin_lag3, data = last_5years))

Call:
lm(formula = avgwin ~ . - team - payroll_lag4 - avgwin_lag5 - 
    avgwin_lag1 - payroll_lag5 - payroll_lag3 - payroll_lag2 - 
    avgwin_lag3, data = last_5years)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.14646 -0.01807  0.00922  0.02539  0.07564 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)   0.508958   0.080029    6.36  9.8e-07 ***
payroll_lag1  0.000305   0.000211    1.45   0.1598    
avgwin_lag2   0.383337   0.128052    2.99   0.0060 ** 
avgwin_lag4  -0.464237   0.133145   -3.49   0.0018 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.0457 on 26 degrees of freedom
Multiple R-squared:  0.466, Adjusted R-squared:  0.404 
F-statistic: 7.56 on 3 and 26 DF,  p-value: 0.000853
summary(lm(avgwin ~ . -team -payroll_lag4 -avgwin_lag5 -avgwin_lag1 -payroll_lag5 -payroll_lag3 -payroll_lag2 -avgwin_lag3 -payroll_lag1, data = last_5years))

Call:
lm(formula = avgwin ~ . - team - payroll_lag4 - avgwin_lag5 - 
    avgwin_lag1 - payroll_lag5 - payroll_lag3 - payroll_lag2 - 
    avgwin_lag3 - payroll_lag1, data = last_5years)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.14569 -0.01120  0.00467  0.02812  0.07996 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)   0.4791     0.0789    6.07  1.7e-06 ***
avgwin_lag2   0.4543     0.1207    3.77  0.00082 ***
avgwin_lag4  -0.4126     0.1308   -3.15  0.00393 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.0466 on 27 degrees of freedom
Multiple R-squared:  0.423, Adjusted R-squared:  0.38 
F-statistic:  9.9 on 2 and 27 DF,  p-value: 0.000597

Using this process, we would select a model with the two explanatory variables of the average winning percentage from 2 years and 4 years ago. This model seems rather arbitrary because there is not something significant about 2 years or 4 years ago.

Perhaps a better solutions is to build features from the previous years. Below we attempt to using simple exponential smoothing (\(\alpha = 0.6\) to weight recent values more) of payroll and winning percentage over 3 years.

alpha <- 0.6
nyears <- 3
fn_ses_forecast <- function(x) {
  if (sum(!is.na(x)) < nyears) {
    fore <- as.double(NA)
  } else {
    fore <- data.frame(ses(x, alpha = alpha, initial = 'simple'))[,1][1]
  }
  return(fore)
}
last_2years <-
    ml_pay2 %>%
        filter(metric %in% c("payroll","avgwin")) %>%
        select(-payroll, -avgwin) %>%
        arrange(team, year) %>%
        spread(metric, value) %>%
        group_by(team) %>%
        mutate(
            payroll_ses = rollapply(payroll, FUN = fn_ses_forecast, 
                            width = list(-nyears:-1), fill = NA, by.column = TRUE, align = "right")
            , avgwin_ses = rollapply(avgwin, FUN = fn_ses_forecast, 
                            width = list(-nyears:-1), fill = NA, by.column = TRUE, align = "right")
        ) %>%
        ungroup() %>%
        group_by(team) %>%
        mutate(
            payroll_ses_lag1 = lag(payroll_ses,1)
            , avgwin_ses_lag1 = lag(avgwin_ses,1)
        ) %>%
        ungroup() %>%
        filter(year == 2014) %>%
        select(team, avgwin, payroll_ses, avgwin_ses, payroll_ses_lag1, avgwin_ses_lag1)
last_2years

Despite our efforts above, we find that this is not very helpful. We settle on a model that contains the 3-year smoothed payroll figures.

(ses_fit <- step(lm(avgwin ~ . -team, data = last_2years)))
Start:  AIC=-167.8
avgwin ~ (team + payroll_ses + avgwin_ses + payroll_ses_lag1 + 
    avgwin_ses_lag1) - team

                   Df Sum of Sq    RSS  AIC
- avgwin_ses_lag1   1   0.00030 0.0804 -170
- avgwin_ses        1   0.00354 0.0837 -168
- payroll_ses_lag1  1   0.00532 0.0855 -168
- payroll_ses       1   0.00542 0.0855 -168
<none>                          0.0801 -168

Step:  AIC=-169.7
avgwin ~ payroll_ses + avgwin_ses + payroll_ses_lag1

                   Df Sum of Sq    RSS  AIC
- avgwin_ses        1   0.00474 0.0852 -170
- payroll_ses       1   0.00521 0.0856 -170
<none>                          0.0804 -170
- payroll_ses_lag1  1   0.00561 0.0860 -170

Step:  AIC=-169.9
avgwin ~ payroll_ses + payroll_ses_lag1

                   Df Sum of Sq    RSS  AIC
<none>                          0.0852 -170
- payroll_ses_lag1  1    0.0134 0.0986 -168
- payroll_ses       1    0.0166 0.1018 -167

Call:
lm(formula = avgwin ~ payroll_ses + payroll_ses_lag1, data = last_2years)

Coefficients:
     (Intercept)       payroll_ses  payroll_ses_lag1  
         0.49336           0.00124          -0.00123  

Using this model, we can predict the winning percentage for the teams in 2015. To do this we just need to change the width of our rolling simple exponential smoothing function to include 2014 data as this was not being used in the previous prediction of 2014.

last_2years_2015 <-
    ml_pay2 %>%
        filter(metric %in% c("payroll","avgwin")) %>%
        select(-payroll, -avgwin) %>%
        arrange(team, year) %>%
        spread(metric, value) %>%
        group_by(team) %>%
        mutate(
            payroll_ses = rollapply(payroll, FUN = fn_ses_forecast, 
                            width = list((-nyears+1):0), fill = NA, by.column = TRUE, align = "right")
            , avgwin_ses = rollapply(avgwin, FUN = fn_ses_forecast, 
                            width = list((-nyears+1):0), fill = NA, by.column = TRUE, align = "right")
        ) %>%
        ungroup() %>%
        group_by(team) %>%
        mutate(
            payroll_ses_lag1 = lag(payroll_ses,1)
            , avgwin_ses_lag1 = lag(avgwin_ses,1)
        ) %>%
        ungroup() %>%
        filter(year == 2014) %>%
        select(team, avgwin, payroll_ses, avgwin_ses, payroll_ses_lag1, avgwin_ses_lag1)
cbind(
    last_2years_2015 %>% select(team), 
    prediction_2015 = predict(ses_fit, last_2years_2015)
) %>% tbl_df %>%
    print(n = 30)

5 Question 5

5.1 Exploratory Analysis

We can start of with basic pairs plot, but it’s difficult to read.

pairs(Auto)

But, we can do much better than that using ggpairs. Here we can learn much more about our dataset.

Auto_proper <- 
    Auto %>% tbl_df %>%
    mutate(
          cylinders = as.factor(cylinders)
        , year = as.integer(paste0("19", year))
        , year2 = as.factor(paste0("19", year))
        , origin = factor(origin, labels = c('American', 'European', 'Japanese'))
        , name = as.character(name)
    )
ggpairs(Auto_proper %>% select(-name, -year2)) + theme_jrf()

From this plot alone, we can glean a lot information about the Auto dataset.

  1. Cars with fewer cylinders generally have higher MPG
  2. There are negative relationships between displacement, horsepower, and weight and MPG
  3. In general, newer cars have better MPG
  4. American made cars have much lower MPGs than European or Japanese cars
  5. Most of the cars in the dataset are from America
  6. Cars with 6 and 8 cylinders almost exclusively come from America
  7. Each year in the range of the dataset has a nearly equal number of cars
  8. Generally cars with fewer cylinders are lighter

More points can be made but this is a strong starting point.

Before going much further, let’s check to ensure we have no missing data.

# Complete.cases shows there is no missing values
sum(!complete.cases(Auto_proper))
[1] 0

We can do some summary statistics to get a feel of the bounds of the variables

sapply(Auto_proper, summary)
$mpg
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    9.0    17.0    22.8    23.4    29.0    46.6 

$cylinders
  3   4   5   6   8 
  4 199   3  83 103 

$displacement
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
     68     105     151     194     276     455 

$horsepower
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   46.0    75.0    93.5   104.0   126.0   230.0 

$weight
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1610    2230    2800    2980    3610    5140 

$acceleration
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    8.0    13.8    15.5    15.5    17.0    24.8 

$year
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1970    1970    1980    1980    1980    1980 

$origin
American European Japanese 
     245       68       79 

$name
   Length     Class      Mode 
      392 character character 

$year2
191970 191971 191972 191973 191974 191975 191976 191977 191978 191979 191980 191981 191982 
    29     27     28     40     26     30     34     28     36     29     27     28     30 

5.2 Year

5.2.1 MPG vs Year

auto_fit1 <- lm(mpg ~ year, data = Auto_proper)
summary(auto_fit1)

Call:
lm(formula = mpg ~ year, data = Auto_proper)

Residuals:
    Min      1Q  Median      3Q     Max 
-12.021  -5.441  -0.441   4.974  18.209 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -2.41e+03   1.73e+02   -13.9   <2e-16 ***
year         1.23e+00   8.74e-02    14.1   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 6.36 on 390 degrees of freedom
Multiple R-squared:  0.337, Adjusted R-squared:  0.335 
F-statistic:  198 on 1 and 390 DF,  p-value: <2e-16

We find that model year is a significant variable at the 0.05 level. We have strong evidence against the hypothesis that the coefficient associated with year is equal to 0 (P-value = 1.076e-36).

We estimate that for each additional year (car being newer) a cars MPG increases by 1.23. For example, for a car with model year 1980 we estimate 28.3912 mpg and a car with model year 1981 we estimate 29.6212. The difference a year makes in the estimate is \(29.6212 - 28.3912= 0\).

5.2.2 Add Horsepower

auto_fit2 <- lm(mpg ~ horsepower + year, data = Auto_proper)
summary(auto_fit2)

Call:
lm(formula = mpg ~ horsepower + year, data = Auto_proper)

Residuals:
    Min      1Q  Median      3Q     Max 
-12.077  -3.078  -0.431   2.588  15.315 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1.26e+03   1.31e+02   -9.61   <2e-16 ***
horsepower  -1.32e-01   6.34e-03  -20.76   <2e-16 ***
year         6.57e-01   6.63e-02    9.92   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 4.39 on 389 degrees of freedom
Multiple R-squared:  0.685, Adjusted R-squared:  0.684 
F-statistic:  424 on 2 and 389 DF,  p-value: <2e-16

Year is still significant at the 0.05 level. We have strong evidence against the hypothesis that the coefficient associated with year is equal to 0 (P-value = 7.994e-21).

For cars with the same horsepower, we estimate that each additional year (car being newer) a cars MPG increases by 0.6573.

We show the two confidence intervals for the coefficient of year between the two models.

confint(auto_fit1, "year", level = 0.95)
     2.5 % 97.5 %
year 1.058  1.402
confint(auto_fit2, "year", level = 0.95)
     2.5 % 97.5 %
year 0.527 0.7875

These two confidence intervals are different. To a non-statistician, we would describe this difference as

In our first model to predict MPG, we only use the model year of the car. In our second model, we include horsepower which explains part of the variation in MPG between cars. In other words, the effect of the model year on MPG is smaller when we include the variation explained by horsepower.

5.2.3 Interaction Term

auto_fit3 <- lm(mpg ~ horsepower * year, data = Auto_proper)
summary(auto_fit3)

Call:
lm(formula = mpg ~ horsepower * year, data = Auto_proper)

Residuals:
    Min      1Q  Median      3Q     Max 
-12.349  -2.451  -0.456   2.406  14.444 

Coefficients:
                 Estimate Std. Error t value Pr(>|t|)    
(Intercept)     -4.29e+03   3.19e+02   -13.5   <2e-16 ***
horsepower       3.14e+01   3.08e+00    10.2   <2e-16 ***
year             2.19e+00   1.61e-01    13.6   <2e-16 ***
horsepower:year -1.60e-02   1.56e-03   -10.2   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.9 on 388 degrees of freedom
Multiple R-squared:  0.752, Adjusted R-squared:  0.75 
F-statistic:  393 on 3 and 388 DF,  p-value: <2e-16

The interaction term is significant at the 0.05 level. We have strong evidence against the hypothesis that the coefficient associated with the interaction of year and horsepower is equal to 0 (P-value = 7.367e-22)

Now the effect of year cannot be interpreted uniformly when holding the other variable horsepower constant as it depends on the value of horsepower. Thus, we show the effect of a 1 year increase in model year (one year newer), on the 25th percentile, median, and 75th percentile horsepower values in our dataset.

Horsepower = 75 Horsepower = 93.5 Horsepower = 126
Effect of 1 year increase in model year 0.9951 0.6999 0.1812

5.3 Cylinder

We have the cylinder variable coded a categorical variable because the number of cylinders is a characteristic of the car, rather than a feature that can be easily changed. In other words, a 1 unit change in cylinder is really not meaningful as most engines are made with cylinders with multiples of 2.

Auto_proper %>%
    filter(!(cylinders %in% c("3","5"))) %>%
    ggplot(aes(x = cylinders, y = mpg, fill = cylinders)) + 
    scale_fill_manual("Cylinders", values = c('4' = pal538['blue'][[1]], '6' = pal538['green'][[1]], '8' = pal538['red'][[1]])) +
    geom_boxplot() + 
    theme_jrf() +
    labs(title = "MPG vs Cylinders", x = "Cylinders", y  = "MPG")

5.3.1 As Quantitative Variable

Per the question, we will use cylinders as a integer (not continuous).

auto_fit4 <- lm(mpg ~ horsepower + as.integer(cylinders), data = Auto_proper)
summary(auto_fit4)

Call:
lm(formula = mpg ~ horsepower + as.integer(cylinders), data = Auto_proper)

Residuals:
    Min      1Q  Median      3Q     Max 
-12.392  -2.965  -0.318   2.149  16.634 

Coefficients:
                      Estimate Std. Error t value Pr(>|t|)    
(Intercept)           40.72614    0.65869   61.83   <2e-16 ***
horsepower            -0.08617    0.00985   -8.75   <2e-16 ***
as.integer(cylinders) -2.57965    0.28486   -9.06   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 4.46 on 389 degrees of freedom
Multiple R-squared:  0.675, Adjusted R-squared:  0.673 
F-statistic:  403 on 2 and 389 DF,  p-value: <2e-16

Cylinders is significant at the 0.01 level. We have strong evidence against the hypothesis that the coefficient associated with cylinders is equal to 0 (P-value = 6.634e-18).

We estimate that, holding horsepower constant, for each additional cylinder in the car, the car’s mpg is -2.5796 lower.

5.3.2 As Categorical Variable

auto_fit5 <- lm(mpg ~ horsepower + cylinders, data = Auto_proper)
summary(auto_fit5)

Call:
lm(formula = mpg ~ horsepower + cylinders, data = Auto_proper)

Residuals:
   Min     1Q Median     3Q    Max 
 -9.59  -2.71  -0.61   1.90  16.33 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  30.7761     2.4128   12.76   <2e-16 ***
horsepower   -0.1030     0.0113   -9.09   <2e-16 ***
cylinders4    6.5734     2.1692    3.03   0.0026 ** 
cylinders5    5.0737     3.2666    1.55   0.1212    
cylinders6   -0.3441     2.1858   -0.16   0.8750    
cylinders8    0.4974     2.2764    0.22   0.8272    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 4.27 on 386 degrees of freedom
Multiple R-squared:  0.705, Adjusted R-squared:  0.701 
F-statistic:  184 on 5 and 386 DF,  p-value: <2e-16

Cylinders is significant at the 0.01 level. If one of the coefficients of the factor levels in the model is significant, then the variable as whole is significant. We then use ANOVA to compare the two models.

anova(auto_fit4, auto_fit5)
Analysis of Variance Table

Model 1: mpg ~ horsepower + as.integer(cylinders)
Model 2: mpg ~ horsepower + cylinders
  Res.Df  RSS Df Sum of Sq    F  Pr(>F)    
1    389 7752                              
2    386 7037  3       715 13.1 3.8e-08 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

We have strong evidence that the model using categorical variable for cylinder better explains the variation in MPG than the model that uses a quantitative variable for cylinder (P-value = 3.782e-08)

5.3.3 Difference

The fundamental difference between model 1 and model 2 is that model 1 assumes that there can be incremental increase in cylinders whereas model 2 assumes that there are different types of cylinders. In reality, you cannot increase a cars cylinders by 0.25 so model 1 is not practically valid. Model 2 recognizes the nature of the variable cylinder and how cars are made, thus being a practically applicable model.

The plots below provide a comparison between the two models.

cylinders <- seq(from = 2, to = 8, by = 0.25)
int_line <- data_frame(
                  cylinders = cylinders
                , int = coef(summary(auto_fit4))[,1][[1]] + coef(summary(auto_fit4))[,1][[3]]*cylinders
                , slope = coef(summary(auto_fit4))[,1][[2]]
                )
g1 <- 
    ggplot(Auto_proper, aes(x = horsepower, y = mpg)) + 
    geom_point(alpha = 0.5) + 
    geom_abline(data = int_line, aes(intercept = int, slope = slope, colour = as.factor(cylinders))) +
    theme_jrf() +
    labs(title = "Model 1: Quantative Cylinders", x = "Horsepower", y  = "MPG") +
    guides(colour = guide_legend(title = "Cylinders (sample)"))
    
g2 <-
    ggplot(Auto_proper, aes(x = horsepower, y = mpg, colour = cylinders)) + 
    geom_point(alpha = 0.5) + 
    geom_smooth(method = "lm", se = FALSE) +
    theme_jrf() +
    labs(title = "Model 2: Categorical Cylinders", x = "Horsepower", y  = "MPG") +
    scale_colour_manual("Cylinders", values = c('3' = "#ffff00",
                                                 '4' = pal538['blue'][[1]], 
                                                 '5' = pal538['dkgray'][[1]],
                                                 '6' = pal538['green'][[1]], 
                                                 '8' = pal538['red'][[1]]))
grid.arrange(g1, g2, ncol = 2)

5.4 Final Model

First we make a dataframe of the car that we will predict MPG.

future_car <- data_frame(
       year = 1983
     , length = 180 
     , cylinders = factor(8, levels = c(3,4,5,6,8))
     , displacement = 350 
     , horsepower = 260
     , weight = 4000
     )

Reviewing the diagonal from the pairs plot in the exploratory analysis, we note that the continuous predictors and the dependent variable MPG are all somewhat normally distribute and we decide not to perform any transformations.

We will use the leaps package using each of the 3 methods. With each we will:

  1. Show the feature combinations for each value of d (number of predictors)
  2. Plot Mallow’s Cp, BIC, and Adjusted \(R^2\).

5.4.1 Exhaustive

auto_fit6 <- regsubsets(mpg ~ ., data = Auto_proper %>% select(-name, -year2), nvmax = 11, method="exhaustive")
auto_fit6_sum <- summary(auto_fit6)
as_data_frame(auto_fit6_sum$outmat) %>% print(width = Inf)
data_frame(
      predictors = 1:length(auto_fit6_sum$cp)
    , cp = auto_fit6_sum$cp
    , bic = auto_fit6_sum$bic
    , adjr2 = auto_fit6_sum$adjr2
) %>%
    gather(metric, value, -predictors) %>%
    mutate(metric = factor(metric, levels = c("cp","bic","adjr2"))) %>%
    ggplot(aes(x = predictors, y = value, colour = metric)) +
    facet_grid(metric ~ ., scale = "free_y", switch = "y", 
               labeller = ggplot2::labeller(metric = c(cp = "Cp", bic = "BIC", adjr2 = "Adjusted R^2"))) +
    geom_vline(xintercept = 3, alpha = 0.5) + geom_line() + geom_point() +
    geom_label(data = data_frame(
        predictors = c(which.min(auto_fit6_sum$cp), which.min(auto_fit6_sum$bic), which.max(auto_fit6_sum$adjr2))
        , metric = factor(c("cp","bic","adjr2"), levels = c("cp","bic","adjr2"))
        , value = c(min(auto_fit6_sum$cp), min(auto_fit6_sum$bic), max(auto_fit6_sum$adjr2))
        , label = paste0("Optimal\nd=", c(which.min(auto_fit6_sum$cp), which.min(auto_fit6_sum$bic), which.max(auto_fit6_sum$adjr2)))
        , vjust = c(-.5, -.5, 1.25)
    ), aes(x = predictors, y = value, label = label, vjust = vjust), family = "DecimaMonoPro") +
    theme_jrf() + 
    labs(title = "Exhaustive Search", x = "# of Predictors", y = NULL) +
    geom_label(data = data_frame(x = 3, y = 300, metric = factor(c("cp"), levels = c("cp","bic","adjr2")), 
                label = "Elbow with\n3 predictors"), aes(x=x,y=y,label=label), colour = "black", hjust = -.1,
               family = "DecimaMonoPro") + 
    scale_colour_manual(guide = FALSE, values = c(pal538['red'][[1]], pal538['green'][[1]], pal538['blue'][[1]]))

5.4.2 Forward

auto_fit7 <- regsubsets(mpg ~ ., data = Auto_proper %>% select(-name, -year2), nvmax = 11, method="forward")
auto_fit7_sum <- summary(auto_fit7)
as_data_frame(auto_fit7_sum$outmat) %>% print(width = Inf)
data_frame(
      predictors = 1:length(auto_fit7_sum$cp)
    , cp = auto_fit7_sum$cp
    , bic = auto_fit7_sum$bic
    , adjr2 = auto_fit7_sum$adjr2
) %>%
    gather(metric, value, -predictors) %>%
    mutate(metric = factor(metric, levels = c("cp","bic","adjr2"))) %>%
    ggplot(aes(x = predictors, y = value, colour = metric)) +
    facet_grid(metric ~ ., scale = "free_y", switch = "y", 
               labeller = ggplot2::labeller(metric = c(cp = "Cp", bic = "BIC", adjr2 = "Adjusted R^2"))) +
    geom_vline(xintercept = 3, alpha = 0.5) + geom_line() + geom_point() +
    geom_label(data = data_frame(
        predictors = c(which.min(auto_fit7_sum$cp), which.min(auto_fit7_sum$bic), which.max(auto_fit7_sum$adjr2))
        , metric = factor(c("cp","bic","adjr2"), levels = c("cp","bic","adjr2"))
        , value = c(min(auto_fit7_sum$cp), min(auto_fit7_sum$bic), max(auto_fit7_sum$adjr2))
        , label = paste0("Optimal\nd=", c(which.min(auto_fit7_sum$cp), which.min(auto_fit7_sum$bic) ,which.max(auto_fit7_sum$adjr2)))
        , vjust = c(-.5, -.5, 1.25)
    ), aes(x = predictors, y = value, label = label, vjust = vjust), family = "DecimaMonoPro") +
    theme_jrf() + 
    labs(title = "Forward Search", x = "# of Predictors", y = NULL) +
    geom_label(data = data_frame(x = 3, y = 300, metric = factor(c("cp"), levels = c("cp","bic","adjr2")), 
                label = "Elbow with\n3 predictors"), aes(x=x,y=y,label=label), colour = "black", hjust = -.1,
               family = "DecimaMonoPro") + 
    scale_colour_manual(guide = FALSE, values = c(pal538['red'][[1]], pal538['green'][[1]], pal538['blue'][[1]]))

5.4.3 Backward

auto_fit8 <- regsubsets(mpg ~ ., data = Auto_proper %>% select(-name, -year2), nvmax = 11, method="backward")
auto_fit8_sum <- summary(auto_fit7)
as_data_frame(auto_fit8_sum$outmat) %>% print(width = Inf)
data_frame(
      predictors = 1:length(auto_fit8_sum$cp)
    , cp = auto_fit8_sum$cp
    , bic = auto_fit8_sum$bic
    , adjr2 = auto_fit8_sum$adjr2
) %>%
    gather(metric, value, -predictors) %>%
    mutate(metric = factor(metric, levels = c("cp","bic","adjr2"))) %>%
    ggplot(aes(x = predictors, y = value, colour = metric)) +
    facet_grid(metric ~ ., scale = "free_y", switch = "y", 
               labeller = ggplot2::labeller(metric = c(cp = "Cp", bic = "BIC", adjr2 = "Adjusted R^2"))) +
    geom_vline(xintercept = 3, alpha = 0.5) + geom_line() + geom_point() +
    geom_label(data = data_frame(
        predictors = c(which.min(auto_fit8_sum$cp), which.min(auto_fit8_sum$bic), which.max(auto_fit8_sum$adjr2))
        , metric = factor(c("cp","bic","adjr2"), levels = c("cp","bic","adjr2"))
        , value = c(min(auto_fit8_sum$cp), min(auto_fit8_sum$bic), max(auto_fit8_sum$adjr2))
        , label = paste0("Optimal\nd=", c(which.min(auto_fit8_sum$cp), which.min(auto_fit8_sum$bic), which.max(auto_fit8_sum$adjr2)))
        , vjust = c(-.5, -.5, 1.25)
    ), aes(x = predictors, y = value, label = label, vjust = vjust), family = "DecimaMonoPro") +
    theme_jrf() + 
    labs(title = "Backward Search", x = "# of Predictors", y = NULL) +
    geom_label(data = data_frame(x = 3, y = 300, metric = factor(c("cp"), levels = c("cp","bic","adjr2")), 
                label = "Elbow with\n3 predictors"), aes(x=x,y=y,label=label), colour = "black", hjust = -.1,
               family = "DecimaMonoPro") + 
    scale_colour_manual(guide = FALSE, values = c(pal538['red'][[1]], pal538['green'][[1]], pal538['blue'][[1]]))

5.4.4 Selection

In all 3 methods, we find that there is an elbow in the information criteria at 3 predictors. These three predictors are

  1. Weight
  2. Year
  3. 6 Cylinder Level of Cylinders

Regarding (3), this indicates that we might want to try creating a binary variable, whether or not the car is 6 cylinders. We will create 4 models

  1. Model 1: Cylinders - all levels
  2. Model 2: Binary 6-cylinder
  3. Model 3: Binary 6-cylinder & Horsepower
  4. Model 4: Cylinders - all levels & Horsepower

Model 1: Cylinders - all levels

Auto_proper2 <-
    Auto_proper %>%
    mutate(
        is_6cylinder = cylinders == 6
    )
auto_fit9 <- lm(mpg ~ weight + year + cylinders, Auto_proper2)
summary(auto_fit9)

Call:
lm(formula = mpg ~ weight + year + cylinders, data = Auto_proper2)

Residuals:
   Min     1Q Median     3Q    Max 
-8.618 -2.047 -0.129  1.772 13.882 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1.45e+03   9.32e+01  -15.55  < 2e-16 ***
weight      -6.17e-03   4.38e-04  -14.06  < 2e-16 ***
year         7.51e-01   4.71e-02   15.94  < 2e-16 ***
cylinders4   7.01e+00   1.62e+00    4.33  1.9e-05 ***
cylinders5   8.53e+00   2.47e+00    3.45  0.00061 ***
cylinders6   4.04e+00   1.68e+00    2.41  0.01649 *  
cylinders8   6.19e+00   1.80e+00    3.44  0.00064 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.2 on 385 degrees of freedom
Multiple R-squared:  0.834, Adjusted R-squared:  0.832 
F-statistic:  323 on 6 and 385 DF,  p-value: <2e-16

Model 2: Binary 6-cylinder

auto_fit10 <- lm(mpg ~ weight + year + is_6cylinder, Auto_proper2)
summary(auto_fit10)

Call:
lm(formula = mpg ~ weight + year + is_6cylinder, data = Auto_proper2)

Residuals:
   Min     1Q Median     3Q    Max 
-9.196 -2.005 -0.116  1.824 13.929 

Coefficients:
                  Estimate Std. Error t value Pr(>|t|)    
(Intercept)      -1.48e+03   9.36e+01  -15.78  < 2e-16 ***
weight           -6.45e-03   2.07e-04  -31.15  < 2e-16 ***
year              7.69e-01   4.73e-02   16.27  < 2e-16 ***
is_6cylinderTRUE -2.54e+00   4.09e-01   -6.22  1.3e-09 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.27 on 388 degrees of freedom
Multiple R-squared:  0.826, Adjusted R-squared:  0.824 
F-statistic:  612 on 3 and 388 DF,  p-value: <2e-16

Model 3: Binary 6-cylinder & Horsepower

auto_fit11 <- lm(mpg ~ weight + year + is_6cylinder + horsepower, Auto_proper2)
summary(auto_fit11)

Call:
lm(formula = mpg ~ weight + year + is_6cylinder + horsepower, 
    data = Auto_proper2)

Residuals:
   Min     1Q Median     3Q    Max 
-8.942 -2.027 -0.059  1.757 13.851 

Coefficients:
                  Estimate Std. Error t value Pr(>|t|)    
(Intercept)      -1.39e+03   9.78e+01  -14.24  < 2e-16 ***
weight           -5.47e-03   4.13e-04  -13.27  < 2e-16 ***
year              7.27e-01   4.94e-02   14.71  < 2e-16 ***
is_6cylinderTRUE -2.92e+00   4.28e-01   -6.81  3.7e-11 ***
horsepower       -2.57e-02   9.43e-03   -2.72   0.0067 ** 
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.25 on 387 degrees of freedom
Multiple R-squared:  0.829, Adjusted R-squared:  0.827 
F-statistic:  469 on 4 and 387 DF,  p-value: <2e-16

Model 4: Cylinders - all levels & Horsepower

auto_fit12 <- lm(mpg ~ weight + year + cylinders + horsepower, Auto_proper2)
summary(auto_fit12)

Call:
lm(formula = mpg ~ weight + year + cylinders + horsepower, data = Auto_proper2)

Residuals:
   Min     1Q Median     3Q    Max 
-8.723 -1.996 -0.079  1.773 13.781 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1.39e+03   9.63e+01  -14.47  < 2e-16 ***
weight      -5.64e-03   4.99e-04  -11.30  < 2e-16 ***
year         7.23e-01   4.87e-02   14.86  < 2e-16 ***
cylinders4   6.65e+00   1.62e+00    4.10    5e-05 ***
cylinders5   7.90e+00   2.48e+00    3.19  0.00155 ** 
cylinders6   3.68e+00   1.68e+00    2.19  0.02889 *  
cylinders8   6.52e+00   1.80e+00    3.63  0.00032 ***
horsepower  -2.16e-02   9.94e-03   -2.17  0.03070 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.19 on 384 degrees of freedom
Multiple R-squared:  0.836, Adjusted R-squared:  0.833 
F-statistic:  280 on 7 and 384 DF,  p-value: <2e-16

Now that we have 4 models, we can compare the AIC (Mallow’s Cp in linear regression) and BIC. If there is not a significant difference, we will use the simplest model (model 2).

data_frame(
    Model = c("1: Cylinders - all levels", "2: Binary 6-cylinder",
              "3: Binary 6-cylinder & Horsepower","4: Cylinders - all levels & Horsepower")
    , AIC = AIC(auto_fit9, auto_fit10, auto_fit11, auto_fit12)$AIC
    , BIC = BIC(auto_fit9, auto_fit10, auto_fit11, auto_fit12)$BIC
) %>%
    kable()
Model AIC BIC
1: Cylinders - all levels 2034 2066
2: Binary 6-cylinder 2048 2068
3: Binary 6-cylinder & Horsepower 2042 2066
4: Cylinders - all levels & Horsepower 2031 2067

Model 2 has higher AIC and BIC values compared to the other 3 models, indicating it explains less variation in MPG. However, the difference is not large and it is the simplest model. We will use model 2 as our final model.

5.4.4.1 Quadratic Term

We notice that we might want a quadratic term for the predictor weight by looking at the following charts. We may be concerned about overfitting, particularly for 6-cylinder cars (i.e. 1970 and 1982). However, we know that we will be predicting the MPG of a 8-cylinder car.

Auto_proper2 %>%
    select(mpg, weight, year, cylinders, is_6cylinder) %>%
    ggplot(aes(x = weight, y = mpg, colour = is_6cylinder)) + 
    facet_wrap(~ year) +
    geom_point(alpha = 0.5) +
    theme_jrf(base_size) +
    geom_smooth(method = "lm", formula = y ~ x + I(x^2), se = FALSE) +
    labs(title = "Evidence for adding Quadratic Term for Weight", x = "Weight", y = "MPG") +
    scale_colour_manual("Is 6 Cylinder?", values = c('TRUE' = pal538['green'][[1]], "FALSE" = pal538['red'][[1]])) +
    guides(colour = guide_legend(reverse = TRUE)) +
    theme(legend.position = 'bottom')

We create a model to add in the quadratic term for weight. We see that the binary variable for whether the car is a 6-cylinder is now only marginally significant.

auto_fit13 <- lm(mpg ~ weight + I(weight^2) + year + is_6cylinder, Auto_proper2)
summary(auto_fit13)

Call:
lm(formula = mpg ~ weight + I(weight^2) + year + is_6cylinder, 
    data = Auto_proper2)

Residuals:
   Min     1Q Median     3Q    Max 
-9.501 -1.660 -0.126  1.556 13.164 

Coefficients:
                  Estimate Std. Error t value Pr(>|t|)    
(Intercept)      -1.57e+03   8.72e+01  -17.98  < 2e-16 ***
weight           -2.01e-02   1.67e-03  -12.01  < 2e-16 ***
I(weight^2)       2.12e-06   2.59e-07    8.21  3.5e-15 ***
year              8.26e-01   4.42e-02   18.67  < 2e-16 ***
is_6cylinderTRUE -7.52e-01   4.36e-01   -1.72    0.086 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.02 on 387 degrees of freedom
Multiple R-squared:  0.851, Adjusted R-squared:  0.85 
F-statistic:  554 on 4 and 387 DF,  p-value: <2e-16

Let’s remove the binary predictor.

auto_fit14 <- lm(mpg ~ weight + I(weight^2) + year, Auto_proper2)
summary(auto_fit14)

Call:
lm(formula = mpg ~ weight + I(weight^2) + year, data = Auto_proper2)

Residuals:
   Min     1Q Median     3Q    Max 
-9.456 -1.708 -0.173  1.519 13.179 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1.57e+03   8.74e+01   -18.0   <2e-16 ***
weight      -2.15e-02   1.44e-03   -14.9   <2e-16 ***
I(weight^2)  2.35e-06   2.25e-07    10.4   <2e-16 ***
year         8.29e-01   4.43e-02    18.7   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.03 on 388 degrees of freedom
Multiple R-squared:  0.85,  Adjusted R-squared:  0.849 
F-statistic:  734 on 3 and 388 DF,  p-value: <2e-16

When we plot this model the results look great.

Auto_proper2 %>%
    select(mpg, weight, year, cylinders, is_6cylinder) %>%
    ggplot(aes(x = weight, y = mpg)) + 
    facet_wrap(~ year) +
    geom_point(color = pal538['blue'][[1]], alpha = 0.5) +
    theme_jrf() +
    geom_smooth(method = "lm", formula = y ~ x + I(x^2), se = FALSE, colour = pal538['red'][[1]]) +
    labs(title = "Removing the Binary Is 6-cylinder Variable", x = "Weight", y = "MPG")

Let’s compare the AIC and BIC values for all of these models.

data_frame(
    Model = c("1: Cylinders - all levels", "2: Binary 6-cylinder",
              "3: Binary 6-cylinder & Horsepower","4: Cylinders - all levels & Horsepower",
              "5: Binary 6-cylinder and Quadratic Weight", "6: Quadratic Weight Only")
    , AIC = AIC(auto_fit9, auto_fit10, auto_fit11, auto_fit12, auto_fit13, auto_fit14)$AIC
    , BIC = BIC(auto_fit9, auto_fit10, auto_fit11, auto_fit12, auto_fit13, auto_fit14)$BIC
) %>%
    kable()
Model AIC BIC
1: Cylinders - all levels 2034 2066
2: Binary 6-cylinder 2048 2068
3: Binary 6-cylinder & Horsepower 2042 2066
4: Cylinders - all levels & Horsepower 2031 2067
5: Binary 6-cylinder and Quadratic Weight 1987 2011
6: Quadratic Weight Only 1988 2008

There is only a slight information gain with the binary 6-cylinder variable and we believe this to be overfitting. We will proceed with model 6. Let’s check what would happen if we used regsubsets with a the quadratic term.

auto_fit15 <- regsubsets(mpg ~ . + I(weight^2), data = Auto_proper2 %>% select(-name, -year2), nvmax = 11, method="exhaustive")
Reordering variables and trying again:
auto_fit15_sum <- summary(auto_fit15)
as_data_frame(auto_fit15_sum$outmat) %>% print(width = Inf)
data_frame(
      predictors = 1:length(auto_fit15_sum$cp)
    , cp = auto_fit15_sum$cp
    , bic = auto_fit15_sum$bic
    , adjr2 = auto_fit15_sum$adjr2
) %>%
    gather(metric, value, -predictors) %>%
    mutate(metric = factor(metric, levels = c("cp","bic","adjr2"))) %>%
    ggplot(aes(x = predictors, y = value, colour = metric)) +
    facet_grid(metric ~ ., scale = "free_y", switch = "y", 
               labeller = ggplot2::labeller(metric = c(cp = "Cp", bic = "BIC", adjr2 = "Adjusted R^2"))) +
    geom_vline(xintercept = 3, alpha = 0.5) + geom_line() + geom_point() +
    geom_label(data = data_frame(
        predictors = c(which.min(auto_fit15_sum$cp), which.min(auto_fit15_sum$bic), which.max(auto_fit15_sum$adjr2))
        , metric = factor(c("cp","bic","adjr2"), levels = c("cp","bic","adjr2"))
        , value = c(min(auto_fit15_sum$cp), min(auto_fit15_sum$bic), max(auto_fit15_sum$adjr2))
        , label = paste0("Optimal\nd=", c(which.min(auto_fit15_sum$cp), which.min(auto_fit15_sum$bic), which.max(auto_fit15_sum$adjr2)))
        , vjust = c(-.5, -.5, 1.25)
    ), aes(x = predictors, y = value, label = label, vjust = vjust), family = "DecimaMonoPro") +
    theme_jrf() + 
    labs(title = "Exhaustive Search with Quadratic Weight", x = "# of Predictors", y = NULL) +
    geom_label(data = data_frame(x = 3, y = 300, metric = factor(c("cp"), levels = c("cp","bic","adjr2")), 
                label = "Elbow with\n3 predictors"), aes(x=x,y=y,label=label), colour = "black", hjust = -.1,
               family = "DecimaMonoPro") + 
    scale_colour_manual(guide = FALSE, values = c(pal538['red'][[1]], pal538['green'][[1]], pal538['blue'][[1]]))

The result confirms our thinking: a 3 predictor model with a quadratic weight term.

5.4.4.2 Summary

Our final model to predict MPG based on the predictors in the Auto dataset is

\[MPG = \beta_0 + \beta_1 Weight + \beta_2 Weight^2 + \beta_3 year\]

(auto_fit_final <- summary(auto_fit14))

Call:
lm(formula = mpg ~ weight + I(weight^2) + year, data = Auto_proper2)

Residuals:
   Min     1Q Median     3Q    Max 
-9.456 -1.708 -0.173  1.519 13.179 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1.57e+03   8.74e+01   -18.0   <2e-16 ***
weight      -2.15e-02   1.44e-03   -14.9   <2e-16 ***
I(weight^2)  2.35e-06   2.25e-07    10.4   <2e-16 ***
year         8.29e-01   4.43e-02    18.7   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.03 on 388 degrees of freedom
Multiple R-squared:  0.85,  Adjusted R-squared:  0.849 
F-statistic:  734 on 3 and 388 DF,  p-value: <2e-16

Thus the model is

\[MPG = -1572.84 + -0.02 Weight + 2.3e-06 Weight^2 + 0.83 year\]

5.4.4.2.1 Checking Model Assumptions

The normal Q-Q plot shows that residuals might not come from a normal distribution at the tails, but all together is somewhat normal.

data_frame(std.resid = rstandard(auto_fit14)) %>% 
    ggplot() +
    stat_qq(aes(sample = std.resid), colour = pal538['blue']) +
    geom_abline(data =
        . %>%
        summarise(
            slope = diff(quantile(std.resid, c(0.25, 0.75))) / diff(qnorm(c(0.25, 0.75)))
            , int = quantile(std.resid, c(0.25, 0.75))[1L] - 
               (diff(quantile(std.resid, c(0.25, 0.75))) / 
                    diff(qnorm(c(0.25, 0.75)))) * qnorm(c(0.25, 0.75))[1L]
        ),
        aes(slope = slope, intercept = int), alpha = 0.5
    ) +
    theme_jrf() +
    scale_x_continuous(labels = NULL) + 
    labs(title = "Normal Q-Q", y = "Standardized Residuals", x = "Theoretical Quantiles")

In addition, the Shapiro-Wilks test shows that we have evidence that the residuals do not come from a normal distribution.

shapiro.test(rstandard(auto_fit14))

    Shapiro-Wilk normality test

data:  rstandard(auto_fit14)
W = 0.95, p-value = 4e-10

The fitted values vs residuals plots show approximately equal variance of the residuals (i.e. no heteroscedasticity).

data_frame(
    fitted = auto_fit14$fitted.values
    , resid = auto_fit14$residuals
    ) %>%
    ggplot(aes(x = fitted, y = resid)) +
    geom_point(colour = pal538['blue']) +
    geom_smooth(method = "loess", colour = pal538['red'], se = FALSE, size = .25, alpha = 0.5) + 
    geom_hline(yintercept = 0, alpha = 0.5, linetype = 'dashed', color = 'black') +
    theme_jrf() +
    scale_x_continuous(labels = NULL) + 
    labs(title = "Fitted Values vs Residuals", y = "Residuals", x = "Fitted Values")

5.4.4.2.2 Statistical Inference
  • The F-test for regression provides extremely strong evidence against the hypothesis that none of the variables are related to the response MPG (P-value \(\approx\) 0).
  • The Multiple R2 is 0.85 indicating that 90% of the variation in car MPG is explained by the variation in weight and model year, so the model will be good for prediction, however normality is not satisfied so predictions may be unreliable.
  • The intercept is not meaningful (MPG = 0).
  • We have extremely strong evidence against the hypotheses that the coefficients associated with weight are equal to 0 (P-values \(\approx\) 0).
  • We have extremely evidence against the hypothesis that the coefficient associated with model year is equal to 0 (P-value \(\approx\) 0).

The effects associated with weight are difficult to describe because of the quadratic term. Holding the effect of weight constant, each additional year of car (newer model years), a car’s MPG increases by 0.8289.

Finally, we can find a 95% prediction interval for the car built in 1983.

future_car_pred <- predict(auto_fit14, future_car, interval = "prediction")

The prediction interval for the MPG of this car is

\[(16.2702, 28.3166)\]

LS0tCnRpdGxlOiAiU1RBVFM3MDEgSG9tZXdvcmsgMSIKYXV0aG9yOiAiSm9yZGFuIEZhcnJlciIKZGF0ZTogJzIwMTYtMDktMjUnCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBjc3M6IHN0eWxlLmNzcwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiBmbGF0bHkKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNzczogc3R5bGUuY3NzCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdGhlbWU6IGZsYXRseQogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgcGRmX2RvY3VtZW50OgogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRvYzogeWVzCi0tLQoKIyBTZXR1cAoKCkZ1bGwgcmVwbzogW2h0dHBzOi8vZ2l0aHViLmNvbS9qcmZhcnJlci9zdGF0czcwMV9odzEvXShodHRwczovL2dpdGh1Yi5jb20vanJmYXJyZXIvc3RhdHM3MDFfaHcxLykKClB1Ymxpc2hlZCBmaWxlOiBbaHR0cHM6Ly9qcmZhcnJlci5naXRodWIuaW8vc3RhdHM3MDFfaHcxL10oaHR0cHM6Ly9qcmZhcnJlci5naXRodWIuaW8vc3RhdHM3MDFfaHcxLykKCkJlZ2luIGJ5IHNldHRpbmcgdXAgdGhlIFIgc2Vzc2lvbiwgY3JlYXRpbmcgYSBsb2dnZXIgZnVuY3Rpb24sIGFuZCBsb2FkaW5nIHBhY2thZ2VzLgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CiMgU2V0IG9wdGlvbnMgZm9yIHRoZSBybWFya2Rvd24gZmlsZQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgZmlnLmFsaWduID0gJ2NlbnRlcicsIHdpZHRoID0gMTAwLCBtZXNzYWdlID0gRkFMU0UpCmBgYAoKYGBge3Igc2V0dXAyfQojIFNldCB0aGUgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5CnNldC5zZWVkKDQ0KQojIFNldCB0aGUgbG9jYWxlIG9mIHRoZSBzZXNzaW9uIHNvIGxhbmd1YWdlcyBvdGhlciB0aGFuIEVuZ2xpc2ggY2FuIGJlIHVzZWQKaW52aXNpYmxlKFN5cy5zZXRsb2NhbGUoIkxDX0FMTCIsICJlbl9VUy5VVEYtOCIpKQojIFByZXZlbnQgcHJpbnRpbmcgaW4gc2NpZW50aWZpYyBub3RhdGlvbgpvcHRpb25zKGRpZ2l0cyA9IDQsIHdpZHRoID0gMjIwKQoKIyBDcmVhdGUgYSBsb2dnZXIgZnVuY3Rpb24KbG9nZ2VyIDwtIGZ1bmN0aW9uKG1zZywgbGV2ZWwgPSAiaW5mbyIsIGZpbGUgPSBsb2dfZmlsZSkgewogICAgY2F0KHBhc3RlMCgiWyIsIGZvcm1hdChTeXMudGltZSgpLCAiJVktJW0tJWQgJUg6JU06JVMuJU9TIiksICJdWyIsIGxldmVsLCAiXSAiLCBtc2csICJcbiIpLCBmaWxlID0gc3Rkb3V0KCkpCn0KCiMgU2V0IHRoZSBwcm9qZWN0IGRpcmVjdG9yeQpiYXNlX2RpciA8LSAnJwpkYXRhX2RpciA8LSBwYXN0ZTAoYmFzZV9kaXIsICJkYXRhLyIpCmNvZGVfZGlyIDwtIHBhc3RlMChiYXNlX2RpciwgImNvZGUvIikKdml6X2RpciA8LSBwYXN0ZTAoYmFzZV9kaXIsICJ2aXovIikKCmRpci5jcmVhdGUoZGF0YV9kaXIsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQpkaXIuY3JlYXRlKGNvZGVfZGlyLCBzaG93V2FybmluZ3MgPSBGQUxTRSkKZGlyLmNyZWF0ZSh2aXpfZGlyLCBzaG93V2FybmluZ3MgPSBGQUxTRSkKYGBgCgpgYGB7ciBMb2FkIFBhY2thZ2VzfQojIENyZWF0ZSBhIGZ1bmN0aW9uIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGxvYWQvaW5zdGFsbCBwYWNrYWdlcwpmbl9sb2FkX3BhY2thZ2VzIDwtIGZ1bmN0aW9uKHApIHsKICBpZiAoIWlzLmVsZW1lbnQocCwgaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdKSB8fCAocCA9PSJEVCIgJiYgIShwYWNrYWdlVmVyc2lvbihwKSA+ICIwLjEiKSkpIHsKICAgIGlmIChwID09ICJEVCIpIHsKICAgICAgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCdyc3R1ZGlvL0RUJykKICAgIH0gZWxzZSB7CiAgICAgIGluc3RhbGwucGFja2FnZXMocCwgZGVwID0gVFJVRSwgcmVwb3MgPSAnaHR0cDovL2NyYW4udXMuci1wcm9qZWN0Lm9yZycpCiAgICB9CiAgfQogIGEgPC0gc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHJlcXVpcmUocCwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkKICBpZiAoYSkgewogICAgbG9nZ2VyKHBhc3RlMCgiTG9hZGVkIHBhY2thZ2UgIiwgcCwgIiB2ZXJzaW9uICIsIHBhY2thZ2VWZXJzaW9uKHApKSkKICB9IGVsc2UgewogICAgbG9nZ2VyKHBhc3RlMCgiVW5hYmxlIHRvIGxvYWQgcGFja2FnZXMgIiwgcCkpCiAgfQp9CiMgQ3JlYXRlIGEgdmVjdG9yIG9mIHBhY2thZ2VzCnBhY2thZ2VzIDwtIGMoJ3RpZHl2ZXJzZScsJ2dndGhlbWVzJywna25pdHInLCdyZWFkeGwnLCdicm9vbScsJ2ZvcmVjYXN0Jywnc3RyaW5ncicsCiAgICAgICAgICAgICAgJ0lTTFInLCdHR2FsbHknLCdncmlkRXh0cmEnLCdsZWFwcycsJ2V4dHJhZm9udCcsJ3BhbmRlcicpCiMgVXNlIGZ1bmN0aW9uIHRvIGxvYWQgdGhlIHJlcXVpcmVkIHBhY2thZ2VzCmludmlzaWJsZShsYXBwbHkocGFja2FnZXMsIGZuX2xvYWRfcGFja2FnZXMpKQpgYGAKCmBgYHtyfQojIFRvIHRoZSBmb250IHNlY29uZCBmb250LCBydW4gdGhlIGZvbGxvd2luZyB0d28gbGluZXMgb2YgY29kZSBhbmQgYWRkIG5hbWUgb2YgdXNlciB0byB2ZWN0b3IKIyBzeXN0ZW0ocGFzdGUwKCJjcCAtciAiLHZpel9kaXIsImZvbnRzLy4gfi9MaWJyYXJ5L0ZvbnRzLyIpKSAjIGluc3RhbnRhbmVvdXMKIyBmb250X2ltcG9ydCgpICMgdGFrZXMgYXBwcm94aW1hdGVseSA1LTEwIG1pbgp1c2Vyc192IDwtIGMoIkpvcmRhbiIpCmBgYAoKYGBge3IgQ3JlYXRlIHBhbGV0dGUgYW5kIHRoZW1lfQojIENyZWF0ZSBhIGNvbG9yIHBhbGV0dGUKcGFsNTM4IDwtIGdndGhlbWVzX2RhdGEkZml2ZXRoaXJ0eWVpZ2h0CgojIENyZWF0ZSBhIHRoZW1lIHRvIHVzZSB0aHJvdWdob3V0IHRoZSBhbmFseXNpcwp0aGVtZV9qcmYgPC0gZnVuY3Rpb24oYmFzZV9zaXplID0gOCwgYmFzZV9mYW1pbHkgPSBpZmVsc2UoU3lzLmluZm8oKVtbJ3VzZXInXV0gJWluJSB1c2Vyc192LCAiRGVjaW1hTW9ub1BybyIsICJIZWx2ZXRpY2EiKSkgewogICAgdGhlbWUoCiAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAiI0YwRjBGMCIsIGNvbG91ciA9ICIjNjA2MDYzIiksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICIjRjBGMEYwIiwgY29sb3VyID0gTkEpLCAKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9ICAgZWxlbWVudF9saW5lKGNvbG91ciA9ICIjRDdEN0Q4IiksCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9ICAgZWxlbWVudF9saW5lKGNvbG91ciA9ICIjRDdEN0Q4Iiwgc2l6ZSA9IDAuMjUpLAogICAgICAgIHBhbmVsLm1hcmdpbiA9ICAgICAgIHVuaXQoMC4yNSwgImxpbmVzIiksCiAgICAgICAgcGFuZWwubWFyZ2luLnggPSAgICAgTlVMTCwKICAgICAgICBwYW5lbC5tYXJnaW4ueSA9ICAgICBOVUxMLAogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoY29sb3VyID0gIiNBMEEwQTMiKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCh2anVzdCA9IDEsIGZhbWlseSA9ICdIZWx2ZXRpY2EnLCBjb2xvdXIgPSAnIzNDM0MzQycpLAogICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMSwgZmFtaWx5ID0gJ0hlbHZldGljYScsIGNvbG91ciA9ICcjM0MzQzNDJyksCiAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgbGVnZW5kLmtleSA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gJ2JvbGQnLCBjb2xvdXIgPSAnIzNDM0MzQycsIGhqdXN0ID0gMCksCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgZmFtaWx5ID0gaWZlbHNlKFN5cy5pbmZvKClbWyd1c2VyJ11dICVpbiUgdXNlcnNfdiwiRGVjaW1hTW9ub1BybyIsICJIZWx2ZXRpY2EiKSksCiAgICAgICAgdGl0bGUgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gaWZlbHNlKFN5cy5pbmZvKClbWyd1c2VyJ11dICVpbiUgdXNlcnNfdiwiRGVjaW1hTW9ub1BybyIsICJIZWx2ZXRpY2EiKSkKICAgICAgICAKICAgICkKfQpgYGAKCiMgUXVlc3Rpb24gMgoKIyMgRGF0YSBMb2FkaW5nCgpMZXQncyB1c2UgSGFkbGV5J3MgcmVhZHIgcGFja2FnZSB0byBsb2FkIHRoZSBkYXRhc2V0LCB1c2luZyB0aGUgY29sX25hbWUgcGFyYW1ldGVyIHRvIHNldCB0aGUgY29sdW1uIG5hbWVzIG9mIHRoZSB0aWJibGUuCgpgYGB7ciBMb2FkIGFuZCBDbGVhbiBkYXRhLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRX0KIyBMb2FkIHRoZSBjc3Ygd2l0aCBtZWFuaW5nZnVsIGNvbHVtbiBuYW1lcwpzdXJ2ZXlfcmVzdWx0cyA8LSByZWFkX2NzdihwYXN0ZTAoZGF0YV9kaXIsJ1N1cnZleV9yZXN1bHRzX2ZpbmFsLmNzdicpLCBza2lwID0gMSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sX25hbWVzID0gYygnaGl0aWQnLCdoaXR0eXBlaWQnLCd0aXRsZScsJ2Rlc2NyaXB0aW9uJywna2V5d29yZHMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3Jld2FyZCcsJ2NyZWF0aW9udGltZScsJ21heGFzc2lnbm1lbnRzJywncmVxdWVzdGVyYW5ub3RhdGlvbicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYXNzaWdubWVudGR1cmF0aW9uaW5zZWNvbmRzJywnYXV0b2FwcHJvdmFsZGVsYXlpbnNlY29uZHMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2V4cGlyYXRpb24nLCdudW1iZXJvZnNpbWlsYXJoaXRzJywnbGlmZXRpbWVpbnNlY29uZHMnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2Fzc2lnbm1lbnRpZCcsJ3dvcmtlcmlkJywnYXNzaWdubWVudHN0YXR1cycsJ2FjY2VwdHRpbWUnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgJ3N1Ym1pdHRpbWUnLCdhdXRvYXBwcm92YWx0aW1lJywnYXBwcm92YWx0aW1lJywncmVqZWN0aW9udGltZScsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAncmVxdWVzdGVyZmVlZGJhY2snLCd3b3JrdGltZScsJ2xpZmV0aW1lYXBwcm92YWxyYXRlJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdsYXN0MzBkYXlzYXBwcm92YWxyYXRlJywnbGFzdDdkYXlzYXBwcm92YWxyYXRlJywnYWdlJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdlZHVjYXRpb24nLCdnZW5kZXInLCdpbmNvbWUnLCdzaXJpdXMnLCd3aGFydG9uJywnYXBwcm92ZScsJ3JlamVjdCcpKQoKIyBQcmludCBhIGZldyByZWNvcmRzIGluIHRoZSB0aWJibGUKc3VydmV5X3Jlc3VsdHMgJT4lIAogICAgc2VsZWN0KGFnZSwgZWR1Y2F0aW9uLCBnZW5kZXIsIGluY29tZSwgc2lyaXVzLCB3aGFydG9uLCB3b3JrdGltZSkgCgojIFB1dCBpbnRvIGEgbmV3IHRpYmJsZSB3ZSdsbCB1c2UgZm9yIGNsZWFuaW5nICh0aGVyZSB3aWxsIGJlIGEgZmluYWwgbGF0ZXIpCnN1cnZleV9yZXN1bHRzX2NsZWFuaW5nIDwtIHN1cnZleV9yZXN1bHRzCmBgYAoKIyMgRGF0YSBDbGVhbmluZwoKV2UnbGwgc2VxdWVudGlhbGx5IGNsZWFuIGVhY2ggb2YgdGhlIHByaW1hcnkgdmFyaWFibGVzIG9mIHRoZSBkYXRhc2V0IGFuZCBjcmVhdGUgZXhwbG9yYXRvcnkgc3VtbWFyaWVzLgoKIyMjIEFnZQoKTGV0J3MgcXVpY2tseSBzdW1tYXJpemUgdGhlIGFnZSB2YXJpYWJsZSwgbm90aW5nIHRoYXQgaXQgaXMgYSBjaGFyYWN0ZXIuCgpgYGB7ciBEYXRhIGNsZWFuaW5nIC0gQWdlMX0Kc3VydmV5X3Jlc3VsdHNfY2xlYW5pbmcgJT4lIGdyb3VwX2J5KGFnZSkgJT4lIHN1bW1hcmlzZShjbnQgPSBuKCkpICU+JSBhcnJhbmdlKGFnZSkKYGBgCgpXZSBjb3JyZWN0IHNvbWUgZXJyYW50IHZhbHVlcywgdXNpbmcgb3VyIGp1ZGdlbWVudCBhcyAqKmRhdGEgYW5hbHlzdHMqKiBhbmQgcGxvdCBhIGhpc3RvZ3JhbS4KCmBgYHtyIERhdGEgY2xlYW5pbmcgLSBBZ2UyfQpzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZyA8LQogICAgc3VydmV5X3Jlc3VsdHMgJT4lCiAgICBtdXRhdGUoCiAgICAgICAgYWdlMiA9IGlmZWxzZShhZ2UgPT0gJ0VpZ2h0ZWVuICgxOCknLCAiMTgiLCBpZmVsc2UoYWdlID09ICdmZW1hbGUnLCBOQSwgaWZlbHNlKGFnZSA9PSAiMjdgIiwgIjI3IiwgYWdlKSkpCiAgICAgICAgLCBhZ2UyID0gYXMuaW50ZWdlcihhZ2UyKQogICAgKQoKZ2dwbG90KHN1cnZleV9yZXN1bHRzX2NsZWFuaW5nLCBhZXMoeCA9IGFnZTIpKSArIAogICAgZ2VvbV9wb2ludChhZXMoeCA9IDQsIHkgPSAxKSwgc2hhcGUgPSAxLCBjb2xvdXIgPSBwYWw1MzhbJ3JlZCddLCBmaWxsID0gTkEsIHNpemUgPSA2LCBzdHJva2UgPSAxKSArIAogICAgZ2VvbV9wb2ludChhZXMoeCA9IDIyMywgeSA9IDEpLCBzaGFwZSA9IDEsIGNvbG91ciA9IHBhbDUzOFsncmVkJ10sIGZpbGwgPSBOQSwgc2l6ZSA9IDYsIHN0cm9rZSA9IDEpICsgCiAgICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEsIGZpbGwgPSBwYWw1MzhbJ2JsdWUnXSkgKwogICAgdGhlbWVfanJmKCkgKwogICAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMC4wNSwgMC4wMSkpICsgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMC4wMiwgMC4wMSkpICsgCiAgICBsYWJzKHRpdGxlID0gIkFnZSIsIHkgPSAiQ291bnQiLCB4ID0gIkFnZSAoeWVhcnMpIikKYGBgCgpJdCBsb29rcyBsaWtlIHdlIHN0aWxsIG1pc3NlZCBzb21lIGJhZCB2YWx1ZXMuCgpgYGB7ciBEYXRhIGNsZWFuaW5nIC0gQWdlM30Kc29ydCh1bmlxdWUoc3VydmV5X3Jlc3VsdHNfY2xlYW5pbmckYWdlMikpCmBgYAoKV2UgZml4IHRob3NlIHRvbyBhbmQgcGxvdCB0aGUgaGlzdG9ncmFtLgoKYGBge3IgRGF0YSBjbGVhbmluZyAtIEFnZTR9CnN1cnZleV9yZXN1bHRzX2NsZWFuaW5nIDwtCiAgICBzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZyAlPiUKICAgIG11dGF0ZSgKICAgICAgICBhZ2UzID0gaWZlbHNlKGFnZTIgJWluJSBjKDQsIDIyMyksIE5BLCBhZ2UyKQogICAgKQoKZ2dwbG90KHN1cnZleV9yZXN1bHRzX2NsZWFuaW5nLCBhZXMoeCA9IGFnZTMpKSArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSwgZmlsbCA9IHBhbDUzOFsnYmx1ZSddKSArCiAgICB0aGVtZV9qcmYoKSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLjA1LCAwLjAxKSkgKyBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLjAyLCAwLjAxKSkgKyAKICAgIGxhYnModGl0bGUgPSAiQWdlIiwgeSA9ICJDb3VudCIsIHggPSAiQWdlICh5ZWFycykiKQpgYGAKCiMjIyBFZHVjYXRpb24KCkxldCdzIGxvb2sgYXQgdGhlIHVuaXF1ZSB2YWx1ZXMgYW5kIGNvdW50cy4KCmBgYHtyIERhdGEgY2xlYW5pbmcgLSBFZHVjYXRpb24xfQpzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZyAlPiUgZ3JvdXBfYnkoZWR1Y2F0aW9uKSAlPiUgc3VtbWFyaXNlKGNudCA9IG4oKSkgJT4lIGFycmFuZ2UoZWR1Y2F0aW9uKQpgYGAKCkl0IGFwcGVhcnMgdGhhdCBgciBucm93KGZpbHRlcihzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZywgZWR1Y2F0aW9uID09ICJzZWxlY3Qgb25lIikpYCByZXNwb25kZW50cyBsZWZ0IHRoZSBzdXJ2ZXkgb24gdGhlIGRlZmF1bHQgd2hpY2ggcmVhZCAnc2VsZWN0IG9uZScuIFdlJ2xsIHVwZGF0ZSB0aGF0IHRvICdPdGhlcicgYW5kIG1vZGlmeSB0aGlzIHZhcmlhYmxlIHRvIGJlIGEgZmFjdG9yLgoKYGBge3IgRGF0YSBjbGVhbmluZyAtIEVkdWNhdGlvbjJ9CnN1cnZleV9yZXN1bHRzX2NsZWFuaW5nIDwtCiAgICBzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZyAlPiUgCiAgICBtdXRhdGUoCiAgICAgICAgZWR1Y2F0aW9uMiA9IGlmZWxzZShlZHVjYXRpb24gPT0gInNlbGVjdCBvbmUiLCAiT3RoZXIiLCBlZHVjYXRpb24pCiAgICAgICAgLCBlZHVjYXRpb24yID0gZmFjdG9yKGVkdWNhdGlvbjIsIGxldmVscyA9IGMoJ0xlc3MgdGhhbiAxMiB5ZWFyczsgbm8gaGlnaCBzY2hvb2wgZGlwbG9tYScKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsICdIaWdoIHNjaG9vbCBncmFkdWF0ZSAob3IgZXF1aXZhbGVudCknCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCAnU29tZSBjb2xsZWdlLCBubyBkaXBsb21hOyBvciBBc3NvY2lhdGXigJlzIGRlZ3JlZScKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsICdCYWNoZWxvcuKAmXMgZGVncmVlIG9yIG90aGVyIDQteWVhciBkZWdyZWUnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCAnR3JhZHVhdGUgb3IgcHJvZmVzc2lvbmFsIGRlZ3JlZScKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsICdPdGhlcicpKQogICAgKQoKc3VydmV5X3Jlc3VsdHNfY2xlYW5pbmcgJT4lIGdyb3VwX2J5KGVkdWNhdGlvbjIpICU+JSBzdW1tYXJpc2UoY250ID0gbigpKSAlPiUgYXJyYW5nZShlZHVjYXRpb24yKQpgYGAKCiMjIyBHZW5kZXIKCldlJ2xsIHN1bW1hcml6ZSB0aGUgZ2VuZGVyIHZhcmlhYmxlLiAKCmBgYHtyIERhdGEgY2xlYW5pbmcgLSBHZW5kZXIxfQpzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZyAlPiUgZ3JvdXBfYnkoZ2VuZGVyKSAlPiUgc3VtbWFyaXNlKGNudCA9IG4oKSkgJT4lIGFycmFuZ2UoZ2VuZGVyKQpgYGAKCldlIHVwZGF0ZSB0aGlzIHRvIGJlIGEgZmFjdG9yLgoKYGBge3IgRGF0YSBjbGVhbmluZyAtIEdlbmRlcjJ9CnN1cnZleV9yZXN1bHRzX2NsZWFuaW5nIDwtCiAgICBzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZyAlPiUKICAgIG11dGF0ZShnZW5kZXIyID0gYXMuZmFjdG9yKGdlbmRlcikpCgpzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZyAlPiUgCiAgICBncm91cF9ieShnZW5kZXIyKSAlPiUgCiAgICBzdW1tYXJpc2UoY250ID0gbigpKSAlPiUgCiAgICBhcnJhbmdlKGdlbmRlcjIpICU+JQogICAgbXV0YXRlKHByb3AgPSBjbnQgLyBzdW0oY250KSkKYGBgCgojIyMgSW5jb21lCgpgYGB7ciBEYXRhIGNsZWFuIC0gSW5jb21lMX0Kc3VydmV5X3Jlc3VsdHNfY2xlYW5pbmcgJT4lIGdyb3VwX2J5KGluY29tZSkgJT4lIHN1bW1hcmlzZShjbnQgPSBuKCkpICU+JSBhcnJhbmdlKGluY29tZSkKYGBgCgpMZXQncyBjb252ZXJ0IHRoaXMgdG8gYSBmYWN0b3IgdmFyaWFibGUuCgpgYGB7ciBEYXRhIGNsZWFuIC0gSW5jb21lMn0Kc3VydmV5X3Jlc3VsdHNfY2xlYW5pbmcgPC0KICAgIHN1cnZleV9yZXN1bHRzX2NsZWFuaW5nICU+JSAKICAgIG11dGF0ZSgKICAgICAgICBpbmNvbWUyID0gZmFjdG9yKGluY29tZSwgbGV2ZWxzID0gYygnTGVzcyB0aGFuICQxNSwwMDAnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCAnJDE1LDAwMCAtICQzMCwwMDAnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCAnJDMwLDAwMCAtICQ1MCwwMDAnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCAnJDUwLDAwMCAtICQ3NSwwMDAnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCAnJDc1LDAwMCAtICQxNTAsMDAwJwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICwgJ0Fib3ZlICQxNTAsMDAwJykpCiAgICApCgpzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZyAlPiUgZ3JvdXBfYnkoaW5jb21lMikgJT4lIHN1bW1hcmlzZShjbnQgPSBuKCkpICU+JSBhcnJhbmdlKGluY29tZTIpCmBgYAoKIyMjIFNpcml1cyBhbmQgV2hhcnRvbgoKYGBge3IgRGF0YSBjbGVhbiAtIHNpcml1c193aGFydG9uMX0Kc3VydmV5X3Jlc3VsdHNfY2xlYW5pbmcgJT4lIGdyb3VwX2J5KHNpcml1cykgJT4lIHN1bW1hcmlzZShjbnQgPSBuKCkpICU+JSBhcnJhbmdlKHNpcml1cykKc3VydmV5X3Jlc3VsdHNfY2xlYW5pbmcgJT4lIGdyb3VwX2J5KHdoYXJ0b24pICU+JSBzdW1tYXJpc2UoY250ID0gbigpKSAlPiUgYXJyYW5nZSh3aGFydG9uKQpgYGAKCkxldCdzIGNvbnZlcnQgdGhlc2UgdG8gZmFjdG9ycyBmb3IgYmV0dGVyIGFuYWx5c2lzIGNhcGFiaWxpdGllcy4KCmBgYHtyIERhdGEgY2xlYW4gLSBzaXJpdXNfd2hhcnRvbjJ9CnN1cnZleV9yZXN1bHRzX2NsZWFuaW5nIDwtCiAgICBzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZyAlPiUKICAgIG11dGF0ZSgKICAgICAgICBzaXJpdXMyID0gZmFjdG9yKHNpcml1cywgbGV2ZWxzID0gYygiWWVzIiwiTm8iKSkKICAgICAgICAsIHdoYXJ0b24yID0gZmFjdG9yKHdoYXJ0b24sIGxldmVscyA9IGMoIlllcyIsIk5vIikpCiAgICApCgpzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZyAlPiUgZ3JvdXBfYnkoc2lyaXVzMikgJT4lIHN1bW1hcmlzZShjbnQgPSBuKCkpICU+JSBhcnJhbmdlKHNpcml1czIpCnN1cnZleV9yZXN1bHRzX2NsZWFuaW5nICU+JSBncm91cF9ieSh3aGFydG9uMikgJT4lIHN1bW1hcmlzZShjbnQgPSBuKCkpICU+JSBhcnJhbmdlKHdoYXJ0b24yKQpgYGAKCiMjIyBXb3JrdGltZQoKYGBge3IgRGF0YSBjbGVhbiAtIFdvcmt0aW1lMSwgZmlnLmFsaWduID0gJ2NlbnRlcid9CmdncGxvdChzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZywgYWVzKHggPSB3b3JrdGltZSkpICsgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxLCBmaWxsID0gcGFsNTM4WydibHVlJ10pICsgCiAgICB0aGVtZV9qcmYoKSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLjA1LCAwLjAxKSkgKyBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLjAyLCAwLjAxKSkgKyAKICAgIGxhYnModGl0bGUgPSAiV29ya3RpbWUiLCB5ID0gIkNvdW50IiwgeCA9ICJXb3JrdGltZSBpbiBTZWNvbmRzIikKYGBgCgojIyMgRmluYWwgRGF0YSBGcmFtZQoKV2Ugc2VsZWN0IGFuZCByZW5hbWUgdGhlIGNvbHVtbnMuCgpgYGB7ciBEYXRhIENsZWFuIC0gRmluYWx9CnN1cnZleV9yZXN1bHRzX2NsZWFuaW5nIDwtIAogICAgc3VydmV5X3Jlc3VsdHNfY2xlYW5pbmcgJT4lCiAgICBzZWxlY3QoYWdlMywgZWR1Y2F0aW9uMiwgZ2VuZGVyMiwgaW5jb21lMiwgc2lyaXVzMiwgd2hhcnRvbjIsIHdvcmt0aW1lKSAlPiUKICAgIHJlbmFtZSgKICAgICAgICBhZ2UgPSBhZ2UzCiAgICAgICAgLCBlZHVjYXRpb24gPSBlZHVjYXRpb24yCiAgICAgICAgLCBnZW5kZXIgPSBnZW5kZXIyCiAgICAgICAgLCBpbmNvbWUgPSBpbmNvbWUyCiAgICAgICAgLCBzaXJpdXMgPSBzaXJpdXMyCiAgICAgICAgLCB3aGFydG9uID0gd2hhcnRvbjIKICAgICkKYGBgCgpMZXQncyByZXZpZXcgdGhlIHJlY29yZHMgd2l0aCBtaXNzaW5nIGRhdGEuIAoKYGBge3J9CnN1cnZleV9yZXN1bHRzX2NsZWFuaW5nWyFjb21wbGV0ZS5jYXNlcyhzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZyksIF0gJT4lIHByaW50KHdpZHRoID0gSW5mKQpgYGAKCldlIHdpbGwgcmVtb3ZlIHRoZSBgciBucm93KGZpbHRlcihzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZywgaXMubmEoc2lyaXVzKSB8IGlzLm5hKHdoYXJ0b24pKSlgIHJlY29yZHMgdGhhdCBoYXZlIE5BcyBmb3Igc2lyaXVzIG9yIHdoYXJ0b24uIFdpdGhvdXQgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHJlc3BvbnNlLCB3ZSB3aWxsIGhhdmUgdHJvdWJsZSBtYWtpbmcgYW4gZXN0aW1hdGUgb2YgKnAqLCB0aGUgcHJvcG9ydGlvbiBvZiBTaXJpdXMgbGlzdGVuZXJzIHdobyBsaXN0ZW5lZCB0byBCdXNpbmVzcyBSYWRpbyBQb3dlcmVkIGJ5IHRoZSBXaGFydG9uIFNjaG9vbC4KCmBgYHtyfQpzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZyAlPiUKICAgIGZpbHRlcihpcy5uYShzaXJpdXMpIHwgaXMubmEod2hhcnRvbikpCmBgYCAKCldlIHB1dCB0b2dldGhlciBhIGZpbmFsIGRhdGEgZnJhbWUuCgpgYGB7cn0Kc3VydmV5X3Jlc3VsdHNfZmluYWwgPC0gCiAgICBzdXJ2ZXlfcmVzdWx0c19jbGVhbmluZyAlPiUKICAgIGZpbHRlcighaXMubmEoc2lyaXVzKSAmICFpcy5uYSh3aGFydG9uKSkKYGBgCgojIyBTdW1tYXJ5CgpXZSBwcmV2aW91c2x5IGxpc3RlbmVkIHRvIHRoZSBwb2RjYXN0IFBsYW5ldCBNb25leSdzICBbZXBpc29kZV0oaHR0cDovL3d3dy5ucHIub3JnL3NlY3Rpb25zL21vbmV5LzIwMTUvMDEvMzAvMzgyNjU3NjU3L2VwaXNvZGUtNjAwLXRoZS1wZW9wbGUtaW5zaWRlLXlvdXItbWFjaGluZSkgYWJvdXQgQW1hem9uJ3MgTWVjaGFuaWNhbCBUdXJrIHByb2dyYW0uCgpgYGB7ciBTdW1tYXJ5IC0gU3VtbWFyeSBTdGF0YSwgcmVzdWx0cyA9ICdhc2lzJ30KcGFuZGVyKHN1bW1hcnkoc3VydmV5X3Jlc3VsdHNfZmluYWwpLCBtaXNzaW5nID0gIiIsIHNwbGl0LnRhYmxlID0gSW5mKQpgYGAKCnxWYXJpYWJsZXxDbGFzc3xEZXNjcmlwdGlvbnwKfC0tLXwtLS0tLXwtLS0tLXwKfEFnZXxJbnRlZ2VyfFRoZSBhZ2UgaW4geWVhcnMgb2YgdGhlIHN1cnZleSByZXNwb25kZW50fAp8RWR1Y2F0aW9ufEZhY3RvcnxMZXZlbCBvZiBlZHVjYXRpb24gYXR0YWluIGJ5IHRoZSBzdXJ2ZXkgcmVzcG9uZGVudHwKfEdlbmRlcnxGYWN0b3J8R2VuZGVyIGluZGljYXRlZCBieSB0aGUgc3VydmV5IHJlc3BvbmRlbnQgKE1hbGUgb3IgRmVtYWxlKXwKfEluY29tZXxGYWN0b3J8SW5jb21lIGxldmVsIHByb3ZpZGVkIGJ5IHRoZSBzdXJ2ZXkgcmVzcG9uZGVudHwKfFNpcml1c3xGYWN0b3J8UmVzcG9uc2UgdG8gIkhhdmUgeW91IGV2ZXIgbGlzdGVuZWQgdG8gU2lyaXVzIFJhZGlvPyJ8CnxXaGFydG9ufEZhY3RvcnxSZXNwb25zZSB0byAiSGF2ZSB5b3UgZXZlciBsaXN0ZW5lZCB0byBTaXJpdXMgQnVzaW5lc3MgUmFkaW8gYnkgV2hhcnRvbj8ifAp8V29ya3RpbWV8SW50ZWdlcnxOdW1iZXIgb2Ygc2Vjb25kIHNwZW50IGNvbXBsZXRpbmcgdGhlIHN1cnZleXwKCiMjIFNhbXBsZSBwcm9wZXJ0aWVzCgojIyMgKDEpCgpPbiB0aGUgc3VyZmFjZSwgd2UgaGF2ZSBubyByZWFzb24gdG8gYmVsaWV2ZSB0aGF0IHRoZSBNVFVSSyBkYXRhc2V0IGNvdWxkIGJlIHJlcHJlc2VudGF0aXZlIG9mIHRoZSBVUyBwb3B1bGF0aW9uLiBLbm93bGVkZ2Ugb2YgTVRVUksgaXMgbm90IHVuaXZlcnNhbCBhbmQgYXR0cmFjdHMgcGFydGljdWxhciB0eXBlcyBvZiBpbmRpdmlkdWFscyB3aWxsaW5nIHRvIHBlcmZvcm0gbWFueSBzbWFsbCB0YXNrcyBmb3IgYSBtaW5vciByZXdhcmQgKGZyb20gUGxhbmV0IE1vbmV5IHBvZGNhc3QpLiAKCkZpcnN0LCB3ZSBxdWlja2x5IHNlZSB0aGF0IHRoZSBwcm9wb3J0aW9uIG9mIFNpcml1cyBsaXN0ZW5lcnMgaXMgbXVjaCBoaWdoZXIgdGhhbiB0aGUgZ2l2ZW4gcHJvcG9ydGlvbi4gSWYgdGhlIFVTIHBvcHVsYXRpb24gaXMgWzMyMS40IG1pbGxpb25dKGh0dHBzOi8vd3d3LmNlbnN1cy5nb3YvcXVpY2tmYWN0cy8pLCB0aGVuIHRoZSBwcm9wb3J0aW9uIG9mIFNpcml1cyBsaXN0ZW5lcnMgaXMgCgokJFxmcmFjezUxLjZ9ezMyMS40fSA9IGByIHJvdW5kKDUxLjYvMzIxLjQsIDQpYCQkCgpIb3dldmVyLCB3ZSBxdWlja2x5IHNlZSB0aGF0IGluIHRoZSBzdXJ2ZXkgZGF0YSBmcm9tIE1UVVJLLCB0aGUgcHJvcG9ydGlvbiBvZiBTaXJpdXMgbGlzdGVuZXJzIGlzIG11Y2ggaGlnaGVyLgoKYGBge3J9CihzaXJpdXNfcHJvcCA8LSBzdXJ2ZXlfcmVzdWx0c19maW5hbCAlPiUgc3VtbWFyaXNlKHByb3Bfc2lyaXVzID0gc3VtKHNpcml1cyA9PSAiWWVzIikgLyBuKCkpKQpgYGAKCldlIHNlZSB0aGF0IG9mIHRoZSBzdXJ2ZXkgcmVzcG9uZGVudHMsIGByIHJvdW5kKDEwMCogc2lyaXVzX3Byb3AsMilgJSBzYXkgdGhhdCBoYXZlIGxpc3RlbmVkIHRvIFNpcml1cy4KClNlY29uZCwgaW4gb3JkZXIgdG8gYW5zd2VyIHRoZSBxdWVzdGlvbiAiRG9lcyB0aGlzIGFwcGVhciB0byBiZSBhIHJhbmRvbSBzYW1wbGUgZnJvbSB0aGUgVVMgcG9wdWxhdGlvbj8iIGVtcGlyaWNhbGx5IHdlIGNhbiBsb29rIGF0IHRoZSBmb3VyIGNoYXJhY3RlcmlzdGljcyBpbiBvdXIgZmluYWwgZGF0YXNldAoKMS4gQWdlCjIuIEdlbmRlcgozLiBFZHVjYXRpb24KNC4gSW5jb21lCgpGb3IgYWdlIGFuZCBnZW5kZXIsIHdlIGRvd25sb2FkIGEgdGFibGUgY2FsbGVkICJQb3B1bGF0aW9uIGJ5IEFnZSIgZnJvbSBVUyBDZW5zdXMgQnVyZWF1J3MgW0N1cnJlbnQgUG9wdWxhdGlvbiBTdXJ2ZXldKGh0dHA6Ly93d3cuY2Vuc3VzLmdvdi9wb3B1bGF0aW9uL2FnZS9kYXRhL2ZpbGVzLzIwMTIvMjAxMmdlbmRlcl90YWJsZTEuY3N2KSBpbiAyMDEyLgoKYGBge3J9CmNlbnN1c19hZ2VfZ2VuZGVyIDwtIHJlYWRfY3N2KHVybCgiaHR0cDovL3d3dy5jZW5zdXMuZ292L3BvcHVsYXRpb24vYWdlL2RhdGEvZmlsZXMvMjAxMi8yMDEyZ2VuZGVyX3RhYmxlMS5jc3YiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNraXAgPSA2LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sX25hbWVzID0gYygiYWdlIiwgImFsbCIsImFsbF9wZXJjZW50IiwibWFsZSIsIm1hbGVfcGVyY2VudCIsImZlbWFsZSIsImZlbWFsZV9wZXJjZW50IiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xfdHlwZXMgPSBjb2xzKGFnZSA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsbCA9IGNvbF9udW1iZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsbF9wZXJjZW50ID0gY29sX251bWJlcigpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFsZSA9IGNvbF9udW1iZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hbGVfcGVyY2VudCA9IGNvbF9udW1iZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlbWFsZSA9IGNvbF9udW1iZXIoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZlbWFsZV9wZXJjZW50ID0gY29sX251bWJlcigpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCgpjZW5zdXNfYWdlX2dlbmRlcgpgYGAKCldlIHdpbGwgbmVlZCB0byBidWNrZXQgb3VyIE1UVVJLIGRhdGFzZXQgdG8gbWF0Y2ggdGhlIGNhdGVnb3JpZXMgb2YgdGhlIENlbnN1cyBCdXJlYXUncy4gSW4gZG9pbmcgc28gd2UgcmVtb3ZlIHRoZSBgciBucm93KGZpbHRlcihzdXJ2ZXlfcmVzdWx0c19maW5hbCwgYWdlIDwgMjAgfCBpcy5uYShnZW5kZXIpKSlgIHJlY29yZHMgd2l0aCBhbiBhZ2UgMTgtMTkgYW5kIHdpdGhvdXQgYSBsaXN0ZWQgZ2VuZGVyLgoKYGBge3J9ICAgIAphY3R1YWwgPC0gCiAgICBzdXJ2ZXlfcmVzdWx0c19maW5hbCAlPiUgCiAgICAgICAgZmlsdGVyKGFnZSA+PSAyMCAmICFpcy5uYShnZW5kZXIpKSAlPiUKICAgICAgICBtdXRhdGUoYWdlX2J1Y2tldCA9IHBhc3RlMChmbG9vcihhZ2UgLyAxMCksICIwIHRvICIsIGZsb29yKGFnZSAvIDEwKSwiOSB5ZWFycyIpKSAlPiUKICAgICAgICBncm91cF9ieShhZ2VfYnVja2V0LCBnZW5kZXIpICU+JQogICAgICAgIHN1bW1hcmlzZSgKICAgICAgICAgICAgbiA9IG4oKQogICAgICAgICkgJT4lCiAgICAgICAgdW5ncm91cCgpICU+JQogICAgICAgIG11dGF0ZShzb3VyY2UgPSAiQWN0dWFsIikgJT4lCiAgICAgICAgc2VsZWN0KHNvdXJjZSwgYWdlX2J1Y2tldCwgZ2VuZGVyLCBuKQoKYWN0dWFsX3NpemUgPC0gc3VtKGFjdHVhbCRuKQoKYWN0dWFsCmBgYAoKTmV4dCB3ZSBjbGVhbiB0aGUgQ2Vuc3VzIEJ1cmVhdSdzIGRhdGFzZXQgYW5kIHNjYWxlIHRoZSBleHBlY3RlZCBudW1iZXIgb2YgaW5kaXZpZHVhbHMgdG8gb3VyIGRhdGFzZXQgc2l6ZSBvZiBgciBhY3R1YWxfc2l6ZWAuCgpgYGB7cn0gICAgCmV4cGVjdGVkIDwtIAogICAgY2Vuc3VzX2FnZV9nZW5kZXIgJT4lCiAgICAgICAgZmlsdGVyKHJvd19udW1iZXIoKSA8PSAxOSkgJT4lCiAgICAgICAgc2VsZWN0KGFnZSwgbWFsZSwgZmVtYWxlKSAlPiUKICAgICAgICBtdXRhdGUoYWdlID0gZ3N1YigiXFwuIiwiIiwgYWdlKSkgJT4lCiAgICAgICAgZmlsdGVyKCEoYWdlICVpbiUgYygnVW5kZXIgNSB5ZWFycycsJ0FsbCBhZ2VzJywnNSB0byA5IHllYXJzJywnMTAgdG8gMTQgeWVhcnMnLCcxNSB0byAxOSB5ZWFycycpKSkgJT4lCiAgICAgICAgbXV0YXRlKGFnZV9idWNrZXQgPSBwYXN0ZTAoc3Vic3RyaW5nKGFnZSwxLDEpLCAiMCB0byAiLCBzdWJzdHJpbmcoYWdlLDEsMSksIjkgeWVhcnMiKSkgJT4lCiAgICAgICAgbXV0YXRlKGFnZV9idWNrZXQgPSBpZmVsc2UoYWdlX2J1Y2tldCA9PSAiODAgdG8gODkgeWVhcnMiLCAiODAgeWVhcnMgcGx1cyIsIGFnZV9idWNrZXQpKSAlPiUKICAgICAgICBzZWxlY3QoLWFnZSkgJT4lCiAgICAgICAgZ2F0aGVyKGdlbmRlciwgbiwgLWFnZV9idWNrZXQpICU+JQogICAgICAgIGdyb3VwX2J5KGFnZV9idWNrZXQsIGdlbmRlcikgJT4lCiAgICAgICAgc3VtbWFyaXNlKG4gPSBzdW0obikpICU+JQogICAgICAgIHVuZ3JvdXAoKSAlPiUKICAgICAgICBtdXRhdGUoZ2VuZGVyID0gcGFzdGUwKHRvdXBwZXIoc3Vic3RyaW5nKGdlbmRlciwxLDEpKSwgc3Vic3RyaW5nKGdlbmRlciwgMiwgOTk5KSkpICU+JQogICAgICAgIG11dGF0ZShwZXJjZW50ID0gbiAvIHN1bShuKSkgJT4lCiAgICAgICAgbXV0YXRlKEV4cGVjdGVkID0gYWN0dWFsX3NpemUgKiBwZXJjZW50KSAlPiUKICAgICAgICBzZWxlY3QoYWdlX2J1Y2tldCwgZ2VuZGVyLCBFeHBlY3RlZCkgJT4lCiAgICAgICAgZ2F0aGVyKHNvdXJjZSwgbiwgLWFnZV9idWNrZXQsIC1nZW5kZXIpICU+JQogICAgICAgIHNlbGVjdChzb3VyY2UsIGFnZV9idWNrZXQsIGdlbmRlciwgbikKCmV4cGVjdGVkCmBgYAoKVGhlbiB3ZSBjYW4gY29tYmluZSB0aGUgdHdvLgoKYGBge3J9CmFjdHVhbF9leHBlY3RlZCA8LSAKICAgIHVuaW9uKGFjdHVhbCwgZXhwZWN0ZWQpICU+JQogICAgICAgIG11dGF0ZSgKICAgICAgICAgICAgc291cmNlID0gZmFjdG9yKHNvdXJjZSwgbGV2ZWxzID0gYygiQWN0dWFsIiwiRXhwZWN0ZWQiKSkKICAgICAgICAgICAgLCBnZW5kZXIgPSBmYWN0b3IoZ2VuZGVyLCBsZXZlbHMgPSBjKCJNYWxlIiwiRmVtYWxlIikpCiAgICAgICAgKQpgYGAgICAgCiAgCldlIGZpbmQgdGhhdCB0aGUgTVRVUksgc2FtcGxlIGlzIHlvdW5nZXIgYW5kIG1vcmUgbWFsZSB0aGUgVVMgcG9wdWxhdGlvbi4gRm9yIGV4YW1wbGUsIGluIGEgc2FtcGxlIGByIGFjdHVhbF9zaXplYCB3ZSB3b3VsZCBleHBlY3QgdG8gZmluZCBgciBhY3R1YWxfZXhwZWN0ZWQgJT4lIGZpbHRlcihzb3VyY2UgID09ICJFeHBlY3RlZCIgJiBhZ2VfYnVja2V0ID09ICIyMCB0byAyOSB5ZWFycyIgJiBnZW5kZXIgPT0gIk1hbGUiKSAlPiUgc2VsZWN0KG4pICU+JSB1bmxpc3QoKWAgbWFsZXMsIDIwIHRvIDI5IHllYXJzIG9sZC4gSG93ZXZlciwgaW4gdGhlIE1UVVJLIHNhbXBsZSB0aGVyZSBhcmUgYHIgYWN0dWFsX2V4cGVjdGVkICU+JSBmaWx0ZXIoc291cmNlICA9PSAiQWN0dWFsIiAmIGFnZV9idWNrZXQgPT0gIjIwIHRvIDI5IHllYXJzIiAmIGdlbmRlciA9PSAiTWFsZSIpICU+JSBzZWxlY3QobikgJT4lIHVubGlzdCgpYCBtYWxlcywgMjAgdG8gMjkgeWVhcnMgb2xkLCBvciBgciBhY3R1YWxfZXhwZWN0ZWQgJT4lIGZpbHRlcihzb3VyY2UgID09ICJBY3R1YWwiICYgYWdlX2J1Y2tldCA9PSAiMjAgdG8gMjkgeWVhcnMiICYgZ2VuZGVyID09ICJNYWxlIikgJT4lIHNlbGVjdChuKSAlPiUgdW5saXN0KCkgLSAgYWN0dWFsX2V4cGVjdGVkICU+JSBmaWx0ZXIoc291cmNlICA9PSAiRXhwZWN0ZWQiICYgYWdlX2J1Y2tldCA9PSAiMjAgdG8gMjkgeWVhcnMiICYgZ2VuZGVyID09ICJNYWxlIikgJT4lIHNlbGVjdChuKSAlPiUgdW5saXN0KClgIG1vcmUgdGhhbiBleHBlY3RlZC4gSW4gYWRkaXRpb24sIGluIHRoZSBVUyBwb3B1bGF0aW9uIHdlIHdvdWxkIGV4cGVjdCBgciBhY3R1YWxfZXhwZWN0ZWQgJT4lIGdyb3VwX2J5KHNvdXJjZSwgZ2VuZGVyKSAlPiUgc3VtbWFyaXNlKHN1bSA9IHN1bShuKSkgJT4lIG11dGF0ZShwID0gcm91bmQoMTAwICogc3VtIC8gc3VtKHN1bSksIDEpKSAlPiUgZmlsdGVyKHNvdXJjZSA9PSAiRXhwZWN0ZWQiICYgZ2VuZGVyID09ICJGZW1hbGUiKSAlPiUgdW5saXN0KClgJSBmZW1hbGVzIGFuZCBgciBhY3R1YWxfZXhwZWN0ZWQgJT4lIGdyb3VwX2J5KHNvdXJjZSwgZ2VuZGVyKSAlPiUgc3VtbWFyaXNlKHN1bSA9IHN1bShuKSkgJT4lIG11dGF0ZShwID0gcm91bmQoMTAwICogc3VtIC8gc3VtKHN1bSksIDEpKSAlPiUgZmlsdGVyKHNvdXJjZSA9PSAiRXhwZWN0ZWQiICYgZ2VuZGVyID09ICJNYWxlIikgJT4lIHVubGlzdCgpYCUgbWFsZXMuIEhvd2V2ZXIsIHRoZSBNVFVLIHNhbXBsZSBoYXMgYHIgYWN0dWFsX2V4cGVjdGVkICU+JSBncm91cF9ieShzb3VyY2UsIGdlbmRlcikgJT4lIHN1bW1hcmlzZShzdW0gPSBzdW0obikpICU+JSBtdXRhdGUocCA9IHJvdW5kKDEwMCAqIHN1bSAvIHN1bShzdW0pLCAxKSkgJT4lIGZpbHRlcihzb3VyY2UgPT0gIkFjdHVhbCIgJiBnZW5kZXIgPT0gIkZlbWFsZSIpICU+JSB1bmxpc3QoKWAlIGZlbWFsZXMgYW5kIGByIGFjdHVhbF9leHBlY3RlZCAlPiUgZ3JvdXBfYnkoc291cmNlLCBnZW5kZXIpICU+JSBzdW1tYXJpc2Uoc3VtID0gc3VtKG4pKSAlPiUgbXV0YXRlKHAgPSByb3VuZCgxMDAgKiBzdW0gLyBzdW0oc3VtKSwgMSkpICU+JSBmaWx0ZXIoc291cmNlID09ICJBY3R1YWwiICYgZ2VuZGVyID09ICJNYWxlIikgJT4lIHVubGlzdCgpYCUgbWFsZXMuCiAgICAKYGBge3IgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9OH0gICAgCmFjdHVhbF9leHBlY3RlZCAlPiUgICAgCiAgICBnZ3Bsb3QoYWVzKHggPSBzb3VyY2UsIHkgPSBuLCBmaWxsID0gc291cmNlKSkgKyAKICAgIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArIAogICAgY29vcmRfZmxpcCgpICsgCiAgICBmYWNldF9ncmlkKGFnZV9idWNrZXQgfiBnZW5kZXIsIHN3aXRjaCA9ICJ5IikgKwogICAgdGhlbWVfanJmKCkgKyAKICAgIGxhYnModGl0bGUgPSAiTVRVUksgaXMgeW91bmdlciBhbmQgbW9yZSBtYWxlIHRoYW4gVVMgUG9wdWxhdGlvbiIsIAogICAgICAgICB5ID0gcGFzdGUwKCJSZXNwb25kZW50cyB0byBTdXJ2ZXkgKCIsIGFjdHVhbF9zaXplLCAiKSIpLCB4ID0gTlVMTCkgKwogICAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYyhBY3R1YWwgPSBwYWw1MzhbJ2JsdWUnXVtbMV1dLCBFeHBlY3RlZCA9IHBhbDUzOFsncmVkJ11bWzFdXSkpICsKICAgIGd1aWRlcyhmaWxsID0gRkFMU0UpICsKICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChuLCAwKSksIGhqdXN0ID0gMCwgZmFtaWx5ID0gIkRlY2ltYU1vbm9Qcm8iKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLjAyLCA0MCkpICsKICAgIHRoZW1lKHN0cmlwLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gNikpCmBgYAoKTG9va2luZyBhdCBlZHVjYXRpb24sIHdlIGRvd25sb2FkIGRhdGEgdGhlIFVTIENlbnN1cyBCdXJlYXUncyBbQ3VycmVudCBQb3B1bGF0aW9uIFJlcG9ydCBdKGh0dHBzOi8vd3d3LmNlbnN1cy5nb3YvaGhlcy9zb2NkZW1vL2VkdWNhdGlvbi9kYXRhL2Nwcy8yMDE1L1RhYmxlJTIwMS0wMS5jc3YpIHRoYXQgc2hvd3Mgc3RhdGlzdGljcyBvbiBlZHVjYXRpb25hbCBhdHRhaW5tZW50LiBUaGUgZGF0YSBpcyBieSBhZ2UgYW5kIGdlbmRlciwgYnV0IHdlIGFnZ3JlZ2F0ZSB0aGUgYWdlIHNlY3Rpb24gdG8gdGhlIHRvdGFsIHBvcHVsYXRpb24gdG8gY29tcGFyZSB0byB0aGUgTVRVUksgc2FtcGxlLiBUaGUgdGFibGUgYmVsb3cgc2hvd3MgZXhwZWN0ZWQgdnMgYWN0dWFsIHByb3BvcnRpb25zLgoKYGBge3IgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIGVycm9yID0gRkFMU0V9CmNlbnN1c19lZHUgPC0gcmVhZF9jc3YodXJsKCJodHRwczovL3d3dy5jZW5zdXMuZ292L2hoZXMvc29jZGVtby9lZHVjYXRpb24vZGF0YS9jcHMvMjAxNS9UYWJsZSUyMDEtMDEuY3N2IiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBza2lwID0gNSkKCmVkdV9leHBlY3RlZCA8LQogICAgY2Vuc3VzX2VkdSAlPiUKICAgIHNlbGVjdCgxLCAzOjE3KSAlPiUKICAgIGZpbHRlcihyb3dfbnVtYmVyKCkgJWluJSBjKDI6MTUpKSAlPiUKICAgIHNlbGVjdCgtWDEpICU+JQogICAgbXV0YXRlKGBEb2N0b3JhbCBkZWdyZWVgID0gYXMuaW50ZWdlcihnc3ViKCIsIiwiIixgRG9jdG9yYWwgZGVncmVlYCkpKSAlPiUKICAgIHN1bW1hcmlzZV9lYWNoKGZ1bnMoc3VtKC4sIG5hLnJtID1UUlVFKSkpICU+JQogICAgZ2F0aGVyKGVkdWNhdGlvbiwgbikgJT4lCiAgICBtdXRhdGUoCiAgICAgICAgZWR1Y2F0aW9uID0gaWZlbHNlKGVkdWNhdGlvbiA9PSAiTm9uZSIsICJPdGhlciIsIAogICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZWR1Y2F0aW9uICVpbiUgYygiMXN0IC0gNHRoIGdyYWRlIiwiNXRoIC0gNnRoIGdyYWRlIiwiN3RoIC0gOHRoIGdyYWRlIiwiOXRoIGdyYWRlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjEwdGggZ3JhZGUiLCIxMXRoIGdyYWRlIC8yIiksICJMZXNzIHRoYW4gMTIgeWVhcnM7IG5vIGhpZ2ggc2Nob29sIGRpcGxvbWEiLAogICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZWR1Y2F0aW9uID09ICJIaWdoIHNjaG9vbCBncmFkdWF0ZSIsICJIaWdoIHNjaG9vbCBncmFkdWF0ZSAob3IgZXF1aXZhbGVudCkiLAogICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZWR1Y2F0aW9uICVpbiUgYygiU29tZSBjb2xsZWdlLCBubyBkZWdyZWUiLCJBc3NvY2lhdGUncyBkZWdyZWUsIG9jY3VwYXRpb25hbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJBc3NvY2lhdGUncyBkZWdyZWUsIGFjYWRlbWljIiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU29tZSBjb2xsZWdlLCBubyBkaXBsb21hOyBvciBBc3NvY2lhdGXigJlzIGRlZ3JlZSIsCiAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShlZHVjYXRpb24gPT0gIkJhY2hlbG9yJ3MgZGVncmVlIiwgIkJhY2hlbG9y4oCZcyBkZWdyZWUgb3Igb3RoZXIgNC15ZWFyIGRlZ3JlZSIsIAogICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoZWR1Y2F0aW9uICVpbiUgYygiTWFzdGVyJ3MgZGVncmVlIiwiUHJvZmVzc2lvbmFsIGRlZ3JlZSIsIkRvY3RvcmFsIGRlZ3JlZSIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJHcmFkdWF0ZSBvciBwcm9mZXNzaW9uYWwgZGVncmVlIiwgTkEpKSkpKSkKICAgICAgICAsIGVkdWNhdGlvbiA9IGZhY3RvcihlZHVjYXRpb24sIGxldmVscyA9IGMoJ0xlc3MgdGhhbiAxMiB5ZWFyczsgbm8gaGlnaCBzY2hvb2wgZGlwbG9tYScKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsICdIaWdoIHNjaG9vbCBncmFkdWF0ZSAob3IgZXF1aXZhbGVudCknCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCAnU29tZSBjb2xsZWdlLCBubyBkaXBsb21hOyBvciBBc3NvY2lhdGXigJlzIGRlZ3JlZScKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsICdCYWNoZWxvcuKAmXMgZGVncmVlIG9yIG90aGVyIDQteWVhciBkZWdyZWUnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCAnR3JhZHVhdGUgb3IgcHJvZmVzc2lvbmFsIGRlZ3JlZScKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsICdPdGhlcicpKQogICAgKSAlPiUKICAgIGdyb3VwX2J5KGVkdWNhdGlvbikgJT4lCiAgICBzdW1tYXJpc2UoZXhwZWN0ZWRfbiA9IHN1bShuKSkgJT4lCiAgICB1bmdyb3VwKCkgJT4lCiAgICBtdXRhdGUoZXhwZWN0ZWQgPSBleHBlY3RlZF9uIC8gc3VtKGV4cGVjdGVkX24pKQoKZWR1X2FjdHVhbCA8LQogICAgc3VydmV5X3Jlc3VsdHNfZmluYWwgJT4lCiAgICBncm91cF9ieShlZHVjYXRpb24pICU+JQogICAgc3VtbWFyaXNlKGFjdHVhbF9uID0gbigpKSAlPiUKICAgIHVuZ3JvdXAoKSAlPiUKICAgIG11dGF0ZShhY3R1YWwgPSBhY3R1YWxfbiAvIHN1bShhY3R1YWxfbikpCiAgICAKY29tcGFyaXNvbl90YmxfZWR1IDwtCiAgICAgICAgaW5uZXJfam9pbihlZHVfZXhwZWN0ZWQsIGVkdV9hY3R1YWwsIGJ5ID0gYygiZWR1Y2F0aW9uIikpICU+JQogICAgICAgIG11dGF0ZSgKICAgICAgICAgICAgZXhwZWN0ZWQgPSBwYXN0ZTAocm91bmQoMTAwKmV4cGVjdGVkLDEpLCAiJSIpCiAgICAgICAgICAgICwgYWN0dWFsID0gcGFzdGUwKHJvdW5kKDEwMCphY3R1YWwsMSksICIlIikKICAgICAgICApICU+JQogICAgICAgIHNlbGVjdCgtYWN0dWFsX24sIC1leHBlY3RlZF9uKQoKY29tcGFyaXNvbl90YmxfZWR1CmBgYAoKV2UgZmluZCB0aGF0IHRoZSBNVFVSSyBzYW1wbGUgb3ZlciBpbmRleGVzIG9uIHBlb3BsZSBoYXZlIGJlZW4gdG8gY29sbGVnZSBvciBncmFkdWF0ZWQgZnJvbSBjb2xsZWdlLiBOb3RhYmx5LCBpbiBhIHNhbXBsZSBvZiB0aGUgVVMgcG9wdWxhdGlvbiB3ZSB3b3VsZCBleHBlY3QgdG8gZmluZCBgciBjb21wYXJpc29uX3RibF9lZHUgJT4lIGZpbHRlcihlZHVjYXRpb24gPT0gIlNvbWUgY29sbGVnZSwgbm8gZGlwbG9tYTsgb3IgQXNzb2NpYXRl4oCZcyBkZWdyZWUiKSAlPiUgc2VsZWN0KGV4cGVjdGVkKSAlPiUgdW5saXN0KClgIG9mIHBlb3BsZSB0byBoYXZlICdTb21lIGNvbGxlZ2UsIG5vIGRpcGxvbWE7IG9yIEFzc29jaWF0ZeKAmXMgZGVncmVlJywgYnV0IGluIHRoZSBNVFVSSyBzYW1wbGUgYHIgY29tcGFyaXNvbl90YmxfZWR1ICU+JSBmaWx0ZXIoZWR1Y2F0aW9uID09ICJTb21lIGNvbGxlZ2UsIG5vIGRpcGxvbWE7IG9yIEFzc29jaWF0ZeKAmXMgZGVncmVlIikgJT4lIHNlbGVjdChhY3R1YWwpICU+JSB1bmxpc3QoKWAgZml0IHRoaXMgY2F0ZWdvcnkgb2YgZWR1Y2F0aW9uYWwgYXR0YWlubWVudC4KCldlIHVzZSBhIHByb3BvcnRpb25zIHRlc3QgdG8gZGV0ZXJtaW5lIGlmIHRoZSBwcm9wb3J0aW9ucyBhcmUgaW5kZWVkIGRpZmZlcmVudC4KCmBgYHtyfQplZHVfbWF0cml4IDwtIGlubmVyX2pvaW4oZWR1X2V4cGVjdGVkLCBlZHVfYWN0dWFsLCBieSA9IGMoImVkdWNhdGlvbiIpKSAlPiUgc2VsZWN0KGV4cGVjdGVkX24sIGFjdHVhbF9uKSAlPiUgYXMubWF0cml4CihlZHVfcHJvcF90ZXN0IDwtIHByb3AudGVzdChlZHVfbWF0cml4KSkKYGBgCgpVc2luZyBgciBlZHVfcHJvcF90ZXN0JG1ldGhvZGAgd2UgaGF2ZSBzdHJvbmcgZXZpZGVuY2UgYWdhaW5zdCB0aGUgbnVsbCBoeXBvdGhlc2lzIHRoYXQgdGhlIHByb3BvcnRpb25zIGluIHRoZSBlZHVjYXRpb24gZ3JvdXBzIGFyZSB0aGUgc2FtZS4gVGhpcyBwcm92aWRlcyBmdXJ0aGVyIGV2aWRlbmNlIHRoYXQgdGhlIE1UVVJLIHNhbXBsZSBkb2VzIG5vdCByZXByZXNlbnQgdGhlIFVTIHBvcHVsYXRpb24uCgpMb29raW5nIGF0IGluY29tZSwgd2UgZG93bmxvYWQgZnJvbSB0aGUgVVMgQ2Vuc3VzIEJ1cmVhdSBzdGF0aXN0aWNzIG9uIFtwZXJzb25hbCBpbmNvbWVdKGh0dHA6Ly93d3cuY2Vuc3VzLmdvdi9kYXRhL3RhYmxlcy90aW1lLXNlcmllcy9kZW1vL2luY29tZS1wb3ZlcnR5L2Nwcy1waW5jL3BpbmMtMDEuaHRtbCkuCgpgYGB7cn0KZG93bmxvYWQuZmlsZSgiaHR0cDovL3d3dzIuY2Vuc3VzLmdvdi9wcm9ncmFtcy1zdXJ2ZXlzL2Nwcy90YWJsZXMvcGluYy0wMS8yMDE2L3BpbmMwMV8xXzFfMS54bHMiLAogICAgICAgICAgICAgIGRlc3RmaWxlID0gcGFzdGUwKGRhdGFfZGlyLCAncGluYzAxXzFfMV8xLnhscycpLCBtb2RlID0gIndiIikKaW5jb21lIDwtIHJlYWRfZXhjZWwocGFzdGUwKGRhdGFfZGlyLCAncGluYzAxXzFfMV8xLnhscycpLCBza2lwID0gOCkKCmluY29tZV9leHBlY3RlZCA8LSAKICAgIGluY29tZVssIGMoNDo0NCldICU+JSAKICAgIGZpbHRlcihyb3dfbnVtYmVyKCkgPT0gMikgJT4lCiAgICBnYXRoZXIoaW5jb21lLCBuKSAlPiUKICAgIG11dGF0ZSgKICAgICAgICBuID0gYXMuaW50ZWdlcihuKQogICAgKSAlPiUKICAgIHNlbGVjdChpbmNvbWUsIG4pICU+JSAKICAgIG11dGF0ZSgKICAgICAgICBpbmNvbWUgPSBpZmVsc2Uocm93X251bWJlcigpIDw9IDYsICJMZXNzIHRoYW4gJDE1LDAwMCIsIAogICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2Uocm93X251bWJlcigpIDw9IDEyLCAiJDE1LDAwMCAtICQzMCwwMDAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKHJvd19udW1iZXIoKSA8PSAyMCwgIiQzMCwwMDAgLSAkNTAsMDAwIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2Uocm93X251bWJlcigpIDw9IDMwLCAiJDUwLDAwMCAtICQ3NSwwMDAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiQWJvdmUgJDc1LDAwMCIpKSkpCiAgICAgICAgLCBpbmNvbWUgPSBmYWN0b3IoaW5jb21lLCBsZXZlbHMgPSBjKCJMZXNzIHRoYW4gJDE1LDAwMCIsIiQxNSwwMDAgLSAkMzAsMDAwIiwgIiQzMCwwMDAgLSAkNTAsMDAwIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiQ1MCwwMDAgLSAkNzUsMDAwIiwgIkFib3ZlICQ3NSwwMDAiKSkKICAgICkgJT4lCiAgICBncm91cF9ieShpbmNvbWUpICU+JQogICAgc3VtbWFyaXNlKAogICAgICAgIG4gPSBzdW0obikKICAgICkgJT4lCiAgICB1bmdyb3VwKCkgJT4lCiAgICBtdXRhdGUoZXhwZWN0ZWQgPSBuIC8gc3VtKG4pKSAlPiUKICAgIG11dGF0ZShleHBlY3RlZF9uID0gbikgJT4lCiAgICBzZWxlY3QoaW5jb21lLCBleHBlY3RlZF9uLCBleHBlY3RlZCkKCmluY29tZV9hY3R1YWwgPC0gCiAgICBzdXJ2ZXlfcmVzdWx0c19maW5hbCAlPiUKICAgIGZpbHRlcighaXMubmEoaW5jb21lKSkgJT4lCiAgICBtdXRhdGUoCiAgICAgICAgaW5jb21lID0gYXMuY2hhcmFjdGVyKGluY29tZSkKICAgICAgICAsIGluY29tZSA9IGlmZWxzZShpbmNvbWUgJWluJSBjKCIkNzUsMDAwIC0gJDE1MCwwMDAiLCJBYm92ZSAkMTUwLDAwMCIpLCAiQWJvdmUgJDc1LDAwMCIsIGluY29tZSkKICAgICAgICAsIGluY29tZSA9IGZhY3RvcihpbmNvbWUsIGxldmVscyA9IGMoIkxlc3MgdGhhbiAkMTUsMDAwIiwiJDE1LDAwMCAtICQzMCwwMDAiLCAiJDMwLDAwMCAtICQ1MCwwMDAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiJDUwLDAwMCAtICQ3NSwwMDAiLCAiQWJvdmUgJDc1LDAwMCIpKQogICAgKSAlPiUKICAgIGdyb3VwX2J5KGluY29tZSkgJT4lCiAgICBzdW1tYXJpc2UoCiAgICAgICAgbiA9IG4oKQogICAgKSAlPiUKICAgIHVuZ3JvdXAoKSAlPiUKICAgIG11dGF0ZShhY3R1YWwgPSBuIC8gc3VtKG4pKSAlPiUKICAgIG11dGF0ZShhY3R1YWxfbiA9IG4pICU+JQogICAgc2VsZWN0KGluY29tZSwgYWN0dWFsX24sIGFjdHVhbCkKCmNvbXBhcmlzb25fdGJsX2luY29tZSA8LQogICAgaW5uZXJfam9pbihpbmNvbWVfZXhwZWN0ZWQsIGluY29tZV9hY3R1YWwsIGJ5ID0gYygiaW5jb21lIikpICU+JQogICAgICAgIG11dGF0ZSgKICAgICAgICAgICAgZXhwZWN0ZWQgPSBwYXN0ZTAocm91bmQoMTAwKmV4cGVjdGVkLDEpLCAiJSIpCiAgICAgICAgICAgICwgYWN0dWFsID0gcGFzdGUwKHJvdW5kKDEwMCphY3R1YWwsMSksICIlIikKICAgICAgICApICU+JQogICAgICAgIHNlbGVjdCgtYWN0dWFsX24sIC1leHBlY3RlZF9uKQoKY29tcGFyaXNvbl90YmxfaW5jb21lCmBgYAoKTG9va2luZyBhdCB0aGUgdGFibGUgYWJvdmUgd2Ugc2VlIHRoZXJlIGlzIGEgc21hbGxlciBwZXJjZW50YWdlIG9mIGxvd2VyIGluY29tZSByZXNwb25kZW50cyB0aGFuIGV4cGVjdGVkIChgciBjb21wYXJpc29uX3RibF9pbmNvbWUgJT4lIGZpbHRlcihpbmNvbWUgPT0gIkxlc3MgdGhhbiAkMTUsMDAwIikgJT4lIHNlbGVjdChleHBlY3RlZCkgJT4lIHVubGlzdCgpYCB2cy4gYHIgY29tcGFyaXNvbl90YmxfaW5jb21lICU+JSBmaWx0ZXIoaW5jb21lID09ICJMZXNzIHRoYW4gJDE1LDAwMCIpICU+JSBzZWxlY3QoYWN0dWFsKSAlPiUgdW5saXN0KClgKS4gSW4gYWRkaXRpb24sIHRoZXJlIGlzIGxhcmdlciBwZXJjZW50YWdlIG9mIGhpZ2ggZWFybmluZyByZXNwb25kZW50cyB0aGFuIGV4cGVjdGVkIChgciBjb21wYXJpc29uX3RibF9pbmNvbWUgJT4lIGZpbHRlcihpbmNvbWUgPT0gIkFib3ZlICQ3NSwwMDAiKSAlPiUgc2VsZWN0KGV4cGVjdGVkKSAlPiUgdW5saXN0KClgIHZzLiBgciBjb21wYXJpc29uX3RibF9pbmNvbWUgJT4lIGZpbHRlcihpbmNvbWUgPT0gIkFib3ZlICQ3NSwwMDAiKSAlPiUgc2VsZWN0KGFjdHVhbCkgJT4lIHVubGlzdCgpYCkuCgpXZSB1c2UgYSBwcm9wb3J0aW9ucyB0ZXN0IHRvIGRldGVybWluZSBpZiB0aGUgcHJvcG9ydGlvbnMgYXJlIGluZGVlZCBkaWZmZXJlbnQuCgpgYGB7cn0KaW5jb21lX21hdHJpeCA8LSBpbm5lcl9qb2luKGluY29tZV9leHBlY3RlZCwgaW5jb21lX2FjdHVhbCwgYnkgPSBjKCJpbmNvbWUiKSkgJT4lIHNlbGVjdChleHBlY3RlZF9uLCBhY3R1YWxfbikgJT4lIGFzLm1hdHJpeAooaW5jb21lX3Byb3BfdGVzdCA8LSBwcm9wLnRlc3QoaW5jb21lX21hdHJpeCkpCmBgYAoKVXNpbmcgYHIgaW5jb21lX3Byb3BfdGVzdCRtZXRob2RgIHdlIGhhdmUgc3Ryb25nIGV2aWRlbmNlIGFnYWluc3QgdGhlIG51bGwgaHlwb3RoZXNpcyB0aGF0IHRoZSBwcm9wb3J0aW9ucyBpbiB0aGUgaW5jb21lIGdyb3VwcyBhcmUgdGhlIHNhbWUuIFRoaXMgcHJvdmlkZXMgZnVydGhlciBldmlkZW5jZSB0aGF0IHRoZSBNVFVSSyBzYW1wbGUgZG9lcyBub3QgcmVwcmVzZW50IHRoZSBVUyBwb3B1bGF0aW9uLgoKIyMjICgyKQoKVGhvdWdoIHdlIG1pZ2h0IGJlIGNvbmNlcm5lZCB0aGF0IG91ciBzYW1wbGUgZG9lcyBub3QgcmVwcmVzZW50IHRoZSBNVFVSSyBwb3B1bGF0aW9uIGFzIGEgd2hvbGUgd2UgaGF2ZSBubyBldmlkZW5jZSB0byBzdXBwb3J0IHRoaXMgZnJvbSB0aGUgZGF0YSBwcm92aWRlZC4gVGhlcmUgc2hvdWxkIGJlIGNvbmNlcm4gdGhhdCBzb21lb25lIHdobyBvcHRzIHRvIHBhcnRpY2lwYXRlIGluIGEgc3VydmV5IGFib3V0IHNhdGVsbGl0ZSByYWRpbyBtaWdodCBiZSBtb3JlIGxpa2VseSB0byBiZSBhIHNhdGVsbGl0ZSByYWRpbyBsaXN0ZW5lciAodW5sZXNzIE1UVVJLIHdvcmtlcnMgYXJlIG11Y2ggbW9yZSBsaWtlbHkgdG8gYmUgU2lyaXVzIGxpc3RlbmVycykuIEhvd2V2ZXIsIHdlIGhhdmUgbm8gZXZpZGVuY2UgdG8gc3VwcG9ydCB0aGlzIGNsYWltLgoKSW4gdGhpbmtpbmcgYWJvdXQgdGhpcyBxdWVzdGlvbiB3ZSByZWFkIHRoZSBhcnRpY2xlcyBb4oCcV2hvIGFyZSB0aGVzZSBwZW9wbGU/4oCdIEV2YWx1YXRpbmcgdGhlIGRlbW9ncmFwaGljIGNoYXJhY3RlcmlzdGljcyBhbmQgcG9saXRpY2FsIHByZWZlcmVuY2VzIG9mIE1UdXJrIHN1cnZleSByZXNwb25kZW50c10oaHR0cDovL3NjaG9sYXIuaGFydmFyZC5lZHUvZHRpbmdsZXkvZmlsZXMvd2hvYXJldGhlc2VwZW9wbGUucGRmKSBhbmQgWyJEZW1vZ3JhcGhpY3Mgb2YgTWVjaGFuaWNhbCBUdXJrIl0oaHR0cHM6Ly93d3cucmVzZWFyY2hnYXRlLm5ldC9wdWJsaWNhdGlvbi8yMjgxNDAzNDdfRGVtb2dyYXBoaWNzX29mX01lY2hhbmljYWxfVHVyaykuCgojIyMgKDMpCgpJbiBvcmRlciB0byBlc3RpbWF0ZSB0aGUgbnVtYmVyIG9mIFdoYXJ0b24gbGlzdGVuZXJzIGluIHRoZSBVUyB3ZSB3aWxsIGNyZWF0ZSBhIDk1JSBjb25maWRlbmNlIGludGVydmFsIG9mIHRoZSBwcm9wb3J0aW9uIG9mIFdoYXJ0b24gbGlzdGVuZXJzIGluIHRoZSBNVFVSSyBkYXRhc2V0IGFuZCBtdWx0aXBseSB0aGlzIGJ5IHRoZSBTaXJpdXMgcmFkaW8gbGlzdGVuZXJzICg1MS42IG1pbGxpb24pLgoKJCRcaGF0e3B9IFxwbSAgIHogXHNxcnR7XGZyYWN7XGhhdHtwfSgxLVxoYXR7cH0pfXtufX0kJAoKYGBge3J9CnBfaGF0IDwtIAogICAgc3VydmV5X3Jlc3VsdHNfZmluYWwgJT4lIAogICAgZmlsdGVyKHNpcml1cyA9PSAiWWVzIikgJT4lIAogICAgc3VtbWFyaXNlKHBfaGF0ID0gc3VtKHdoYXJ0b24gPT0gIlllcyIpIC8gbigpKSAlPiUgCiAgICB1bmxpc3QoKQoKY2kyIDwtIGMocF9oYXQgLSBxdCgwLjk3NSwgbnJvdyhzdXJ2ZXlfcmVzdWx0c19maW5hbCkpICogc3FydChwX2hhdCooMS1wX2hhdCkgLyBucm93KHN1cnZleV9yZXN1bHRzX2ZpbmFsKSkKICAgICAgICAsIHBfaGF0ICsgcXQoMC45NzUsIG5yb3coc3VydmV5X3Jlc3VsdHNfZmluYWwpKSAqIHNxcnQocF9oYXQqKDEtcF9oYXQpIC8gbnJvdyhzdXJ2ZXlfcmVzdWx0c19maW5hbCkpKQoKcG9wX3AgPC0gcF9oYXQgKiA1MS42CnBvcF9jaSA8LSByb3VuZChjaTIgKiA1MS42LDIpCmBgYAoKV2UgZXN0aW1hdGUgdGhlIHNhbXBsZSBwcm9wb3J0aW9uIHRvIGJlICoqYHIgcF9oYXRgKiogYW5kIHRoZSA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbCB0byBiZQoKJCQoYHIgY2kyWzFdYCwgYHIgY2kyWzJdYCkkJAoKVGh1cyB3ZSBlc3RpbWF0ZSB0aGUgc2l6ZSBvZiB0aGUgV2hhcnRvbiBsaXN0ZW5lcnMgaW4gdGhlIFVTIHRvIGJlICoqYHIgcm91bmQocG9wX3AsMilgKiogbWlsbGlvbiBhbmQgdGhlIDk1JSBjb25maWRlbmNlIGludGVydmFsIHRvIGJlIChpbiBtaWxsaW9ucykKCiQkKGByIHBvcF9jaVsxXWAsIGByIHBvcF9jaVsyXWApJCQKCiMjIEJyaWVmIFJlcG9ydAoKV2UgaGF2ZSByZXZpZXdlZCB0aGUgc3VydmV5IG9mIGByIG5yb3coc3VydmV5X3Jlc3VsdHNfY2xlYW5pbmcpYCByZXNwb25kZW50cyBvZiB0aGUgTVRVUksgc3VydmV5LiBXZSBoYXZlIGV2aWRlbmNlIHRoYXQgdGhlIHN1cnZleSByZXNwb25kZW50cyBkbyBub3QgcmVwcmVzZW50IHRoYXQgcG9wdWxhdGlvbiBvZiB0aGUgVVMgYmFzZWQgb24gdGhlIHByb3BvcnRpb24gb2YgU2lyaXVzIGxpc3RlbmVycyAoYHIgNTEuNi8zMjEuNGAgdnMgYHIgc2lyaXVzX3Byb3BgKSBhbmQgYWdlLCBnZW5kZXIsIGluY29tZSwgYW5kIGVkdWNhdGlvbiBjaGFyYWN0ZXJpc3RpY3MuIEhvd2V2ZXIsIGFzc3VtaW5nIHRoYXQgdGhlIHNhbXBsZSByZXByZXNlbnRzIHRoZSBwb3B1bGF0aW9uLCB3ZSBlc3RpbWF0ZSB0aGF0IHRoZXJlIGFyZSBiZXR3ZWVuIGByIHBvcF9jaVsxXWAgYW5kIGByIHBvcF9jaVsyXWAgbWlsbGlvbiBsaXN0ZW5lcnMgb2YgIkJ1c2luZXNzIFJhZGlvIFBvd2VyZWQgYnkgdGhlIFdoYXJ0b24gU2Nob29sIi4KCiMgUXVlc3Rpb24gMwoKIyMgUGFydCBBCgpgYGB7cn0KeCA8LSBzZXEoMCwgMSwgbGVuZ3RoID0gNDApCnkgPC0gMSArIDEuMip4ICsgcm5vcm0oNDAsIG1lYW4gPSAwLCBzZCA9IDIpCgpnZ3Bsb3QoZGF0YV9mcmFtZSh4LCB5KSwgYWVzKHggPSB4LCB5ID0geSkpICsgZ2VvbV9wb2ludChjb2xvdXIgPSBwYWw1MzhbJ2JsdWUnXSkgKyAKICAgIHRoZW1lX2pyZigpICsKICAgIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAuMDUsIDAuMDEpKSArIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAuMDIsIDAuMDEpKSArIAogICAgbGFicyh0aXRsZSA9ICJTY2F0dGVycGxvdCBvZiAoeCx5KSBwYWlycyIsIHkgPSAieSIsIHggPSAieCIpCmBgYAoKV2UgdXNlIHRoZSB0aGUgbG0gZnVuY3Rpb24gdG8gY3JlYXRlIGEgbGluZWFyIG1vZGVsLiAKCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CmZpdDEgPC0gbG0oeSB+IHgpCnRpZHkoZml0MSkgJT4lIHBhbmRlcigpCmBgYAoKV2UgZmluZCB0aGF0ICRcYmV0YV8wPWByIGZpdDEkY29lZmZpY2llbnRzWycoSW50ZXJjZXB0KSddYCQgYW5kICRcYmV0YV8xPWByIGZpdDEkY29lZmZpY2llbnRzWyd4J11gJC4gTmV4dCB3ZSBvdmVybGF5IExTIGVxdWF0aW9uIG9uIHRoZSBzY2F0dGVycGxvdC4KCmBgYHtyIGZpZy5hbGlnbiA9ICdjZW50ZXInfQpnZ3Bsb3QoZGF0YSA9IGZpdDEkbW9kZWwsIGFlcyh4ID0geCwgeSA9IHkpKSArIGdlb21fcG9pbnQoY29sb3VyID0gcGFsNTM4WydibHVlJ10pICsgCiAgICBnZW9tX3Ntb290aChtZXRob2Q9ImxtIiwgc2UgPSBUUlVFLCBjb2xvdXIgPSBwYWw1MzhbJ3JlZCddKSArCiAgICB0aGVtZV9qcmYoKSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLjA1LCAwLjAxKSkgKyBzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLjAyLCAwLjAxKSkgKyAKICAgIGxhYnModGl0bGUgPSAiTFMgRXF1YXRpb24iLCB5ID0gInkiLCB4ID0gIngiKQpgYGAKClRoZSA5NSUgY29uZmlkZW5jZSBpbnRlcnZhbCBmb3IgJFxiZXRhXzEkIGlzIAokJGByIGZpdDEkY29lZmZpY2llbnRzWyd4J11gIFxwbSAxLjk2XHRpbWVzIGByIGNvZWYoc3VtbWFyeShmaXQxKSlbLCAiU3RkLiBFcnJvciJdWyJ4Il1gJCQgCm9yCiQkKGByIGZpdDEkY29lZmZpY2llbnRzWyd4J10gLSBxdCgwLjk3NSwgMzgpKmNvZWYoc3VtbWFyeShmaXQxKSlbLCAiU3RkLiBFcnJvciJdWyJ4Il1gLCBgciBmaXQxJGNvZWZmaWNpZW50c1sneCddICsgcXQoMC45NzUsIDM4KSpjb2VmKHN1bW1hcnkoZml0MSkpWywgIlN0ZC4gRXJyb3IiXVsieCJdYCkkJCAKClRoaXMgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgZG9lcyBpbmRlZWQgY29udGFpbiB0aGUgdHJ1ZSAkXGJldGFfMSQgd2hpY2ggaXMgJDEuMiQuCgpUaGUgUlNFIGlzIGByIHNpZ21hKGZpdDEpYCB3aGljaCBpcyB2ZXJ5IGNsb3NlIHRvIHRoZSB0cnVlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgZXJyb3Igb2YgJFxzaWdtYSA9IDIkLgoKCiMjIFBhcnQgQgoKV2UgYmVnaW4gd2l0aCB0aGUgZ2l2ZW4gc2ltdWxhdGlvbiBjb2RlIGNodW5rOgoKYGBge3IgfQp4IDwtIHNlcSgwLCAxLCBsZW5ndGggPSA0MCkKbl9zaW0gPC0gMTAwCmIxIDwtIG51bWVyaWMobl9zaW0pICMgbnNpbSBtYW55IExTIGVzdGltYXRlcyBvZiBiZXRhMSAoPTEuMikgCnVwcGVyX2NpIDwtIG51bWVyaWMobl9zaW0pICMgbG93ZXIgYm91bmQKbG93ZXJfY2kgPC0gbnVtZXJpYyhuX3NpbSkgIyB1cHBlciBib3VuZAp0X3N0YXIgPC0gcXQoMC45NzUsIDM4KQoKIyBDYXJyeSBvdXQgdGhlIHNpbXVsYXRpb24gCmZvciAoaSBpbiAxOm5fc2ltKXsKICAgIHkgPC0gMSArIDEuMiAqIHggKyBybm9ybSg0MCwgc2QgPSAyKSAKICAgIGxzZSA8LSBsbSh5IH4geCkKICAgIGxzZV9vdXQgPC0gc3VtbWFyeShsc2UpJGNvZWZmaWNpZW50cyAKICAgIHNlIDwtIGxzZV9vdXRbMiwgMl0KICAgIGIxW2ldIDwtIGxzZV9vdXRbMiwgMV0gCiAgICB1cHBlcl9jaVtpXSA8LSBiMVtpXSArIHRfc3RhciAqIHNlIAogICAgbG93ZXJfY2lbaV0gPC0gYjFbaV0gLSB0X3N0YXIgKiBzZQp9CmBgYAoKV2Ugd2lsbCBzdW1tYXJpemUgJFxiZXRhXzEkLgoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30Kc3VtbWFyeShiMSkgJT4lIHBhbmRlcigpCmBgYAoKYGBge3IgfQpnZ3Bsb3QoZGF0YSA9IGRhdGFfZnJhbWUoYjEgPSBiMSksIGFlcyh4ID0gYjEpKSArIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMC4yLCBmaWxsID0gcGFsNTM4WydibHVlJ10pICsgCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAxLjIsIGNvbG91ciA9IHBhbDUzOFsncmVkJ10pICsKICAgIGdlb21fbGFiZWwoYWVzKHggPSAxLjIsIHkgPSBJbmYsIGxhYmVsID0gJ2JldGFbMV0gPT0gMS4yJyksIAogICAgICAgICAgICAgICB2anVzdCA9ICJpbndhcmQiLCBoanVzdCA9ICJpbndhcmQiLCBwYXJzZSA9IFRSVUUsIGNvbG91ciA9IHBhbDUzOFsncmVkJ10pICsKICAgIHRoZW1lX2pyZigpICsKICAgIHNjYWxlX3hfY29udGludW91cyhleHBhbmQgPSBjKDAuMDUsIDAuMDEpKSArIHNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAuMDIsIDAuMDEpKSArIAogICAgbGFicyh0aXRsZSA9IGV4cHJlc3Npb24oIkhpc3RvZ3JhbSBvZiBMUyBlc3RpbWF0ZXMgIn5iWzFdfiIgb2YgIn5iZXRhWzFdKSwgeSA9ICJDb3VudCIsIHggPSBleHByZXNzaW9uKGJbMV0pKQpgYGAKClRoZSBzYW1wbGluZyBkaXN0cmlidXRpb24gZG9lcyBhZ3JlZSB3aXRoIHRoZSB0aGVvcnkgYXMgbW9zdCBvZiB0aGUgTFMgZXN0aW1hdGUgb2YgJFxiZXRhXzEkIGFyZSBjbG9zZSB0byAxLjIuCgpgYGB7ciB9CmNpIDwtIGRhdGFfZnJhbWUobiA9IDE6MTAwLCBiMSA9IGIxICwgbG93ZXJfY2kgPSBsb3dlcl9jaSwgdXBwZXJfY2kgPSB1cHBlcl9jaSwKICAgICAgICAgICAgICAgICBjb3ZlcnMgPSBmYWN0b3IoaWZlbHNlKGxvd2VyX2NpIDwgMS4yICYgdXBwZXJfY2kgPiAxLjIsICJZZXMiLCAiTm8iKSwgbGV2ZWxzID0gYygiWWVzIiwgIk5vIikpKQoKYGBgCgpXZSBmaW5kIHRoYXQgYHIgc3VtKGNpJGNvdmVycyA9PSAiWWVzIilgIG91dCBvZiBgciBucm93KGNpKWAgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIGNvdmVyIHRoZSB0cnVlICRcYmV0YV8xJC4gV2Ugc2hvdyB0aGlzIGdyYXBoaWNhbGx5IGJlbG93LCB3aGVyZSB0aGUgcmVkIGludGVydmFscyBkbyBub3QgY292ZXIgdGhlIHRydWUgJFxiZXRhXzEkIGFuZCB0aGUgZ3JlZW4gaW50ZXJ2YWxzIGRvIGNvdmVyIHRoZSB0cnVlICRcYmV0YV8xJC4KCmBgYHtyIGZpZy5hbGlnbiA9ICdjZW50ZXInLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD03fQpnZ3Bsb3QoZGF0YSA9IGNpKSArIAogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMS4yKSArCiAgICBnZW9tX3NlZ21lbnQoYWVzKHggPSBsb3dlcl9jaSwgeGVuZCA9IHVwcGVyX2NpLCB5ID0gbiwgeWVuZCA9IG4sIGNvbG91ciA9IGNvdmVycykpICsKICAgIGxhYnModGl0bGUgPSAiMTAwIFNhbXBsZSBDb25maWRlbmNlIEludGVydmFscyIsIHkgPSBOVUxMLCB4ID0gZXhwcmVzc2lvbihiZXRhWzFdKSkgKwogICAgZ2VvbV9sYWJlbChhZXMoeCA9IDEuMiwgeSA9IEluZiwgbGFiZWwgPSAnYmV0YVsxXSA9PSAxLjInKSwgdmp1c3QgPSAiaW53YXJkIiwgaGp1c3QgPSAiaW53YXJkIiwgcGFyc2UgPSBUUlVFKSArCiAgICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQodGl0bGUgPSBleHByZXNzaW9uKCJDb3ZlcnMgIn5iZXRhWzFdfiI/IikpKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnYm90dG9tJykgKwogICAgdGhlbWVfanJmKCkgKwogICAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZCA9IGMoMC4wNSwgMC4wMSkpICsgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMC4wMiwgMC4wMSkpICsgCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoJ1llcycgPSBwYWw1MzhbJ2dyZWVuJ11bWzFdXSwgJ05vJyA9IHBhbDUzOFsncmVkJ11bWzFdXSkpCmBgYAoKIyBRdWVzdGlvbiA0CgojIyBTdW1tYXJ5CgpXZSBiZWdpbiBieSBsb2FkaW5nIGFuZCB0aWR5aW5nIHRoZSBNTCBQYXkgZGF0YXNldC4KCmBgYHtyIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQojIFJlYWQgaW4gdGhlIE1MIFBheSBkYXRhc2V0Cm1sX3BheSA8LSByZWFkX2NzdihwYXN0ZTAoZGF0YV9kaXIsICJNTFBheURhdGFfVG90YWwuY3N2IikpCgojIExldCdzIHRpZHkgdGhlIGRhdGFzZXQKbWxfcGF5MiA8LSAKICAgIG1sX3BheSAlPiUKICAgIHJlbmFtZSh0ZWFtID0gVGVhbS5uYW1lLjIwMTQpICU+JQogICAgZ2F0aGVyKG1ldHJpY19yYXcsIHZhbHVlLCAtcGF5cm9sbCwgLWF2Z3dpbiwgLXRlYW0pICU+JQogICAgbXV0YXRlKAogICAgICAgIHllYXIgPSBhcy5mYWN0b3Ioc3RyX2V4dHJhY3QobWV0cmljX3JhdywgIihcXGQpKyIpKQogICAgICAgICwgbWV0cmljID0gaWZlbHNlKHN1YnN0cmluZyhtZXRyaWNfcmF3LDEsMSkgPT0gInAiLCAicGF5cm9sbCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShzdHJfZGV0ZWN0KG1ldHJpY19yYXcsICIucGN0IiksICJhdmd3aW4iLCAid2lucyIpKQogICAgKSAlPiUKICAgIHNlbGVjdCh0ZWFtLCB5ZWFyLCBtZXRyaWMsIHZhbHVlLCBwYXlyb2xsLCBhdmd3aW4pCgptbF9wYXkyCmBgYAoKTGV0J3MgZG8gYSBmZXcgZGF0YSBxdWFsaXR5IGNoZWNrcywgd2hlcmUgd2UgZW5zdXJlIHRoZXJlIGFyZSAzMCB0ZWFtcyBwZXIgeWVhciBhbmQgdGhlcmUgYXJlIG5vIG1pc3NpbmcgdmFsdWVzLgoKYGBge3J9CiMgU2hvdyB0aGVyZSBhcmUgMzAgdW5pcXVlIHRlYW1zIHBlciB5ZWFyCm1sX3BheTIgJT4lCiAgICBncm91cF9ieSh5ZWFyKSAlPiUKICAgIHN1bW1hcmlzZSgKICAgICAgICBuID0gbigpCiAgICAgICAgLCBuX2Rpc3RpbmN0ID0gbl9kaXN0aW5jdCh0ZWFtKQogICAgKQoKIyBTaG93IHRoYXQgdGhlcmUgYXJlIG5vIG1pc3NpbmcgdmFsdWVzCm1sX3BheTIgJT4lCiAgICBzdW1tYXJpc2UoCiAgICAgICAgbmEgPSBzdW0oaXMubmEodmFsdWUpKQogICAgICAgICwgbmFuID0gc3VtKGlzLm5hbih2YWx1ZSkpCiAgICApCmBgYAoKCkZvciB0aGUgMTcgeWVhcnMgYmV0d2VlbiAxOTk4IGFuZCAyMDE0LCB3ZSBzdW1tYXJpemUgdGhlIHBheXJvbGwgb2YgdGhlIDMwIHRlYW1zLgoKYGBge3J9Cm1sX3BheTIgJT4lCiAgICBmaWx0ZXIobWV0cmljID09ICJwYXlyb2xsIikgJT4lCiAgICBncm91cF9ieSh5ZWFyKSAlPiUKICAgIHN1bW1hcmlzZSgKICAgICAgICAgIG1pbiA9IG1pbih2YWx1ZSkKICAgICAgICAsIHAyNSA9IHF1YW50aWxlKHZhbHVlLCAuMjUpCiAgICAgICAgLCBwNTAgPSBxdWFudGlsZSh2YWx1ZSwgLjUpCiAgICAgICAgLCBtZWFuID0gbWVhbih2YWx1ZSkKICAgICAgICAsIHA3NSA9IHF1YW50aWxlKHZhbHVlLCAuNzUpCiAgICAgICAgLCBtYXggPSBtYXgodmFsdWUpCiAgICApIApgYGAKClRoZSBib3gtcGxvdCBiZWxvdyBzaG93IHRoZXJlIHdhcyBhIGdlbmVyYWwgZ3Jvd3RoIGluIHBheXJvbGwgc3BlbmRpbmcgb3ZlciB0aGUgMTcgeWVhcnMgaW4gdGhlIE1MQi4gVGhlIG91dGxpZXIgYXQgdGhlIGhpZ2ggZW5kIG9mIHRoZSBwYXlyb2xsIHNjYWxlIGlzIHRoZSBOZXcgWW9yayBZYW5rZWVzLgoKYGBge3J9Cm1sX3BheTIgJT4lCiAgICBmaWx0ZXIobWV0cmljID09ICJwYXlyb2xsIikgJT4lCiAgICBnZ3Bsb3QoYWVzKHllYXIsIHZhbHVlKSkgKyBnZW9tX2JveHBsb3QoZmlsbCA9IHBhbDUzOFsnYmx1ZSddKSArCiAgICB0aGVtZV9qcmYoKSArCiAgICBsYWJzKHRpdGxlID0gIlBheXJvbGwgR3Jvd3RoIiwgeSA9ICJUZWFtIFBheXJvbGwgKCRtKSIsIHggPSBOVUxMKQpgYGAKCkxldCdzIGlkZW50aWZ5IHdoYXQgdGhlIHllYXItb3Zlci15ZWFyICh5b3kpIGdyb3d0aCBpbiBwYXlyb2xsIGhhcyBiZWVuIGJ5IHRlYW0uCgpgYGB7cn0KbWxfcGF5MiAlPiUKICAgIGZpbHRlcihtZXRyaWMgPT0gInBheXJvbGwiKSAlPiUKICAgIGFycmFuZ2UodGVhbSwgeWVhcikgJT4lCiAgICBncm91cF9ieSh0ZWFtKSAlPiUKICAgIG11dGF0ZSgKICAgICAgICB5b3lfZ3Jvd3RoID0gKHZhbHVlIC0gbGFnKHZhbHVlKSkgLyBsYWcodmFsdWUpCiAgICApICU+JQogICAgZ3JvdXBfYnkodGVhbSkgJT4lCiAgICBzdW1tYXJpc2UoCiAgICAgICAgYXZnX3lveV9ncm93dGggPSBtZWFuKHlveV9ncm93dGgsIG5hLnJtID0gVFJVRSkKICAgICkgJT4lIAogICAgdW5ncm91cCgpICU+JQogICAgYXJyYW5nZShkZXNjKGF2Z195b3lfZ3Jvd3RoKSkgJT4lCiAgICBwcmludChuID0gMzApCmBgYAoKTGV0J3Mgc3VtbWFyaXplIHRoaXMgYXMgdGhlIHlveSBncm93dGggb3ZlcmFsbC4gCgpgYGB7cn0KYXZnX3lveV9ncm93dGggPC0gCiAgICBtbF9wYXkyICU+JQogICAgICAgIGZpbHRlcihtZXRyaWMgPT0gInBheXJvbGwiKSAlPiUKICAgICAgICBhcnJhbmdlKHRlYW0sIHllYXIpICU+JQogICAgICAgIGdyb3VwX2J5KHRlYW0pICU+JQogICAgICAgIG11dGF0ZSgKICAgICAgICAgICAgeW95X2dyb3d0aCA9ICh2YWx1ZSAtIGxhZyh2YWx1ZSkpIC8gbGFnKHZhbHVlKQogICAgICAgICkgJT4lCiAgICAgICAgZ3JvdXBfYnkodGVhbSkgJT4lCiAgICAgICAgc3VtbWFyaXNlKAogICAgICAgICAgICBhdmdfeW95X2dyb3d0aCA9IG1lYW4oeW95X2dyb3d0aCwgbmEucm0gPSBUUlVFKQogICAgICAgICkgJT4lIAogICAgICAgIHVuZ3JvdXAoKSAlPiUKICAgICAgICBzdW1tYXJpc2UoCiAgICAgICAgICAgIGF2Z195b3lfZ3Jvd3RoID0gbWVhbihhdmdfeW95X2dyb3d0aCkKICAgICAgICApICU+JQogICAgICAgIHVubGlzdCgpCgphdmdfeW95X2dyb3d0aApgYGAKCk5leHQsIHdlIHN1bW1hcml6ZSB0aGUgd2lubmluZyBwZXJjZW50YWdlIGZvciB0aGUgMTcgeWVhcnMuIFRoaXMgaXMgbm90IHBhcnRpY3VsYXJseSBtZWFuaW5nZnVsLCBidXQgaXQgaXMgYSB3YXkgdG8gaWRlbnRpZnkgYW55IGVycmFudCB2YWx1ZXMuCgpgYGB7cn0KbWxfcGF5MiAlPiUKICAgIGZpbHRlcihtZXRyaWMgPT0gImF2Z3dpbiIpICU+JQogICAgZ3JvdXBfYnkoeWVhcikgJT4lCiAgICBzdW1tYXJpc2UoCiAgICAgICAgICBtaW4gPSBtaW4odmFsdWUpCiAgICAgICAgLCBwMjUgPSBxdWFudGlsZSh2YWx1ZSwgLjI1KQogICAgICAgICwgcDUwID0gcXVhbnRpbGUodmFsdWUsIC41KQogICAgICAgICwgbWVhbiA9IG1lYW4odmFsdWUpCiAgICAgICAgLCBwNzUgPSBxdWFudGlsZSh2YWx1ZSwgLjc1KQogICAgICAgICwgbWF4ID0gbWF4KHZhbHVlKQogICAgKQpgYGAKClRoZSBib3gtcGxvdCBiZWxvdyBzaG93cyB0aGUgZGlzcGVyc2lvbiBvZiB3aW5uaW5nIHBlcmNlbnRhZ2Ugb3ZlcnRpbWUuIFlvdSBjYW4gc2VlIHRoZSBkb3QgaW4gMjAwMyBpcyB0aGUgRGV0cm9pdCBUaWdlcnMgd2hvIGxvc3QgbW9yZSBnYW1lcyB0aGFuIGFueSBBbWVyaWNhbiBMZWFndWUgdGVhbSBpbiBoaXN0b3J5ICg0My0xMTkpLgoKYGBge3J9Cm1sX3BheTIgJT4lCiAgICBmaWx0ZXIobWV0cmljID09ICJhdmd3aW4iKSAlPiUKICAgIGdncGxvdChhZXMoeWVhciwgdmFsdWUpKSArIGdlb21fYm94cGxvdChmaWxsID0gcGFsNTM4WydibHVlJ10pICsKICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsgCiAgICB0aGVtZV9qcmYoKSArCiAgICBsYWJzKHRpdGxlID0gIldpbm5pbmcgUGVyY2VudGFnZSIsIHkgPSAiV2lubmluZyBQZXJjZW50YWdlIiwgeCA9IE5VTEwpCmBgYAoKTGV0J3Mgc3VtbWFyaXplIHRoZSB0d28gdmFyaWFibGVzIGFjcm9zcyB0aW1lIHRvIGdldCBhbiBpZGVhIG9mIHdoZXJlIHRoZSB2YWx1ZXMgZmFsbC4KCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9Cm1sX3BheTIgJT4lCiAgICBmaWx0ZXIobWV0cmljICVpbiUgYygicGF5cm9sbCIsImF2Z3dpbiIpKSAlPiUKICAgIGdyb3VwX2J5KG1ldHJpYykgJT4lCiAgICBzdW1tYXJpc2UoCiAgICAgICAgICBtaW4gPSBtaW4odmFsdWUpCiAgICAgICAgLCBwMjUgPSBxdWFudGlsZSh2YWx1ZSwgLjI1KQogICAgICAgICwgcDUwID0gcXVhbnRpbGUodmFsdWUsIC41KQogICAgICAgICwgbWVhbiA9IG1lYW4odmFsdWUpCiAgICAgICAgLCBwNzUgPSBxdWFudGlsZSh2YWx1ZSwgLjc1KQogICAgICAgICwgbWF4ID0gbWF4KHZhbHVlKQogICAgKSAlPiUKICAgIHBhbmRlcigpCmBgYAoKTmV4dCwgbGV0J3MgbG9vayBhdCBzY2F0dGVyIHBsb3RzIG9mIHBheXJvbGwgdnMgd2lubmluZyBwZXJjZW50YWdlIG92ZXIgdGhlIDE3IHllYXJzLiBUaGlzIHBsb3QgaGVscHMgaGlnaGxpZ2h0IHRoZSBmYWN0IHRoYXQgYXZlcmFnZSBwYXlyb2xsIGluY3JlYXNlcyBvdmVyIHRpbWUuCgpgYGB7ciBmaWcud2lkdGg9NywgZmlnLmhlaWdodD02fQptbF9wYXkyICU+JQogICAgZmlsdGVyKG1ldHJpYyAlaW4lIGMoInBheXJvbGwiLCJhdmd3aW4iKSkgJT4lCiAgICBzZWxlY3QoLXBheXJvbGwsIC1hdmd3aW4pICU+JQogICAgc3ByZWFkKG1ldHJpYywgdmFsdWUpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gcGF5cm9sbCwgeSA9IGF2Z3dpbikpICsgZmFjZXRfd3JhcCh+IHllYXIpICsgCiAgICBnZW9tX3BvaW50KGNvbG91ciA9IHBhbDUzOFsnYmx1ZSddLCBhbHBoYSA9IDAuNzUpICsgCiAgICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBjb2xvdXIgPSBwYWw1MzhbJ3JlZCddKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArIAogICAgbGFicyh0aXRsZSA9ICJQYXlyb2xsIHZzIFdpbm5pbmcgUGVyY2VudGFnZSIsIHkgPSAiV2lubmluZyBQZXJjZW50YWdlIiwgeCA9ICJQYXlyb2xsICgkbSkiKSArIAogICAgdGhlbWVfanJmKCkgKwogICAgZ2VvbV90ZXh0KGRhdGEgPQogICAgICAgICAgICAgICAgICAuICU+JQogICAgICAgICAgICAgICAgICBncm91cF9ieSh5ZWFyKSAlPiUKICAgICAgICAgICAgICAgICAgc3VtbWFyaXNlKAogICAgICAgICAgICAgICAgICAgICAgY29yID0gY29yKHBheXJvbGwsIGF2Z3dpbikKICAgICAgICAgICAgICAgICAgKSwKICAgICAgICBhZXMoeCA9IDE3MCwgeSA9IC43LCBsYWJlbCA9IHBhc3RlMCgiY29yID0gIiwgcm91bmQoY29yLCAzKSkpLAogICAgICAgICAgICBzaXplID0gMwogICAgICAgICkKCmF2Z19wZXJzb25fY29yIDwtCiAgICBtbF9wYXkyICU+JQogICAgICAgIGZpbHRlcihtZXRyaWMgJWluJSBjKCJwYXlyb2xsIiwiYXZnd2luIikpICU+JQogICAgICAgIHNlbGVjdCgtcGF5cm9sbCwgLWF2Z3dpbikgJT4lCiAgICAgICAgc3ByZWFkKG1ldHJpYywgdmFsdWUpICU+JQogICAgICAgIGdyb3VwX2J5KHllYXIpICU+JQogICAgICAgIHN1bW1hcmlzZSgKICAgICAgICAgICAgY29yID0gY29yKHBheXJvbGwsIGF2Z3dpbikgICAgCiAgICAgICAgKSAlPiUKICAgICAgICB1bmdyb3VwKCkgJT4lCiAgICAgICAgc3VtbWFyaXNlKAogICAgICAgICAgICBjb3IgPSBtZWFuKGNvcikKICAgICAgICApICU+JQogICAgICAgIHVubGlzdCgpCgojIEF2ZyBQZXJzb24gQ29ycmVsYXRpb24KYXZnX3BlcnNvbl9jb3IKYGBgCgpMZXQncyBzaG93IHRoZSB0cmVuZHMgaW4gdGhlIHR3byB2YXJpYWJsZSBieSB0ZWFtLgoKYGBge3J9Cm1sX3BheTIgJT4lCiAgICBmaWx0ZXIobWV0cmljICVpbiUgYygicGF5cm9sbCIsImF2Z3dpbiIpKSAlPiUKICAgIGdncGxvdChhZXMoeCA9IHllYXIsIHkgPSB2YWx1ZSwgZ3JvdXAgPSB0ZWFtKSkgKyAKICAgIGZhY2V0X2dyaWQobWV0cmljIH4gLiwgc2NhbGVzID0gImZyZWVfeSIsIHN3aXRjaCA9ICJ5IiwgCiAgICAgICAgICAgICAgIGxhYmVsbGVyID0gZ2dwbG90Mjo6bGFiZWxsZXIobWV0cmljID0gYyhhdmd3aW4gPSAiV2lubmluZyBQZXJjZW50YWdlIiwgcGF5cm9sbCA9ICJQYXlyb2xsIikpKSArCiAgICBnZW9tX2xpbmUoY29sb3VyID0gcGFsNTM4WydibHVlJ10pICsgCiAgICBndWlkZXMoY29sb3IgPSBGQUxTRSkgKwogICAgdGhlbWVfanJmKCkgKwogICAgbGFicyh0aXRsZSA9ICJQYXlyb2xsIGFuZCBXaW5uaW5nIFBlcmNlbnRhZ2UgYnkgVGVhbSIsIHkgPSBOVUxMLCB4ID0gTlVMTCkKYGBgCgoKU3VtbWFyeSBvZiBFeHBsb3JhdG9yeSBBbmFseXNpczoKCjEuIFBheXJvbGwgaGFzIGdlbmVyYWxseSBiZWVuIGluY3JlYXNpbmcgLSB0aGUgeW95IGF2ZXJhZ2UgZ3Jvd3RoIGlzICoqYHIgcm91bmQoYXZnX3lveV9ncm93dGggKiAxMDAsMilgJSoqLgoyLiBUaGVyZSBkb2VzIGFwcGVhciB0byBiZSBhIGxpbmVhciByZWxhdGlvbnNoaXAgYmV0d2VlbiBwYXlyb2xsIGFuZCB3aW5uaW5nIHBlcmNlbnRhZ2UsIGluIGEgZ2l2ZW4geWVhci4gVGhlIGF2ZXJhZ2UgUGVyc29uIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50IGlzICoqYHIgcm91bmQoYXZnX3BlcnNvbl9jb3IsMylgKiouCgojIyBQcmVkaWN0aW9uCgpMZXQncyBidWlsZCBhIGxpbmVhciBtb2RlbCB0byBwcmVkaWN0IHdpbm5pbmcgcGVyY2VudGFnZSBmb3IgZWFjaCBvZiB0aGUgMTcgeWVhcnMuIFRoZSBiZXN0IHdheSB0byBkbyB0aGlzIGlzIHVzaW5nIFtuZXN0ZWQgZGF0YSBmcmFtZXMgKHRpZHlyKSwgcHVycnIsIGFuZCBicm9vbV0oaHR0cHM6Ly9ibG9nLnJzdHVkaW8ub3JnLzIwMTYvMDIvMDIvdGlkeXItMC00LTAvKS4KCmBgYHtyfQpsbV9ieV95ZWFyIDwtIAogIG1sX3BheTIgJT4lCiAgZmlsdGVyKG1ldHJpYyAlaW4lIGMoInBheXJvbGwiLCJhdmd3aW4iKSkgJT4lCiAgc2VsZWN0KC1wYXlyb2xsLCAtYXZnd2luKSAlPiUKICBzcHJlYWQobWV0cmljLCB2YWx1ZSkgJT4lCiAgZ3JvdXBfYnkoeWVhcikgJT4lIAogIG5lc3QoKSAlPiUKICBtdXRhdGUoCiAgICBtb2RlbCA9IHB1cnJyOjptYXAoZGF0YSwgfiBsbShhdmd3aW4gfiBwYXlyb2xsLCBkYXRhID0gLikpCiAgKQpgYGAKCkJlbG93IGlzIGEgc3VtbWFyeSBvZiBlYWNoIG1vZGVsLiBJdCBhcHBlYXJzIHRoYXQgc29tZSBvZiB0aGUgbW9kZWxzIGFyZSBzaWduaWZpY2FudCBhdCA5NSUgY29uZmlkZW5jZSBsZXZlbCwgYnV0IGEgbnVtYmVyIG9mIG1vZGVscyBhcmUgbm90LCBub3RhYmx5IDIwMTIsIDIwMTQsIGFuZCAyMDAwLiBJZiB3ZSBsb29rIGJhY2sgYXQgdGhlIFBheXJvbGwgdnMgV2lubmluZyBQZXJjZW50YWdlIHBsb3QsIHdlIGNhbiBzZWUgdGhhdCBjb3JyZWxhdGlvbiBmb3IgdGhlc2UgeWVhcnMgYXJlIGxvd2VyIHRoYW4gb3RoZXJzLgoKYGBge3J9CmxtX2J5X3llYXIgJT4lIAogICAgdW5uZXN0KG1vZGVsICU+JSBwdXJycjo6bWFwKGJyb29tOjpnbGFuY2UpKSAlPiUKICAgIHNlbGVjdCh5ZWFyLCByLnNxdWFyZWQsIGFkai5yLnNxdWFyZWQsIHNpZ21hLCBzdGF0aXN0aWMsIHAudmFsdWUpCmBgYAoKQmVsb3cgaXMgdGhlIGZ1bGwgc3VtbWFyeSBvZiB0aGUgbW9kZWwgZm9yIDE5OTggYW5kIG5vdGUgdGhhdCB0aGUgcmVzdWx0cyBtYXRjaCB0aGUgMTk5OCByZWNvcmQgYWJvdmUuCgpgYGB7cn0Kc3VtbWFyeShsbV9ieV95ZWFyJG1vZGVsW1sxXV0pCmBgYAoKSG93ZXZlciwgYmVmb3JlIHdlIGludGVycHJldCB0aGVzZSBtb2RlbHMgbGV0J3MgY2hlY2sgb3VyIG1vZGVsIGFzc3VtcHRpb25zLCBhc2lkZSBmcm9tIGxpbmVhcml0eSwgd2UgbmVlZCB0byBjaGVjayAoMSkgbm9ybWFsaXR5IGFuZCAoMikgZXF1YWwgdmFyaWFuY2Ugb2YgdGhlIHJlc2lkdWFscy4KClRoZSBub3JtYWwgUS1RIHBsb3RzIGJlbG93IHNob3cgdGhhdCB0aGUgcmVzaWR1YWxzIGFyZSBhcHByb3hpbWF0ZWx5IG5vcm1hbC4KCmBgYHtyfQpsbV9ieV95ZWFyICU+JSAKICAgIHVubmVzdChtb2RlbCAlPiUgcHVycnI6Om1hcChicm9vbTo6YXVnbWVudCkpICU+JQogICAgZ2dwbG90KCkgKwogICAgZmFjZXRfd3JhcCh+IHllYXIpICsKICAgIHN0YXRfcXEoYWVzKHNhbXBsZSA9IC5zdGQucmVzaWQpLCBjb2xvdXIgPSBwYWw1MzhbJ2JsdWUnXSkgKwogICAgZ2VvbV9hYmxpbmUoZGF0YSA9CiAgICAgICAgLiAlPiUgCiAgICAgICAgZ3JvdXBfYnkoeWVhcikgJT4lCiAgICAgICAgc3VtbWFyaXNlKAogICAgICAgICAgICBzbG9wZSA9IGRpZmYocXVhbnRpbGUoLnN0ZC5yZXNpZCwgYygwLjI1LCAwLjc1KSkpIC8gZGlmZihxbm9ybShjKDAuMjUsIDAuNzUpKSkKICAgICAgICAgICAgLCBpbnQgPSBxdWFudGlsZSguc3RkLnJlc2lkLCBjKDAuMjUsIDAuNzUpKVsxTF0gLSAKICAgICAgICAgICAgICAgKGRpZmYocXVhbnRpbGUoLnN0ZC5yZXNpZCwgYygwLjI1LCAwLjc1KSkpIC8gCiAgICAgICAgICAgICAgICAgICAgZGlmZihxbm9ybShjKDAuMjUsIDAuNzUpKSkpICogcW5vcm0oYygwLjI1LCAwLjc1KSlbMUxdCiAgICAgICAgKSwKICAgICAgICBhZXMoc2xvcGUgPSBzbG9wZSwgaW50ZXJjZXB0ID0gaW50KSwgYWxwaGEgPSAwLjUKICAgICkgKwogICAgdGhlbWVfanJmKCkgKwogICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IE5VTEwpICsgCiAgICBsYWJzKHRpdGxlID0gIk5vcm1hbCBRLVEiLCB5ID0gIlN0YW5kYXJkaXplZCBSZXNpZHVhbHMiLCB4ID0gIlRoZW9yZXRpY2FsIFF1YW50aWxlcyIpCmBgYAoKVGhlIGZpdHRlZCB2YWx1ZXMgdnMgcmVzaWR1YWxzIHBsb3RzIHNob3cgYXBwcm94aW1hdGVseSBlcXVhbCB2YXJpYW5jZSBvZiB0aGUgcmVzaWR1YWxzIChpLmUuIG5vIGhldGVyb3NjZWRhc3RpY2l0eSkuCgpgYGB7cn0KbG1fYnlfeWVhciAlPiUgCiAgICB1bm5lc3QobW9kZWwgJT4lIHB1cnJyOjptYXAoYnJvb206OmF1Z21lbnQpKSAlPiUKICAgIGdncGxvdChhZXMoeCA9IC5maXR0ZWQsIHkgPSAucmVzaWQpKSArCiAgICBmYWNldF93cmFwKH4geWVhciwgc2NhbGUgPSAiZnJlZV94IikgKwogICAgZ2VvbV9wb2ludChjb2xvdXIgPSBwYWw1MzhbJ2JsdWUnXSkgKwogICAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxvZXNzIiwgY29sb3VyID0gcGFsNTM4WydyZWQnXSwgc2UgPSBGQUxTRSwgc2l6ZSA9IC4yNSwgYWxwaGEgPSAwLjUpICsgCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBhbHBoYSA9IDAuNSwgbGluZXR5cGUgPSAnZGFzaGVkJywgY29sb3IgPSAnYmxhY2snKSArCiAgICB0aGVtZV9qcmYoKSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gTlVMTCkgKyAKICAgIGxhYnModGl0bGUgPSAiRml0dGVkIFZhbHVlcyB2cyBSZXNpZHVhbHMiLCB5ID0gIlJlc2lkdWFscyIsIHggPSAiRml0dGVkIFZhbHVlcyIpCmBgYAoKSGF2aW5nIGNoZWNrZWQgdGhlIG1vZGVsIGFzc3VtcHRpb25zLCB3ZSBjYW4gbG9vayBhdCB0aGUgJFxiZXRhJCB0aGF0IGhhdmUgYmVlbiBlc3RpbWF0ZWQgYnkgdGhlIG1vZGVscy4gQmVsb3cgYXJlIHRoZSAzNCBjb2VmZmljaWVudHMgKDE3IG1vZGVscyB3aXRoIGFuIGludGVyY2VwdCB0ZXJtIGFuZCBhIGNvZWZmaWNpZW50IGZvciBwYXlyb2xsKS4gVGhlIHAtdmFsdWVzIHNob3cgdGhhdCBmb3IgbWFueSBvZiB0aGUgZXN0aW1hdGVkIGNvZWZmaWNpZW50cyB3ZSBkbyBub3QgaGF2ZSBlbm91Z2ggZXZpZGVuY2UgdG8gcmVqZWN0IHRoZSBudWxsIGh5cG90aGVzaXMgdGhhdCB0aGUgY29lZmZpY2llbnRzIGRpZmZlciBmcm9tIDAuCgpgYGB7cn0KbG1fYnlfeWVhciAlPiUgCiAgICB1bm5lc3QobW9kZWwgJT4lIHB1cnJyOjptYXAoYnJvb206OnRpZHkpKSAlPiUKICAgIHByaW50KG4gPSAzNCkKYGBgCgpXZSBmaW5kIHRoYXQgaW4gc29tZSB5ZWFycywgcGF5cm9sbCBpcyBhIHNpZ25pZmljYW50IHZhcmlhYmxlIGluIHByZWRpY3Rpbmcgd2lubmluZyBwZXJjZW50YWdlIHdoaWxlIGluIG90aGVycyBpdCBpcyBub3QuIFdlIG1pZ2h0IGNvbnNpZGVyIHVzaW5nIHByZXZpb3VzIHllYXJzIHBheXJvbGwgdG8gcHJlZGljdCB3aW5uaW5nIHBlcmNlbnRhZ2UuCgojIyBBZ2dyZWdhdGVkIEluZm9ybWF0aW9uIAoKVXNpbmcgdGhlIGFnZ3JlZ2F0ZWQgZGF0YSBwcm92aWRlZCBpbiAqTUxQYXlEYXRhX1RvdGFsLmNzdiogd2UgY3JlYXRlIGxpbmVhciByZWdyZXNzaW9uIHRvIHByZWRpY3QgYXZlcmFnZSB3aW5uaW5nIHBlcmNlbnRhZ2UuCgpgYGB7cn0KZml0MSA8LSBsbShhdmd3aW4gfiBwYXlyb2xsLCBkYXRhID0gbWxfcGF5MiAlPiUgc2VsZWN0KHRlYW0sIHBheXJvbGwsIGF2Z3dpbikgJT4lIGRpc3RpbmN0KCkpCnN1bW1hcnkoZml0MSkKYGBgCgpXZSBmaW5kIHRoYXQgbW9kZWwgaXMgc2lnbmlmaWNhbnQgd2l0aCBhbiBGLXN0YXRpc3RpYyBvZiBgciBzdW1tYXJ5KGZpdDEpJGZzdGF0aXN0aWNbMV1gLgoKIyMjIFJlZCBTb3gKCmBgYHtyfQpyZWRfc294IDwtIG1sX3BheTIgJT4lIHNlbGVjdCh0ZWFtLCBwYXlyb2xsLCBhdmd3aW4pICU+JSBkaXN0aW5jdCgpICU+JSBmaWx0ZXIodGVhbSA9PSAiQm9zdG9uIFJlZCBTb3giKQoocmVkX3NveF9pbnRlcnZhbCA8LSBwcmVkaWN0KGZpdDEsIHJlZF9zb3gsIGludGVydmFsID0gInByZWRpY3Rpb24iLCBsZXZlbCA9IC45NSkpCmBgYAoKVGhlIDk1JSBwcmVkaWN0aW9uIGludGVydmFsIGZvciB0aGUgQm9zdG9uIFJlZCBTb3ggaXMgJChgciByZWRfc294X2ludGVydmFsWzJdYCwgYHIgcmVkX3NveF9pbnRlcnZhbFszXWApJCBhbmQgdGhlaXIgd2lubmluZyBwZXJjZW50YWdlIGlzICRgciByZWRfc294JGF2Z3dpbltbMV1dYCQuIEluIG90aGVyIHdvcmRzLCB0aGUgbW9kZWwgZG9lcyBxdWl0ZSB3ZWxsIGluIHByZWRpY3RpbmcgdGhlIEJvc3RvbiBSZWQgU294J3Mgd2lubmluZyBwZXJjZW50YWdlIG92ZXIgdGhlIDE3IHllYXIgcGVyaW9kLgoKIyMjIE9ha2xhbmQgQSdzCgpgYGB7cn0Kb2FrbGFuZCA8LSBtbF9wYXkyICU+JSBzZWxlY3QodGVhbSwgcGF5cm9sbCwgYXZnd2luKSAlPiUgZGlzdGluY3QoKSAlPiUgZmlsdGVyKHRlYW0gPT0gIk9ha2xhbmQgQXRobGV0aWNzIikKKG9ha2xhbmRfaW50ZXJ2YWwgPC0gcHJlZGljdChmaXQxLCBvYWtsYW5kLCBpbnRlcnZhbCA9ICJwcmVkaWN0aW9uIiwgbGV2ZWwgPSAuOTUpKQpgYGAKClRoZSA5NSUgcHJlZGljdGlvbiBpbnRlcnZhbCBmb3IgdGhlIE9ha2xhbmQgQXRobGV0aWNzIGlzICQoYHIgb2FrbGFuZF9pbnRlcnZhbFsyXWAsIGByIG9ha2xhbmRfaW50ZXJ2YWxbM11gKSQgYW5kIHRoZWlyIHdpbm5pbmcgcGVyY2VudGFnZSBpcyAkYHIgb2FrbGFuZCRhdmd3aW5bWzFdXWAkLiBJbiBvdGhlciB3b3JkcywgdGhlIG1vZGVsIHVuZGVyLXByZWRpY3RpbmcgdGhlIE9ha2xhbmQgQXRobGV0aWMncyB3aW5uaW5nIHBlcmNlbnRhZ2Ugb3ZlciB0aGUgMTcgeWVhciBwZXJpb2QgYXMgaXQncyBvdXRzaWRlIHRoZSBwcmVkaWN0aW9uIGludGVydmFsLiBUaGlzIHdhcyBhbiBleHBlY3RlZCByZXN1bHQgYXMgQmlsbHkgQmVhbmUgd2FzIHRoZSBnZW5lcmFsIG1hbmFnZXIgZm9yIHRoZSBBJ3MgZHVyaW5nIHRoaXMgcGVyaW9kLiAKCiMjIEJlc3QgTW9kZWwgd2l0aCBIaXN0b3JpY2FscwoKVG8gYnVpbGQgYSBtb2RlbCB0byBiZXN0IHByZWRpY3QgdGhlIHdpbm5pbmcgcGVyY2VudGFnZSBpbiAyMDE0LCB3ZSdsbCB1c2UgdGhlIHBheXJvbGwgYW5kIHdpbm5pbmcgcGVyY2VudGFnZSBmcm9tIHByZXZpb3VzIHllYXJzLiBXZSB3aWxsIHVzZSB0aGUgbGFzdCA1IHllYXJzIG9mIHdpbm5pbmcgcGVyY2VudGFnZXMgYW5kIHBheXJvbGwgZmlndXJlcy4gV2UgdXNlIG91ciBkb21haW4ga25vd2xlZGdlIHRvIGFzc3VtZSB0aGF0IGRhdGEgbW9yZSB0aGFuIDUgeWVhcnMgYmFjayB3aWxsIG5vdCBoYXZlIGFuIGluZmx1ZW5jZSBvbiB0aGUgY3VycmVudCBzZWFzb24uCgpgYGB7cn0KbGFzdF81eWVhcnMgPC0gCiAgICBtbF9wYXkyICU+JQogICAgICAgIGZpbHRlcihtZXRyaWMgJWluJSBjKCJwYXlyb2xsIiwiYXZnd2luIikpICU+JQogICAgICAgIHNlbGVjdCgtcGF5cm9sbCwgLWF2Z3dpbikgJT4lCiAgICAgICAgYXJyYW5nZSh0ZWFtLCB5ZWFyKSAlPiUKICAgICAgICBzcHJlYWQobWV0cmljLCB2YWx1ZSkgJT4lCiAgICAgICAgZ3JvdXBfYnkodGVhbSkgJT4lCiAgICAgICAgbXV0YXRlKAogICAgICAgICAgICAgIHBheXJvbGxfbGFnMSA9IGxhZyhwYXlyb2xsLCAxKQogICAgICAgICAgICAsIHBheXJvbGxfbGFnMiA9IGxhZyhwYXlyb2xsLCAyKQogICAgICAgICAgICAsIHBheXJvbGxfbGFnMyA9IGxhZyhwYXlyb2xsLCAzKQogICAgICAgICAgICAsIHBheXJvbGxfbGFnNCA9IGxhZyhwYXlyb2xsLCA0KQogICAgICAgICAgICAsIHBheXJvbGxfbGFnNSA9IGxhZyhwYXlyb2xsLCA1KQogICAgICAgICAgICAsIGF2Z3dpbl9sYWcxID0gbGFnKGF2Z3dpbiwgMSkKICAgICAgICAgICAgLCBhdmd3aW5fbGFnMiA9IGxhZyhhdmd3aW4sIDIpCiAgICAgICAgICAgICwgYXZnd2luX2xhZzMgPSBsYWcoYXZnd2luLCAzKQogICAgICAgICAgICAsIGF2Z3dpbl9sYWc0ID0gbGFnKGF2Z3dpbiwgNCkKICAgICAgICAgICAgLCBhdmd3aW5fbGFnNSA9IGxhZyhhdmd3aW4sIDUpCiAgICAgICAgKSAlPiUKICAgICAgICB1bmdyb3VwKCkgJT4lCiAgICAgICAgZmlsdGVyKHllYXIgPT0gMjAxNCkgJT4lCiAgICAgICAgc2VsZWN0KC1wYXlyb2xsLCAteWVhcikKCnN1bW1hcnkobG0oYXZnd2luIH4gLiAtdGVhbSwgZGF0YSA9IGxhc3RfNXllYXJzKSkKYGBgCgpXZSB3aWxsIGl0ZXJhdGl2ZWx5IHJlbW92ZSB0aGUgZXhwbGFuYXRvcnkgdmFyaWFibGUgdGhhdCBoYXMgdGhlIGxhcmdlc3QgcC12YWx1ZSBmb3IgdGhlIGNvZWZmaWNpZW50IGVzdGltYXRlLgoKYGBge3J9CnN1bW1hcnkobG0oYXZnd2luIH4gLiAtdGVhbSAtcGF5cm9sbF9sYWc0LCBkYXRhID0gbGFzdF81eWVhcnMpKQpzdW1tYXJ5KGxtKGF2Z3dpbiB+IC4gLXRlYW0gLXBheXJvbGxfbGFnNCAtYXZnd2luX2xhZzUsIGRhdGEgPSBsYXN0XzV5ZWFycykpCnN1bW1hcnkobG0oYXZnd2luIH4gLiAtdGVhbSAtcGF5cm9sbF9sYWc0IC1hdmd3aW5fbGFnNSAtYXZnd2luX2xhZzEsIGRhdGEgPSBsYXN0XzV5ZWFycykpCnN1bW1hcnkobG0oYXZnd2luIH4gLiAtdGVhbSAtcGF5cm9sbF9sYWc0IC1hdmd3aW5fbGFnNSAtYXZnd2luX2xhZzEgLXBheXJvbGxfbGFnNSwgZGF0YSA9IGxhc3RfNXllYXJzKSkKc3VtbWFyeShsbShhdmd3aW4gfiAuIC10ZWFtIC1wYXlyb2xsX2xhZzQgLWF2Z3dpbl9sYWc1IC1hdmd3aW5fbGFnMSAtcGF5cm9sbF9sYWc1IC1wYXlyb2xsX2xhZzMsIGRhdGEgPSBsYXN0XzV5ZWFycykpCnN1bW1hcnkobG0oYXZnd2luIH4gLiAtdGVhbSAtcGF5cm9sbF9sYWc0IC1hdmd3aW5fbGFnNSAtYXZnd2luX2xhZzEgLXBheXJvbGxfbGFnNSAtcGF5cm9sbF9sYWczIC1wYXlyb2xsX2xhZzIsIGRhdGEgPSBsYXN0XzV5ZWFycykpCnN1bW1hcnkobG0oYXZnd2luIH4gLiAtdGVhbSAtcGF5cm9sbF9sYWc0IC1hdmd3aW5fbGFnNSAtYXZnd2luX2xhZzEgLXBheXJvbGxfbGFnNSAtcGF5cm9sbF9sYWczIC1wYXlyb2xsX2xhZzIgLWF2Z3dpbl9sYWczLCBkYXRhID0gbGFzdF81eWVhcnMpKQpzdW1tYXJ5KGxtKGF2Z3dpbiB+IC4gLXRlYW0gLXBheXJvbGxfbGFnNCAtYXZnd2luX2xhZzUgLWF2Z3dpbl9sYWcxIC1wYXlyb2xsX2xhZzUgLXBheXJvbGxfbGFnMyAtcGF5cm9sbF9sYWcyIC1hdmd3aW5fbGFnMyAtcGF5cm9sbF9sYWcxLCBkYXRhID0gbGFzdF81eWVhcnMpKQpgYGAKClVzaW5nIHRoaXMgcHJvY2Vzcywgd2Ugd291bGQgc2VsZWN0IGEgbW9kZWwgd2l0aCB0aGUgdHdvIGV4cGxhbmF0b3J5IHZhcmlhYmxlcyBvZiB0aGUgYXZlcmFnZSB3aW5uaW5nIHBlcmNlbnRhZ2UgZnJvbSAyIHllYXJzIGFuZCA0IHllYXJzIGFnby4gVGhpcyBtb2RlbCBzZWVtcyByYXRoZXIgYXJiaXRyYXJ5IGJlY2F1c2UgdGhlcmUgaXMgbm90IHNvbWV0aGluZyBzaWduaWZpY2FudCBhYm91dCAyIHllYXJzIG9yIDQgeWVhcnMgYWdvLgoKUGVyaGFwcyBhIGJldHRlciBzb2x1dGlvbnMgaXMgdG8gYnVpbGQgZmVhdHVyZXMgZnJvbSB0aGUgcHJldmlvdXMgeWVhcnMuIEJlbG93IHdlIGF0dGVtcHQgdG8gdXNpbmcgc2ltcGxlIGV4cG9uZW50aWFsIHNtb290aGluZyAoJFxhbHBoYSA9IDAuNiQgdG8gd2VpZ2h0IHJlY2VudCB2YWx1ZXMgbW9yZSkgb2YgcGF5cm9sbCBhbmQgd2lubmluZyBwZXJjZW50YWdlIG92ZXIgMyB5ZWFycy4KCmBgYHtyfQphbHBoYSA8LSAwLjYKbnllYXJzIDwtIDMKCmZuX3Nlc19mb3JlY2FzdCA8LSBmdW5jdGlvbih4KSB7CiAgaWYgKHN1bSghaXMubmEoeCkpIDwgbnllYXJzKSB7CiAgICBmb3JlIDwtIGFzLmRvdWJsZShOQSkKICB9IGVsc2UgewogICAgZm9yZSA8LSBkYXRhLmZyYW1lKHNlcyh4LCBhbHBoYSA9IGFscGhhLCBpbml0aWFsID0gJ3NpbXBsZScpKVssMV1bMV0KICB9CiAgcmV0dXJuKGZvcmUpCn0KCmxhc3RfMnllYXJzIDwtCiAgICBtbF9wYXkyICU+JQogICAgICAgIGZpbHRlcihtZXRyaWMgJWluJSBjKCJwYXlyb2xsIiwiYXZnd2luIikpICU+JQogICAgICAgIHNlbGVjdCgtcGF5cm9sbCwgLWF2Z3dpbikgJT4lCiAgICAgICAgYXJyYW5nZSh0ZWFtLCB5ZWFyKSAlPiUKICAgICAgICBzcHJlYWQobWV0cmljLCB2YWx1ZSkgJT4lCiAgICAgICAgZ3JvdXBfYnkodGVhbSkgJT4lCiAgICAgICAgbXV0YXRlKAogICAgICAgICAgICBwYXlyb2xsX3NlcyA9IHJvbGxhcHBseShwYXlyb2xsLCBGVU4gPSBmbl9zZXNfZm9yZWNhc3QsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSBsaXN0KC1ueWVhcnM6LTEpLCBmaWxsID0gTkEsIGJ5LmNvbHVtbiA9IFRSVUUsIGFsaWduID0gInJpZ2h0IikKICAgICAgICAgICAgLCBhdmd3aW5fc2VzID0gcm9sbGFwcGx5KGF2Z3dpbiwgRlVOID0gZm5fc2VzX2ZvcmVjYXN0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdpZHRoID0gbGlzdCgtbnllYXJzOi0xKSwgZmlsbCA9IE5BLCBieS5jb2x1bW4gPSBUUlVFLCBhbGlnbiA9ICJyaWdodCIpCiAgICAgICAgKSAlPiUKICAgICAgICB1bmdyb3VwKCkgJT4lCiAgICAgICAgZ3JvdXBfYnkodGVhbSkgJT4lCiAgICAgICAgbXV0YXRlKAogICAgICAgICAgICBwYXlyb2xsX3Nlc19sYWcxID0gbGFnKHBheXJvbGxfc2VzLDEpCiAgICAgICAgICAgICwgYXZnd2luX3Nlc19sYWcxID0gbGFnKGF2Z3dpbl9zZXMsMSkKICAgICAgICApICU+JQogICAgICAgIHVuZ3JvdXAoKSAlPiUKICAgICAgICBmaWx0ZXIoeWVhciA9PSAyMDE0KSAlPiUKICAgICAgICBzZWxlY3QodGVhbSwgYXZnd2luLCBwYXlyb2xsX3NlcywgYXZnd2luX3NlcywgcGF5cm9sbF9zZXNfbGFnMSwgYXZnd2luX3Nlc19sYWcxKQoKbGFzdF8yeWVhcnMKYGBgCgpEZXNwaXRlIG91ciBlZmZvcnRzIGFib3ZlLCB3ZSBmaW5kIHRoYXQgdGhpcyBpcyBub3QgdmVyeSBoZWxwZnVsLiBXZSBzZXR0bGUgb24gYSBtb2RlbCB0aGF0IGNvbnRhaW5zIHRoZSAzLXllYXIgc21vb3RoZWQgcGF5cm9sbCBmaWd1cmVzLgoKYGBge3J9CihzZXNfZml0IDwtIHN0ZXAobG0oYXZnd2luIH4gLiAtdGVhbSwgZGF0YSA9IGxhc3RfMnllYXJzKSkpCmBgYAoKVXNpbmcgdGhpcyBtb2RlbCwgd2UgY2FuIHByZWRpY3QgdGhlIHdpbm5pbmcgcGVyY2VudGFnZSBmb3IgdGhlIHRlYW1zIGluIDIwMTUuIFRvIGRvIHRoaXMgd2UganVzdCBuZWVkIHRvIGNoYW5nZSB0aGUgd2lkdGggb2Ygb3VyIHJvbGxpbmcgc2ltcGxlIGV4cG9uZW50aWFsIHNtb290aGluZyBmdW5jdGlvbiB0byBpbmNsdWRlIDIwMTQgZGF0YSBhcyB0aGlzIHdhcyBub3QgYmVpbmcgdXNlZCBpbiB0aGUgcHJldmlvdXMgcHJlZGljdGlvbiBvZiAyMDE0LgoKYGBge3J9Cmxhc3RfMnllYXJzXzIwMTUgPC0KICAgIG1sX3BheTIgJT4lCiAgICAgICAgZmlsdGVyKG1ldHJpYyAlaW4lIGMoInBheXJvbGwiLCJhdmd3aW4iKSkgJT4lCiAgICAgICAgc2VsZWN0KC1wYXlyb2xsLCAtYXZnd2luKSAlPiUKICAgICAgICBhcnJhbmdlKHRlYW0sIHllYXIpICU+JQogICAgICAgIHNwcmVhZChtZXRyaWMsIHZhbHVlKSAlPiUKICAgICAgICBncm91cF9ieSh0ZWFtKSAlPiUKICAgICAgICBtdXRhdGUoCiAgICAgICAgICAgIHBheXJvbGxfc2VzID0gcm9sbGFwcGx5KHBheXJvbGwsIEZVTiA9IGZuX3Nlc19mb3JlY2FzdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aWR0aCA9IGxpc3QoKC1ueWVhcnMrMSk6MCksIGZpbGwgPSBOQSwgYnkuY29sdW1uID0gVFJVRSwgYWxpZ24gPSAicmlnaHQiKQogICAgICAgICAgICAsIGF2Z3dpbl9zZXMgPSByb2xsYXBwbHkoYXZnd2luLCBGVU4gPSBmbl9zZXNfZm9yZWNhc3QsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSBsaXN0KCgtbnllYXJzKzEpOjApLCBmaWxsID0gTkEsIGJ5LmNvbHVtbiA9IFRSVUUsIGFsaWduID0gInJpZ2h0IikKICAgICAgICApICU+JQogICAgICAgIHVuZ3JvdXAoKSAlPiUKICAgICAgICBncm91cF9ieSh0ZWFtKSAlPiUKICAgICAgICBtdXRhdGUoCiAgICAgICAgICAgIHBheXJvbGxfc2VzX2xhZzEgPSBsYWcocGF5cm9sbF9zZXMsMSkKICAgICAgICAgICAgLCBhdmd3aW5fc2VzX2xhZzEgPSBsYWcoYXZnd2luX3NlcywxKQogICAgICAgICkgJT4lCiAgICAgICAgdW5ncm91cCgpICU+JQogICAgICAgIGZpbHRlcih5ZWFyID09IDIwMTQpICU+JQogICAgICAgIHNlbGVjdCh0ZWFtLCBhdmd3aW4sIHBheXJvbGxfc2VzLCBhdmd3aW5fc2VzLCBwYXlyb2xsX3Nlc19sYWcxLCBhdmd3aW5fc2VzX2xhZzEpCgpjYmluZCgKICAgIGxhc3RfMnllYXJzXzIwMTUgJT4lIHNlbGVjdCh0ZWFtKSwgCiAgICBwcmVkaWN0aW9uXzIwMTUgPSBwcmVkaWN0KHNlc19maXQsIGxhc3RfMnllYXJzXzIwMTUpCikgJT4lIHRibF9kZiAlPiUKICAgIHByaW50KG4gPSAzMCkKYGBgCgojIFF1ZXN0aW9uIDUKCiMjIEV4cGxvcmF0b3J5IEFuYWx5c2lzCgpXZSBjYW4gc3RhcnQgb2Ygd2l0aCBiYXNpYyBwYWlycyBwbG90LCBidXQgaXQncyBkaWZmaWN1bHQgdG8gcmVhZC4KCmBgYHtyfQpwYWlycyhBdXRvKQpgYGAKCkJ1dCwgd2UgY2FuIGRvIG11Y2ggYmV0dGVyIHRoYW4gdGhhdCB1c2luZyBbZ2dwYWlyc10oaHR0cDovL3ZpdGEuaGFkLmNvLm56L3BhcGVycy9ncHAucGRmKS4gSGVyZSB3ZSBjYW4gbGVhcm4gbXVjaCBtb3JlIGFib3V0IG91ciBkYXRhc2V0LgoKYGBge3IgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9N30KQXV0b19wcm9wZXIgPC0gCiAgICBBdXRvICU+JSB0YmxfZGYgJT4lCiAgICBtdXRhdGUoCiAgICAgICAgICBjeWxpbmRlcnMgPSBhcy5mYWN0b3IoY3lsaW5kZXJzKQogICAgICAgICwgeWVhciA9IGFzLmludGVnZXIocGFzdGUwKCIxOSIsIHllYXIpKQogICAgICAgICwgeWVhcjIgPSBhcy5mYWN0b3IocGFzdGUwKCIxOSIsIHllYXIpKQogICAgICAgICwgb3JpZ2luID0gZmFjdG9yKG9yaWdpbiwgbGFiZWxzID0gYygnQW1lcmljYW4nLCAnRXVyb3BlYW4nLCAnSmFwYW5lc2UnKSkKICAgICAgICAsIG5hbWUgPSBhcy5jaGFyYWN0ZXIobmFtZSkKICAgICkKCmdncGFpcnMoQXV0b19wcm9wZXIgJT4lIHNlbGVjdCgtbmFtZSwgLXllYXIyKSkgKyB0aGVtZV9qcmYoKQpgYGAKCkZyb20gdGhpcyBwbG90IGFsb25lLCB3ZSBjYW4gZ2xlYW4gYSBsb3QgaW5mb3JtYXRpb24gYWJvdXQgdGhlIEF1dG8gZGF0YXNldC4KCjEuIENhcnMgd2l0aCBmZXdlciBjeWxpbmRlcnMgZ2VuZXJhbGx5IGhhdmUgaGlnaGVyIE1QRwoyLiBUaGVyZSBhcmUgbmVnYXRpdmUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIGRpc3BsYWNlbWVudCwgaG9yc2Vwb3dlciwgYW5kIHdlaWdodCBhbmQgTVBHCjMuIEluIGdlbmVyYWwsIG5ld2VyIGNhcnMgaGF2ZSBiZXR0ZXIgTVBHCjQuIEFtZXJpY2FuIG1hZGUgY2FycyBoYXZlIG11Y2ggbG93ZXIgTVBHcyB0aGFuIEV1cm9wZWFuIG9yIEphcGFuZXNlIGNhcnMKNS4gTW9zdCBvZiB0aGUgY2FycyBpbiB0aGUgZGF0YXNldCBhcmUgZnJvbSBBbWVyaWNhCjYuIENhcnMgd2l0aCA2IGFuZCA4IGN5bGluZGVycyBhbG1vc3QgZXhjbHVzaXZlbHkgY29tZSBmcm9tIEFtZXJpY2EKNy4gRWFjaCB5ZWFyIGluIHRoZSByYW5nZSBvZiB0aGUgZGF0YXNldCBoYXMgYSBuZWFybHkgZXF1YWwgbnVtYmVyIG9mIGNhcnMKOC4gR2VuZXJhbGx5IGNhcnMgd2l0aCBmZXdlciBjeWxpbmRlcnMgYXJlIGxpZ2h0ZXIKCk1vcmUgcG9pbnRzIGNhbiBiZSBtYWRlIGJ1dCB0aGlzIGlzIGEgc3Ryb25nIHN0YXJ0aW5nIHBvaW50LiAKCkJlZm9yZSBnb2luZyBtdWNoIGZ1cnRoZXIsIGxldCdzIGNoZWNrIHRvIGVuc3VyZSB3ZSBoYXZlIG5vIG1pc3NpbmcgZGF0YS4KCmBgYHtyfQojIENvbXBsZXRlLmNhc2VzIHNob3dzIHRoZXJlIGlzIG5vIG1pc3NpbmcgdmFsdWVzCnN1bSghY29tcGxldGUuY2FzZXMoQXV0b19wcm9wZXIpKQpgYGAKCldlIGNhbiBkbyBzb21lIHN1bW1hcnkgc3RhdGlzdGljcyB0byBnZXQgYSBmZWVsIG9mIHRoZSBib3VuZHMgb2YgdGhlIHZhcmlhYmxlcwoKYGBge3J9CnNhcHBseShBdXRvX3Byb3Blciwgc3VtbWFyeSkKYGBgCgojIyBZZWFyCgojIyMgTVBHIHZzIFllYXIKCmBgYHtyfQphdXRvX2ZpdDEgPC0gbG0obXBnIH4geWVhciwgZGF0YSA9IEF1dG9fcHJvcGVyKQpzdW1tYXJ5KGF1dG9fZml0MSkKYGBgCgpXZSBmaW5kIHRoYXQgbW9kZWwgeWVhciBpcyBhIHNpZ25pZmljYW50IHZhcmlhYmxlIGF0IHRoZSAwLjA1IGxldmVsLiBXZSBoYXZlIHN0cm9uZyBldmlkZW5jZSBhZ2FpbnN0IHRoZSBoeXBvdGhlc2lzIHRoYXQgdGhlIGNvZWZmaWNpZW50IGFzc29jaWF0ZWQgd2l0aCB5ZWFyIGlzIGVxdWFsIHRvIDAgKCpQLXZhbHVlID0gYHIgZm9ybWF0KGNvZWYoc3VtbWFyeShhdXRvX2ZpdDEpKVssIDRdWzJdLCBzY2llbnRpZmljID0gVFJVRSlgKikuCgpXZSBlc3RpbWF0ZSB0aGF0IGZvciBlYWNoIGFkZGl0aW9uYWwgeWVhciAoY2FyIGJlaW5nIG5ld2VyKSBhIGNhcnMgTVBHIGluY3JlYXNlcyBieSAqKmByIGNvZWYoc3VtbWFyeShhdXRvX2ZpdDEpKVssMV1bWzJdXWAqKi4gRm9yIGV4YW1wbGUsIGZvciBhIGNhciB3aXRoIG1vZGVsIHllYXIgMTk4MCB3ZSBlc3RpbWF0ZSBgciBwcmVkaWN0KGF1dG9fZml0MSwgZGF0YV9mcmFtZSh5ZWFyID0gMTk4MCkpYCBtcGcgYW5kIGEgY2FyIHdpdGggbW9kZWwgeWVhciAxOTgxIHdlIGVzdGltYXRlIGByIHByZWRpY3QoYXV0b19maXQxLCBkYXRhX2ZyYW1lKHllYXIgPSAxOTgxKSlgLiBUaGUgZGlmZmVyZW5jZSBhIHllYXIgbWFrZXMgaW4gdGhlIGVzdGltYXRlIGlzICRgciBwcmVkaWN0KGF1dG9fZml0MSwgZGF0YV9mcmFtZSh5ZWFyID0gMTk4MSkpYCAtIGByIHByZWRpY3QoYXV0b19maXQxLCBkYXRhX2ZyYW1lKHllYXIgPSAxOTgwKSlgPSBgciBwcmVkaWN0KGF1dG9fZml0MSwgZGF0YV9mcmFtZSh5ZWFyID0gMTk4MSkpIC0gcHJlZGljdChhdXRvX2ZpdDEsIGRhdGFfZnJhbWUoeWVhciA9IDE5ODEpKWAkLgoKIyMjIEFkZCBIb3JzZXBvd2VyCgpgYGB7cn0KYXV0b19maXQyIDwtIGxtKG1wZyB+IGhvcnNlcG93ZXIgKyB5ZWFyLCBkYXRhID0gQXV0b19wcm9wZXIpCnN1bW1hcnkoYXV0b19maXQyKQpgYGAKClllYXIgaXMgc3RpbGwgc2lnbmlmaWNhbnQgYXQgdGhlIDAuMDUgbGV2ZWwuIFdlIGhhdmUgc3Ryb25nIGV2aWRlbmNlIGFnYWluc3QgdGhlIGh5cG90aGVzaXMgdGhhdCB0aGUgY29lZmZpY2llbnQgYXNzb2NpYXRlZCB3aXRoIHllYXIgaXMgZXF1YWwgdG8gMCAoKlAtdmFsdWUgPSBgciBmb3JtYXQoY29lZihzdW1tYXJ5KGF1dG9fZml0MikpWywgNF1bM10sIHNjaWVudGlmaWMgPSBUUlVFKWAqKS4KCkZvciBjYXJzIHdpdGggdGhlIHNhbWUgaG9yc2Vwb3dlciwgd2UgZXN0aW1hdGUgdGhhdCBlYWNoIGFkZGl0aW9uYWwgeWVhciAoY2FyIGJlaW5nIG5ld2VyKSBhIGNhcnMgTVBHIGluY3JlYXNlcyBieSAgKipgciBjb2VmKHN1bW1hcnkoYXV0b19maXQyKSlbLDFdW1szXV1gKiouIAoKV2Ugc2hvdyB0aGUgdHdvIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIGZvciB0aGUgY29lZmZpY2llbnQgb2YgeWVhciBiZXR3ZWVuIHRoZSB0d28gbW9kZWxzLgoKYGBge3J9CmNvbmZpbnQoYXV0b19maXQxLCAieWVhciIsIGxldmVsID0gMC45NSkKY29uZmludChhdXRvX2ZpdDIsICJ5ZWFyIiwgbGV2ZWwgPSAwLjk1KQpgYGAKClRoZXNlIHR3byBjb25maWRlbmNlIGludGVydmFscyBhcmUgZGlmZmVyZW50LiBUbyBhIG5vbi1zdGF0aXN0aWNpYW4sIHdlIHdvdWxkIGRlc2NyaWJlIHRoaXMgZGlmZmVyZW5jZSBhcwoKPkluIG91ciBmaXJzdCBtb2RlbCB0byBwcmVkaWN0IE1QRywgd2Ugb25seSB1c2UgdGhlIG1vZGVsIHllYXIgb2YgdGhlIGNhci4gSW4gb3VyIHNlY29uZCBtb2RlbCwgd2UgaW5jbHVkZSBob3JzZXBvd2VyIHdoaWNoIGV4cGxhaW5zIHBhcnQgb2YgdGhlIHZhcmlhdGlvbiBpbiBNUEcgYmV0d2VlbiBjYXJzLiBJbiBvdGhlciB3b3JkcywgdGhlIGVmZmVjdCBvZiB0aGUgbW9kZWwgeWVhciBvbiBNUEcgaXMgc21hbGxlciB3aGVuIHdlIGluY2x1ZGUgdGhlIHZhcmlhdGlvbiBleHBsYWluZWQgYnkgaG9yc2Vwb3dlci4gICAgCgojIyMgSW50ZXJhY3Rpb24gVGVybQoKYGBge3J9CmF1dG9fZml0MyA8LSBsbShtcGcgfiBob3JzZXBvd2VyICogeWVhciwgZGF0YSA9IEF1dG9fcHJvcGVyKQpzdW1tYXJ5KGF1dG9fZml0MykKYGBgCgpUaGUgaW50ZXJhY3Rpb24gdGVybSBpcyBzaWduaWZpY2FudCBhdCB0aGUgMC4wNSBsZXZlbC4gV2UgaGF2ZSBzdHJvbmcgZXZpZGVuY2UgYWdhaW5zdCB0aGUgaHlwb3RoZXNpcyB0aGF0IHRoZSBjb2VmZmljaWVudCBhc3NvY2lhdGVkIHdpdGggdGhlIGludGVyYWN0aW9uIG9mIHllYXIgYW5kIGhvcnNlcG93ZXIgaXMgZXF1YWwgdG8gMCAoKlAtdmFsdWUgPSBgciBmb3JtYXQoY29lZihzdW1tYXJ5KGF1dG9fZml0MykpWywgNF1bNF0sIHNjaWVudGlmaWMgPSBUUlVFKWAqKQoKTm93IHRoZSBlZmZlY3Qgb2YgeWVhciBjYW5ub3QgYmUgaW50ZXJwcmV0ZWQgdW5pZm9ybWx5IHdoZW4gaG9sZGluZyB0aGUgb3RoZXIgdmFyaWFibGUgaG9yc2Vwb3dlciBjb25zdGFudCBhcyBpdCBkZXBlbmRzIG9uIHRoZSB2YWx1ZSBvZiBob3JzZXBvd2VyLiBUaHVzLCB3ZSBzaG93IHRoZSBlZmZlY3Qgb2YgYSAxIHllYXIgaW5jcmVhc2UgaW4gbW9kZWwgeWVhciAob25lIHllYXIgbmV3ZXIpLCBvbiB0aGUgMjV0aCBwZXJjZW50aWxlLCBtZWRpYW4sIGFuZCA3NXRoIHBlcmNlbnRpbGUgaG9yc2Vwb3dlciB2YWx1ZXMgaW4gb3VyIGRhdGFzZXQuCgp8IHxIb3JzZXBvd2VyID0gYHIgcXVhbnRpbGUoQXV0b19wcm9wZXIkaG9yc2Vwb3dlciwgLjI1KWB8SG9yc2Vwb3dlciA9IGByIHF1YW50aWxlKEF1dG9fcHJvcGVyJGhvcnNlcG93ZXIsIC41KWB8SG9yc2Vwb3dlciA9IGByIHF1YW50aWxlKEF1dG9fcHJvcGVyJGhvcnNlcG93ZXIsIC43NSlgfAp8LS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLXwKfEVmZmVjdCBvZiAxIHllYXIgaW5jcmVhc2UgaW4gbW9kZWwgeWVhcnxgciBwcmVkaWN0KGF1dG9fZml0MywgZGF0YV9mcmFtZSh5ZWFyID0gMTk4MSwgaG9yc2Vwb3dlciA9IHF1YW50aWxlKEF1dG9fcHJvcGVyJGhvcnNlcG93ZXIsIC4yNSkpKSAtIHByZWRpY3QoYXV0b19maXQzLCBkYXRhX2ZyYW1lKHllYXIgPSAxOTgwLCBob3JzZXBvd2VyID0gcXVhbnRpbGUoQXV0b19wcm9wZXIkaG9yc2Vwb3dlciwgLjI1KSkpYHxgciBwcmVkaWN0KGF1dG9fZml0MywgZGF0YV9mcmFtZSh5ZWFyID0gMTk4MSwgaG9yc2Vwb3dlciA9IHF1YW50aWxlKEF1dG9fcHJvcGVyJGhvcnNlcG93ZXIsIC41KSkpIC0gcHJlZGljdChhdXRvX2ZpdDMsIGRhdGFfZnJhbWUoeWVhciA9IDE5ODAsIGhvcnNlcG93ZXIgPSBxdWFudGlsZShBdXRvX3Byb3BlciRob3JzZXBvd2VyLCAuNSkpKWB8YHIgcHJlZGljdChhdXRvX2ZpdDMsIGRhdGFfZnJhbWUoeWVhciA9IDE5ODEsIGhvcnNlcG93ZXIgPSBxdWFudGlsZShBdXRvX3Byb3BlciRob3JzZXBvd2VyLCAuNzUpKSkgLSBwcmVkaWN0KGF1dG9fZml0MywgZGF0YV9mcmFtZSh5ZWFyID0gMTk4MCwgaG9yc2Vwb3dlciA9IHF1YW50aWxlKEF1dG9fcHJvcGVyJGhvcnNlcG93ZXIsIC43NSkpKWB8CgoKIyMgQ3lsaW5kZXIKCldlIGhhdmUgdGhlIGN5bGluZGVyIHZhcmlhYmxlIGNvZGVkIGEgY2F0ZWdvcmljYWwgdmFyaWFibGUgYmVjYXVzZSB0aGUgbnVtYmVyIG9mIGN5bGluZGVycyBpcyBhIGNoYXJhY3RlcmlzdGljIG9mIHRoZSBjYXIsIHJhdGhlciB0aGFuIGEgZmVhdHVyZSB0aGF0IGNhbiBiZSBlYXNpbHkgY2hhbmdlZC4gSW4gb3RoZXIgd29yZHMsIGEgMSB1bml0IGNoYW5nZSBpbiBjeWxpbmRlciBpcyByZWFsbHkgbm90IG1lYW5pbmdmdWwgYXMgbW9zdCBlbmdpbmVzIGFyZSBtYWRlIHdpdGggY3lsaW5kZXJzIHdpdGggbXVsdGlwbGVzIG9mIDIuCgpgYGB7cn0KQXV0b19wcm9wZXIgJT4lCiAgICBmaWx0ZXIoIShjeWxpbmRlcnMgJWluJSBjKCIzIiwiNSIpKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBjeWxpbmRlcnMsIHkgPSBtcGcsIGZpbGwgPSBjeWxpbmRlcnMpKSArIAogICAgc2NhbGVfZmlsbF9tYW51YWwoIkN5bGluZGVycyIsIHZhbHVlcyA9IGMoJzQnID0gcGFsNTM4WydibHVlJ11bWzFdXSwgJzYnID0gcGFsNTM4WydncmVlbiddW1sxXV0sICc4JyA9IHBhbDUzOFsncmVkJ11bWzFdXSkpICsKICAgIGdlb21fYm94cGxvdCgpICsgCiAgICB0aGVtZV9qcmYoKSArCiAgICBsYWJzKHRpdGxlID0gIk1QRyB2cyBDeWxpbmRlcnMiLCB4ID0gIkN5bGluZGVycyIsIHkgID0gIk1QRyIpCgpgYGAKCiMjIyBBcyBRdWFudGl0YXRpdmUgVmFyaWFibGUKClBlciB0aGUgcXVlc3Rpb24sIHdlIHdpbGwgdXNlIGN5bGluZGVycyBhcyBhIGludGVnZXIgKG5vdCBjb250aW51b3VzKS4KCmBgYHtyfQphdXRvX2ZpdDQgPC0gbG0obXBnIH4gaG9yc2Vwb3dlciArIGFzLmludGVnZXIoY3lsaW5kZXJzKSwgZGF0YSA9IEF1dG9fcHJvcGVyKQpzdW1tYXJ5KGF1dG9fZml0NCkKYGBgCgpDeWxpbmRlcnMgaXMgc2lnbmlmaWNhbnQgYXQgdGhlIDAuMDEgbGV2ZWwuIFdlIGhhdmUgc3Ryb25nIGV2aWRlbmNlIGFnYWluc3QgdGhlIGh5cG90aGVzaXMgdGhhdCB0aGUgY29lZmZpY2llbnQgYXNzb2NpYXRlZCB3aXRoIGN5bGluZGVycyBpcyBlcXVhbCB0byAwICgqUC12YWx1ZSA9IGByIGZvcm1hdChjb2VmKHN1bW1hcnkoYXV0b19maXQ0KSlbLCA0XVszXSwgc2NpZW50aWZpYyA9IFRSVUUpYCopLgoKV2UgZXN0aW1hdGUgdGhhdCwgaG9sZGluZyBob3JzZXBvd2VyIGNvbnN0YW50LCBmb3IgZWFjaCBhZGRpdGlvbmFsIGN5bGluZGVyIGluIHRoZSBjYXIsIHRoZSBjYXIncyBtcGcgaXMgYHIgY29lZihzdW1tYXJ5KGF1dG9fZml0NCkpWywxXVtbM11dYCBsb3dlci4KCiMjIyBBcyBDYXRlZ29yaWNhbCBWYXJpYWJsZQoKYGBge3J9CmF1dG9fZml0NSA8LSBsbShtcGcgfiBob3JzZXBvd2VyICsgY3lsaW5kZXJzLCBkYXRhID0gQXV0b19wcm9wZXIpCnN1bW1hcnkoYXV0b19maXQ1KQpgYGAKCkN5bGluZGVycyBpcyBzaWduaWZpY2FudCBhdCB0aGUgMC4wMSBsZXZlbC4gSWYgb25lIG9mIHRoZSBjb2VmZmljaWVudHMgb2YgdGhlIGZhY3RvciBsZXZlbHMgaW4gdGhlIG1vZGVsIGlzIHNpZ25pZmljYW50LCB0aGVuIHRoZSB2YXJpYWJsZSBhcyB3aG9sZSBpcyBzaWduaWZpY2FudC4gV2UgdGhlbiB1c2UgQU5PVkEgdG8gY29tcGFyZSB0aGUgdHdvIG1vZGVscy4KCmBgYHtyfQphbm92YShhdXRvX2ZpdDQsIGF1dG9fZml0NSkKYGBgCgpXZSBoYXZlIHN0cm9uZyBldmlkZW5jZSB0aGF0IHRoZSBtb2RlbCB1c2luZyBjYXRlZ29yaWNhbCB2YXJpYWJsZSBmb3IgY3lsaW5kZXIgYmV0dGVyIGV4cGxhaW5zIHRoZSB2YXJpYXRpb24gaW4gTVBHIHRoYW4gdGhlIG1vZGVsIHRoYXQgdXNlcyBhIHF1YW50aXRhdGl2ZSB2YXJpYWJsZSBmb3IgY3lsaW5kZXIgKCpQLXZhbHVlID0gYHIgZm9ybWF0KGFub3ZhKGF1dG9fZml0NCwgYXV0b19maXQ1KVssIDZdW1syXV0sIHNjaWVudGlmaWMgPSBUUlVFKWAqKQoKIyMjIERpZmZlcmVuY2UKClRoZSBmdW5kYW1lbnRhbCBkaWZmZXJlbmNlIGJldHdlZW4gbW9kZWwgMSBhbmQgbW9kZWwgMiBpcyB0aGF0IG1vZGVsIDEgYXNzdW1lcyB0aGF0IHRoZXJlIGNhbiBiZSBpbmNyZW1lbnRhbCBpbmNyZWFzZSBpbiBjeWxpbmRlcnMgd2hlcmVhcyBtb2RlbCAyIGFzc3VtZXMgdGhhdCB0aGVyZSBhcmUgZGlmZmVyZW50IHR5cGVzIG9mIGN5bGluZGVycy4gSW4gcmVhbGl0eSwgeW91IGNhbm5vdCBpbmNyZWFzZSBhIGNhcnMgY3lsaW5kZXJzIGJ5IDAuMjUgc28gbW9kZWwgMSBpcyBub3QgcHJhY3RpY2FsbHkgdmFsaWQuIE1vZGVsIDIgcmVjb2duaXplcyB0aGUgbmF0dXJlIG9mIHRoZSB2YXJpYWJsZSBjeWxpbmRlciBhbmQgaG93IGNhcnMgYXJlIG1hZGUsIHRodXMgYmVpbmcgYSBwcmFjdGljYWxseSBhcHBsaWNhYmxlIG1vZGVsLgoKVGhlIHBsb3RzIGJlbG93IHByb3ZpZGUgYSBjb21wYXJpc29uIGJldHdlZW4gdGhlIHR3byBtb2RlbHMuCgpgYGB7ciBmaWcud2lkdGggPSA4LCBmaWcuaGVpZ2h0ID0gNH0KY3lsaW5kZXJzIDwtIHNlcShmcm9tID0gMiwgdG8gPSA4LCBieSA9IDAuMjUpCmludF9saW5lIDwtIGRhdGFfZnJhbWUoCiAgICAgICAgICAgICAgICAgIGN5bGluZGVycyA9IGN5bGluZGVycwogICAgICAgICAgICAgICAgLCBpbnQgPSBjb2VmKHN1bW1hcnkoYXV0b19maXQ0KSlbLDFdW1sxXV0gKyBjb2VmKHN1bW1hcnkoYXV0b19maXQ0KSlbLDFdW1szXV0qY3lsaW5kZXJzCiAgICAgICAgICAgICAgICAsIHNsb3BlID0gY29lZihzdW1tYXJ5KGF1dG9fZml0NCkpWywxXVtbMl1dCiAgICAgICAgICAgICAgICApCgpnMSA8LSAKICAgIGdncGxvdChBdXRvX3Byb3BlciwgYWVzKHggPSBob3JzZXBvd2VyLCB5ID0gbXBnKSkgKyAKICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsgCiAgICBnZW9tX2FibGluZShkYXRhID0gaW50X2xpbmUsIGFlcyhpbnRlcmNlcHQgPSBpbnQsIHNsb3BlID0gc2xvcGUsIGNvbG91ciA9IGFzLmZhY3RvcihjeWxpbmRlcnMpKSkgKwogICAgdGhlbWVfanJmKCkgKwogICAgbGFicyh0aXRsZSA9ICJNb2RlbCAxOiBRdWFudGF0aXZlIEN5bGluZGVycyIsIHggPSAiSG9yc2Vwb3dlciIsIHkgID0gIk1QRyIpICsKICAgIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQodGl0bGUgPSAiQ3lsaW5kZXJzIChzYW1wbGUpIikpCiAgICAKZzIgPC0KICAgIGdncGxvdChBdXRvX3Byb3BlciwgYWVzKHggPSBob3JzZXBvd2VyLCB5ID0gbXBnLCBjb2xvdXIgPSBjeWxpbmRlcnMpKSArIAogICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKyAKICAgIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIHNlID0gRkFMU0UpICsKICAgIHRoZW1lX2pyZigpICsKICAgIGxhYnModGl0bGUgPSAiTW9kZWwgMjogQ2F0ZWdvcmljYWwgQ3lsaW5kZXJzIiwgeCA9ICJIb3JzZXBvd2VyIiwgeSAgPSAiTVBHIikgKwogICAgc2NhbGVfY29sb3VyX21hbnVhbCgiQ3lsaW5kZXJzIiwgdmFsdWVzID0gYygnMycgPSAiI2ZmZmYwMCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnNCcgPSBwYWw1MzhbJ2JsdWUnXVtbMV1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc1JyA9IHBhbDUzOFsnZGtncmF5J11bWzFdXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc2JyA9IHBhbDUzOFsnZ3JlZW4nXVtbMV1dLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc4JyA9IHBhbDUzOFsncmVkJ11bWzFdXSkpCgoKZ3JpZC5hcnJhbmdlKGcxLCBnMiwgbmNvbCA9IDIpCmBgYCAKCiMjIEZpbmFsIE1vZGVsCgpGaXJzdCB3ZSBtYWtlIGEgZGF0YWZyYW1lIG9mIHRoZSBjYXIgdGhhdCB3ZSB3aWxsIHByZWRpY3QgTVBHLgoKYGBge3J9CmZ1dHVyZV9jYXIgPC0gZGF0YV9mcmFtZSgKICAgICAgIHllYXIgPSAxOTgzCiAgICAgLCBsZW5ndGggPSAxODAgCiAgICAgLCBjeWxpbmRlcnMgPSBmYWN0b3IoOCwgbGV2ZWxzID0gYygzLDQsNSw2LDgpKQogICAgICwgZGlzcGxhY2VtZW50ID0gMzUwIAogICAgICwgaG9yc2Vwb3dlciA9IDI2MAogICAgICwgd2VpZ2h0ID0gNDAwMAogICAgICkKYGBgCgpSZXZpZXdpbmcgdGhlIGRpYWdvbmFsIGZyb20gdGhlIHBhaXJzIHBsb3QgaW4gdGhlIFtleHBsb3JhdG9yeSBhbmFseXNpc10oIzUxX2V4cGxvcmF0b3J5X2FuYWx5c2lzKSwgd2Ugbm90ZSB0aGF0IHRoZSBjb250aW51b3VzIHByZWRpY3RvcnMgYW5kIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgTVBHIGFyZSBhbGwgc29tZXdoYXQgbm9ybWFsbHkgZGlzdHJpYnV0ZSBhbmQgd2UgZGVjaWRlIG5vdCB0byBwZXJmb3JtIGFueSB0cmFuc2Zvcm1hdGlvbnMuIAoKV2Ugd2lsbCB1c2UgdGhlIGBsZWFwc2AgcGFja2FnZSB1c2luZyBlYWNoIG9mIHRoZSAzIG1ldGhvZHMuIFdpdGggZWFjaCB3ZSB3aWxsOgoKMS4gU2hvdyB0aGUgZmVhdHVyZSBjb21iaW5hdGlvbnMgZm9yIGVhY2ggdmFsdWUgb2YgKmQqIChudW1iZXIgb2YgcHJlZGljdG9ycykKMi4gUGxvdCBNYWxsb3cncyBDcCwgQklDLCBhbmQgQWRqdXN0ZWQgJFJeMiQuIAoKIyMjIEV4aGF1c3RpdmUKCmBgYHtyfQphdXRvX2ZpdDYgPC0gcmVnc3Vic2V0cyhtcGcgfiAuLCBkYXRhID0gQXV0b19wcm9wZXIgJT4lIHNlbGVjdCgtbmFtZSwgLXllYXIyKSwgbnZtYXggPSAxMSwgbWV0aG9kPSJleGhhdXN0aXZlIikKYXV0b19maXQ2X3N1bSA8LSBzdW1tYXJ5KGF1dG9fZml0NikKYXNfZGF0YV9mcmFtZShhdXRvX2ZpdDZfc3VtJG91dG1hdCkgJT4lIHByaW50KHdpZHRoID0gSW5mKQpgYGAKCmBgYHtyfQpkYXRhX2ZyYW1lKAogICAgICBwcmVkaWN0b3JzID0gMTpsZW5ndGgoYXV0b19maXQ2X3N1bSRjcCkKICAgICwgY3AgPSBhdXRvX2ZpdDZfc3VtJGNwCiAgICAsIGJpYyA9IGF1dG9fZml0Nl9zdW0kYmljCiAgICAsIGFkanIyID0gYXV0b19maXQ2X3N1bSRhZGpyMgopICU+JQogICAgZ2F0aGVyKG1ldHJpYywgdmFsdWUsIC1wcmVkaWN0b3JzKSAlPiUKICAgIG11dGF0ZShtZXRyaWMgPSBmYWN0b3IobWV0cmljLCBsZXZlbHMgPSBjKCJjcCIsImJpYyIsImFkanIyIikpKSAlPiUKICAgIGdncGxvdChhZXMoeCA9IHByZWRpY3RvcnMsIHkgPSB2YWx1ZSwgY29sb3VyID0gbWV0cmljKSkgKwogICAgZmFjZXRfZ3JpZChtZXRyaWMgfiAuLCBzY2FsZSA9ICJmcmVlX3kiLCBzd2l0Y2ggPSAieSIsIAogICAgICAgICAgICAgICBsYWJlbGxlciA9IGdncGxvdDI6OmxhYmVsbGVyKG1ldHJpYyA9IGMoY3AgPSAiQ3AiLCBiaWMgPSAiQklDIiwgYWRqcjIgPSAiQWRqdXN0ZWQgUl4yIikpKSArCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAzLCBhbHBoYSA9IDAuNSkgKyBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX2xhYmVsKGRhdGEgPSBkYXRhX2ZyYW1lKAogICAgICAgIHByZWRpY3RvcnMgPSBjKHdoaWNoLm1pbihhdXRvX2ZpdDZfc3VtJGNwKSwgd2hpY2gubWluKGF1dG9fZml0Nl9zdW0kYmljKSwgd2hpY2gubWF4KGF1dG9fZml0Nl9zdW0kYWRqcjIpKQogICAgICAgICwgbWV0cmljID0gZmFjdG9yKGMoImNwIiwiYmljIiwiYWRqcjIiKSwgbGV2ZWxzID0gYygiY3AiLCJiaWMiLCJhZGpyMiIpKQogICAgICAgICwgdmFsdWUgPSBjKG1pbihhdXRvX2ZpdDZfc3VtJGNwKSwgbWluKGF1dG9fZml0Nl9zdW0kYmljKSwgbWF4KGF1dG9fZml0Nl9zdW0kYWRqcjIpKQogICAgICAgICwgbGFiZWwgPSBwYXN0ZTAoIk9wdGltYWxcbmQ9IiwgYyh3aGljaC5taW4oYXV0b19maXQ2X3N1bSRjcCksIHdoaWNoLm1pbihhdXRvX2ZpdDZfc3VtJGJpYyksIHdoaWNoLm1heChhdXRvX2ZpdDZfc3VtJGFkanIyKSkpCiAgICAgICAgLCB2anVzdCA9IGMoLS41LCAtLjUsIDEuMjUpCiAgICApLCBhZXMoeCA9IHByZWRpY3RvcnMsIHkgPSB2YWx1ZSwgbGFiZWwgPSBsYWJlbCwgdmp1c3QgPSB2anVzdCksIGZhbWlseSA9ICJEZWNpbWFNb25vUHJvIikgKwogICAgdGhlbWVfanJmKCkgKyAKICAgIGxhYnModGl0bGUgPSAiRXhoYXVzdGl2ZSBTZWFyY2giLCB4ID0gIiMgb2YgUHJlZGljdG9ycyIsIHkgPSBOVUxMKSArCiAgICBnZW9tX2xhYmVsKGRhdGEgPSBkYXRhX2ZyYW1lKHggPSAzLCB5ID0gMzAwLCBtZXRyaWMgPSBmYWN0b3IoYygiY3AiKSwgbGV2ZWxzID0gYygiY3AiLCJiaWMiLCJhZGpyMiIpKSwgCiAgICAgICAgICAgICAgICBsYWJlbCA9ICJFbGJvdyB3aXRoXG4zIHByZWRpY3RvcnMiKSwgYWVzKHg9eCx5PXksbGFiZWw9bGFiZWwpLCBjb2xvdXIgPSAiYmxhY2siLCBoanVzdCA9IC0uMSwKICAgICAgICAgICAgICAgZmFtaWx5ID0gIkRlY2ltYU1vbm9Qcm8iKSArIAogICAgc2NhbGVfY29sb3VyX21hbnVhbChndWlkZSA9IEZBTFNFLCB2YWx1ZXMgPSBjKHBhbDUzOFsncmVkJ11bWzFdXSwgcGFsNTM4WydncmVlbiddW1sxXV0sIHBhbDUzOFsnYmx1ZSddW1sxXV0pKQpgYGAKCiMjIyBGb3J3YXJkCgpgYGB7cn0KYXV0b19maXQ3IDwtIHJlZ3N1YnNldHMobXBnIH4gLiwgZGF0YSA9IEF1dG9fcHJvcGVyICU+JSBzZWxlY3QoLW5hbWUsIC15ZWFyMiksIG52bWF4ID0gMTEsIG1ldGhvZD0iZm9yd2FyZCIpCmF1dG9fZml0N19zdW0gPC0gc3VtbWFyeShhdXRvX2ZpdDcpCmFzX2RhdGFfZnJhbWUoYXV0b19maXQ3X3N1bSRvdXRtYXQpICU+JSBwcmludCh3aWR0aCA9IEluZikKYGBgCgpgYGB7cn0KZGF0YV9mcmFtZSgKICAgICAgcHJlZGljdG9ycyA9IDE6bGVuZ3RoKGF1dG9fZml0N19zdW0kY3ApCiAgICAsIGNwID0gYXV0b19maXQ3X3N1bSRjcAogICAgLCBiaWMgPSBhdXRvX2ZpdDdfc3VtJGJpYwogICAgLCBhZGpyMiA9IGF1dG9fZml0N19zdW0kYWRqcjIKKSAlPiUKICAgIGdhdGhlcihtZXRyaWMsIHZhbHVlLCAtcHJlZGljdG9ycykgJT4lCiAgICBtdXRhdGUobWV0cmljID0gZmFjdG9yKG1ldHJpYywgbGV2ZWxzID0gYygiY3AiLCJiaWMiLCJhZGpyMiIpKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBwcmVkaWN0b3JzLCB5ID0gdmFsdWUsIGNvbG91ciA9IG1ldHJpYykpICsKICAgIGZhY2V0X2dyaWQobWV0cmljIH4gLiwgc2NhbGUgPSAiZnJlZV95Iiwgc3dpdGNoID0gInkiLCAKICAgICAgICAgICAgICAgbGFiZWxsZXIgPSBnZ3Bsb3QyOjpsYWJlbGxlcihtZXRyaWMgPSBjKGNwID0gIkNwIiwgYmljID0gIkJJQyIsIGFkanIyID0gIkFkanVzdGVkIFJeMiIpKSkgKwogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMywgYWxwaGEgPSAwLjUpICsgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KCkgKwogICAgZ2VvbV9sYWJlbChkYXRhID0gZGF0YV9mcmFtZSgKICAgICAgICBwcmVkaWN0b3JzID0gYyh3aGljaC5taW4oYXV0b19maXQ3X3N1bSRjcCksIHdoaWNoLm1pbihhdXRvX2ZpdDdfc3VtJGJpYyksIHdoaWNoLm1heChhdXRvX2ZpdDdfc3VtJGFkanIyKSkKICAgICAgICAsIG1ldHJpYyA9IGZhY3RvcihjKCJjcCIsImJpYyIsImFkanIyIiksIGxldmVscyA9IGMoImNwIiwiYmljIiwiYWRqcjIiKSkKICAgICAgICAsIHZhbHVlID0gYyhtaW4oYXV0b19maXQ3X3N1bSRjcCksIG1pbihhdXRvX2ZpdDdfc3VtJGJpYyksIG1heChhdXRvX2ZpdDdfc3VtJGFkanIyKSkKICAgICAgICAsIGxhYmVsID0gcGFzdGUwKCJPcHRpbWFsXG5kPSIsIGMod2hpY2gubWluKGF1dG9fZml0N19zdW0kY3ApLCB3aGljaC5taW4oYXV0b19maXQ3X3N1bSRiaWMpICx3aGljaC5tYXgoYXV0b19maXQ3X3N1bSRhZGpyMikpKQogICAgICAgICwgdmp1c3QgPSBjKC0uNSwgLS41LCAxLjI1KQogICAgKSwgYWVzKHggPSBwcmVkaWN0b3JzLCB5ID0gdmFsdWUsIGxhYmVsID0gbGFiZWwsIHZqdXN0ID0gdmp1c3QpLCBmYW1pbHkgPSAiRGVjaW1hTW9ub1BybyIpICsKICAgIHRoZW1lX2pyZigpICsgCiAgICBsYWJzKHRpdGxlID0gIkZvcndhcmQgU2VhcmNoIiwgeCA9ICIjIG9mIFByZWRpY3RvcnMiLCB5ID0gTlVMTCkgKwogICAgZ2VvbV9sYWJlbChkYXRhID0gZGF0YV9mcmFtZSh4ID0gMywgeSA9IDMwMCwgbWV0cmljID0gZmFjdG9yKGMoImNwIiksIGxldmVscyA9IGMoImNwIiwiYmljIiwiYWRqcjIiKSksIAogICAgICAgICAgICAgICAgbGFiZWwgPSAiRWxib3cgd2l0aFxuMyBwcmVkaWN0b3JzIiksIGFlcyh4PXgseT15LGxhYmVsPWxhYmVsKSwgY29sb3VyID0gImJsYWNrIiwgaGp1c3QgPSAtLjEsCiAgICAgICAgICAgICAgIGZhbWlseSA9ICJEZWNpbWFNb25vUHJvIikgKyAKICAgIHNjYWxlX2NvbG91cl9tYW51YWwoZ3VpZGUgPSBGQUxTRSwgdmFsdWVzID0gYyhwYWw1MzhbJ3JlZCddW1sxXV0sIHBhbDUzOFsnZ3JlZW4nXVtbMV1dLCBwYWw1MzhbJ2JsdWUnXVtbMV1dKSkKYGBgCgojIyMgQmFja3dhcmQKCmBgYHtyLCByZXN1bHRzID0gJ2FzaXMnfQphdXRvX2ZpdDggPC0gcmVnc3Vic2V0cyhtcGcgfiAuLCBkYXRhID0gQXV0b19wcm9wZXIgJT4lIHNlbGVjdCgtbmFtZSwgLXllYXIyKSwgbnZtYXggPSAxMSwgbWV0aG9kPSJiYWNrd2FyZCIpCmF1dG9fZml0OF9zdW0gPC0gc3VtbWFyeShhdXRvX2ZpdDcpCmFzX2RhdGFfZnJhbWUoYXV0b19maXQ4X3N1bSRvdXRtYXQpICU+JSBwcmludCh3aWR0aCA9IEluZikKYGBgCgpgYGB7cn0KZGF0YV9mcmFtZSgKICAgICAgcHJlZGljdG9ycyA9IDE6bGVuZ3RoKGF1dG9fZml0OF9zdW0kY3ApCiAgICAsIGNwID0gYXV0b19maXQ4X3N1bSRjcAogICAgLCBiaWMgPSBhdXRvX2ZpdDhfc3VtJGJpYwogICAgLCBhZGpyMiA9IGF1dG9fZml0OF9zdW0kYWRqcjIKKSAlPiUKICAgIGdhdGhlcihtZXRyaWMsIHZhbHVlLCAtcHJlZGljdG9ycykgJT4lCiAgICBtdXRhdGUobWV0cmljID0gZmFjdG9yKG1ldHJpYywgbGV2ZWxzID0gYygiY3AiLCJiaWMiLCJhZGpyMiIpKSkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBwcmVkaWN0b3JzLCB5ID0gdmFsdWUsIGNvbG91ciA9IG1ldHJpYykpICsKICAgIGZhY2V0X2dyaWQobWV0cmljIH4gLiwgc2NhbGUgPSAiZnJlZV95Iiwgc3dpdGNoID0gInkiLCAKICAgICAgICAgICAgICAgbGFiZWxsZXIgPSBnZ3Bsb3QyOjpsYWJlbGxlcihtZXRyaWMgPSBjKGNwID0gIkNwIiwgYmljID0gIkJJQyIsIGFkanIyID0gIkFkanVzdGVkIFJeMiIpKSkgKwogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMywgYWxwaGEgPSAwLjUpICsgZ2VvbV9saW5lKCkgKyBnZW9tX3BvaW50KCkgKwogICAgZ2VvbV9sYWJlbChkYXRhID0gZGF0YV9mcmFtZSgKICAgICAgICBwcmVkaWN0b3JzID0gYyh3aGljaC5taW4oYXV0b19maXQ4X3N1bSRjcCksIHdoaWNoLm1pbihhdXRvX2ZpdDhfc3VtJGJpYyksIHdoaWNoLm1heChhdXRvX2ZpdDhfc3VtJGFkanIyKSkKICAgICAgICAsIG1ldHJpYyA9IGZhY3RvcihjKCJjcCIsImJpYyIsImFkanIyIiksIGxldmVscyA9IGMoImNwIiwiYmljIiwiYWRqcjIiKSkKICAgICAgICAsIHZhbHVlID0gYyhtaW4oYXV0b19maXQ4X3N1bSRjcCksIG1pbihhdXRvX2ZpdDhfc3VtJGJpYyksIG1heChhdXRvX2ZpdDhfc3VtJGFkanIyKSkKICAgICAgICAsIGxhYmVsID0gcGFzdGUwKCJPcHRpbWFsXG5kPSIsIGMod2hpY2gubWluKGF1dG9fZml0OF9zdW0kY3ApLCB3aGljaC5taW4oYXV0b19maXQ4X3N1bSRiaWMpLCB3aGljaC5tYXgoYXV0b19maXQ4X3N1bSRhZGpyMikpKQogICAgICAgICwgdmp1c3QgPSBjKC0uNSwgLS41LCAxLjI1KQogICAgKSwgYWVzKHggPSBwcmVkaWN0b3JzLCB5ID0gdmFsdWUsIGxhYmVsID0gbGFiZWwsIHZqdXN0ID0gdmp1c3QpLCBmYW1pbHkgPSAiRGVjaW1hTW9ub1BybyIpICsKICAgIHRoZW1lX2pyZigpICsgCiAgICBsYWJzKHRpdGxlID0gIkJhY2t3YXJkIFNlYXJjaCIsIHggPSAiIyBvZiBQcmVkaWN0b3JzIiwgeSA9IE5VTEwpICsKICAgIGdlb21fbGFiZWwoZGF0YSA9IGRhdGFfZnJhbWUoeCA9IDMsIHkgPSAzMDAsIG1ldHJpYyA9IGZhY3RvcihjKCJjcCIpLCBsZXZlbHMgPSBjKCJjcCIsImJpYyIsImFkanIyIikpLCAKICAgICAgICAgICAgICAgIGxhYmVsID0gIkVsYm93IHdpdGhcbjMgcHJlZGljdG9ycyIpLCBhZXMoeD14LHk9eSxsYWJlbD1sYWJlbCksIGNvbG91ciA9ICJibGFjayIsIGhqdXN0ID0gLS4xLAogICAgICAgICAgICAgICBmYW1pbHkgPSAiRGVjaW1hTW9ub1BybyIpICsgCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKGd1aWRlID0gRkFMU0UsIHZhbHVlcyA9IGMocGFsNTM4WydyZWQnXVtbMV1dLCBwYWw1MzhbJ2dyZWVuJ11bWzFdXSwgcGFsNTM4WydibHVlJ11bWzFdXSkpCmBgYAoKIyMjIFNlbGVjdGlvbgoKSW4gYWxsIDMgbWV0aG9kcywgd2UgZmluZCB0aGF0IHRoZXJlIGlzIGFuIGVsYm93IGluIHRoZSBpbmZvcm1hdGlvbiBjcml0ZXJpYSBhdCAzIHByZWRpY3RvcnMuIFRoZXNlIHRocmVlIHByZWRpY3RvcnMgYXJlCgoxLiBXZWlnaHQKMi4gWWVhcgozLiA2IEN5bGluZGVyIExldmVsIG9mIEN5bGluZGVycwoKUmVnYXJkaW5nICgzKSwgdGhpcyBpbmRpY2F0ZXMgdGhhdCB3ZSBtaWdodCB3YW50IHRvIHRyeSBjcmVhdGluZyBhIGJpbmFyeSB2YXJpYWJsZSwgd2hldGhlciBvciBub3QgdGhlIGNhciBpcyA2IGN5bGluZGVycy4gV2Ugd2lsbCBjcmVhdGUgNCBtb2RlbHMKCjEuIE1vZGVsIDE6IEN5bGluZGVycyAtIGFsbCBsZXZlbHMKMi4gTW9kZWwgMjogQmluYXJ5IDYtY3lsaW5kZXIKMy4gTW9kZWwgMzogQmluYXJ5IDYtY3lsaW5kZXIgJiBIb3JzZXBvd2VyCjQuIE1vZGVsIDQ6IEN5bGluZGVycyAtIGFsbCBsZXZlbHMgJiBIb3JzZXBvd2VyCgpNb2RlbCAxOiBDeWxpbmRlcnMgLSBhbGwgbGV2ZWxzCgpgYGB7cn0KQXV0b19wcm9wZXIyIDwtCiAgICBBdXRvX3Byb3BlciAlPiUKICAgIG11dGF0ZSgKICAgICAgICBpc182Y3lsaW5kZXIgPSBjeWxpbmRlcnMgPT0gNgogICAgKQphdXRvX2ZpdDkgPC0gbG0obXBnIH4gd2VpZ2h0ICsgeWVhciArIGN5bGluZGVycywgQXV0b19wcm9wZXIyKQpzdW1tYXJ5KGF1dG9fZml0OSkKYGBgCgpNb2RlbCAyOiBCaW5hcnkgNi1jeWxpbmRlcgoKYGBge3J9CmF1dG9fZml0MTAgPC0gbG0obXBnIH4gd2VpZ2h0ICsgeWVhciArIGlzXzZjeWxpbmRlciwgQXV0b19wcm9wZXIyKQpzdW1tYXJ5KGF1dG9fZml0MTApCmBgYAoKTW9kZWwgMzogQmluYXJ5IDYtY3lsaW5kZXIgJiBIb3JzZXBvd2VyCgpgYGB7cn0KYXV0b19maXQxMSA8LSBsbShtcGcgfiB3ZWlnaHQgKyB5ZWFyICsgaXNfNmN5bGluZGVyICsgaG9yc2Vwb3dlciwgQXV0b19wcm9wZXIyKQpzdW1tYXJ5KGF1dG9fZml0MTEpCmBgYAoKTW9kZWwgNDogQ3lsaW5kZXJzIC0gYWxsIGxldmVscyAmIEhvcnNlcG93ZXIKCmBgYHtyfQphdXRvX2ZpdDEyIDwtIGxtKG1wZyB+IHdlaWdodCArIHllYXIgKyBjeWxpbmRlcnMgKyBob3JzZXBvd2VyLCBBdXRvX3Byb3BlcjIpCnN1bW1hcnkoYXV0b19maXQxMikKYGBgCgpOb3cgdGhhdCB3ZSBoYXZlIDQgbW9kZWxzLCB3ZSBjYW4gY29tcGFyZSB0aGUgQUlDIChNYWxsb3cncyBDcCBpbiBsaW5lYXIgcmVncmVzc2lvbikgYW5kIEJJQy4gSWYgdGhlcmUgaXMgbm90IGEgc2lnbmlmaWNhbnQgZGlmZmVyZW5jZSwgd2Ugd2lsbCB1c2UgdGhlIHNpbXBsZXN0IG1vZGVsIChtb2RlbCAyKS4KCmBgYHtyfQpkYXRhX2ZyYW1lKAogICAgTW9kZWwgPSBjKCIxOiBDeWxpbmRlcnMgLSBhbGwgbGV2ZWxzIiwgIjI6IEJpbmFyeSA2LWN5bGluZGVyIiwKICAgICAgICAgICAgICAiMzogQmluYXJ5IDYtY3lsaW5kZXIgJiBIb3JzZXBvd2VyIiwiNDogQ3lsaW5kZXJzIC0gYWxsIGxldmVscyAmIEhvcnNlcG93ZXIiKQogICAgLCBBSUMgPSBBSUMoYXV0b19maXQ5LCBhdXRvX2ZpdDEwLCBhdXRvX2ZpdDExLCBhdXRvX2ZpdDEyKSRBSUMKICAgICwgQklDID0gQklDKGF1dG9fZml0OSwgYXV0b19maXQxMCwgYXV0b19maXQxMSwgYXV0b19maXQxMikkQklDCikgJT4lCiAgICBrYWJsZSgpCmBgYAoKTW9kZWwgMiBoYXMgaGlnaGVyIEFJQyBhbmQgQklDIHZhbHVlcyBjb21wYXJlZCB0byB0aGUgb3RoZXIgMyBtb2RlbHMsIGluZGljYXRpbmcgaXQgZXhwbGFpbnMgbGVzcyB2YXJpYXRpb24gaW4gTVBHLiBIb3dldmVyLCB0aGUgZGlmZmVyZW5jZSBpcyBub3QgbGFyZ2UgYW5kIGl0IGlzIHRoZSBzaW1wbGVzdCBtb2RlbC4gV2Ugd2lsbCB1c2UgbW9kZWwgMiBhcyBvdXIgZmluYWwgbW9kZWwuCgojIyMjIFF1YWRyYXRpYyBUZXJtCgpXZSBub3RpY2UgdGhhdCB3ZSBtaWdodCB3YW50IGEgcXVhZHJhdGljIHRlcm0gZm9yIHRoZSBwcmVkaWN0b3Igd2VpZ2h0IGJ5IGxvb2tpbmcgYXQgdGhlIGZvbGxvd2luZyBjaGFydHMuIFdlIG1heSBiZSBjb25jZXJuZWQgYWJvdXQgb3ZlcmZpdHRpbmcsIHBhcnRpY3VsYXJseSBmb3IgNi1jeWxpbmRlciBjYXJzIChpLmUuIDE5NzAgYW5kIDE5ODIpLiBIb3dldmVyLCB3ZSBrbm93IHRoYXQgd2Ugd2lsbCBiZSBwcmVkaWN0aW5nIHRoZSBNUEcgb2YgYSA4LWN5bGluZGVyIGNhci4KCmBgYHtyIHdhcm5pbmdzID0gRkFMU0UsIGZpZy5oZWlnaHQgPSA3fQpBdXRvX3Byb3BlcjIgJT4lCiAgICBzZWxlY3QobXBnLCB3ZWlnaHQsIHllYXIsIGN5bGluZGVycywgaXNfNmN5bGluZGVyKSAlPiUKICAgIGdncGxvdChhZXMoeCA9IHdlaWdodCwgeSA9IG1wZywgY29sb3VyID0gaXNfNmN5bGluZGVyKSkgKyAKICAgIGZhY2V0X3dyYXAofiB5ZWFyKSArCiAgICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgICB0aGVtZV9qcmYoYmFzZV9zaXplKSArCiAgICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBmb3JtdWxhID0geSB+IHggKyBJKHheMiksIHNlID0gRkFMU0UpICsKICAgIGxhYnModGl0bGUgPSAiRXZpZGVuY2UgZm9yIGFkZGluZyBRdWFkcmF0aWMgVGVybSBmb3IgV2VpZ2h0IiwgeCA9ICJXZWlnaHQiLCB5ID0gIk1QRyIpICsKICAgIHNjYWxlX2NvbG91cl9tYW51YWwoIklzIDYgQ3lsaW5kZXI/IiwgdmFsdWVzID0gYygnVFJVRScgPSBwYWw1MzhbJ2dyZWVuJ11bWzFdXSwgIkZBTFNFIiA9IHBhbDUzOFsncmVkJ11bWzFdXSkpICsKICAgIGd1aWRlcyhjb2xvdXIgPSBndWlkZV9sZWdlbmQocmV2ZXJzZSA9IFRSVUUpKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnYm90dG9tJykKYGBgCgpXZSBjcmVhdGUgYSBtb2RlbCB0byBhZGQgaW4gdGhlIHF1YWRyYXRpYyB0ZXJtIGZvciB3ZWlnaHQuIFdlIHNlZSB0aGF0IHRoZSBiaW5hcnkgdmFyaWFibGUgZm9yIHdoZXRoZXIgdGhlIGNhciBpcyBhIDYtY3lsaW5kZXIgaXMgbm93IG9ubHkgbWFyZ2luYWxseSBzaWduaWZpY2FudC4KCmBgYHtyfQphdXRvX2ZpdDEzIDwtIGxtKG1wZyB+IHdlaWdodCArIEkod2VpZ2h0XjIpICsgeWVhciArIGlzXzZjeWxpbmRlciwgQXV0b19wcm9wZXIyKQpzdW1tYXJ5KGF1dG9fZml0MTMpCmBgYAoKTGV0J3MgcmVtb3ZlIHRoZSBiaW5hcnkgcHJlZGljdG9yLiAKCmBgYHtyfQphdXRvX2ZpdDE0IDwtIGxtKG1wZyB+IHdlaWdodCArIEkod2VpZ2h0XjIpICsgeWVhciwgQXV0b19wcm9wZXIyKQpzdW1tYXJ5KGF1dG9fZml0MTQpCmBgYAoKV2hlbiB3ZSBwbG90IHRoaXMgbW9kZWwgdGhlIHJlc3VsdHMgbG9vayBncmVhdC4KCmBgYHtyIHdhcm5pbmdzID0gRkFMU0V9CkF1dG9fcHJvcGVyMiAlPiUKICAgIHNlbGVjdChtcGcsIHdlaWdodCwgeWVhciwgY3lsaW5kZXJzLCBpc182Y3lsaW5kZXIpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gd2VpZ2h0LCB5ID0gbXBnKSkgKyAKICAgIGZhY2V0X3dyYXAofiB5ZWFyKSArCiAgICBnZW9tX3BvaW50KGNvbG9yID0gcGFsNTM4WydibHVlJ11bWzFdXSwgYWxwaGEgPSAwLjUpICsKICAgIHRoZW1lX2pyZigpICsKICAgIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGZvcm11bGEgPSB5IH4geCArIEkoeF4yKSwgc2UgPSBGQUxTRSwgY29sb3VyID0gcGFsNTM4WydyZWQnXVtbMV1dKSArCiAgICBsYWJzKHRpdGxlID0gIlJlbW92aW5nIHRoZSBCaW5hcnkgSXMgNi1jeWxpbmRlciBWYXJpYWJsZSIsIHggPSAiV2VpZ2h0IiwgeSA9ICJNUEciKQpgYGAKCkxldCdzIGNvbXBhcmUgdGhlIEFJQyBhbmQgQklDIHZhbHVlcyBmb3IgYWxsIG9mIHRoZXNlIG1vZGVscy4KCmBgYHtyfQpkYXRhX2ZyYW1lKAogICAgTW9kZWwgPSBjKCIxOiBDeWxpbmRlcnMgLSBhbGwgbGV2ZWxzIiwgIjI6IEJpbmFyeSA2LWN5bGluZGVyIiwKICAgICAgICAgICAgICAiMzogQmluYXJ5IDYtY3lsaW5kZXIgJiBIb3JzZXBvd2VyIiwiNDogQ3lsaW5kZXJzIC0gYWxsIGxldmVscyAmIEhvcnNlcG93ZXIiLAogICAgICAgICAgICAgICI1OiBCaW5hcnkgNi1jeWxpbmRlciBhbmQgUXVhZHJhdGljIFdlaWdodCIsICI2OiBRdWFkcmF0aWMgV2VpZ2h0IE9ubHkiKQogICAgLCBBSUMgPSBBSUMoYXV0b19maXQ5LCBhdXRvX2ZpdDEwLCBhdXRvX2ZpdDExLCBhdXRvX2ZpdDEyLCBhdXRvX2ZpdDEzLCBhdXRvX2ZpdDE0KSRBSUMKICAgICwgQklDID0gQklDKGF1dG9fZml0OSwgYXV0b19maXQxMCwgYXV0b19maXQxMSwgYXV0b19maXQxMiwgYXV0b19maXQxMywgYXV0b19maXQxNCkkQklDCikgJT4lCiAgICBrYWJsZSgpCmBgYAoKVGhlcmUgaXMgb25seSBhIHNsaWdodCBpbmZvcm1hdGlvbiBnYWluIHdpdGggdGhlIGJpbmFyeSA2LWN5bGluZGVyIHZhcmlhYmxlIGFuZCB3ZSBiZWxpZXZlIHRoaXMgdG8gYmUgb3ZlcmZpdHRpbmcuIFdlIHdpbGwgcHJvY2VlZCB3aXRoIG1vZGVsIDYuIExldCdzIGNoZWNrIHdoYXQgd291bGQgaGFwcGVuIGlmIHdlIHVzZWQgYHJlZ3N1YnNldHNgIHdpdGggYSB0aGUgcXVhZHJhdGljIHRlcm0uCgpgYGB7cn0KYXV0b19maXQxNSA8LSByZWdzdWJzZXRzKG1wZyB+IC4gKyBJKHdlaWdodF4yKSwgZGF0YSA9IEF1dG9fcHJvcGVyMiAlPiUgc2VsZWN0KC1uYW1lLCAteWVhcjIpLCBudm1heCA9IDExLCBtZXRob2Q9ImV4aGF1c3RpdmUiKQphdXRvX2ZpdDE1X3N1bSA8LSBzdW1tYXJ5KGF1dG9fZml0MTUpCmFzX2RhdGFfZnJhbWUoYXV0b19maXQxNV9zdW0kb3V0bWF0KSAlPiUgcHJpbnQod2lkdGggPSBJbmYpCmBgYAoKYGBge3J9CmRhdGFfZnJhbWUoCiAgICAgIHByZWRpY3RvcnMgPSAxOmxlbmd0aChhdXRvX2ZpdDE1X3N1bSRjcCkKICAgICwgY3AgPSBhdXRvX2ZpdDE1X3N1bSRjcAogICAgLCBiaWMgPSBhdXRvX2ZpdDE1X3N1bSRiaWMKICAgICwgYWRqcjIgPSBhdXRvX2ZpdDE1X3N1bSRhZGpyMgopICU+JQogICAgZ2F0aGVyKG1ldHJpYywgdmFsdWUsIC1wcmVkaWN0b3JzKSAlPiUKICAgIG11dGF0ZShtZXRyaWMgPSBmYWN0b3IobWV0cmljLCBsZXZlbHMgPSBjKCJjcCIsImJpYyIsImFkanIyIikpKSAlPiUKICAgIGdncGxvdChhZXMoeCA9IHByZWRpY3RvcnMsIHkgPSB2YWx1ZSwgY29sb3VyID0gbWV0cmljKSkgKwogICAgZmFjZXRfZ3JpZChtZXRyaWMgfiAuLCBzY2FsZSA9ICJmcmVlX3kiLCBzd2l0Y2ggPSAieSIsIAogICAgICAgICAgICAgICBsYWJlbGxlciA9IGdncGxvdDI6OmxhYmVsbGVyKG1ldHJpYyA9IGMoY3AgPSAiQ3AiLCBiaWMgPSAiQklDIiwgYWRqcjIgPSAiQWRqdXN0ZWQgUl4yIikpKSArCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAzLCBhbHBoYSA9IDAuNSkgKyBnZW9tX2xpbmUoKSArIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX2xhYmVsKGRhdGEgPSBkYXRhX2ZyYW1lKAogICAgICAgIHByZWRpY3RvcnMgPSBjKHdoaWNoLm1pbihhdXRvX2ZpdDE1X3N1bSRjcCksIHdoaWNoLm1pbihhdXRvX2ZpdDE1X3N1bSRiaWMpLCB3aGljaC5tYXgoYXV0b19maXQxNV9zdW0kYWRqcjIpKQogICAgICAgICwgbWV0cmljID0gZmFjdG9yKGMoImNwIiwiYmljIiwiYWRqcjIiKSwgbGV2ZWxzID0gYygiY3AiLCJiaWMiLCJhZGpyMiIpKQogICAgICAgICwgdmFsdWUgPSBjKG1pbihhdXRvX2ZpdDE1X3N1bSRjcCksIG1pbihhdXRvX2ZpdDE1X3N1bSRiaWMpLCBtYXgoYXV0b19maXQxNV9zdW0kYWRqcjIpKQogICAgICAgICwgbGFiZWwgPSBwYXN0ZTAoIk9wdGltYWxcbmQ9IiwgYyh3aGljaC5taW4oYXV0b19maXQxNV9zdW0kY3ApLCB3aGljaC5taW4oYXV0b19maXQxNV9zdW0kYmljKSwgd2hpY2gubWF4KGF1dG9fZml0MTVfc3VtJGFkanIyKSkpCiAgICAgICAgLCB2anVzdCA9IGMoLS41LCAtLjUsIDEuMjUpCiAgICApLCBhZXMoeCA9IHByZWRpY3RvcnMsIHkgPSB2YWx1ZSwgbGFiZWwgPSBsYWJlbCwgdmp1c3QgPSB2anVzdCksIGZhbWlseSA9ICJEZWNpbWFNb25vUHJvIikgKwogICAgdGhlbWVfanJmKCkgKyAKICAgIGxhYnModGl0bGUgPSAiRXhoYXVzdGl2ZSBTZWFyY2ggd2l0aCBRdWFkcmF0aWMgV2VpZ2h0IiwgeCA9ICIjIG9mIFByZWRpY3RvcnMiLCB5ID0gTlVMTCkgKwogICAgZ2VvbV9sYWJlbChkYXRhID0gZGF0YV9mcmFtZSh4ID0gMywgeSA9IDMwMCwgbWV0cmljID0gZmFjdG9yKGMoImNwIiksIGxldmVscyA9IGMoImNwIiwiYmljIiwiYWRqcjIiKSksIAogICAgICAgICAgICAgICAgbGFiZWwgPSAiRWxib3cgd2l0aFxuMyBwcmVkaWN0b3JzIiksIGFlcyh4PXgseT15LGxhYmVsPWxhYmVsKSwgY29sb3VyID0gImJsYWNrIiwgaGp1c3QgPSAtLjEsCiAgICAgICAgICAgICAgIGZhbWlseSA9ICJEZWNpbWFNb25vUHJvIikgKyAKICAgIHNjYWxlX2NvbG91cl9tYW51YWwoZ3VpZGUgPSBGQUxTRSwgdmFsdWVzID0gYyhwYWw1MzhbJ3JlZCddW1sxXV0sIHBhbDUzOFsnZ3JlZW4nXVtbMV1dLCBwYWw1MzhbJ2JsdWUnXVtbMV1dKSkKYGBgCgpUaGUgcmVzdWx0IGNvbmZpcm1zIG91ciB0aGlua2luZzogYSAzIHByZWRpY3RvciBtb2RlbCB3aXRoIGEgcXVhZHJhdGljIHdlaWdodCB0ZXJtLgoKIyMjIyBTdW1tYXJ5CgpPdXIgZmluYWwgbW9kZWwgdG8gcHJlZGljdCBNUEcgYmFzZWQgb24gdGhlIHByZWRpY3RvcnMgaW4gdGhlICpBdXRvKiBkYXRhc2V0IGlzIAoKJCRNUEcgPSBcYmV0YV8wICsgXGJldGFfMSBXZWlnaHQgKyBcYmV0YV8yIFdlaWdodF4yICsgXGJldGFfMyB5ZWFyJCQKCmBgYHtyfQooYXV0b19maXRfZmluYWwgPC0gc3VtbWFyeShhdXRvX2ZpdDE0KSkKYGBgCgpUaHVzIHRoZSBtb2RlbCBpcwoKJCRNUEcgPSBgciByb3VuZChjb2VmKGF1dG9fZml0X2ZpbmFsKVssIDFdW1sxXV0sMilgICsgYHIgcm91bmQoY29lZihhdXRvX2ZpdF9maW5hbClbLCAxXVtbMl1dLDIpYCBXZWlnaHQgKyBgciBhcy5jaGFyYWN0ZXIocm91bmQoY29lZihhdXRvX2ZpdF9maW5hbClbLCAxXVtbM11dLCA3KSlgIFdlaWdodF4yICsgYHIgcm91bmQoY29lZihhdXRvX2ZpdF9maW5hbClbLCAxXVtbNF1dLDIpYCB5ZWFyJCQKCiMjIyMjIENoZWNraW5nIE1vZGVsIEFzc3VtcHRpb25zCgpUaGUgbm9ybWFsIFEtUSBwbG90IHNob3dzIHRoYXQgcmVzaWR1YWxzIG1pZ2h0IG5vdCBjb21lIGZyb20gYSBub3JtYWwgZGlzdHJpYnV0aW9uIGF0IHRoZSB0YWlscywgYnV0IGFsbCB0b2dldGhlciBpcyBzb21ld2hhdCBub3JtYWwuCgpgYGB7cn0KZGF0YV9mcmFtZShzdGQucmVzaWQgPSByc3RhbmRhcmQoYXV0b19maXQxNCkpICU+JSAKICAgIGdncGxvdCgpICsKICAgIHN0YXRfcXEoYWVzKHNhbXBsZSA9IHN0ZC5yZXNpZCksIGNvbG91ciA9IHBhbDUzOFsnYmx1ZSddKSArCiAgICBnZW9tX2FibGluZShkYXRhID0KICAgICAgICAuICU+JQogICAgICAgIHN1bW1hcmlzZSgKICAgICAgICAgICAgc2xvcGUgPSBkaWZmKHF1YW50aWxlKHN0ZC5yZXNpZCwgYygwLjI1LCAwLjc1KSkpIC8gZGlmZihxbm9ybShjKDAuMjUsIDAuNzUpKSkKICAgICAgICAgICAgLCBpbnQgPSBxdWFudGlsZShzdGQucmVzaWQsIGMoMC4yNSwgMC43NSkpWzFMXSAtIAogICAgICAgICAgICAgICAoZGlmZihxdWFudGlsZShzdGQucmVzaWQsIGMoMC4yNSwgMC43NSkpKSAvIAogICAgICAgICAgICAgICAgICAgIGRpZmYocW5vcm0oYygwLjI1LCAwLjc1KSkpKSAqIHFub3JtKGMoMC4yNSwgMC43NSkpWzFMXQogICAgICAgICksCiAgICAgICAgYWVzKHNsb3BlID0gc2xvcGUsIGludGVyY2VwdCA9IGludCksIGFscGhhID0gMC41CiAgICApICsKICAgIHRoZW1lX2pyZigpICsKICAgIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBOVUxMKSArIAogICAgbGFicyh0aXRsZSA9ICJOb3JtYWwgUS1RIiwgeSA9ICJTdGFuZGFyZGl6ZWQgUmVzaWR1YWxzIiwgeCA9ICJUaGVvcmV0aWNhbCBRdWFudGlsZXMiKQpgYGAKCkluIGFkZGl0aW9uLCB0aGUgU2hhcGlyby1XaWxrcyB0ZXN0IHNob3dzIHRoYXQgd2UgaGF2ZSBldmlkZW5jZSB0aGF0IHRoZSByZXNpZHVhbHMgZG8gbm90IGNvbWUgZnJvbSBhIG5vcm1hbCBkaXN0cmlidXRpb24uCgpgYGB7ciBTaGFwaXJvIFdpbGsgVGVzdH0Kc2hhcGlyby50ZXN0KHJzdGFuZGFyZChhdXRvX2ZpdDE0KSkKYGBgCgpUaGUgZml0dGVkIHZhbHVlcyB2cyByZXNpZHVhbHMgcGxvdHMgc2hvdyBhcHByb3hpbWF0ZWx5IGVxdWFsIHZhcmlhbmNlIG9mIHRoZSByZXNpZHVhbHMgKGkuZS4gbm8gaGV0ZXJvc2NlZGFzdGljaXR5KS4KCmBgYHtyfQpkYXRhX2ZyYW1lKAogICAgZml0dGVkID0gYXV0b19maXQxNCRmaXR0ZWQudmFsdWVzCiAgICAsIHJlc2lkID0gYXV0b19maXQxNCRyZXNpZHVhbHMKICAgICkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBmaXR0ZWQsIHkgPSByZXNpZCkpICsKICAgIGdlb21fcG9pbnQoY29sb3VyID0gcGFsNTM4WydibHVlJ10pICsKICAgIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIsIGNvbG91ciA9IHBhbDUzOFsncmVkJ10sIHNlID0gRkFMU0UsIHNpemUgPSAuMjUsIGFscGhhID0gMC41KSArIAogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgYWxwaGEgPSAwLjUsIGxpbmV0eXBlID0gJ2Rhc2hlZCcsIGNvbG9yID0gJ2JsYWNrJykgKwogICAgdGhlbWVfanJmKCkgKwogICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IE5VTEwpICsgCiAgICBsYWJzKHRpdGxlID0gIkZpdHRlZCBWYWx1ZXMgdnMgUmVzaWR1YWxzIiwgeSA9ICJSZXNpZHVhbHMiLCB4ID0gIkZpdHRlZCBWYWx1ZXMiKQpgYGAKCiMjIyMjIFN0YXRpc3RpY2FsIEluZmVyZW5jZQoKKyBUaGUgRi10ZXN0IGZvciByZWdyZXNzaW9uIHByb3ZpZGVzIGV4dHJlbWVseSBzdHJvbmcgZXZpZGVuY2UgYWdhaW5zdCB0aGUgaHlwb3RoZXNpcyB0aGF0IG5vbmUgb2YgdGhlIHZhcmlhYmxlcyBhcmUgcmVsYXRlZCB0byB0aGUgcmVzcG9uc2UgTVBHICgqUC12YWx1ZSAkXGFwcHJveCQgMCopLgorIFRoZSBNdWx0aXBsZSBSMiBpcyBgciByb3VuZChhdXRvX2ZpdF9maW5hbCRyLnNxdWFyZWQsMylgIGluZGljYXRpbmcgdGhhdCBgciAxMDAqcm91bmQoYXV0b19maXRfZmluYWwkci5zcXVhcmVkLCAxKWAlIG9mIHRoZSB2YXJpYXRpb24gaW4gY2FyIE1QRyBpcyBleHBsYWluZWQgYnkgdGhlIHZhcmlhdGlvbiBpbiB3ZWlnaHQgYW5kIG1vZGVsIHllYXIsIHNvIHRoZSBtb2RlbCB3aWxsIGJlIGdvb2QgZm9yIHByZWRpY3Rpb24sIGhvd2V2ZXIgbm9ybWFsaXR5IGlzIG5vdCBzYXRpc2ZpZWQgc28gcHJlZGljdGlvbnMgbWF5IGJlIHVucmVsaWFibGUuCisgVGhlIGludGVyY2VwdCBpcyBub3QgbWVhbmluZ2Z1bCAoKk1QRyA9IDAqKS4KKyBXZSBoYXZlIGV4dHJlbWVseSBzdHJvbmcgZXZpZGVuY2UgYWdhaW5zdCB0aGUgaHlwb3RoZXNlcyB0aGF0IHRoZSBjb2VmZmljaWVudHMgYXNzb2NpYXRlZCB3aXRoIHdlaWdodCBhcmUgZXF1YWwgdG8gMCAoKlAtdmFsdWVzICRcYXBwcm94JCAwKikuCisgV2UgaGF2ZSBleHRyZW1lbHkgZXZpZGVuY2UgYWdhaW5zdCB0aGUgaHlwb3RoZXNpcyB0aGF0IHRoZSBjb2VmZmljaWVudCBhc3NvY2lhdGVkIHdpdGggbW9kZWwgeWVhciBpcyBlcXVhbCB0byAwICgqUC12YWx1ZSAkXGFwcHJveCQgMCopLgoKVGhlIGVmZmVjdHMgYXNzb2NpYXRlZCB3aXRoIHdlaWdodCBhcmUgZGlmZmljdWx0IHRvIGRlc2NyaWJlIGJlY2F1c2Ugb2YgdGhlIHF1YWRyYXRpYyB0ZXJtLiAKSG9sZGluZyB0aGUgZWZmZWN0IG9mIHdlaWdodCBjb25zdGFudCwgZWFjaCBhZGRpdGlvbmFsIHllYXIgb2YgY2FyIChuZXdlciBtb2RlbCB5ZWFycyksIGEgY2FyJ3MgTVBHIGluY3JlYXNlcyBieSBgciBjb2VmKGF1dG9fZml0X2ZpbmFsKVssMV1bWzRdXWAuCgpGaW5hbGx5LCB3ZSBjYW4gZmluZCBhIDk1JSBwcmVkaWN0aW9uIGludGVydmFsIGZvciB0aGUgY2FyIGJ1aWx0IGluIDE5ODMuIAoKYGBge3J9CmZ1dHVyZV9jYXJfcHJlZCA8LSBwcmVkaWN0KGF1dG9fZml0MTQsIGZ1dHVyZV9jYXIsIGludGVydmFsID0gInByZWRpY3Rpb24iKQpgYGAKClRoZSBwcmVkaWN0aW9uIGludGVydmFsIGZvciB0aGUgTVBHIG9mIHRoaXMgY2FyIGlzIAoKJCQoYHIgZnV0dXJlX2Nhcl9wcmVkWzJdYCwgYHIgZnV0dXJlX2Nhcl9wcmVkWzNdYCkkJAoK