https://rpubs.com/Azizkhaled/1167315

Q1: Environment Canada Daily Weather

#libraries
options(warn = -1)
library(tidyverse)
library(lubridate)
library(RColorBrewer)
library(tidymodels) # a framework for modelling in tidyverse
library(GGally) # pairs plot
library(glue) # new favourite library
library(fda) # for a nice plot about splines
library(mgcv) # library we will use for splines
library(dplyr)

Step 1: Obtain Environment Canada’s daily temperature daily for any weather station. (I chose SAANICHTON CDA BC) Spanning 1970 to 2022.

Reason for choosing SAANICHTON: I tried multiple stations, SAANICHTON was among the few stations that has data between 1970 and 2022 with few Max.Temp missing values, similar to the Ottawa CDA data.

Data acquisition:

Similar to the tutorial provided, changing the stationID to 87 for SAANICHTON

base_url = c("https://climate.weather.gc.ca/climate_data/bulk_data_e.html?format=csv&stationID=87&Prov=BC&Year=", "&Month=1&Day=1&timeframe=2&submit=Download+Data")

destfile = list()
for(year in 1970:2022){
  download.file(paste0(
                      base_url[1],year,
                      base_url[2]),
                      destfile = paste0("Data/SAANICHTON",year,"-daily.csv"))
}

Creating the data set:

# Find all csv files in the "Data" folder.
files2load = list.files(path="Data",pattern="daily")
data_daily=NULL
for(lp in 1:length(files2load)){
  data_daily = rbind(data_daily,read_csv(paste0("Data/",files2load[lp])))
}

Step 2: Data Cleaning and Exploratory Data plots

Adding the day.of.year variable

data_daily <- data_daily |> 
  rename_all(make.names) |>
  mutate(Date.Time = as.Date(Date.Time, format = "%Y-%m-%d"),
         day.of.year = yday(Date.Time)) # Add day.of.year variable

Lets generate some plots to understand the data better

data_daily <- data_daily |> 
  dplyr::select(Date.Time, Month, Year, `day.of.year`, `Max.Temp...C.`)
# data_daily <- na.omit(data_daily)
data_daily |> 
  ggpairs(title = "Daily Data",
          axisLabels = "show")

We can see the plots above are not very pretty, but you still can see the relationship between day.of.year and Max.Temp. It hard to see the other relationship. Let’s try plotting them separately for better interpetation

Max temp vs date plot:

ggplot(data_daily, aes(x = Date.Time, y = Max.Temp...C., colour=factor(Year))) +
  geom_line(alpha = 0.7) + 
  geom_smooth(se = TRUE, color = "red") + 
  ggtitle("Time Series of Max Temperature with Trend") +
  xlab("Date") + ylab("Max Temperature (C)") +
  theme(legend.position = "none") 

We can see that the max temperature was fairly consistant accross the years. However, This plot does not provide much information due to huge data variation

Focusing on one month to see the trend over the years.

# Plot means of the Septemper max temperatures
data_daily |> 
  filter(Month == "09") |> 
  group_by(Year) |> 
  summarise(MeanOfSepMax = mean(Max.Temp...C.)) |>
  ungroup() |>
  ggplot() +
  geom_line(aes(x = Year, y = MeanOfSepMax, color = "red")) +
  ggtitle("Means of the Sep Max Temperatures") +
  labs(x = "Year", y = "degrees C") +
  theme(legend.position = "none") 

Despite the gaps indicating missing data, the remaining data seems to be consistent and shows a reasonable level of variation from year to year, suggesting no abrupt changes in temperature trends.

Plotting September data ignoring the missing days in some Septs

# Plot means of the June max and min temperatures
# Plot means of the Septemper max temperatures
data_daily |> 
  filter(Month == "09") |> 
  group_by(Year) |> 
  summarise(MeanOfSepMax = mean(Max.Temp...C., na.rm = TRUE)) |>
  ungroup() |>
  ggplot() +
  geom_line(aes(x = Year, y = MeanOfSepMax, color = "red")) +
  ggtitle("Means of the Sep Max Temperatures") +
  labs(x = "Year", y = "degrees C") +
  theme(legend.position = "none") 

This plot shows that there is no missing gaps or obvious outliers when ignoring the missing days. Might indicate that ignoring the missing values might be a safe approach since we lack the knowledge on how to import missing values.

Max Temp vs day.of.year

# Scatter Plot with Day of Year
ggplot(data_daily, aes(x = day.of.year, y = Max.Temp...C., , color=factor(Year))) +
  geom_point(alpha = 0.8) + 
  ggtitle("Max Temperature vs. Day of Year") +
 theme(legend.position = "none") 

From this plot, we can see that there is a clear seasonal pattern where temperatures peak in the middle of the year and are lower at the beginning and end, which corresponds to summer and winter seasons, respectively. The overlapping points suggest variability in maximum temperatures for the same days across different years. This indicates that Spline models might be a good fit here.

Step 3: Find best model to fit to Max.Temp…C. with Year and day.of.year as predictors.

data_daily <- data_daily |> 
  dplyr::select(Year, `day.of.year`, `Max.Temp...C.`)
data_daily <- na.omit(data_daily)

#going with option 1
# Split the data into training and testing sets
set.seed(123)  # Set a seed for reproducibility
data_split <- initial_split(data_daily, prop = 0.5)
train_data <- training(data_split)
test_data <- testing(data_split)

Linear models- Simple Linear :


lm_model = linear_reg() |>
set_engine("lm")

Simple_basic_recipe <- recipe(Max.Temp...C. ~ Year + `day.of.year` , data = train_data) 

# Create and fit the workflow with the training data
Simple_fitted_model <- workflow() |>
  add_recipe(Simple_basic_recipe) |>
  add_model(lm_model) |>
  fit(data = train_data)
summary(Simple_fitted_model|> extract_fit_engine())

Call:
stats::lm(formula = ..y ~ ., data = data)

Residuals:
     Min       1Q   Median       3Q      Max 
-21.9199  -4.6154  -0.6071   4.7348  22.1548 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept) -2.378e+01  8.527e+00  -2.789  0.00529 ** 
Year         1.805e-02  4.273e-03   4.225 2.41e-05 ***
day.of.year  9.230e-03  6.096e-04  15.142  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 6.275 on 9479 degrees of freedom
Multiple R-squared:  0.0255,    Adjusted R-squared:  0.02529 
F-statistic:   124 on 2 and 9479 DF,  p-value: < 2.2e-16
# Simple_fitted_model |> extract_fit_engine() |> plot(scheme = 2, residuals = TRUE)


# Generate predictions on the testing set
predictions <- predict(Simple_fitted_model, new_data = test_data) |>
  bind_cols(test_data)

# Plot the results
ggplot() +
  geom_point(aes(x = `day.of.year`, y = `Max.Temp...C.`, color = "Actual"), data = test_data) +
  geom_line(aes(x = `day.of.year`, y = .pred, color = "Predicted"), data = predictions) +
  labs(title = "Max Temperature Predictions by Day of Year (Simple_Linear)",
       x = "Day of Year",
       y = "Max Temperature (C)",
       color = "Legend") +
  theme(legend.position = "bottom")

The simple linear model roughly fitted a straight line over the average Max temp as in ‘Max Temperature Predictions by Day of Year (Simple_Linear)’ plot, which shows that the model is not capturing the variability in the data well. Not the best modelling choice, as we can also see in the low R-squared value of 0.025

Linear Models: step_cut


# Update the recipe with the training data

step_cut_recipe <- recipe(Max.Temp...C. ~ Year + `day.of.year` , data = train_data) |> 
  step_cut(`day.of.year`, breaks = seq(1, 365, length.out = 12+1))

# Create and fit the workflow with the training data
step_cut_fitted_model <- workflow() |>
  add_recipe(step_cut_recipe) |>
  add_model(lm_model) |>
  fit(data = train_data)

summary(step_cut_fitted_model|> extract_fit_engine())

Call:
stats::lm(formula = ..y ~ ., data = data)

Residuals:
    Min      1Q  Median      3Q     Max 
-17.663  -1.965  -0.142   1.810  16.549 

Coefficients:
                         Estimate Std. Error t value Pr(>|t|)    
(Intercept)            -28.307360   4.227640  -6.696 2.27e-11 ***
Year                     0.017649   0.002118   8.333  < 2e-16 ***
day.of.year(31.3,61.7]   1.366141   0.155394   8.791  < 2e-16 ***
day.of.year(61.7,92]     3.475982   0.153416  22.657  < 2e-16 ***
day.of.year(92,122]      6.209084   0.155705  39.877  < 2e-16 ***
day.of.year(122,153]     9.580320   0.155928  61.441  < 2e-16 ***
day.of.year(153,183]    12.589525   0.153755  81.881  < 2e-16 ***
day.of.year(183,213]    15.239769   0.154984  98.332  < 2e-16 ***
day.of.year(213,244]    15.293723   0.155497  98.354  < 2e-16 ***
day.of.year(244,274]    12.119983   0.155445  77.970  < 2e-16 ***
day.of.year(274,304]     6.862771   0.155239  44.208  < 2e-16 ***
day.of.year(304,335]     2.293135   0.153797  14.910  < 2e-16 ***
day.of.year(335,365]    -0.053149   0.154632  -0.344    0.731    
day.of.year(365,366]    -1.512043   1.180135  -1.281    0.200    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 3.109 on 9468 degrees of freedom
Multiple R-squared:  0.761, Adjusted R-squared:  0.7607 
F-statistic:  2319 on 13 and 9468 DF,  p-value: < 2.2e-16
step_cut_fitted_model |> extract_fit_engine() |> plot(scheme = 2, residuals = TRUE)

Warning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameter

Warning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameter

Warning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameterWarning: "scheme" is not a graphical parameterWarning: "residuals" is not a graphical parameter

# Generate predictions on the testing set
predictions <- predict(step_cut_fitted_model, new_data = test_data) |>
  bind_cols(test_data)

# Plot the results
ggplot() +
  geom_point(aes(x = `day.of.year`, y = `Max.Temp...C.`, color = "Actual"), data = test_data) +
  geom_line(aes(x = `day.of.year`, y = .pred, color = "Predicted"), data = predictions) +
  labs(title = "Max Temperature Predictions by Day of Year with 12 step cut",
       x = "Day of Year",
       y = "Max Temperature (C)",
       color = "Legend") +
  theme(legend.position = "bottom")

With the step cut, the model effectively captures the seasonal variation, resulting in a pattern that aligns with the cyclical nature of temperature changes over the year. As we increase the number of step cuts, the model will fit better to the data. 12 cuts here roughly represents the months.

Linear Models: step_poly


# Update the recipe with the training data

poly_recipe <- recipe(Max.Temp...C. ~ Year + `day.of.year` , data = train_data) |> 
  step_poly(`day.of.year`, degree = 10)

# Create and fit the workflow with the training data
poly_model <- workflow() |>
  add_recipe(poly_recipe) |>
  add_model(lm_model) |>
  fit(data = train_data)

summary(poly_model|> extract_fit_engine())
poly_model |> extract_fit_engine() |> plot(scheme = 2, residuals = TRUE)


# Generate predictions on the testing set
predictions <- predict(poly_model, new_data = test_data) |>
  bind_cols(test_data)

# Plot the results
ggplot() +
  geom_point(aes(x = `day.of.year`, y = `Max.Temp...C.`, color = "Actual"), data = test_data) +
  geom_line(aes(x = `day.of.year`, y = .pred, color = "Predicted"), data = predictions) +
  labs(title = "Max Temperature Predictions by Day of Year with step_poly",
       x = "Day of Year",
       y = "Max Temperature (C)",
       color = "Legend") +
  theme(legend.position = "bottom")

Aiming for a smoother fit, the 10-degree poly_cut model delivers whats promised. The summary statistics show an improvement with a higher R-squared value of 0.7817, indicating that the polynomial model explains more of the variance in the data than the previous linear model with step cuts. The lower residual standard error also suggests better predictive accuracy. Higher degrees might fit the training data better, but might result in an overfitting

GAM models:

gam_model = gen_additive_mod( mode = "regression" ) |>
  set_engine("mgcv")
gam_fit = workflow() |>
  add_variables(outcomes = Max.Temp...C., predictors = c(Year, `day.of.year`)) |>
    add_model(gam_model,
      formula = Max.Temp...C. ~ s(Year) +
        s(`day.of.year`)) |>
fit(data = train_data)
gam_fit |> extract_fit_engine() |> summary()

Family: gaussian 
Link function: identity 

Formula:
Max.Temp...C. ~ s(Year) + s(day.of.year)

Parametric coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) 13.92762    0.03026   460.3   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Approximate significance of smooth terms:
                 edf Ref.df       F p-value    
s(Year)        8.913  8.998   26.12  <2e-16 ***
s(day.of.year) 8.771  8.985 3823.74  <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

R-sq.(adj) =  0.785   Deviance explained = 78.5%
GCV = 8.6984  Scale est. = 8.6813    n = 9482
# gam_fit |> extract_fit_engine() |>
#   plot(residuals = TRUE, cex = 5, shade = TRUE)

gam_fit |> extract_fit_engine() |> plot(scheme = 2, residuals = TRUE)




# Generate predictions on the testing set
predictions <- predict(gam_fit, new_data = test_data) |>
  bind_cols(test_data)

# Plot the results
ggplot() +
  geom_point(aes(x = `day.of.year`, y = `Max.Temp...C.`, color = "Actual"), data = test_data) +
  geom_line(aes(x = `day.of.year`, y = .pred, color = "Predicted"), data = predictions) +
  labs(title = "Max Temperature Predictions by Day of Year with gam_fit",
       x = "Day of Year",
       y = "Max Temperature (C)",
       color = "Legend") +
  theme(legend.position = "bottom")

The GAM is fitting a non-linear relationship between the day of the year and temperature, allowing for a smooth curve that captures the seasonal trend. Compared to the linear model, the GAM provides a more flexible fit, accommodating the cyclical nature of temperature changes, which is evidenced by the smooth prediction curve closely tracking the actual data points.

The summary of the GAM shows that both the smooth term for the year (s(Year)) and the day of the year (s(day.of.year)) are statistically significant, with the model explaining 78.5% of the deviance (a good level of model fit for this type of data). The model’s adjusted R-squared value is 0.785, indicating that it accounts for a substantial proportion of the variability in the maximum temperatures, which is an improvement over simpler linear models and justifies the reason behind selecting it as the best fit to our data.

Q2: Option wine: An R attempt (Solution is submetted in jupyter notebook)

library(tidyverse) # readable code that is easier to troubleshoot
library(tidymodels) # a framework for modelling in tidyverse
library(vip) # variable importance
library(ISLR2) # Boston dataset

Load the dataset and perform data exploration

# Load the wine quality dataset
wine_data <- read_csv("Assignment_4_wine.csv")
Rows: 6497 Columns: 13── Column specification ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
chr  (1): type
dbl (12): fixed.acidity, volatile.acidity, citric.acid, residual.sugar, chlorides, free.sulfur.dioxide, total.sulfur.dioxide, density, pH, sulphates, alcohol, quality
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
quality_classes <- unique(wine_data$quality)
num_classes <- length(quality_classes)
print(num_classes)
[1] 7
quality_counts <- table(wine_data$quality)
print(quality_counts)

   3    4    5    6    7    8    9 
  30  216 2138 2836 1079  193    5 
ggplot(wine_data, aes(x = quality)) +
  geom_histogram(binwidth = 1, boundary = 0.5, fill = "blue", color = "black") +
  labs(title = "Histogram of Wine Quality", x = "Quality", y = "Count") +
  theme_minimal()

We can see how the quality data is uniformly distributed, which shows great class imbalance. We need to consider this when splitting the data between training and test. Using stratify will ensure that both testing and training sets has all classes

Data split and model initialization

# Split the data into training and testing sets
set.seed(42)
wine_data$quality <- as.factor(wine_data$quality)
data_split <- initial_split(wine_data, prop = 0.8, strata = quality)
train_data <- training(data_split)
test_data <- testing(data_split)
data_folds = vfold_cv(train_data, v = 5, strata = quality)

# Define the model and recipe
forest_model <- rand_forest(trees = tune(), mtry = tune(), mode = "classification") |> 
  set_engine("ranger", importance = "impurity") |> 
  set_mode("classification")

wine_recipe <- recipe(quality ~ ., data = train_data) 

# Create a workflow
model_flow = workflow() |>
  add_recipe( wine_recipe ) |>
  add_model( forest_model )

Plotting the variable importance:



final_workflow <- finalize_workflow(model_flow, best_results)
# Fit the final model to the entire training dataset
final_fit <- fit(final_workflow, data = train_data)

# Extract the fitted model object
Fitobject <- final_fit |> extract_fit_engine()

# Define the prediction function for the permutation importance
pred_fun <- function(model, newdata) {
  predict(model, newdata = newdata, type = "prob")$.pred_class
}

# Calculate permutation-based variable importance
set.seed(123) # For reproducibility
vip_perm <- vi(object = Fitobject, 
               feature_names = names(select(train_data, -quality)), 
               target = "quality",
               metric = "f_meas",
               train = train_data,
               pred_wrapper = pred_fun,
               nsim = 1000,
               parallel = TRUE,
               verbose = TRUE)

# Plot the variable importance
vip(vip_perm)

We can see that the alcohol level has larger importance compared to the other features. However, I am not sure why some features are not in the plot. I assume that ‘type’ feature has very little importance in predicting the quality, but to confirm this. I performed the same approach on python using scikit-learn. The results confirm the plots above.

Q3: The analysis and plot is in the ipynb file

results: we can confirm that following are the important variables: X11 , X1 , X3 , X47 ||| - X6 - X78 - X38 - X36 - X29 - X54

LS0tCnRpdGxlOiAiQXppeiBBbC1OYWpqYXIsIEFzc2lnbm1lbnQgNCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKPGh0dHBzOi8vcnB1YnMuY29tL0F6aXpraGFsZWQvMTE2NzMxNT4KCiMjIFExOiBFbnZpcm9ubWVudCBDYW5hZGEgRGFpbHkgV2VhdGhlcgoKYGBge3J9CiNsaWJyYXJpZXMKb3B0aW9ucyh3YXJuID0gLTEpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkodGlkeW1vZGVscykgIyBhIGZyYW1ld29yayBmb3IgbW9kZWxsaW5nIGluIHRpZHl2ZXJzZQpsaWJyYXJ5KEdHYWxseSkgIyBwYWlycyBwbG90CmxpYnJhcnkoZ2x1ZSkgIyBuZXcgZmF2b3VyaXRlIGxpYnJhcnkKbGlicmFyeShmZGEpICMgZm9yIGEgbmljZSBwbG90IGFib3V0IHNwbGluZXMKbGlicmFyeShtZ2N2KSAjIGxpYnJhcnkgd2Ugd2lsbCB1c2UgZm9yIHNwbGluZXMKbGlicmFyeShkcGx5cikKYGBgCgojIyMgU3RlcCAxOiBPYnRhaW4gRW52aXJvbm1lbnQgQ2FuYWRh4oCZcyBkYWlseSB0ZW1wZXJhdHVyZSBkYWlseSBmb3IgYW55IHdlYXRoZXIgc3RhdGlvbi4gKEkgY2hvc2UgKipTQUFOSUNIVE9OIENEQSBCQykqKiBTcGFubmluZyAxOTcwIHRvIDIwMjIuCgpSZWFzb24gZm9yIGNob29zaW5nICoqU0FBTklDSFRPTjoqKiBJIHRyaWVkIG11bHRpcGxlIHN0YXRpb25zLCBTQUFOSUNIVE9OIHdhcyBhbW9uZyB0aGUgZmV3IHN0YXRpb25zIHRoYXQgaGFzIGRhdGEgYmV0d2VlbiAxOTcwIGFuZCAyMDIyIHdpdGggZmV3IE1heC5UZW1wIG1pc3NpbmcgdmFsdWVzLCBzaW1pbGFyIHRvIHRoZSBPdHRhd2EgQ0RBIGRhdGEuCgojIyMjIERhdGEgYWNxdWlzaXRpb246CgpTaW1pbGFyIHRvIHRoZSB0dXRvcmlhbCBwcm92aWRlZCwgY2hhbmdpbmcgdGhlIHN0YXRpb25JRCB0byA4NyBmb3IgU0FBTklDSFRPTgoKYGBge3J9CmJhc2VfdXJsID0gYygiaHR0cHM6Ly9jbGltYXRlLndlYXRoZXIuZ2MuY2EvY2xpbWF0ZV9kYXRhL2J1bGtfZGF0YV9lLmh0bWw/Zm9ybWF0PWNzdiZzdGF0aW9uSUQ9ODcmUHJvdj1CQyZZZWFyPSIsICImTW9udGg9MSZEYXk9MSZ0aW1lZnJhbWU9MiZzdWJtaXQ9RG93bmxvYWQrRGF0YSIpCgpkZXN0ZmlsZSA9IGxpc3QoKQpmb3IoeWVhciBpbiAxOTcwOjIwMjIpewogIGRvd25sb2FkLmZpbGUocGFzdGUwKAogICAgICAgICAgICAgICAgICAgICAgYmFzZV91cmxbMV0seWVhciwKICAgICAgICAgICAgICAgICAgICAgIGJhc2VfdXJsWzJdKSwKICAgICAgICAgICAgICAgICAgICAgIGRlc3RmaWxlID0gcGFzdGUwKCJEYXRhL1NBQU5JQ0hUT04iLHllYXIsIi1kYWlseS5jc3YiKSkKfQoKCgoKYGBgCgpDcmVhdGluZyB0aGUgZGF0YSBzZXQ6CgpgYGB7cn0KIyBGaW5kIGFsbCBjc3YgZmlsZXMgaW4gdGhlICJEYXRhIiBmb2xkZXIuCmZpbGVzMmxvYWQgPSBsaXN0LmZpbGVzKHBhdGg9IkRhdGEiLHBhdHRlcm49ImRhaWx5IikKZGF0YV9kYWlseT1OVUxMCmZvcihscCBpbiAxOmxlbmd0aChmaWxlczJsb2FkKSl7CiAgZGF0YV9kYWlseSA9IHJiaW5kKGRhdGFfZGFpbHkscmVhZF9jc3YocGFzdGUwKCJEYXRhLyIsZmlsZXMybG9hZFtscF0pKSkKfQpgYGAKCiMjIyBTdGVwIDI6IERhdGEgQ2xlYW5pbmcgYW5kIEV4cGxvcmF0b3J5IERhdGEgcGxvdHMKCiMjIyMgQWRkaW5nIHRoZSBkYXkub2YueWVhciB2YXJpYWJsZQoKYGBge3J9CmRhdGFfZGFpbHkgPC0gZGF0YV9kYWlseSB8PiAKICByZW5hbWVfYWxsKG1ha2UubmFtZXMpIHw+CiAgbXV0YXRlKERhdGUuVGltZSA9IGFzLkRhdGUoRGF0ZS5UaW1lLCBmb3JtYXQgPSAiJVktJW0tJWQiKSwKICAgICAgICAgZGF5Lm9mLnllYXIgPSB5ZGF5KERhdGUuVGltZSkpICMgQWRkIGRheS5vZi55ZWFyIHZhcmlhYmxlCgpgYGAKCiMjIyMgTGV0cyBnZW5lcmF0ZSBzb21lIHBsb3RzIHRvIHVuZGVyc3RhbmQgdGhlIGRhdGEgYmV0dGVyCgpgYGB7cn0KZGF0YV9kYWlseSA8LSBkYXRhX2RhaWx5IHw+IAogIGRwbHlyOjpzZWxlY3QoRGF0ZS5UaW1lLCBNb250aCwgWWVhciwgYGRheS5vZi55ZWFyYCwgYE1heC5UZW1wLi4uQy5gKQojIGRhdGFfZGFpbHkgPC0gbmEub21pdChkYXRhX2RhaWx5KQpkYXRhX2RhaWx5IHw+IAogIGdncGFpcnModGl0bGUgPSAiRGFpbHkgRGF0YSIsCiAgICAgICAgICBheGlzTGFiZWxzID0gInNob3ciKQpgYGAKCldlIGNhbiBzZWUgdGhlIHBsb3RzIGFib3ZlIGFyZSBub3QgdmVyeSBwcmV0dHksIGJ1dCB5b3Ugc3RpbGwgY2FuIHNlZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gZGF5Lm9mLnllYXIgYW5kIE1heC5UZW1wLiBJdCBoYXJkIHRvIHNlZSB0aGUgb3RoZXIgcmVsYXRpb25zaGlwLiBMZXQncyB0cnkgKipwbG90dGluZyB0aGVtIHNlcGFyYXRlbHkqKiBmb3IgYmV0dGVyIGludGVycGV0YXRpb24KCiMjIyMgTWF4IHRlbXAgdnMgZGF0ZSBwbG90OgoKYGBge3J9CmdncGxvdChkYXRhX2RhaWx5LCBhZXMoeCA9IERhdGUuVGltZSwgeSA9IE1heC5UZW1wLi4uQy4sIGNvbG91cj1mYWN0b3IoWWVhcikpKSArCiAgZ2VvbV9saW5lKGFscGhhID0gMC43KSArIAogIGdlb21fc21vb3RoKHNlID0gVFJVRSwgY29sb3IgPSAicmVkIikgKyAKICBnZ3RpdGxlKCJUaW1lIFNlcmllcyBvZiBNYXggVGVtcGVyYXR1cmUgd2l0aCBUcmVuZCIpICsKICB4bGFiKCJEYXRlIikgKyB5bGFiKCJNYXggVGVtcGVyYXR1cmUgKEMpIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgCmBgYAoKV2UgY2FuIHNlZSB0aGF0IHRoZSBtYXggdGVtcGVyYXR1cmUgd2FzIGZhaXJseSBjb25zaXN0YW50IGFjY3Jvc3MgdGhlIHllYXJzLiBIb3dldmVyLCBUaGlzIHBsb3QgZG9lcyBub3QgcHJvdmlkZSBtdWNoIGluZm9ybWF0aW9uIGR1ZSB0byBodWdlIGRhdGEgdmFyaWF0aW9uCgojIyMjIEZvY3VzaW5nIG9uIG9uZSBtb250aCB0byBzZWUgdGhlIHRyZW5kIG92ZXIgdGhlIHllYXJzLgoKYGBge3J9CiMgUGxvdCBtZWFucyBvZiB0aGUgU2VwdGVtcGVyIG1heCB0ZW1wZXJhdHVyZXMKZGF0YV9kYWlseSB8PiAKICBmaWx0ZXIoTW9udGggPT0gIjA5IikgfD4gCiAgZ3JvdXBfYnkoWWVhcikgfD4gCiAgc3VtbWFyaXNlKE1lYW5PZlNlcE1heCA9IG1lYW4oTWF4LlRlbXAuLi5DLikpIHw+CiAgdW5ncm91cCgpIHw+CiAgZ2dwbG90KCkgKwogIGdlb21fbGluZShhZXMoeCA9IFllYXIsIHkgPSBNZWFuT2ZTZXBNYXgsIGNvbG9yID0gInJlZCIpKSArCiAgZ2d0aXRsZSgiTWVhbnMgb2YgdGhlIFNlcCBNYXggVGVtcGVyYXR1cmVzIikgKwogIGxhYnMoeCA9ICJZZWFyIiwgeSA9ICJkZWdyZWVzIEMiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSAKYGBgCgpEZXNwaXRlIHRoZSBnYXBzIGluZGljYXRpbmcgbWlzc2luZyBkYXRhLCB0aGUgcmVtYWluaW5nIGRhdGEgc2VlbXMgdG8gYmUgY29uc2lzdGVudCBhbmQgc2hvd3MgYSByZWFzb25hYmxlIGxldmVsIG9mIHZhcmlhdGlvbiBmcm9tIHllYXIgdG8geWVhciwgc3VnZ2VzdGluZyBubyBhYnJ1cHQgY2hhbmdlcyBpbiB0ZW1wZXJhdHVyZSB0cmVuZHMuCgojIyMjIFBsb3R0aW5nIFNlcHRlbWJlciBkYXRhIGlnbm9yaW5nIHRoZSBtaXNzaW5nIGRheXMgaW4gc29tZSBTZXB0cwoKYGBge3J9CiMgUGxvdCBtZWFucyBvZiB0aGUgSnVuZSBtYXggYW5kIG1pbiB0ZW1wZXJhdHVyZXMKIyBQbG90IG1lYW5zIG9mIHRoZSBTZXB0ZW1wZXIgbWF4IHRlbXBlcmF0dXJlcwpkYXRhX2RhaWx5IHw+IAogIGZpbHRlcihNb250aCA9PSAiMDkiKSB8PiAKICBncm91cF9ieShZZWFyKSB8PiAKICBzdW1tYXJpc2UoTWVhbk9mU2VwTWF4ID0gbWVhbihNYXguVGVtcC4uLkMuLCBuYS5ybSA9IFRSVUUpKSB8PgogIHVuZ3JvdXAoKSB8PgogIGdncGxvdCgpICsKICBnZW9tX2xpbmUoYWVzKHggPSBZZWFyLCB5ID0gTWVhbk9mU2VwTWF4LCBjb2xvciA9ICJyZWQiKSkgKwogIGdndGl0bGUoIk1lYW5zIG9mIHRoZSBTZXAgTWF4IFRlbXBlcmF0dXJlcyIpICsKICBsYWJzKHggPSAiWWVhciIsIHkgPSAiZGVncmVlcyBDIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgCmBgYAoKVGhpcyBwbG90IHNob3dzIHRoYXQgdGhlcmUgaXMgbm8gbWlzc2luZyBnYXBzIG9yIG9idmlvdXMgb3V0bGllcnMgd2hlbiBpZ25vcmluZyB0aGUgbWlzc2luZyBkYXlzLiBNaWdodCBpbmRpY2F0ZSB0aGF0IGlnbm9yaW5nIHRoZSBtaXNzaW5nIHZhbHVlcyBtaWdodCBiZSBhIHNhZmUgYXBwcm9hY2ggc2luY2Ugd2UgbGFjayB0aGUga25vd2xlZGdlIG9uIGhvdyB0byBpbXBvcnQgbWlzc2luZyB2YWx1ZXMuCgojIyMjIE1heCBUZW1wIHZzIGRheS5vZi55ZWFyCgpgYGB7cn0KIyBTY2F0dGVyIFBsb3Qgd2l0aCBEYXkgb2YgWWVhcgpnZ3Bsb3QoZGF0YV9kYWlseSwgYWVzKHggPSBkYXkub2YueWVhciwgeSA9IE1heC5UZW1wLi4uQy4sICwgY29sb3I9ZmFjdG9yKFllYXIpKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjgpICsgCiAgZ2d0aXRsZSgiTWF4IFRlbXBlcmF0dXJlIHZzLiBEYXkgb2YgWWVhciIpICsKIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgCmBgYAoKRnJvbSB0aGlzIHBsb3QsIHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBpcyBhIGNsZWFyIHNlYXNvbmFsIHBhdHRlcm4gd2hlcmUgdGVtcGVyYXR1cmVzIHBlYWsgaW4gdGhlIG1pZGRsZSBvZiB0aGUgeWVhciBhbmQgYXJlIGxvd2VyIGF0IHRoZSBiZWdpbm5pbmcgYW5kIGVuZCwgd2hpY2ggY29ycmVzcG9uZHMgdG8gc3VtbWVyIGFuZCB3aW50ZXIgc2Vhc29ucywgcmVzcGVjdGl2ZWx5LiBUaGUgb3ZlcmxhcHBpbmcgcG9pbnRzIHN1Z2dlc3QgdmFyaWFiaWxpdHkgaW4gbWF4aW11bSB0ZW1wZXJhdHVyZXMgZm9yIHRoZSBzYW1lIGRheXMgYWNyb3NzIGRpZmZlcmVudCB5ZWFycy4gVGhpcyBpbmRpY2F0ZXMgdGhhdCBTcGxpbmUgbW9kZWxzIG1pZ2h0IGJlIGEgZ29vZCBmaXQgaGVyZS4KCiMjIyBTdGVwIDM6IEZpbmQgYmVzdCBtb2RlbCB0byBmaXQgdG8gTWF4LlRlbXAuLi5DLiB3aXRoIFllYXIgYW5kIGRheS5vZi55ZWFyIGFzIHByZWRpY3RvcnMuCgpgYGB7cn0KZGF0YV9kYWlseSA8LSBkYXRhX2RhaWx5IHw+IAogIGRwbHlyOjpzZWxlY3QoWWVhciwgYGRheS5vZi55ZWFyYCwgYE1heC5UZW1wLi4uQy5gKQpkYXRhX2RhaWx5IDwtIG5hLm9taXQoZGF0YV9kYWlseSkKCiNnb2luZyB3aXRoIG9wdGlvbiAxCiMgU3BsaXQgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzCnNldC5zZWVkKDEyMykgICMgU2V0IGEgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5CmRhdGFfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChkYXRhX2RhaWx5LCBwcm9wID0gMC41KQp0cmFpbl9kYXRhIDwtIHRyYWluaW5nKGRhdGFfc3BsaXQpCnRlc3RfZGF0YSA8LSB0ZXN0aW5nKGRhdGFfc3BsaXQpCmBgYAoKIyMjIyBMaW5lYXIgbW9kZWxzLSBTaW1wbGUgTGluZWFyIDoKCmBgYHtyfQoKbG1fbW9kZWwgPSBsaW5lYXJfcmVnKCkgfD4Kc2V0X2VuZ2luZSgibG0iKQoKU2ltcGxlX2Jhc2ljX3JlY2lwZSA8LSByZWNpcGUoTWF4LlRlbXAuLi5DLiB+IFllYXIgKyBgZGF5Lm9mLnllYXJgICwgZGF0YSA9IHRyYWluX2RhdGEpIAoKIyBDcmVhdGUgYW5kIGZpdCB0aGUgd29ya2Zsb3cgd2l0aCB0aGUgdHJhaW5pbmcgZGF0YQpTaW1wbGVfZml0dGVkX21vZGVsIDwtIHdvcmtmbG93KCkgfD4KICBhZGRfcmVjaXBlKFNpbXBsZV9iYXNpY19yZWNpcGUpIHw+CiAgYWRkX21vZGVsKGxtX21vZGVsKSB8PgogIGZpdChkYXRhID0gdHJhaW5fZGF0YSkKc3VtbWFyeShTaW1wbGVfZml0dGVkX21vZGVsfD4gZXh0cmFjdF9maXRfZW5naW5lKCkpCiMgU2ltcGxlX2ZpdHRlZF9tb2RlbCB8PiBleHRyYWN0X2ZpdF9lbmdpbmUoKSB8PiBwbG90KHNjaGVtZSA9IDIsIHJlc2lkdWFscyA9IFRSVUUpCgoKIyBHZW5lcmF0ZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdGluZyBzZXQKcHJlZGljdGlvbnMgPC0gcHJlZGljdChTaW1wbGVfZml0dGVkX21vZGVsLCBuZXdfZGF0YSA9IHRlc3RfZGF0YSkgfD4KICBiaW5kX2NvbHModGVzdF9kYXRhKQoKIyBQbG90IHRoZSByZXN1bHRzCmdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gYGRheS5vZi55ZWFyYCwgeSA9IGBNYXguVGVtcC4uLkMuYCwgY29sb3IgPSAiQWN0dWFsIiksIGRhdGEgPSB0ZXN0X2RhdGEpICsKICBnZW9tX2xpbmUoYWVzKHggPSBgZGF5Lm9mLnllYXJgLCB5ID0gLnByZWQsIGNvbG9yID0gIlByZWRpY3RlZCIpLCBkYXRhID0gcHJlZGljdGlvbnMpICsKICBsYWJzKHRpdGxlID0gIk1heCBUZW1wZXJhdHVyZSBQcmVkaWN0aW9ucyBieSBEYXkgb2YgWWVhciAoU2ltcGxlX0xpbmVhcikiLAogICAgICAgeCA9ICJEYXkgb2YgWWVhciIsCiAgICAgICB5ID0gIk1heCBUZW1wZXJhdHVyZSAoQykiLAogICAgICAgY29sb3IgPSAiTGVnZW5kIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQoKYGBgCgpUaGUgc2ltcGxlIGxpbmVhciBtb2RlbCByb3VnaGx5IGZpdHRlZCBhIHN0cmFpZ2h0IGxpbmUgb3ZlciB0aGUgYXZlcmFnZSBNYXggdGVtcCBhcyBpbiAnTWF4IFRlbXBlcmF0dXJlIFByZWRpY3Rpb25zIGJ5IERheSBvZiBZZWFyIChTaW1wbGVfTGluZWFyKScgcGxvdCwgd2hpY2ggc2hvd3MgdGhhdCB0aGUgbW9kZWwgaXMgbm90IGNhcHR1cmluZyB0aGUgdmFyaWFiaWxpdHkgaW4gdGhlIGRhdGEgd2VsbC4gTm90IHRoZSBiZXN0IG1vZGVsbGluZyBjaG9pY2UsIGFzIHdlIGNhbiBhbHNvIHNlZSBpbiB0aGUgbG93IFItc3F1YXJlZCB2YWx1ZSBvZiAwLjAyNVwKCiMjIyMgTGluZWFyIE1vZGVsczogc3RlcF9jdXQKCmBgYHtyfQoKIyBVcGRhdGUgdGhlIHJlY2lwZSB3aXRoIHRoZSB0cmFpbmluZyBkYXRhCgpzdGVwX2N1dF9yZWNpcGUgPC0gcmVjaXBlKE1heC5UZW1wLi4uQy4gfiBZZWFyICsgYGRheS5vZi55ZWFyYCAsIGRhdGEgPSB0cmFpbl9kYXRhKSB8PiAKICBzdGVwX2N1dChgZGF5Lm9mLnllYXJgLCBicmVha3MgPSBzZXEoMSwgMzY1LCBsZW5ndGgub3V0ID0gMTIrMSkpCgojIENyZWF0ZSBhbmQgZml0IHRoZSB3b3JrZmxvdyB3aXRoIHRoZSB0cmFpbmluZyBkYXRhCnN0ZXBfY3V0X2ZpdHRlZF9tb2RlbCA8LSB3b3JrZmxvdygpIHw+CiAgYWRkX3JlY2lwZShzdGVwX2N1dF9yZWNpcGUpIHw+CiAgYWRkX21vZGVsKGxtX21vZGVsKSB8PgogIGZpdChkYXRhID0gdHJhaW5fZGF0YSkKCnN1bW1hcnkoc3RlcF9jdXRfZml0dGVkX21vZGVsfD4gZXh0cmFjdF9maXRfZW5naW5lKCkpCnN0ZXBfY3V0X2ZpdHRlZF9tb2RlbCB8PiBleHRyYWN0X2ZpdF9lbmdpbmUoKSB8PiBwbG90KHNjaGVtZSA9IDIsIHJlc2lkdWFscyA9IFRSVUUpCgojIEdlbmVyYXRlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0aW5nIHNldApwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHN0ZXBfY3V0X2ZpdHRlZF9tb2RlbCwgbmV3X2RhdGEgPSB0ZXN0X2RhdGEpIHw+CiAgYmluZF9jb2xzKHRlc3RfZGF0YSkKCiMgUGxvdCB0aGUgcmVzdWx0cwpnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IGBkYXkub2YueWVhcmAsIHkgPSBgTWF4LlRlbXAuLi5DLmAsIGNvbG9yID0gIkFjdHVhbCIpLCBkYXRhID0gdGVzdF9kYXRhKSArCiAgZ2VvbV9saW5lKGFlcyh4ID0gYGRheS5vZi55ZWFyYCwgeSA9IC5wcmVkLCBjb2xvciA9ICJQcmVkaWN0ZWQiKSwgZGF0YSA9IHByZWRpY3Rpb25zKSArCiAgbGFicyh0aXRsZSA9ICJNYXggVGVtcGVyYXR1cmUgUHJlZGljdGlvbnMgYnkgRGF5IG9mIFllYXIgd2l0aCAxMiBzdGVwIGN1dCIsCiAgICAgICB4ID0gIkRheSBvZiBZZWFyIiwKICAgICAgIHkgPSAiTWF4IFRlbXBlcmF0dXJlIChDKSIsCiAgICAgICBjb2xvciA9ICJMZWdlbmQiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgpgYGAKCldpdGggdGhlIHN0ZXAgY3V0LCB0aGUgbW9kZWwgZWZmZWN0aXZlbHkgY2FwdHVyZXMgdGhlIHNlYXNvbmFsIHZhcmlhdGlvbiwgcmVzdWx0aW5nIGluIGEgcGF0dGVybiB0aGF0IGFsaWducyB3aXRoIHRoZSBjeWNsaWNhbCBuYXR1cmUgb2YgdGVtcGVyYXR1cmUgY2hhbmdlcyBvdmVyIHRoZSB5ZWFyLiBBcyB3ZSBpbmNyZWFzZSB0aGUgbnVtYmVyIG9mIHN0ZXAgY3V0cywgdGhlIG1vZGVsIHdpbGwgZml0IGJldHRlciB0byB0aGUgZGF0YS4gMTIgY3V0cyBoZXJlIHJvdWdobHkgcmVwcmVzZW50cyB0aGUgbW9udGhzLgoKIyMjIyBMaW5lYXIgTW9kZWxzOiBzdGVwX3BvbHkKCmBgYHtyfQoKIyBVcGRhdGUgdGhlIHJlY2lwZSB3aXRoIHRoZSB0cmFpbmluZyBkYXRhCgpwb2x5X3JlY2lwZSA8LSByZWNpcGUoTWF4LlRlbXAuLi5DLiB+IFllYXIgKyBgZGF5Lm9mLnllYXJgICwgZGF0YSA9IHRyYWluX2RhdGEpIHw+IAogIHN0ZXBfcG9seShgZGF5Lm9mLnllYXJgLCBkZWdyZWUgPSAxMCkKCiMgQ3JlYXRlIGFuZCBmaXQgdGhlIHdvcmtmbG93IHdpdGggdGhlIHRyYWluaW5nIGRhdGEKcG9seV9tb2RlbCA8LSB3b3JrZmxvdygpIHw+CiAgYWRkX3JlY2lwZShwb2x5X3JlY2lwZSkgfD4KICBhZGRfbW9kZWwobG1fbW9kZWwpIHw+CiAgZml0KGRhdGEgPSB0cmFpbl9kYXRhKQoKc3VtbWFyeShwb2x5X21vZGVsfD4gZXh0cmFjdF9maXRfZW5naW5lKCkpCnBvbHlfbW9kZWwgfD4gZXh0cmFjdF9maXRfZW5naW5lKCkgfD4gcGxvdChzY2hlbWUgPSAyLCByZXNpZHVhbHMgPSBUUlVFKQoKCiMgR2VuZXJhdGUgcHJlZGljdGlvbnMgb24gdGhlIHRlc3Rpbmcgc2V0CnByZWRpY3Rpb25zIDwtIHByZWRpY3QocG9seV9tb2RlbCwgbmV3X2RhdGEgPSB0ZXN0X2RhdGEpIHw+CiAgYmluZF9jb2xzKHRlc3RfZGF0YSkKCiMgUGxvdCB0aGUgcmVzdWx0cwpnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IGBkYXkub2YueWVhcmAsIHkgPSBgTWF4LlRlbXAuLi5DLmAsIGNvbG9yID0gIkFjdHVhbCIpLCBkYXRhID0gdGVzdF9kYXRhKSArCiAgZ2VvbV9saW5lKGFlcyh4ID0gYGRheS5vZi55ZWFyYCwgeSA9IC5wcmVkLCBjb2xvciA9ICJQcmVkaWN0ZWQiKSwgZGF0YSA9IHByZWRpY3Rpb25zKSArCiAgbGFicyh0aXRsZSA9ICJNYXggVGVtcGVyYXR1cmUgUHJlZGljdGlvbnMgYnkgRGF5IG9mIFllYXIgd2l0aCBzdGVwX3BvbHkiLAogICAgICAgeCA9ICJEYXkgb2YgWWVhciIsCiAgICAgICB5ID0gIk1heCBUZW1wZXJhdHVyZSAoQykiLAogICAgICAgY29sb3IgPSAiTGVnZW5kIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQoKCmBgYAoKQWltaW5nIGZvciBhIHNtb290aGVyIGZpdCwgdGhlIDEwLWRlZ3JlZSBwb2x5X2N1dCBtb2RlbCBkZWxpdmVycyB3aGF0cyBwcm9taXNlZC4gVGhlIHN1bW1hcnkgc3RhdGlzdGljcyBzaG93IGFuIGltcHJvdmVtZW50IHdpdGggYSBoaWdoZXIgUi1zcXVhcmVkIHZhbHVlIG9mIDAuNzgxNywgaW5kaWNhdGluZyB0aGF0IHRoZSBwb2x5bm9taWFsIG1vZGVsIGV4cGxhaW5zIG1vcmUgb2YgdGhlIHZhcmlhbmNlIGluIHRoZSBkYXRhIHRoYW4gdGhlIHByZXZpb3VzIGxpbmVhciBtb2RlbCB3aXRoIHN0ZXAgY3V0cy4gVGhlIGxvd2VyIHJlc2lkdWFsIHN0YW5kYXJkIGVycm9yIGFsc28gc3VnZ2VzdHMgYmV0dGVyIHByZWRpY3RpdmUgYWNjdXJhY3kuIEhpZ2hlciBkZWdyZWVzIG1pZ2h0IGZpdCB0aGUgdHJhaW5pbmcgZGF0YSBiZXR0ZXIsIGJ1dCBtaWdodCByZXN1bHQgaW4gYW4gb3ZlcmZpdHRpbmcKCiMjIyMgR0FNIG1vZGVsczoKCmBgYHtyfQpnYW1fbW9kZWwgPSBnZW5fYWRkaXRpdmVfbW9kKCBtb2RlID0gInJlZ3Jlc3Npb24iICkgfD4KICBzZXRfZW5naW5lKCJtZ2N2IikKZ2FtX2ZpdCA9IHdvcmtmbG93KCkgfD4KICBhZGRfdmFyaWFibGVzKG91dGNvbWVzID0gTWF4LlRlbXAuLi5DLiwgcHJlZGljdG9ycyA9IGMoWWVhciwgYGRheS5vZi55ZWFyYCkpIHw+CiAgICBhZGRfbW9kZWwoZ2FtX21vZGVsLAogICAgICBmb3JtdWxhID0gTWF4LlRlbXAuLi5DLiB+IHMoWWVhcikgKwogICAgICAgIHMoYGRheS5vZi55ZWFyYCkpIHw+CmZpdChkYXRhID0gdHJhaW5fZGF0YSkKZ2FtX2ZpdCB8PiBleHRyYWN0X2ZpdF9lbmdpbmUoKSB8PiBzdW1tYXJ5KCkKIyBnYW1fZml0IHw+IGV4dHJhY3RfZml0X2VuZ2luZSgpIHw+CiMgICBwbG90KHJlc2lkdWFscyA9IFRSVUUsIGNleCA9IDUsIHNoYWRlID0gVFJVRSkKCmdhbV9maXQgfD4gZXh0cmFjdF9maXRfZW5naW5lKCkgfD4gcGxvdChzY2hlbWUgPSAyLCByZXNpZHVhbHMgPSBUUlVFKQoKCgojIEdlbmVyYXRlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0aW5nIHNldApwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGdhbV9maXQsIG5ld19kYXRhID0gdGVzdF9kYXRhKSB8PgogIGJpbmRfY29scyh0ZXN0X2RhdGEpCgojIFBsb3QgdGhlIHJlc3VsdHMKZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBgZGF5Lm9mLnllYXJgLCB5ID0gYE1heC5UZW1wLi4uQy5gLCBjb2xvciA9ICJBY3R1YWwiKSwgZGF0YSA9IHRlc3RfZGF0YSkgKwogIGdlb21fbGluZShhZXMoeCA9IGBkYXkub2YueWVhcmAsIHkgPSAucHJlZCwgY29sb3IgPSAiUHJlZGljdGVkIiksIGRhdGEgPSBwcmVkaWN0aW9ucykgKwogIGxhYnModGl0bGUgPSAiTWF4IFRlbXBlcmF0dXJlIFByZWRpY3Rpb25zIGJ5IERheSBvZiBZZWFyIHdpdGggZ2FtX2ZpdCIsCiAgICAgICB4ID0gIkRheSBvZiBZZWFyIiwKICAgICAgIHkgPSAiTWF4IFRlbXBlcmF0dXJlIChDKSIsCiAgICAgICBjb2xvciA9ICJMZWdlbmQiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgpgYGAKClRoZSBHQU0gaXMgZml0dGluZyBhIG5vbi1saW5lYXIgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGRheSBvZiB0aGUgeWVhciBhbmQgdGVtcGVyYXR1cmUsIGFsbG93aW5nIGZvciBhIHNtb290aCBjdXJ2ZSB0aGF0IGNhcHR1cmVzIHRoZSBzZWFzb25hbCB0cmVuZC4gQ29tcGFyZWQgdG8gdGhlIGxpbmVhciBtb2RlbCwgdGhlIEdBTSBwcm92aWRlcyBhIG1vcmUgZmxleGlibGUgZml0LCBhY2NvbW1vZGF0aW5nIHRoZSBjeWNsaWNhbCBuYXR1cmUgb2YgdGVtcGVyYXR1cmUgY2hhbmdlcywgd2hpY2ggaXMgZXZpZGVuY2VkIGJ5IHRoZSBzbW9vdGggcHJlZGljdGlvbiBjdXJ2ZSBjbG9zZWx5IHRyYWNraW5nIHRoZSBhY3R1YWwgZGF0YSBwb2ludHMuCgpUaGUgc3VtbWFyeSBvZiB0aGUgR0FNIHNob3dzIHRoYXQgYm90aCB0aGUgc21vb3RoIHRlcm0gZm9yIHRoZSB5ZWFyICgqKmBzKFllYXIpYCoqKSBhbmQgdGhlIGRheSBvZiB0aGUgeWVhciAoKipgcyhkYXkub2YueWVhcilgKiopIGFyZSBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50LCB3aXRoIHRoZSBtb2RlbCBleHBsYWluaW5nIDc4LjUlIG9mIHRoZSBkZXZpYW5jZSAoYSBnb29kIGxldmVsIG9mIG1vZGVsIGZpdCBmb3IgdGhpcyB0eXBlIG9mIGRhdGEpLiBUaGUgbW9kZWwncyBhZGp1c3RlZCBSLXNxdWFyZWQgdmFsdWUgaXMgMC43ODUsIGluZGljYXRpbmcgdGhhdCBpdCBhY2NvdW50cyBmb3IgYSBzdWJzdGFudGlhbCBwcm9wb3J0aW9uIG9mIHRoZSB2YXJpYWJpbGl0eSBpbiB0aGUgbWF4aW11bSB0ZW1wZXJhdHVyZXMsIHdoaWNoIGlzIGFuIGltcHJvdmVtZW50IG92ZXIgc2ltcGxlciBsaW5lYXIgbW9kZWxzIGFuZCBqdXN0aWZpZXMgdGhlIHJlYXNvbiBiZWhpbmQgc2VsZWN0aW5nIGl0IGFzIHRoZSBiZXN0IGZpdCB0byBvdXIgZGF0YS4KCiMgUTI6IE9wdGlvbiB3aW5lOiBBbiBSIGF0dGVtcHQgKFNvbHV0aW9uIGlzIHN1Ym1ldHRlZCBpbiBqdXB5dGVyIG5vdGVib29rKSAKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkgIyByZWFkYWJsZSBjb2RlIHRoYXQgaXMgZWFzaWVyIHRvIHRyb3VibGVzaG9vdApsaWJyYXJ5KHRpZHltb2RlbHMpICMgYSBmcmFtZXdvcmsgZm9yIG1vZGVsbGluZyBpbiB0aWR5dmVyc2UKbGlicmFyeSh2aXApICMgdmFyaWFibGUgaW1wb3J0YW5jZQpsaWJyYXJ5KElTTFIyKSAjIEJvc3RvbiBkYXRhc2V0CmBgYAoKIyMjIyBMb2FkIHRoZSBkYXRhc2V0IGFuZCBwZXJmb3JtIGRhdGEgZXhwbG9yYXRpb24KCmBgYHtyfQojIExvYWQgdGhlIHdpbmUgcXVhbGl0eSBkYXRhc2V0CndpbmVfZGF0YSA8LSByZWFkX2NzdigiQXNzaWdubWVudF80X3dpbmUuY3N2IikKcXVhbGl0eV9jbGFzc2VzIDwtIHVuaXF1ZSh3aW5lX2RhdGEkcXVhbGl0eSkKbnVtX2NsYXNzZXMgPC0gbGVuZ3RoKHF1YWxpdHlfY2xhc3NlcykKcHJpbnQobnVtX2NsYXNzZXMpCnF1YWxpdHlfY291bnRzIDwtIHRhYmxlKHdpbmVfZGF0YSRxdWFsaXR5KQpwcmludChxdWFsaXR5X2NvdW50cykKCmdncGxvdCh3aW5lX2RhdGEsIGFlcyh4ID0gcXVhbGl0eSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEsIGJvdW5kYXJ5ID0gMC41LCBmaWxsID0gImJsdWUiLCBjb2xvciA9ICJibGFjayIpICsKICBsYWJzKHRpdGxlID0gIkhpc3RvZ3JhbSBvZiBXaW5lIFF1YWxpdHkiLCB4ID0gIlF1YWxpdHkiLCB5ID0gIkNvdW50IikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCldlIGNhbiBzZWUgaG93IHRoZSBxdWFsaXR5IGRhdGEgaXMgdW5pZm9ybWx5IGRpc3RyaWJ1dGVkLCB3aGljaCBzaG93cyBncmVhdCBjbGFzcyBpbWJhbGFuY2UuIFdlIG5lZWQgdG8gY29uc2lkZXIgdGhpcyB3aGVuIHNwbGl0dGluZyB0aGUgZGF0YSBiZXR3ZWVuIHRyYWluaW5nIGFuZCB0ZXN0LiBVc2luZyBzdHJhdGlmeSB3aWxsIGVuc3VyZSB0aGF0IGJvdGggdGVzdGluZyBhbmQgdHJhaW5pbmcgc2V0cyBoYXMgYWxsIGNsYXNzZXMKCiMjIyMgRGF0YSBzcGxpdCBhbmQgbW9kZWwgaW5pdGlhbGl6YXRpb24KCmBgYHtyfQojIFNwbGl0IHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cwpzZXQuc2VlZCg0MikKd2luZV9kYXRhJHF1YWxpdHkgPC0gYXMuZmFjdG9yKHdpbmVfZGF0YSRxdWFsaXR5KQpkYXRhX3NwbGl0IDwtIGluaXRpYWxfc3BsaXQod2luZV9kYXRhLCBwcm9wID0gMC44LCBzdHJhdGEgPSBxdWFsaXR5KQp0cmFpbl9kYXRhIDwtIHRyYWluaW5nKGRhdGFfc3BsaXQpCnRlc3RfZGF0YSA8LSB0ZXN0aW5nKGRhdGFfc3BsaXQpCmRhdGFfZm9sZHMgPSB2Zm9sZF9jdih0cmFpbl9kYXRhLCB2ID0gMTAsIHN0cmF0YSA9IHF1YWxpdHkpCgojIERlZmluZSB0aGUgbW9kZWwgYW5kIHJlY2lwZQpmb3Jlc3RfbW9kZWwgPC0gcmFuZF9mb3Jlc3QodHJlZXMgPSB0dW5lKCksIG10cnkgPSB0dW5lKCksIG1vZGUgPSAiY2xhc3NpZmljYXRpb24iKSB8PiAKICBzZXRfZW5naW5lKCJyYW5nZXIiLCBpbXBvcnRhbmNlID0gImltcHVyaXR5IikgfD4gCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikKCndpbmVfcmVjaXBlIDwtIHJlY2lwZShxdWFsaXR5IH4gLiwgZGF0YSA9IHRyYWluX2RhdGEpIAoKIyBDcmVhdGUgYSB3b3JrZmxvdwptb2RlbF9mbG93ID0gd29ya2Zsb3coKSB8PgogIGFkZF9yZWNpcGUoIHdpbmVfcmVjaXBlICkgfD4KICBhZGRfbW9kZWwoIGZvcmVzdF9tb2RlbCApCgpgYGAKCiMjIyMgTW9kZWwgRmluZS10dW5pbmcgd2l0aCBncmlkX3NlYXJjaDoKCkNvbnNpZGVyaW5nIHRoZSBtYXNzaXZlIGRhdGEgaW1iYWxhbmNlLCB3ZSBpbmNsdWRlIHRoZSBwcmVjaXNpb24gYW5kIHJlY2FsbCBpbiB0aGUgYW5hbHlzaXMuCgpgYGB7cn0KbXlfbWV0cmljcyA8LSBtZXRyaWNfc2V0KGFjY3VyYWN5LCBwcmVjaXNpb24sIHJlY2FsbCwgZl9tZWFzKQpncmlkX2N0cmwgPSBjb250cm9sX2dyaWQoc2F2ZV9wcmVkID0gRkFMU0UsCiAgYWxsb3dfcGFyID0gVFJVRSwgcGFyYWxsZWxfb3ZlciA9ICJldmVyeXRoaW5nIiwKICBzYXZlX3dvcmtmbG93ID0gVFJVRSkKVDEgPSBTeXMudGltZSgpCiMgUGVyZm9ybSB0aGUgZ3JpZCBzZWFyY2ggd2l0aCB0aGUgc3BlY2lmaWVkIG1ldHJpY3MKZ3JpZF90dW5lZCA8LSBtb2RlbF9mbG93IHw+IHR1bmVfZ3JpZCgKICByZXNhbXBsZXMgPSBkYXRhX2ZvbGRzLAogIGdyaWQgPSBncmlkX3JlZ3VsYXIoCiAgICBtdHJ5KHJhbmdlID0gYygyLCBuY29sKHRyYWluX2RhdGEpIC0gMSkpLAogICAgdHJlZXMocmFuZ2UgPSBjKDEsIDYwMCkpLAogICAgbGV2ZWxzID0gMTAKICApLAogIG1ldHJpY3MgPSBteV9tZXRyaWNzLCAKICBjb250cm9sID0gZ3JpZF9jdHJsCikKKGVsYXBzZWQgPSBTeXMudGltZSgpLVQxKQojIFByaW50IHRoZSBlbGFwc2VkIHRpbWUgYW5kIGdyaWQgc2VhcmNoIHJlc3VsdHMKCmBgYAoKYGBge3J9CnByaW50KGVsYXBzZWQpCnByaW50KGdyaWRfdHVuZWQpCmBgYAoKYGBge3J9CmJlc3RfcmVzdWx0cyA9IGdyaWRfdHVuZWQgfD4gc2VsZWN0X2Jlc3QobWV0cmljID0gICJmX21lYXMiKQpmaW5hbF9mb3Jlc3RfZml0ID0gbW9kZWxfZmxvdyB8PgogIGZpbmFsaXplX3dvcmtmbG93KGJlc3RfcmVzdWx0cykgfD4KICBsYXN0X2ZpdChzcGxpdCA9IGRhdGFfc3BsaXQpCmJlc3RfcmVzdWx0cwpgYGAKCkFmdGVyIGZpbmUtdHVuaW5nLCB3ZSBzZWUgdGhhdCB0aGUgYmVzdCBmMSBzY29yZSBwZXJmb3JtYW5jZSBvY2N1cmVkIHdpdGggbXRyeSA9IDIgYW5kIHRyZWVzID0gMTAwCgojIyMjIFBsb3R0aW5nIHRoZSB2YXJpYWJsZSBpbXBvcnRhbmNlOgoKYGBge3J9CgpuYW1lcyhzZWxlY3QodHJhaW5fZGF0YSwgLXF1YWxpdHkpKQoKYGBgCgpgYGB7cn0KCgpmaW5hbF93b3JrZmxvdyA8LSBmaW5hbGl6ZV93b3JrZmxvdyhtb2RlbF9mbG93LCBiZXN0X3Jlc3VsdHMpCiMgRml0IHRoZSBmaW5hbCBtb2RlbCB0byB0aGUgZW50aXJlIHRyYWluaW5nIGRhdGFzZXQKZmluYWxfZml0IDwtIGZpdChmaW5hbF93b3JrZmxvdywgZGF0YSA9IHRyYWluX2RhdGEpCgojIEV4dHJhY3QgdGhlIGZpdHRlZCBtb2RlbCBvYmplY3QKRml0b2JqZWN0IDwtIGZpbmFsX2ZpdCB8PiBleHRyYWN0X2ZpdF9lbmdpbmUoKQoKIyBEZWZpbmUgdGhlIHByZWRpY3Rpb24gZnVuY3Rpb24gZm9yIHRoZSBwZXJtdXRhdGlvbiBpbXBvcnRhbmNlCnByZWRfZnVuIDwtIGZ1bmN0aW9uKG1vZGVsLCBuZXdkYXRhKSB7CiAgcHJlZGljdChtb2RlbCwgbmV3ZGF0YSA9IG5ld2RhdGEsIHR5cGUgPSAicHJvYiIpJC5wcmVkX2NsYXNzCn0KCiMgQ2FsY3VsYXRlIHBlcm11dGF0aW9uLWJhc2VkIHZhcmlhYmxlIGltcG9ydGFuY2UKc2V0LnNlZWQoMTIzKSAjIEZvciByZXByb2R1Y2liaWxpdHkKdmlwX3Blcm0gPC0gdmkob2JqZWN0ID0gRml0b2JqZWN0LCAKICAgICAgICAgICAgICAgZmVhdHVyZV9uYW1lcyA9IG5hbWVzKHNlbGVjdCh0cmFpbl9kYXRhLCAtcXVhbGl0eSkpLCAKICAgICAgICAgICAgICAgdGFyZ2V0ID0gInF1YWxpdHkiLAogICAgICAgICAgICAgICBtZXRyaWMgPSAiZl9tZWFzIiwKICAgICAgICAgICAgICAgdHJhaW4gPSB0cmFpbl9kYXRhLAogICAgICAgICAgICAgICBwcmVkX3dyYXBwZXIgPSBwcmVkX2Z1biwKICAgICAgICAgICAgICAgbnNpbSA9IDEwMDAsCiAgICAgICAgICAgICAgIHBhcmFsbGVsID0gVFJVRSwKICAgICAgICAgICAgICAgdmVyYm9zZSA9IFRSVUUpCgojIFBsb3QgdGhlIHZhcmlhYmxlIGltcG9ydGFuY2UKdmlwKHZpcF9wZXJtKQoKYGBgCgpXZSBjYW4gc2VlIHRoYXQgdGhlIGFsY29ob2wgbGV2ZWwgaGFzIGxhcmdlciBpbXBvcnRhbmNlIGNvbXBhcmVkIHRvIHRoZSBvdGhlciBmZWF0dXJlcy4gSG93ZXZlciwgSSBhbSBub3Qgc3VyZSB3aHkgc29tZSBmZWF0dXJlcyBhcmUgbm90IGluIHRoZSBwbG90LiBJIGFzc3VtZSB0aGF0ICd0eXBlJyBmZWF0dXJlIGhhcyB2ZXJ5IGxpdHRsZSBpbXBvcnRhbmNlIGluIHByZWRpY3RpbmcgdGhlIHF1YWxpdHksIGJ1dCB0byBjb25maXJtIHRoaXMuIEkgcGVyZm9ybWVkIHRoZSBzYW1lIGFwcHJvYWNoIG9uIHB5dGhvbiB1c2luZyBzY2lraXQtbGVhcm4uIFRoZSByZXN1bHRzIGNvbmZpcm0gdGhlIHBsb3RzIGFib3ZlLgoKIyMgUTM6IFRoZSBhbmFseXNpcyBhbmQgcGxvdCBpcyBpbiB0aGUgaXB5bmIgZmlsZQoKcmVzdWx0czogd2UgY2FuIGNvbmZpcm0gdGhhdCBmb2xsb3dpbmcgYXJlIHRoZSBpbXBvcnRhbnQgdmFyaWFibGVzOiBYMTEgLCBYMSAsIFgzICwgWDQ3IFx8XHxcfCAtIFg2IC0gWDc4IC0gWDM4IC0gWDM2IC0gWDI5IC0gWDU0Cg==