library(tidyverse)
library(openintro)

Instructions

  • Complete the coded in the code chunks.
  • Type the answers as text below each question.
  • Knit your file.
  • Upload resulting HTML file to the Canvas

Dataset

The data is related with direct marketing campaigns (phone calls) of a Portuguese banking institution. The classification goal is to predict if the client will subscribe a term deposit (variable y).

Input variables:

# bank client data:

1 - age (numeric)
2 - job: type of job (categorical: “admin.”,“unknown”,“unemployed”,“management”,“housemaid”,“entrepreneur”,“student”, “blue-collar”,“self-employed”,“retired”,“technician”,“services”) 3 - marital: marital status (categorical: “married”,“divorced”,“single”; note: “divorced” means divorced or widowed)
4 - education (categorical: 1=“primary”,2=“secondary”,2=“tertiary”,9=“unknown”,)
5 - default: has credit in default? (binary: “yes”,“no”)
6 - balance: average yearly balance, in euros (numeric)
7 - housing: has housing loan? (binary: “yes”,“no”)
8 - loan: has personal loan? (binary: “yes”,“no”)

# related with the last contact of the current campaign:

9 - contact: contact communication type (categorical: “unknown”,“telephone”,“cellular”) 10 - day: last contact day of the month (numeric)
11 - month: last contact month of year (categorical: “jan”, “feb”, “mar”, …, “nov”, “dec”)
12 - duration: last contact duration, in seconds (numeric)

# other attributes:

13 - campaign: number of contacts performed during this campaign and for this client (numeric, includes last contact)
14 - pdays: number of days that passed by after the client was last contacted from a previous campaign (numeric, -1 means client was not previously contacted)
15 - previous: number of contacts performed before this campaign and for this client (numeric)
16 - poutcome: outcome of the previous marketing campaign (categorical: “unknown”,“other”,“failure”,“success”)

Output variable (desired target):
17 - y - has the client subscribed a term deposit? (binary: “yes”,“no”)

Load and Inspect Data

# Load required libraries
library(neuralnet)
## 
## Attaching package: 'neuralnet'
## The following object is masked from 'package:dplyr':
## 
##     compute
library(nnet) # for dummy variable coding
library(gmodels) # for confusion matrix

# Load the dataset
bank.df <- read.csv("bank.csv", na.strings = "", sep = ";")

# Display the first few rows and structure
head(bank.df)
##   age         job marital education default balance housing loan  contact day
## 1  30  unemployed married   primary      no    1787      no   no cellular  19
## 2  33    services married secondary      no    4789     yes  yes cellular  11
## 3  35  management  single  tertiary      no    1350     yes   no cellular  16
## 4  30  management married  tertiary      no    1476     yes  yes  unknown   3
## 5  59 blue-collar married secondary      no       0     yes   no  unknown   5
## 6  35  management  single  tertiary      no     747      no   no cellular  23
##   month duration campaign pdays previous poutcome  y
## 1   oct       79        1    -1        0  unknown no
## 2   may      220        1   339        4  failure no
## 3   apr      185        1   330        1  failure no
## 4   jun      199        4    -1        0  unknown no
## 5   may      226        1    -1        0  unknown no
## 6   feb      141        2   176        3  failure no
str(bank.df)
## 'data.frame':    4521 obs. of  17 variables:
##  $ age      : int  30 33 35 30 59 35 36 39 41 43 ...
##  $ job      : chr  "unemployed" "services" "management" "management" ...
##  $ marital  : chr  "married" "married" "single" "married" ...
##  $ education: chr  "primary" "secondary" "tertiary" "tertiary" ...
##  $ default  : chr  "no" "no" "no" "no" ...
##  $ balance  : int  1787 4789 1350 1476 0 747 307 147 221 -88 ...
##  $ housing  : chr  "no" "yes" "yes" "yes" ...
##  $ loan     : chr  "no" "yes" "no" "yes" ...
##  $ contact  : chr  "cellular" "cellular" "cellular" "unknown" ...
##  $ day      : int  19 11 16 3 5 23 14 6 14 17 ...
##  $ month    : chr  "oct" "may" "apr" "jun" ...
##  $ duration : int  79 220 185 199 226 141 341 151 57 313 ...
##  $ campaign : int  1 1 1 4 1 2 1 2 2 1 ...
##  $ pdays    : int  -1 339 330 -1 -1 176 330 -1 -1 147 ...
##  $ previous : int  0 4 1 0 0 3 2 0 0 2 ...
##  $ poutcome : chr  "unknown" "failure" "failure" "unknown" ...
##  $ y        : chr  "no" "no" "no" "no" ...

**Complete the code below by filling spaces (“___“).**

Question 1: Scale Numeric Variables

Instructions: - Normalize age, balance, campaign, and previous to the range [0, 1]. - Use the formula: (x - min(x)) / (max(x) - min(x)). - Report the first 6 scaled records.

# Define the normalization function
normalize <- function(x) {
  return((x - min(x)) / (max(x) - min(x)))
}

# Normalize numeric variables to [0,1]
bank.df$age <- normalize(bank.df$age)
bank.df$balance <- normalize(bank.df$balance)
bank.df$campaign <- normalize(bank.df$campaign)
bank.df$duration <- normalize(bank.df$duration)

# Display first 6 records
head(bank.df, n = 6)
##         age         job marital education default    balance housing loan
## 1 0.1617647  unemployed married   primary      no 0.06845546      no   no
## 2 0.2058824    services married secondary      no 0.10875022     yes  yes
## 3 0.2352941  management  single  tertiary      no 0.06258976     yes   no
## 4 0.1617647  management married  tertiary      no 0.06428102     yes  yes
## 5 0.5882353 blue-collar married secondary      no 0.04446920     yes   no
## 6 0.2352941  management  single  tertiary      no 0.05449591      no   no
##    contact day month   duration   campaign pdays previous poutcome  y
## 1 cellular  19   oct 0.02482622 0.00000000    -1        0  unknown no
## 2 cellular  11   may 0.07149950 0.00000000   339        4  failure no
## 3 cellular  16   apr 0.05991394 0.00000000   330        1  failure no
## 4  unknown   3   jun 0.06454816 0.06122449    -1        0  unknown no
## 5  unknown   5   may 0.07348560 0.00000000    -1        0  unknown no
## 6 cellular  23   feb 0.04534922 0.02040816   176        3  failure no

Question 2: Create Dummy Variables

Instructions: - Create dummy variables for education and y using class.ind from the nnet package. - Combine them with the original dataframe to create bank.df1. - Rename the new columns appropriately.

# Create dummy variables
bank.df1 <- cbind(bank.df, class.ind(bank.df$education), class.ind(bank.df$y))

# Rename columns
names(bank.df1)[18:23] <- c(
  paste("edu_", c(1, 2, 3, 9), sep = ""),
  paste("term_d_", c("yes", "no"), sep = "")
)

# Display column names
names(bank.df1)
##  [1] "age"        "job"        "marital"    "education"  "default"   
##  [6] "balance"    "housing"    "loan"       "contact"    "day"       
## [11] "month"      "duration"   "campaign"   "pdays"      "previous"  
## [16] "poutcome"   "y"          "edu_1"      "edu_2"      "edu_3"     
## [21] "edu_9"      "term_d_yes" "term_d_no"

Question 3: Select Attributes

Instructions: - Select the following attributes: age, balance, campaign, previous, edu_1, edu_2, edu_3, edu_9, term_d_yes, term_d_no. - Create a new dataframe bank.df2 with only these attributes.

# Select specific attributes
vars <- c("age", "balance", "campaign", "previous", "edu_1", "edu_2", "edu_3", "edu_9", "term_d_yes", "term_d_no")
bank.df2 <- bank.df1[, vars]

# Display column names
names(bank.df2)
##  [1] "age"        "balance"    "campaign"   "previous"   "edu_1"     
##  [6] "edu_2"      "edu_3"      "edu_9"      "term_d_yes" "term_d_no"

Question 4: Partition Data

Instructions: - Partition the data into 60% training and 40% test sets. - Use a random seed to ensure reproducibility.

# Partition the data
set.seed(2)
train.index <- sample(rownames(bank.df2), dim(bank.df2)[1] * 0.6)
test.index <- setdiff(rownames(bank.df2), train.index)
train.df <- bank.df2[train.index, ]
test.df <- bank.df2[test.index, ]

# Display dimensions of training and test sets
dim(train.df)
## [1] 2712   10
dim(test.df)
## [1] 1809   10

Question 5: Fit Neural Network

Instructions: - Fit a neural network with hidden layers (3, 2). - Plot the resulting network.

# Fit neural network
nn <- neuralnet(term_d_yes + term_d_no ~ ., data = train.df, hidden = c(3, 2))

# Plot the neural network
plot(nn, rep = "best")

Question 6: Evaluate Model Performance

Instructions: - Predict classes for training and test datasets. - Use CrossTable from the gmodels package to create a confusion matrix. - Calculate the accuracy for both training and test predictions using mean.

# (a) Predictions on training set
training.prediction <- compute(nn, train.df[, -c(9:10)])
training.class <- apply(training.prediction$net.result, 1, which.max) - 1

# Confusion matrix and accuracy for training set
CrossTable(factor(training.class), factor(train.df$term_d_no), 
           prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  2712 
## 
##  
##                        | factor(train.df$term_d_no) 
## factor(training.class) |         0 |         1 | Row Total | 
## -----------------------|-----------|-----------|-----------|
##                      0 |      2367 |       311 |      2678 | 
##                        |     0.873 |     0.115 |           | 
## -----------------------|-----------|-----------|-----------|
##                      1 |        12 |        22 |        34 | 
##                        |     0.004 |     0.008 |           | 
## -----------------------|-----------|-----------|-----------|
##           Column Total |      2379 |       333 |      2712 | 
## -----------------------|-----------|-----------|-----------|
## 
## 
training.accuracy <- mean(training.class == train.df$term_d_no)
training.accuracy
## [1] 0.8808997
# (b) Predictions on test set
test.prediction <- compute(nn, test.df[, -c(9:10)])
test.class <- apply(test.prediction$net.result, 1, which.max) - 1

# Confusion matrix and accuracy for test set
CrossTable(factor(test.class), factor(test.df$term_d_no),
           prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE)
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  1809 
## 
##  
##                    | factor(test.df$term_d_no) 
## factor(test.class) |         0 |         1 | Row Total | 
## -------------------|-----------|-----------|-----------|
##                  0 |      1600 |       182 |      1782 | 
##                    |     0.884 |     0.101 |           | 
## -------------------|-----------|-----------|-----------|
##                  1 |        21 |         6 |        27 | 
##                    |     0.012 |     0.003 |           | 
## -------------------|-----------|-----------|-----------|
##       Column Total |      1621 |       188 |      1809 | 
## -------------------|-----------|-----------|-----------|
## 
## 
test.accuracy <- mean(test.class == train.df$term_d_no)
## Warning in test.class == train.df$term_d_no: longer object length is not a
## multiple of shorter object length
test.accuracy
## [1] 0.8646755

Answer the following questions:

  1. How does the ANN model perform in predicting positive (“yes”) and negative (“no”) outcomes?

Answer: The ANN performs pretty well predicting both the positives and negatives due to a high accuracy on both the training and testing df.

  1. What are your thoughts on the performance of the ANN model based on the accuracy estimates for the training and test datasets?

Answer: There is a 3 percent decrease in accruacy, however there also is a decrease in the length of df. This means that although the score wasn’t as high in the testing data set as it was for the training set, it was still above average and was able to accurately predict most of the time.

Question 7: Logistic Regression Model

Instructions: - Fit a logistic regression model to classify the outcome term_d_no. - Use the same predictors and the same training and test datasets, except edu_9 and term_d_yes - Display the coefficients using coef. - Add a summary of the model.

# need to drop columns 8 and 9 (edu_9 and term_d_yes)
train.df.log <- train.df[, -c(8,9)]
test.df.log <- test.df[, -c(8,9)]

# Fit logistic regression model
logistic.model <- glm(term_d_no ~ ., data = train.df.log, family = "binomial")

# Display coefficients
coef(logistic.model)
##  (Intercept)          age      balance     campaign     previous        edu_1 
## -2.433914133  1.211885563  1.023242015 -3.716195024  0.119418065 -0.367928506 
##        edu_2        edu_3 
##  0.003841825  0.261760907
# Add a summary of the model
summary(logistic.model)
## 
## Call:
## glm(formula = term_d_no ~ ., family = "binomial", data = train.df.log)
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept) -2.433914   0.340480  -7.148 8.77e-13 ***
## age          1.211886   0.376046   3.223  0.00127 ** 
## balance      1.023242   1.363276   0.751  0.45291    
## campaign    -3.716195   1.346734  -2.759  0.00579 ** 
## previous     0.119418   0.028596   4.176 2.97e-05 ***
## edu_1       -0.367929   0.336362  -1.094  0.27402    
## edu_2        0.003842   0.302848   0.013  0.98988    
## edu_3        0.261761   0.307831   0.850  0.39514    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 2020.1  on 2711  degrees of freedom
## Residual deviance: 1973.2  on 2704  degrees of freedom
## AIC: 1989.2
## 
## Number of Fisher Scoring iterations: 5

Question 8: Predict and Evaluate Logistic Model

Instructions: - Predict the outcomes for the test set using the logistic regression model. - Use the pROC library to plot the ROC curve. - Display the AUC value. - Create a confusion matrix for the predictions with a threshold of 0.50, assuming “yes” for predicted probabilities greater than 0.50.

# Load pROC library
library(pROC)
## Warning: package 'pROC' was built under R version 4.3.3
## Type 'citation("pROC")' for a citation.
## 
## Attaching package: 'pROC'
## The following object is masked from 'package:gmodels':
## 
##     ci
## The following objects are masked from 'package:stats':
## 
##     cov, smooth, var
# Predict probabilities for test set
test.prob <- predict(logistic.model, test.df.log, type = "response")

# Plot ROC curve
roc_curve <- roc(test.df.log$term_d_no, test.prob)
## Setting levels: control = 0, case = 1
## Setting direction: controls < cases
plot(roc_curve, main = "ROC Curve for Logistic Regression")

# Display AUC value
auc(roc_curve)
## Area under the curve: 0.6534
# Create confusion matrix with threshold 0.50
test.pred.class <- ifelse(test.prob > 0.50, 1, 0)
CrossTable(factor(test.pred.class), factor(test.df.log$term_d_no), 
           prop.chisq = FALSE, prop.c = FALSE, prop.r = FALSE, 
           dnn = c("Predicted", "Actual"))
## 
##  
##    Cell Contents
## |-------------------------|
## |                       N |
## |         N / Table Total |
## |-------------------------|
## 
##  
## Total Observations in Table:  1809 
## 
##  
##              | Actual 
##    Predicted |         0 |         1 | Row Total | 
## -------------|-----------|-----------|-----------|
##            0 |      1617 |       188 |      1805 | 
##              |     0.894 |     0.104 |           | 
## -------------|-----------|-----------|-----------|
##            1 |         4 |         0 |         4 | 
##              |     0.002 |     0.000 |           | 
## -------------|-----------|-----------|-----------|
## Column Total |      1621 |       188 |      1809 | 
## -------------|-----------|-----------|-----------|
## 
## 
test.accuracy.log <- mean(test.pred.class == test.df.log$term_d_no)
test.accuracy.log
## [1] 0.893864

Answer the following questions:

  1. How does the logistic model perform in predicting positive the (“no”) outcomes?

Answer: The logistic model performs well in predicting “no” outcomes, because of a relatively high accuracy on the test set. The confusion matrix shows that the model predicts “no” with good accuracy. The ROC curve and AUC further support the model’s effectiveness in distinguishing between “yes” and “no” outcomes. However, there is always room for improvement through model adjustments and threshold tuning.

  1. Which predictors are significant? Why?

Answer:

The significant predictors are determined by their p-values in the summary. Variables with low p-values which are usually less than 0.05 are considered significant. Previous contacts shows the strongest associations with the likelihood of not subscribing.

  1. How would you interpret the coefficient of edu_3=tertiary?

Answer:

The coefficient of edu_3=tertiary represents the log-odds of a client subscribing to a term deposit with relation to the reference category. A positive coefficient means that tertiary education increases the likelihood of a “no” outcome. This relates back to the use of two variables not necessarily increasing the price of homes as found in our previous labs. The coefficient is the ultimate factor of the impact of two variables creating an effect.

  1. How would you explain properties of ROC and AUC estimates you have obtained?

Answer:

The ROC curve tells us that there is trade-off between true positive rate and false positive rate across different thresholds. The AUC value quantifies this, with a higher AUC which would indicate a better model performance. An AUC of 0.8 or higher is considered strong, therefore meaning that the model is good at distinguishing between yes and no. In this case, the AUC suggests that the logistic model has good performance.

  1. Do you see any problems using the accuracy estimate for the logistic model based on the probabilty threshold of 0.50?

Answer: When using a 0.50 threshold for accuracy in this case it worked out well, however if the dataset is imbalanced it can also backfire. For example, if “no” outcomes dominate, the model might predict “no” most of the time, resulting in high accuracy but poor model performance in predicting “yes” outcomes. A change in threshold is something that must be determined after understanding the data.

LS0tDQp0aXRsZTogIkJBTkwgMzIwMDogU3VwZXJ2aXNlcyBNYWNoaW5lIExlYXJuaW5nIg0Kc3VidGl0bGU6ICJGaW5hbCBFeGFtOiBTdWdnZXN0ZWQgU29sdXRpb24iDQphdXRob3I6ICJKYWRlbiBTYW1wc29uIg0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0OiBvcGVuaW50cm86OmxhYl9yZXBvcnQNCi0tLQ0KDQpgYGB7ciwgc2V0dXAsIGluY2x1ZGU9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoDQogIGV2YWwgPSBUUlVFDQopDQpgYGANCg0KYGBge3IgbG9hZC1wYWNrYWdlcywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShvcGVuaW50cm8pDQpgYGANCg0KIyMjIEluc3RydWN0aW9ucw0KDQotIENvbXBsZXRlIHRoZSBjb2RlZCBpbiB0aGUgY29kZSBjaHVua3MuDQotIFR5cGUgdGhlIGFuc3dlcnMgYXMgdGV4dCBiZWxvdyBlYWNoIHF1ZXN0aW9uLg0KLSBLbml0IHlvdXIgZmlsZS4NCi0gVXBsb2FkIHJlc3VsdGluZyBIVE1MIGZpbGUgdG8gdGhlIENhbnZhcw0KDQoNCiMjIyBEYXRhc2V0DQoNClRoZSBkYXRhIGlzIHJlbGF0ZWQgd2l0aCBkaXJlY3QgbWFya2V0aW5nIGNhbXBhaWducyAocGhvbmUgY2FsbHMpIG9mIGEgUG9ydHVndWVzZSBiYW5raW5nIGluc3RpdHV0aW9uLiBUaGUgY2xhc3NpZmljYXRpb24gZ29hbCBpcyB0byBwcmVkaWN0IGlmIHRoZSBjbGllbnQgd2lsbCBzdWJzY3JpYmUgYSB0ZXJtIGRlcG9zaXQgKHZhcmlhYmxlIGB5YCkuDQoNCioqSW5wdXQgdmFyaWFibGVzOioqDQoNCioqYCNgIGJhbmsgY2xpZW50IGRhdGE6KioNCg0KICAgMSAtICoqYWdlKiogKG51bWVyaWMpICAgDQogICAyIC0gKipqb2IqKjogdHlwZSBvZiBqb2IgKGNhdGVnb3JpY2FsOiAiYWRtaW4uIiwidW5rbm93biIsInVuZW1wbG95ZWQiLCJtYW5hZ2VtZW50IiwiaG91c2VtYWlkIiwiZW50cmVwcmVuZXVyIiwic3R1ZGVudCIsICJibHVlLWNvbGxhciIsInNlbGYtZW1wbG95ZWQiLCJyZXRpcmVkIiwidGVjaG5pY2lhbiIsInNlcnZpY2VzIikgDQogICAzIC0gKiptYXJpdGFsKio6IG1hcml0YWwgc3RhdHVzIChjYXRlZ29yaWNhbDogIm1hcnJpZWQiLCJkaXZvcmNlZCIsInNpbmdsZSI7IG5vdGU6ICJkaXZvcmNlZCIgbWVhbnMgZGl2b3JjZWQgb3Igd2lkb3dlZCkgIA0KICAgNCAtICoqZWR1Y2F0aW9uKiogKGNhdGVnb3JpY2FsOiAxPSJwcmltYXJ5IiwyPSJzZWNvbmRhcnkiLDI9InRlcnRpYXJ5Iiw5PSJ1bmtub3duIiwpICAgDQogICA1IC0gKipkZWZhdWx0Kio6IGhhcyBjcmVkaXQgaW4gZGVmYXVsdD8gKGJpbmFyeTogInllcyIsIm5vIikgIA0KICAgNiAtICoqYmFsYW5jZSoqOiBhdmVyYWdlIHllYXJseSBiYWxhbmNlLCBpbiBldXJvcyAobnVtZXJpYykgICAgDQogICA3IC0gKipob3VzaW5nKio6IGhhcyBob3VzaW5nIGxvYW4/IChiaW5hcnk6ICJ5ZXMiLCJubyIpICAgDQogICA4IC0gKipsb2FuKio6IGhhcyBwZXJzb25hbCBsb2FuPyAoYmluYXJ5OiAieWVzIiwibm8iKSAgDQoNCioqYCNgIHJlbGF0ZWQgd2l0aCB0aGUgbGFzdCBjb250YWN0IG9mIHRoZSBjdXJyZW50IGNhbXBhaWduOioqDQoNCiAgIDkgLSAqKmNvbnRhY3QqKjogY29udGFjdCBjb21tdW5pY2F0aW9uIHR5cGUgKGNhdGVnb3JpY2FsOiAidW5rbm93biIsInRlbGVwaG9uZSIsImNlbGx1bGFyIikgDQogIDEwIC0gKipkYXkqKjogbGFzdCBjb250YWN0IGRheSBvZiB0aGUgbW9udGggKG51bWVyaWMpICANCiAgMTEgLSBtb250aDogbGFzdCBjb250YWN0IG1vbnRoIG9mIHllYXIgKGNhdGVnb3JpY2FsOiAiamFuIiwgImZlYiIsICJtYXIiLCAuLi4sICJub3YiLCAiZGVjIikgIA0KICAxMiAtICoqZHVyYXRpb24qKjogbGFzdCBjb250YWN0IGR1cmF0aW9uLCBpbiBzZWNvbmRzIChudW1lcmljKSAgIA0KDQoqKmAjYCBvdGhlciBhdHRyaWJ1dGVzOioqIA0KDQogIDEzIC0gKipjYW1wYWlnbioqOiBudW1iZXIgb2YgY29udGFjdHMgcGVyZm9ybWVkIGR1cmluZyB0aGlzIGNhbXBhaWduIGFuZCBmb3IgdGhpcyBjbGllbnQgKG51bWVyaWMsIGluY2x1ZGVzIGxhc3QgY29udGFjdCkgIA0KICAxNCAtICoqcGRheXMqKjogbnVtYmVyIG9mIGRheXMgdGhhdCBwYXNzZWQgYnkgYWZ0ZXIgdGhlIGNsaWVudCB3YXMgbGFzdCBjb250YWN0ZWQgZnJvbSBhIHByZXZpb3VzIGNhbXBhaWduIChudW1lcmljLCAtMSBtZWFucyBjbGllbnQgd2FzIG5vdCBwcmV2aW91c2x5IGNvbnRhY3RlZCkgIA0KICAxNSAtICoqcHJldmlvdXMqKjogbnVtYmVyIG9mIGNvbnRhY3RzIHBlcmZvcm1lZCBiZWZvcmUgdGhpcyBjYW1wYWlnbiBhbmQgZm9yIHRoaXMgY2xpZW50IChudW1lcmljKSAgDQogIDE2IC0gKipwb3V0Y29tZSoqOiBvdXRjb21lIG9mIHRoZSBwcmV2aW91cyBtYXJrZXRpbmcgY2FtcGFpZ24gKGNhdGVnb3JpY2FsOiAidW5rbm93biIsIm90aGVyIiwiZmFpbHVyZSIsInN1Y2Nlc3MiKSAgDQoNCioqT3V0cHV0IHZhcmlhYmxlIChkZXNpcmVkIHRhcmdldCk6KiogIA0KICAxNyAtICoqeSoqIC0gaGFzIHRoZSBjbGllbnQgc3Vic2NyaWJlZCBhIHRlcm0gZGVwb3NpdD8gKGJpbmFyeTogInllcyIsIm5vIikgIA0KDQojIyMgTG9hZCBhbmQgSW5zcGVjdCBEYXRhDQogDQpgYGB7ciBsb2FkLWRhdGEsIHdhcm5pbmc9RkFMU0V9DQojIExvYWQgcmVxdWlyZWQgbGlicmFyaWVzDQpsaWJyYXJ5KG5ldXJhbG5ldCkNCmxpYnJhcnkobm5ldCkgIyBmb3IgZHVtbXkgdmFyaWFibGUgY29kaW5nDQpsaWJyYXJ5KGdtb2RlbHMpICMgZm9yIGNvbmZ1c2lvbiBtYXRyaXgNCg0KIyBMb2FkIHRoZSBkYXRhc2V0DQpiYW5rLmRmIDwtIHJlYWQuY3N2KCJiYW5rLmNzdiIsIG5hLnN0cmluZ3MgPSAiIiwgc2VwID0gIjsiKQ0KDQojIERpc3BsYXkgdGhlIGZpcnN0IGZldyByb3dzIGFuZCBzdHJ1Y3R1cmUNCmhlYWQoYmFuay5kZikNCnN0cihiYW5rLmRmKQ0KYGBgDQoNCg0KKipDb21wbGV0ZSB0aGUgY29kZSBiZWxvdyBieSBmaWxsaW5nIHNwYWNlcyAoIl9fXyIpLioqDQoNCg0KIyMjIFF1ZXN0aW9uIDE6IFNjYWxlIE51bWVyaWMgVmFyaWFibGVzDQoNCioqSW5zdHJ1Y3Rpb25zOioqDQotIE5vcm1hbGl6ZSBgYWdlYCwgYGJhbGFuY2VgLCBgY2FtcGFpZ25gLCBhbmQgYHByZXZpb3VzYCB0byB0aGUgcmFuZ2UgWzAsIDFdLg0KLSBVc2UgdGhlIGZvcm11bGE6IGAoeCAtIG1pbih4KSkgLyAobWF4KHgpIC0gbWluKHgpKWAuDQotIFJlcG9ydCB0aGUgZmlyc3QgNiBzY2FsZWQgcmVjb3Jkcy4NCg0KYGBge3Igc2NhbGUtdmFyaWFibGVzLCB3YXJuaW5nPUZBTFNFfQ0KIyBEZWZpbmUgdGhlIG5vcm1hbGl6YXRpb24gZnVuY3Rpb24NCm5vcm1hbGl6ZSA8LSBmdW5jdGlvbih4KSB7DQogIHJldHVybigoeCAtIG1pbih4KSkgLyAobWF4KHgpIC0gbWluKHgpKSkNCn0NCg0KIyBOb3JtYWxpemUgbnVtZXJpYyB2YXJpYWJsZXMgdG8gWzAsMV0NCmJhbmsuZGYkYWdlIDwtIG5vcm1hbGl6ZShiYW5rLmRmJGFnZSkNCmJhbmsuZGYkYmFsYW5jZSA8LSBub3JtYWxpemUoYmFuay5kZiRiYWxhbmNlKQ0KYmFuay5kZiRjYW1wYWlnbiA8LSBub3JtYWxpemUoYmFuay5kZiRjYW1wYWlnbikNCmJhbmsuZGYkZHVyYXRpb24gPC0gbm9ybWFsaXplKGJhbmsuZGYkZHVyYXRpb24pDQoNCiMgRGlzcGxheSBmaXJzdCA2IHJlY29yZHMNCmhlYWQoYmFuay5kZiwgbiA9IDYpDQpgYGANCg0KIyMjIFF1ZXN0aW9uIDI6IENyZWF0ZSBEdW1teSBWYXJpYWJsZXMNCg0KKipJbnN0cnVjdGlvbnM6KioNCi0gQ3JlYXRlIGR1bW15IHZhcmlhYmxlcyBmb3IgYGVkdWNhdGlvbmAgYW5kIGB5YCB1c2luZyBgY2xhc3MuaW5kYCBmcm9tIHRoZSBgbm5ldGAgcGFja2FnZS4NCi0gQ29tYmluZSB0aGVtIHdpdGggdGhlIG9yaWdpbmFsIGRhdGFmcmFtZSB0byBjcmVhdGUgYGJhbmsuZGYxYC4NCi0gUmVuYW1lIHRoZSBuZXcgY29sdW1ucyBhcHByb3ByaWF0ZWx5Lg0KDQpgYGB7ciBjcmVhdGUtZHVtbXktdmFyaWFibGVzfQ0KIyBDcmVhdGUgZHVtbXkgdmFyaWFibGVzDQpiYW5rLmRmMSA8LSBjYmluZChiYW5rLmRmLCBjbGFzcy5pbmQoYmFuay5kZiRlZHVjYXRpb24pLCBjbGFzcy5pbmQoYmFuay5kZiR5KSkNCg0KIyBSZW5hbWUgY29sdW1ucw0KbmFtZXMoYmFuay5kZjEpWzE4OjIzXSA8LSBjKA0KICBwYXN0ZSgiZWR1XyIsIGMoMSwgMiwgMywgOSksIHNlcCA9ICIiKSwNCiAgcGFzdGUoInRlcm1fZF8iLCBjKCJ5ZXMiLCAibm8iKSwgc2VwID0gIiIpDQopDQoNCiMgRGlzcGxheSBjb2x1bW4gbmFtZXMNCm5hbWVzKGJhbmsuZGYxKQ0KYGBgDQoNCiMjIyBRdWVzdGlvbiAzOiBTZWxlY3QgQXR0cmlidXRlcw0KDQoqKkluc3RydWN0aW9uczoqKg0KLSBTZWxlY3QgdGhlIGZvbGxvd2luZyBhdHRyaWJ1dGVzOiBgYWdlYCwgYGJhbGFuY2VgLCBgY2FtcGFpZ25gLCBgcHJldmlvdXNgLCBgZWR1XzFgLCBgZWR1XzJgLCBgZWR1XzNgLCBgZWR1XzlgLCBgdGVybV9kX3llc2AsIGB0ZXJtX2Rfbm9gLg0KLSBDcmVhdGUgYSBuZXcgZGF0YWZyYW1lIGBiYW5rLmRmMmAgd2l0aCBvbmx5IHRoZXNlIGF0dHJpYnV0ZXMuDQoNCmBgYHtyIHNlbGVjdC1hdHRyaWJ1dGVzfQ0KIyBTZWxlY3Qgc3BlY2lmaWMgYXR0cmlidXRlcw0KdmFycyA8LSBjKCJhZ2UiLCAiYmFsYW5jZSIsICJjYW1wYWlnbiIsICJwcmV2aW91cyIsICJlZHVfMSIsICJlZHVfMiIsICJlZHVfMyIsICJlZHVfOSIsICJ0ZXJtX2RfeWVzIiwgInRlcm1fZF9ubyIpDQpiYW5rLmRmMiA8LSBiYW5rLmRmMVssIHZhcnNdDQoNCiMgRGlzcGxheSBjb2x1bW4gbmFtZXMNCm5hbWVzKGJhbmsuZGYyKQ0KYGBgDQoNCiMjIyBRdWVzdGlvbiA0OiBQYXJ0aXRpb24gRGF0YQ0KDQoqKkluc3RydWN0aW9uczoqKg0KLSBQYXJ0aXRpb24gdGhlIGRhdGEgaW50byA2MCUgdHJhaW5pbmcgYW5kIDQwJSB0ZXN0IHNldHMuDQotIFVzZSBhIHJhbmRvbSBzZWVkIHRvIGVuc3VyZSByZXByb2R1Y2liaWxpdHkuDQoNCmBgYHtyIHBhcnRpdGlvbi1kYXRhfQ0KIyBQYXJ0aXRpb24gdGhlIGRhdGENCnNldC5zZWVkKDIpDQp0cmFpbi5pbmRleCA8LSBzYW1wbGUocm93bmFtZXMoYmFuay5kZjIpLCBkaW0oYmFuay5kZjIpWzFdICogMC42KQ0KdGVzdC5pbmRleCA8LSBzZXRkaWZmKHJvd25hbWVzKGJhbmsuZGYyKSwgdHJhaW4uaW5kZXgpDQp0cmFpbi5kZiA8LSBiYW5rLmRmMlt0cmFpbi5pbmRleCwgXQ0KdGVzdC5kZiA8LSBiYW5rLmRmMlt0ZXN0LmluZGV4LCBdDQoNCiMgRGlzcGxheSBkaW1lbnNpb25zIG9mIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMNCmRpbSh0cmFpbi5kZikNCmRpbSh0ZXN0LmRmKQ0KYGBgDQoNCiMjIyBRdWVzdGlvbiA1OiBGaXQgTmV1cmFsIE5ldHdvcmsNCg0KKipJbnN0cnVjdGlvbnM6KioNCi0gRml0IGEgbmV1cmFsIG5ldHdvcmsgd2l0aCBoaWRkZW4gbGF5ZXJzICgzLCAyKS4NCi0gUGxvdCB0aGUgcmVzdWx0aW5nIG5ldHdvcmsuDQoNCmBgYHtyIGZpdC1uZXVyYWwtbmV0d29ya30NCiMgRml0IG5ldXJhbCBuZXR3b3JrDQpubiA8LSBuZXVyYWxuZXQodGVybV9kX3llcyArIHRlcm1fZF9ubyB+IC4sIGRhdGEgPSB0cmFpbi5kZiwgaGlkZGVuID0gYygzLCAyKSkNCg0KIyBQbG90IHRoZSBuZXVyYWwgbmV0d29yaw0KcGxvdChubiwgcmVwID0gImJlc3QiKQ0KYGBgDQoNCiMjIyBRdWVzdGlvbiA2OiBFdmFsdWF0ZSBNb2RlbCBQZXJmb3JtYW5jZQ0KDQoqKkluc3RydWN0aW9uczoqKg0KLSBQcmVkaWN0IGNsYXNzZXMgZm9yIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGFzZXRzLg0KLSBVc2UgYENyb3NzVGFibGVgIGZyb20gdGhlIGBnbW9kZWxzYCBwYWNrYWdlIHRvIGNyZWF0ZSBhIGNvbmZ1c2lvbiBtYXRyaXguDQotIENhbGN1bGF0ZSB0aGUgYWNjdXJhY3kgZm9yIGJvdGggdHJhaW5pbmcgYW5kIHRlc3QgcHJlZGljdGlvbnMgdXNpbmcgYG1lYW5gLg0KDQpgYGB7ciBldmFsdWF0ZS1wZXJmb3JtYW5jZX0NCiMgKGEpIFByZWRpY3Rpb25zIG9uIHRyYWluaW5nIHNldA0KdHJhaW5pbmcucHJlZGljdGlvbiA8LSBjb21wdXRlKG5uLCB0cmFpbi5kZlssIC1jKDk6MTApXSkNCnRyYWluaW5nLmNsYXNzIDwtIGFwcGx5KHRyYWluaW5nLnByZWRpY3Rpb24kbmV0LnJlc3VsdCwgMSwgd2hpY2gubWF4KSAtIDENCg0KIyBDb25mdXNpb24gbWF0cml4IGFuZCBhY2N1cmFjeSBmb3IgdHJhaW5pbmcgc2V0DQpDcm9zc1RhYmxlKGZhY3Rvcih0cmFpbmluZy5jbGFzcyksIGZhY3Rvcih0cmFpbi5kZiR0ZXJtX2Rfbm8pLCANCiAgICAgICAgICAgcHJvcC5jaGlzcSA9IEZBTFNFLCBwcm9wLmMgPSBGQUxTRSwgcHJvcC5yID0gRkFMU0UpDQp0cmFpbmluZy5hY2N1cmFjeSA8LSBtZWFuKHRyYWluaW5nLmNsYXNzID09IHRyYWluLmRmJHRlcm1fZF9ubykNCnRyYWluaW5nLmFjY3VyYWN5DQoNCiMgKGIpIFByZWRpY3Rpb25zIG9uIHRlc3Qgc2V0DQp0ZXN0LnByZWRpY3Rpb24gPC0gY29tcHV0ZShubiwgdGVzdC5kZlssIC1jKDk6MTApXSkNCnRlc3QuY2xhc3MgPC0gYXBwbHkodGVzdC5wcmVkaWN0aW9uJG5ldC5yZXN1bHQsIDEsIHdoaWNoLm1heCkgLSAxDQoNCiMgQ29uZnVzaW9uIG1hdHJpeCBhbmQgYWNjdXJhY3kgZm9yIHRlc3Qgc2V0DQpDcm9zc1RhYmxlKGZhY3Rvcih0ZXN0LmNsYXNzKSwgZmFjdG9yKHRlc3QuZGYkdGVybV9kX25vKSwNCiAgICAgICAgICAgcHJvcC5jaGlzcSA9IEZBTFNFLCBwcm9wLmMgPSBGQUxTRSwgcHJvcC5yID0gRkFMU0UpDQp0ZXN0LmFjY3VyYWN5IDwtIG1lYW4odGVzdC5jbGFzcyA9PSB0cmFpbi5kZiR0ZXJtX2Rfbm8pDQp0ZXN0LmFjY3VyYWN5DQpgYGANCg0KKipBbnN3ZXIgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnM6KioNCg0KMS4gSG93IGRvZXMgdGhlIEFOTiBtb2RlbCBwZXJmb3JtIGluIHByZWRpY3RpbmcgcG9zaXRpdmUgKCJ5ZXMiKSBhbmQgbmVnYXRpdmUgKCJubyIpIG91dGNvbWVzPw0KDQpfKipBbnN3ZXI6KipfIA0KVGhlIEFOTiBwZXJmb3JtcyBwcmV0dHkgd2VsbCBwcmVkaWN0aW5nIGJvdGggdGhlIHBvc2l0aXZlcyBhbmQgbmVnYXRpdmVzIGR1ZSB0byBhIGhpZ2ggYWNjdXJhY3kgb24gYm90aCB0aGUgdHJhaW5pbmcgYW5kIA0KdGVzdGluZyBkZi4gDQoNCjIuIFdoYXQgYXJlIHlvdXIgdGhvdWdodHMgb24gdGhlIHBlcmZvcm1hbmNlIG9mIHRoZSBBTk4gbW9kZWwgYmFzZWQgb24gdGhlIGFjY3VyYWN5IGVzdGltYXRlcyBmb3IgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGFzZXRzPw0KDQpfKipBbnN3ZXI6KipfIA0KVGhlcmUgaXMgYSAzIHBlcmNlbnQgZGVjcmVhc2UgaW4gYWNjcnVhY3ksIGhvd2V2ZXIgdGhlcmUgYWxzbyBpcyBhIGRlY3JlYXNlIGluIHRoZSBsZW5ndGggb2YgZGYuIFRoaXMgbWVhbnMgdGhhdCBhbHRob3VnaA0KdGhlIHNjb3JlIHdhc24ndCBhcyBoaWdoIGluIHRoZSB0ZXN0aW5nIGRhdGEgc2V0IGFzIGl0IHdhcyBmb3IgdGhlIHRyYWluaW5nIHNldCwgaXQgd2FzIHN0aWxsIGFib3ZlIGF2ZXJhZ2UgYW5kIHdhcyBhYmxlDQp0byBhY2N1cmF0ZWx5IHByZWRpY3QgbW9zdCBvZiB0aGUgdGltZS4NCg0KDQojIyMgUXVlc3Rpb24gNzogTG9naXN0aWMgUmVncmVzc2lvbiBNb2RlbA0KDQoqKkluc3RydWN0aW9uczoqKg0KLSBGaXQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHRvIGNsYXNzaWZ5IHRoZSBvdXRjb21lIGB0ZXJtX2Rfbm9gLg0KLSBVc2UgdGhlIHNhbWUgcHJlZGljdG9ycyBhbmQgdGhlIHNhbWUgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YXNldHMsDQogIGV4Y2VwdCBgZWR1XzlgIGFuZCBgdGVybV9kX3llc2ANCi0gRGlzcGxheSB0aGUgY29lZmZpY2llbnRzIHVzaW5nIGBjb2VmYC4NCi0gQWRkIGEgc3VtbWFyeSBvZiB0aGUgbW9kZWwuDQoNCmBgYHtyIGxvZ2lzdGljLXJlZ3Jlc3Npb259DQojIG5lZWQgdG8gZHJvcCBjb2x1bW5zIDggYW5kIDkgKGVkdV85IGFuZCB0ZXJtX2RfeWVzKQ0KdHJhaW4uZGYubG9nIDwtIHRyYWluLmRmWywgLWMoOCw5KV0NCnRlc3QuZGYubG9nIDwtIHRlc3QuZGZbLCAtYyg4LDkpXQ0KDQojIEZpdCBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsDQpsb2dpc3RpYy5tb2RlbCA8LSBnbG0odGVybV9kX25vIH4gLiwgZGF0YSA9IHRyYWluLmRmLmxvZywgZmFtaWx5ID0gImJpbm9taWFsIikNCg0KIyBEaXNwbGF5IGNvZWZmaWNpZW50cw0KY29lZihsb2dpc3RpYy5tb2RlbCkNCg0KIyBBZGQgYSBzdW1tYXJ5IG9mIHRoZSBtb2RlbA0Kc3VtbWFyeShsb2dpc3RpYy5tb2RlbCkNCmBgYA0KDQojIyMgUXVlc3Rpb24gODogUHJlZGljdCBhbmQgRXZhbHVhdGUgTG9naXN0aWMgTW9kZWwNCg0KKipJbnN0cnVjdGlvbnM6KioNCi0gUHJlZGljdCB0aGUgb3V0Y29tZXMgZm9yIHRoZSB0ZXN0IHNldCB1c2luZyB0aGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbC4NCi0gVXNlIHRoZSBgcFJPQ2AgbGlicmFyeSB0byBwbG90IHRoZSBST0MgY3VydmUuDQotIERpc3BsYXkgdGhlIEFVQyB2YWx1ZS4NCi0gQ3JlYXRlIGEgY29uZnVzaW9uIG1hdHJpeCBmb3IgdGhlIHByZWRpY3Rpb25zIHdpdGggYSB0aHJlc2hvbGQgb2YgMC41MCwgYXNzdW1pbmcgInllcyIgZm9yIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIGdyZWF0ZXIgdGhhbiAwLjUwLg0KDQpgYGB7ciBsb2dpc3RpYy1yb2MtYXVjfQ0KIyBMb2FkIHBST0MgbGlicmFyeQ0KbGlicmFyeShwUk9DKQ0KDQojIFByZWRpY3QgcHJvYmFiaWxpdGllcyBmb3IgdGVzdCBzZXQNCnRlc3QucHJvYiA8LSBwcmVkaWN0KGxvZ2lzdGljLm1vZGVsLCB0ZXN0LmRmLmxvZywgdHlwZSA9ICJyZXNwb25zZSIpDQoNCiMgUGxvdCBST0MgY3VydmUNCnJvY19jdXJ2ZSA8LSByb2ModGVzdC5kZi5sb2ckdGVybV9kX25vLCB0ZXN0LnByb2IpDQpwbG90KHJvY19jdXJ2ZSwgbWFpbiA9ICJST0MgQ3VydmUgZm9yIExvZ2lzdGljIFJlZ3Jlc3Npb24iKQ0KDQojIERpc3BsYXkgQVVDIHZhbHVlDQphdWMocm9jX2N1cnZlKQ0KDQojIENyZWF0ZSBjb25mdXNpb24gbWF0cml4IHdpdGggdGhyZXNob2xkIDAuNTANCnRlc3QucHJlZC5jbGFzcyA8LSBpZmVsc2UodGVzdC5wcm9iID4gMC41MCwgMSwgMCkNCkNyb3NzVGFibGUoZmFjdG9yKHRlc3QucHJlZC5jbGFzcyksIGZhY3Rvcih0ZXN0LmRmLmxvZyR0ZXJtX2Rfbm8pLCANCiAgICAgICAgICAgcHJvcC5jaGlzcSA9IEZBTFNFLCBwcm9wLmMgPSBGQUxTRSwgcHJvcC5yID0gRkFMU0UsIA0KICAgICAgICAgICBkbm4gPSBjKCJQcmVkaWN0ZWQiLCAiQWN0dWFsIikpDQoNCnRlc3QuYWNjdXJhY3kubG9nIDwtIG1lYW4odGVzdC5wcmVkLmNsYXNzID09IHRlc3QuZGYubG9nJHRlcm1fZF9ubykNCnRlc3QuYWNjdXJhY3kubG9nDQoNCmBgYA0KKipBbnN3ZXIgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnM6KioNCg0KMS4gSG93IGRvZXMgdGhlIGxvZ2lzdGljIG1vZGVsIHBlcmZvcm0gaW4gcHJlZGljdGluZyBwb3NpdGl2ZSB0aGUgKCJubyIpIG91dGNvbWVzPw0KDQpfKipBbnN3ZXI6KipfIA0KVGhlIGxvZ2lzdGljIG1vZGVsIHBlcmZvcm1zIHdlbGwgaW4gcHJlZGljdGluZyAibm8iIG91dGNvbWVzLCBiZWNhdXNlIG9mIGEgcmVsYXRpdmVseSBoaWdoIGFjY3VyYWN5IG9uIHRoZSB0ZXN0IHNldC4gVGhlDQpjb25mdXNpb24gbWF0cml4IHNob3dzIHRoYXQgdGhlIG1vZGVsIHByZWRpY3RzICJubyIgd2l0aCBnb29kIGFjY3VyYWN5LiBUaGUgUk9DIGN1cnZlIGFuZCBBVUMgZnVydGhlciBzdXBwb3J0IHRoZSBtb2RlbCdzDQplZmZlY3RpdmVuZXNzIGluIGRpc3Rpbmd1aXNoaW5nIGJldHdlZW4gInllcyIgYW5kICJubyIgb3V0Y29tZXMuIEhvd2V2ZXIsIHRoZXJlIGlzIGFsd2F5cyByb29tIGZvciBpbXByb3ZlbWVudCB0aHJvdWdoDQptb2RlbCBhZGp1c3RtZW50cyBhbmQgdGhyZXNob2xkIHR1bmluZy4NCg0KDQoyLiBXaGljaCBwcmVkaWN0b3JzIGFyZSBzaWduaWZpY2FudD8gV2h5Pw0KDQpfKipBbnN3ZXI6KipfIA0KDQpUaGUgc2lnbmlmaWNhbnQgcHJlZGljdG9ycyBhcmUgZGV0ZXJtaW5lZCBieSB0aGVpciBwLXZhbHVlcyBpbiB0aGUgc3VtbWFyeS4gVmFyaWFibGVzIHdpdGggbG93IHAtdmFsdWVzIHdoaWNoIGFyZSB1c3VhbGx5DQpsZXNzIHRoYW4gMC4wNSBhcmUgY29uc2lkZXJlZCBzaWduaWZpY2FudC4gUHJldmlvdXMgY29udGFjdHMgc2hvd3MgdGhlIHN0cm9uZ2VzdCBhc3NvY2lhdGlvbnMgd2l0aCB0aGUgbGlrZWxpaG9vZCBvZiBub3QNCnN1YnNjcmliaW5nLiANCg0KMy4gSG93IHdvdWxkIHlvdSBpbnRlcnByZXQgdGhlIGNvZWZmaWNpZW50IG9mIGVkdV8zPXRlcnRpYXJ5Pw0KDQpfKipBbnN3ZXI6KipfIA0KDQpUaGUgY29lZmZpY2llbnQgb2YgZWR1XzM9dGVydGlhcnkgcmVwcmVzZW50cyB0aGUgbG9nLW9kZHMgb2YgYSBjbGllbnQgc3Vic2NyaWJpbmcgdG8gYSB0ZXJtIGRlcG9zaXQgd2l0aCByZWxhdGlvbiB0byB0aGUNCnJlZmVyZW5jZSBjYXRlZ29yeS4gQSBwb3NpdGl2ZSBjb2VmZmljaWVudCBtZWFucyB0aGF0IHRlcnRpYXJ5IGVkdWNhdGlvbiBpbmNyZWFzZXMgdGhlIGxpa2VsaWhvb2Qgb2YgYSAibm8iIG91dGNvbWUuIFRoaXMNCnJlbGF0ZXMgYmFjayB0byB0aGUgdXNlIG9mIHR3byB2YXJpYWJsZXMgbm90IG5lY2Vzc2FyaWx5IGluY3JlYXNpbmcgdGhlIHByaWNlIG9mIGhvbWVzIGFzIGZvdW5kIGluIG91ciBwcmV2aW91cyBsYWJzLiBUaGUNCmNvZWZmaWNpZW50IGlzIHRoZSB1bHRpbWF0ZSBmYWN0b3Igb2YgdGhlIGltcGFjdCBvZiB0d28gdmFyaWFibGVzIGNyZWF0aW5nIGFuIGVmZmVjdC4gDQoNCjQuIEhvdyB3b3VsZCB5b3UgZXhwbGFpbiBwcm9wZXJ0aWVzIG9mIFJPQyBhbmQgQVVDIGVzdGltYXRlcyB5b3UgaGF2ZSBvYnRhaW5lZD8NCg0KXyoqQW5zd2VyOioqXw0KDQpUaGUgUk9DIGN1cnZlIHRlbGxzIHVzIHRoYXQgdGhlcmUgaXMgdHJhZGUtb2ZmIGJldHdlZW4gdHJ1ZSBwb3NpdGl2ZSByYXRlIGFuZCBmYWxzZSBwb3NpdGl2ZSByYXRlIGFjcm9zcyBkaWZmZXJlbnQNCnRocmVzaG9sZHMuIFRoZSBBVUMgdmFsdWUgcXVhbnRpZmllcyB0aGlzLCB3aXRoIGEgaGlnaGVyIEFVQyB3aGljaCB3b3VsZCBpbmRpY2F0ZSBhIGJldHRlciBtb2RlbCBwZXJmb3JtYW5jZS4gQW4gQVVDIG9mDQowLjggb3IgaGlnaGVyIGlzIGNvbnNpZGVyZWQgc3Ryb25nLCB0aGVyZWZvcmUgbWVhbmluZyB0aGF0IHRoZSBtb2RlbCBpcyBnb29kIGF0IGRpc3Rpbmd1aXNoaW5nIGJldHdlZW4geWVzIGFuZCBuby4gSW4NCnRoaXMgY2FzZSwgdGhlIEFVQyBzdWdnZXN0cyB0aGF0IHRoZSBsb2dpc3RpYyBtb2RlbCBoYXMgZ29vZCBwZXJmb3JtYW5jZS4NCg0KNS4gRG8geW91IHNlZSBhbnkgcHJvYmxlbXMgdXNpbmcgdGhlIGFjY3VyYWN5IGVzdGltYXRlIGZvciB0aGUgbG9naXN0aWMgbW9kZWwgYmFzZWQgb24gdGhlIHByb2JhYmlsdHkgdGhyZXNob2xkIG9mIDAuNTA/DQoNCl8qKkFuc3dlcjoqKl8gDQpXaGVuIHVzaW5nIGEgMC41MCB0aHJlc2hvbGQgZm9yIGFjY3VyYWN5IGluIHRoaXMgY2FzZSBpdCB3b3JrZWQgb3V0IHdlbGwsIGhvd2V2ZXIgaWYgdGhlIGRhdGFzZXQgaXMgaW1iYWxhbmNlZCBpdCBjYW4NCmFsc28gYmFja2ZpcmUuIEZvciBleGFtcGxlLCBpZiAibm8iIG91dGNvbWVzIGRvbWluYXRlLCB0aGUgbW9kZWwgbWlnaHQgcHJlZGljdCAibm8iIG1vc3Qgb2YgdGhlIHRpbWUsIHJlc3VsdGluZyBpbiBoaWdoDQphY2N1cmFjeSBidXQgcG9vciBtb2RlbCBwZXJmb3JtYW5jZSBpbiBwcmVkaWN0aW5nICJ5ZXMiIG91dGNvbWVzLiBBIGNoYW5nZSBpbiB0aHJlc2hvbGQgaXMgc29tZXRoaW5nIHRoYXQgbXVzdCBiZQ0KZGV0ZXJtaW5lZCBhZnRlciB1bmRlcnN0YW5kaW5nIHRoZSBkYXRhLg0KDQoNCg0KDQoNCg==