1 Executive Summary

Note: This is the technical report. For business-friendly recommendations, please refer to the separate “Non-Technical Report for Leadership.”

1.1 Project Objective

New regulations require ABC Beverage to understand manufacturing factors that control pH and provide a working predictive model. We have historical data from 2,567 production runs and must predict pH for 267 new runs.

1.2 Key Findings

  • Random Forest selected as final model (RMSE = 0.0921, MAE = 0.0685, R² = 0.726)
  • Top 3 predictors: Manufacturing Flow, Bowl Setpoint, Filler Level
  • Brand matters: Significant pH differences across brands (p < 0.001)
  • Predictions delivered: Excel file with 90% confidence intervals (±0.1514 pH)

1.3 Load Training and Testing data

# Set working directory (adjust to your path)
# setwd("your/folder/path")
# Read the training data
train_data <- read_excel("StudentData.xlsx")
test_data <- read_excel("StudentEvaluation.xlsx")

1.3.1 Training Data

# Check structure of training data
cat("=== TRAINING DATA ===\n")
## === TRAINING DATA ===
cat("Dimensions:", dim(train_data), "\n")
## Dimensions: 2571 33
cat("\nColumn names:\n")
## 
## Column names:
print(names(train_data))
##  [1] "Brand Code"        "Carb Volume"       "Fill Ounces"      
##  [4] "PC Volume"         "Carb Pressure"     "Carb Temp"        
##  [7] "PSC"               "PSC Fill"          "PSC CO2"          
## [10] "Mnf Flow"          "Carb Pressure1"    "Fill Pressure"    
## [13] "Hyd Pressure1"     "Hyd Pressure2"     "Hyd Pressure3"    
## [16] "Hyd Pressure4"     "Filler Level"      "Filler Speed"     
## [19] "Temperature"       "Usage cont"        "Carb Flow"        
## [22] "Density"           "MFR"               "Balling"          
## [25] "Pressure Vacuum"   "PH"                "Oxygen Filler"    
## [28] "Bowl Setpoint"     "Pressure Setpoint" "Air Pressurer"    
## [31] "Alch Rel"          "Carb Rel"          "Balling Lvl"
cat("\nFirst few rows:\n")
## 
## First few rows:
print(head(train_data,3))
## # A tibble: 3 × 33
##   `Brand Code` `Carb Volume` `Fill Ounces` `PC Volume` `Carb Pressure`
##   <chr>                <dbl>         <dbl>       <dbl>           <dbl>
## 1 B                     5.34          24.0       0.263            68.2
## 2 A                     5.43          24.0       0.239            68.4
## 3 B                     5.29          24.1       0.263            70.8
## # ℹ 28 more variables: `Carb Temp` <dbl>, PSC <dbl>, `PSC Fill` <dbl>,
## #   `PSC CO2` <dbl>, `Mnf Flow` <dbl>, `Carb Pressure1` <dbl>,
## #   `Fill Pressure` <dbl>, `Hyd Pressure1` <dbl>, `Hyd Pressure2` <dbl>,
## #   `Hyd Pressure3` <dbl>, `Hyd Pressure4` <dbl>, `Filler Level` <dbl>,
## #   `Filler Speed` <dbl>, Temperature <dbl>, `Usage cont` <dbl>,
## #   `Carb Flow` <dbl>, Density <dbl>, MFR <dbl>, Balling <dbl>,
## #   `Pressure Vacuum` <dbl>, PH <dbl>, `Oxygen Filler` <dbl>, …
cat("\nSummary of training data:\n")
## 
## Summary of training data:
print(summary(train_data))
##   Brand Code         Carb Volume     Fill Ounces      PC Volume      
##  Length:2571        Min.   :5.040   Min.   :23.63   Min.   :0.07933  
##  Class :character   1st Qu.:5.293   1st Qu.:23.92   1st Qu.:0.23917  
##  Mode  :character   Median :5.347   Median :23.97   Median :0.27133  
##                     Mean   :5.370   Mean   :23.97   Mean   :0.27712  
##                     3rd Qu.:5.453   3rd Qu.:24.03   3rd Qu.:0.31200  
##                     Max.   :5.700   Max.   :24.32   Max.   :0.47800  
##                     NA's   :10      NA's   :38      NA's   :39       
##  Carb Pressure     Carb Temp          PSC             PSC Fill     
##  Min.   :57.00   Min.   :128.6   Min.   :0.00200   Min.   :0.0000  
##  1st Qu.:65.60   1st Qu.:138.4   1st Qu.:0.04800   1st Qu.:0.1000  
##  Median :68.20   Median :140.8   Median :0.07600   Median :0.1800  
##  Mean   :68.19   Mean   :141.1   Mean   :0.08457   Mean   :0.1954  
##  3rd Qu.:70.60   3rd Qu.:143.8   3rd Qu.:0.11200   3rd Qu.:0.2600  
##  Max.   :79.40   Max.   :154.0   Max.   :0.27000   Max.   :0.6200  
##  NA's   :27      NA's   :26      NA's   :33        NA's   :23      
##     PSC CO2           Mnf Flow       Carb Pressure1  Fill Pressure  
##  Min.   :0.00000   Min.   :-100.20   Min.   :105.6   Min.   :34.60  
##  1st Qu.:0.02000   1st Qu.:-100.00   1st Qu.:119.0   1st Qu.:46.00  
##  Median :0.04000   Median :  65.20   Median :123.2   Median :46.40  
##  Mean   :0.05641   Mean   :  24.57   Mean   :122.6   Mean   :47.92  
##  3rd Qu.:0.08000   3rd Qu.: 140.80   3rd Qu.:125.4   3rd Qu.:50.00  
##  Max.   :0.24000   Max.   : 229.40   Max.   :140.2   Max.   :60.40  
##  NA's   :39        NA's   :2         NA's   :32      NA's   :22     
##  Hyd Pressure1   Hyd Pressure2   Hyd Pressure3   Hyd Pressure4   
##  Min.   :-0.80   Min.   : 0.00   Min.   :-1.20   Min.   : 52.00  
##  1st Qu.: 0.00   1st Qu.: 0.00   1st Qu.: 0.00   1st Qu.: 86.00  
##  Median :11.40   Median :28.60   Median :27.60   Median : 96.00  
##  Mean   :12.44   Mean   :20.96   Mean   :20.46   Mean   : 96.29  
##  3rd Qu.:20.20   3rd Qu.:34.60   3rd Qu.:33.40   3rd Qu.:102.00  
##  Max.   :58.00   Max.   :59.40   Max.   :50.00   Max.   :142.00  
##  NA's   :11      NA's   :15      NA's   :15      NA's   :30      
##   Filler Level    Filler Speed   Temperature      Usage cont      Carb Flow   
##  Min.   : 55.8   Min.   : 998   Min.   :63.60   Min.   :12.08   Min.   :  26  
##  1st Qu.: 98.3   1st Qu.:3888   1st Qu.:65.20   1st Qu.:18.36   1st Qu.:1144  
##  Median :118.4   Median :3982   Median :65.60   Median :21.79   Median :3028  
##  Mean   :109.3   Mean   :3687   Mean   :65.97   Mean   :20.99   Mean   :2468  
##  3rd Qu.:120.0   3rd Qu.:3998   3rd Qu.:66.40   3rd Qu.:23.75   3rd Qu.:3186  
##  Max.   :161.2   Max.   :4030   Max.   :76.20   Max.   :25.90   Max.   :5104  
##  NA's   :20      NA's   :57     NA's   :14      NA's   :5       NA's   :2     
##     Density           MFR           Balling       Pressure Vacuum 
##  Min.   :0.240   Min.   : 31.4   Min.   :-0.170   Min.   :-6.600  
##  1st Qu.:0.900   1st Qu.:706.3   1st Qu.: 1.496   1st Qu.:-5.600  
##  Median :0.980   Median :724.0   Median : 1.648   Median :-5.400  
##  Mean   :1.174   Mean   :704.0   Mean   : 2.198   Mean   :-5.216  
##  3rd Qu.:1.620   3rd Qu.:731.0   3rd Qu.: 3.292   3rd Qu.:-5.000  
##  Max.   :1.920   Max.   :868.6   Max.   : 4.012   Max.   :-3.600  
##  NA's   :1       NA's   :212     NA's   :1                        
##        PH        Oxygen Filler     Bowl Setpoint   Pressure Setpoint
##  Min.   :7.880   Min.   :0.00240   Min.   : 70.0   Min.   :44.00    
##  1st Qu.:8.440   1st Qu.:0.02200   1st Qu.:100.0   1st Qu.:46.00    
##  Median :8.540   Median :0.03340   Median :120.0   Median :46.00    
##  Mean   :8.546   Mean   :0.04684   Mean   :109.3   Mean   :47.62    
##  3rd Qu.:8.680   3rd Qu.:0.06000   3rd Qu.:120.0   3rd Qu.:50.00    
##  Max.   :9.360   Max.   :0.40000   Max.   :140.0   Max.   :52.00    
##  NA's   :4       NA's   :12        NA's   :2       NA's   :12       
##  Air Pressurer      Alch Rel        Carb Rel      Balling Lvl  
##  Min.   :140.8   Min.   :5.280   Min.   :4.960   Min.   :0.00  
##  1st Qu.:142.2   1st Qu.:6.540   1st Qu.:5.340   1st Qu.:1.38  
##  Median :142.6   Median :6.560   Median :5.400   Median :1.48  
##  Mean   :142.8   Mean   :6.897   Mean   :5.437   Mean   :2.05  
##  3rd Qu.:143.0   3rd Qu.:7.240   3rd Qu.:5.540   3rd Qu.:3.14  
##  Max.   :148.2   Max.   :8.620   Max.   :6.060   Max.   :3.66  
##                  NA's   :9       NA's   :10      NA's   :1

Key Insights from Training Data:

  • pH is numeric in training data (range: 7.88 to 9.36, mean ~8.55) — this is our target variable

  • Dataset have missing values that need handling before modeling

  • 33 predictor variables available (excluding Brand Code which is categorical)

1.3.2 Test Data

cat("\n\n=== TEST DATA ===\n")
## 
## 
## === TEST DATA ===
cat("Dimensions:", dim(test_data), "\n")
## Dimensions: 267 33
cat("\nColumn names:\n")
## 
## Column names:
print(names(test_data))
##  [1] "Brand Code"        "Carb Volume"       "Fill Ounces"      
##  [4] "PC Volume"         "Carb Pressure"     "Carb Temp"        
##  [7] "PSC"               "PSC Fill"          "PSC CO2"          
## [10] "Mnf Flow"          "Carb Pressure1"    "Fill Pressure"    
## [13] "Hyd Pressure1"     "Hyd Pressure2"     "Hyd Pressure3"    
## [16] "Hyd Pressure4"     "Filler Level"      "Filler Speed"     
## [19] "Temperature"       "Usage cont"        "Carb Flow"        
## [22] "Density"           "MFR"               "Balling"          
## [25] "Pressure Vacuum"   "PH"                "Oxygen Filler"    
## [28] "Bowl Setpoint"     "Pressure Setpoint" "Air Pressurer"    
## [31] "Alch Rel"          "Carb Rel"          "Balling Lvl"
cat("\nFirst few rows:\n")
## 
## First few rows:
print(head(test_data,3))
## # A tibble: 3 × 33
##   `Brand Code` `Carb Volume` `Fill Ounces` `PC Volume` `Carb Pressure`
##   <chr>                <dbl>         <dbl>       <dbl>           <dbl>
## 1 D                     5.48          24.0       0.27             65.4
## 2 A                     5.39          24.0       0.227            63.2
## 3 B                     5.29          23.9       0.303            66.4
## # ℹ 28 more variables: `Carb Temp` <dbl>, PSC <dbl>, `PSC Fill` <dbl>,
## #   `PSC CO2` <dbl>, `Mnf Flow` <dbl>, `Carb Pressure1` <dbl>,
## #   `Fill Pressure` <dbl>, `Hyd Pressure1` <dbl>, `Hyd Pressure2` <dbl>,
## #   `Hyd Pressure3` <dbl>, `Hyd Pressure4` <dbl>, `Filler Level` <dbl>,
## #   `Filler Speed` <dbl>, Temperature <dbl>, `Usage cont` <dbl>,
## #   `Carb Flow` <dbl>, Density <dbl>, MFR <dbl>, Balling <dbl>,
## #   `Pressure Vacuum` <dbl>, PH <lgl>, `Oxygen Filler` <dbl>, …
cat("\nSummary of test data:\n")
## 
## Summary of test data:
print(summary(test_data))
##   Brand Code         Carb Volume     Fill Ounces      PC Volume      
##  Length:267         Min.   :5.147   Min.   :23.75   Min.   :0.09867  
##  Class :character   1st Qu.:5.287   1st Qu.:23.92   1st Qu.:0.23333  
##  Mode  :character   Median :5.340   Median :23.97   Median :0.27533  
##                     Mean   :5.369   Mean   :23.97   Mean   :0.27769  
##                     3rd Qu.:5.465   3rd Qu.:24.01   3rd Qu.:0.32200  
##                     Max.   :5.667   Max.   :24.20   Max.   :0.46400  
##                     NA's   :1       NA's   :6       NA's   :4        
##  Carb Pressure     Carb Temp          PSC             PSC Fill     
##  Min.   :60.20   Min.   :130.0   Min.   :0.00400   Min.   :0.0200  
##  1st Qu.:65.30   1st Qu.:138.4   1st Qu.:0.04450   1st Qu.:0.1000  
##  Median :68.00   Median :140.8   Median :0.07600   Median :0.1800  
##  Mean   :68.25   Mean   :141.2   Mean   :0.08545   Mean   :0.1903  
##  3rd Qu.:70.60   3rd Qu.:143.8   3rd Qu.:0.11200   3rd Qu.:0.2600  
##  Max.   :77.60   Max.   :154.0   Max.   :0.24600   Max.   :0.6200  
##                  NA's   :1       NA's   :5         NA's   :3       
##     PSC CO2           Mnf Flow       Carb Pressure1  Fill Pressure  
##  Min.   :0.00000   Min.   :-100.20   Min.   :113.0   Min.   :37.80  
##  1st Qu.:0.02000   1st Qu.:-100.00   1st Qu.:120.2   1st Qu.:46.00  
##  Median :0.04000   Median :   0.20   Median :123.4   Median :47.80  
##  Mean   :0.05107   Mean   :  21.03   Mean   :123.0   Mean   :48.14  
##  3rd Qu.:0.06000   3rd Qu.: 141.30   3rd Qu.:125.5   3rd Qu.:50.20  
##  Max.   :0.24000   Max.   : 220.40   Max.   :136.0   Max.   :60.20  
##  NA's   :5                           NA's   :4       NA's   :2      
##  Hyd Pressure1    Hyd Pressure2    Hyd Pressure3    Hyd Pressure4   
##  Min.   :-50.00   Min.   :-50.00   Min.   :-50.00   Min.   : 68.00  
##  1st Qu.:  0.00   1st Qu.:  0.00   1st Qu.:  0.00   1st Qu.: 90.00  
##  Median : 10.40   Median : 26.80   Median : 27.70   Median : 98.00  
##  Mean   : 12.01   Mean   : 20.11   Mean   : 19.61   Mean   : 97.84  
##  3rd Qu.: 20.40   3rd Qu.: 34.80   3rd Qu.: 33.00   3rd Qu.:104.00  
##  Max.   : 50.00   Max.   : 61.40   Max.   : 49.20   Max.   :140.00  
##                   NA's   :1        NA's   :1        NA's   :4       
##   Filler Level    Filler Speed   Temperature      Usage cont      Carb Flow   
##  Min.   : 69.2   Min.   :1006   Min.   :63.80   Min.   :12.90   Min.   :   0  
##  1st Qu.:100.6   1st Qu.:3812   1st Qu.:65.40   1st Qu.:18.12   1st Qu.:1083  
##  Median :118.6   Median :3978   Median :65.80   Median :21.44   Median :3038  
##  Mean   :110.3   Mean   :3581   Mean   :66.23   Mean   :20.90   Mean   :2409  
##  3rd Qu.:120.2   3rd Qu.:3996   3rd Qu.:66.60   3rd Qu.:23.74   3rd Qu.:3215  
##  Max.   :153.2   Max.   :4020   Max.   :75.40   Max.   :24.60   Max.   :3858  
##  NA's   :2       NA's   :10     NA's   :2       NA's   :2                     
##     Density           MFR           Balling      Pressure Vacuum 
##  Min.   :0.060   Min.   : 15.6   Min.   :0.902   Min.   :-6.400  
##  1st Qu.:0.920   1st Qu.:707.0   1st Qu.:1.498   1st Qu.:-5.600  
##  Median :0.980   Median :724.6   Median :1.648   Median :-5.200  
##  Mean   :1.177   Mean   :697.8   Mean   :2.203   Mean   :-5.174  
##  3rd Qu.:1.600   3rd Qu.:731.5   3rd Qu.:3.242   3rd Qu.:-4.800  
##  Max.   :1.840   Max.   :784.8   Max.   :3.788   Max.   :-3.600  
##  NA's   :1       NA's   :31      NA's   :1       NA's   :1       
##     PH          Oxygen Filler     Bowl Setpoint   Pressure Setpoint
##  Mode:logical   Min.   :0.00240   Min.   : 70.0   Min.   :44.00    
##  NA's:267       1st Qu.:0.01960   1st Qu.:100.0   1st Qu.:46.00    
##                 Median :0.03370   Median :120.0   Median :46.00    
##                 Mean   :0.04666   Mean   :109.6   Mean   :47.73    
##                 3rd Qu.:0.05440   3rd Qu.:120.0   3rd Qu.:50.00    
##                 Max.   :0.39800   Max.   :130.0   Max.   :52.00    
##                 NA's   :3         NA's   :1       NA's   :2        
##  Air Pressurer      Alch Rel        Carb Rel     Balling Lvl   
##  Min.   :141.2   Min.   :6.400   Min.   :5.18   Min.   :0.000  
##  1st Qu.:142.2   1st Qu.:6.540   1st Qu.:5.34   1st Qu.:1.380  
##  Median :142.6   Median :6.580   Median :5.40   Median :1.480  
##  Mean   :142.8   Mean   :6.907   Mean   :5.44   Mean   :2.051  
##  3rd Qu.:142.8   3rd Qu.:7.180   3rd Qu.:5.56   3rd Qu.:3.080  
##  Max.   :147.2   Max.   :7.820   Max.   :5.74   Max.   :3.420  
##  NA's   :1       NA's   :3       NA's   :2

1.3.3 Check missing values

# Check for missing values in both datasets
cat("\n=== MISSING VALUES ===\n")
## 
## === MISSING VALUES ===
cat("Training data missing values per column:\n")
## Training data missing values per column:
print(colSums(is.na(train_data)))
##        Brand Code       Carb Volume       Fill Ounces         PC Volume 
##               120                10                38                39 
##     Carb Pressure         Carb Temp               PSC          PSC Fill 
##                27                26                33                23 
##           PSC CO2          Mnf Flow    Carb Pressure1     Fill Pressure 
##                39                 2                32                22 
##     Hyd Pressure1     Hyd Pressure2     Hyd Pressure3     Hyd Pressure4 
##                11                15                15                30 
##      Filler Level      Filler Speed       Temperature        Usage cont 
##                20                57                14                 5 
##         Carb Flow           Density               MFR           Balling 
##                 2                 1               212                 1 
##   Pressure Vacuum                PH     Oxygen Filler     Bowl Setpoint 
##                 0                 4                12                 2 
## Pressure Setpoint     Air Pressurer          Alch Rel          Carb Rel 
##                12                 0                 9                10 
##       Balling Lvl 
##                 1
cat("\nTest data missing values per column:\n")
## 
## Test data missing values per column:
print(colSums(is.na(test_data)))
##        Brand Code       Carb Volume       Fill Ounces         PC Volume 
##                 8                 1                 6                 4 
##     Carb Pressure         Carb Temp               PSC          PSC Fill 
##                 0                 1                 5                 3 
##           PSC CO2          Mnf Flow    Carb Pressure1     Fill Pressure 
##                 5                 0                 4                 2 
##     Hyd Pressure1     Hyd Pressure2     Hyd Pressure3     Hyd Pressure4 
##                 0                 1                 1                 4 
##      Filler Level      Filler Speed       Temperature        Usage cont 
##                 2                10                 2                 2 
##         Carb Flow           Density               MFR           Balling 
##                 0                 1                31                 1 
##   Pressure Vacuum                PH     Oxygen Filler     Bowl Setpoint 
##                 1               267                 3                 1 
## Pressure Setpoint     Air Pressurer          Alch Rel          Carb Rel 
##                 2                 1                 3                 2 
##       Balling Lvl 
##                 0

1.4 Problem Understanding

1.4.1 Business Goal:

ABC Beverage need to comply with new regulations requiring you to:

  • Understand your manufacturing process and which factors predict pH
  • Report a predictive model for pH to leadership

1.4.2 Data Situation:

  • Training data (StudentData.xlsx): Has historical manufacturing measurements INCLUDING pH values (it contains pH as numeric)
  • Test data (StudentEvaluation.xlsx): Has the SAME manufacturing measurements BUT pH is empty/logical (all NA values) — this is what we need to predict

Challenge: Missing values in BOTH datasets across many columns (Brand Code has 120 NAs in training, MFR has 212 NAs, etc.)

1.5 Data preprocessing

# Create a copy for preprocessing
train_clean <- train_data
test_clean <- test_data

# IDENTIFY COLUMN TYPES
cat("=== COLUMN TYPES IN TRAINING ===\n")
## === COLUMN TYPES IN TRAINING ===
col_types <- sapply(train_clean, class)
print(table(col_types))
## col_types
## character   numeric 
##         1        32
# Separate features and target in training
# Remove rows where PH is missing (only 4 NAs in training)
train_clean <- train_clean %>% filter(!is.na(PH))
cat("\nTraining rows after removing NA in PH:", nrow(train_clean), "\n")
## 
## Training rows after removing NA in PH: 2567
# For test data, PH is all NA - we'll keep it as placeholder
# But we need to ensure test has same structure as training features

# Handle Brand Code as factor (categorical)
train_clean$`Brand Code` <- as.factor(train_clean$`Brand Code`)
test_clean$`Brand Code` <- as.factor(test_clean$`Brand Code`)

# Check levels match
cat("\nBrand Code levels in training:", levels(train_clean$`Brand Code`), "\n")
## 
## Brand Code levels in training: A B C D
cat("Brand Code levels in test:", levels(test_clean$`Brand Code`), "\n")
## Brand Code levels in test: A B C D
# MISSING VALUE ANALYSIS
cat("\n=== MISSING VALUE SUMMARY ===\n")
## 
## === MISSING VALUE SUMMARY ===
missing_train <- sort(colSums(is.na(train_clean)), decreasing = TRUE)
missing_train <- missing_train[missing_train > 0]
cat("Columns with missing values in training:\n")
## Columns with missing values in training:
print(missing_train)
##               MFR        Brand Code      Filler Speed         PC Volume 
##               208               120                54                39 
##           PSC CO2       Fill Ounces               PSC    Carb Pressure1 
##                39                38                33                32 
##     Hyd Pressure4     Carb Pressure         Carb Temp          PSC Fill 
##                28                27                26                23 
##     Fill Pressure      Filler Level     Hyd Pressure2     Hyd Pressure3 
##                18                16                15                15 
##       Temperature Pressure Setpoint     Hyd Pressure1     Oxygen Filler 
##                12                12                11                11 
##       Carb Volume          Carb Rel          Alch Rel        Usage cont 
##                10                 8                 7                 5 
##         Carb Flow     Bowl Setpoint       Balling Lvl 
##                 2                 2                 1
# Identify columns with >30% missing in training
high_missing <- names(missing_train[missing_train > 0.3 * nrow(train_clean)])
cat("\nColumns with >30% missing:", high_missing, "\n")
## 
## Columns with >30% missing:
# For now, let's remove columns with >50% missing
threshold <- 0.5 * nrow(train_clean)
cols_to_remove <- names(missing_train[missing_train > threshold])
cat("Columns to remove (>50% missing):", cols_to_remove, "\n")
## Columns to remove (>50% missing):
# Remove these columns from both datasets
if(length(cols_to_remove) > 0) {
  train_clean <- train_clean %>% select(-one_of(cols_to_remove))
  test_clean <- test_clean %>% select(-one_of(cols_to_remove))
}

1.5.1 Handle Missing Data with Imputation

# For remaining missing values, use median/mode imputation
# Separate numeric and character columns
numeric_cols <- names(train_clean)[sapply(train_clean, is.numeric)]
factor_cols <- names(train_clean)[sapply(train_clean, is.factor)]

# Impute numeric columns with median
for(col in numeric_cols) {
  if(any(is.na(train_clean[[col]]))) {
    med_val <- median(train_clean[[col]], na.rm = TRUE)
    train_clean[[col]][is.na(train_clean[[col]])] <- med_val
  }
  if(any(is.na(test_clean[[col]]))) {
    med_val <- median(test_clean[[col]], na.rm = TRUE)
    test_clean[[col]][is.na(test_clean[[col]])] <- med_val
  }
}

# Impute factor columns with mode
for(col in factor_cols) {
  if(any(is.na(train_clean[[col]]))) {
    mode_val <- names(sort(table(train_clean[[col]]), decreasing = TRUE))[1]
    train_clean[[col]][is.na(train_clean[[col]])] <- mode_val
  }
  if(any(is.na(test_clean[[col]]))) {
    mode_val <- names(sort(table(test_clean[[col]]), decreasing = TRUE))[1]
    test_clean[[col]][is.na(test_clean[[col]])] <- mode_val
  }
}

# Verify no missing values remain
cat("\n=== MISSING VALUES AFTER CLEANING ===\n")
## 
## === MISSING VALUES AFTER CLEANING ===
cat("Training:", sum(is.na(train_clean)), "\n")
## Training: 0
cat("Test:", sum(is.na(test_clean)), "\n")
## Test: 267

1.5.1.1 Summary of handling missing values

  • Removed target variable NAs: Dropped 4 rows from training where pH was missing, leaving 2,567 complete training records

  • Handled missing values in predictors: Imputed numeric columns with median values and categorical (Brand Code) with mode; no columns exceeded 50% missing, so all 33 original features were retained

  • Aligned datasets: Ensured both training and test data have identical structure with same 33 columns and matching factor levels (A, B, C, D for Brand Code)

1.5.2 Outliers and Anomalies Detection

# Ensure factor
train_clean$Brand_Code <- as.factor(train_clean$'Brand Code')
test_clean$Brand_Code <- as.factor(test_clean$'Brand Code')

# 1. OUTLIER DETECTION using IQR method
cat("=== OUTLIER ANALYSIS ===\n")
## === OUTLIER ANALYSIS ===
# Get numeric columns (exclude Brand Code and PH)
numeric_cols <- names(train_clean)[sapply(train_clean, is.numeric)]
numeric_cols <- numeric_cols[!numeric_cols %in% c("PH")]

outlier_summary <- data.frame(
  Variable = character(),
  Outliers_Count = integer(),
  Outliers_Percent = numeric(),
  stringsAsFactors = FALSE
)

for(col in numeric_cols) {
  Q1 <- quantile(train_clean[[col]], 0.25, na.rm = TRUE)
  Q3 <- quantile(train_clean[[col]], 0.75, na.rm = TRUE)
  IQR <- Q3 - Q1
  lower_bound <- Q1 - 1.5 * IQR
  upper_bound <- Q3 + 1.5 * IQR
  
  outliers <- sum(train_clean[[col]] < lower_bound | train_clean[[col]] > upper_bound, na.rm = TRUE)
  outlier_pct <- (outliers / nrow(train_clean)) * 100
  
  outlier_summary <- rbind(outlier_summary, 
                           data.frame(Variable = col, 
                                      Outliers_Count = outliers, 
                                      Outliers_Percent = round(outlier_pct, 2)))
}

# Show top 10 variables with most outliers
outlier_summary <- outlier_summary[order(-outlier_summary$Outliers_Percent), ]
cat("\nTop 10 variables with highest outlier percentage:\n")
## 
## Top 10 variables with highest outlier percentage:
print(head(outlier_summary, 10))
##           Variable Outliers_Count Outliers_Percent
## 17    Filler Speed            440            17.14
## 22             MFR            279            10.87
## 28   Air Pressurer            220             8.57
## 25   Oxygen Filler            193             7.52
## 24 Pressure Vacuum            125             4.87
## 18     Temperature            122             4.75
## 3        PC Volume             95             3.70
## 15   Hyd Pressure4             82             3.19
## 11   Fill Pressure             79             3.08
## 8          PSC CO2             77             3.00

1.5.2.1 Data Quality Findings

1.5.2.2 Top 3 variables with most outliers:

  • Filler Speed - 17.1% outliers (440 out of 2,567 rows)
  • MFR - 10.9% outliers (279 rows)
  • Air Pressurer - 8.6% outliers (220 rows)

Key insight: These are not data entry errors — they represent legitimate manufacturing variation. Filler speed extremes likely occur during production ramp-up/down or different product runs.

1.5.2.3 Recommendation

**Lets Keep outliers but we will document them. Why?

  • Manufacturing processes naturally have extreme values (startup, shutdown, different brands)

  • Removing 17% of data would bias our model

  • These extremes help predict pH across full operating range

1.5.3 Check Negative and zero values

# 2. CHECK FOR IMPOSSIBLE/NONSENSICAL VALUES
cat("\n=== LOGICAL CONSISTENCY CHECKS ===\n")
## 
## === LOGICAL CONSISTENCY CHECKS ===
# Check for negative pressures (should be impossible)
pressure_cols <- grep("Pressure", names(train_clean), value = TRUE)
for(col in pressure_cols) {
  negatives <- sum(train_clean[[col]] < 0, na.rm = TRUE)
  if(negatives > 0) {
    cat(col, ": ", negatives, "negative values (range:", min(train_clean[[col]]), "to", max(train_clean[[col]]), ")\n")
  }
}
## Hyd Pressure1 :  38 negative values (range: -0.8 to 58 )
## Hyd Pressure3 :  70 negative values (range: -1.2 to 50 )
## Pressure Vacuum :  2567 negative values (range: -6.6 to -3.6 )
# Check for zero or negative in volume/flow measurements
volume_cols <- c("Carb.Volume", "Fill.Ounces", "PC.Volume", "Carb.Flow")
for(col in volume_cols) {
  if(col %in% names(train_clean)) {
    zeros_or_neg <- sum(train_clean[[col]] <= 0, na.rm = TRUE)
    if(zeros_or_neg > 0) {
      cat(col, ": ", zeros_or_neg, "values <= 0\n")
    }
  }
}

# 3. CHECK DATA TYPES AND FORMATS
cat("\n=== DATA TYPE VERIFICATION ===\n")
## 
## === DATA TYPE VERIFICATION ===
cat("pH range in training:", range(train_clean$PH), "\n")
## pH range in training: 7.88 9.36
cat("pH should be between 0-14. All values valid:", 
    all(train_clean$PH >= 0 & train_clean$PH <= 14), "\n")
## pH should be between 0-14. All values valid: TRUE
# Check for duplicate rows
duplicates <- sum(duplicated(train_clean[, !names(train_clean) %in% "PH"]))
cat("Duplicate rows (excluding pH):", duplicates, "\n")
## Duplicate rows (excluding pH): 0

1.5.3.1 Key Findings from Quality Checks

Negative values detected :

  • Hyd Pressure1: 38 negative values (down to -0.8)

  • Hyd Pressure3: 70 negative values (down to -1.2)

_ Pressure Vacuum: ALL values negative (-6.6 to -3.6) — this is correct (vacuum = negative pressure)

No duplicate rows — data is clean

pH range: 7.88 to 9.36 (normal for beverages)

1.5.4 Save clean Datasets

# Save cleaned data
write.csv(train_clean, "training_data_cleaned.csv", row.names = FALSE)
write.csv(test_clean, "test_data_cleaned.csv", row.names = FALSE)

cat("\n=== CLEANING COMPLETE ===\n")
## 
## === CLEANING COMPLETE ===
cat("Final training dimensions:", dim(train_clean), "\n")
## Final training dimensions: 2567 34
cat("Final test dimensions:", dim(test_clean), "\n")
## Final test dimensions: 267 34

1.6 Exploratory Data Analysis

1.6.1 Correlation Matrix for pH Prediction

# EDA: Correlation Matrix for pH Prediction

# Ensure Brand Code is factor
train_clean$`Brand Code` <- as.factor(train_clean$`Brand Code`)

# Calculate correlations with pH
numeric_cols <- names(train_clean)[sapply(train_clean, is.numeric)]
target_correlations <- data.frame(
  Variable = character(),
  Correlation = numeric(),
  stringsAsFactors = FALSE
)

for(col in numeric_cols) {
  if(col != "PH") {
    cor_val <- cor(train_clean[[col]], train_clean$PH, use = "complete.obs")
    target_correlations <- rbind(target_correlations, 
                                  data.frame(Variable = col, 
                                             Correlation = round(cor_val, 3)))
  }
}

# Sort by absolute correlation
target_correlations <- target_correlations[order(-abs(target_correlations$Correlation)), ]

cat("=== TOP 15 VARIABLES MOST CORRELATED WITH pH ===\n")
## === TOP 15 VARIABLES MOST CORRELATED WITH pH ===
print(head(target_correlations, 15))
##             Variable Correlation
## 9           Mnf Flow      -0.447
## 26     Bowl Setpoint       0.349
## 16      Filler Level       0.322
## 19        Usage cont      -0.318
## 27 Pressure Setpoint      -0.306
## 14     Hyd Pressure3      -0.240
## 24   Pressure Vacuum       0.221
## 11     Fill Pressure      -0.212
## 13     Hyd Pressure2      -0.201
## 25     Oxygen Filler       0.166
## 30          Carb Rel       0.162
## 18       Temperature      -0.158
## 20         Carb Flow       0.156
## 29          Alch Rel       0.147
## 15     Hyd Pressure4      -0.140
# Create correlation matrix for top variables
top_vars <- head(target_correlations$Variable, 15)
top_vars <- c(top_vars, "PH")
cor_matrix <- cor(train_clean[, top_vars], use = "complete.obs")

# Visualize correlation matrix
corrplot(cor_matrix, 
         method = "color", 
         type = "upper",
         tl.cex = 0.8,
         tl.col = "black",
         title = "Top 15 Variable Correlations with pH",
         mar = c(0,0,2,0))

# Bar plot of top correlations with pH
ggplot(head(target_correlations, 15), aes(x = reorder(Variable, abs(Correlation)), 
                                            y = Correlation, 
                                            fill = Correlation > 0)) +
  geom_bar(stat = "identity") +
  coord_flip() +
  labs(title = "Top 15 Factors Correlated with pH",
       x = "Process Variables",
       y = "Correlation with pH") +
  theme_minimal() +
  scale_fill_manual(values = c("darkred", "darkgreen"), 
                    labels = c("Negative", "Positive"),
                    name = "Direction")

# Summary statistics for top 3 predictors vs pH
top_3 <- head(target_correlations$Variable, 3)
cat("\n=== TOP 3 PREDICTORS SUMMARY ===\n")
## 
## === TOP 3 PREDICTORS SUMMARY ===
for(var in top_3) {
  cat("\n", var, "(Correlation:", target_correlations[target_correlations$Variable == var, "Correlation"], ")\n")
  print(summary(train_clean[[var]]))
}
## 
##  Mnf Flow (Correlation: -0.447 )
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
## -100.20 -100.00   70.20   24.63  140.80  229.40 
## 
##  Bowl Setpoint (Correlation: 0.349 )
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    70.0   100.0   120.0   109.4   120.0   140.0 
## 
##  Filler Level (Correlation: 0.322 )
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##    55.8    98.7   118.4   109.3   120.0   161.2

1.6.1.1 Correlation Analysis Findings

1.6.1.2 Top pH Predictors Identified:

  • Mnf Flow (Manufacturing Flow): Strongest negative correlation (-0.447) — Higher flow rates = Lower pH

  • Bowl Setpoint: Strong positive correlation (0.349) — Higher setpoint = Higher pH

  • Filler Level: Moderate positive correlation (0.322) — Higher fill level = Higher pH

  • Usage cont: Negative correlation (-0.318) — More usage = Lower pH

  • Pressure Setpoint: Negative correlation (-0.306) — Higher pressure = Lower pH

Key Insight: Manufacturing flow rate (Mnf Flow) is the single most important factor controlling pH, followed by bowl setpoint and filler level.

1.6.2 Brand Analysis

# EDA: Brand Analysis - Does pH differ by brand?


# Calculate pH statistics by brand
brand_stats <- train_clean %>%
  group_by(`Brand Code`) %>%
  summarise(
    Count = n(),
    Mean_pH = round(mean(PH), 3),
    SD_pH = round(sd(PH), 3),
    Min_pH = round(min(PH), 3),
    Max_pH = round(max(PH), 3),
    .groups = 'drop'
  )

cat("=== PH STATISTICS BY BRAND ===\n")
## === PH STATISTICS BY BRAND ===
print(brand_stats)
## # A tibble: 4 × 6
##   `Brand Code` Count Mean_pH SD_pH Min_pH Max_pH
##   <fct>        <int>   <dbl> <dbl>  <dbl>  <dbl>
## 1 A              293    8.50 0.163   8.06   8.86
## 2 B             1355    8.56 0.171   8      8.94
## 3 C              304    8.41 0.177   7.88   9.36
## 4 D              615    8.60 0.135   8.16   8.92
# ANOVA test to check if brand differences are significant
anova_result <- aov(PH ~ `Brand Code`, data = train_clean)
cat("\n=== ANOVA RESULTS ===\n")
## 
## === ANOVA RESULTS ===
print(summary(anova_result))
##                Df Sum Sq Mean Sq F value Pr(>F)    
## `Brand Code`    3   8.24  2.7462   103.3 <2e-16 ***
## Residuals    2563  68.13  0.0266                   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Visualization: Boxplot of pH by brand
ggplot(train_clean, aes(x = `Brand Code`, y = PH, fill = `Brand Code`)) +
  geom_boxplot() +
  labs(title = "pH Distribution by Brand Code",
       x = "Brand",
       y = "pH Level") +
  theme_minimal() +
  scale_fill_brewer(palette = "Set2") +
  stat_summary(fun = mean, geom = "point", shape = 18, size = 3, color = "red")

# Visualization: Density plot
ggplot(train_clean, aes(x = PH, fill = `Brand Code`)) +
  geom_density(alpha = 0.5) +
  labs(title = "pH Density Distribution by Brand",
       x = "pH",
       y = "Density") +
  theme_minimal() +
  scale_fill_brewer(palette = "Set2")

1.6.2.1 Brand Analysis Summary

  • Significant pH differences across brands (p-value < 0.001) — Brand D has highest pH (8.60), Brand C lowest (8.41)

  • Brand B has largest volume (1,355 batches) and wide pH range (8.00 to 8.94)

  • Brand C shows most variation (SD = 0.177) with extreme values (7.88 to 9.36)

1.6.3 Interaction Effects

# EDA: Interaction Effects - Do combinations of factors predict pH better?

# Interaction plot: Mnf Flow × Brand on pH
ggplot(train_clean, aes(x = `Mnf Flow`, y = PH, color = `Brand Code`)) +
  geom_point(alpha = 0.3) +
  geom_smooth(method = "lm", se = FALSE) +
  labs(title = "Interaction: Manufacturing Flow × Brand on pH",
       x = "Manufacturing Flow (Mnf Flow)",
       y = "pH",
       color = "Brand") +
  theme_minimal()

# Check interaction: Bowl Setpoint × Filler Level
ggplot(train_clean, aes(x = `Bowl Setpoint`, y = PH, color = `Filler Level`)) +
  geom_point(alpha = 0.4) +
  scale_color_gradient(low = "blue", high = "red") +
  geom_smooth(method = "lm", se = FALSE, color = "black") +
  labs(title = "Interaction: Bowl Setpoint × Filler Level on pH",
       x = "Bowl Setpoint",
       y = "pH",
       color = "Filler Level") +
  theme_minimal()

# Calculate interaction strength
model_main <- lm(PH ~ `Mnf Flow` + `Brand Code`, data = train_clean)
model_interaction <- lm(PH ~ `Mnf Flow` * `Brand Code`, data = train_clean)

cat("=== INTERACTION EFFECT SIGNIFICANCE ===\n")
## === INTERACTION EFFECT SIGNIFICANCE ===
cat("Main effects R²:", round(summary(model_main)$r.squared, 4), "\n")
## Main effects R²: 0.305
cat("With interaction R²:", round(summary(model_interaction)$r.squared, 4), "\n")
## With interaction R²: 0.3227
cat("Improvement:", round(summary(model_interaction)$r.squared - summary(model_main)$r.squared, 4), "\n")
## Improvement: 0.0178
# ANOVA to test if interaction is significant
anova_compare <- anova(model_main, model_interaction)
print(anova_compare)
## Analysis of Variance Table
## 
## Model 1: PH ~ `Mnf Flow` + `Brand Code`
## Model 2: PH ~ `Mnf Flow` * `Brand Code`
##   Res.Df    RSS Df Sum of Sq      F    Pr(>F)    
## 1   2562 53.079                                  
## 2   2559 51.723  3    1.3561 22.364 2.723e-14 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

1.6.3.1 Interaction Effects Summary

  • Interaction improves prediction by 1.8% (R² from 0.305 to 0.323) — statistically significant (p < 0.001)
  • Brand affects how Mnf Flow influences pH — relationship differs across brands A, B, C, D
  • Need to include Brand in final model — ignoring it reduces accuracy

1.6.4 pH Distribution Check

# EDA: Skewness Check for pH

# Load library
library(e1071)

# Calculate skewness
skew_value <- skewness(train_clean$PH)
cat("Skewness of pH:", round(skew_value, 4), "\n")
## Skewness of pH: -0.2906
# Interpretation rules of thumb
if(abs(skew_value) < 0.5) {
  cat("Interpretation: Approximately symmetric\n")
} else if(abs(skew_value) < 1) {
  cat("Interpretation: Moderately skewed\n")
} else {
  cat("Interpretation: Highly skewed\n")
}
## Interpretation: Approximately symmetric
# Quick visual: Simple histogram
hist(train_clean$PH, 
     main = "pH Distribution", 
     xlab = "pH",
     col = "lightblue",
     border = "black",
     breaks = 20)

# Add vertical line at mean
abline(v = mean(train_clean$PH), col = "red", lwd = 2, lty = 2)

# Show mean vs median
cat("\nMean:", round(mean(train_clean$PH), 4))
## 
## Mean: 8.5456
cat("\nMedian:", round(median(train_clean$PH), 4))
## 
## Median: 8.54
cat("\nDifference:", round(mean(train_clean$PH) - median(train_clean$PH), 4))
## 
## Difference: 0.0056

1.6.4.1 Skewness Summary

  • pH is approximately symmetric (skewness = -0.29, well within ±0.5 range)

  • Mean and median are nearly identical (8.5456 vs 8.5400, difference only 0.0056)

  • Linear regression is viable — no severe skewness requiring transformation

1.6.5 Multicollinearity Check

# EDA Multicollinearity - Are top predictors correlated with each other?


# Select top 10 predictors from earlier correlation analysis
top_predictors <- c("Mnf Flow", "Bowl Setpoint", "Filler Level", "Usage cont", 
                    "Pressure Setpoint", "Hyd Pressure3", "Pressure Vacuum", 
                    "Fill Pressure", "Hyd Pressure2", "Oxygen Filler")

# Create correlation matrix for top predictors
cor_matrix_top <- cor(train_clean[, top_predictors], use = "complete.obs")

# Visualize
corrplot::corrplot(cor_matrix_top, 
                   method = "number", 
                   type = "upper",
                   tl.cex = 0.8,
                   title = "Multicollinearity Among Top Predictors",
                   mar = c(0,0,2,0))

# Calculate VIF (Variance Inflation Factor)
# Build linear model with all top predictors
vif_model <- lm(PH ~ `Mnf Flow` + `Bowl Setpoint` + `Filler Level` + `Usage cont` + 
                      `Pressure Setpoint` + `Hyd Pressure3` + `Pressure Vacuum` + 
                      `Fill Pressure` + `Hyd Pressure2` + `Oxygen Filler`, 
                data = train_clean)

vif_values <- vif(vif_model)

cat("=== VARIANCE INFLATION FACTOR (VIF) ===\n")
## === VARIANCE INFLATION FACTOR (VIF) ===
cat("Rule of thumb: VIF > 5 indicates high multicollinearity\n")
## Rule of thumb: VIF > 5 indicates high multicollinearity
cat("VIF > 10 indicates severe multicollinearity\n\n")
## VIF > 10 indicates severe multicollinearity
print(data.frame(Variable = names(vif_values), VIF = round(vif_values, 2)))
##                                Variable   VIF
## `Mnf Flow`                   `Mnf Flow`  3.88
## `Bowl Setpoint`         `Bowl Setpoint`  8.80
## `Filler Level`           `Filler Level`  8.85
## `Usage cont`               `Usage cont`  1.41
## `Pressure Setpoint` `Pressure Setpoint`  2.00
## `Hyd Pressure3`         `Hyd Pressure3` 10.72
## `Pressure Vacuum`     `Pressure Vacuum`  1.62
## `Fill Pressure`         `Fill Pressure`  1.95
## `Hyd Pressure2`         `Hyd Pressure2`  7.62
## `Oxygen Filler`         `Oxygen Filler`  1.44
# Check if any VIF > 5
high_vif <- names(vif_values[vif_values > 5])
if(length(high_vif) > 0) {
  cat("\n High multicollinearity detected for:", paste(high_vif, collapse = ", "), "\n")
  cat("Recommendation: Remove or combine these variables\n")
} else {
  cat("\n No concerning multicollinearity (all VIF < 5)\n")
  cat("All top predictors can be used together\n")
}
## 
##  High multicollinearity detected for: `Bowl Setpoint`, `Filler Level`, `Hyd Pressure3`, `Hyd Pressure2` 
## Recommendation: Remove or combine these variables

1.6.5.1 Multicollinearity Summary

  • High correlation detected — Bowl Setpoint (VIF 8.8), Filler Level (8.85), Hyd Pressure3 (10.72), Hyd Pressure2 (7.62) are redundant
  • Impact on modeling — These variables measure similar process conditions; keeping all causes unstable coefficient estimates
  • Recommendation — Select representative variables (e.g., keep Filler Level, drop Hyd Pressure2/3) or use tree-based models (Random Forest/XGBoost) which handle redundancy naturally

1.6.6 Non-linear Relationships

# EDA: Check for non-linear relationships with pH


# Top 3 predictors vs pH with LOESS smooth curves
p1 <- ggplot(train_clean, aes(x = `Mnf Flow`, y = PH)) +
  geom_point(alpha = 0.3, size = 0.8) +
  geom_smooth(method = "loess", color = "red", se = TRUE) +
  geom_smooth(method = "lm", color = "blue", linetype = "dashed", se = FALSE) +
  labs(title = "Mnf Flow vs pH",
       x = "Manufacturing Flow",
       y = "pH") +
  theme_minimal()

p2 <- ggplot(train_clean, aes(x = `Bowl Setpoint`, y = PH)) +
  geom_point(alpha = 0.3, size = 0.8) +
  geom_smooth(method = "loess", color = "red", se = TRUE) +
  geom_smooth(method = "lm", color = "blue", linetype = "dashed", se = FALSE) +
  labs(title = "Bowl Setpoint vs pH",
       x = "Bowl Setpoint",
       y = "pH") +
  theme_minimal()

p3 <- ggplot(train_clean, aes(x = `Filler Level`, y = PH)) +
  geom_point(alpha = 0.3, size = 0.8) +
  geom_smooth(method = "loess", color = "red", se = TRUE) +
  geom_smooth(method = "lm", color = "blue", linetype = "dashed", se = FALSE) +
  labs(title = "Filler Level vs pH",
       x = "Filler Level",
       y = "pH") +
  theme_minimal()

# Arrange plots
library(gridExtra)
grid.arrange(p1, p2, p3, ncol = 3)

# Quick check: Is linear model sufficient?
model_linear <- lm(PH ~ `Mnf Flow`, data = train_clean)
model_poly <- lm(PH ~ poly(`Mnf Flow`, 2), data = train_clean)

cat("=== LINEAR vs POLYNOMIAL COMPARISON ===\n")
## === LINEAR vs POLYNOMIAL COMPARISON ===
cat("Linear R²:", round(summary(model_linear)$r.squared, 4), "\n")
## Linear R²: 0.1998
cat("Polynomial (degree 2) R²:", round(summary(model_poly)$r.squared, 4), "\n")
## Polynomial (degree 2) R²: 0.2153
if(summary(model_poly)$r.squared - summary(model_linear)$r.squared > 0.01) {
  cat("\n Non-linear pattern detected - polynomial improves fit by >1%\n")
} else {
  cat("\n Relationships are approximately linear\n")
}
## 
##  Non-linear pattern detected - polynomial improves fit by >1%

1.6.7 Non-linear Relationships Summary

  • Non-linear patterns detected — Polynomial model improves R² by 1.5% (0.200 to 0.215) for Mnf Flow vs pH
  • Practical implication — Simple linear regression may underfit; tree-based models (Random Forest, XGBoost) capture these curves automatically
  • Final recommendation — Use ensemble methods that handle both non-linearity and multicollinearity without manual feature selection

2 Next Step Modeling

Linear Regression (baseline) Random Forest (handles non-linearity) XGBoost (handles interactions)

2.0.1 Modeling section is performed by Sachi Kapoor

  • Part 2: model implementation
  • Part 3: testing the models and showing the results

The two cleaned files used here are:

  • training_data_cleaned.csv
  • test_data_cleaned.csv

From the earlier EDA, we already know two things:

  1. Brand matters and should stay in the modeling process.
  2. pH is not strongly skewed, so I am not going to log transform the target unless there is a strong reason to do that later.

2.1 Load the cleaned data

train_clean <- read.csv("training_data_cleaned.csv", check.names = FALSE)
test_clean  <- read.csv("test_data_cleaned.csv", check.names = FALSE)

cat("Training rows:", nrow(train_clean), "\n")
## Training rows: 2567
cat("Training columns:", ncol(train_clean), "\n")
## Training columns: 34
cat("Test rows:", nrow(test_clean), "\n")
## Test rows: 267
cat("Test columns:", ncol(test_clean), "\n")
## Test columns: 34

2.2 Make the column names model-friendly

One issue with this dataset is that several variable names have spaces in them, like Brand Code and Mnf Flow.

That is fine for basic viewing, but some modeling functions, especially the tree model through caret, can be picky about column names. To avoid that problem, I am standardizing the names once at the start. This does not change the data itself. It only makes the variable names easier for R to work with.

original_names <- names(train_clean)
safe_names <- make.names(original_names, unique = TRUE)

names(train_clean) <- safe_names
names(test_clean)  <- make.names(names(test_clean), unique = TRUE)

name_key <- data.frame(
  Original_Name = original_names,
  Modeling_Name = safe_names
)

kable(name_key, caption = "Original variable names and model-friendly names")
Original variable names and model-friendly names
Original_Name Modeling_Name
Brand Code Brand.Code
Carb Volume Carb.Volume
Fill Ounces Fill.Ounces
PC Volume PC.Volume
Carb Pressure Carb.Pressure
Carb Temp Carb.Temp
PSC PSC
PSC Fill PSC.Fill
PSC CO2 PSC.CO2
Mnf Flow Mnf.Flow
Carb Pressure1 Carb.Pressure1
Fill Pressure Fill.Pressure
Hyd Pressure1 Hyd.Pressure1
Hyd Pressure2 Hyd.Pressure2
Hyd Pressure3 Hyd.Pressure3
Hyd Pressure4 Hyd.Pressure4
Filler Level Filler.Level
Filler Speed Filler.Speed
Temperature Temperature
Usage cont Usage.cont
Carb Flow Carb.Flow
Density Density
MFR MFR
Balling Balling
Pressure Vacuum Pressure.Vacuum
PH PH
Oxygen Filler Oxygen.Filler
Bowl Setpoint Bowl.Setpoint
Pressure Setpoint Pressure.Setpoint
Air Pressurer Air.Pressurer
Alch Rel Alch.Rel
Carb Rel Carb.Rel
Balling Lvl Balling.Lvl
Brand_Code Brand_Code

2.3 Prepare the data

# Remove duplicate brand column if both versions exist
if ("Brand_Code" %in% names(train_clean)) {
  train_clean <- train_clean %>% select(-Brand_Code)
}

if ("Brand_Code" %in% names(test_clean)) {
  test_clean <- test_clean %>% select(-Brand_Code)
}

# Keep only rows with a known target in training
train_clean <- train_clean %>% filter(!is.na(PH))

# Convert Brand.Code to factor
train_clean$Brand.Code <- as.factor(train_clean$Brand.Code)
test_clean$Brand.Code  <- as.factor(test_clean$Brand.Code)

# Match factor levels between training and test data
all_levels <- union(levels(train_clean$Brand.Code), levels(test_clean$Brand.Code))
train_clean$Brand.Code <- factor(train_clean$Brand.Code, levels = all_levels)
test_clean$Brand.Code  <- factor(test_clean$Brand.Code, levels = all_levels)

# Split into training and validation sets
set.seed(42)
train_index <- createDataPartition(train_clean$PH, p = 0.80, list = FALSE)
train_set <- train_clean[train_index, ]
valid_set <- train_clean[-train_index, ]

cat("Modeling train rows:", nrow(train_set), "\n")
## Modeling train rows: 2055
cat("Validation rows:", nrow(valid_set), "\n")
## Validation rows: 512

2.4 Check missing values

missing_train <- sort(colSums(is.na(train_set)), decreasing = TRUE)
missing_train <- missing_train[missing_train > 0]
missing_train
## named numeric(0)

2.5 Handle missing values

Instead of using automatic imputation inside every model call, I am filling the missing values first. This makes the modeling steps more stable and easier to follow.

For Brand.Code, I use the most common category in the training set.

For numeric predictors, I use median imputation based on the training set.

# Fill missing Brand.Code with the most common value
mode_brand <- names(sort(table(train_set$Brand.Code), decreasing = TRUE))[1]

train_set$Brand.Code[is.na(train_set$Brand.Code)] <- mode_brand
valid_set$Brand.Code[is.na(valid_set$Brand.Code)] <- mode_brand
test_clean$Brand.Code[is.na(test_clean$Brand.Code)] <- mode_brand

# Median imputation for numeric predictors
numeric_cols <- names(train_set)[sapply(train_set, is.numeric)]
numeric_predictors <- setdiff(numeric_cols, "PH")

for (col in numeric_predictors) {
  med_value <- median(train_set[[col]], na.rm = TRUE)
  train_set[[col]][is.na(train_set[[col]])] <- med_value
  valid_set[[col]][is.na(valid_set[[col]])] <- med_value
  test_clean[[col]][is.na(test_clean[[col]])] <- med_value
}

# Also complete the full training set for the final model
train_clean$Brand.Code[is.na(train_clean$Brand.Code)] <- mode_brand

for (col in numeric_predictors) {
  med_value <- median(train_set[[col]], na.rm = TRUE)
  train_clean[[col]][is.na(train_clean[[col]])] <- med_value
}

# Quick check
colSums(is.na(train_set))
##        Brand.Code       Carb.Volume       Fill.Ounces         PC.Volume 
##                 0                 0                 0                 0 
##     Carb.Pressure         Carb.Temp               PSC          PSC.Fill 
##                 0                 0                 0                 0 
##           PSC.CO2          Mnf.Flow    Carb.Pressure1     Fill.Pressure 
##                 0                 0                 0                 0 
##     Hyd.Pressure1     Hyd.Pressure2     Hyd.Pressure3     Hyd.Pressure4 
##                 0                 0                 0                 0 
##      Filler.Level      Filler.Speed       Temperature        Usage.cont 
##                 0                 0                 0                 0 
##         Carb.Flow           Density               MFR           Balling 
##                 0                 0                 0                 0 
##   Pressure.Vacuum                PH     Oxygen.Filler     Bowl.Setpoint 
##                 0                 0                 0                 0 
## Pressure.Setpoint     Air.Pressurer          Alch.Rel          Carb.Rel 
##                 0                 0                 0                 0 
##       Balling.Lvl 
##                 0

2.6 Check the shape of the target

skew_value <- e1071::skewness(train_clean$PH)
cat("Skewness of PH:", round(skew_value, 4), "\n")
## Skewness of PH: -0.2906

Since the skewness is close to zero, I kept the target on its original scale.

2.7 Modeling plan

I am testing five different models:

  1. Linear Regression
  2. Ridge Regression
  3. Decision Tree
  4. Random Forest
  5. Gradient Boosting

This gives a mix of simpler models and more flexible nonlinear models.

ctrl <- trainControl(
  method = "cv",
  number = 5
)

2.8 Model 1: Linear Regression

set.seed(42)
model_lm <- train(
  PH ~ .,
  data = train_set,
  method = "lm",
  trControl = ctrl
)
model_lm
## Linear Regression 
## 
## 2055 samples
##   32 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 1643, 1644, 1645, 1644, 1644 
## Resampling results:
## 
##   RMSE       Rsquared   MAE      
##   0.1351935  0.3858023  0.1047519
## 
## Tuning parameter 'intercept' was held constant at a value of TRUE

2.9 Model 2: Ridge Regression

set.seed(42)
model_ridge <- train(
  PH ~ .,
  data = train_set,
  method = "glmnet",
  trControl = ctrl,
  preProcess = c("center", "scale"),
  tuneGrid = expand.grid(
    alpha = 0,
    lambda = seq(0.0001, 1, length = 20)
  )
)
model_ridge
## glmnet 
## 
## 2055 samples
##   32 predictor
## 
## Pre-processing: centered (34), scaled (34) 
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 1643, 1644, 1645, 1644, 1644 
## Resampling results across tuning parameters:
## 
##   lambda      RMSE       Rsquared   MAE      
##   0.00010000  0.1358004  0.3799658  0.1059655
##   0.05272632  0.1387485  0.3600465  0.1096929
##   0.10535263  0.1413069  0.3450262  0.1120139
##   0.15797895  0.1433040  0.3341904  0.1136882
##   0.21060526  0.1449582  0.3256599  0.1150341
##   0.26323158  0.1463730  0.3186534  0.1161775
##   0.31585789  0.1476051  0.3128023  0.1171778
##   0.36848421  0.1486928  0.3078064  0.1180522
##   0.42111053  0.1496734  0.3034231  0.1188466
##   0.47373684  0.1505561  0.2996170  0.1195543
##   0.52636316  0.1513603  0.2962506  0.1202114
##   0.57898947  0.1520995  0.2932383  0.1208241
##   0.63161579  0.1527822  0.2905286  0.1213918
##   0.68424211  0.1534170  0.2880675  0.1219265
##   0.73686842  0.1540060  0.2858437  0.1224314
##   0.78949474  0.1545535  0.2838368  0.1229017
##   0.84212105  0.1550660  0.2819998  0.1233432
##   0.89474737  0.1555545  0.2802607  0.1237670
##   0.94737368  0.1560067  0.2787120  0.1241692
##   1.00000000  0.1564361  0.2772598  0.1245510
## 
## Tuning parameter 'alpha' was held constant at a value of 0
## RMSE was used to select the optimal model using the smallest value.
## The final values used for the model were alpha = 0 and lambda = 1e-04.

2.10 Model 3: Decision Tree

set.seed(42)
model_tree <- train(
  x = subset(train_set, select = -PH),
  y = train_set$PH,
  method = "rpart",
  trControl = ctrl,
  tuneLength = 10
)
model_tree
## CART 
## 
## 2055 samples
##   32 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 1643, 1644, 1645, 1644, 1644 
## Resampling results across tuning parameters:
## 
##   cp          RMSE       Rsquared   MAE      
##   0.01093342  0.1301153  0.4333166  0.1003090
##   0.01109126  0.1301153  0.4333166  0.1003090
##   0.01324912  0.1330186  0.4077438  0.1030744
##   0.01432077  0.1353196  0.3867687  0.1054120
##   0.01456794  0.1359107  0.3815954  0.1055995
##   0.01938675  0.1396470  0.3464937  0.1095377
##   0.02866996  0.1404106  0.3391121  0.1100662
##   0.04210985  0.1447224  0.2999125  0.1139650
##   0.07021331  0.1506207  0.2397604  0.1175867
##   0.21545429  0.1626997  0.1827404  0.1296840
## 
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was cp = 0.01109126.
rpart.plot(model_tree$finalModel, main = "Decision Tree for pH Prediction")

2.11 Model 4: Random Forest

set.seed(42)
model_rf <- train(
  PH ~ .,
  data = train_set,
  method = "rf",
  trControl = ctrl,
  tuneLength = 5,
  importance = TRUE,
  ntree = 300
)
model_rf
## Random Forest 
## 
## 2055 samples
##   32 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 1643, 1644, 1645, 1644, 1644 
## Resampling results across tuning parameters:
## 
##   mtry  RMSE       Rsquared   MAE       
##    2    0.1138655  0.6121972  0.08560002
##   10    0.1021945  0.6684124  0.07451729
##   18    0.1006049  0.6730289  0.07283461
##   26    0.1001186  0.6716816  0.07208962
##   34    0.1008149  0.6638367  0.07188785
## 
## RMSE was used to select the optimal model using the smallest value.
## The final value used for the model was mtry = 26.

2.12 Model 5: Gradient Boosting

set.seed(42)
model_gbm <- train(
  PH ~ .,
  data = train_set,
  method = "gbm",
  trControl = ctrl,
  verbose = FALSE,
  tuneLength = 5
)
model_gbm
## Stochastic Gradient Boosting 
## 
## 2055 samples
##   32 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 1643, 1644, 1645, 1644, 1644 
## Resampling results across tuning parameters:
## 
##   interaction.depth  n.trees  RMSE       Rsquared   MAE       
##   1                   50      0.1370280  0.3860059  0.10786037
##   1                  100      0.1331103  0.4134434  0.10433427
##   1                  150      0.1306928  0.4328776  0.10210430
##   1                  200      0.1293571  0.4413648  0.10083512
##   1                  250      0.1288080  0.4446195  0.09995516
##   2                   50      0.1290572  0.4553366  0.10106246
##   2                  100      0.1240966  0.4896051  0.09627840
##   2                  150      0.1215857  0.5078372  0.09355046
##   2                  200      0.1200410  0.5187605  0.09215074
##   2                  250      0.1189785  0.5259303  0.09093541
##   3                   50      0.1255740  0.4835986  0.09794601
##   3                  100      0.1203402  0.5194404  0.09269385
##   3                  150      0.1173839  0.5399920  0.08973551
##   3                  200      0.1160355  0.5496639  0.08831585
##   3                  250      0.1148768  0.5578661  0.08699483
##   4                   50      0.1220018  0.5121369  0.09441330
##   4                  100      0.1166098  0.5469151  0.08954224
##   4                  150      0.1146449  0.5593636  0.08732072
##   4                  200      0.1136345  0.5660380  0.08604221
##   4                  250      0.1120033  0.5779589  0.08489552
##   5                   50      0.1189395  0.5363747  0.09158402
##   5                  100      0.1144811  0.5633077  0.08729330
##   5                  150      0.1123623  0.5775069  0.08525031
##   5                  200      0.1106779  0.5890316  0.08368335
##   5                  250      0.1100211  0.5936124  0.08273296
## 
## Tuning parameter 'shrinkage' was held constant at a value of 0.1
## 
## Tuning parameter 'n.minobsinnode' was held constant at a value of 10
## RMSE was used to select the optimal model using the smallest value.
## The final values used for the model were n.trees = 250, interaction.depth =
##  5, shrinkage = 0.1 and n.minobsinnode = 10.

2.13 Make validation predictions

pred_lm    <- predict(model_lm, valid_set)
pred_ridge <- predict(model_ridge, valid_set)
pred_tree  <- predict(model_tree, subset(valid_set, select = -PH))
pred_rf    <- predict(model_rf, valid_set)
pred_gbm   <- predict(model_gbm, valid_set)

2.14 Compare model performance

I used RMSE, MAE, and R-squared to compare the models on the validation set.

get_metrics <- function(actual, predicted, model_name) {
  data.frame(
    Model = model_name,
    RMSE = RMSE(predicted, actual),
    MAE  = MAE(predicted, actual),
    R2   = R2(predicted, actual)
  )
}

results_table <- bind_rows(
  get_metrics(valid_set$PH, pred_lm, "Linear Regression"),
  get_metrics(valid_set$PH, pred_ridge, "Ridge Regression"),
  get_metrics(valid_set$PH, pred_tree, "Decision Tree"),
  get_metrics(valid_set$PH, pred_rf, "Random Forest"),
  get_metrics(valid_set$PH, pred_gbm, "Gradient Boosting")
) %>%
  arrange(RMSE)

kable(results_table, digits = 4, caption = "Validation performance comparison")
Validation performance comparison
Model RMSE MAE R2
Random Forest 0.0921 0.0685 0.7260
Gradient Boosting 0.1062 0.0806 0.6232
Decision Tree 0.1325 0.1041 0.4104
Linear Regression 0.1336 0.1044 0.4010
Ridge Regression 0.1338 0.1059 0.4006

2.15 Best model

best_model_name <- results_table$Model[1]
cat("Best model based on validation RMSE:", best_model_name, "\n")
## Best model based on validation RMSE: Random Forest

2.16 Variable importance

Even though I tested several models, Random Forest is especially useful because it can also show which predictors mattered most.

rf_importance <- varImp(model_rf)
rf_importance_df <- rf_importance$importance %>%
  rownames_to_column("Feature") %>%
  arrange(desc(Overall))

kable(head(rf_importance_df, 12), digits = 4, caption = "Top 12 important predictors from Random Forest")
Top 12 important predictors from Random Forest
Feature Overall
Mnf.Flow 100.0000
Brand.CodeC 79.7399
Oxygen.Filler 57.9940
Pressure.Vacuum 57.9121
Balling.Lvl 50.3985
Hyd.Pressure3 46.4134
Alch.Rel 44.4176
Air.Pressurer 43.9509
Temperature 41.0065
Carb.Rel 40.4981
Filler.Speed 34.7497
Carb.Flow 30.2658
plot(varImp(model_rf), top = 12, main = "Random Forest Variable Importance")

2.16.1 Refit the final model on all training data

After comparing the models, I refit the final model using all available training rows with known pH.

set.seed(42)
final_model <- train(
  PH ~ .,
  data = train_clean,
  method = "rf",
  trControl = ctrl,
  tuneGrid = model_rf$bestTune,
  importance = TRUE,
  ntree = 300
)
final_model
## Random Forest 
## 
## 2567 samples
##   32 predictor
## 
## No pre-processing
## Resampling: Cross-Validated (5 fold) 
## Summary of sample sizes: 2054, 2054, 2053, 2054, 2053 
## Resampling results:
## 
##   RMSE        Rsquared   MAE       
##   0.09467057  0.7104964  0.06802449
## 
## Tuning parameter 'mtry' was held constant at a value of 26

2.16.2 Predict pH for the evaluation data

final_predictions <- predict(final_model, test_clean)

prediction_output <- test_clean %>%
  mutate(
    Predicted_PH_Final_Model = round(final_predictions, 4),
    Final_Model = "Random Forest",
    Prediction_Note = "PH was blank in evaluation file, so the final model prediction is provided here."
  )

summary(prediction_output$Predicted_PH_Final_Model)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
##   8.150   8.471   8.533   8.548   8.661   8.800

2.16.2.1 Save the output files

write.csv(results_table, "model_validation_results.csv", row.names = FALSE)
write.csv(rf_importance_df, "random_forest_variable_importance.csv", row.names = FALSE)
write.csv(prediction_output, "ABC_Beverage_PH_Predictions.csv", row.names = FALSE)

2.16.3 Final thoughts

cat("Based on the validation results, I selected the model with the lowest RMSE as the final model. ")

Based on the validation results, I selected the model with the lowest RMSE as the final model.

cat("In this type of manufacturing data, tree-based models are often helpful because they can capture nonlinear patterns and interactions that a basic linear model may miss. ")

In this type of manufacturing data, tree-based models are often helpful because they can capture nonlinear patterns and interactions that a basic linear model may miss.

cat("That is especially relevant here because earlier analysis already suggested that Brand Code changes the relationship between Mnf Flow and pH.\n\n")

That is especially relevant here because earlier analysis already suggested that Brand Code changes the relationship between Mnf Flow and pH.

cat("The final prediction file created by this report is **ABC_Beverage_PH_Predictions.csv**. ")

The final prediction file created by this report is ABC_Beverage_PH_Predictions.csv.

cat("This file can be turned in as the model prediction output for the evaluation dataset.\n")

This file can be turned in as the model prediction output for the evaluation dataset.

2.16.3.1 Write all predictions on excel file

# Write all predictions on excel file  
# ============================================================
# Step 1: Read all your actual data files
# ============================================================

# Read the predictions CSV (test data with predictions)
predictions_raw <- read_csv("ABC_Beverage_PH_Predictions.csv")

# Read the model comparison results
model_results <- results_table # Your 5-model comparison

# Read the random forest variable importance
rf_importance <- read_csv("random_forest_variable_importance.csv")  # Your 34-feature importance

# ============================================================
# Extract actual metrics
# ============================================================

rf_metrics <- results_table %>% filter(Model == "Random Forest")
RMSE_actual <- rf_metrics$RMSE
R2_actual <- rf_metrics$R2

# 90% prediction interval (z = 1.645)
PI_width <- round(1.645 * RMSE_actual, 4)

# ============================================================
# Sheet 1: Predictions (main output)
# ============================================================

predictions_df <- predictions_raw %>%
  mutate(
    RowID = row_number(),
    PH_Predicted = Predicted_PH_Final_Model,
    PH_Lower_90CI = PH_Predicted - (1.645 * RMSE_actual),
    PH_Upper_90CI = PH_Predicted + (1.645 * RMSE_actual),
    Alert_Flag = case_when(
      PH_Predicted < 8.2 ~ "LOW - Review",
      PH_Predicted > 8.9 ~ "HIGH - Review",
      TRUE ~ "OK"
    )
  ) %>%
  select(RowID, Brand_Code = `Brand.Code`, Mnf.Flow, Bowl.Setpoint, 
         Filler.Level, PH_Predicted, PH_Lower_90CI, PH_Upper_90CI, 
         Alert_Flag, Final_Model)

# ============================================================
# Sheet 2: Model Metadata
# ============================================================

metadata_df <- data.frame(
  Metric = c("Model", "RMSE", "R_Squared", "Prediction_Interval_90%", "Test_Rows"),
  Value = c("Random Forest", round(RMSE_actual, 4), round(R2_actual, 4), 
            paste0("±", PI_width), nrow(predictions_raw))
)

# ============================================================
# Sheet 3: Variable Importance (from your rf_importance_df)
# ============================================================

importance_df <- rf_importance_df %>%
  mutate(Rank = row_number(), Importance = round(Overall, 2)) %>%
  select(Rank, Variable = Feature, Importance)

# ============================================================
# Sheet 4: Model Comparison
# ============================================================

comparison_df <- results_table %>%
  arrange(RMSE) %>%
  mutate(across(c(RMSE, MAE, R2), ~ round(., 4))) %>%
  select(Model, RMSE, MAE, R2)

# ============================================================
# Sheet 5: Brand Summary
# ============================================================

brand_df <- predictions_df %>%
  group_by(Brand_Code) %>%
  summarise(
    Count = n(),
    Avg_pH = round(mean(PH_Predicted), 3),
    Min_pH = round(min(PH_Predicted), 3),
    Max_pH = round(max(PH_Predicted), 3),
    .groups = "drop"
  )

# ============================================================
# Sheet 6: Executive Summary (simple)
# ============================================================

summary_df <- data.frame(
  Key_Finding = c("Total Predictions", "Model RMSE", "Model R²", "Top Predictor", 
                  "Lowest pH Brand", "Runs Needing Review"),
  Value = c(nrow(predictions_df), round(RMSE_actual, 4), round(R2_actual, 4),
            rf_importance_df$Feature[1],
            brand_df$Brand_Code[which.min(brand_df$Avg_pH)],
            sum(predictions_df$Alert_Flag != "OK"))
)

# ============================================================
# Write to Excel
# ============================================================

sheets_list <- list(
  "Predictions" = predictions_df,
  "Model_Metadata" = metadata_df,
  "Variable_Importance" = importance_df,
  "Model_Comparison" = comparison_df,
  "Brand_Summary" = brand_df,
  "Executive_Summary" = summary_df
)

write_xlsx(sheets_list, "ABC_Beverage_pH_Predictions.xlsx")
LS0tCnRpdGxlOiAicEggUHJlZGljdGl2ZSBNb2RlbGluZyBmb3IgQUJDIEJldmVyYWdlIgpzdWJ0aXRsZTogIkRBVEEgNjI0IC0gUHJvamVjdCAyIgphdXRob3I6IAogIC0gIk1laHJlZW4gQWxpIEdpbGxhbmkiCiAgLSAiU2FjaGkgS2Fwb29yIgogIC0gIkl6emEgS2hhbiIKZGF0ZTogIjIwMjYtMDUtMjMiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6CiAgICAgIHNtb290aF9zY3JvbGw6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRoZW1lOiBmbGF0bHkKICAgIGhpZ2hsaWdodDogdGFuZ28KICAgIGZpZ193aWR0aDogNwogICAgZmlnX2hlaWdodDogNQogICAgZmlnX2FsaWduOiBjZW50ZXIKICAgIGRldjogcG5nCiAgICBkZl9wcmludDoga2FibGUKICAgIGtlZXBfbWQ6IGZhbHNlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBlY2hvID0gVFJVRSwKICB3YXJuaW5nID0gRkFMU0UsCiAgbWVzc2FnZSA9IEZBTFNFLAogIGZpZy5hbGlnbiA9ICJjZW50ZXIiLAogIG91dC53aWR0aCA9ICI4MCUiCikKIyBMb2FkIHJlcXVpcmVkIGxpYnJhcmllcwpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShyZWFkeGwpICAgIyBGb3IgcmVhZGluZyBFeGNlbCBmaWxlcwpsaWJyYXJ5KGNvcnJwbG90KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoY2FyKQpsaWJyYXJ5KGNhcmV0KSAgICAgICMgRm9yIGRhdGEgc3BsaXR0aW5nIGFuZCBwcmVwcm9jZXNzaW5nCmxpYnJhcnkocmFuZG9tRm9yZXN0KSAjIFdpbGwgdHJ5IHRoaXMgbW9kZWwKbGlicmFyeSh4Z2Jvb3N0KSAgICAjIFdpbGwgdHJ5IHRoaXMgdG9vCmxpYnJhcnkoZ2xtbmV0KQpsaWJyYXJ5KGdibSkKbGlicmFyeShycGFydCkKbGlicmFyeShycGFydC5wbG90KQpsaWJyYXJ5KGUxMDcxKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KHJlYWRyKSAgICAgICMgRm9yIHJlYWRpbmcgQ1NWCmxpYnJhcnkoZHBseXIpICAgICAgIyBGb3IgZGF0YSBtYW5pcHVsYXRpb24KbGlicmFyeSh0aWR5cikgICAgICAjIEZvciBkYXRhIGNsZWFuaW5nCmxpYnJhcnkod3JpdGV4bCkgICAgIyBGb3Igd3JpdGluZyBFeGNlbCBmaWxlcwpsaWJyYXJ5KHN0cmluZ3IpICAgICMgRm9yIHN0cmluZyBtYW5pcHVsYXRpb24KYGBgCiMgRXhlY3V0aXZlIFN1bW1hcnkKCj4gKipOb3RlOioqIFRoaXMgaXMgdGhlIHRlY2huaWNhbCByZXBvcnQuIEZvciBidXNpbmVzcy1mcmllbmRseSByZWNvbW1lbmRhdGlvbnMsIHBsZWFzZSByZWZlciB0byB0aGUgc2VwYXJhdGUgIk5vbi1UZWNobmljYWwgUmVwb3J0IGZvciBMZWFkZXJzaGlwLiIKCiMjIFByb2plY3QgT2JqZWN0aXZlCgpOZXcgcmVndWxhdGlvbnMgcmVxdWlyZSBBQkMgQmV2ZXJhZ2UgdG8gdW5kZXJzdGFuZCBtYW51ZmFjdHVyaW5nIGZhY3RvcnMgdGhhdCBjb250cm9sIHBIIGFuZCBwcm92aWRlIGEgd29ya2luZyBwcmVkaWN0aXZlIG1vZGVsLiBXZSBoYXZlIGhpc3RvcmljYWwgZGF0YSBmcm9tIDIsNTY3IHByb2R1Y3Rpb24gcnVucyBhbmQgbXVzdCBwcmVkaWN0IHBIIGZvciAyNjcgbmV3IHJ1bnMuCgojIyBLZXkgRmluZGluZ3MKCi0gKipSYW5kb20gRm9yZXN0Kiogc2VsZWN0ZWQgYXMgZmluYWwgbW9kZWwgKFJNU0UgPSAwLjA5MjEsIE1BRSA9IDAuMDY4NSwgUsKyID0gMC43MjYpCi0gKipUb3AgMyBwcmVkaWN0b3JzOioqIE1hbnVmYWN0dXJpbmcgRmxvdywgQm93bCBTZXRwb2ludCwgRmlsbGVyIExldmVsCi0gKipCcmFuZCBtYXR0ZXJzOioqIFNpZ25pZmljYW50IHBIIGRpZmZlcmVuY2VzIGFjcm9zcyBicmFuZHMgKHAgPCAwLjAwMSkKLSAqKlByZWRpY3Rpb25zIGRlbGl2ZXJlZDoqKiBFeGNlbCBmaWxlIHdpdGggOTAlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzICjCsTAuMTUxNCBwSCkKCiMjIExvYWQgVHJhaW5pbmcgYW5kIFRlc3RpbmcgZGF0YQpgYGB7ciByZWFkX2ZpbGVzfQoKIyBTZXQgd29ya2luZyBkaXJlY3RvcnkgKGFkanVzdCB0byB5b3VyIHBhdGgpCiMgc2V0d2QoInlvdXIvZm9sZGVyL3BhdGgiKQojIFJlYWQgdGhlIHRyYWluaW5nIGRhdGEKdHJhaW5fZGF0YSA8LSByZWFkX2V4Y2VsKCJTdHVkZW50RGF0YS54bHN4IikKdGVzdF9kYXRhIDwtIHJlYWRfZXhjZWwoIlN0dWRlbnRFdmFsdWF0aW9uLnhsc3giKQoKCmBgYAojIyMgVHJhaW5pbmcgRGF0YSAKCmBgYHtyIHRyYWluaW5nX2RhdGFzZXR9CiMgQ2hlY2sgc3RydWN0dXJlIG9mIHRyYWluaW5nIGRhdGEKY2F0KCI9PT0gVFJBSU5JTkcgREFUQSA9PT1cbiIpCmNhdCgiRGltZW5zaW9uczoiLCBkaW0odHJhaW5fZGF0YSksICJcbiIpCmNhdCgiXG5Db2x1bW4gbmFtZXM6XG4iKQpwcmludChuYW1lcyh0cmFpbl9kYXRhKSkKY2F0KCJcbkZpcnN0IGZldyByb3dzOlxuIikKcHJpbnQoaGVhZCh0cmFpbl9kYXRhLDMpKQpjYXQoIlxuU3VtbWFyeSBvZiB0cmFpbmluZyBkYXRhOlxuIikKcHJpbnQoc3VtbWFyeSh0cmFpbl9kYXRhKSkKYGBgCgoqKktleSBJbnNpZ2h0cyBmcm9tIFRyYWluaW5nIERhdGE6KioKCi0gcEggaXMgbnVtZXJpYyBpbiB0cmFpbmluZyBkYXRhIChyYW5nZTogNy44OCB0byA5LjM2LCBtZWFuIH44LjU1KSDigJQgdGhpcyBpcyBvdXIgdGFyZ2V0IHZhcmlhYmxlCgotIERhdGFzZXQgaGF2ZSBtaXNzaW5nIHZhbHVlcyB0aGF0IG5lZWQgaGFuZGxpbmcgYmVmb3JlIG1vZGVsaW5nCgotIDMzIHByZWRpY3RvciB2YXJpYWJsZXMgYXZhaWxhYmxlIChleGNsdWRpbmcgQnJhbmQgQ29kZSB3aGljaCBpcyBjYXRlZ29yaWNhbCkKCiMjIyBUZXN0IERhdGEgCgpgYGB7ciB0ZXN0X2RhdGFzZXR9CmNhdCgiXG5cbj09PSBURVNUIERBVEEgPT09XG4iKQpjYXQoIkRpbWVuc2lvbnM6IiwgZGltKHRlc3RfZGF0YSksICJcbiIpCmNhdCgiXG5Db2x1bW4gbmFtZXM6XG4iKQpwcmludChuYW1lcyh0ZXN0X2RhdGEpKQpjYXQoIlxuRmlyc3QgZmV3IHJvd3M6XG4iKQpwcmludChoZWFkKHRlc3RfZGF0YSwzKSkKY2F0KCJcblN1bW1hcnkgb2YgdGVzdCBkYXRhOlxuIikKcHJpbnQoc3VtbWFyeSh0ZXN0X2RhdGEpKQpgYGAKCiMjIyBDaGVjayBtaXNzaW5nIHZhbHVlcwpgYGB7ciBjaGVja19taXNzaW5nbmVzc30KIyBDaGVjayBmb3IgbWlzc2luZyB2YWx1ZXMgaW4gYm90aCBkYXRhc2V0cwpjYXQoIlxuPT09IE1JU1NJTkcgVkFMVUVTID09PVxuIikKY2F0KCJUcmFpbmluZyBkYXRhIG1pc3NpbmcgdmFsdWVzIHBlciBjb2x1bW46XG4iKQpwcmludChjb2xTdW1zKGlzLm5hKHRyYWluX2RhdGEpKSkKY2F0KCJcblRlc3QgZGF0YSBtaXNzaW5nIHZhbHVlcyBwZXIgY29sdW1uOlxuIikKcHJpbnQoY29sU3Vtcyhpcy5uYSh0ZXN0X2RhdGEpKSkKYGBgCgojIyBQcm9ibGVtIFVuZGVyc3RhbmRpbmcKCiMjIyBCdXNpbmVzcyBHb2FsOgpBQkMgQmV2ZXJhZ2UgbmVlZCB0byBjb21wbHkgd2l0aCBuZXcgcmVndWxhdGlvbnMgcmVxdWlyaW5nIHlvdSB0bzoKCi0gVW5kZXJzdGFuZCB5b3VyIG1hbnVmYWN0dXJpbmcgcHJvY2VzcyBhbmQgd2hpY2ggZmFjdG9ycyBwcmVkaWN0IHBICi0gUmVwb3J0IGEgcHJlZGljdGl2ZSBtb2RlbCBmb3IgcEggdG8gbGVhZGVyc2hpcAoKIyMjIERhdGEgU2l0dWF0aW9uOgoKLSAqKlRyYWluaW5nIGRhdGEgKFN0dWRlbnREYXRhLnhsc3gpOioqIEhhcyBoaXN0b3JpY2FsIG1hbnVmYWN0dXJpbmcgbWVhc3VyZW1lbnRzIElOQ0xVRElORyBwSCB2YWx1ZXMgKGl0IGNvbnRhaW5zIHBIIGFzIG51bWVyaWMpCi0gKipUZXN0IGRhdGEgKFN0dWRlbnRFdmFsdWF0aW9uLnhsc3gpOioqIEhhcyB0aGUgU0FNRSBtYW51ZmFjdHVyaW5nIG1lYXN1cmVtZW50cyBCVVQgcEggaXMgZW1wdHkvbG9naWNhbCAoYWxsIE5BIHZhbHVlcykg4oCUIHRoaXMgaXMgd2hhdCB3ZSBuZWVkIHRvIHByZWRpY3QKCioqQ2hhbGxlbmdlOioqIE1pc3NpbmcgdmFsdWVzIGluIEJPVEggZGF0YXNldHMgYWNyb3NzIG1hbnkgY29sdW1ucyAoQnJhbmQgQ29kZSBoYXMgMTIwIE5BcyBpbiB0cmFpbmluZywgTUZSIGhhcyAyMTIgTkFzLCBldGMuKQoKIyMgRGF0YSBwcmVwcm9jZXNzaW5nCgpgYGB7ciBwcmVwcm9jZXNzaW5nfQojIENyZWF0ZSBhIGNvcHkgZm9yIHByZXByb2Nlc3NpbmcKdHJhaW5fY2xlYW4gPC0gdHJhaW5fZGF0YQp0ZXN0X2NsZWFuIDwtIHRlc3RfZGF0YQoKIyBJREVOVElGWSBDT0xVTU4gVFlQRVMKY2F0KCI9PT0gQ09MVU1OIFRZUEVTIElOIFRSQUlOSU5HID09PVxuIikKY29sX3R5cGVzIDwtIHNhcHBseSh0cmFpbl9jbGVhbiwgY2xhc3MpCnByaW50KHRhYmxlKGNvbF90eXBlcykpCgojIFNlcGFyYXRlIGZlYXR1cmVzIGFuZCB0YXJnZXQgaW4gdHJhaW5pbmcKIyBSZW1vdmUgcm93cyB3aGVyZSBQSCBpcyBtaXNzaW5nIChvbmx5IDQgTkFzIGluIHRyYWluaW5nKQp0cmFpbl9jbGVhbiA8LSB0cmFpbl9jbGVhbiAlPiUgZmlsdGVyKCFpcy5uYShQSCkpCmNhdCgiXG5UcmFpbmluZyByb3dzIGFmdGVyIHJlbW92aW5nIE5BIGluIFBIOiIsIG5yb3codHJhaW5fY2xlYW4pLCAiXG4iKQoKIyBGb3IgdGVzdCBkYXRhLCBQSCBpcyBhbGwgTkEgLSB3ZSdsbCBrZWVwIGl0IGFzIHBsYWNlaG9sZGVyCiMgQnV0IHdlIG5lZWQgdG8gZW5zdXJlIHRlc3QgaGFzIHNhbWUgc3RydWN0dXJlIGFzIHRyYWluaW5nIGZlYXR1cmVzCgojIEhhbmRsZSBCcmFuZCBDb2RlIGFzIGZhY3RvciAoY2F0ZWdvcmljYWwpCnRyYWluX2NsZWFuJGBCcmFuZCBDb2RlYCA8LSBhcy5mYWN0b3IodHJhaW5fY2xlYW4kYEJyYW5kIENvZGVgKQp0ZXN0X2NsZWFuJGBCcmFuZCBDb2RlYCA8LSBhcy5mYWN0b3IodGVzdF9jbGVhbiRgQnJhbmQgQ29kZWApCgojIENoZWNrIGxldmVscyBtYXRjaApjYXQoIlxuQnJhbmQgQ29kZSBsZXZlbHMgaW4gdHJhaW5pbmc6IiwgbGV2ZWxzKHRyYWluX2NsZWFuJGBCcmFuZCBDb2RlYCksICJcbiIpCmNhdCgiQnJhbmQgQ29kZSBsZXZlbHMgaW4gdGVzdDoiLCBsZXZlbHModGVzdF9jbGVhbiRgQnJhbmQgQ29kZWApLCAiXG4iKQoKIyBNSVNTSU5HIFZBTFVFIEFOQUxZU0lTCmNhdCgiXG49PT0gTUlTU0lORyBWQUxVRSBTVU1NQVJZID09PVxuIikKbWlzc2luZ190cmFpbiA8LSBzb3J0KGNvbFN1bXMoaXMubmEodHJhaW5fY2xlYW4pKSwgZGVjcmVhc2luZyA9IFRSVUUpCm1pc3NpbmdfdHJhaW4gPC0gbWlzc2luZ190cmFpblttaXNzaW5nX3RyYWluID4gMF0KY2F0KCJDb2x1bW5zIHdpdGggbWlzc2luZyB2YWx1ZXMgaW4gdHJhaW5pbmc6XG4iKQpwcmludChtaXNzaW5nX3RyYWluKQoKIyBJZGVudGlmeSBjb2x1bW5zIHdpdGggPjMwJSBtaXNzaW5nIGluIHRyYWluaW5nCmhpZ2hfbWlzc2luZyA8LSBuYW1lcyhtaXNzaW5nX3RyYWluW21pc3NpbmdfdHJhaW4gPiAwLjMgKiBucm93KHRyYWluX2NsZWFuKV0pCmNhdCgiXG5Db2x1bW5zIHdpdGggPjMwJSBtaXNzaW5nOiIsIGhpZ2hfbWlzc2luZywgIlxuIikKCiMgRm9yIG5vdywgbGV0J3MgcmVtb3ZlIGNvbHVtbnMgd2l0aCA+NTAlIG1pc3NpbmcKdGhyZXNob2xkIDwtIDAuNSAqIG5yb3codHJhaW5fY2xlYW4pCmNvbHNfdG9fcmVtb3ZlIDwtIG5hbWVzKG1pc3NpbmdfdHJhaW5bbWlzc2luZ190cmFpbiA+IHRocmVzaG9sZF0pCmNhdCgiQ29sdW1ucyB0byByZW1vdmUgKD41MCUgbWlzc2luZyk6IiwgY29sc190b19yZW1vdmUsICJcbiIpCgojIFJlbW92ZSB0aGVzZSBjb2x1bW5zIGZyb20gYm90aCBkYXRhc2V0cwppZihsZW5ndGgoY29sc190b19yZW1vdmUpID4gMCkgewogIHRyYWluX2NsZWFuIDwtIHRyYWluX2NsZWFuICU+JSBzZWxlY3QoLW9uZV9vZihjb2xzX3RvX3JlbW92ZSkpCiAgdGVzdF9jbGVhbiA8LSB0ZXN0X2NsZWFuICU+JSBzZWxlY3QoLW9uZV9vZihjb2xzX3RvX3JlbW92ZSkpCn0KYGBgCiMjIyBIYW5kbGUgTWlzc2luZyBEYXRhIHdpdGggSW1wdXRhdGlvbgoKYGBge3IgaW1wdXRpbmd9CgojIEZvciByZW1haW5pbmcgbWlzc2luZyB2YWx1ZXMsIHVzZSBtZWRpYW4vbW9kZSBpbXB1dGF0aW9uCiMgU2VwYXJhdGUgbnVtZXJpYyBhbmQgY2hhcmFjdGVyIGNvbHVtbnMKbnVtZXJpY19jb2xzIDwtIG5hbWVzKHRyYWluX2NsZWFuKVtzYXBwbHkodHJhaW5fY2xlYW4sIGlzLm51bWVyaWMpXQpmYWN0b3JfY29scyA8LSBuYW1lcyh0cmFpbl9jbGVhbilbc2FwcGx5KHRyYWluX2NsZWFuLCBpcy5mYWN0b3IpXQoKIyBJbXB1dGUgbnVtZXJpYyBjb2x1bW5zIHdpdGggbWVkaWFuCmZvcihjb2wgaW4gbnVtZXJpY19jb2xzKSB7CiAgaWYoYW55KGlzLm5hKHRyYWluX2NsZWFuW1tjb2xdXSkpKSB7CiAgICBtZWRfdmFsIDwtIG1lZGlhbih0cmFpbl9jbGVhbltbY29sXV0sIG5hLnJtID0gVFJVRSkKICAgIHRyYWluX2NsZWFuW1tjb2xdXVtpcy5uYSh0cmFpbl9jbGVhbltbY29sXV0pXSA8LSBtZWRfdmFsCiAgfQogIGlmKGFueShpcy5uYSh0ZXN0X2NsZWFuW1tjb2xdXSkpKSB7CiAgICBtZWRfdmFsIDwtIG1lZGlhbih0ZXN0X2NsZWFuW1tjb2xdXSwgbmEucm0gPSBUUlVFKQogICAgdGVzdF9jbGVhbltbY29sXV1baXMubmEodGVzdF9jbGVhbltbY29sXV0pXSA8LSBtZWRfdmFsCiAgfQp9CgojIEltcHV0ZSBmYWN0b3IgY29sdW1ucyB3aXRoIG1vZGUKZm9yKGNvbCBpbiBmYWN0b3JfY29scykgewogIGlmKGFueShpcy5uYSh0cmFpbl9jbGVhbltbY29sXV0pKSkgewogICAgbW9kZV92YWwgPC0gbmFtZXMoc29ydCh0YWJsZSh0cmFpbl9jbGVhbltbY29sXV0pLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdCiAgICB0cmFpbl9jbGVhbltbY29sXV1baXMubmEodHJhaW5fY2xlYW5bW2NvbF1dKV0gPC0gbW9kZV92YWwKICB9CiAgaWYoYW55KGlzLm5hKHRlc3RfY2xlYW5bW2NvbF1dKSkpIHsKICAgIG1vZGVfdmFsIDwtIG5hbWVzKHNvcnQodGFibGUodGVzdF9jbGVhbltbY29sXV0pLCBkZWNyZWFzaW5nID0gVFJVRSkpWzFdCiAgICB0ZXN0X2NsZWFuW1tjb2xdXVtpcy5uYSh0ZXN0X2NsZWFuW1tjb2xdXSldIDwtIG1vZGVfdmFsCiAgfQp9CgojIFZlcmlmeSBubyBtaXNzaW5nIHZhbHVlcyByZW1haW4KY2F0KCJcbj09PSBNSVNTSU5HIFZBTFVFUyBBRlRFUiBDTEVBTklORyA9PT1cbiIpCmNhdCgiVHJhaW5pbmc6Iiwgc3VtKGlzLm5hKHRyYWluX2NsZWFuKSksICJcbiIpCmNhdCgiVGVzdDoiLCBzdW0oaXMubmEodGVzdF9jbGVhbikpLCAiXG4iKQpgYGAKCiMjIyMgU3VtbWFyeSBvZiBoYW5kbGluZyBtaXNzaW5nIHZhbHVlcwoKLSAqKlJlbW92ZWQgdGFyZ2V0IHZhcmlhYmxlIE5BczoqKiBEcm9wcGVkIDQgcm93cyBmcm9tIHRyYWluaW5nIHdoZXJlIHBIIHdhcyBtaXNzaW5nLCBsZWF2aW5nIDIsNTY3IGNvbXBsZXRlIHRyYWluaW5nIHJlY29yZHMKCi0gKipIYW5kbGVkIG1pc3NpbmcgdmFsdWVzIGluIHByZWRpY3RvcnM6KiogSW1wdXRlZCBudW1lcmljIGNvbHVtbnMgd2l0aCBtZWRpYW4gdmFsdWVzIGFuZCBjYXRlZ29yaWNhbCAoQnJhbmQgQ29kZSkgd2l0aCBtb2RlOyBubyBjb2x1bW5zIGV4Y2VlZGVkIDUwJSBtaXNzaW5nLCBzbyBhbGwgMzMgb3JpZ2luYWwgZmVhdHVyZXMgd2VyZSByZXRhaW5lZAoKLSAqKkFsaWduZWQgZGF0YXNldHM6KiogRW5zdXJlZCBib3RoIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgaGF2ZSBpZGVudGljYWwgc3RydWN0dXJlIHdpdGggc2FtZSAzMyBjb2x1bW5zIGFuZCBtYXRjaGluZyBmYWN0b3IgbGV2ZWxzIChBLCBCLCBDLCBEIGZvciBCcmFuZCBDb2RlKQoKCgojIyMgT3V0bGllcnMgYW5kIEFub21hbGllcyBEZXRlY3Rpb24KCgpgYGB7ciBvdXRsaWVyc30KCiMgRW5zdXJlIGZhY3Rvcgp0cmFpbl9jbGVhbiRCcmFuZF9Db2RlIDwtIGFzLmZhY3Rvcih0cmFpbl9jbGVhbiQnQnJhbmQgQ29kZScpCnRlc3RfY2xlYW4kQnJhbmRfQ29kZSA8LSBhcy5mYWN0b3IodGVzdF9jbGVhbiQnQnJhbmQgQ29kZScpCgojIDEuIE9VVExJRVIgREVURUNUSU9OIHVzaW5nIElRUiBtZXRob2QKY2F0KCI9PT0gT1VUTElFUiBBTkFMWVNJUyA9PT1cbiIpCgojIEdldCBudW1lcmljIGNvbHVtbnMgKGV4Y2x1ZGUgQnJhbmQgQ29kZSBhbmQgUEgpCm51bWVyaWNfY29scyA8LSBuYW1lcyh0cmFpbl9jbGVhbilbc2FwcGx5KHRyYWluX2NsZWFuLCBpcy5udW1lcmljKV0KbnVtZXJpY19jb2xzIDwtIG51bWVyaWNfY29sc1shbnVtZXJpY19jb2xzICVpbiUgYygiUEgiKV0KCm91dGxpZXJfc3VtbWFyeSA8LSBkYXRhLmZyYW1lKAogIFZhcmlhYmxlID0gY2hhcmFjdGVyKCksCiAgT3V0bGllcnNfQ291bnQgPSBpbnRlZ2VyKCksCiAgT3V0bGllcnNfUGVyY2VudCA9IG51bWVyaWMoKSwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKKQoKZm9yKGNvbCBpbiBudW1lcmljX2NvbHMpIHsKICBRMSA8LSBxdWFudGlsZSh0cmFpbl9jbGVhbltbY29sXV0sIDAuMjUsIG5hLnJtID0gVFJVRSkKICBRMyA8LSBxdWFudGlsZSh0cmFpbl9jbGVhbltbY29sXV0sIDAuNzUsIG5hLnJtID0gVFJVRSkKICBJUVIgPC0gUTMgLSBRMQogIGxvd2VyX2JvdW5kIDwtIFExIC0gMS41ICogSVFSCiAgdXBwZXJfYm91bmQgPC0gUTMgKyAxLjUgKiBJUVIKICAKICBvdXRsaWVycyA8LSBzdW0odHJhaW5fY2xlYW5bW2NvbF1dIDwgbG93ZXJfYm91bmQgfCB0cmFpbl9jbGVhbltbY29sXV0gPiB1cHBlcl9ib3VuZCwgbmEucm0gPSBUUlVFKQogIG91dGxpZXJfcGN0IDwtIChvdXRsaWVycyAvIG5yb3codHJhaW5fY2xlYW4pKSAqIDEwMAogIAogIG91dGxpZXJfc3VtbWFyeSA8LSByYmluZChvdXRsaWVyX3N1bW1hcnksIAogICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKFZhcmlhYmxlID0gY29sLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPdXRsaWVyc19Db3VudCA9IG91dGxpZXJzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBPdXRsaWVyc19QZXJjZW50ID0gcm91bmQob3V0bGllcl9wY3QsIDIpKSkKfQoKIyBTaG93IHRvcCAxMCB2YXJpYWJsZXMgd2l0aCBtb3N0IG91dGxpZXJzCm91dGxpZXJfc3VtbWFyeSA8LSBvdXRsaWVyX3N1bW1hcnlbb3JkZXIoLW91dGxpZXJfc3VtbWFyeSRPdXRsaWVyc19QZXJjZW50KSwgXQpjYXQoIlxuVG9wIDEwIHZhcmlhYmxlcyB3aXRoIGhpZ2hlc3Qgb3V0bGllciBwZXJjZW50YWdlOlxuIikKcHJpbnQoaGVhZChvdXRsaWVyX3N1bW1hcnksIDEwKSkKYGBgCgojIyMjIERhdGEgUXVhbGl0eSBGaW5kaW5ncwoKIyMjIyBUb3AgMyB2YXJpYWJsZXMgd2l0aCBtb3N0IG91dGxpZXJzOgoKLSBGaWxsZXIgU3BlZWQgLSAxNy4xJSBvdXRsaWVycyAoNDQwIG91dCBvZiAyLDU2NyByb3dzKQotIE1GUiAtIDEwLjklIG91dGxpZXJzICgyNzkgcm93cykKLSBBaXIgUHJlc3N1cmVyIC0gOC42JSBvdXRsaWVycyAoMjIwIHJvd3MpCgoqKktleSBpbnNpZ2h0OioqIFRoZXNlIGFyZSBub3QgZGF0YSBlbnRyeSBlcnJvcnMg4oCUIHRoZXkgcmVwcmVzZW50IGxlZ2l0aW1hdGUgbWFudWZhY3R1cmluZyB2YXJpYXRpb24uIEZpbGxlciBzcGVlZCBleHRyZW1lcyBsaWtlbHkgb2NjdXIgZHVyaW5nIHByb2R1Y3Rpb24gcmFtcC11cC9kb3duIG9yIGRpZmZlcmVudCBwcm9kdWN0IHJ1bnMuCgojIyMjIFJlY29tbWVuZGF0aW9uCgoqKkxldHMgS2VlcCBvdXRsaWVycyBidXQgd2Ugd2lsbCBkb2N1bWVudCB0aGVtLiBXaHk/CgotIE1hbnVmYWN0dXJpbmcgcHJvY2Vzc2VzIG5hdHVyYWxseSBoYXZlIGV4dHJlbWUgdmFsdWVzIChzdGFydHVwLCBzaHV0ZG93biwgZGlmZmVyZW50IGJyYW5kcykKCi0gUmVtb3ZpbmcgMTclIG9mIGRhdGEgd291bGQgYmlhcyBvdXIgbW9kZWwKCi0gVGhlc2UgZXh0cmVtZXMgaGVscCBwcmVkaWN0IHBIIGFjcm9zcyBmdWxsIG9wZXJhdGluZyByYW5nZQoKIyMjIENoZWNrIE5lZ2F0aXZlIGFuZCB6ZXJvIHZhbHVlcwoKYGBge3IgY2hlY2tfaW1wb3NzaWJsZX0KIyAyLiBDSEVDSyBGT1IgSU1QT1NTSUJMRS9OT05TRU5TSUNBTCBWQUxVRVMKY2F0KCJcbj09PSBMT0dJQ0FMIENPTlNJU1RFTkNZIENIRUNLUyA9PT1cbiIpCgojIENoZWNrIGZvciBuZWdhdGl2ZSBwcmVzc3VyZXMgKHNob3VsZCBiZSBpbXBvc3NpYmxlKQpwcmVzc3VyZV9jb2xzIDwtIGdyZXAoIlByZXNzdXJlIiwgbmFtZXModHJhaW5fY2xlYW4pLCB2YWx1ZSA9IFRSVUUpCmZvcihjb2wgaW4gcHJlc3N1cmVfY29scykgewogIG5lZ2F0aXZlcyA8LSBzdW0odHJhaW5fY2xlYW5bW2NvbF1dIDwgMCwgbmEucm0gPSBUUlVFKQogIGlmKG5lZ2F0aXZlcyA+IDApIHsKICAgIGNhdChjb2wsICI6ICIsIG5lZ2F0aXZlcywgIm5lZ2F0aXZlIHZhbHVlcyAocmFuZ2U6IiwgbWluKHRyYWluX2NsZWFuW1tjb2xdXSksICJ0byIsIG1heCh0cmFpbl9jbGVhbltbY29sXV0pLCAiKVxuIikKICB9Cn0KCiMgQ2hlY2sgZm9yIHplcm8gb3IgbmVnYXRpdmUgaW4gdm9sdW1lL2Zsb3cgbWVhc3VyZW1lbnRzCnZvbHVtZV9jb2xzIDwtIGMoIkNhcmIuVm9sdW1lIiwgIkZpbGwuT3VuY2VzIiwgIlBDLlZvbHVtZSIsICJDYXJiLkZsb3ciKQpmb3IoY29sIGluIHZvbHVtZV9jb2xzKSB7CiAgaWYoY29sICVpbiUgbmFtZXModHJhaW5fY2xlYW4pKSB7CiAgICB6ZXJvc19vcl9uZWcgPC0gc3VtKHRyYWluX2NsZWFuW1tjb2xdXSA8PSAwLCBuYS5ybSA9IFRSVUUpCiAgICBpZih6ZXJvc19vcl9uZWcgPiAwKSB7CiAgICAgIGNhdChjb2wsICI6ICIsIHplcm9zX29yX25lZywgInZhbHVlcyA8PSAwXG4iKQogICAgfQogIH0KfQoKIyAzLiBDSEVDSyBEQVRBIFRZUEVTIEFORCBGT1JNQVRTCmNhdCgiXG49PT0gREFUQSBUWVBFIFZFUklGSUNBVElPTiA9PT1cbiIpCmNhdCgicEggcmFuZ2UgaW4gdHJhaW5pbmc6IiwgcmFuZ2UodHJhaW5fY2xlYW4kUEgpLCAiXG4iKQpjYXQoInBIIHNob3VsZCBiZSBiZXR3ZWVuIDAtMTQuIEFsbCB2YWx1ZXMgdmFsaWQ6IiwgCiAgICBhbGwodHJhaW5fY2xlYW4kUEggPj0gMCAmIHRyYWluX2NsZWFuJFBIIDw9IDE0KSwgIlxuIikKCiMgQ2hlY2sgZm9yIGR1cGxpY2F0ZSByb3dzCmR1cGxpY2F0ZXMgPC0gc3VtKGR1cGxpY2F0ZWQodHJhaW5fY2xlYW5bLCAhbmFtZXModHJhaW5fY2xlYW4pICVpbiUgIlBIIl0pKQpjYXQoIkR1cGxpY2F0ZSByb3dzIChleGNsdWRpbmcgcEgpOiIsIGR1cGxpY2F0ZXMsICJcbiIpCmBgYAoKIyMjIyBLZXkgRmluZGluZ3MgZnJvbSBRdWFsaXR5IENoZWNrcwoKKipOZWdhdGl2ZSB2YWx1ZXMgZGV0ZWN0ZWQgOioqCgotIEh5ZCBQcmVzc3VyZTE6IDM4IG5lZ2F0aXZlIHZhbHVlcyAoZG93biB0byAtMC44KQoKLSBIeWQgUHJlc3N1cmUzOiA3MCBuZWdhdGl2ZSB2YWx1ZXMgKGRvd24gdG8gLTEuMikKCl8gUHJlc3N1cmUgVmFjdXVtOiBBTEwgdmFsdWVzIG5lZ2F0aXZlICgtNi42IHRvIC0zLjYpIOKAlCB0aGlzIGlzIGNvcnJlY3QgKHZhY3V1bSA9IG5lZ2F0aXZlIHByZXNzdXJlKQoKKk5vIGR1cGxpY2F0ZSByb3dzIOKAlCBkYXRhIGlzIGNsZWFuKgoKcEggcmFuZ2U6IDcuODggdG8gOS4zNiAobm9ybWFsIGZvciBiZXZlcmFnZXMpCgoKCiMjIyBTYXZlIGNsZWFuIERhdGFzZXRzCgpgYGB7ciBzYXZlX2NsZWFuRGF0YX0KIyBTYXZlIGNsZWFuZWQgZGF0YQp3cml0ZS5jc3YodHJhaW5fY2xlYW4sICJ0cmFpbmluZ19kYXRhX2NsZWFuZWQuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpCndyaXRlLmNzdih0ZXN0X2NsZWFuLCAidGVzdF9kYXRhX2NsZWFuZWQuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpCgpjYXQoIlxuPT09IENMRUFOSU5HIENPTVBMRVRFID09PVxuIikKY2F0KCJGaW5hbCB0cmFpbmluZyBkaW1lbnNpb25zOiIsIGRpbSh0cmFpbl9jbGVhbiksICJcbiIpCmNhdCgiRmluYWwgdGVzdCBkaW1lbnNpb25zOiIsIGRpbSh0ZXN0X2NsZWFuKSwgIlxuIikKYGBgCgojIyBFeHBsb3JhdG9yeSBEYXRhIEFuYWx5c2lzCgojIyMgQ29ycmVsYXRpb24gTWF0cml4IGZvciBwSCBQcmVkaWN0aW9uCgpgYGB7ciBlZGFfY29ycn0KIyBFREE6IENvcnJlbGF0aW9uIE1hdHJpeCBmb3IgcEggUHJlZGljdGlvbgoKIyBFbnN1cmUgQnJhbmQgQ29kZSBpcyBmYWN0b3IKdHJhaW5fY2xlYW4kYEJyYW5kIENvZGVgIDwtIGFzLmZhY3Rvcih0cmFpbl9jbGVhbiRgQnJhbmQgQ29kZWApCgojIENhbGN1bGF0ZSBjb3JyZWxhdGlvbnMgd2l0aCBwSApudW1lcmljX2NvbHMgPC0gbmFtZXModHJhaW5fY2xlYW4pW3NhcHBseSh0cmFpbl9jbGVhbiwgaXMubnVtZXJpYyldCnRhcmdldF9jb3JyZWxhdGlvbnMgPC0gZGF0YS5mcmFtZSgKICBWYXJpYWJsZSA9IGNoYXJhY3RlcigpLAogIENvcnJlbGF0aW9uID0gbnVtZXJpYygpLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopCgpmb3IoY29sIGluIG51bWVyaWNfY29scykgewogIGlmKGNvbCAhPSAiUEgiKSB7CiAgICBjb3JfdmFsIDwtIGNvcih0cmFpbl9jbGVhbltbY29sXV0sIHRyYWluX2NsZWFuJFBILCB1c2UgPSAiY29tcGxldGUub2JzIikKICAgIHRhcmdldF9jb3JyZWxhdGlvbnMgPC0gcmJpbmQodGFyZ2V0X2NvcnJlbGF0aW9ucywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLmZyYW1lKFZhcmlhYmxlID0gY29sLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ29ycmVsYXRpb24gPSByb3VuZChjb3JfdmFsLCAzKSkpCiAgfQp9CgojIFNvcnQgYnkgYWJzb2x1dGUgY29ycmVsYXRpb24KdGFyZ2V0X2NvcnJlbGF0aW9ucyA8LSB0YXJnZXRfY29ycmVsYXRpb25zW29yZGVyKC1hYnModGFyZ2V0X2NvcnJlbGF0aW9ucyRDb3JyZWxhdGlvbikpLCBdCgpjYXQoIj09PSBUT1AgMTUgVkFSSUFCTEVTIE1PU1QgQ09SUkVMQVRFRCBXSVRIIHBIID09PVxuIikKcHJpbnQoaGVhZCh0YXJnZXRfY29ycmVsYXRpb25zLCAxNSkpCgojIENyZWF0ZSBjb3JyZWxhdGlvbiBtYXRyaXggZm9yIHRvcCB2YXJpYWJsZXMKdG9wX3ZhcnMgPC0gaGVhZCh0YXJnZXRfY29ycmVsYXRpb25zJFZhcmlhYmxlLCAxNSkKdG9wX3ZhcnMgPC0gYyh0b3BfdmFycywgIlBIIikKY29yX21hdHJpeCA8LSBjb3IodHJhaW5fY2xlYW5bLCB0b3BfdmFyc10sIHVzZSA9ICJjb21wbGV0ZS5vYnMiKQoKIyBWaXN1YWxpemUgY29ycmVsYXRpb24gbWF0cml4CmNvcnJwbG90KGNvcl9tYXRyaXgsIAogICAgICAgICBtZXRob2QgPSAiY29sb3IiLCAKICAgICAgICAgdHlwZSA9ICJ1cHBlciIsCiAgICAgICAgIHRsLmNleCA9IDAuOCwKICAgICAgICAgdGwuY29sID0gImJsYWNrIiwKICAgICAgICAgdGl0bGUgPSAiVG9wIDE1IFZhcmlhYmxlIENvcnJlbGF0aW9ucyB3aXRoIHBIIiwKICAgICAgICAgbWFyID0gYygwLDAsMiwwKSkKCiMgQmFyIHBsb3Qgb2YgdG9wIGNvcnJlbGF0aW9ucyB3aXRoIHBICmdncGxvdChoZWFkKHRhcmdldF9jb3JyZWxhdGlvbnMsIDE1KSwgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlLCBhYnMoQ29ycmVsYXRpb24pKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IENvcnJlbGF0aW9uLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gQ29ycmVsYXRpb24gPiAwKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiKSArCiAgY29vcmRfZmxpcCgpICsKICBsYWJzKHRpdGxlID0gIlRvcCAxNSBGYWN0b3JzIENvcnJlbGF0ZWQgd2l0aCBwSCIsCiAgICAgICB4ID0gIlByb2Nlc3MgVmFyaWFibGVzIiwKICAgICAgIHkgPSAiQ29ycmVsYXRpb24gd2l0aCBwSCIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoImRhcmtyZWQiLCAiZGFya2dyZWVuIiksIAogICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIk5lZ2F0aXZlIiwgIlBvc2l0aXZlIiksCiAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJEaXJlY3Rpb24iKQoKIyBTdW1tYXJ5IHN0YXRpc3RpY3MgZm9yIHRvcCAzIHByZWRpY3RvcnMgdnMgcEgKdG9wXzMgPC0gaGVhZCh0YXJnZXRfY29ycmVsYXRpb25zJFZhcmlhYmxlLCAzKQpjYXQoIlxuPT09IFRPUCAzIFBSRURJQ1RPUlMgU1VNTUFSWSA9PT1cbiIpCmZvcih2YXIgaW4gdG9wXzMpIHsKICBjYXQoIlxuIiwgdmFyLCAiKENvcnJlbGF0aW9uOiIsIHRhcmdldF9jb3JyZWxhdGlvbnNbdGFyZ2V0X2NvcnJlbGF0aW9ucyRWYXJpYWJsZSA9PSB2YXIsICJDb3JyZWxhdGlvbiJdLCAiKVxuIikKICBwcmludChzdW1tYXJ5KHRyYWluX2NsZWFuW1t2YXJdXSkpCn0KYGBgCgojIyMjIENvcnJlbGF0aW9uIEFuYWx5c2lzIEZpbmRpbmdzCgojIyMjIFRvcCBwSCBQcmVkaWN0b3JzIElkZW50aWZpZWQ6CgotICoqTW5mIEZsb3cgKE1hbnVmYWN0dXJpbmcgRmxvdyk6KiogU3Ryb25nZXN0IG5lZ2F0aXZlIGNvcnJlbGF0aW9uICgtMC40NDcpIOKAlCBIaWdoZXIgZmxvdyByYXRlcyA9IExvd2VyIHBICgotICoqQm93bCBTZXRwb2ludDoqKiBTdHJvbmcgcG9zaXRpdmUgY29ycmVsYXRpb24gKDAuMzQ5KSDigJQgSGlnaGVyIHNldHBvaW50ID0gSGlnaGVyIHBICgotICoqRmlsbGVyIExldmVsOioqIE1vZGVyYXRlIHBvc2l0aXZlIGNvcnJlbGF0aW9uICgwLjMyMikg4oCUIEhpZ2hlciBmaWxsIGxldmVsID0gSGlnaGVyIHBICgotICoqVXNhZ2UgY29udDoqKiBOZWdhdGl2ZSBjb3JyZWxhdGlvbiAoLTAuMzE4KSDigJQgTW9yZSB1c2FnZSA9IExvd2VyIHBICgotICoqUHJlc3N1cmUgU2V0cG9pbnQ6KiogTmVnYXRpdmUgY29ycmVsYXRpb24gKC0wLjMwNikg4oCUIEhpZ2hlciBwcmVzc3VyZSA9IExvd2VyIHBICgoKKipLZXkgSW5zaWdodDoqKiBNYW51ZmFjdHVyaW5nIGZsb3cgcmF0ZSAoTW5mIEZsb3cpIGlzIHRoZSBzaW5nbGUgbW9zdCBpbXBvcnRhbnQgZmFjdG9yIGNvbnRyb2xsaW5nIHBILCBmb2xsb3dlZCBieSBib3dsIHNldHBvaW50IGFuZCBmaWxsZXIgbGV2ZWwuCgojIyMgQnJhbmQgQW5hbHlzaXMKCmBgYHtyIGJyYWRuX2FuYWx5c2lzfQojIEVEQTogQnJhbmQgQW5hbHlzaXMgLSBEb2VzIHBIIGRpZmZlciBieSBicmFuZD8KCgojIENhbGN1bGF0ZSBwSCBzdGF0aXN0aWNzIGJ5IGJyYW5kCmJyYW5kX3N0YXRzIDwtIHRyYWluX2NsZWFuICU+JQogIGdyb3VwX2J5KGBCcmFuZCBDb2RlYCkgJT4lCiAgc3VtbWFyaXNlKAogICAgQ291bnQgPSBuKCksCiAgICBNZWFuX3BIID0gcm91bmQobWVhbihQSCksIDMpLAogICAgU0RfcEggPSByb3VuZChzZChQSCksIDMpLAogICAgTWluX3BIID0gcm91bmQobWluKFBIKSwgMyksCiAgICBNYXhfcEggPSByb3VuZChtYXgoUEgpLCAzKSwKICAgIC5ncm91cHMgPSAnZHJvcCcKICApCgpjYXQoIj09PSBQSCBTVEFUSVNUSUNTIEJZIEJSQU5EID09PVxuIikKcHJpbnQoYnJhbmRfc3RhdHMpCgojIEFOT1ZBIHRlc3QgdG8gY2hlY2sgaWYgYnJhbmQgZGlmZmVyZW5jZXMgYXJlIHNpZ25pZmljYW50CmFub3ZhX3Jlc3VsdCA8LSBhb3YoUEggfiBgQnJhbmQgQ29kZWAsIGRhdGEgPSB0cmFpbl9jbGVhbikKY2F0KCJcbj09PSBBTk9WQSBSRVNVTFRTID09PVxuIikKcHJpbnQoc3VtbWFyeShhbm92YV9yZXN1bHQpKQoKIyBWaXN1YWxpemF0aW9uOiBCb3hwbG90IG9mIHBIIGJ5IGJyYW5kCmdncGxvdCh0cmFpbl9jbGVhbiwgYWVzKHggPSBgQnJhbmQgQ29kZWAsIHkgPSBQSCwgZmlsbCA9IGBCcmFuZCBDb2RlYCkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgbGFicyh0aXRsZSA9ICJwSCBEaXN0cmlidXRpb24gYnkgQnJhbmQgQ29kZSIsCiAgICAgICB4ID0gIkJyYW5kIiwKICAgICAgIHkgPSAicEggTGV2ZWwiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlNldDIiKSArCiAgc3RhdF9zdW1tYXJ5KGZ1biA9IG1lYW4sIGdlb20gPSAicG9pbnQiLCBzaGFwZSA9IDE4LCBzaXplID0gMywgY29sb3IgPSAicmVkIikKCiMgVmlzdWFsaXphdGlvbjogRGVuc2l0eSBwbG90CmdncGxvdCh0cmFpbl9jbGVhbiwgYWVzKHggPSBQSCwgZmlsbCA9IGBCcmFuZCBDb2RlYCkpICsKICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjUpICsKICBsYWJzKHRpdGxlID0gInBIIERlbnNpdHkgRGlzdHJpYnV0aW9uIGJ5IEJyYW5kIiwKICAgICAgIHggPSAicEgiLAogICAgICAgeSA9ICJEZW5zaXR5IikgKwogIHRoZW1lX21pbmltYWwoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJTZXQyIikKYGBgCgojIyMjIEJyYW5kIEFuYWx5c2lzIFN1bW1hcnkKCi0gKipTaWduaWZpY2FudCBwSCBkaWZmZXJlbmNlcyBhY3Jvc3MgYnJhbmRzIChwLXZhbHVlIDwgMC4wMDEpKiog4oCUIEJyYW5kIEQgaGFzIGhpZ2hlc3QgcEggKDguNjApLCBCcmFuZCBDIGxvd2VzdCAoOC40MSkKCi0gKipCcmFuZCBCKiogaGFzIGxhcmdlc3Qgdm9sdW1lICgxLDM1NSBiYXRjaGVzKSBhbmQgd2lkZSBwSCByYW5nZSAoOC4wMCB0byA4Ljk0KQoKLSAqKkJyYW5kIEMqKiBzaG93cyBtb3N0IHZhcmlhdGlvbiAoU0QgPSAwLjE3Nykgd2l0aCBleHRyZW1lIHZhbHVlcyAoNy44OCB0byA5LjM2KQoKIyMjIEludGVyYWN0aW9uIEVmZmVjdHMKCmBgYHtyIEludGVyYWN0aW9uX0VmZmVjdHN9CiMgRURBOiBJbnRlcmFjdGlvbiBFZmZlY3RzIC0gRG8gY29tYmluYXRpb25zIG9mIGZhY3RvcnMgcHJlZGljdCBwSCBiZXR0ZXI/CgojIEludGVyYWN0aW9uIHBsb3Q6IE1uZiBGbG93IMOXIEJyYW5kIG9uIHBICmdncGxvdCh0cmFpbl9jbGVhbiwgYWVzKHggPSBgTW5mIEZsb3dgLCB5ID0gUEgsIGNvbG9yID0gYEJyYW5kIENvZGVgKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjMpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFKSArCiAgbGFicyh0aXRsZSA9ICJJbnRlcmFjdGlvbjogTWFudWZhY3R1cmluZyBGbG93IMOXIEJyYW5kIG9uIHBIIiwKICAgICAgIHggPSAiTWFudWZhY3R1cmluZyBGbG93IChNbmYgRmxvdykiLAogICAgICAgeSA9ICJwSCIsCiAgICAgICBjb2xvciA9ICJCcmFuZCIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQ2hlY2sgaW50ZXJhY3Rpb246IEJvd2wgU2V0cG9pbnQgw5cgRmlsbGVyIExldmVsCmdncGxvdCh0cmFpbl9jbGVhbiwgYWVzKHggPSBgQm93bCBTZXRwb2ludGAsIHkgPSBQSCwgY29sb3IgPSBgRmlsbGVyIExldmVsYCkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC40KSArCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQobG93ID0gImJsdWUiLCBoaWdoID0gInJlZCIpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJibGFjayIpICsKICBsYWJzKHRpdGxlID0gIkludGVyYWN0aW9uOiBCb3dsIFNldHBvaW50IMOXIEZpbGxlciBMZXZlbCBvbiBwSCIsCiAgICAgICB4ID0gIkJvd2wgU2V0cG9pbnQiLAogICAgICAgeSA9ICJwSCIsCiAgICAgICBjb2xvciA9ICJGaWxsZXIgTGV2ZWwiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIENhbGN1bGF0ZSBpbnRlcmFjdGlvbiBzdHJlbmd0aAptb2RlbF9tYWluIDwtIGxtKFBIIH4gYE1uZiBGbG93YCArIGBCcmFuZCBDb2RlYCwgZGF0YSA9IHRyYWluX2NsZWFuKQptb2RlbF9pbnRlcmFjdGlvbiA8LSBsbShQSCB+IGBNbmYgRmxvd2AgKiBgQnJhbmQgQ29kZWAsIGRhdGEgPSB0cmFpbl9jbGVhbikKCmNhdCgiPT09IElOVEVSQUNUSU9OIEVGRkVDVCBTSUdOSUZJQ0FOQ0UgPT09XG4iKQpjYXQoIk1haW4gZWZmZWN0cyBSwrI6Iiwgcm91bmQoc3VtbWFyeShtb2RlbF9tYWluKSRyLnNxdWFyZWQsIDQpLCAiXG4iKQpjYXQoIldpdGggaW50ZXJhY3Rpb24gUsKyOiIsIHJvdW5kKHN1bW1hcnkobW9kZWxfaW50ZXJhY3Rpb24pJHIuc3F1YXJlZCwgNCksICJcbiIpCmNhdCgiSW1wcm92ZW1lbnQ6Iiwgcm91bmQoc3VtbWFyeShtb2RlbF9pbnRlcmFjdGlvbikkci5zcXVhcmVkIC0gc3VtbWFyeShtb2RlbF9tYWluKSRyLnNxdWFyZWQsIDQpLCAiXG4iKQoKIyBBTk9WQSB0byB0ZXN0IGlmIGludGVyYWN0aW9uIGlzIHNpZ25pZmljYW50CmFub3ZhX2NvbXBhcmUgPC0gYW5vdmEobW9kZWxfbWFpbiwgbW9kZWxfaW50ZXJhY3Rpb24pCnByaW50KGFub3ZhX2NvbXBhcmUpCmBgYAoKIyMjIyBJbnRlcmFjdGlvbiBFZmZlY3RzIFN1bW1hcnkKCi0gKipJbnRlcmFjdGlvbiBpbXByb3ZlcyBwcmVkaWN0aW9uIGJ5IDEuOCUgKFLCsiBmcm9tIDAuMzA1IHRvIDAuMzIzKSDigJQqKiBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50IChwIDwgMC4wMDEpCi0gKipCcmFuZCBhZmZlY3RzIGhvdyBNbmYgRmxvdyBpbmZsdWVuY2VzIHBIIOKAlCoqIHJlbGF0aW9uc2hpcCBkaWZmZXJzIGFjcm9zcyBicmFuZHMgQSwgQiwgQywgRAotICoqTmVlZCB0byBpbmNsdWRlIEJyYW5kIGluIGZpbmFsIG1vZGVsIOKAlCoqIGlnbm9yaW5nIGl0IHJlZHVjZXMgYWNjdXJhY3kKCiMjIyBwSCBEaXN0cmlidXRpb24gQ2hlY2sKCmBgYHtyIHBIX3NrZXduZXNzX2NoZWNrfQojIEVEQTogU2tld25lc3MgQ2hlY2sgZm9yIHBICgojIExvYWQgbGlicmFyeQpsaWJyYXJ5KGUxMDcxKQoKIyBDYWxjdWxhdGUgc2tld25lc3MKc2tld192YWx1ZSA8LSBza2V3bmVzcyh0cmFpbl9jbGVhbiRQSCkKY2F0KCJTa2V3bmVzcyBvZiBwSDoiLCByb3VuZChza2V3X3ZhbHVlLCA0KSwgIlxuIikKCiMgSW50ZXJwcmV0YXRpb24gcnVsZXMgb2YgdGh1bWIKaWYoYWJzKHNrZXdfdmFsdWUpIDwgMC41KSB7CiAgY2F0KCJJbnRlcnByZXRhdGlvbjogQXBwcm94aW1hdGVseSBzeW1tZXRyaWNcbiIpCn0gZWxzZSBpZihhYnMoc2tld192YWx1ZSkgPCAxKSB7CiAgY2F0KCJJbnRlcnByZXRhdGlvbjogTW9kZXJhdGVseSBza2V3ZWRcbiIpCn0gZWxzZSB7CiAgY2F0KCJJbnRlcnByZXRhdGlvbjogSGlnaGx5IHNrZXdlZFxuIikKfQoKIyBRdWljayB2aXN1YWw6IFNpbXBsZSBoaXN0b2dyYW0KaGlzdCh0cmFpbl9jbGVhbiRQSCwgCiAgICAgbWFpbiA9ICJwSCBEaXN0cmlidXRpb24iLCAKICAgICB4bGFiID0gInBIIiwKICAgICBjb2wgPSAibGlnaHRibHVlIiwKICAgICBib3JkZXIgPSAiYmxhY2siLAogICAgIGJyZWFrcyA9IDIwKQoKIyBBZGQgdmVydGljYWwgbGluZSBhdCBtZWFuCmFibGluZSh2ID0gbWVhbih0cmFpbl9jbGVhbiRQSCksIGNvbCA9ICJyZWQiLCBsd2QgPSAyLCBsdHkgPSAyKQoKIyBTaG93IG1lYW4gdnMgbWVkaWFuCmNhdCgiXG5NZWFuOiIsIHJvdW5kKG1lYW4odHJhaW5fY2xlYW4kUEgpLCA0KSkKY2F0KCJcbk1lZGlhbjoiLCByb3VuZChtZWRpYW4odHJhaW5fY2xlYW4kUEgpLCA0KSkKY2F0KCJcbkRpZmZlcmVuY2U6Iiwgcm91bmQobWVhbih0cmFpbl9jbGVhbiRQSCkgLSBtZWRpYW4odHJhaW5fY2xlYW4kUEgpLCA0KSkKYGBgCgojIyMjIFNrZXduZXNzIFN1bW1hcnkKCi0gKipwSCBpcyBhcHByb3hpbWF0ZWx5IHN5bW1ldHJpYyoqIChza2V3bmVzcyA9IC0wLjI5LCB3ZWxsIHdpdGhpbiDCsTAuNSByYW5nZSkKCi0gKipNZWFuIGFuZCBtZWRpYW4gYXJlIG5lYXJseSBpZGVudGljYWwqKiAoOC41NDU2IHZzIDguNTQwMCwgZGlmZmVyZW5jZSBvbmx5IDAuMDA1NikKCi0gKipMaW5lYXIgcmVncmVzc2lvbiBpcyB2aWFibGUqKiDigJQgbm8gc2V2ZXJlIHNrZXduZXNzIHJlcXVpcmluZyB0cmFuc2Zvcm1hdGlvbgoKCiMjIyBNdWx0aWNvbGxpbmVhcml0eSBDaGVjawoKYGBge3J9CiMgRURBIE11bHRpY29sbGluZWFyaXR5IC0gQXJlIHRvcCBwcmVkaWN0b3JzIGNvcnJlbGF0ZWQgd2l0aCBlYWNoIG90aGVyPwoKCiMgU2VsZWN0IHRvcCAxMCBwcmVkaWN0b3JzIGZyb20gZWFybGllciBjb3JyZWxhdGlvbiBhbmFseXNpcwp0b3BfcHJlZGljdG9ycyA8LSBjKCJNbmYgRmxvdyIsICJCb3dsIFNldHBvaW50IiwgIkZpbGxlciBMZXZlbCIsICJVc2FnZSBjb250IiwgCiAgICAgICAgICAgICAgICAgICAgIlByZXNzdXJlIFNldHBvaW50IiwgIkh5ZCBQcmVzc3VyZTMiLCAiUHJlc3N1cmUgVmFjdXVtIiwgCiAgICAgICAgICAgICAgICAgICAgIkZpbGwgUHJlc3N1cmUiLCAiSHlkIFByZXNzdXJlMiIsICJPeHlnZW4gRmlsbGVyIikKCiMgQ3JlYXRlIGNvcnJlbGF0aW9uIG1hdHJpeCBmb3IgdG9wIHByZWRpY3RvcnMKY29yX21hdHJpeF90b3AgPC0gY29yKHRyYWluX2NsZWFuWywgdG9wX3ByZWRpY3RvcnNdLCB1c2UgPSAiY29tcGxldGUub2JzIikKCiMgVmlzdWFsaXplCmNvcnJwbG90Ojpjb3JycGxvdChjb3JfbWF0cml4X3RvcCwgCiAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAibnVtYmVyIiwgCiAgICAgICAgICAgICAgICAgICB0eXBlID0gInVwcGVyIiwKICAgICAgICAgICAgICAgICAgIHRsLmNleCA9IDAuOCwKICAgICAgICAgICAgICAgICAgIHRpdGxlID0gIk11bHRpY29sbGluZWFyaXR5IEFtb25nIFRvcCBQcmVkaWN0b3JzIiwKICAgICAgICAgICAgICAgICAgIG1hciA9IGMoMCwwLDIsMCkpCgojIENhbGN1bGF0ZSBWSUYgKFZhcmlhbmNlIEluZmxhdGlvbiBGYWN0b3IpCiMgQnVpbGQgbGluZWFyIG1vZGVsIHdpdGggYWxsIHRvcCBwcmVkaWN0b3JzCnZpZl9tb2RlbCA8LSBsbShQSCB+IGBNbmYgRmxvd2AgKyBgQm93bCBTZXRwb2ludGAgKyBgRmlsbGVyIExldmVsYCArIGBVc2FnZSBjb250YCArIAogICAgICAgICAgICAgICAgICAgICAgYFByZXNzdXJlIFNldHBvaW50YCArIGBIeWQgUHJlc3N1cmUzYCArIGBQcmVzc3VyZSBWYWN1dW1gICsgCiAgICAgICAgICAgICAgICAgICAgICBgRmlsbCBQcmVzc3VyZWAgKyBgSHlkIFByZXNzdXJlMmAgKyBgT3h5Z2VuIEZpbGxlcmAsIAogICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2NsZWFuKQoKdmlmX3ZhbHVlcyA8LSB2aWYodmlmX21vZGVsKQoKY2F0KCI9PT0gVkFSSUFOQ0UgSU5GTEFUSU9OIEZBQ1RPUiAoVklGKSA9PT1cbiIpCmNhdCgiUnVsZSBvZiB0aHVtYjogVklGID4gNSBpbmRpY2F0ZXMgaGlnaCBtdWx0aWNvbGxpbmVhcml0eVxuIikKY2F0KCJWSUYgPiAxMCBpbmRpY2F0ZXMgc2V2ZXJlIG11bHRpY29sbGluZWFyaXR5XG5cbiIpCgpwcmludChkYXRhLmZyYW1lKFZhcmlhYmxlID0gbmFtZXModmlmX3ZhbHVlcyksIFZJRiA9IHJvdW5kKHZpZl92YWx1ZXMsIDIpKSkKCiMgQ2hlY2sgaWYgYW55IFZJRiA+IDUKaGlnaF92aWYgPC0gbmFtZXModmlmX3ZhbHVlc1t2aWZfdmFsdWVzID4gNV0pCmlmKGxlbmd0aChoaWdoX3ZpZikgPiAwKSB7CiAgY2F0KCJcbiBIaWdoIG11bHRpY29sbGluZWFyaXR5IGRldGVjdGVkIGZvcjoiLCBwYXN0ZShoaWdoX3ZpZiwgY29sbGFwc2UgPSAiLCAiKSwgIlxuIikKICBjYXQoIlJlY29tbWVuZGF0aW9uOiBSZW1vdmUgb3IgY29tYmluZSB0aGVzZSB2YXJpYWJsZXNcbiIpCn0gZWxzZSB7CiAgY2F0KCJcbiBObyBjb25jZXJuaW5nIG11bHRpY29sbGluZWFyaXR5IChhbGwgVklGIDwgNSlcbiIpCiAgY2F0KCJBbGwgdG9wIHByZWRpY3RvcnMgY2FuIGJlIHVzZWQgdG9nZXRoZXJcbiIpCn0KYGBgCgojIyMjIE11bHRpY29sbGluZWFyaXR5IFN1bW1hcnkKCi0gKipIaWdoIGNvcnJlbGF0aW9uIGRldGVjdGVkKiog4oCUIEJvd2wgU2V0cG9pbnQgKFZJRiA4LjgpLCBGaWxsZXIgTGV2ZWwgKDguODUpLCBIeWQgUHJlc3N1cmUzICgxMC43MiksIEh5ZCBQcmVzc3VyZTIgKDcuNjIpIGFyZSByZWR1bmRhbnQKLSAqKkltcGFjdCBvbiBtb2RlbGluZyoqIOKAlCBUaGVzZSB2YXJpYWJsZXMgbWVhc3VyZSBzaW1pbGFyIHByb2Nlc3MgY29uZGl0aW9uczsga2VlcGluZyBhbGwgY2F1c2VzIHVuc3RhYmxlIGNvZWZmaWNpZW50IGVzdGltYXRlcwotICoqUmVjb21tZW5kYXRpb24qKiDigJQgU2VsZWN0IHJlcHJlc2VudGF0aXZlIHZhcmlhYmxlcyAoZS5nLiwga2VlcCBGaWxsZXIgTGV2ZWwsIGRyb3AgSHlkIFByZXNzdXJlMi8zKSBvciB1c2UgdHJlZS1iYXNlZCBtb2RlbHMgKFJhbmRvbSBGb3Jlc3QvWEdCb29zdCkgd2hpY2ggaGFuZGxlIHJlZHVuZGFuY3kgbmF0dXJhbGx5CgoKIyMjIE5vbi1saW5lYXIgUmVsYXRpb25zaGlwcwoKYGBge3IgQ2hlY2tfZm9yX25vbi1saW5lYXJfcmVsYXRpb25zaGlwc30KIyBFREE6IENoZWNrIGZvciBub24tbGluZWFyIHJlbGF0aW9uc2hpcHMgd2l0aCBwSAoKCiMgVG9wIDMgcHJlZGljdG9ycyB2cyBwSCB3aXRoIExPRVNTIHNtb290aCBjdXJ2ZXMKcDEgPC0gZ2dwbG90KHRyYWluX2NsZWFuLCBhZXMoeCA9IGBNbmYgRmxvd2AsIHkgPSBQSCkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4zLCBzaXplID0gMC44KSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxvZXNzIiwgY29sb3IgPSAicmVkIiwgc2UgPSBUUlVFKSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgY29sb3IgPSAiYmx1ZSIsIGxpbmV0eXBlID0gImRhc2hlZCIsIHNlID0gRkFMU0UpICsKICBsYWJzKHRpdGxlID0gIk1uZiBGbG93IHZzIHBIIiwKICAgICAgIHggPSAiTWFudWZhY3R1cmluZyBGbG93IiwKICAgICAgIHkgPSAicEgiKSArCiAgdGhlbWVfbWluaW1hbCgpCgpwMiA8LSBnZ3Bsb3QodHJhaW5fY2xlYW4sIGFlcyh4ID0gYEJvd2wgU2V0cG9pbnRgLCB5ID0gUEgpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMywgc2l6ZSA9IDAuOCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIsIGNvbG9yID0gInJlZCIsIHNlID0gVFJVRSkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGNvbG9yID0gImJsdWUiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzZSA9IEZBTFNFKSArCiAgbGFicyh0aXRsZSA9ICJCb3dsIFNldHBvaW50IHZzIHBIIiwKICAgICAgIHggPSAiQm93bCBTZXRwb2ludCIsCiAgICAgICB5ID0gInBIIikgKwogIHRoZW1lX21pbmltYWwoKQoKcDMgPC0gZ2dwbG90KHRyYWluX2NsZWFuLCBhZXMoeCA9IGBGaWxsZXIgTGV2ZWxgLCB5ID0gUEgpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMywgc2l6ZSA9IDAuOCkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsb2VzcyIsIGNvbG9yID0gInJlZCIsIHNlID0gVFJVRSkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIsIGNvbG9yID0gImJsdWUiLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBzZSA9IEZBTFNFKSArCiAgbGFicyh0aXRsZSA9ICJGaWxsZXIgTGV2ZWwgdnMgcEgiLAogICAgICAgeCA9ICJGaWxsZXIgTGV2ZWwiLAogICAgICAgeSA9ICJwSCIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQXJyYW5nZSBwbG90cwpsaWJyYXJ5KGdyaWRFeHRyYSkKZ3JpZC5hcnJhbmdlKHAxLCBwMiwgcDMsIG5jb2wgPSAzKQoKIyBRdWljayBjaGVjazogSXMgbGluZWFyIG1vZGVsIHN1ZmZpY2llbnQ/Cm1vZGVsX2xpbmVhciA8LSBsbShQSCB+IGBNbmYgRmxvd2AsIGRhdGEgPSB0cmFpbl9jbGVhbikKbW9kZWxfcG9seSA8LSBsbShQSCB+IHBvbHkoYE1uZiBGbG93YCwgMiksIGRhdGEgPSB0cmFpbl9jbGVhbikKCmNhdCgiPT09IExJTkVBUiB2cyBQT0xZTk9NSUFMIENPTVBBUklTT04gPT09XG4iKQpjYXQoIkxpbmVhciBSwrI6Iiwgcm91bmQoc3VtbWFyeShtb2RlbF9saW5lYXIpJHIuc3F1YXJlZCwgNCksICJcbiIpCmNhdCgiUG9seW5vbWlhbCAoZGVncmVlIDIpIFLCsjoiLCByb3VuZChzdW1tYXJ5KG1vZGVsX3BvbHkpJHIuc3F1YXJlZCwgNCksICJcbiIpCgppZihzdW1tYXJ5KG1vZGVsX3BvbHkpJHIuc3F1YXJlZCAtIHN1bW1hcnkobW9kZWxfbGluZWFyKSRyLnNxdWFyZWQgPiAwLjAxKSB7CiAgY2F0KCJcbiBOb24tbGluZWFyIHBhdHRlcm4gZGV0ZWN0ZWQgLSBwb2x5bm9taWFsIGltcHJvdmVzIGZpdCBieSA+MSVcbiIpCn0gZWxzZSB7CiAgY2F0KCJcbiBSZWxhdGlvbnNoaXBzIGFyZSBhcHByb3hpbWF0ZWx5IGxpbmVhclxuIikKfQpgYGAKCiMjIyBOb24tbGluZWFyIFJlbGF0aW9uc2hpcHMgU3VtbWFyeQoKLSAqKk5vbi1saW5lYXIgcGF0dGVybnMgZGV0ZWN0ZWQqKiDigJQgUG9seW5vbWlhbCBtb2RlbCBpbXByb3ZlcyBSwrIgYnkgMS41JSAoMC4yMDAgdG8gMC4yMTUpIGZvciBNbmYgRmxvdyB2cyBwSAotICoqUHJhY3RpY2FsIGltcGxpY2F0aW9uKiog4oCUIFNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbiBtYXkgdW5kZXJmaXQ7IHRyZWUtYmFzZWQgbW9kZWxzIChSYW5kb20gRm9yZXN0LCBYR0Jvb3N0KSBjYXB0dXJlIHRoZXNlIGN1cnZlcyBhdXRvbWF0aWNhbGx5Ci0gKipGaW5hbCByZWNvbW1lbmRhdGlvbioqIOKAlCBVc2UgZW5zZW1ibGUgbWV0aG9kcyB0aGF0IGhhbmRsZSBib3RoIG5vbi1saW5lYXJpdHkgYW5kIG11bHRpY29sbGluZWFyaXR5IHdpdGhvdXQgbWFudWFsIGZlYXR1cmUgc2VsZWN0aW9uCgoKIyBOZXh0IFN0ZXAgTW9kZWxpbmcgCgpMaW5lYXIgUmVncmVzc2lvbiAoYmFzZWxpbmUpClJhbmRvbSBGb3Jlc3QgKGhhbmRsZXMgbm9uLWxpbmVhcml0eSkKWEdCb29zdCAoaGFuZGxlcyBpbnRlcmFjdGlvbnMpCgojIyMgTW9kZWxpbmcgc2VjdGlvbiBpcyBwZXJmb3JtZWQgYnkgU2FjaGkgS2Fwb29yCgotIFBhcnQgMjogbW9kZWwgaW1wbGVtZW50YXRpb24KLSBQYXJ0IDM6IHRlc3RpbmcgdGhlIG1vZGVscyBhbmQgc2hvd2luZyB0aGUgcmVzdWx0cwoKVGhlIHR3byBjbGVhbmVkIGZpbGVzIHVzZWQgaGVyZSBhcmU6CgotIGB0cmFpbmluZ19kYXRhX2NsZWFuZWQuY3N2YAotIGB0ZXN0X2RhdGFfY2xlYW5lZC5jc3ZgCgpGcm9tIHRoZSBlYXJsaWVyIEVEQSwgd2UgYWxyZWFkeSBrbm93IHR3byB0aGluZ3M6CgoxLiBCcmFuZCBtYXR0ZXJzIGFuZCBzaG91bGQgc3RheSBpbiB0aGUgbW9kZWxpbmcgcHJvY2Vzcy4KMi4gcEggaXMgbm90IHN0cm9uZ2x5IHNrZXdlZCwgc28gSSBhbSBub3QgZ29pbmcgdG8gbG9nIHRyYW5zZm9ybSB0aGUgdGFyZ2V0IHVubGVzcyB0aGVyZSBpcyBhIHN0cm9uZyByZWFzb24gdG8gZG8gdGhhdCBsYXRlci4KCgojIyBMb2FkIHRoZSBjbGVhbmVkIGRhdGEKCmBgYHtyIGxvYWQtZGF0YX0KdHJhaW5fY2xlYW4gPC0gcmVhZC5jc3YoInRyYWluaW5nX2RhdGFfY2xlYW5lZC5jc3YiLCBjaGVjay5uYW1lcyA9IEZBTFNFKQp0ZXN0X2NsZWFuICA8LSByZWFkLmNzdigidGVzdF9kYXRhX2NsZWFuZWQuY3N2IiwgY2hlY2submFtZXMgPSBGQUxTRSkKCmNhdCgiVHJhaW5pbmcgcm93czoiLCBucm93KHRyYWluX2NsZWFuKSwgIlxuIikKY2F0KCJUcmFpbmluZyBjb2x1bW5zOiIsIG5jb2wodHJhaW5fY2xlYW4pLCAiXG4iKQpjYXQoIlRlc3Qgcm93czoiLCBucm93KHRlc3RfY2xlYW4pLCAiXG4iKQpjYXQoIlRlc3QgY29sdW1uczoiLCBuY29sKHRlc3RfY2xlYW4pLCAiXG4iKQpgYGAKCiMjIE1ha2UgdGhlIGNvbHVtbiBuYW1lcyBtb2RlbC1mcmllbmRseQoKT25lIGlzc3VlIHdpdGggdGhpcyBkYXRhc2V0IGlzIHRoYXQgc2V2ZXJhbCB2YXJpYWJsZSBuYW1lcyBoYXZlIHNwYWNlcyBpbiB0aGVtLCBsaWtlIGBCcmFuZCBDb2RlYCBhbmQgYE1uZiBGbG93YC4KClRoYXQgaXMgZmluZSBmb3IgYmFzaWMgdmlld2luZywgYnV0IHNvbWUgbW9kZWxpbmcgZnVuY3Rpb25zLCBlc3BlY2lhbGx5IHRoZSB0cmVlIG1vZGVsIHRocm91Z2ggYGNhcmV0YCwgY2FuIGJlIHBpY2t5IGFib3V0IGNvbHVtbiBuYW1lcy4gVG8gYXZvaWQgdGhhdCBwcm9ibGVtLCBJIGFtIHN0YW5kYXJkaXppbmcgdGhlIG5hbWVzIG9uY2UgYXQgdGhlIHN0YXJ0LiBUaGlzIGRvZXMgKipub3QqKiBjaGFuZ2UgdGhlIGRhdGEgaXRzZWxmLiBJdCBvbmx5IG1ha2VzIHRoZSB2YXJpYWJsZSBuYW1lcyBlYXNpZXIgZm9yIFIgdG8gd29yayB3aXRoLgoKYGBge3IgY2xlYW4tY29sdW1uLW5hbWVzfQpvcmlnaW5hbF9uYW1lcyA8LSBuYW1lcyh0cmFpbl9jbGVhbikKc2FmZV9uYW1lcyA8LSBtYWtlLm5hbWVzKG9yaWdpbmFsX25hbWVzLCB1bmlxdWUgPSBUUlVFKQoKbmFtZXModHJhaW5fY2xlYW4pIDwtIHNhZmVfbmFtZXMKbmFtZXModGVzdF9jbGVhbikgIDwtIG1ha2UubmFtZXMobmFtZXModGVzdF9jbGVhbiksIHVuaXF1ZSA9IFRSVUUpCgpuYW1lX2tleSA8LSBkYXRhLmZyYW1lKAogIE9yaWdpbmFsX05hbWUgPSBvcmlnaW5hbF9uYW1lcywKICBNb2RlbGluZ19OYW1lID0gc2FmZV9uYW1lcwopCgprYWJsZShuYW1lX2tleSwgY2FwdGlvbiA9ICJPcmlnaW5hbCB2YXJpYWJsZSBuYW1lcyBhbmQgbW9kZWwtZnJpZW5kbHkgbmFtZXMiKQpgYGAKCiMjIFByZXBhcmUgdGhlIGRhdGEKCmBgYHtyIHByZXBhcmUtZGF0YX0KIyBSZW1vdmUgZHVwbGljYXRlIGJyYW5kIGNvbHVtbiBpZiBib3RoIHZlcnNpb25zIGV4aXN0CmlmICgiQnJhbmRfQ29kZSIgJWluJSBuYW1lcyh0cmFpbl9jbGVhbikpIHsKICB0cmFpbl9jbGVhbiA8LSB0cmFpbl9jbGVhbiAlPiUgc2VsZWN0KC1CcmFuZF9Db2RlKQp9CgppZiAoIkJyYW5kX0NvZGUiICVpbiUgbmFtZXModGVzdF9jbGVhbikpIHsKICB0ZXN0X2NsZWFuIDwtIHRlc3RfY2xlYW4gJT4lIHNlbGVjdCgtQnJhbmRfQ29kZSkKfQoKIyBLZWVwIG9ubHkgcm93cyB3aXRoIGEga25vd24gdGFyZ2V0IGluIHRyYWluaW5nCnRyYWluX2NsZWFuIDwtIHRyYWluX2NsZWFuICU+JSBmaWx0ZXIoIWlzLm5hKFBIKSkKCiMgQ29udmVydCBCcmFuZC5Db2RlIHRvIGZhY3Rvcgp0cmFpbl9jbGVhbiRCcmFuZC5Db2RlIDwtIGFzLmZhY3Rvcih0cmFpbl9jbGVhbiRCcmFuZC5Db2RlKQp0ZXN0X2NsZWFuJEJyYW5kLkNvZGUgIDwtIGFzLmZhY3Rvcih0ZXN0X2NsZWFuJEJyYW5kLkNvZGUpCgojIE1hdGNoIGZhY3RvciBsZXZlbHMgYmV0d2VlbiB0cmFpbmluZyBhbmQgdGVzdCBkYXRhCmFsbF9sZXZlbHMgPC0gdW5pb24obGV2ZWxzKHRyYWluX2NsZWFuJEJyYW5kLkNvZGUpLCBsZXZlbHModGVzdF9jbGVhbiRCcmFuZC5Db2RlKSkKdHJhaW5fY2xlYW4kQnJhbmQuQ29kZSA8LSBmYWN0b3IodHJhaW5fY2xlYW4kQnJhbmQuQ29kZSwgbGV2ZWxzID0gYWxsX2xldmVscykKdGVzdF9jbGVhbiRCcmFuZC5Db2RlICA8LSBmYWN0b3IodGVzdF9jbGVhbiRCcmFuZC5Db2RlLCBsZXZlbHMgPSBhbGxfbGV2ZWxzKQoKIyBTcGxpdCBpbnRvIHRyYWluaW5nIGFuZCB2YWxpZGF0aW9uIHNldHMKc2V0LnNlZWQoNDIpCnRyYWluX2luZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24odHJhaW5fY2xlYW4kUEgsIHAgPSAwLjgwLCBsaXN0ID0gRkFMU0UpCnRyYWluX3NldCA8LSB0cmFpbl9jbGVhblt0cmFpbl9pbmRleCwgXQp2YWxpZF9zZXQgPC0gdHJhaW5fY2xlYW5bLXRyYWluX2luZGV4LCBdCgpjYXQoIk1vZGVsaW5nIHRyYWluIHJvd3M6IiwgbnJvdyh0cmFpbl9zZXQpLCAiXG4iKQpjYXQoIlZhbGlkYXRpb24gcm93czoiLCBucm93KHZhbGlkX3NldCksICJcbiIpCmBgYAoKIyMgQ2hlY2sgbWlzc2luZyB2YWx1ZXMKCmBgYHtyIG1pc3Npbmctc3VtbWFyeX0KbWlzc2luZ190cmFpbiA8LSBzb3J0KGNvbFN1bXMoaXMubmEodHJhaW5fc2V0KSksIGRlY3JlYXNpbmcgPSBUUlVFKQptaXNzaW5nX3RyYWluIDwtIG1pc3NpbmdfdHJhaW5bbWlzc2luZ190cmFpbiA+IDBdCm1pc3NpbmdfdHJhaW4KYGBgCgojIyBIYW5kbGUgbWlzc2luZyB2YWx1ZXMKCkluc3RlYWQgb2YgdXNpbmcgYXV0b21hdGljIGltcHV0YXRpb24gaW5zaWRlIGV2ZXJ5IG1vZGVsIGNhbGwsIEkgYW0gZmlsbGluZyB0aGUgbWlzc2luZyB2YWx1ZXMgZmlyc3QuIFRoaXMgbWFrZXMgdGhlIG1vZGVsaW5nIHN0ZXBzIG1vcmUgc3RhYmxlIGFuZCBlYXNpZXIgdG8gZm9sbG93LgoKRm9yIGBCcmFuZC5Db2RlYCwgSSB1c2UgdGhlIG1vc3QgY29tbW9uIGNhdGVnb3J5IGluIHRoZSB0cmFpbmluZyBzZXQuCgpGb3IgbnVtZXJpYyBwcmVkaWN0b3JzLCBJIHVzZSBtZWRpYW4gaW1wdXRhdGlvbiBiYXNlZCBvbiB0aGUgdHJhaW5pbmcgc2V0LgoKYGBge3IgbWFudWFsLWltcHV0YXRpb259CiMgRmlsbCBtaXNzaW5nIEJyYW5kLkNvZGUgd2l0aCB0aGUgbW9zdCBjb21tb24gdmFsdWUKbW9kZV9icmFuZCA8LSBuYW1lcyhzb3J0KHRhYmxlKHRyYWluX3NldCRCcmFuZC5Db2RlKSwgZGVjcmVhc2luZyA9IFRSVUUpKVsxXQoKdHJhaW5fc2V0JEJyYW5kLkNvZGVbaXMubmEodHJhaW5fc2V0JEJyYW5kLkNvZGUpXSA8LSBtb2RlX2JyYW5kCnZhbGlkX3NldCRCcmFuZC5Db2RlW2lzLm5hKHZhbGlkX3NldCRCcmFuZC5Db2RlKV0gPC0gbW9kZV9icmFuZAp0ZXN0X2NsZWFuJEJyYW5kLkNvZGVbaXMubmEodGVzdF9jbGVhbiRCcmFuZC5Db2RlKV0gPC0gbW9kZV9icmFuZAoKIyBNZWRpYW4gaW1wdXRhdGlvbiBmb3IgbnVtZXJpYyBwcmVkaWN0b3JzCm51bWVyaWNfY29scyA8LSBuYW1lcyh0cmFpbl9zZXQpW3NhcHBseSh0cmFpbl9zZXQsIGlzLm51bWVyaWMpXQpudW1lcmljX3ByZWRpY3RvcnMgPC0gc2V0ZGlmZihudW1lcmljX2NvbHMsICJQSCIpCgpmb3IgKGNvbCBpbiBudW1lcmljX3ByZWRpY3RvcnMpIHsKICBtZWRfdmFsdWUgPC0gbWVkaWFuKHRyYWluX3NldFtbY29sXV0sIG5hLnJtID0gVFJVRSkKICB0cmFpbl9zZXRbW2NvbF1dW2lzLm5hKHRyYWluX3NldFtbY29sXV0pXSA8LSBtZWRfdmFsdWUKICB2YWxpZF9zZXRbW2NvbF1dW2lzLm5hKHZhbGlkX3NldFtbY29sXV0pXSA8LSBtZWRfdmFsdWUKICB0ZXN0X2NsZWFuW1tjb2xdXVtpcy5uYSh0ZXN0X2NsZWFuW1tjb2xdXSldIDwtIG1lZF92YWx1ZQp9CgojIEFsc28gY29tcGxldGUgdGhlIGZ1bGwgdHJhaW5pbmcgc2V0IGZvciB0aGUgZmluYWwgbW9kZWwKdHJhaW5fY2xlYW4kQnJhbmQuQ29kZVtpcy5uYSh0cmFpbl9jbGVhbiRCcmFuZC5Db2RlKV0gPC0gbW9kZV9icmFuZAoKZm9yIChjb2wgaW4gbnVtZXJpY19wcmVkaWN0b3JzKSB7CiAgbWVkX3ZhbHVlIDwtIG1lZGlhbih0cmFpbl9zZXRbW2NvbF1dLCBuYS5ybSA9IFRSVUUpCiAgdHJhaW5fY2xlYW5bW2NvbF1dW2lzLm5hKHRyYWluX2NsZWFuW1tjb2xdXSldIDwtIG1lZF92YWx1ZQp9CgojIFF1aWNrIGNoZWNrCmNvbFN1bXMoaXMubmEodHJhaW5fc2V0KSkKYGBgCgojIyBDaGVjayB0aGUgc2hhcGUgb2YgdGhlIHRhcmdldAoKYGBge3Igc2tld25lc3MtY2hlY2t9CnNrZXdfdmFsdWUgPC0gZTEwNzE6OnNrZXduZXNzKHRyYWluX2NsZWFuJFBIKQpjYXQoIlNrZXduZXNzIG9mIFBIOiIsIHJvdW5kKHNrZXdfdmFsdWUsIDQpLCAiXG4iKQpgYGAKClNpbmNlIHRoZSBza2V3bmVzcyBpcyBjbG9zZSB0byB6ZXJvLCBJIGtlcHQgdGhlIHRhcmdldCBvbiBpdHMgb3JpZ2luYWwgc2NhbGUuCgojIyBNb2RlbGluZyBwbGFuCgpJIGFtIHRlc3RpbmcgZml2ZSBkaWZmZXJlbnQgbW9kZWxzOgoKMS4gTGluZWFyIFJlZ3Jlc3Npb24KMi4gUmlkZ2UgUmVncmVzc2lvbgozLiBEZWNpc2lvbiBUcmVlCjQuIFJhbmRvbSBGb3Jlc3QKNS4gR3JhZGllbnQgQm9vc3RpbmcKClRoaXMgZ2l2ZXMgYSBtaXggb2Ygc2ltcGxlciBtb2RlbHMgYW5kIG1vcmUgZmxleGlibGUgbm9ubGluZWFyIG1vZGVscy4KCmBgYHtyIHRyYWluLWNvbnRyb2x9CmN0cmwgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJjdiIsCiAgbnVtYmVyID0gNQopCmBgYAoKIyMgTW9kZWwgMTogTGluZWFyIFJlZ3Jlc3Npb24KCmBgYHtyIGxpbmVhci1tb2RlbH0Kc2V0LnNlZWQoNDIpCm1vZGVsX2xtIDwtIHRyYWluKAogIFBIIH4gLiwKICBkYXRhID0gdHJhaW5fc2V0LAogIG1ldGhvZCA9ICJsbSIsCiAgdHJDb250cm9sID0gY3RybAopCm1vZGVsX2xtCmBgYAoKIyMgTW9kZWwgMjogUmlkZ2UgUmVncmVzc2lvbgoKYGBge3IgcmlkZ2UtbW9kZWx9CnNldC5zZWVkKDQyKQptb2RlbF9yaWRnZSA8LSB0cmFpbigKICBQSCB+IC4sCiAgZGF0YSA9IHRyYWluX3NldCwKICBtZXRob2QgPSAiZ2xtbmV0IiwKICB0ckNvbnRyb2wgPSBjdHJsLAogIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCAic2NhbGUiKSwKICB0dW5lR3JpZCA9IGV4cGFuZC5ncmlkKAogICAgYWxwaGEgPSAwLAogICAgbGFtYmRhID0gc2VxKDAuMDAwMSwgMSwgbGVuZ3RoID0gMjApCiAgKQopCm1vZGVsX3JpZGdlCmBgYAoKIyMgTW9kZWwgMzogRGVjaXNpb24gVHJlZQoKYGBge3IgdHJlZS1tb2RlbH0Kc2V0LnNlZWQoNDIpCm1vZGVsX3RyZWUgPC0gdHJhaW4oCiAgeCA9IHN1YnNldCh0cmFpbl9zZXQsIHNlbGVjdCA9IC1QSCksCiAgeSA9IHRyYWluX3NldCRQSCwKICBtZXRob2QgPSAicnBhcnQiLAogIHRyQ29udHJvbCA9IGN0cmwsCiAgdHVuZUxlbmd0aCA9IDEwCikKbW9kZWxfdHJlZQpgYGAKCmBgYHtyIHRyZWUtcGxvdCwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTZ9CnJwYXJ0LnBsb3QobW9kZWxfdHJlZSRmaW5hbE1vZGVsLCBtYWluID0gIkRlY2lzaW9uIFRyZWUgZm9yIHBIIFByZWRpY3Rpb24iKQpgYGAKCiMjIE1vZGVsIDQ6IFJhbmRvbSBGb3Jlc3QKCmBgYHtyIHJmLW1vZGVsfQpzZXQuc2VlZCg0MikKbW9kZWxfcmYgPC0gdHJhaW4oCiAgUEggfiAuLAogIGRhdGEgPSB0cmFpbl9zZXQsCiAgbWV0aG9kID0gInJmIiwKICB0ckNvbnRyb2wgPSBjdHJsLAogIHR1bmVMZW5ndGggPSA1LAogIGltcG9ydGFuY2UgPSBUUlVFLAogIG50cmVlID0gMzAwCikKbW9kZWxfcmYKYGBgCgojIyBNb2RlbCA1OiBHcmFkaWVudCBCb29zdGluZwoKYGBge3IgZ2JtLW1vZGVsfQpzZXQuc2VlZCg0MikKbW9kZWxfZ2JtIDwtIHRyYWluKAogIFBIIH4gLiwKICBkYXRhID0gdHJhaW5fc2V0LAogIG1ldGhvZCA9ICJnYm0iLAogIHRyQ29udHJvbCA9IGN0cmwsCiAgdmVyYm9zZSA9IEZBTFNFLAogIHR1bmVMZW5ndGggPSA1CikKbW9kZWxfZ2JtCmBgYAoKIyMgTWFrZSB2YWxpZGF0aW9uIHByZWRpY3Rpb25zCgpgYGB7ciBwcmVkaWN0aW9uc30KcHJlZF9sbSAgICA8LSBwcmVkaWN0KG1vZGVsX2xtLCB2YWxpZF9zZXQpCnByZWRfcmlkZ2UgPC0gcHJlZGljdChtb2RlbF9yaWRnZSwgdmFsaWRfc2V0KQpwcmVkX3RyZWUgIDwtIHByZWRpY3QobW9kZWxfdHJlZSwgc3Vic2V0KHZhbGlkX3NldCwgc2VsZWN0ID0gLVBIKSkKcHJlZF9yZiAgICA8LSBwcmVkaWN0KG1vZGVsX3JmLCB2YWxpZF9zZXQpCnByZWRfZ2JtICAgPC0gcHJlZGljdChtb2RlbF9nYm0sIHZhbGlkX3NldCkKYGBgCgojIyBDb21wYXJlIG1vZGVsIHBlcmZvcm1hbmNlCgpJIHVzZWQgUk1TRSwgTUFFLCBhbmQgUi1zcXVhcmVkIHRvIGNvbXBhcmUgdGhlIG1vZGVscyBvbiB0aGUgdmFsaWRhdGlvbiBzZXQuCgpgYGB7ciByZXN1bHRzLXRhYmxlfQpnZXRfbWV0cmljcyA8LSBmdW5jdGlvbihhY3R1YWwsIHByZWRpY3RlZCwgbW9kZWxfbmFtZSkgewogIGRhdGEuZnJhbWUoCiAgICBNb2RlbCA9IG1vZGVsX25hbWUsCiAgICBSTVNFID0gUk1TRShwcmVkaWN0ZWQsIGFjdHVhbCksCiAgICBNQUUgID0gTUFFKHByZWRpY3RlZCwgYWN0dWFsKSwKICAgIFIyICAgPSBSMihwcmVkaWN0ZWQsIGFjdHVhbCkKICApCn0KCnJlc3VsdHNfdGFibGUgPC0gYmluZF9yb3dzKAogIGdldF9tZXRyaWNzKHZhbGlkX3NldCRQSCwgcHJlZF9sbSwgIkxpbmVhciBSZWdyZXNzaW9uIiksCiAgZ2V0X21ldHJpY3ModmFsaWRfc2V0JFBILCBwcmVkX3JpZGdlLCAiUmlkZ2UgUmVncmVzc2lvbiIpLAogIGdldF9tZXRyaWNzKHZhbGlkX3NldCRQSCwgcHJlZF90cmVlLCAiRGVjaXNpb24gVHJlZSIpLAogIGdldF9tZXRyaWNzKHZhbGlkX3NldCRQSCwgcHJlZF9yZiwgIlJhbmRvbSBGb3Jlc3QiKSwKICBnZXRfbWV0cmljcyh2YWxpZF9zZXQkUEgsIHByZWRfZ2JtLCAiR3JhZGllbnQgQm9vc3RpbmciKQopICU+JQogIGFycmFuZ2UoUk1TRSkKCmthYmxlKHJlc3VsdHNfdGFibGUsIGRpZ2l0cyA9IDQsIGNhcHRpb24gPSAiVmFsaWRhdGlvbiBwZXJmb3JtYW5jZSBjb21wYXJpc29uIikKYGBgCgojIyBCZXN0IG1vZGVsCgpgYGB7ciBiZXN0LW1vZGVsfQpiZXN0X21vZGVsX25hbWUgPC0gcmVzdWx0c190YWJsZSRNb2RlbFsxXQpjYXQoIkJlc3QgbW9kZWwgYmFzZWQgb24gdmFsaWRhdGlvbiBSTVNFOiIsIGJlc3RfbW9kZWxfbmFtZSwgIlxuIikKYGBgCgojIyBWYXJpYWJsZSBpbXBvcnRhbmNlCgpFdmVuIHRob3VnaCBJIHRlc3RlZCBzZXZlcmFsIG1vZGVscywgUmFuZG9tIEZvcmVzdCBpcyBlc3BlY2lhbGx5IHVzZWZ1bCBiZWNhdXNlIGl0IGNhbiBhbHNvIHNob3cgd2hpY2ggcHJlZGljdG9ycyBtYXR0ZXJlZCBtb3N0LgoKYGBge3IgdmFyaWFibGUtaW1wb3J0YW5jZX0KcmZfaW1wb3J0YW5jZSA8LSB2YXJJbXAobW9kZWxfcmYpCnJmX2ltcG9ydGFuY2VfZGYgPC0gcmZfaW1wb3J0YW5jZSRpbXBvcnRhbmNlICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigiRmVhdHVyZSIpICU+JQogIGFycmFuZ2UoZGVzYyhPdmVyYWxsKSkKCmthYmxlKGhlYWQocmZfaW1wb3J0YW5jZV9kZiwgMTIpLCBkaWdpdHMgPSA0LCBjYXB0aW9uID0gIlRvcCAxMiBpbXBvcnRhbnQgcHJlZGljdG9ycyBmcm9tIFJhbmRvbSBGb3Jlc3QiKQpgYGAKCmBgYHtyIGltcG9ydGFuY2UtcGxvdCwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTd9CnBsb3QodmFySW1wKG1vZGVsX3JmKSwgdG9wID0gMTIsIG1haW4gPSAiUmFuZG9tIEZvcmVzdCBWYXJpYWJsZSBJbXBvcnRhbmNlIikKYGBgCgojIyMgUmVmaXQgdGhlIGZpbmFsIG1vZGVsIG9uIGFsbCB0cmFpbmluZyBkYXRhCgpBZnRlciBjb21wYXJpbmcgdGhlIG1vZGVscywgSSByZWZpdCB0aGUgZmluYWwgbW9kZWwgdXNpbmcgYWxsIGF2YWlsYWJsZSB0cmFpbmluZyByb3dzIHdpdGgga25vd24gcEguCgpgYGB7ciBmaW5hbC1tb2RlbH0Kc2V0LnNlZWQoNDIpCmZpbmFsX21vZGVsIDwtIHRyYWluKAogIFBIIH4gLiwKICBkYXRhID0gdHJhaW5fY2xlYW4sCiAgbWV0aG9kID0gInJmIiwKICB0ckNvbnRyb2wgPSBjdHJsLAogIHR1bmVHcmlkID0gbW9kZWxfcmYkYmVzdFR1bmUsCiAgaW1wb3J0YW5jZSA9IFRSVUUsCiAgbnRyZWUgPSAzMDAKKQpmaW5hbF9tb2RlbApgYGAKCiMjIyBQcmVkaWN0IHBIIGZvciB0aGUgZXZhbHVhdGlvbiBkYXRhCgpgYGB7ciBmaW5hbC1wcmVkaWN0aW9uc30KZmluYWxfcHJlZGljdGlvbnMgPC0gcHJlZGljdChmaW5hbF9tb2RlbCwgdGVzdF9jbGVhbikKCnByZWRpY3Rpb25fb3V0cHV0IDwtIHRlc3RfY2xlYW4gJT4lCiAgbXV0YXRlKAogICAgUHJlZGljdGVkX1BIX0ZpbmFsX01vZGVsID0gcm91bmQoZmluYWxfcHJlZGljdGlvbnMsIDQpLAogICAgRmluYWxfTW9kZWwgPSAiUmFuZG9tIEZvcmVzdCIsCiAgICBQcmVkaWN0aW9uX05vdGUgPSAiUEggd2FzIGJsYW5rIGluIGV2YWx1YXRpb24gZmlsZSwgc28gdGhlIGZpbmFsIG1vZGVsIHByZWRpY3Rpb24gaXMgcHJvdmlkZWQgaGVyZS4iCiAgKQoKc3VtbWFyeShwcmVkaWN0aW9uX291dHB1dCRQcmVkaWN0ZWRfUEhfRmluYWxfTW9kZWwpCmBgYAoKIyMjIyBTYXZlIHRoZSBvdXRwdXQgZmlsZXMKCmBgYHtyIHNhdmUtb3V0cHV0fQp3cml0ZS5jc3YocmVzdWx0c190YWJsZSwgIm1vZGVsX3ZhbGlkYXRpb25fcmVzdWx0cy5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkKd3JpdGUuY3N2KHJmX2ltcG9ydGFuY2VfZGYsICJyYW5kb21fZm9yZXN0X3ZhcmlhYmxlX2ltcG9ydGFuY2UuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpCndyaXRlLmNzdihwcmVkaWN0aW9uX291dHB1dCwgIkFCQ19CZXZlcmFnZV9QSF9QcmVkaWN0aW9ucy5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkKYGBgCgojIyMgRmluYWwgdGhvdWdodHMKCmBgYHtyIGludGVycHJldGF0aW9uLCByZXN1bHRzPSdhc2lzJ30KY2F0KCJCYXNlZCBvbiB0aGUgdmFsaWRhdGlvbiByZXN1bHRzLCBJIHNlbGVjdGVkIHRoZSBtb2RlbCB3aXRoIHRoZSBsb3dlc3QgUk1TRSBhcyB0aGUgZmluYWwgbW9kZWwuICIpCmNhdCgiSW4gdGhpcyB0eXBlIG9mIG1hbnVmYWN0dXJpbmcgZGF0YSwgdHJlZS1iYXNlZCBtb2RlbHMgYXJlIG9mdGVuIGhlbHBmdWwgYmVjYXVzZSB0aGV5IGNhbiBjYXB0dXJlIG5vbmxpbmVhciBwYXR0ZXJucyBhbmQgaW50ZXJhY3Rpb25zIHRoYXQgYSBiYXNpYyBsaW5lYXIgbW9kZWwgbWF5IG1pc3MuICIpCmNhdCgiVGhhdCBpcyBlc3BlY2lhbGx5IHJlbGV2YW50IGhlcmUgYmVjYXVzZSBlYXJsaWVyIGFuYWx5c2lzIGFscmVhZHkgc3VnZ2VzdGVkIHRoYXQgQnJhbmQgQ29kZSBjaGFuZ2VzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBNbmYgRmxvdyBhbmQgcEguXG5cbiIpCgpjYXQoIlRoZSBmaW5hbCBwcmVkaWN0aW9uIGZpbGUgY3JlYXRlZCBieSB0aGlzIHJlcG9ydCBpcyAqKkFCQ19CZXZlcmFnZV9QSF9QcmVkaWN0aW9ucy5jc3YqKi4gIikKY2F0KCJUaGlzIGZpbGUgY2FuIGJlIHR1cm5lZCBpbiBhcyB0aGUgbW9kZWwgcHJlZGljdGlvbiBvdXRwdXQgZm9yIHRoZSBldmFsdWF0aW9uIGRhdGFzZXQuXG4iKQpgYGAKCiMjIyMgV3JpdGUgYWxsIHByZWRpY3Rpb25zIG9uIGV4Y2VsIGZpbGUgCmBgYHtyfQojIFdyaXRlIGFsbCBwcmVkaWN0aW9ucyBvbiBleGNlbCBmaWxlICAKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBTdGVwIDE6IFJlYWQgYWxsIHlvdXIgYWN0dWFsIGRhdGEgZmlsZXMKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiMgUmVhZCB0aGUgcHJlZGljdGlvbnMgQ1NWICh0ZXN0IGRhdGEgd2l0aCBwcmVkaWN0aW9ucykKcHJlZGljdGlvbnNfcmF3IDwtIHJlYWRfY3N2KCJBQkNfQmV2ZXJhZ2VfUEhfUHJlZGljdGlvbnMuY3N2IikKCiMgUmVhZCB0aGUgbW9kZWwgY29tcGFyaXNvbiByZXN1bHRzCm1vZGVsX3Jlc3VsdHMgPC0gcmVzdWx0c190YWJsZSAjIFlvdXIgNS1tb2RlbCBjb21wYXJpc29uCgojIFJlYWQgdGhlIHJhbmRvbSBmb3Jlc3QgdmFyaWFibGUgaW1wb3J0YW5jZQpyZl9pbXBvcnRhbmNlIDwtIHJlYWRfY3N2KCJyYW5kb21fZm9yZXN0X3ZhcmlhYmxlX2ltcG9ydGFuY2UuY3N2IikgICMgWW91ciAzNC1mZWF0dXJlIGltcG9ydGFuY2UKCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgRXh0cmFjdCBhY3R1YWwgbWV0cmljcwojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKcmZfbWV0cmljcyA8LSByZXN1bHRzX3RhYmxlICU+JSBmaWx0ZXIoTW9kZWwgPT0gIlJhbmRvbSBGb3Jlc3QiKQpSTVNFX2FjdHVhbCA8LSByZl9tZXRyaWNzJFJNU0UKUjJfYWN0dWFsIDwtIHJmX21ldHJpY3MkUjIKCiMgOTAlIHByZWRpY3Rpb24gaW50ZXJ2YWwgKHogPSAxLjY0NSkKUElfd2lkdGggPC0gcm91bmQoMS42NDUgKiBSTVNFX2FjdHVhbCwgNCkKCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgU2hlZXQgMTogUHJlZGljdGlvbnMgKG1haW4gb3V0cHV0KQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKcHJlZGljdGlvbnNfZGYgPC0gcHJlZGljdGlvbnNfcmF3ICU+JQogIG11dGF0ZSgKICAgIFJvd0lEID0gcm93X251bWJlcigpLAogICAgUEhfUHJlZGljdGVkID0gUHJlZGljdGVkX1BIX0ZpbmFsX01vZGVsLAogICAgUEhfTG93ZXJfOTBDSSA9IFBIX1ByZWRpY3RlZCAtICgxLjY0NSAqIFJNU0VfYWN0dWFsKSwKICAgIFBIX1VwcGVyXzkwQ0kgPSBQSF9QcmVkaWN0ZWQgKyAoMS42NDUgKiBSTVNFX2FjdHVhbCksCiAgICBBbGVydF9GbGFnID0gY2FzZV93aGVuKAogICAgICBQSF9QcmVkaWN0ZWQgPCA4LjIgfiAiTE9XIC0gUmV2aWV3IiwKICAgICAgUEhfUHJlZGljdGVkID4gOC45IH4gIkhJR0ggLSBSZXZpZXciLAogICAgICBUUlVFIH4gIk9LIgogICAgKQogICkgJT4lCiAgc2VsZWN0KFJvd0lELCBCcmFuZF9Db2RlID0gYEJyYW5kLkNvZGVgLCBNbmYuRmxvdywgQm93bC5TZXRwb2ludCwgCiAgICAgICAgIEZpbGxlci5MZXZlbCwgUEhfUHJlZGljdGVkLCBQSF9Mb3dlcl85MENJLCBQSF9VcHBlcl85MENJLCAKICAgICAgICAgQWxlcnRfRmxhZywgRmluYWxfTW9kZWwpCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIFNoZWV0IDI6IE1vZGVsIE1ldGFkYXRhCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgptZXRhZGF0YV9kZiA8LSBkYXRhLmZyYW1lKAogIE1ldHJpYyA9IGMoIk1vZGVsIiwgIlJNU0UiLCAiUl9TcXVhcmVkIiwgIlByZWRpY3Rpb25fSW50ZXJ2YWxfOTAlIiwgIlRlc3RfUm93cyIpLAogIFZhbHVlID0gYygiUmFuZG9tIEZvcmVzdCIsIHJvdW5kKFJNU0VfYWN0dWFsLCA0KSwgcm91bmQoUjJfYWN0dWFsLCA0KSwgCiAgICAgICAgICAgIHBhc3RlMCgiwrEiLCBQSV93aWR0aCksIG5yb3cocHJlZGljdGlvbnNfcmF3KSkKKQoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBTaGVldCAzOiBWYXJpYWJsZSBJbXBvcnRhbmNlIChmcm9tIHlvdXIgcmZfaW1wb3J0YW5jZV9kZikKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCmltcG9ydGFuY2VfZGYgPC0gcmZfaW1wb3J0YW5jZV9kZiAlPiUKICBtdXRhdGUoUmFuayA9IHJvd19udW1iZXIoKSwgSW1wb3J0YW5jZSA9IHJvdW5kKE92ZXJhbGwsIDIpKSAlPiUKICBzZWxlY3QoUmFuaywgVmFyaWFibGUgPSBGZWF0dXJlLCBJbXBvcnRhbmNlKQoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBTaGVldCA0OiBNb2RlbCBDb21wYXJpc29uCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Cgpjb21wYXJpc29uX2RmIDwtIHJlc3VsdHNfdGFibGUgJT4lCiAgYXJyYW5nZShSTVNFKSAlPiUKICBtdXRhdGUoYWNyb3NzKGMoUk1TRSwgTUFFLCBSMiksIH4gcm91bmQoLiwgNCkpKSAlPiUKICBzZWxlY3QoTW9kZWwsIFJNU0UsIE1BRSwgUjIpCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIFNoZWV0IDU6IEJyYW5kIFN1bW1hcnkKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCmJyYW5kX2RmIDwtIHByZWRpY3Rpb25zX2RmICU+JQogIGdyb3VwX2J5KEJyYW5kX0NvZGUpICU+JQogIHN1bW1hcmlzZSgKICAgIENvdW50ID0gbigpLAogICAgQXZnX3BIID0gcm91bmQobWVhbihQSF9QcmVkaWN0ZWQpLCAzKSwKICAgIE1pbl9wSCA9IHJvdW5kKG1pbihQSF9QcmVkaWN0ZWQpLCAzKSwKICAgIE1heF9wSCA9IHJvdW5kKG1heChQSF9QcmVkaWN0ZWQpLCAzKSwKICAgIC5ncm91cHMgPSAiZHJvcCIKICApCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIFNoZWV0IDY6IEV4ZWN1dGl2ZSBTdW1tYXJ5IChzaW1wbGUpCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpzdW1tYXJ5X2RmIDwtIGRhdGEuZnJhbWUoCiAgS2V5X0ZpbmRpbmcgPSBjKCJUb3RhbCBQcmVkaWN0aW9ucyIsICJNb2RlbCBSTVNFIiwgIk1vZGVsIFLCsiIsICJUb3AgUHJlZGljdG9yIiwgCiAgICAgICAgICAgICAgICAgICJMb3dlc3QgcEggQnJhbmQiLCAiUnVucyBOZWVkaW5nIFJldmlldyIpLAogIFZhbHVlID0gYyhucm93KHByZWRpY3Rpb25zX2RmKSwgcm91bmQoUk1TRV9hY3R1YWwsIDQpLCByb3VuZChSMl9hY3R1YWwsIDQpLAogICAgICAgICAgICByZl9pbXBvcnRhbmNlX2RmJEZlYXR1cmVbMV0sCiAgICAgICAgICAgIGJyYW5kX2RmJEJyYW5kX0NvZGVbd2hpY2gubWluKGJyYW5kX2RmJEF2Z19wSCldLAogICAgICAgICAgICBzdW0ocHJlZGljdGlvbnNfZGYkQWxlcnRfRmxhZyAhPSAiT0siKSkKKQoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBXcml0ZSB0byBFeGNlbAojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKc2hlZXRzX2xpc3QgPC0gbGlzdCgKICAiUHJlZGljdGlvbnMiID0gcHJlZGljdGlvbnNfZGYsCiAgIk1vZGVsX01ldGFkYXRhIiA9IG1ldGFkYXRhX2RmLAogICJWYXJpYWJsZV9JbXBvcnRhbmNlIiA9IGltcG9ydGFuY2VfZGYsCiAgIk1vZGVsX0NvbXBhcmlzb24iID0gY29tcGFyaXNvbl9kZiwKICAiQnJhbmRfU3VtbWFyeSIgPSBicmFuZF9kZiwKICAiRXhlY3V0aXZlX1N1bW1hcnkiID0gc3VtbWFyeV9kZgopCgp3cml0ZV94bHN4KHNoZWV0c19saXN0LCAiQUJDX0JldmVyYWdlX3BIX1ByZWRpY3Rpb25zLnhsc3giKQoKYGBgCg==