1 Introduction

Recall the following workflow of the data science project.

Data Science Process

Data Science Process

Feature engineering is constructing new input features from the existing raw data to better represent the underlying problem to predictive models, resulting in improved model accuracy on unseen data. As part of the machine learning pipeline, feature engineering comes after data collection, cleaning, and EDA, but before model training and evaluation.

In the previous note, we introduced EDA with visual aids to extract patterns such as distribution, clusters, association, missing values, etc. These patterns can be used as bases to create features to improve the performance of the subsequent modeling and analyses.

In this note, we will explore some techniques that are used to engineer impactful features. Properly applying these techniques enables machine learning algorithms to learn more robust patterns, avoid over-fitting, and enhance predictive accuracy. The key steps in feature engineering include

  • Data Exploration and Understanding: Explore and understand the dataset, including the types of features and their distributions. Understanding the shape of the data is key.

  • Handling Missing Data: Address missing values through imputation or removal of instances or features with missing data. There are many algorithmic approaches to handling missing data.

  • Variable Encoding: Convert categorical variables into a numerical format suitable for machine learning algorithms using methods.

  • Feature Scaling: Standardize or normalize numerical features to ensure they are on a similar scale, improving model performance.

  • Feature Creation: Generate new features by combining existing ones to capture relationships between variables.

  • Handling Outliers: Identify and address outliers in the data through techniques like trimming or transforming the data.

  • Normalization: Normalize features to bring them to a common scale, important for algorithms sensitive to feature magnitudes.

  • Binning or Discretization: Convert continuous features into discrete bins to capture specific patterns in certain ranges.

  • Text Data Processing: If dealing with text data, perform tasks such as tokenization, stemming, and removing stop words.

  • Time Series Features: Extract relevant time-based features such as lag features or rolling statistics for time series data.

  • Vector Features: Vector features are commonly used for training in machine learning. In machine learning, data is represented in the form of features, and these features are often organized into vectors.

  • Feature Selection: Identify and select the most relevant features to improve model interpretability and efficiency using techniques like univariate feature selection or recursive feature elimination.

  • Feature Extraction: Feature extraction aims to reduce data complexity (often known as “data dimensionality”) while retaining as much relevant information as possible. This helps to improve the performance and efficiency of machine learning algorithms and simplify the analysis process. Feature extraction may involve the creation of new features (“feature engineering”) and data manipulation to separate and simplify the use of meaningful features from irrelevant ones. Create new features or reduce dimensionality using techniques such as Principal Component Analysis (PCA).

  • Cross-validation: selecting features before cross-validation can introduce significant bias. Evaluate the impact of feature engineering on model performance using cross-validation techniques.

Feature engineering is often an iterative process; evaluate, refine, and repeat as needed to achieve optimal results. These steps may vary based on the nature of the data and the machine learning task, but collectively, they contribute to creating a robust and effective feature set for model training.

Some of the above tasks are initiated from exploratory data analysis while others can be initiated from modeling. In the next few sections, we will discuss each of the above feature engineering tasks with some examples. A case study with EDA and feature engineering with some illustrative examples.

2 EDA-based Feature Engineering

We have introduced EDA to identify patterns such as distribution, relationships, missing values, etc. When building models and implementing algorithms, we need to assess the assumption based on which the models and algorithms were developed. For example, extremely skewed or multi-modal predictor variables may cause inaccurate prediction, sparse categorical variables (some categories have extremely small counts) cause unstable prediction, etc. We need to use various techniques to create new features to improve the performance of modeling.

2.1 Handling Missing Values

Imputing missing values is a crucial step in data pre-processing, especially for machine learning and statistical analysis. There are many imputation methods. Here are some common methods for handling missing data:

2.1.1 Simple Imputation

Mean/Median/Mode Imputation: Replace missing values with the mean, median, or mode of the column. This is straightforward but can introduce bias.

Constant Value Imputation: Replace missing values with a constant value, such as 0 or -1.

The above two methods are simple to implement but perform poorly

2.1.2 Model-based Imputation

K-Nearest Neighbors (KNN) Imputation: Use the values from the nearest neighbors to impute missing data. This method can capture local patterns in the data. The following figure explains how KNN imputation works.

K-nearest neighborhood imputation

K-nearest neighborhood imputation

The idea is to replace the missing components with the corresponding components of the nearest point. In the above figure. the orange point has at least one missing component, and the red triangle is the nearest point. We replace the missing component(s) of the orange point should be replaced with the corresponding non-missing component(s) in the red triangle point based on the KNN imputation method.

Multivariate Imputation by Chained Equations (MICE): Iteratively imputes missing values by modeling each feature with missing values as a function of other features. We will not discuss the technical development of this method. The R library mice can be used to implement MICE imputation.

2.1.3 Regression Imputation

Regression Imputation: Uses regression models to estimate replacements for missing values based on correlations with other features. This method assumes that there is one or more variables that are correlated. The process of regression imputation involves the following steps:

Step 1: The first step in regression imputation is to identify the variables with missing data. Once these variables have been identified, they can be used as the dependent variables in the regression model.

Step 2: The next step is to identify the variables that can be used as independent variables in the regression model. These variables should be strongly correlated with the dependent variable and should not have missing values themselves.

Step 3: Fit a regression model Once the variables have been identified, a regression model can be fit using the available data. The regression model can be any appropriate regression model, such as linear regression or logistic regression.

Step 4: Use the regression model to impute missing values Finally, the regression model can be used to predict the missing values. The predicted values can be substituted for the missing values in the data set.

2.2 Discretization

Discretization is a technique in feature engineering where continuous variables are converted into discrete intervals or categories. This can simplify the feature space and sometimes improve model performance, especially for algorithms that handle categorical data better. Here are some common methods of discretization.

2.2.1 Equal Width Binning

Equal width binning divides the range of the variable into intervals of equal width. The following toy example demonstrates this method.

# Sample data
data <- data.frame(value = c(1.1, 2.3, 3.5, 4.7, 5.9))

# Equal width binning
data$bin <- cut(data$value, breaks = 3, labels = c("Low", "Medium", "High"))
print(data)
  value    bin
1   1.1    Low
2   2.3    Low
3   3.5 Medium
4   4.7   High
5   5.9   High

2.2.2 Equal Frequency Binning

Equal frequency binning divides the data into intervals that contain approximately the same number of observations. This method is sometimes used to develop a chi-squared test for the goodness-of-fit test. The following is a simple example with R code.

# Sample data
data <- data.frame(value = c(1.1, 2.3, 3.5, 4.7, 5.9))

# Equal frequency binning
data$bin <- cut(data$value, breaks = quantile(data$value, probs = seq(0, 1, by = 1/3)), 
                include.lowest = TRUE, include.highest = TRUE, labels = c("Low", "Medium", "High"))
print(data)
  value    bin
1   1.1    Low
2   2.3    Low
3   3.5 Medium
4   4.7   High
5   5.9   High

2.2.3 Other Binning Methods

Unequal Width Binning: In some applications, unequal binning is used based on practical considerations. For example, in clinical studies, age is usually discretized based on clinical considerations.

Algorithm-based Discretization - there are several machine learning algorithms such as k-mean and decision trees can also be used for discretizing continuous variables.

2.3 Variable transformation

Feature transformation is a key part of feature engineering in machine learning. Depending on the models and algorithms to be used and the patterns observed in EDA, it involves different transformations to convert raw data into a format that is more suitable for model training and prediction. Here are some common types of feature transformations:

2.3.1 Conventional Transformations

Log Transformation: Applying a logarithmic function to skewed data to make it more normally distributed. This is particularly useful for data with exponential growth patterns.

Polynomial Features: Creating new features by raising existing features to a power. This can help capture non-linear relationships in the data.

2.3.2 Box-Cox Transformation

Box-cox Transformation: The Box-cox transformation is a statistical technique used to transform non-normal dependent variables into a normal distribution. This transformation is particularly useful in improving the accuracy of predictions made using linear regression and other statistical models that assume normality. The transformation takes the following form

\[ y_{\text{new}} = \left\{ \begin{array}{lcl} \frac{y^\lambda -1}{\lambda} & \text{if} & \lambda \ne 0 \\ \log(y) & \text{if} & \lambda = 0. \end{array} \right. \] Parameter \(\lambda\) is estimated from the data. We can see that the log transformation is a special Box-cox transformation.

The Box-cox transformation is used for

Normalization: It helps in transforming skewed data to resemble a normal distribution.

Variance Stabilization: It can stabilize variance across levels of the data.

Improving Model Performance: By meeting the assumptions of normality, it enhances the performance of statistical models.

The \(y\) has negative values, we need to use the modified version of Box-cox or use other transformations to convert \(y\) into a positive variable before applying the Box-cox transformation.

Example: To perform a Box-Cox transformation in R, you can use the boxcox function from the MASS package. This function helps find the optimal \(\lambda\) value to transform the given data to approximate a normal distribution.

library(MASS)
y <- c(1, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 3, 6, 7, 8)
x <- c(7, 7, 8, 3, 2, 4, 4, 6, 6, 7, 5, 3, 3, 5, 8)

#Fit a Linear Regression Model:
model = lm(y~x)
#Find the Optimal Lambda for Box-Cox Transformation:
bc <- boxcox(y ~ x, lambda = seq(-2, 2, 1/10))
Box-Cox Transformation Demonstration

Box-Cox Transformation Demonstration

lambda <- bc$x[which.max(bc$y)]

#Fit a New Linear Regression Model Using the Box-Cox Transformation:
new_model <- lm(((y^lambda - 1) / lambda) ~ x)
## Visualize the Differences in Residuals:
op <- par(pty = "s", mfrow = c(1, 2))
qqnorm(model$residuals)
qqline(model$residuals)
qqnorm(new_model$residuals)
qqline(new_model$residuals)
Box-Cox Transformation Demonstration

Box-Cox Transformation Demonstration

par(op)

The boxcox function computes the log-likelihood for different values of \(\lambda\) and helps identify the optimal \(\lambda\). The new model uses the transformed response variable to improve normality and meet the assumptions of linear regression. To check whether the transformed variable follows a normal distribution, we can use Q-Q plots to visualize the normality of residuals before and after the transformation.


2.4 Outlier handling

Handling outliers in R is a crucial step in feature engineering to ensure the robustness of your models. Here are some common techniques for detecting and handling outliers in R:

  • Graphical approaches include boxplot, scatter plot, etc.

  • Statistical approaches include Z-score transformation, and interquartile range (IQR).

Several methods are commonly used to handle outliers, among trimming, capping (Winsorizing), imputation, transformation, and binning, transformation and binning are commonly used in classical statistics.


3 Model-based Feature Extraction

Model/algorithm-based feature extraction methods clustering, principal component analysis (PCA), etc.

3.1 Principal Component Analysis (PCA)

Principal Component Analysis (PCA) is a powerful technique used to reduce the dimensionality of data while retaining most of the variance.

Illustration of principal component analysis

Illustration of principal component analysis

The technical description will be discussed later. Here’s a step-by-step example of how to perform PCA in R using the USArrests dataset, which contains data on arrests per 100,000 residents for various crimes in each U.S. state. The data set is built in the library tidyverse. The following R code implements PCA.

# Load the dataset
data("USArrests")
# View the first few rows of the dataset
head(USArrests)
           Murder Assault UrbanPop Rape
Alabama      13.2     236       58 21.2
Alaska       10.0     263       48 44.5
Arizona       8.1     294       80 31.0
Arkansas      8.8     190       50 19.5
California    9.0     276       91 40.6
Colorado      7.9     204       78 38.7
# Summary of the data
summary(USArrests)
     Murder          Assault         UrbanPop          Rape      
 Min.   : 0.800   Min.   : 45.0   Min.   :32.00   Min.   : 7.30  
 1st Qu.: 4.075   1st Qu.:109.0   1st Qu.:54.50   1st Qu.:15.07  
 Median : 7.250   Median :159.0   Median :66.00   Median :20.10  
 Mean   : 7.788   Mean   :170.8   Mean   :65.54   Mean   :21.23  
 3rd Qu.:11.250   3rd Qu.:249.0   3rd Qu.:77.75   3rd Qu.:26.18  
 Max.   :17.400   Max.   :337.0   Max.   :91.00   Max.   :46.00  
# Standardize the data
USArrests_scaled <- scale(USArrests)
# Perform PCA
pca_result <- prcomp(USArrests_scaled, center = TRUE, scale. = TRUE)
# Summary of PCA results
summary(pca_result)
Importance of components:
                          PC1    PC2     PC3     PC4
Standard deviation     1.5749 0.9949 0.59713 0.41645
Proportion of Variance 0.6201 0.2474 0.08914 0.04336
Cumulative Proportion  0.6201 0.8675 0.95664 1.00000
# setting up plot layout
#par(mfrow =c(1,2))
# Scree plot
screeplot(pca_result, type = "lines")
Implementing principal component analysis (PCA)

Implementing principal component analysis (PCA)

# Biplot
#biplot(pca_result, scale = 0)

The next step is to extract the new feature variables. For comparison, we add the new features to the original data set.

PCA.scores = round(pca_result$x,5)
combined.data = cbind(USArrests, PCA.scores)
head(combined.data )
           Murder Assault UrbanPop Rape      PC1      PC2      PC3      PC4
Alabama      13.2     236       58 21.2 -0.97566 -1.12200  0.43980  0.15470
Alaska       10.0     263       48 44.5 -1.93054 -1.06243 -2.01950 -0.43418
Arizona       8.1     294       80 31.0 -1.74544  0.73846 -0.05423 -0.82626
Arkansas      8.8     190       50 19.5  0.14000 -1.10854 -0.11342 -0.18097
California    9.0     276       91 40.6 -2.49861  1.52743 -0.59254 -0.33856
Colorado      7.9     204       78 38.7 -1.49934  0.97763 -1.08400  0.00145

Comments: A few comments on PCA:

  1. PCA scores are transformed from the original features. Therefore, PCA is a model-based transformation.

  2. The PCA is also called a dimension reduction method because, in this example, the first three PCs carry about \(96\%\) of the total information in the original 4 features. In modeling, we simply use the first three PCs and ignore the fourth one. In other words, we can reduce the dimension from 4 to three after performing PCA!

  3. It is dependent on the application and your tolerance of information loss to decide the number of PCs to retain for the subsequent analyses. If you decide to carry at least \(85\%\) information in the original feature sets, you only need to choose the first 2 PCs for the subsequent analysis.

  4. The PCA is only performed on numerical variables that we at the same scale and correlated!. In classical inferential statistics, we should also consider the interpretability of the PCA. In machine learning, we can consider this as a black-box procedure.

  5. We can extract the factor loading from PCA (which is a system of orthogonal linear transformation - a typical topic covered in a linear algebra course).

  6. The method of PCA is also one of the information aggregation methods.


3.2 Cluster Analysis

Unlike the PCA in which the original features are transformed to PCs and retain strong PCs for subsequent analyses and drop weak PCs to reduce the dimension of the data, clustering employs the topological shape and structure to cluster data points that are close to each other based on some distances. The distance used in data science is not necessarily to be Euclidean. There are different types of distances in data science algorithms. More detailed information on clustering will be discussed in future notes. Here are two commonly used clustering algorithms.

3.2.1 K-means Clustering

K-means clustering is a popular unsupervised machine learning algorithm used to partition a dataset into (\(k\)) distinct, non-overlapping clusters. The basic steps for implementing k-means clustering for numerical features are outlined in the following.

  1. Initialization: Choose the number of clusters (\(k\)) and randomly initialize (\(k\)) cluster centroids.

  2. Assignment: Assign each data point to the nearest cluster centroid based on the Euclidean distance.

  3. Update: Recalculate the centroids as the mean of all data points assigned to each cluster.

  4. Repeat: Repeat the assignment and update steps until the centroids no longer change significantly or a maximum number of iterations is reached.

K-means clustering analysis

K-means clustering analysis

The following is a simple example with R code using USA arrest Data. To visualize the clusters, we first perform PCA and use the first two PCs to make the scatter plot. The following R code use library ****

# Load and prepare data
data("USArrests")
df <- na.omit(USArrests)
df <- scale(df)

# Perform K-means clustering
set.seed(123)  # For reproducibility
kmeans_result <- kmeans(df, centers = 3, nstart = 25)

# Visualize clusters
fviz_cluster(kmeans_result, data = df, xlab="PC1", ylab = "PC2")
Visualizing clusters based on k-means

Visualizing clusters based on k-means

What is the new feature? - The new feature extracted from the data is the vector of the cluster labels!.

In R, we extract the cluster labels using the following code.

clust.ID = kmeans_result$cluster
cbind(USArrests, clust.ID)
               Murder Assault UrbanPop Rape clust.ID
Alabama          13.2     236       58 21.2        1
Alaska           10.0     263       48 44.5        1
Arizona           8.1     294       80 31.0        1
Arkansas          8.8     190       50 19.5        3
California        9.0     276       91 40.6        1
Colorado          7.9     204       78 38.7        1
Connecticut       3.3     110       77 11.1        3
Delaware          5.9     238       72 15.8        3
Florida          15.4     335       80 31.9        1
Georgia          17.4     211       60 25.8        1
Hawaii            5.3      46       83 20.2        3
Idaho             2.6     120       54 14.2        2
Illinois         10.4     249       83 24.0        1
Indiana           7.2     113       65 21.0        3
Iowa              2.2      56       57 11.3        2
Kansas            6.0     115       66 18.0        3
Kentucky          9.7     109       52 16.3        2
Louisiana        15.4     249       66 22.2        1
Maine             2.1      83       51  7.8        2
Maryland         11.3     300       67 27.8        1
Massachusetts     4.4     149       85 16.3        3
Michigan         12.1     255       74 35.1        1
Minnesota         2.7      72       66 14.9        2
Mississippi      16.1     259       44 17.1        1
Missouri          9.0     178       70 28.2        1
Montana           6.0     109       53 16.4        2
Nebraska          4.3     102       62 16.5        2
Nevada           12.2     252       81 46.0        1
New Hampshire     2.1      57       56  9.5        2
New Jersey        7.4     159       89 18.8        3
New Mexico       11.4     285       70 32.1        1
New York         11.1     254       86 26.1        1
North Carolina   13.0     337       45 16.1        1
North Dakota      0.8      45       44  7.3        2
Ohio              7.3     120       75 21.4        3
Oklahoma          6.6     151       68 20.0        3
Oregon            4.9     159       67 29.3        3
Pennsylvania      6.3     106       72 14.9        3
Rhode Island      3.4     174       87  8.3        3
South Carolina   14.4     279       48 22.5        1
South Dakota      3.8      86       45 12.8        2
Tennessee        13.2     188       59 26.9        1
Texas            12.7     201       80 25.5        1
Utah              3.2     120       80 22.9        3
Vermont           2.2      48       32 11.2        2
Virginia          8.5     156       63 20.7        3
Washington        4.0     145       73 26.2        3
West Virginia     5.7      81       39  9.3        2
Wisconsin         2.6      53       66 10.8        2
Wyoming           6.8     161       60 15.6        3

3.2.2 Hierarchical Clustering

Hierarchical clustering is a method of cluster analysis in data mining and statistics that builds a hierarchy of clusters. There are two main types:

Agglomerative (Bottom-Up) Approach: Each observation starts in its own cluster, and pairs of clusters are merged as one moves up the hierarchy.

Divisive (Top-Down) Approach: All observations start in one cluster, and splits are performed recursively as one moves down the hierarchy. The results are often presented in a dendrogram, a tree-like diagram that shows the arrangement of the clusters produced by the algorithm.

We will discuss these methods in detail in a future note. The following figure illustrates the rough idea of hierarchical clustering.

Hierarchical clustering illustration

Hierarchical clustering illustration


4 Other Methods of Feature Creation

It is quite often to create new feature variables based on existing features through basic algebraic operations and string manipulations. This section introduces a few commonly used methods. Some of the methods are also considered as transformations.

4.1 Extracting Features from Text

In many cases, your integrated data set may have fields containing information such as home address, email address, text of vial labels, etc.

Example of vial label containing dose information

Example of vial label containing dose information

R has many string functions you can use to locate the particular information such as ZIP code in the address, email domain in the email address, dose information in the vial labels, etc. in the text and then extract it to define new feature variables. As an example, the following R code illustrates how to extract the email domain from the email address using sub() or str_extract() functions.

#library(stringr)
# email addresses
emails <- c("user1@example.com", "user2@domain.org", "user3@company.net")

# Extract domains
domains <- sub(".*@", "", emails)  # sub() is a function in the base package
print(domains) # [1] "example.com" "domain.org" "company.net"
[1] "example.com" "domain.org"  "company.net"
# using library {stringr}

# Extract domains
domains <- str_extract(emails, "(?<=@).*")
print(domains) # [1] "example.com" "domain.org" "company.net"
[1] "example.com" "domain.org"  "company.net"

4.2 Extracting Feature from Dates

Extracting features from dates in R can be quite straightforward, especially with the help of the lubridate package. Here are some common tasks and how to accomplish them. The following code illustrates how to use R functions in the lubridate package to extract various pieces of information from date.

#library(lubridate)

# Example date
date <- ymd("2024-08-29")

# Extract year, month, and day
year <- year(date)
month <- month(date)
day <- day(date)

print(year)  # 2024
[1] 2024
print(month) # 8
[1] 8
print(day)   # 29
[1] 29
# extract weekday
weekday <- wday(date, label = TRUE)
print(weekday) # "Thu"
[1] Thu
Levels: Sun < Mon < Tue < Wed < Thu < Fri < Sat
#extracting quarter
quarter <- quarter(date)
print(quarter) # 3
[1] 3
# extracting Day of the Year
day_of_year <- yday(date)
print(day_of_year) # 242
[1] 242
#extracting week of the year
week_of_year <- week(date)
print(week_of_year) # 35
[1] 35

4.3 Feature Scaling/Standardization

Feature scaling, including standardization, is a crucial step in data preprocessing, especially for machine learning models. It ensures that all features contribute equally to the model and prevents features with larger scales from dominating. Here are some common methods for feature scaling in R.

4.3.1 Standardization

Standardization scales the data so that it has a mean of 0 and a standard deviation of 1. This is done using the formula \[ z = \frac{x-\mu}{\sigma} \] where \(x\) is the original feature value, \(\mu\) is the mean of the feature, and \(\sigma\) is the standard deviation. We can also use the scale() function in R to standardize the data. The following is an example of standardization.

# Example data frame
data <- data.frame(Age = rnorm(500, 50, 8), Weight = rnorm(500, 80, 10))

# Standardize the data
data_standardized <- scale(data)
print(head(data_standardized))
             Age     Weight
[1,] -0.51442769  0.6009307
[2,]  0.77869126  2.3227369
[3,] -0.11684497 -0.5497135
[4,]  0.23240093  0.7984363
[5,] -0.05997781 -0.7660397
[6,] -0.07483578  1.0540121

4.3.2 Min-Max Normalization

Min-Max normalization scales the data to a fixed range, usually [0, 1]. This is done using the formula:

\[ x^\prime = \frac{x - \min(x)}{\max(x) - \min(x)} \]

Here’s how we apply Min-Max normalization in R.

# Min-Max normalization function
min.max.norm <- function(x) {
  (x - min(x)) / (max(x) - min(x))
}

# generate data
data <- data.frame(Age = rnorm(500, 50, 8), Weight = rnorm(500, 80, 10))

# Apply to data frame
data.normalized <- as.data.frame(lapply(data, min.max.norm))
print(head(data.normalized))
        Age    Weight
1 0.3616426 0.5777162
2 0.7307436 0.2369332
3 0.4520588 0.4631262
4 0.4973567 0.4774196
5 0.4117000 0.3612147
6 0.5202067 0.3174263

4.3.3 Robust Scaling

Robust scaling uses the median and the interquartile range (IQR) to scale the data, making it less sensitive to outliers. The formula is

\[ x^\prime =\frac{x - \text{median}(x)}{\text{IQR}(x)} \] Here is an example in R of robust scaling in R.

# Robust scaling function
robust.Scaling <- function(x) {
  qq = quantile(x, probs = seq(0, 1, 0.25), na.rm = FALSE, names = TRUE, type = 7)
  (x - qq[3]) / (qq[4] - qq[2])
}

# generate data
data <- data.frame(Age = rnorm(500, 50, 8), Weight = rnorm(500, 80, 10))

# Apply to data frame
data.scaled <- as.data.frame(lapply(data, robust.Scaling))
print(head(data.scaled))
          Age     Weight
1  0.03809158  0.4063677
2 -0.52570332  0.3769409
3 -0.74301152 -1.4864576
4 -0.10542341  0.1613421
5 -0.22350339  0.4746830
6  0.72421617  0.6428771

5 Wrapping Up Feature Engineering

Feature engineering is a part of the data science process as described in the following figure.

Part of data science that resukt in an analytic data set.

Part of data science that resukt in an analytic data set.

Feature engineering is an input-process-out (IPO) model that can be capsulated and organized with a function in R or other programming languages.

Wrapping up feature engineering.

Wrapping up feature engineering.

The idea is to write a function to accept records from the integrated data and produce engineered records to feed candidate models for prediction.

6 Concluding Remarks

It is dependent on specific applications, data experience, technical expertise, and amount of effort, one can engineer features in different ways that result in different accuracy with the same model. This note discussed the basic feature engineering methods that are commonly used in data science projects. We will discuss more feature engineering methods in different applications in the subsequent machine learning course.

Special attention must paid to missing value. Feeding an engineered record with missing components to a predictive model will end up with a missing predicted value. For example, a subset of features were engineered and the rest of the features were not engineered (i.e., the original features were used to feed the final model).

The above-engineered record has no missing component. Feeding this normal engineered record to the final model will result in a normal result. However, if the incoming new records contain missing components, the missing components will be passed to the engineered records.

The above-engineered record still has a missing component. Feeding this engineered record with missing components will generate a missing predicted value. This is practically important since it will prevent the potential failure prediction from a dynamic prediction system!

LS0tDQp0aXRsZTogJ0Jhc2ljIFRlY2huaXF1ZXMgZm9yIEZlYXR1cmUgRW5naW5lZXJpbmcnDQphdXRob3I6ICJDaGVuZyBQZW5nIg0KZGF0ZTogIiBTVEEgNTExIC0gRm91ZGF0aW9ucyBvZiBEYXRhIFNjaWVuY2UiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRvY19jb2xsYXBzZWQ6IHllcw0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIHNtb290aF9zY3JvbGw6IHllcw0KICAgIHRoZW1lOiBsdW1lbg0KICB3b3JkX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICBrZWVwX21kOiB5ZXMNCiAgcGRmX2RvY3VtZW50OiANCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KICAgIGZpZ19jYXB0aW9uOiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIGZpZ193aWR0aDogMw0KICAgIGZpZ19oZWlnaHQ6IDMNCmVkaXRvcl9vcHRpb25zOiANCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQ0KLS0tDQoNCmBgYHs9aHRtbH0NCg0KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4NCg0KLyogQ2FzY2FkaW5nIFN0eWxlIFNoZWV0cyAoQ1NTKSBpcyBhIHN0eWxlc2hlZXQgbGFuZ3VhZ2UgdXNlZCB0byBkZXNjcmliZSB0aGUgcHJlc2VudGF0aW9uIG9mIGEgZG9jdW1lbnQgd3JpdHRlbiBpbiBIVE1MIG9yIFhNTC4gaXQgaXMgYSBzaW1wbGUgbWVjaGFuaXNtIGZvciBhZGRpbmcgc3R5bGUgKGUuZy4sIGZvbnRzLCBjb2xvcnMsIHNwYWNpbmcpIHRvIFdlYiBkb2N1bWVudHMuICovDQoNCmgxLnRpdGxlIHsgIC8qIFRpdGxlIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiB0aGUgcmVwb3J0IHRpdGxlICovDQogIGZvbnQtc2l6ZTogMjRweDsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGNvbG9yOiBEYXJrUmVkOw0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogIGZvbnQtZmFtaWx5OiAiR2lsbCBTYW5zIiwgc2Fucy1zZXJpZjsNCn0NCmg0LmF1dGhvciB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgYXV0aG9ycyAgKi8NCiAgZm9udC1zaXplOiAyMHB4Ow0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogRGFya1JlZDsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCmg0LmRhdGUgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIHRoZSBkYXRlICAqLw0KICBmb250LXNpemU6IDE4cHg7DQogIGZvbnQtZmFtaWx5OiBzeXN0ZW0tdWk7DQogIGNvbG9yOiBEYXJrQmx1ZTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCmgxIHsgLyogSGVhZGVyIDEgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAxIHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAyMnB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCn0NCmgyIHsgLyogSGVhZGVyIDIgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciBsZXZlbCAyIHNlY3Rpb24gdGl0bGUgKi8NCiAgICBmb250LXNpemU6IDIwcHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCiAgICBmb250LXdlaWdodDogYm9sZDsNCn0NCg0KaDMgeyAvKiBIZWFkZXIgMyAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgbGV2ZWwgMyBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMThweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpoNCB7IC8qIEhlYWRlciA0IC0gZm9udCBzcGVjaWZpY2F0aW9ucyBvZiBsZXZlbCA0IHNlY3Rpb24gdGl0bGUgICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBkYXJrcmVkOw0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQp9DQoNCmJvZHkgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9DQoNCi5oaWdobGlnaHRtZSB7IGJhY2tncm91bmQtY29sb3I6eWVsbG93OyB9DQoNCnAgeyBiYWNrZ3JvdW5kLWNvbG9yOndoaXRlOyB9DQoNCjwvc3R5bGU+DQpgYGANCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQojIGNvZGUgY2h1bmsgc3BlY2lmaWVzIHdoZXRoZXIgdGhlIFIgY29kZSwgd2FybmluZ3MsIGFuZCBvdXRwdXQgDQojIHdpbGwgYmUgaW5jbHVkZWQgaW4gdGhlIG91dHB1dCBmaWxlcy4NCmlmICghcmVxdWlyZSgia25pdHIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygia25pdHIiKQ0KICAgbGlicmFyeShrbml0cikNCn0NCmlmICghcmVxdWlyZSgidGlkeXZlcnNlIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpDQogICBsaWJyYXJ5KHRpZHl2ZXJzZSkNCn0NCmlmICghcmVxdWlyZSgiZ2dwbG90MiIpKSB7DQogICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCiAgIGxpYnJhcnkoZ2dwbG90MikNCn0gIA0KaWYgKCFyZXF1aXJlKCJmYWN0b2V4dHJhIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImZhY3RvZXh0cmEiKQ0KICAgbGlicmFyeShmYWN0b2V4dHJhKQ0KfQ0KaWYgKCFyZXF1aXJlKCJjbHVzdGVyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoImNsdXN0ZXIiKQ0KICAgbGlicmFyeShjbHVzdGVyKQ0KfQ0KaWYgKCFyZXF1aXJlKCJsdWJyaWRhdGUiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygibHVicmlkYXRlIikNCiAgIGxpYnJhcnkobHVicmlkYXRlKQ0KfQ0KaWYgKCFyZXF1aXJlKCJzdHJpbmdyIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInN0cmluZ3IiKQ0KICAgbGlicmFyeShzdHJpbmdyKQ0KfQ0KDQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsICAgICAgICMgaW5jbHVkZSBjb2RlIGNodW5rIGluIHRoZSBvdXRwdXQgZmlsZQ0KICAgICAgICAgICAgICAgICAgICAgIHdhcm5pbmdzID0gRkFMU0UsICAjIHNvbWV0aW1lcywgeW91IGNvZGUgbWF5IHByb2R1Y2Ugd2FybmluZyBtZXNzYWdlcywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyB5b3UgY2FuIGNob29zZSB0byBpbmNsdWRlIHRoZSB3YXJuaW5nIG1lc3NhZ2VzIGluDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgdGhlIG91dHB1dCBmaWxlLiANCiAgICAgICAgICAgICAgICAgICAgICByZXN1bHRzID0gVFJVRSwgICAgIyB5b3UgY2FuIGFsc28gZGVjaWRlIHdoZXRoZXIgdG8gaW5jbHVkZSB0aGUgb3V0cHV0DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgaW4gdGhlIG91dHB1dCBmaWxlLg0KICAgICAgICAgICAgICAgICAgICAgIG1lc3NhZ2UgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgICAgICBjb21tZW50ID0gTkENCiAgICAgICAgICAgICAgICAgICAgICApICANCmBgYA0KDQoNClxuZXdwYWdlDQoNCiMgSW50cm9kdWN0aW9uDQoNClJlY2FsbCB0aGUgZm9sbG93aW5nIHdvcmtmbG93IG9mIHRoZSBkYXRhIHNjaWVuY2UgcHJvamVjdC4NCg0KYGBge3IgZWNobyA9IEZBTFNFLCBmaWcuYWxpZ249J2NlbnRlcicsIG91dC53aWR0aD0iNzAlIiwgZmlnLmNhcD0iRGF0YSBTY2llbmNlIFByb2Nlc3MifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL0RTLXByb2plY3Qtd29ya2Zsb3cucG5nIikNCmBgYA0KDQoNCioqRmVhdHVyZSBlbmdpbmVlcmluZyoqIGlzIGNvbnN0cnVjdGluZyBuZXcgaW5wdXQgZmVhdHVyZXMgZnJvbSB0aGUgZXhpc3RpbmcgcmF3IGRhdGEgdG8gYmV0dGVyIHJlcHJlc2VudCB0aGUgdW5kZXJseWluZyBwcm9ibGVtIHRvIHByZWRpY3RpdmUgbW9kZWxzLCByZXN1bHRpbmcgaW4gaW1wcm92ZWQgbW9kZWwgYWNjdXJhY3kgb24gdW5zZWVuIGRhdGEuIEFzIHBhcnQgb2YgdGhlIG1hY2hpbmUgbGVhcm5pbmcgcGlwZWxpbmUsIGZlYXR1cmUgZW5naW5lZXJpbmcgY29tZXMgYWZ0ZXIgZGF0YSBjb2xsZWN0aW9uLCBjbGVhbmluZywgYW5kIEVEQSwgYnV0IGJlZm9yZSBtb2RlbCB0cmFpbmluZyBhbmQgZXZhbHVhdGlvbi4NCg0KSW4gdGhlIHByZXZpb3VzIG5vdGUsIHdlIGludHJvZHVjZWQgRURBIHdpdGggdmlzdWFsIGFpZHMgdG8gZXh0cmFjdCBwYXR0ZXJucyBzdWNoIGFzIGRpc3RyaWJ1dGlvbiwgY2x1c3RlcnMsIGFzc29jaWF0aW9uLCBtaXNzaW5nIHZhbHVlcywgZXRjLiBUaGVzZSBwYXR0ZXJucyBjYW4gYmUgdXNlZCBhcyBiYXNlcyB0byBjcmVhdGUgZmVhdHVyZXMgdG8gaW1wcm92ZSB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIHN1YnNlcXVlbnQgbW9kZWxpbmcgYW5kIGFuYWx5c2VzLg0KDQpJbiB0aGlzIG5vdGUsIHdlIHdpbGwgZXhwbG9yZSBzb21lIHRlY2huaXF1ZXMgdGhhdCBhcmUgdXNlZCB0byBlbmdpbmVlciBpbXBhY3RmdWwgZmVhdHVyZXMuIFByb3Blcmx5IGFwcGx5aW5nIHRoZXNlIHRlY2huaXF1ZXMgZW5hYmxlcyBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMgdG8gbGVhcm4gbW9yZSByb2J1c3QgcGF0dGVybnMsIGF2b2lkIG92ZXItZml0dGluZywgYW5kIGVuaGFuY2UgcHJlZGljdGl2ZSBhY2N1cmFjeS4gVGhlIGtleSBzdGVwcyBpbiBmZWF0dXJlIGVuZ2luZWVyaW5nIGluY2x1ZGUNCg0KKiAqKkRhdGEgRXhwbG9yYXRpb24gYW5kIFVuZGVyc3RhbmRpbmcqKjogRXhwbG9yZSBhbmQgdW5kZXJzdGFuZCB0aGUgZGF0YXNldCwgaW5jbHVkaW5nIHRoZSB0eXBlcyBvZiBmZWF0dXJlcyBhbmQgdGhlaXIgZGlzdHJpYnV0aW9ucy4gVW5kZXJzdGFuZGluZyB0aGUgc2hhcGUgb2YgdGhlIGRhdGEgaXMga2V5Lg0KDQoqICoqSGFuZGxpbmcgTWlzc2luZyBEYXRhKio6IEFkZHJlc3MgbWlzc2luZyB2YWx1ZXMgdGhyb3VnaCBpbXB1dGF0aW9uIG9yIHJlbW92YWwgb2YgaW5zdGFuY2VzIG9yIGZlYXR1cmVzIHdpdGggbWlzc2luZyBkYXRhLiBUaGVyZSBhcmUgbWFueSBhbGdvcml0aG1pYyBhcHByb2FjaGVzIHRvIGhhbmRsaW5nIG1pc3NpbmcgZGF0YS4NCg0KKiAqKlZhcmlhYmxlIEVuY29kaW5nKio6IENvbnZlcnQgY2F0ZWdvcmljYWwgdmFyaWFibGVzIGludG8gYSBudW1lcmljYWwgZm9ybWF0IHN1aXRhYmxlIGZvciBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMgdXNpbmcgbWV0aG9kcy4NCg0KKiAqKkZlYXR1cmUgU2NhbGluZyoqOiBTdGFuZGFyZGl6ZSBvciBub3JtYWxpemUgbnVtZXJpY2FsIGZlYXR1cmVzIHRvIGVuc3VyZSB0aGV5IGFyZSBvbiBhIHNpbWlsYXIgc2NhbGUsIGltcHJvdmluZyBtb2RlbCBwZXJmb3JtYW5jZS4NCg0KKiAqKkZlYXR1cmUgQ3JlYXRpb24qKjogR2VuZXJhdGUgbmV3IGZlYXR1cmVzIGJ5IGNvbWJpbmluZyBleGlzdGluZyBvbmVzIHRvIGNhcHR1cmUgcmVsYXRpb25zaGlwcyBiZXR3ZWVuIHZhcmlhYmxlcy4NCg0KKiAqKkhhbmRsaW5nIE91dGxpZXJzKio6IElkZW50aWZ5IGFuZCBhZGRyZXNzIG91dGxpZXJzIGluIHRoZSBkYXRhIHRocm91Z2ggdGVjaG5pcXVlcyBsaWtlIHRyaW1taW5nIG9yIHRyYW5zZm9ybWluZyB0aGUgZGF0YS4NCg0KKiAqKk5vcm1hbGl6YXRpb24qKjogTm9ybWFsaXplIGZlYXR1cmVzIHRvIGJyaW5nIHRoZW0gdG8gYSBjb21tb24gc2NhbGUsIGltcG9ydGFudCBmb3IgYWxnb3JpdGhtcyBzZW5zaXRpdmUgdG8gZmVhdHVyZSBtYWduaXR1ZGVzLg0KDQoqICoqQmlubmluZyBvciBEaXNjcmV0aXphdGlvbioqOiBDb252ZXJ0IGNvbnRpbnVvdXMgZmVhdHVyZXMgaW50byBkaXNjcmV0ZSBiaW5zIHRvIGNhcHR1cmUgc3BlY2lmaWMgcGF0dGVybnMgaW4gY2VydGFpbiByYW5nZXMuDQoNCiogKipUZXh0IERhdGEgUHJvY2Vzc2luZyoqOiBJZiBkZWFsaW5nIHdpdGggdGV4dCBkYXRhLCBwZXJmb3JtIHRhc2tzIHN1Y2ggYXMgdG9rZW5pemF0aW9uLCBzdGVtbWluZywgYW5kIHJlbW92aW5nIHN0b3Agd29yZHMuDQoNCiogKipUaW1lIFNlcmllcyBGZWF0dXJlcyoqOiBFeHRyYWN0IHJlbGV2YW50IHRpbWUtYmFzZWQgZmVhdHVyZXMgc3VjaCBhcyBsYWcgZmVhdHVyZXMgb3Igcm9sbGluZyBzdGF0aXN0aWNzIGZvciB0aW1lIHNlcmllcyBkYXRhLg0KDQoqICoqVmVjdG9yIEZlYXR1cmVzKio6IFZlY3RvciBmZWF0dXJlcyBhcmUgY29tbW9ubHkgdXNlZCBmb3IgdHJhaW5pbmcgaW4gbWFjaGluZSBsZWFybmluZy4gSW4gbWFjaGluZSBsZWFybmluZywgZGF0YSBpcyByZXByZXNlbnRlZCBpbiB0aGUgZm9ybSBvZiBmZWF0dXJlcywgYW5kIHRoZXNlIGZlYXR1cmVzIGFyZSBvZnRlbiBvcmdhbml6ZWQgaW50byB2ZWN0b3JzLiANCg0KKiAqKkZlYXR1cmUgU2VsZWN0aW9uKio6IElkZW50aWZ5IGFuZCBzZWxlY3QgdGhlIG1vc3QgcmVsZXZhbnQgZmVhdHVyZXMgdG8gaW1wcm92ZSBtb2RlbCBpbnRlcnByZXRhYmlsaXR5IGFuZCBlZmZpY2llbmN5IHVzaW5nIHRlY2huaXF1ZXMgbGlrZSB1bml2YXJpYXRlIGZlYXR1cmUgc2VsZWN0aW9uIG9yIHJlY3Vyc2l2ZSBmZWF0dXJlIGVsaW1pbmF0aW9uLg0KDQoqICoqRmVhdHVyZSBFeHRyYWN0aW9uKio6IEZlYXR1cmUgZXh0cmFjdGlvbiBhaW1zIHRvIHJlZHVjZSBkYXRhIGNvbXBsZXhpdHkgKG9mdGVuIGtub3duIGFzIOKAnGRhdGEgZGltZW5zaW9uYWxpdHnigJ0pIHdoaWxlIHJldGFpbmluZyBhcyBtdWNoIHJlbGV2YW50IGluZm9ybWF0aW9uIGFzIHBvc3NpYmxlLiBUaGlzIGhlbHBzIHRvIGltcHJvdmUgdGhlIHBlcmZvcm1hbmNlIGFuZCBlZmZpY2llbmN5IG9mIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcyBhbmQgc2ltcGxpZnkgdGhlIGFuYWx5c2lzIHByb2Nlc3MuIEZlYXR1cmUgZXh0cmFjdGlvbiBtYXkgaW52b2x2ZSB0aGUgY3JlYXRpb24gb2YgbmV3IGZlYXR1cmVzICjigJxmZWF0dXJlIGVuZ2luZWVyaW5n4oCdKSBhbmQgZGF0YSBtYW5pcHVsYXRpb24gdG8gc2VwYXJhdGUgYW5kIHNpbXBsaWZ5IHRoZSB1c2Ugb2YgbWVhbmluZ2Z1bCBmZWF0dXJlcyBmcm9tIGlycmVsZXZhbnQgb25lcy4gQ3JlYXRlIG5ldyBmZWF0dXJlcyBvciByZWR1Y2UgZGltZW5zaW9uYWxpdHkgdXNpbmcgdGVjaG5pcXVlcyBzdWNoIGFzICpQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzIChQQ0EpKi4gDQoNCiogKipDcm9zcy12YWxpZGF0aW9uKio6IHNlbGVjdGluZyBmZWF0dXJlcyBiZWZvcmUgY3Jvc3MtdmFsaWRhdGlvbiBjYW4gaW50cm9kdWNlIHNpZ25pZmljYW50IGJpYXMuIEV2YWx1YXRlIHRoZSBpbXBhY3Qgb2YgZmVhdHVyZSBlbmdpbmVlcmluZyBvbiBtb2RlbCBwZXJmb3JtYW5jZSB1c2luZyBjcm9zcy12YWxpZGF0aW9uIHRlY2huaXF1ZXMuDQoNCg0KPGZvbnQgY29sb3IgPSAicmVkIj4qXGNvbG9ye3JlZH1GZWF0dXJlIGVuZ2luZWVyaW5nIGlzIG9mdGVuIGFuIGl0ZXJhdGl2ZSBwcm9jZXNzOyBldmFsdWF0ZSwgcmVmaW5lLCBhbmQgcmVwZWF0IGFzIG5lZWRlZCB0byBhY2hpZXZlIG9wdGltYWwgcmVzdWx0cy4gVGhlc2Ugc3RlcHMgbWF5IHZhcnkgYmFzZWQgb24gdGhlIG5hdHVyZSBvZiB0aGUgZGF0YSBhbmQgdGhlIG1hY2hpbmUgbGVhcm5pbmcgdGFzaywgYnV0IGNvbGxlY3RpdmVseSwgdGhleSBjb250cmlidXRlIHRvIGNyZWF0aW5nIGEgcm9idXN0IGFuZCBlZmZlY3RpdmUgZmVhdHVyZSBzZXQgZm9yIG1vZGVsIHRyYWluaW5nLio8L2ZvbnQ+DQoNClNvbWUgb2YgdGhlIGFib3ZlIHRhc2tzIGFyZSBpbml0aWF0ZWQgZnJvbSBleHBsb3JhdG9yeSBkYXRhIGFuYWx5c2lzIHdoaWxlIG90aGVycyBjYW4gYmUgaW5pdGlhdGVkIGZyb20gbW9kZWxpbmcuIEluIHRoZSBuZXh0IGZldyBzZWN0aW9ucywgd2Ugd2lsbCBkaXNjdXNzIGVhY2ggb2YgdGhlIGFib3ZlIGZlYXR1cmUgZW5naW5lZXJpbmcgdGFza3Mgd2l0aCBzb21lIGV4YW1wbGVzLiBBIGNhc2Ugc3R1ZHkgd2l0aCBFREEgYW5kIGZlYXR1cmUgZW5naW5lZXJpbmcgd2l0aCBzb21lIGlsbHVzdHJhdGl2ZSBleGFtcGxlcy4NCg0KDQojIEVEQS1iYXNlZCBGZWF0dXJlIEVuZ2luZWVyaW5nIA0KDQpXZSBoYXZlIGludHJvZHVjZWQgRURBIHRvIGlkZW50aWZ5IHBhdHRlcm5zIHN1Y2ggYXMgZGlzdHJpYnV0aW9uLCByZWxhdGlvbnNoaXBzLCBtaXNzaW5nIHZhbHVlcywgZXRjLiBXaGVuIGJ1aWxkaW5nIG1vZGVscyBhbmQgaW1wbGVtZW50aW5nIGFsZ29yaXRobXMsIHdlIG5lZWQgdG8gYXNzZXNzIHRoZSBhc3N1bXB0aW9uIGJhc2VkIG9uIHdoaWNoIHRoZSBtb2RlbHMgYW5kIGFsZ29yaXRobXMgd2VyZSBkZXZlbG9wZWQuIEZvciBleGFtcGxlLCBleHRyZW1lbHkgc2tld2VkIG9yIG11bHRpLW1vZGFsIHByZWRpY3RvciB2YXJpYWJsZXMgbWF5IGNhdXNlIGluYWNjdXJhdGUgcHJlZGljdGlvbiwgc3BhcnNlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyAoc29tZSBjYXRlZ29yaWVzIGhhdmUgZXh0cmVtZWx5IHNtYWxsIGNvdW50cykgY2F1c2UgdW5zdGFibGUgcHJlZGljdGlvbiwgZXRjLiBXZSBuZWVkIHRvIHVzZSB2YXJpb3VzIHRlY2huaXF1ZXMgdG8gY3JlYXRlIG5ldyBmZWF0dXJlcyB0byBpbXByb3ZlIHRoZSBwZXJmb3JtYW5jZSBvZiBtb2RlbGluZy4gDQoNCiMjIEhhbmRsaW5nIE1pc3NpbmcgVmFsdWVzDQoNCkltcHV0aW5nIG1pc3NpbmcgdmFsdWVzIGlzIGEgY3J1Y2lhbCBzdGVwIGluIGRhdGEgcHJlLXByb2Nlc3NpbmcsIGVzcGVjaWFsbHkgZm9yIG1hY2hpbmUgbGVhcm5pbmcgYW5kIHN0YXRpc3RpY2FsIGFuYWx5c2lzLiBUaGVyZSBhcmUgbWFueSBpbXB1dGF0aW9uIG1ldGhvZHMuIEhlcmUgYXJlIHNvbWUgY29tbW9uIG1ldGhvZHMgZm9yIGhhbmRsaW5nIG1pc3NpbmcgZGF0YToNCg0KIyMjIFNpbXBsZSBJbXB1dGF0aW9uDQoNCioqTWVhbi9NZWRpYW4vTW9kZSBJbXB1dGF0aW9uKio6IFJlcGxhY2UgbWlzc2luZyB2YWx1ZXMgd2l0aCB0aGUgbWVhbiwgbWVkaWFuLCBvciBtb2RlIG9mIHRoZSBjb2x1bW4uIFRoaXMgaXMgc3RyYWlnaHRmb3J3YXJkIGJ1dCBjYW4gaW50cm9kdWNlIGJpYXMuDQoNCioqQ29uc3RhbnQgVmFsdWUgSW1wdXRhdGlvbioqOiBSZXBsYWNlIG1pc3NpbmcgdmFsdWVzIHdpdGggYSBjb25zdGFudCB2YWx1ZSwgc3VjaCBhcyAwIG9yIC0xLg0KDQpUaGUgYWJvdmUgdHdvIG1ldGhvZHMgYXJlIHNpbXBsZSB0byBpbXBsZW1lbnQgYnV0IHBlcmZvcm0gcG9vcmx5IA0KDQojIyMgTW9kZWwtYmFzZWQgSW1wdXRhdGlvbg0KDQoqKkstTmVhcmVzdCBOZWlnaGJvcnMgKEtOTikgSW1wdXRhdGlvbioqOiBVc2UgdGhlIHZhbHVlcyBmcm9tIHRoZSBuZWFyZXN0IG5laWdoYm9ycyB0byBpbXB1dGUgbWlzc2luZyBkYXRhLiBUaGlzIG1ldGhvZCBjYW4gY2FwdHVyZSBsb2NhbCBwYXR0ZXJucyBpbiB0aGUgZGF0YS4gVGhlIGZvbGxvd2luZyBmaWd1cmUgZXhwbGFpbnMgaG93IEtOTiBpbXB1dGF0aW9uIHdvcmtzLg0KDQoNCmBgYHtyIGVjaG8gPSBGQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjQwJSIsIGZpZy5jYXA9IkstbmVhcmVzdCBuZWlnaGJvcmhvb2QgaW1wdXRhdGlvbiJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvS05OLUltcHV0YXRpb24ucG5nIikNCmBgYA0KDQpUaGUgaWRlYSBpcyB0byByZXBsYWNlIHRoZSBtaXNzaW5nIGNvbXBvbmVudHMgd2l0aCB0aGUgY29ycmVzcG9uZGluZyBjb21wb25lbnRzIG9mIHRoZSAqKm5lYXJlc3QgcG9pbnQqKi4gSW4gdGhlIGFib3ZlIGZpZ3VyZS4gdGhlIG9yYW5nZSBwb2ludCBoYXMgYXQgbGVhc3Qgb25lIG1pc3NpbmcgY29tcG9uZW50LCBhbmQgdGhlIHJlZCB0cmlhbmdsZSBpcyB0aGUgbmVhcmVzdCBwb2ludC4gV2UgcmVwbGFjZSB0aGUgbWlzc2luZyBjb21wb25lbnQocykgb2YgdGhlIG9yYW5nZSBwb2ludCBzaG91bGQgYmUgcmVwbGFjZWQgd2l0aCB0aGUgY29ycmVzcG9uZGluZyBub24tbWlzc2luZyBjb21wb25lbnQocykgaW4gdGhlICoqcmVkIHRyaWFuZ2xlIHBvaW50KiogYmFzZWQgb24gdGhlIEtOTiBpbXB1dGF0aW9uIG1ldGhvZC4gDQoNCg0KKipNdWx0aXZhcmlhdGUgSW1wdXRhdGlvbiBieSBDaGFpbmVkIEVxdWF0aW9ucyAoTUlDRSkqKjogSXRlcmF0aXZlbHkgaW1wdXRlcyBtaXNzaW5nIHZhbHVlcyBieSBtb2RlbGluZyBlYWNoIGZlYXR1cmUgd2l0aCBtaXNzaW5nIHZhbHVlcyBhcyBhIGZ1bmN0aW9uIG9mIG90aGVyIGZlYXR1cmVzLiBXZSB3aWxsIG5vdCBkaXNjdXNzIHRoZSB0ZWNobmljYWwgZGV2ZWxvcG1lbnQgb2YgdGhpcyBtZXRob2QuIFRoZSBSIGxpYnJhcnkgKiptaWNlKiogY2FuIGJlIHVzZWQgdG8gaW1wbGVtZW50IE1JQ0UgaW1wdXRhdGlvbi4NCg0KDQojIyMgUmVncmVzc2lvbiBJbXB1dGF0aW9uDQoNCioqUmVncmVzc2lvbiBJbXB1dGF0aW9uKio6IFVzZXMgcmVncmVzc2lvbiBtb2RlbHMgdG8gZXN0aW1hdGUgcmVwbGFjZW1lbnRzIGZvciBtaXNzaW5nIHZhbHVlcyBiYXNlZCBvbiBjb3JyZWxhdGlvbnMgd2l0aCBvdGhlciBmZWF0dXJlcy4gVGhpcyBtZXRob2QgYXNzdW1lcyB0aGF0IHRoZXJlIGlzIG9uZSBvciBtb3JlIHZhcmlhYmxlcyB0aGF0IGFyZSBjb3JyZWxhdGVkLiBUaGUgcHJvY2VzcyBvZiByZWdyZXNzaW9uIGltcHV0YXRpb24gaW52b2x2ZXMgdGhlIGZvbGxvd2luZyBzdGVwczoNCg0KKipTdGVwIDEqKjogVGhlIGZpcnN0IHN0ZXAgaW4gcmVncmVzc2lvbiBpbXB1dGF0aW9uIGlzIHRvIGlkZW50aWZ5IHRoZSB2YXJpYWJsZXMgd2l0aCBtaXNzaW5nIGRhdGEuIE9uY2UgdGhlc2UgdmFyaWFibGVzIGhhdmUgYmVlbiBpZGVudGlmaWVkLCB0aGV5IGNhbiBiZSB1c2VkIGFzIHRoZSBkZXBlbmRlbnQgdmFyaWFibGVzIGluIHRoZSByZWdyZXNzaW9uIG1vZGVsLiANCg0KKipTdGVwIDIqKjogVGhlIG5leHQgc3RlcCBpcyB0byBpZGVudGlmeSB0aGUgdmFyaWFibGVzIHRoYXQgY2FuIGJlIHVzZWQgYXMgaW5kZXBlbmRlbnQgdmFyaWFibGVzIGluIHRoZSByZWdyZXNzaW9uIG1vZGVsLiBUaGVzZSB2YXJpYWJsZXMgc2hvdWxkIGJlIHN0cm9uZ2x5IGNvcnJlbGF0ZWQgd2l0aCB0aGUgZGVwZW5kZW50IHZhcmlhYmxlIGFuZCBzaG91bGQgbm90IGhhdmUgbWlzc2luZyB2YWx1ZXMgdGhlbXNlbHZlcy4NCg0KKipTdGVwIDMqKjogRml0IGEgcmVncmVzc2lvbiBtb2RlbCBPbmNlIHRoZSB2YXJpYWJsZXMgaGF2ZSBiZWVuIGlkZW50aWZpZWQsIGEgcmVncmVzc2lvbiBtb2RlbCBjYW4gYmUgZml0IHVzaW5nIHRoZSBhdmFpbGFibGUgZGF0YS4gVGhlIHJlZ3Jlc3Npb24gbW9kZWwgY2FuIGJlIGFueSBhcHByb3ByaWF0ZSByZWdyZXNzaW9uIG1vZGVsLCBzdWNoIGFzIGxpbmVhciByZWdyZXNzaW9uIG9yIGxvZ2lzdGljIHJlZ3Jlc3Npb24uDQoNCioqU3RlcCA0Kio6IFVzZSB0aGUgcmVncmVzc2lvbiBtb2RlbCB0byBpbXB1dGUgbWlzc2luZyB2YWx1ZXMgRmluYWxseSwgdGhlIHJlZ3Jlc3Npb24gbW9kZWwgY2FuIGJlIHVzZWQgdG8gcHJlZGljdCB0aGUgbWlzc2luZyB2YWx1ZXMuIFRoZSBwcmVkaWN0ZWQgdmFsdWVzIGNhbiBiZSBzdWJzdGl0dXRlZCBmb3IgdGhlIG1pc3NpbmcgdmFsdWVzIGluIHRoZSBkYXRhIHNldC4NCg0KDQojIyBEaXNjcmV0aXphdGlvbg0KDQpEaXNjcmV0aXphdGlvbiBpcyBhIHRlY2huaXF1ZSBpbiBmZWF0dXJlIGVuZ2luZWVyaW5nIHdoZXJlIGNvbnRpbnVvdXMgdmFyaWFibGVzIGFyZSBjb252ZXJ0ZWQgaW50byBkaXNjcmV0ZSBpbnRlcnZhbHMgb3IgY2F0ZWdvcmllcy4gVGhpcyBjYW4gc2ltcGxpZnkgdGhlIGZlYXR1cmUgc3BhY2UgYW5kIHNvbWV0aW1lcyBpbXByb3ZlIG1vZGVsIHBlcmZvcm1hbmNlLCBlc3BlY2lhbGx5IGZvciBhbGdvcml0aG1zIHRoYXQgaGFuZGxlIGNhdGVnb3JpY2FsIGRhdGEgYmV0dGVyLiBIZXJlIGFyZSBzb21lIGNvbW1vbiBtZXRob2RzIG9mIGRpc2NyZXRpemF0aW9uLg0KDQoNCiMjIyBFcXVhbCBXaWR0aCBCaW5uaW5nDQoNCkVxdWFsIHdpZHRoIGJpbm5pbmcgZGl2aWRlcyB0aGUgcmFuZ2Ugb2YgdGhlIHZhcmlhYmxlIGludG8gaW50ZXJ2YWxzIG9mIGVxdWFsIHdpZHRoLiBUaGUgZm9sbG93aW5nIHRveSBleGFtcGxlIGRlbW9uc3RyYXRlcyB0aGlzIG1ldGhvZC4NCg0KYGBge3J9DQojIFNhbXBsZSBkYXRhDQpkYXRhIDwtIGRhdGEuZnJhbWUodmFsdWUgPSBjKDEuMSwgMi4zLCAzLjUsIDQuNywgNS45KSkNCg0KIyBFcXVhbCB3aWR0aCBiaW5uaW5nDQpkYXRhJGJpbiA8LSBjdXQoZGF0YSR2YWx1ZSwgYnJlYWtzID0gMywgbGFiZWxzID0gYygiTG93IiwgIk1lZGl1bSIsICJIaWdoIikpDQpwcmludChkYXRhKQ0KYGBgDQoNCg0KIyMjIEVxdWFsIEZyZXF1ZW5jeSBCaW5uaW5nDQoNCkVxdWFsIGZyZXF1ZW5jeSBiaW5uaW5nIGRpdmlkZXMgdGhlIGRhdGEgaW50byBpbnRlcnZhbHMgdGhhdCBjb250YWluIGFwcHJveGltYXRlbHkgdGhlIHNhbWUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucy4gVGhpcyBtZXRob2QgaXMgc29tZXRpbWVzIHVzZWQgdG8gZGV2ZWxvcCBhIGNoaS1zcXVhcmVkIHRlc3QgZm9yIHRoZSBnb29kbmVzcy1vZi1maXQgdGVzdC4gVGhlIGZvbGxvd2luZyBpcyBhIHNpbXBsZSBleGFtcGxlIHdpdGggUiBjb2RlLg0KDQpgYGB7cn0NCiMgU2FtcGxlIGRhdGENCmRhdGEgPC0gZGF0YS5mcmFtZSh2YWx1ZSA9IGMoMS4xLCAyLjMsIDMuNSwgNC43LCA1LjkpKQ0KDQojIEVxdWFsIGZyZXF1ZW5jeSBiaW5uaW5nDQpkYXRhJGJpbiA8LSBjdXQoZGF0YSR2YWx1ZSwgYnJlYWtzID0gcXVhbnRpbGUoZGF0YSR2YWx1ZSwgcHJvYnMgPSBzZXEoMCwgMSwgYnkgPSAxLzMpKSwgDQogICAgICAgICAgICAgICAgaW5jbHVkZS5sb3dlc3QgPSBUUlVFLCBpbmNsdWRlLmhpZ2hlc3QgPSBUUlVFLCBsYWJlbHMgPSBjKCJMb3ciLCAiTWVkaXVtIiwgIkhpZ2giKSkNCnByaW50KGRhdGEpDQpgYGANCg0KIyMjIE90aGVyIEJpbm5pbmcgTWV0aG9kcw0KDQoqKlVuZXF1YWwgV2lkdGggQmlubmluZyoqOiBJbiBzb21lIGFwcGxpY2F0aW9ucywgdW5lcXVhbCBiaW5uaW5nIGlzIHVzZWQgYmFzZWQgb24gcHJhY3RpY2FsIGNvbnNpZGVyYXRpb25zLiBGb3IgZXhhbXBsZSwgaW4gY2xpbmljYWwgc3R1ZGllcywgYWdlIGlzIHVzdWFsbHkgZGlzY3JldGl6ZWQgYmFzZWQgb24gY2xpbmljYWwgY29uc2lkZXJhdGlvbnMuDQoNCioqQWxnb3JpdGhtLWJhc2VkIERpc2NyZXRpemF0aW9uKiogLSB0aGVyZSBhcmUgc2V2ZXJhbCBtYWNoaW5lIGxlYXJuaW5nIGFsZ29yaXRobXMgc3VjaCBhcyBrLW1lYW4gYW5kIGRlY2lzaW9uIHRyZWVzIGNhbiBhbHNvIGJlIHVzZWQgZm9yIGRpc2NyZXRpemluZyBjb250aW51b3VzIHZhcmlhYmxlcy4NCg0KDQojIyBWYXJpYWJsZSB0cmFuc2Zvcm1hdGlvbg0KDQpGZWF0dXJlIHRyYW5zZm9ybWF0aW9uIGlzIGEga2V5IHBhcnQgb2YgZmVhdHVyZSBlbmdpbmVlcmluZyBpbiBtYWNoaW5lIGxlYXJuaW5nLiBEZXBlbmRpbmcgb24gdGhlIG1vZGVscyBhbmQgYWxnb3JpdGhtcyB0byBiZSB1c2VkIGFuZCB0aGUgcGF0dGVybnMgb2JzZXJ2ZWQgaW4gRURBLCBpdCBpbnZvbHZlcyBkaWZmZXJlbnQgdHJhbnNmb3JtYXRpb25zIHRvIGNvbnZlcnQgcmF3IGRhdGEgaW50byBhIGZvcm1hdCB0aGF0IGlzIG1vcmUgc3VpdGFibGUgZm9yIG1vZGVsIHRyYWluaW5nIGFuZCBwcmVkaWN0aW9uLiBIZXJlIGFyZSBzb21lIGNvbW1vbiB0eXBlcyBvZiBmZWF0dXJlIHRyYW5zZm9ybWF0aW9uczoNCg0KIyMjIENvbnZlbnRpb25hbCBUcmFuc2Zvcm1hdGlvbnMNCg0KKipMb2cgVHJhbnNmb3JtYXRpb24qKjogQXBwbHlpbmcgYSBsb2dhcml0aG1pYyBmdW5jdGlvbiB0byBza2V3ZWQgZGF0YSB0byBtYWtlIGl0IG1vcmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQuIFRoaXMgaXMgcGFydGljdWxhcmx5IHVzZWZ1bCBmb3IgZGF0YSB3aXRoIGV4cG9uZW50aWFsIGdyb3d0aCBwYXR0ZXJucy4NCg0KKipQb2x5bm9taWFsIEZlYXR1cmVzKio6IENyZWF0aW5nIG5ldyBmZWF0dXJlcyBieSByYWlzaW5nIGV4aXN0aW5nIGZlYXR1cmVzIHRvIGEgcG93ZXIuIFRoaXMgY2FuIGhlbHAgY2FwdHVyZSBub24tbGluZWFyIHJlbGF0aW9uc2hpcHMgaW4gdGhlIGRhdGEuDQoNCg0KIyMjIEJveC1Db3ggVHJhbnNmb3JtYXRpb24NCg0KKipCb3gtY294IFRyYW5zZm9ybWF0aW9uKio6IFRoZSBCb3gtY294IHRyYW5zZm9ybWF0aW9uIGlzIGEgc3RhdGlzdGljYWwgdGVjaG5pcXVlIHVzZWQgdG8gdHJhbnNmb3JtIG5vbi1ub3JtYWwgZGVwZW5kZW50IHZhcmlhYmxlcyBpbnRvIGEgbm9ybWFsIGRpc3RyaWJ1dGlvbi4gVGhpcyB0cmFuc2Zvcm1hdGlvbiBpcyBwYXJ0aWN1bGFybHkgdXNlZnVsIGluIGltcHJvdmluZyB0aGUgYWNjdXJhY3kgb2YgcHJlZGljdGlvbnMgbWFkZSB1c2luZyBsaW5lYXIgcmVncmVzc2lvbiBhbmQgb3RoZXIgc3RhdGlzdGljYWwgbW9kZWxzIHRoYXQgYXNzdW1lIG5vcm1hbGl0eS4gVGhlIHRyYW5zZm9ybWF0aW9uIHRha2VzIHRoZSBmb2xsb3dpbmcgZm9ybQ0KDQokJA0KeV97XHRleHR7bmV3fX0gPSBcbGVmdFx7DQogICBcYmVnaW57YXJyYXl9e2xjbH0NCiAgIFxmcmFje3leXGxhbWJkYSAtMX17XGxhbWJkYX0gJiBcdGV4dHtpZn0gJiBcbGFtYmRhIFxuZSAwIFxcDQogICBcbG9nKHkpICYgXHRleHR7aWZ9ICYgXGxhbWJkYSA9IDAuDQogICBcZW5ke2FycmF5fQ0KIFxyaWdodC4NCiQkDQpQYXJhbWV0ZXIgJFxsYW1iZGEkIGlzIGVzdGltYXRlZCBmcm9tIHRoZSBkYXRhLiBXZSBjYW4gc2VlIHRoYXQgdGhlIGxvZyB0cmFuc2Zvcm1hdGlvbiBpcyBhIHNwZWNpYWwgQm94LWNveCB0cmFuc2Zvcm1hdGlvbi4NCg0KVGhlIEJveC1jb3ggdHJhbnNmb3JtYXRpb24gaXMgdXNlZCBmb3IgDQoNCioqTm9ybWFsaXphdGlvbioqOiBJdCBoZWxwcyBpbiB0cmFuc2Zvcm1pbmcgc2tld2VkIGRhdGEgdG8gcmVzZW1ibGUgYSBub3JtYWwgZGlzdHJpYnV0aW9uLg0KDQoqKlZhcmlhbmNlIFN0YWJpbGl6YXRpb24qKjogSXQgY2FuIHN0YWJpbGl6ZSB2YXJpYW5jZSBhY3Jvc3MgbGV2ZWxzIG9mIHRoZSBkYXRhLg0KDQoqKkltcHJvdmluZyBNb2RlbCBQZXJmb3JtYW5jZSoqOiBCeSBtZWV0aW5nIHRoZSBhc3N1bXB0aW9ucyBvZiBub3JtYWxpdHksIGl0IGVuaGFuY2VzIHRoZSBwZXJmb3JtYW5jZSBvZiBzdGF0aXN0aWNhbCBtb2RlbHMuDQoNCg0KVGhlICR5JCBoYXMgbmVnYXRpdmUgdmFsdWVzLCB3ZSBuZWVkIHRvIHVzZSB0aGUgbW9kaWZpZWQgdmVyc2lvbiBvZiBCb3gtY294IG9yIHVzZSBvdGhlciB0cmFuc2Zvcm1hdGlvbnMgdG8gY29udmVydCAkeSQgaW50byBhIHBvc2l0aXZlIHZhcmlhYmxlIGJlZm9yZSBhcHBseWluZyB0aGUgQm94LWNveCB0cmFuc2Zvcm1hdGlvbi4NCg0KKipFeGFtcGxlKio6IFRvIHBlcmZvcm0gYSBCb3gtQ294IHRyYW5zZm9ybWF0aW9uIGluIFIsIHlvdSBjYW4gdXNlIHRoZSBgYm94Y294YCBmdW5jdGlvbiBmcm9tIHRoZSBNQVNTIHBhY2thZ2UuIFRoaXMgZnVuY3Rpb24gaGVscHMgZmluZCB0aGUgb3B0aW1hbCAkXGxhbWJkYSQgdmFsdWUgdG8gdHJhbnNmb3JtIHRoZSBnaXZlbiBkYXRhIHRvIGFwcHJveGltYXRlIGEgbm9ybWFsIGRpc3RyaWJ1dGlvbi4gDQoNCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NCwgZmlnLmNhcD0iQm94LUNveCBUcmFuc2Zvcm1hdGlvbiBEZW1vbnN0cmF0aW9uIn0NCg0KbGlicmFyeShNQVNTKQ0KeSA8LSBjKDEsIDEsIDEsIDIsIDIsIDIsIDIsIDIsIDIsIDMsIDMsIDMsIDYsIDcsIDgpDQp4IDwtIGMoNywgNywgOCwgMywgMiwgNCwgNCwgNiwgNiwgNywgNSwgMywgMywgNSwgOCkNCg0KI0ZpdCBhIExpbmVhciBSZWdyZXNzaW9uIE1vZGVsOg0KbW9kZWwgPSBsbSh5fngpDQojRmluZCB0aGUgT3B0aW1hbCBMYW1iZGEgZm9yIEJveC1Db3ggVHJhbnNmb3JtYXRpb246DQpiYyA8LSBib3hjb3goeSB+IHgsIGxhbWJkYSA9IHNlcSgtMiwgMiwgMS8xMCkpDQpsYW1iZGEgPC0gYmMkeFt3aGljaC5tYXgoYmMkeSldDQoNCiNGaXQgYSBOZXcgTGluZWFyIFJlZ3Jlc3Npb24gTW9kZWwgVXNpbmcgdGhlIEJveC1Db3ggVHJhbnNmb3JtYXRpb246DQpuZXdfbW9kZWwgPC0gbG0oKCh5XmxhbWJkYSAtIDEpIC8gbGFtYmRhKSB+IHgpDQojIyBWaXN1YWxpemUgdGhlIERpZmZlcmVuY2VzIGluIFJlc2lkdWFsczoNCm9wIDwtIHBhcihwdHkgPSAicyIsIG1mcm93ID0gYygxLCAyKSkNCnFxbm9ybShtb2RlbCRyZXNpZHVhbHMpDQpxcWxpbmUobW9kZWwkcmVzaWR1YWxzKQ0KcXFub3JtKG5ld19tb2RlbCRyZXNpZHVhbHMpDQpxcWxpbmUobmV3X21vZGVsJHJlc2lkdWFscykNCnBhcihvcCkNCmBgYA0KDQoNClRoZSBib3hjb3ggZnVuY3Rpb24gY29tcHV0ZXMgdGhlIGxvZy1saWtlbGlob29kIGZvciBkaWZmZXJlbnQgdmFsdWVzIG9mICRcbGFtYmRhJCBhbmQgaGVscHMgaWRlbnRpZnkgdGhlIG9wdGltYWwgJFxsYW1iZGEkLiBUaGUgbmV3IG1vZGVsIHVzZXMgdGhlIHRyYW5zZm9ybWVkIHJlc3BvbnNlIHZhcmlhYmxlIHRvIGltcHJvdmUgbm9ybWFsaXR5IGFuZCBtZWV0IHRoZSBhc3N1bXB0aW9ucyBvZiBsaW5lYXIgcmVncmVzc2lvbi4gVG8gY2hlY2sgd2hldGhlciB0aGUgdHJhbnNmb3JtZWQgdmFyaWFibGUgZm9sbG93cyBhIG5vcm1hbCBkaXN0cmlidXRpb24sIHdlIGNhbiB1c2UgUS1RIHBsb3RzIHRvIHZpc3VhbGl6ZSB0aGUgbm9ybWFsaXR5IG9mIHJlc2lkdWFscyBiZWZvcmUgYW5kIGFmdGVyIHRoZSB0cmFuc2Zvcm1hdGlvbi4NCg0KXA0KDQojIyBPdXRsaWVyIGhhbmRsaW5nDQoNCkhhbmRsaW5nIG91dGxpZXJzIGluIFIgaXMgYSBjcnVjaWFsIHN0ZXAgaW4gZmVhdHVyZSBlbmdpbmVlcmluZyB0byBlbnN1cmUgdGhlIHJvYnVzdG5lc3Mgb2YgeW91ciBtb2RlbHMuIEhlcmUgYXJlIHNvbWUgY29tbW9uIHRlY2huaXF1ZXMgZm9yIGRldGVjdGluZyBhbmQgaGFuZGxpbmcgb3V0bGllcnMgaW4gUjoNCg0KKiBHcmFwaGljYWwgYXBwcm9hY2hlcyBpbmNsdWRlIGJveHBsb3QsIHNjYXR0ZXIgcGxvdCwgZXRjLg0KDQoqIFN0YXRpc3RpY2FsIGFwcHJvYWNoZXMgaW5jbHVkZSBaLXNjb3JlIHRyYW5zZm9ybWF0aW9uLCBhbmQgaW50ZXJxdWFydGlsZSByYW5nZSAoSVFSKS4NCg0KU2V2ZXJhbCBtZXRob2RzIGFyZSBjb21tb25seSB1c2VkIHRvIGhhbmRsZSBvdXRsaWVycywgYW1vbmcgdHJpbW1pbmcsIGNhcHBpbmcgKFdpbnNvcml6aW5nKSwgaW1wdXRhdGlvbiwgdHJhbnNmb3JtYXRpb24sIGFuZCBiaW5uaW5nLCB0cmFuc2Zvcm1hdGlvbiBhbmQgYmlubmluZyBhcmUgY29tbW9ubHkgdXNlZCBpbiBjbGFzc2ljYWwgc3RhdGlzdGljcy4NCg0KXA0KDQoNCiMgTW9kZWwtYmFzZWQgRmVhdHVyZSBFeHRyYWN0aW9uDQoNCk1vZGVsL2FsZ29yaXRobS1iYXNlZCBmZWF0dXJlIGV4dHJhY3Rpb24gbWV0aG9kcyBjbHVzdGVyaW5nLCBwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzIChQQ0EpLCBldGMuIA0KDQojIyBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzIChQQ0EpDQoNClByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSkgaXMgYSBwb3dlcmZ1bCB0ZWNobmlxdWUgdXNlZCB0byByZWR1Y2UgdGhlIGRpbWVuc2lvbmFsaXR5IG9mIGRhdGEgd2hpbGUgcmV0YWluaW5nIG1vc3Qgb2YgdGhlIHZhcmlhbmNlLiANCg0KDQpgYGB7ciBlY2hvID0gRkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSI2MCUiLCBmaWcuY2FwPSJJbGx1c3RyYXRpb24gb2YgcHJpbmNpcGFsIGNvbXBvbmVudCBhbmFseXNpcyJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvUENBLnBuZyIpDQpgYGANCg0KVGhlIHRlY2huaWNhbCBkZXNjcmlwdGlvbiB3aWxsIGJlIGRpc2N1c3NlZCBsYXRlci4gSGVyZeKAmXMgYSBzdGVwLWJ5LXN0ZXAgZXhhbXBsZSBvZiBob3cgdG8gcGVyZm9ybSBQQ0EgaW4gUiB1c2luZyB0aGUgKlVTQXJyZXN0cyBkYXRhc2V0Kiwgd2hpY2ggY29udGFpbnMgZGF0YSBvbiBhcnJlc3RzIHBlciAxMDAsMDAwIHJlc2lkZW50cyBmb3IgdmFyaW91cyBjcmltZXMgaW4gZWFjaCBVLlMuIHN0YXRlLiBUaGUgZGF0YSBzZXQgaXMgYnVpbHQgaW4gdGhlIGxpYnJhcnkgKnRpZHl2ZXJzZSouIFRoZSBmb2xsb3dpbmcgUiBjb2RlIGltcGxlbWVudHMgUENBLg0KDQpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTUsIGZpZy5jYXA9IkltcGxlbWVudGluZyBwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzIChQQ0EpIn0NCiMgTG9hZCB0aGUgZGF0YXNldA0KZGF0YSgiVVNBcnJlc3RzIikNCiMgVmlldyB0aGUgZmlyc3QgZmV3IHJvd3Mgb2YgdGhlIGRhdGFzZXQNCmhlYWQoVVNBcnJlc3RzKQ0KIyBTdW1tYXJ5IG9mIHRoZSBkYXRhDQpzdW1tYXJ5KFVTQXJyZXN0cykNCiMgU3RhbmRhcmRpemUgdGhlIGRhdGENClVTQXJyZXN0c19zY2FsZWQgPC0gc2NhbGUoVVNBcnJlc3RzKQ0KIyBQZXJmb3JtIFBDQQ0KcGNhX3Jlc3VsdCA8LSBwcmNvbXAoVVNBcnJlc3RzX3NjYWxlZCwgY2VudGVyID0gVFJVRSwgc2NhbGUuID0gVFJVRSkNCiMgU3VtbWFyeSBvZiBQQ0EgcmVzdWx0cw0Kc3VtbWFyeShwY2FfcmVzdWx0KQ0KIyBzZXR0aW5nIHVwIHBsb3QgbGF5b3V0DQojcGFyKG1mcm93ID1jKDEsMikpDQojIFNjcmVlIHBsb3QNCnNjcmVlcGxvdChwY2FfcmVzdWx0LCB0eXBlID0gImxpbmVzIikNCiMgQmlwbG90DQojYmlwbG90KHBjYV9yZXN1bHQsIHNjYWxlID0gMCkNCmBgYA0KDQpUaGUgbmV4dCBzdGVwIGlzIHRvIGV4dHJhY3QgdGhlIG5ldyBmZWF0dXJlIHZhcmlhYmxlcy4gRm9yIGNvbXBhcmlzb24sIHdlIGFkZCB0aGUgbmV3IGZlYXR1cmVzIHRvIHRoZSBvcmlnaW5hbCBkYXRhIHNldC4NCg0KYGBge3J9DQpQQ0Euc2NvcmVzID0gcm91bmQocGNhX3Jlc3VsdCR4LDUpDQpjb21iaW5lZC5kYXRhID0gY2JpbmQoVVNBcnJlc3RzLCBQQ0Euc2NvcmVzKQ0KaGVhZChjb21iaW5lZC5kYXRhICkNCmBgYA0KDQoqKkNvbW1lbnRzKio6IEEgZmV3IGNvbW1lbnRzIG9uIFBDQToNCg0KMS4gUENBIHNjb3JlcyBhcmUgdHJhbnNmb3JtZWQgZnJvbSB0aGUgb3JpZ2luYWwgZmVhdHVyZXMuIFRoZXJlZm9yZSwgUENBIGlzIGEgbW9kZWwtYmFzZWQgdHJhbnNmb3JtYXRpb24uDQoNCjIuIFRoZSBQQ0EgaXMgYWxzbyBjYWxsZWQgYSAqKmRpbWVuc2lvbiByZWR1Y3Rpb24gbWV0aG9kKiogYmVjYXVzZSwgaW4gdGhpcyBleGFtcGxlLCB0aGUgZmlyc3QgdGhyZWUgUENzIGNhcnJ5IGFib3V0ICQ5NlwlJCBvZiB0aGUgdG90YWwgaW5mb3JtYXRpb24gaW4gdGhlIG9yaWdpbmFsIDQgZmVhdHVyZXMuIEluIG1vZGVsaW5nLCB3ZSBzaW1wbHkgdXNlIHRoZSBmaXJzdCB0aHJlZSBQQ3MgYW5kIGlnbm9yZSB0aGUgZm91cnRoIG9uZS4gSW4gb3RoZXIgd29yZHMsIHdlIGNhbiByZWR1Y2UgdGhlIGRpbWVuc2lvbiBmcm9tIDQgdG8gdGhyZWUgYWZ0ZXIgcGVyZm9ybWluZyBQQ0EhDQoNCjMuIEl0IGlzIGRlcGVuZGVudCBvbiB0aGUgYXBwbGljYXRpb24gYW5kIHlvdXIgdG9sZXJhbmNlIG9mIGluZm9ybWF0aW9uIGxvc3MgdG8gZGVjaWRlIHRoZSBudW1iZXIgb2YgUENzIHRvIHJldGFpbiBmb3IgdGhlIHN1YnNlcXVlbnQgYW5hbHlzZXMuIElmIHlvdSBkZWNpZGUgdG8gY2FycnkgYXQgbGVhc3QgJDg1XCUkIGluZm9ybWF0aW9uIGluIHRoZSBvcmlnaW5hbCBmZWF0dXJlIHNldHMsIHlvdSBvbmx5IG5lZWQgdG8gY2hvb3NlIHRoZSBmaXJzdCAyIFBDcyBmb3IgdGhlIHN1YnNlcXVlbnQgYW5hbHlzaXMuDQoNCjQuIDxmb250IGNvbG9yID0gInJlZCI+KipcY29sb3J7cmVkfVRoZSBQQ0EgaXMgb25seSBwZXJmb3JtZWQgb24gbnVtZXJpY2FsIHZhcmlhYmxlcyB0aGF0IHdlIGF0IHRoZSBzYW1lIHNjYWxlIGFuZCBjb3JyZWxhdGVkISoqPC9mb250Pi4gSW4gY2xhc3NpY2FsIGluZmVyZW50aWFsIHN0YXRpc3RpY3MsIHdlIHNob3VsZCBhbHNvIGNvbnNpZGVyIHRoZSBpbnRlcnByZXRhYmlsaXR5IG9mIHRoZSBQQ0EuIEluIG1hY2hpbmUgbGVhcm5pbmcsIHdlIGNhbiBjb25zaWRlciB0aGlzIGFzIGEgYmxhY2stYm94IHByb2NlZHVyZS4NCg0KNS4gV2UgY2FuIGV4dHJhY3QgdGhlIGZhY3RvciBsb2FkaW5nIGZyb20gUENBICh3aGljaCBpcyBhIHN5c3RlbSBvZiBvcnRob2dvbmFsIGxpbmVhciB0cmFuc2Zvcm1hdGlvbiAtIGEgdHlwaWNhbCB0b3BpYyBjb3ZlcmVkIGluIGEgbGluZWFyIGFsZ2VicmEgY291cnNlKS4NCg0KNi4gVGhlIG1ldGhvZCBvZiBQQ0EgaXMgYWxzbyBvbmUgb2YgdGhlIGluZm9ybWF0aW9uIGFnZ3JlZ2F0aW9uIG1ldGhvZHMuDQoNClwNCg0KIyMgQ2x1c3RlciBBbmFseXNpcw0KDQpVbmxpa2UgdGhlIFBDQSBpbiB3aGljaCB0aGUgb3JpZ2luYWwgZmVhdHVyZXMgYXJlIHRyYW5zZm9ybWVkIHRvIFBDcyBhbmQgcmV0YWluICoqc3Ryb25nKiogUENzIGZvciBzdWJzZXF1ZW50IGFuYWx5c2VzIGFuZCBkcm9wICoqd2VhayoqIFBDcyB0byByZWR1Y2UgdGhlIGRpbWVuc2lvbiBvZiB0aGUgZGF0YSwgY2x1c3RlcmluZyBlbXBsb3lzIHRoZSB0b3BvbG9naWNhbCBzaGFwZSBhbmQgc3RydWN0dXJlIHRvIGNsdXN0ZXIgZGF0YSBwb2ludHMgdGhhdCBhcmUgKipjbG9zZSB0byBlYWNoIG90aGVyKiogYmFzZWQgb24gKipzb21lIGRpc3RhbmNlcyoqLiBUaGUgZGlzdGFuY2UgdXNlZCBpbiBkYXRhIHNjaWVuY2UgaXMgbm90IG5lY2Vzc2FyaWx5IHRvIGJlICpFdWNsaWRlYW4qLiBUaGVyZSBhcmUgZGlmZmVyZW50IHR5cGVzIG9mICpkaXN0YW5jZXMqIGluIGRhdGEgc2NpZW5jZSBhbGdvcml0aG1zLiBNb3JlIGRldGFpbGVkIGluZm9ybWF0aW9uIG9uIGNsdXN0ZXJpbmcgd2lsbCBiZSBkaXNjdXNzZWQgaW4gZnV0dXJlIG5vdGVzLiBIZXJlIGFyZSB0d28gY29tbW9ubHkgdXNlZCBjbHVzdGVyaW5nIGFsZ29yaXRobXMuDQoNCiMjIyBLLW1lYW5zIENsdXN0ZXJpbmcNCg0KSy1tZWFucyBjbHVzdGVyaW5nIGlzIGEgcG9wdWxhciB1bnN1cGVydmlzZWQgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG0gdXNlZCB0byBwYXJ0aXRpb24gYSBkYXRhc2V0IGludG8gKCRrJCkgZGlzdGluY3QsIG5vbi1vdmVybGFwcGluZyBjbHVzdGVycy4gVGhlIGJhc2ljIHN0ZXBzIGZvciBpbXBsZW1lbnRpbmcgKiprLW1lYW5zIGNsdXN0ZXJpbmcqKiBmb3IgbnVtZXJpY2FsIGZlYXR1cmVzIGFyZSBvdXRsaW5lZCBpbiB0aGUgZm9sbG93aW5nLg0KDQoxLiAqKkluaXRpYWxpemF0aW9uKio6IENob29zZSB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzICgkayQpIGFuZCByYW5kb21seSBpbml0aWFsaXplICgkayQpIGNsdXN0ZXIgY2VudHJvaWRzLg0KDQoyLiAqKkFzc2lnbm1lbnQqKjogQXNzaWduIGVhY2ggZGF0YSBwb2ludCB0byB0aGUgbmVhcmVzdCBjbHVzdGVyIGNlbnRyb2lkIGJhc2VkIG9uIHRoZSAqRXVjbGlkZWFuIGRpc3RhbmNlKi4NCg0KMy4gKipVcGRhdGUqKjogUmVjYWxjdWxhdGUgdGhlIGNlbnRyb2lkcyBhcyB0aGUgbWVhbiBvZiBhbGwgZGF0YSBwb2ludHMgYXNzaWduZWQgdG8gZWFjaCBjbHVzdGVyLg0KDQo0LiAqKlJlcGVhdCoqOiBSZXBlYXQgdGhlIGFzc2lnbm1lbnQgYW5kIHVwZGF0ZSBzdGVwcyB1bnRpbCB0aGUgY2VudHJvaWRzIG5vIGxvbmdlciBjaGFuZ2Ugc2lnbmlmaWNhbnRseSBvciBhIG1heGltdW0gbnVtYmVyIG9mIGl0ZXJhdGlvbnMgaXMgcmVhY2hlZC4NCg0KDQoNCmBgYHtyIGVjaG8gPSBGQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjkwJSIsIGZpZy5jYXA9IkstbWVhbnMgY2x1c3RlcmluZyBhbmFseXNpcyJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcva01lYW5zLUNsdXN0ZXJpbmcucG5nIikNCmBgYA0KDQpUaGUgZm9sbG93aW5nIGlzIGEgc2ltcGxlIGV4YW1wbGUgd2l0aCBSIGNvZGUgdXNpbmcgKipVU0EgYXJyZXN0IERhdGEqKi4gVG8gdmlzdWFsaXplIHRoZSBjbHVzdGVycywgd2UgZmlyc3QgcGVyZm9ybSBQQ0EgYW5kIHVzZSB0aGUgZmlyc3QgdHdvIFBDcyB0byBtYWtlIHRoZSBzY2F0dGVyIHBsb3QuIFRoZSBmb2xsb3dpbmcgUiBjb2RlIHVzZSBsaWJyYXJ5ICoqKioNCg0KYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9IDYsIGZpZy5oZWlnaHQ9NCwgZmlnLmNhcD0iVmlzdWFsaXppbmcgY2x1c3RlcnMgYmFzZWQgb24gay1tZWFucyJ9DQojIExvYWQgYW5kIHByZXBhcmUgZGF0YQ0KZGF0YSgiVVNBcnJlc3RzIikNCmRmIDwtIG5hLm9taXQoVVNBcnJlc3RzKQ0KZGYgPC0gc2NhbGUoZGYpDQoNCiMgUGVyZm9ybSBLLW1lYW5zIGNsdXN0ZXJpbmcNCnNldC5zZWVkKDEyMykgICMgRm9yIHJlcHJvZHVjaWJpbGl0eQ0Ka21lYW5zX3Jlc3VsdCA8LSBrbWVhbnMoZGYsIGNlbnRlcnMgPSAzLCBuc3RhcnQgPSAyNSkNCg0KIyBWaXN1YWxpemUgY2x1c3RlcnMNCmZ2aXpfY2x1c3RlcihrbWVhbnNfcmVzdWx0LCBkYXRhID0gZGYsIHhsYWI9IlBDMSIsIHlsYWIgPSAiUEMyIikNCmBgYA0KDQoqKldoYXQgaXMgdGhlIG5ldyBmZWF0dXJlPyoqIC0gPGZvbnQgY29sb3IgPSAicmVkIj4qXGNvbG9ye3JlZH1UaGUgbmV3IGZlYXR1cmUgZXh0cmFjdGVkIGZyb20gdGhlIGRhdGEgaXMgdGhlIHZlY3RvciBvZiB0aGUgY2x1c3RlciBsYWJlbHMhKjwvZm9udD4uDQoNCkluIFIsIHdlIGV4dHJhY3QgdGhlIGNsdXN0ZXIgbGFiZWxzIHVzaW5nIHRoZSBmb2xsb3dpbmcgY29kZS4NCg0KYGBge3J9DQpjbHVzdC5JRCA9IGttZWFuc19yZXN1bHQkY2x1c3Rlcg0KY2JpbmQoVVNBcnJlc3RzLCBjbHVzdC5JRCkNCmBgYA0KDQojIyMgSGllcmFyY2hpY2FsIENsdXN0ZXJpbmcNCg0KSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgaXMgYSBtZXRob2Qgb2YgY2x1c3RlciBhbmFseXNpcyBpbiBkYXRhIG1pbmluZyBhbmQgc3RhdGlzdGljcyB0aGF0IGJ1aWxkcyBhIGhpZXJhcmNoeSBvZiBjbHVzdGVycy4gVGhlcmUgYXJlIHR3byBtYWluIHR5cGVzOg0KDQoqKkFnZ2xvbWVyYXRpdmUgKEJvdHRvbS1VcCkgQXBwcm9hY2gqKjogRWFjaCBvYnNlcnZhdGlvbiBzdGFydHMgaW4gaXRzIG93biBjbHVzdGVyLCBhbmQgcGFpcnMgb2YgY2x1c3RlcnMgYXJlIG1lcmdlZCBhcyBvbmUgbW92ZXMgdXAgdGhlIGhpZXJhcmNoeS4NCg0KKipEaXZpc2l2ZSAoVG9wLURvd24pIEFwcHJvYWNoKio6IEFsbCBvYnNlcnZhdGlvbnMgc3RhcnQgaW4gb25lIGNsdXN0ZXIsIGFuZCBzcGxpdHMgYXJlIHBlcmZvcm1lZCByZWN1cnNpdmVseSBhcyBvbmUgbW92ZXMgZG93biB0aGUgaGllcmFyY2h5Lg0KVGhlIHJlc3VsdHMgYXJlIG9mdGVuIHByZXNlbnRlZCBpbiBhIGRlbmRyb2dyYW0sIGEgdHJlZS1saWtlIGRpYWdyYW0gdGhhdCBzaG93cyB0aGUgYXJyYW5nZW1lbnQgb2YgdGhlIGNsdXN0ZXJzIHByb2R1Y2VkIGJ5IHRoZSBhbGdvcml0aG0uDQoNCldlIHdpbGwgZGlzY3VzcyB0aGVzZSBtZXRob2RzIGluIGRldGFpbCBpbiBhIGZ1dHVyZSBub3RlLiBUaGUgZm9sbG93aW5nIGZpZ3VyZSBpbGx1c3RyYXRlcyB0aGUgcm91Z2ggaWRlYSBvZiBoaWVyYXJjaGljYWwgY2x1c3RlcmluZy4NCg0KDQpgYGB7ciBlY2hvID0gRkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSI4MCUiLCBmaWcuY2FwPSJIaWVyYXJjaGljYWwgY2x1c3RlcmluZyBpbGx1c3RyYXRpb24ifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL0hpZXJhcmNoaWNhbENsdXN0ZXJpbmcucG5nIikNCmBgYA0KDQpcDQoNCg0KIyBPdGhlciBNZXRob2RzIG9mIEZlYXR1cmUgQ3JlYXRpb24NCg0KSXQgaXMgcXVpdGUgb2Z0ZW4gdG8gY3JlYXRlIG5ldyBmZWF0dXJlIHZhcmlhYmxlcyBiYXNlZCBvbiBleGlzdGluZyBmZWF0dXJlcyB0aHJvdWdoIGJhc2ljIGFsZ2VicmFpYyBvcGVyYXRpb25zIGFuZCBzdHJpbmcgbWFuaXB1bGF0aW9ucy4gVGhpcyBzZWN0aW9uIGludHJvZHVjZXMgYSBmZXcgY29tbW9ubHkgdXNlZCBtZXRob2RzLiBTb21lIG9mIHRoZSBtZXRob2RzIGFyZSBhbHNvIGNvbnNpZGVyZWQgYXMgdHJhbnNmb3JtYXRpb25zLg0KDQojIyBFeHRyYWN0aW5nIEZlYXR1cmVzIGZyb20gVGV4dA0KDQpJbiBtYW55IGNhc2VzLCB5b3VyIGludGVncmF0ZWQgZGF0YSBzZXQgbWF5IGhhdmUgZmllbGRzIGNvbnRhaW5pbmcgaW5mb3JtYXRpb24gc3VjaCBhcyBob21lIGFkZHJlc3MsIGVtYWlsIGFkZHJlc3MsIHRleHQgb2YgdmlhbCBsYWJlbHMsIGV0Yy4gDQoNCmBgYHtyIGVjaG8gPSBGQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjYwJSIsIGZpZy5jYXA9IkV4YW1wbGUgb2YgdmlhbCBsYWJlbCBjb250YWluaW5nIGRvc2UgaW5mb3JtYXRpb24ifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL1ZpYWxMYWJlbC5wbmciKQ0KYGBgDQoNClIgaGFzIG1hbnkgc3RyaW5nIGZ1bmN0aW9ucyB5b3UgY2FuIHVzZSB0byBsb2NhdGUgdGhlIHBhcnRpY3VsYXIgaW5mb3JtYXRpb24gc3VjaCBhcyBaSVAgY29kZSBpbiB0aGUgYWRkcmVzcywgZW1haWwgZG9tYWluIGluIHRoZSBlbWFpbCBhZGRyZXNzLCBkb3NlIGluZm9ybWF0aW9uIGluIHRoZSB2aWFsIGxhYmVscywgZXRjLiBpbiB0aGUgdGV4dCBhbmQgdGhlbiBleHRyYWN0IGl0IHRvIGRlZmluZSBuZXcgZmVhdHVyZSB2YXJpYWJsZXMuIEFzIGFuIGV4YW1wbGUsIHRoZSBmb2xsb3dpbmcgUiBjb2RlIGlsbHVzdHJhdGVzIGhvdyB0byBleHRyYWN0IHRoZSBlbWFpbCBkb21haW4gZnJvbSB0aGUgZW1haWwgYWRkcmVzcyB1c2luZyBgc3ViKClgIG9yIGBzdHJfZXh0cmFjdCgpYCBmdW5jdGlvbnMuDQoNCmBgYHtyfQ0KI2xpYnJhcnkoc3RyaW5ncikNCiMgZW1haWwgYWRkcmVzc2VzDQplbWFpbHMgPC0gYygidXNlcjFAZXhhbXBsZS5jb20iLCAidXNlcjJAZG9tYWluLm9yZyIsICJ1c2VyM0Bjb21wYW55Lm5ldCIpDQoNCiMgRXh0cmFjdCBkb21haW5zDQpkb21haW5zIDwtIHN1YigiLipAIiwgIiIsIGVtYWlscykgICMgc3ViKCkgaXMgYSBmdW5jdGlvbiBpbiB0aGUgYmFzZSBwYWNrYWdlDQpwcmludChkb21haW5zKSAjIFsxXSAiZXhhbXBsZS5jb20iICJkb21haW4ub3JnIiAiY29tcGFueS5uZXQiDQoNCiMgdXNpbmcgbGlicmFyeSB7c3RyaW5ncn0NCg0KIyBFeHRyYWN0IGRvbWFpbnMNCmRvbWFpbnMgPC0gc3RyX2V4dHJhY3QoZW1haWxzLCAiKD88PUApLioiKQ0KcHJpbnQoZG9tYWlucykgIyBbMV0gImV4YW1wbGUuY29tIiAiZG9tYWluLm9yZyIgImNvbXBhbnkubmV0Ig0KYGBgDQoNCg0KDQoNCiMjIEV4dHJhY3RpbmcgRmVhdHVyZSBmcm9tIERhdGVzDQoNCkV4dHJhY3RpbmcgZmVhdHVyZXMgZnJvbSBkYXRlcyBpbiBSIGNhbiBiZSBxdWl0ZSBzdHJhaWdodGZvcndhcmQsIGVzcGVjaWFsbHkgd2l0aCB0aGUgaGVscCBvZiB0aGUgYGx1YnJpZGF0ZWAgcGFja2FnZS4gSGVyZSBhcmUgc29tZSBjb21tb24gdGFza3MgYW5kIGhvdyB0byBhY2NvbXBsaXNoIHRoZW0uIFRoZSBmb2xsb3dpbmcgY29kZSBpbGx1c3RyYXRlcyBob3cgdG8gdXNlIFIgZnVuY3Rpb25zIGluIHRoZSBgbHVicmlkYXRlYCBwYWNrYWdlIHRvIGV4dHJhY3QgdmFyaW91cyBwaWVjZXMgb2YgaW5mb3JtYXRpb24gZnJvbSBkYXRlLg0KDQpgYGB7cn0NCiNsaWJyYXJ5KGx1YnJpZGF0ZSkNCg0KIyBFeGFtcGxlIGRhdGUNCmRhdGUgPC0geW1kKCIyMDI0LTA4LTI5IikNCg0KIyBFeHRyYWN0IHllYXIsIG1vbnRoLCBhbmQgZGF5DQp5ZWFyIDwtIHllYXIoZGF0ZSkNCm1vbnRoIDwtIG1vbnRoKGRhdGUpDQpkYXkgPC0gZGF5KGRhdGUpDQoNCnByaW50KHllYXIpICAjIDIwMjQNCnByaW50KG1vbnRoKSAjIDgNCnByaW50KGRheSkgICAjIDI5DQoNCiMgZXh0cmFjdCB3ZWVrZGF5DQp3ZWVrZGF5IDwtIHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKQ0KcHJpbnQod2Vla2RheSkgIyAiVGh1Ig0KDQojZXh0cmFjdGluZyBxdWFydGVyDQpxdWFydGVyIDwtIHF1YXJ0ZXIoZGF0ZSkNCnByaW50KHF1YXJ0ZXIpICMgMw0KDQojIGV4dHJhY3RpbmcgRGF5IG9mIHRoZSBZZWFyDQpkYXlfb2ZfeWVhciA8LSB5ZGF5KGRhdGUpDQpwcmludChkYXlfb2ZfeWVhcikgIyAyNDINCg0KI2V4dHJhY3Rpbmcgd2VlayBvZiB0aGUgeWVhcg0Kd2Vla19vZl95ZWFyIDwtIHdlZWsoZGF0ZSkNCnByaW50KHdlZWtfb2ZfeWVhcikgIyAzNQ0KYGBgDQoNCiMjIEZlYXR1cmUgU2NhbGluZy9TdGFuZGFyZGl6YXRpb24NCg0KRmVhdHVyZSBzY2FsaW5nLCBpbmNsdWRpbmcgc3RhbmRhcmRpemF0aW9uLCBpcyBhIGNydWNpYWwgc3RlcCBpbiBkYXRhIHByZXByb2Nlc3NpbmcsIGVzcGVjaWFsbHkgZm9yIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzLiBJdCBlbnN1cmVzIHRoYXQgYWxsIGZlYXR1cmVzIGNvbnRyaWJ1dGUgZXF1YWxseSB0byB0aGUgbW9kZWwgYW5kIHByZXZlbnRzIGZlYXR1cmVzIHdpdGggbGFyZ2VyIHNjYWxlcyBmcm9tIGRvbWluYXRpbmcuIEhlcmUgYXJlIHNvbWUgY29tbW9uIG1ldGhvZHMgZm9yIGZlYXR1cmUgc2NhbGluZyBpbiBSLg0KDQojIyMgU3RhbmRhcmRpemF0aW9uDQpTdGFuZGFyZGl6YXRpb24gc2NhbGVzIHRoZSBkYXRhIHNvIHRoYXQgaXQgaGFzIGEgbWVhbiBvZiAwIGFuZCBhIHN0YW5kYXJkIGRldmlhdGlvbiBvZiAxLiBUaGlzIGlzIGRvbmUgdXNpbmcgdGhlIGZvcm11bGENCiQkDQp6ID0gXGZyYWN7eC1cbXV9e1xzaWdtYX0NCiQkDQp3aGVyZSAkeCQgaXMgdGhlIG9yaWdpbmFsIGZlYXR1cmUgdmFsdWUsICRcbXUkIGlzIHRoZSBtZWFuIG9mIHRoZSBmZWF0dXJlLCBhbmQgJFxzaWdtYSQgaXMgdGhlIHN0YW5kYXJkIGRldmlhdGlvbi4gV2UgY2FuIGFsc28gdXNlIHRoZSBgc2NhbGUoKWAgZnVuY3Rpb24gaW4gUiB0byBzdGFuZGFyZGl6ZSB0aGUgZGF0YS4gVGhlIGZvbGxvd2luZyBpcyBhbiBleGFtcGxlIG9mIHN0YW5kYXJkaXphdGlvbi4NCg0KYGBge3J9DQojIEV4YW1wbGUgZGF0YSBmcmFtZQ0KZGF0YSA8LSBkYXRhLmZyYW1lKEFnZSA9IHJub3JtKDUwMCwgNTAsIDgpLCBXZWlnaHQgPSBybm9ybSg1MDAsIDgwLCAxMCkpDQoNCiMgU3RhbmRhcmRpemUgdGhlIGRhdGENCmRhdGFfc3RhbmRhcmRpemVkIDwtIHNjYWxlKGRhdGEpDQpwcmludChoZWFkKGRhdGFfc3RhbmRhcmRpemVkKSkNCmBgYA0KDQoNCiMjIyBNaW4tTWF4IE5vcm1hbGl6YXRpb24NCg0KTWluLU1heCBub3JtYWxpemF0aW9uIHNjYWxlcyB0aGUgZGF0YSB0byBhIGZpeGVkIHJhbmdlLCB1c3VhbGx5IFswLCAxXS4gVGhpcyBpcyBkb25lIHVzaW5nIHRoZSBmb3JtdWxhOg0KDQokJA0KeF5ccHJpbWUgPSBcZnJhY3t4IC0gXG1pbih4KX17XG1heCh4KSAtIFxtaW4oeCl9DQokJA0KDQpIZXJl4oCZcyBob3cgd2UgYXBwbHkgTWluLU1heCBub3JtYWxpemF0aW9uIGluIFIuDQoNCg0KYGBge3J9DQojIE1pbi1NYXggbm9ybWFsaXphdGlvbiBmdW5jdGlvbg0KbWluLm1heC5ub3JtIDwtIGZ1bmN0aW9uKHgpIHsNCiAgKHggLSBtaW4oeCkpIC8gKG1heCh4KSAtIG1pbih4KSkNCn0NCg0KIyBnZW5lcmF0ZSBkYXRhDQpkYXRhIDwtIGRhdGEuZnJhbWUoQWdlID0gcm5vcm0oNTAwLCA1MCwgOCksIFdlaWdodCA9IHJub3JtKDUwMCwgODAsIDEwKSkNCg0KIyBBcHBseSB0byBkYXRhIGZyYW1lDQpkYXRhLm5vcm1hbGl6ZWQgPC0gYXMuZGF0YS5mcmFtZShsYXBwbHkoZGF0YSwgbWluLm1heC5ub3JtKSkNCnByaW50KGhlYWQoZGF0YS5ub3JtYWxpemVkKSkNCmBgYA0KDQojIyMgUm9idXN0IFNjYWxpbmcNCg0KUm9idXN0IHNjYWxpbmcgdXNlcyB0aGUgbWVkaWFuIGFuZCB0aGUgaW50ZXJxdWFydGlsZSByYW5nZSAoSVFSKSB0byBzY2FsZSB0aGUgZGF0YSwgbWFraW5nIGl0IGxlc3Mgc2Vuc2l0aXZlIHRvIG91dGxpZXJzLiBUaGUgZm9ybXVsYSBpcw0KDQokJA0KeF5ccHJpbWUgPVxmcmFje3ggLSBcdGV4dHttZWRpYW59KHgpfXtcdGV4dHtJUVJ9KHgpfQ0KJCQNCkhlcmUgaXMgYW4gZXhhbXBsZSBpbiBSIG9mIHJvYnVzdCBzY2FsaW5nIGluIFIuDQoNCmBgYHtyfQ0KIyBSb2J1c3Qgc2NhbGluZyBmdW5jdGlvbg0Kcm9idXN0LlNjYWxpbmcgPC0gZnVuY3Rpb24oeCkgew0KICBxcSA9IHF1YW50aWxlKHgsIHByb2JzID0gc2VxKDAsIDEsIDAuMjUpLCBuYS5ybSA9IEZBTFNFLCBuYW1lcyA9IFRSVUUsIHR5cGUgPSA3KQ0KICAoeCAtIHFxWzNdKSAvIChxcVs0XSAtIHFxWzJdKQ0KfQ0KDQojIGdlbmVyYXRlIGRhdGENCmRhdGEgPC0gZGF0YS5mcmFtZShBZ2UgPSBybm9ybSg1MDAsIDUwLCA4KSwgV2VpZ2h0ID0gcm5vcm0oNTAwLCA4MCwgMTApKQ0KDQojIEFwcGx5IHRvIGRhdGEgZnJhbWUNCmRhdGEuc2NhbGVkIDwtIGFzLmRhdGEuZnJhbWUobGFwcGx5KGRhdGEsIHJvYnVzdC5TY2FsaW5nKSkNCnByaW50KGhlYWQoZGF0YS5zY2FsZWQpKQ0KYGBgDQoNCg0KIyBXcmFwcGluZyBVcCBGZWF0dXJlIEVuZ2luZWVyaW5nDQoNCkZlYXR1cmUgZW5naW5lZXJpbmcgaXMgYSBwYXJ0IG9mIHRoZSBkYXRhIHNjaWVuY2UgcHJvY2VzcyBhcyBkZXNjcmliZWQgaW4gdGhlIGZvbGxvd2luZyBmaWd1cmUuIA0KDQpgYGB7ciBlY2hvID0gRkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSI5MCUiLCBmaWcuY2FwID0gIlBhcnQgb2YgZGF0YSBzY2llbmNlIHRoYXQgcmVzdWt0IGluIGFuIGFuYWx5dGljIGRhdGEgc2V0LiJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvRVRMLUludGVncmF0aW9uLUZlYXR1cmVFbmdpbmVlcmluZy5wbmciKQ0KYGBgDQoNCkZlYXR1cmUgZW5naW5lZXJpbmcgaXMgYW4gaW5wdXQtcHJvY2Vzcy1vdXQgKElQTykgbW9kZWwgdGhhdCBjYW4gYmUgY2Fwc3VsYXRlZCBhbmQgb3JnYW5pemVkIHdpdGggYSBmdW5jdGlvbiBpbiBSIG9yIG90aGVyIHByb2dyYW1taW5nIGxhbmd1YWdlcy4NCg0KDQpgYGB7ciBlY2hvID0gRkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSI5MCUiLCBmaWcuY2FwID0gIldyYXBwaW5nIHVwIGZlYXR1cmUgZW5naW5lZXJpbmcuIn0NCmluY2x1ZGVfZ3JhcGhpY3MoImltZy9GZWF0dXJlRW5naW5lZXJpbmdGdW5jdGlvbi5wbmciKQ0KYGBgDQoNClRoZSBpZGVhIGlzIHRvIHdyaXRlIGEgZnVuY3Rpb24gdG8gYWNjZXB0IHJlY29yZHMgZnJvbSB0aGUgaW50ZWdyYXRlZCBkYXRhIGFuZCBwcm9kdWNlIGVuZ2luZWVyZWQgcmVjb3JkcyB0byBmZWVkIGNhbmRpZGF0ZSBtb2RlbHMgZm9yIHByZWRpY3Rpb24uDQoNCg0KDQojIENvbmNsdWRpbmcgUmVtYXJrcw0KDQpJdCBpcyBkZXBlbmRlbnQgb24gc3BlY2lmaWMgYXBwbGljYXRpb25zLCBkYXRhIGV4cGVyaWVuY2UsIHRlY2huaWNhbCBleHBlcnRpc2UsIGFuZCBhbW91bnQgb2YgZWZmb3J0LCBvbmUgY2FuIGVuZ2luZWVyIGZlYXR1cmVzIGluIGRpZmZlcmVudCB3YXlzIHRoYXQgcmVzdWx0IGluIGRpZmZlcmVudCBhY2N1cmFjeSB3aXRoIHRoZSBzYW1lIG1vZGVsLiBUaGlzIG5vdGUgZGlzY3Vzc2VkIHRoZSBiYXNpYyBmZWF0dXJlIGVuZ2luZWVyaW5nIG1ldGhvZHMgdGhhdCBhcmUgY29tbW9ubHkgdXNlZCBpbiBkYXRhIHNjaWVuY2UgcHJvamVjdHMuIFdlIHdpbGwgZGlzY3VzcyBtb3JlIGZlYXR1cmUgZW5naW5lZXJpbmcgbWV0aG9kcyBpbiBkaWZmZXJlbnQgYXBwbGljYXRpb25zIGluIHRoZSBzdWJzZXF1ZW50IG1hY2hpbmUgbGVhcm5pbmcgY291cnNlLiAgIA0KDQoNCmBgYHtyIGVjaG8gPSBGQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9IjQwJSJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvQmVnaW5XaXRoRW5kSW5NaW5kLnBuZyIpDQpgYGANCg0KDQpTcGVjaWFsIGF0dGVudGlvbiBtdXN0IHBhaWQgdG8gbWlzc2luZyB2YWx1ZS4gRmVlZGluZyBhbiBlbmdpbmVlcmVkIHJlY29yZCB3aXRoIG1pc3NpbmcgY29tcG9uZW50cyB0byBhIHByZWRpY3RpdmUgbW9kZWwgd2lsbCBlbmQgdXAgd2l0aCBhIG1pc3NpbmcgcHJlZGljdGVkIHZhbHVlLiBGb3IgZXhhbXBsZSwgYSBzdWJzZXQgb2YgZmVhdHVyZXMgd2VyZSBlbmdpbmVlcmVkIGFuZCB0aGUgcmVzdCBvZiB0aGUgZmVhdHVyZXMgd2VyZSBub3QgZW5naW5lZXJlZCAoaS5lLiwgdGhlIG9yaWdpbmFsIGZlYXR1cmVzIHdlcmUgdXNlZCB0byBmZWVkIHRoZSBmaW5hbCBtb2RlbCkuIA0KDQpgYGB7ciBlY2hvID0gRkFMU0UsIGZpZy5hbGlnbj0nY2VudGVyJywgb3V0LndpZHRoPSI5NSUifQ0KaW5jbHVkZV9ncmFwaGljcygiaW1nL1ByZU1lYW5SZXBsYWNlUmVjb3JkLnBuZyIpDQpgYGANCg0KVGhlIGFib3ZlLWVuZ2luZWVyZWQgcmVjb3JkIGhhcyBubyBtaXNzaW5nIGNvbXBvbmVudC4gRmVlZGluZyB0aGlzICpub3JtYWwqIGVuZ2luZWVyZWQgcmVjb3JkIHRvIHRoZSBmaW5hbCBtb2RlbCB3aWxsIHJlc3VsdCBpbiBhICpub3JtYWwqIHJlc3VsdC4gSG93ZXZlciwgaWYgdGhlIGluY29taW5nIG5ldyByZWNvcmRzIGNvbnRhaW4gbWlzc2luZyBjb21wb25lbnRzLCB0aGUgbWlzc2luZyBjb21wb25lbnRzIHdpbGwgYmUgcGFzc2VkIHRvIHRoZSAqKmVuZ2luZWVyZWQgcmVjb3JkcyoqLg0KDQoNCmBgYHtyIGVjaG8gPSBGQUxTRSwgZmlnLmFsaWduPSdjZW50ZXInLCBvdXQud2lkdGg9Ijk1JSJ9DQppbmNsdWRlX2dyYXBoaWNzKCJpbWcvcHJvZHVjdGlvblJlY29yZC5wbmciKQ0KYGBgDQoNClRoZSBhYm92ZS1lbmdpbmVlcmVkIHJlY29yZCBzdGlsbCBoYXMgYSBtaXNzaW5nIGNvbXBvbmVudC4gRmVlZGluZyB0aGlzIGVuZ2luZWVyZWQgcmVjb3JkIHdpdGggbWlzc2luZyBjb21wb25lbnRzIHdpbGwgZ2VuZXJhdGUgYSBtaXNzaW5nIHByZWRpY3RlZCB2YWx1ZS4gVGhpcyBpcyBwcmFjdGljYWxseSBpbXBvcnRhbnQgc2luY2UgaXQgd2lsbCBwcmV2ZW50IHRoZSBwb3RlbnRpYWwgZmFpbHVyZSBwcmVkaWN0aW9uIGZyb20gYSBkeW5hbWljIHByZWRpY3Rpb24gc3lzdGVtIQ0K