Introduction
This notebook covers various aspects of working with data, including
data visualization, facets, time series analysis, linear regression, and
classification. We’ll use R and its popular libraries to demonstrate
these concepts, provide detailed explanations, and offer exercises for
practice.
1. Data Visualization
Explanation
Data visualization is the graphical representation of information and
data. It uses statistical graphics, plots, and other visual elements to
communicate complex data relationships and patterns effectively. The
human brain processes visual information much faster than text, making
data visualization an essential tool for understanding and presenting
data.
In R, the ‘ggplot2’ package is widely used for creating data
visualizations. It’s based on the Grammar of Graphics, a layered
approach to describing and constructing visualizations.
Key components of a ggplot: 1. Data: The dataset you’re visualizing
2. Aesthetics (aes): Mapping of variables to visual properties (e.g.,
x-axis, y-axis, color, size) 3. Geometries (geom): The type of plot
(e.g., points, lines, bars) 4. Scales: How the data is mapped to the
visual properties 5. Facets: Division of the plot into subplots based on
categorical variables 6. Theme: Overall visual style of the plot
Example
Let’s create a more complex visualization using the mtcars
dataset:
ggplot(mtcars, aes(x = wt, y = mpg, color = factor(cyl), size = hp)) +
geom_point(alpha = 0.7) +
geom_smooth(method = "lm", se = FALSE) +
scale_color_viridis_d() +
labs(title = "Car Weight vs. MPG",
subtitle = "Colored by number of cylinders, size represents horsepower",
x = "Weight (1000 lbs)",
y = "Miles Per Gallon",
color = "Cylinders",
size = "Horsepower") +
theme_minimal()
Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
ℹ Please use `linewidth` instead.
This warning is displayed once every 8 hours.
Call `lifecycle::last_lifecycle_warnings()` to see where this warning was generated.
`geom_smooth()` using formula = 'y ~ x'
Warning: The following aesthetics were dropped during statistical transformation: size.
ℹ This can happen when ggplot fails to infer the correct grouping structure in the data.
ℹ Did you forget to specify a `group` aesthetic or to convert a numerical variable into a factor?
This plot shows: - The relationship between weight (x-axis) and miles
per gallon (y-axis) - The number of cylinders (color) - The horsepower
(size of points) - A linear regression line for each cylinder group
Exercises
Using the built-in diamonds
dataset, create a
scatter plot of price vs. carat. Color the points by the cut quality and
add a title and proper axis labels.
With the mpg
dataset, create a box plot showing the
distribution of highway miles per gallon (hwy) for different car classes
(class). Add color to the boxes and include a title and axis
labels.
Using the economics
dataset, create a line plot
showing both personal savings rate (psavert) and unemployment rate
(uempmed) over time. Use two different y-axes (hint: look up
sec_axis()
). Include a legend, title, and appropriate
labels.
2. Facets
Explanation
Faceting is a technique used to split a plot into multiple subplots
based on one or more categorical variables. This allows for easy
comparison of patterns across different subgroups in the data. In
ggplot2, there are two main faceting functions:
facet_wrap()
: Arranges a series of plots into a 2D
grid, “wrapping” around with a specified number of rows or columns.
facet_grid()
: Creates a grid of plots with rows and
columns based on different categorical variables.
Faceting is particularly useful when you want to: - Compare trends or
patterns across different categories - Visualize how relationships
change across subgroups - Display multiple related plots in a compact
format
Example
Let’s create a faceted plot using the mpg
dataset:
ggplot(mpg, aes(x = displ, y = hwy)) +
geom_point(aes(color = class), alpha = 0.7) +
geom_smooth(method = "lm", se = FALSE, color = "black") +
facet_wrap(~ manufacturer, scales = "free") +
labs(title = "Highway MPG vs. Engine Displacement",
subtitle = "Faceted by manufacturer, colored by vehicle class",
x = "Engine Displacement (L)",
y = "Highway MPG",
color = "Vehicle Class") +
theme_minimal() +
theme(legend.position = "bottom")
`geom_smooth()` using formula = 'y ~ x'
This plot shows: - The relationship between engine displacement and
highway MPG - Facets for each car manufacturer - Colors representing
different vehicle classes - A linear regression line for each
manufacturer
Exercises
Using the diamonds
dataset, create a faceted
histogram of diamond prices. Facet by cut quality and fill the
histograms by diamond color. Add appropriate labels and a
title.
With the mpg
dataset, create a faceted scatter plot
of city MPG vs. highway MPG. Facet by the number of cylinders and color
the points by the type of drive (drv). Include a regression line for
each facet.
Using the txhousing
dataset, create a faceted line
plot showing the median housing price over time for different cities.
Use facet_wrap()
to create a 3x3 grid of plots for the top
9 cities by median housing price. Add appropriate labels and a
title.
3. Time Series
Explanation
Time series analysis involves studying data points collected over
time, typically at regular intervals. It’s used to identify trends,
seasonal patterns, and other time-dependent structures in the data. Key
components of time series analysis include:
- Trend: Long-term increase or decrease in the data
- Seasonality: Repeating patterns at fixed intervals
- Cyclical patterns: Fluctuations not tied to a fixed period
- Random variations: Unexplained variability in the data
In R, time series data is often stored in specialized objects like
ts
(base R) or xts
(from the xts package). The
lubridate
package is useful for handling dates and times,
while forecast
provides functions for time series
forecasting.
Common techniques in time series analysis include: - Moving averages
- Exponential smoothing - ARIMA (Autoregressive Integrated Moving
Average) models - Decomposition of time series into trend, seasonal, and
residual components
Example
Let’s create a more complex time series example using the
AirPassengers
dataset:
data("AirPassengers")
ap_df <- data.frame(
date = seq(as.Date("1949-01-01"), as.Date("1960-12-01"), by = "month"),
passengers = as.numeric(AirPassengers)
)
# Decompose the time series
ap_decomp <- decompose(AirPassengers)
# Plot the original series and its components
par(mfrow = c(4, 1), mar = c(2, 2, 2, 2))
plot(ap_decomp$x, main = "Original Time Series", ylab = "Passengers")
plot(ap_decomp$trend, main = "Trend", ylab = "Trend")
plot(ap_decomp$seasonal, main = "Seasonal", ylab = "Seasonal")
plot(ap_decomp$random, main = "Random", ylab = "Random")
This example shows: - The original time series of airline passengers
- The decomposition of the series into trend, seasonal, and random
components
Exercises
Using the economics
dataset, create a time series
plot of the personal savings rate (psavert) from 1967 to 2015. Add a
trend line using a simple moving average with a window of 12
months.
With the nhtemp
dataset (average yearly temperatures
in New Haven), create a time series plot and decompose it into trend,
seasonal, and random components. Plot each component
separately.
Using the co2
dataset (atmospheric CO2
concentrations), create a time series forecast for the next 24 months
using an appropriate model (e.g., ARIMA). Plot the original data, the
fitted values, and the forecast with prediction intervals.
4. Linear Regression
Explanation
Linear regression is a statistical method used to model the
relationship between a dependent variable and one or more independent
variables. In its simplest form (simple linear regression), it assumes a
linear relationship between two variables:
y = β₀ + β₁x + ε
Where: - y is the dependent variable - x is the independent variable
- β₀ is the y-intercept - β₁ is the slope - ε is the error term
Multiple linear regression extends this to multiple independent
variables:
y = β₀ + β₁x₁ + β₂x₂ + … + βₚxₚ + ε
Key concepts in linear regression: 1. Coefficients: Estimated using
the method of least squares 2. R-squared: Measure of how well the model
fits the data 3. Residuals: Differences between observed and predicted
values 4. Assumptions: Linearity, independence, homoscedasticity,
normality of residuals
Example
Let’s perform a multiple linear regression using the mtcars
dataset:
# Fit a multiple linear regression model
model <- lm(mpg ~ wt + hp + qsec, data = mtcars)
# Print the summary
summary(model)
Call:
lm(formula = mpg ~ wt + hp + qsec, data = mtcars)
Residuals:
Min 1Q Median 3Q Max
-3.8591 -1.6418 -0.4636 1.1940 5.6092
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 27.61053 8.41993 3.279 0.00278 **
wt -4.35880 0.75270 -5.791 3.22e-06 ***
hp -0.01782 0.01498 -1.190 0.24418
qsec 0.51083 0.43922 1.163 0.25463
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 2.578 on 28 degrees of freedom
Multiple R-squared: 0.8348, Adjusted R-squared: 0.8171
F-statistic: 47.15 on 3 and 28 DF, p-value: 4.506e-11
# Plot diagnostics
par(mfrow = c(2, 2))
plot(model)
This example shows: - A multiple linear regression model predicting
MPG from weight, horsepower, and quarter-mile time - The summary output
with coefficients, R-squared, and p-values - Diagnostic plots for
assessing model assumptions
Exercises
Using the boston
dataset from the MASS package,
create a simple linear regression model to predict median house value
(medv) based on the average number of rooms (rm). Plot the data points
and the regression line, and interpret the coefficients.
With the mtcars
dataset, build a multiple linear
regression model to predict quarter-mile time (qsec) using horsepower
(hp), weight (wt), and transmission type (am). Interpret the results and
check the model assumptions using diagnostic plots.
Using the diamonds
dataset, create a linear
regression model to predict the price of a diamond based on its carat,
cut, color, and clarity. Use dummy variables for categorical predictors.
Interpret the results and discuss which factors have the most
significant impact on diamond prices.
5. Classification
Explanation
Classification is a supervised machine learning technique used to
predict categorical outcomes. It involves training a model on labeled
data to learn the relationship between features and class labels, then
using that model to predict the class of new, unseen instances.
Common classification algorithms include: 1. Logistic Regression 2.
k-Nearest Neighbors (k-NN) 3. Decision Trees 4. Random Forests 5.
Support Vector Machines (SVM) 6. Neural Networks
Key concepts in classification: - Features: Input variables used to
make predictions - Target variable: The categorical outcome we’re trying
to predict - Training and testing sets: Data used to build and evaluate
the model - Confusion matrix: Table showing correct and incorrect
predictions - Accuracy, precision, recall, and F1-score: Metrics for
evaluating model performance
Example
Let’s perform a more complex classification task using the iris
dataset and a Random Forest classifier:
# Load necessary library
library(randomForest)
# Split the data
set.seed(123)
train_indices <- createDataPartition(iris$Species, p = 0.7, list = FALSE)
train_data <- iris[train_indices, ]
test_data <- iris[-train_indices, ]
# Train a Random Forest model
rf_model <- randomForest(Species ~ ., data = train_data, ntree = 100)
# Make predictions
predictions <- predict(rf_model, newdata = test_data)
# Evaluate the model
confusionMatrix(predictions, test_data$Species)
Confusion Matrix and Statistics
Reference
Prediction setosa versicolor virginica
setosa 15 0 0
versicolor 0 14 2
virginica 0 1 13
Overall Statistics
Accuracy : 0.9333
95% CI : (0.8173, 0.986)
No Information Rate : 0.3333
P-Value [Acc > NIR] : < 2.2e-16
Kappa : 0.9
Mcnemar's Test P-Value : NA
Statistics by Class:
Class: setosa Class: versicolor Class: virginica
Sensitivity 1.0000 0.9333 0.8667
Specificity 1.0000 0.9333 0.9667
Pos Pred Value 1.0000 0.8750 0.9286
Neg Pred Value 1.0000 0.9655 0.9355
Prevalence 0.3333 0.3333 0.3333
Detection Rate 0.3333 0.3111 0.2889
Detection Prevalence 0.3333 0.3556 0.3111
Balanced Accuracy 1.0000 0.9333 0.9167
# Plot feature importance
varImpPlot(rf_model, main = "Feature Importance in Random Forest Model")
This example demonstrates: - Training a Random Forest classifier on
the iris dataset - Making predictions on a test set - Evaluating the
model using a confusion matrix - Visualizing feature importance
Exercises
Using the mtcars
dataset, create a logistic
regression model to predict whether a car has an automatic or manual
transmission (am) based on other features. Split the data into training
and testing sets, fit the model, make predictions, and evaluate its
performance using a confusion matrix and ROC curve.
With the wine
dataset from the rattle package, build
a k-Nearest Neighbors classifier to predict the wine type. Experiment
with different values of k and use cross-validation to select the best
model. Evaluate the final model’s performance on a held-out test
set.
Using the Default
dataset from the ISLR package,
create a decision tree classifier to predict whether a customer will
default on their credit card payment. Visualize the tree, interpret the
results, and evaluate the model’s performance. Then, compare its
performance to a random forest classifier on the same data.
LS0tCnRpdGxlOiAiV29ya2luZyB3aXRoIERhdGE6IEEgQ29tcHJlaGVuc2l2ZSBHdWlkZSB3aXRoIEV4ZXJjaXNlcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShjYXJldCkKbGlicmFyeSh0aWR5cikKbGlicmFyeShmb3JlY2FzdCkKbGlicmFyeShyYW5kb21Gb3Jlc3QpIApgYGAKCiMjIEludHJvZHVjdGlvbgoKVGhpcyBub3RlYm9vayBjb3ZlcnMgdmFyaW91cyBhc3BlY3RzIG9mIHdvcmtpbmcgd2l0aCBkYXRhLCBpbmNsdWRpbmcgZGF0YSB2aXN1YWxpemF0aW9uLCBmYWNldHMsIHRpbWUgc2VyaWVzIGFuYWx5c2lzLCBsaW5lYXIgcmVncmVzc2lvbiwgYW5kIGNsYXNzaWZpY2F0aW9uLiBXZSdsbCB1c2UgUiBhbmQgaXRzIHBvcHVsYXIgbGlicmFyaWVzIHRvIGRlbW9uc3RyYXRlIHRoZXNlIGNvbmNlcHRzLCBwcm92aWRlIGRldGFpbGVkIGV4cGxhbmF0aW9ucywgYW5kIG9mZmVyIGV4ZXJjaXNlcyBmb3IgcHJhY3RpY2UuCgojIyAxLiBEYXRhIFZpc3VhbGl6YXRpb24KCiMjIyBFeHBsYW5hdGlvbgoKRGF0YSB2aXN1YWxpemF0aW9uIGlzIHRoZSBncmFwaGljYWwgcmVwcmVzZW50YXRpb24gb2YgaW5mb3JtYXRpb24gYW5kIGRhdGEuIEl0IHVzZXMgc3RhdGlzdGljYWwgZ3JhcGhpY3MsIHBsb3RzLCBhbmQgb3RoZXIgdmlzdWFsIGVsZW1lbnRzIHRvIGNvbW11bmljYXRlIGNvbXBsZXggZGF0YSByZWxhdGlvbnNoaXBzIGFuZCBwYXR0ZXJucyBlZmZlY3RpdmVseS4gVGhlIGh1bWFuIGJyYWluIHByb2Nlc3NlcyB2aXN1YWwgaW5mb3JtYXRpb24gbXVjaCBmYXN0ZXIgdGhhbiB0ZXh0LCBtYWtpbmcgZGF0YSB2aXN1YWxpemF0aW9uIGFuIGVzc2VudGlhbCB0b29sIGZvciB1bmRlcnN0YW5kaW5nIGFuZCBwcmVzZW50aW5nIGRhdGEuCgpJbiBSLCB0aGUgJ2dncGxvdDInIHBhY2thZ2UgaXMgd2lkZWx5IHVzZWQgZm9yIGNyZWF0aW5nIGRhdGEgdmlzdWFsaXphdGlvbnMuIEl0J3MgYmFzZWQgb24gdGhlIEdyYW1tYXIgb2YgR3JhcGhpY3MsIGEgbGF5ZXJlZCBhcHByb2FjaCB0byBkZXNjcmliaW5nIGFuZCBjb25zdHJ1Y3RpbmcgdmlzdWFsaXphdGlvbnMuCgpLZXkgY29tcG9uZW50cyBvZiBhIGdncGxvdDoKMS4gRGF0YTogVGhlIGRhdGFzZXQgeW91J3JlIHZpc3VhbGl6aW5nCjIuIEFlc3RoZXRpY3MgKGFlcyk6IE1hcHBpbmcgb2YgdmFyaWFibGVzIHRvIHZpc3VhbCBwcm9wZXJ0aWVzIChlLmcuLCB4LWF4aXMsIHktYXhpcywgY29sb3IsIHNpemUpCjMuIEdlb21ldHJpZXMgKGdlb20pOiBUaGUgdHlwZSBvZiBwbG90IChlLmcuLCBwb2ludHMsIGxpbmVzLCBiYXJzKQo0LiBTY2FsZXM6IEhvdyB0aGUgZGF0YSBpcyBtYXBwZWQgdG8gdGhlIHZpc3VhbCBwcm9wZXJ0aWVzCjUuIEZhY2V0czogRGl2aXNpb24gb2YgdGhlIHBsb3QgaW50byBzdWJwbG90cyBiYXNlZCBvbiBjYXRlZ29yaWNhbCB2YXJpYWJsZXMKNi4gVGhlbWU6IE92ZXJhbGwgdmlzdWFsIHN0eWxlIG9mIHRoZSBwbG90CgojIyMgRXhhbXBsZQoKTGV0J3MgY3JlYXRlIGEgbW9yZSBjb21wbGV4IHZpc3VhbGl6YXRpb24gdXNpbmcgdGhlIG10Y2FycyBkYXRhc2V0OgoKYGBge3J9CmdncGxvdChtdGNhcnMsIGFlcyh4ID0gd3QsIHkgPSBtcGcsIGNvbG9yID0gZmFjdG9yKGN5bCksIHNpemUgPSBocCkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC43KSArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgc2UgPSBGQUxTRSkgKwogIHNjYWxlX2NvbG9yX3ZpcmlkaXNfZCgpICsKICBsYWJzKHRpdGxlID0gIkNhciBXZWlnaHQgdnMuIE1QRyIsCiAgICAgICBzdWJ0aXRsZSA9ICJDb2xvcmVkIGJ5IG51bWJlciBvZiBjeWxpbmRlcnMsIHNpemUgcmVwcmVzZW50cyBob3JzZXBvd2VyIiwKICAgICAgIHggPSAiV2VpZ2h0ICgxMDAwIGxicykiLAogICAgICAgeSA9ICJNaWxlcyBQZXIgR2FsbG9uIiwKICAgICAgIGNvbG9yID0gIkN5bGluZGVycyIsCiAgICAgICBzaXplID0gIkhvcnNlcG93ZXIiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKVGhpcyBwbG90IHNob3dzOgotIFRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB3ZWlnaHQgKHgtYXhpcykgYW5kIG1pbGVzIHBlciBnYWxsb24gKHktYXhpcykKLSBUaGUgbnVtYmVyIG9mIGN5bGluZGVycyAoY29sb3IpCi0gVGhlIGhvcnNlcG93ZXIgKHNpemUgb2YgcG9pbnRzKQotIEEgbGluZWFyIHJlZ3Jlc3Npb24gbGluZSBmb3IgZWFjaCBjeWxpbmRlciBncm91cAoKIyMjIEV4ZXJjaXNlcwoKMS4gVXNpbmcgdGhlIGJ1aWx0LWluIGBkaWFtb25kc2AgZGF0YXNldCwgY3JlYXRlIGEgc2NhdHRlciBwbG90IG9mIHByaWNlIHZzLiBjYXJhdC4gQ29sb3IgdGhlIHBvaW50cyBieSB0aGUgY3V0IHF1YWxpdHkgYW5kIGFkZCBhIHRpdGxlIGFuZCBwcm9wZXIgYXhpcyBsYWJlbHMuCgoyLiBXaXRoIHRoZSBgbXBnYCBkYXRhc2V0LCBjcmVhdGUgYSBib3ggcGxvdCBzaG93aW5nIHRoZSBkaXN0cmlidXRpb24gb2YgaGlnaHdheSBtaWxlcyBwZXIgZ2FsbG9uIChod3kpIGZvciBkaWZmZXJlbnQgY2FyIGNsYXNzZXMgKGNsYXNzKS4gQWRkIGNvbG9yIHRvIHRoZSBib3hlcyBhbmQgaW5jbHVkZSBhIHRpdGxlIGFuZCBheGlzIGxhYmVscy4KCjMuIFVzaW5nIHRoZSBgZWNvbm9taWNzYCBkYXRhc2V0LCBjcmVhdGUgYSBsaW5lIHBsb3Qgc2hvd2luZyBib3RoIHBlcnNvbmFsIHNhdmluZ3MgcmF0ZSAocHNhdmVydCkgYW5kIHVuZW1wbG95bWVudCByYXRlICh1ZW1wbWVkKSBvdmVyIHRpbWUuIFVzZSB0d28gZGlmZmVyZW50IHktYXhlcyAoaGludDogbG9vayB1cCBgc2VjX2F4aXMoKWApLiBJbmNsdWRlIGEgbGVnZW5kLCB0aXRsZSwgYW5kIGFwcHJvcHJpYXRlIGxhYmVscy4KCiMjIDIuIEZhY2V0cwoKIyMjIEV4cGxhbmF0aW9uCgpGYWNldGluZyBpcyBhIHRlY2huaXF1ZSB1c2VkIHRvIHNwbGl0IGEgcGxvdCBpbnRvIG11bHRpcGxlIHN1YnBsb3RzIGJhc2VkIG9uIG9uZSBvciBtb3JlIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gVGhpcyBhbGxvd3MgZm9yIGVhc3kgY29tcGFyaXNvbiBvZiBwYXR0ZXJucyBhY3Jvc3MgZGlmZmVyZW50IHN1Ymdyb3VwcyBpbiB0aGUgZGF0YS4gSW4gZ2dwbG90MiwgdGhlcmUgYXJlIHR3byBtYWluIGZhY2V0aW5nIGZ1bmN0aW9uczoKCjEuIGBmYWNldF93cmFwKClgOiBBcnJhbmdlcyBhIHNlcmllcyBvZiBwbG90cyBpbnRvIGEgMkQgZ3JpZCwgIndyYXBwaW5nIiBhcm91bmQgd2l0aCBhIHNwZWNpZmllZCBudW1iZXIgb2Ygcm93cyBvciBjb2x1bW5zLgoyLiBgZmFjZXRfZ3JpZCgpYDogQ3JlYXRlcyBhIGdyaWQgb2YgcGxvdHMgd2l0aCByb3dzIGFuZCBjb2x1bW5zIGJhc2VkIG9uIGRpZmZlcmVudCBjYXRlZ29yaWNhbCB2YXJpYWJsZXMuCgpGYWNldGluZyBpcyBwYXJ0aWN1bGFybHkgdXNlZnVsIHdoZW4geW91IHdhbnQgdG86Ci0gQ29tcGFyZSB0cmVuZHMgb3IgcGF0dGVybnMgYWNyb3NzIGRpZmZlcmVudCBjYXRlZ29yaWVzCi0gVmlzdWFsaXplIGhvdyByZWxhdGlvbnNoaXBzIGNoYW5nZSBhY3Jvc3Mgc3ViZ3JvdXBzCi0gRGlzcGxheSBtdWx0aXBsZSByZWxhdGVkIHBsb3RzIGluIGEgY29tcGFjdCBmb3JtYXQKCiMjIyBFeGFtcGxlCgpMZXQncyBjcmVhdGUgYSBmYWNldGVkIHBsb3QgdXNpbmcgdGhlIGBtcGdgIGRhdGFzZXQ6CgpgYGB7cn0KZ2dwbG90KG1wZywgYWVzKHggPSBkaXNwbCwgeSA9IGh3eSkpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGNsYXNzKSwgYWxwaGEgPSAwLjcpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEZBTFNFLCBjb2xvciA9ICJibGFjayIpICsKICBmYWNldF93cmFwKH4gbWFudWZhY3R1cmVyLCBzY2FsZXMgPSAiZnJlZSIpICsKICBsYWJzKHRpdGxlID0gIkhpZ2h3YXkgTVBHIHZzLiBFbmdpbmUgRGlzcGxhY2VtZW50IiwKICAgICAgIHN1YnRpdGxlID0gIkZhY2V0ZWQgYnkgbWFudWZhY3R1cmVyLCBjb2xvcmVkIGJ5IHZlaGljbGUgY2xhc3MiLAogICAgICAgeCA9ICJFbmdpbmUgRGlzcGxhY2VtZW50IChMKSIsCiAgICAgICB5ID0gIkhpZ2h3YXkgTVBHIiwKICAgICAgIGNvbG9yID0gIlZlaGljbGUgQ2xhc3MiKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKYGBgCgpUaGlzIHBsb3Qgc2hvd3M6Ci0gVGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGVuZ2luZSBkaXNwbGFjZW1lbnQgYW5kIGhpZ2h3YXkgTVBHCi0gRmFjZXRzIGZvciBlYWNoIGNhciBtYW51ZmFjdHVyZXIKLSBDb2xvcnMgcmVwcmVzZW50aW5nIGRpZmZlcmVudCB2ZWhpY2xlIGNsYXNzZXMKLSBBIGxpbmVhciByZWdyZXNzaW9uIGxpbmUgZm9yIGVhY2ggbWFudWZhY3R1cmVyCgojIyMgRXhlcmNpc2VzCgoxLiBVc2luZyB0aGUgYGRpYW1vbmRzYCBkYXRhc2V0LCBjcmVhdGUgYSBmYWNldGVkIGhpc3RvZ3JhbSBvZiBkaWFtb25kIHByaWNlcy4gRmFjZXQgYnkgY3V0IHF1YWxpdHkgYW5kIGZpbGwgdGhlIGhpc3RvZ3JhbXMgYnkgZGlhbW9uZCBjb2xvci4gQWRkIGFwcHJvcHJpYXRlIGxhYmVscyBhbmQgYSB0aXRsZS4KCjIuIFdpdGggdGhlIGBtcGdgIGRhdGFzZXQsIGNyZWF0ZSBhIGZhY2V0ZWQgc2NhdHRlciBwbG90IG9mIGNpdHkgTVBHIHZzLiBoaWdod2F5IE1QRy4gRmFjZXQgYnkgdGhlIG51bWJlciBvZiBjeWxpbmRlcnMgYW5kIGNvbG9yIHRoZSBwb2ludHMgYnkgdGhlIHR5cGUgb2YgZHJpdmUgKGRydikuIEluY2x1ZGUgYSByZWdyZXNzaW9uIGxpbmUgZm9yIGVhY2ggZmFjZXQuCgozLiBVc2luZyB0aGUgYHR4aG91c2luZ2AgZGF0YXNldCwgY3JlYXRlIGEgZmFjZXRlZCBsaW5lIHBsb3Qgc2hvd2luZyB0aGUgbWVkaWFuIGhvdXNpbmcgcHJpY2Ugb3ZlciB0aW1lIGZvciBkaWZmZXJlbnQgY2l0aWVzLiBVc2UgYGZhY2V0X3dyYXAoKWAgdG8gY3JlYXRlIGEgM3gzIGdyaWQgb2YgcGxvdHMgZm9yIHRoZSB0b3AgOSBjaXRpZXMgYnkgbWVkaWFuIGhvdXNpbmcgcHJpY2UuIEFkZCBhcHByb3ByaWF0ZSBsYWJlbHMgYW5kIGEgdGl0bGUuCgojIyAzLiBUaW1lIFNlcmllcwoKIyMjIEV4cGxhbmF0aW9uCgpUaW1lIHNlcmllcyBhbmFseXNpcyBpbnZvbHZlcyBzdHVkeWluZyBkYXRhIHBvaW50cyBjb2xsZWN0ZWQgb3ZlciB0aW1lLCB0eXBpY2FsbHkgYXQgcmVndWxhciBpbnRlcnZhbHMuIEl0J3MgdXNlZCB0byBpZGVudGlmeSB0cmVuZHMsIHNlYXNvbmFsIHBhdHRlcm5zLCBhbmQgb3RoZXIgdGltZS1kZXBlbmRlbnQgc3RydWN0dXJlcyBpbiB0aGUgZGF0YS4gS2V5IGNvbXBvbmVudHMgb2YgdGltZSBzZXJpZXMgYW5hbHlzaXMgaW5jbHVkZToKCjEuIFRyZW5kOiBMb25nLXRlcm0gaW5jcmVhc2Ugb3IgZGVjcmVhc2UgaW4gdGhlIGRhdGEKMi4gU2Vhc29uYWxpdHk6IFJlcGVhdGluZyBwYXR0ZXJucyBhdCBmaXhlZCBpbnRlcnZhbHMKMy4gQ3ljbGljYWwgcGF0dGVybnM6IEZsdWN0dWF0aW9ucyBub3QgdGllZCB0byBhIGZpeGVkIHBlcmlvZAo0LiBSYW5kb20gdmFyaWF0aW9uczogVW5leHBsYWluZWQgdmFyaWFiaWxpdHkgaW4gdGhlIGRhdGEKCkluIFIsIHRpbWUgc2VyaWVzIGRhdGEgaXMgb2Z0ZW4gc3RvcmVkIGluIHNwZWNpYWxpemVkIG9iamVjdHMgbGlrZSBgdHNgIChiYXNlIFIpIG9yIGB4dHNgIChmcm9tIHRoZSB4dHMgcGFja2FnZSkuIFRoZSBgbHVicmlkYXRlYCBwYWNrYWdlIGlzIHVzZWZ1bCBmb3IgaGFuZGxpbmcgZGF0ZXMgYW5kIHRpbWVzLCB3aGlsZSBgZm9yZWNhc3RgIHByb3ZpZGVzIGZ1bmN0aW9ucyBmb3IgdGltZSBzZXJpZXMgZm9yZWNhc3RpbmcuCgpDb21tb24gdGVjaG5pcXVlcyBpbiB0aW1lIHNlcmllcyBhbmFseXNpcyBpbmNsdWRlOgotIE1vdmluZyBhdmVyYWdlcwotIEV4cG9uZW50aWFsIHNtb290aGluZwotIEFSSU1BIChBdXRvcmVncmVzc2l2ZSBJbnRlZ3JhdGVkIE1vdmluZyBBdmVyYWdlKSBtb2RlbHMKLSBEZWNvbXBvc2l0aW9uIG9mIHRpbWUgc2VyaWVzIGludG8gdHJlbmQsIHNlYXNvbmFsLCBhbmQgcmVzaWR1YWwgY29tcG9uZW50cwoKIyMjIEV4YW1wbGUKCkxldCdzIGNyZWF0ZSBhIG1vcmUgY29tcGxleCB0aW1lIHNlcmllcyBleGFtcGxlIHVzaW5nIHRoZSBgQWlyUGFzc2VuZ2Vyc2AgZGF0YXNldDoKCmBgYHtyfQpkYXRhKCJBaXJQYXNzZW5nZXJzIikKYXBfZGYgPC0gZGF0YS5mcmFtZSgKICBkYXRlID0gc2VxKGFzLkRhdGUoIjE5NDktMDEtMDEiKSwgYXMuRGF0ZSgiMTk2MC0xMi0wMSIpLCBieSA9ICJtb250aCIpLAogIHBhc3NlbmdlcnMgPSBhcy5udW1lcmljKEFpclBhc3NlbmdlcnMpCikKCiMgRGVjb21wb3NlIHRoZSB0aW1lIHNlcmllcwphcF9kZWNvbXAgPC0gZGVjb21wb3NlKEFpclBhc3NlbmdlcnMpCgojIFBsb3QgdGhlIG9yaWdpbmFsIHNlcmllcyBhbmQgaXRzIGNvbXBvbmVudHMKcGFyKG1mcm93ID0gYyg0LCAxKSwgbWFyID0gYygyLCAyLCAyLCAyKSkKcGxvdChhcF9kZWNvbXAkeCwgbWFpbiA9ICJPcmlnaW5hbCBUaW1lIFNlcmllcyIsIHlsYWIgPSAiUGFzc2VuZ2VycyIpCnBsb3QoYXBfZGVjb21wJHRyZW5kLCBtYWluID0gIlRyZW5kIiwgeWxhYiA9ICJUcmVuZCIpCnBsb3QoYXBfZGVjb21wJHNlYXNvbmFsLCBtYWluID0gIlNlYXNvbmFsIiwgeWxhYiA9ICJTZWFzb25hbCIpCnBsb3QoYXBfZGVjb21wJHJhbmRvbSwgbWFpbiA9ICJSYW5kb20iLCB5bGFiID0gIlJhbmRvbSIpCmBgYAoKVGhpcyBleGFtcGxlIHNob3dzOgotIFRoZSBvcmlnaW5hbCB0aW1lIHNlcmllcyBvZiBhaXJsaW5lIHBhc3NlbmdlcnMKLSBUaGUgZGVjb21wb3NpdGlvbiBvZiB0aGUgc2VyaWVzIGludG8gdHJlbmQsIHNlYXNvbmFsLCBhbmQgcmFuZG9tIGNvbXBvbmVudHMKCiMjIyBFeGVyY2lzZXMKCjEuIFVzaW5nIHRoZSBgZWNvbm9taWNzYCBkYXRhc2V0LCBjcmVhdGUgYSB0aW1lIHNlcmllcyBwbG90IG9mIHRoZSBwZXJzb25hbCBzYXZpbmdzIHJhdGUgKHBzYXZlcnQpIGZyb20gMTk2NyB0byAyMDE1LiBBZGQgYSB0cmVuZCBsaW5lIHVzaW5nIGEgc2ltcGxlIG1vdmluZyBhdmVyYWdlIHdpdGggYSB3aW5kb3cgb2YgMTIgbW9udGhzLgoKMi4gV2l0aCB0aGUgYG5odGVtcGAgZGF0YXNldCAoYXZlcmFnZSB5ZWFybHkgdGVtcGVyYXR1cmVzIGluIE5ldyBIYXZlbiksIGNyZWF0ZSBhIHRpbWUgc2VyaWVzIHBsb3QgYW5kIGRlY29tcG9zZSBpdCBpbnRvIHRyZW5kLCBzZWFzb25hbCwgYW5kIHJhbmRvbSBjb21wb25lbnRzLiBQbG90IGVhY2ggY29tcG9uZW50IHNlcGFyYXRlbHkuCgozLiBVc2luZyB0aGUgYGNvMmAgZGF0YXNldCAoYXRtb3NwaGVyaWMgQ08yIGNvbmNlbnRyYXRpb25zKSwgY3JlYXRlIGEgdGltZSBzZXJpZXMgZm9yZWNhc3QgZm9yIHRoZSBuZXh0IDI0IG1vbnRocyB1c2luZyBhbiBhcHByb3ByaWF0ZSBtb2RlbCAoZS5nLiwgQVJJTUEpLiBQbG90IHRoZSBvcmlnaW5hbCBkYXRhLCB0aGUgZml0dGVkIHZhbHVlcywgYW5kIHRoZSBmb3JlY2FzdCB3aXRoIHByZWRpY3Rpb24gaW50ZXJ2YWxzLgoKIyMgNC4gTGluZWFyIFJlZ3Jlc3Npb24KCiMjIyBFeHBsYW5hdGlvbgoKTGluZWFyIHJlZ3Jlc3Npb24gaXMgYSBzdGF0aXN0aWNhbCBtZXRob2QgdXNlZCB0byBtb2RlbCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gYSBkZXBlbmRlbnQgdmFyaWFibGUgYW5kIG9uZSBvciBtb3JlIGluZGVwZW5kZW50IHZhcmlhYmxlcy4gSW4gaXRzIHNpbXBsZXN0IGZvcm0gKHNpbXBsZSBsaW5lYXIgcmVncmVzc2lvbiksIGl0IGFzc3VtZXMgYSBsaW5lYXIgcmVsYXRpb25zaGlwIGJldHdlZW4gdHdvIHZhcmlhYmxlczoKCnkgPSDOsuKCgCArIM6y4oKBeCArIM61CgpXaGVyZToKLSB5IGlzIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUKLSB4IGlzIHRoZSBpbmRlcGVuZGVudCB2YXJpYWJsZQotIM6y4oKAIGlzIHRoZSB5LWludGVyY2VwdAotIM6y4oKBIGlzIHRoZSBzbG9wZQotIM61IGlzIHRoZSBlcnJvciB0ZXJtCgpNdWx0aXBsZSBsaW5lYXIgcmVncmVzc2lvbiBleHRlbmRzIHRoaXMgdG8gbXVsdGlwbGUgaW5kZXBlbmRlbnQgdmFyaWFibGVzOgoKeSA9IM6y4oKAICsgzrLigoF44oKBICsgzrLigoJ44oKCICsgLi4uICsgzrLigpp44oKaICsgzrUKCktleSBjb25jZXB0cyBpbiBsaW5lYXIgcmVncmVzc2lvbjoKMS4gQ29lZmZpY2llbnRzOiBFc3RpbWF0ZWQgdXNpbmcgdGhlIG1ldGhvZCBvZiBsZWFzdCBzcXVhcmVzCjIuIFItc3F1YXJlZDogTWVhc3VyZSBvZiBob3cgd2VsbCB0aGUgbW9kZWwgZml0cyB0aGUgZGF0YQozLiBSZXNpZHVhbHM6IERpZmZlcmVuY2VzIGJldHdlZW4gb2JzZXJ2ZWQgYW5kIHByZWRpY3RlZCB2YWx1ZXMKNC4gQXNzdW1wdGlvbnM6IExpbmVhcml0eSwgaW5kZXBlbmRlbmNlLCBob21vc2NlZGFzdGljaXR5LCBub3JtYWxpdHkgb2YgcmVzaWR1YWxzCgojIyMgRXhhbXBsZQoKTGV0J3MgcGVyZm9ybSBhIG11bHRpcGxlIGxpbmVhciByZWdyZXNzaW9uIHVzaW5nIHRoZSBtdGNhcnMgZGF0YXNldDoKCmBgYHtyfQojIEZpdCBhIG11bHRpcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsCm1vZGVsIDwtIGxtKG1wZyB+IHd0ICsgaHAgKyBxc2VjLCBkYXRhID0gbXRjYXJzKQoKIyBQcmludCB0aGUgc3VtbWFyeQpzdW1tYXJ5KG1vZGVsKQoKIyBQbG90IGRpYWdub3N0aWNzCnBhcihtZnJvdyA9IGMoMiwgMikpCnBsb3QobW9kZWwpCmBgYAoKVGhpcyBleGFtcGxlIHNob3dzOgotIEEgbXVsdGlwbGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgcHJlZGljdGluZyBNUEcgZnJvbSB3ZWlnaHQsIGhvcnNlcG93ZXIsIGFuZCBxdWFydGVyLW1pbGUgdGltZQotIFRoZSBzdW1tYXJ5IG91dHB1dCB3aXRoIGNvZWZmaWNpZW50cywgUi1zcXVhcmVkLCBhbmQgcC12YWx1ZXMKLSBEaWFnbm9zdGljIHBsb3RzIGZvciBhc3Nlc3NpbmcgbW9kZWwgYXNzdW1wdGlvbnMKCiMjIyBFeGVyY2lzZXMKCjEuIFVzaW5nIHRoZSBgYm9zdG9uYCBkYXRhc2V0IGZyb20gdGhlIE1BU1MgcGFja2FnZSwgY3JlYXRlIGEgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHRvIHByZWRpY3QgbWVkaWFuIGhvdXNlIHZhbHVlIChtZWR2KSBiYXNlZCBvbiB0aGUgYXZlcmFnZSBudW1iZXIgb2Ygcm9vbXMgKHJtKS4gUGxvdCB0aGUgZGF0YSBwb2ludHMgYW5kIHRoZSByZWdyZXNzaW9uIGxpbmUsIGFuZCBpbnRlcnByZXQgdGhlIGNvZWZmaWNpZW50cy4KCjIuIFdpdGggdGhlIGBtdGNhcnNgIGRhdGFzZXQsIGJ1aWxkIGEgbXVsdGlwbGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgdG8gcHJlZGljdCBxdWFydGVyLW1pbGUgdGltZSAocXNlYykgdXNpbmcgaG9yc2Vwb3dlciAoaHApLCB3ZWlnaHQgKHd0KSwgYW5kIHRyYW5zbWlzc2lvbiB0eXBlIChhbSkuIEludGVycHJldCB0aGUgcmVzdWx0cyBhbmQgY2hlY2sgdGhlIG1vZGVsIGFzc3VtcHRpb25zIHVzaW5nIGRpYWdub3N0aWMgcGxvdHMuCgozLiBVc2luZyB0aGUgYGRpYW1vbmRzYCBkYXRhc2V0LCBjcmVhdGUgYSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCB0byBwcmVkaWN0IHRoZSBwcmljZSBvZiBhIGRpYW1vbmQgYmFzZWQgb24gaXRzIGNhcmF0LCBjdXQsIGNvbG9yLCBhbmQgY2xhcml0eS4gVXNlIGR1bW15IHZhcmlhYmxlcyBmb3IgY2F0ZWdvcmljYWwgcHJlZGljdG9ycy4gSW50ZXJwcmV0IHRoZSByZXN1bHRzIGFuZCBkaXNjdXNzIHdoaWNoIGZhY3RvcnMgaGF2ZSB0aGUgbW9zdCBzaWduaWZpY2FudCBpbXBhY3Qgb24gZGlhbW9uZCBwcmljZXMuCgojIyA1LiBDbGFzc2lmaWNhdGlvbgoKIyMjIEV4cGxhbmF0aW9uCgpDbGFzc2lmaWNhdGlvbiBpcyBhIHN1cGVydmlzZWQgbWFjaGluZSBsZWFybmluZyB0ZWNobmlxdWUgdXNlZCB0byBwcmVkaWN0IGNhdGVnb3JpY2FsIG91dGNvbWVzLiBJdCBpbnZvbHZlcyB0cmFpbmluZyBhIG1vZGVsIG9uIGxhYmVsZWQgZGF0YSB0byBsZWFybiB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gZmVhdHVyZXMgYW5kIGNsYXNzIGxhYmVscywgdGhlbiB1c2luZyB0aGF0IG1vZGVsIHRvIHByZWRpY3QgdGhlIGNsYXNzIG9mIG5ldywgdW5zZWVuIGluc3RhbmNlcy4KCkNvbW1vbiBjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG1zIGluY2x1ZGU6CjEuIExvZ2lzdGljIFJlZ3Jlc3Npb24KMi4gay1OZWFyZXN0IE5laWdoYm9ycyAoay1OTikKMy4gRGVjaXNpb24gVHJlZXMKNC4gUmFuZG9tIEZvcmVzdHMKNS4gU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMgKFNWTSkKNi4gTmV1cmFsIE5ldHdvcmtzCgpLZXkgY29uY2VwdHMgaW4gY2xhc3NpZmljYXRpb246Ci0gRmVhdHVyZXM6IElucHV0IHZhcmlhYmxlcyB1c2VkIHRvIG1ha2UgcHJlZGljdGlvbnMKLSBUYXJnZXQgdmFyaWFibGU6IFRoZSBjYXRlZ29yaWNhbCBvdXRjb21lIHdlJ3JlIHRyeWluZyB0byBwcmVkaWN0Ci0gVHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0czogRGF0YSB1c2VkIHRvIGJ1aWxkIGFuZCBldmFsdWF0ZSB0aGUgbW9kZWwKLSBDb25mdXNpb24gbWF0cml4OiBUYWJsZSBzaG93aW5nIGNvcnJlY3QgYW5kIGluY29ycmVjdCBwcmVkaWN0aW9ucwotIEFjY3VyYWN5LCBwcmVjaXNpb24sIHJlY2FsbCwgYW5kIEYxLXNjb3JlOiBNZXRyaWNzIGZvciBldmFsdWF0aW5nIG1vZGVsIHBlcmZvcm1hbmNlCgojIyMgRXhhbXBsZQoKTGV0J3MgcGVyZm9ybSBhIG1vcmUgY29tcGxleCBjbGFzc2lmaWNhdGlvbiB0YXNrIHVzaW5nIHRoZSBpcmlzIGRhdGFzZXQgYW5kIGEgUmFuZG9tIEZvcmVzdCBjbGFzc2lmaWVyOgoKYGBge3J9CiMgTG9hZCBuZWNlc3NhcnkgbGlicmFyeQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKCiMgU3BsaXQgdGhlIGRhdGEKc2V0LnNlZWQoMTIzKQp0cmFpbl9pbmRpY2VzIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oaXJpcyRTcGVjaWVzLCBwID0gMC43LCBsaXN0ID0gRkFMU0UpCnRyYWluX2RhdGEgPC0gaXJpc1t0cmFpbl9pbmRpY2VzLCBdCnRlc3RfZGF0YSA8LSBpcmlzWy10cmFpbl9pbmRpY2VzLCBdCgojIFRyYWluIGEgUmFuZG9tIEZvcmVzdCBtb2RlbApyZl9tb2RlbCA8LSByYW5kb21Gb3Jlc3QoU3BlY2llcyB+IC4sIGRhdGEgPSB0cmFpbl9kYXRhLCBudHJlZSA9IDEwMCkKCiMgTWFrZSBwcmVkaWN0aW9ucwpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KHJmX21vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhKQoKIyBFdmFsdWF0ZSB0aGUgbW9kZWwKY29uZnVzaW9uTWF0cml4KHByZWRpY3Rpb25zLCB0ZXN0X2RhdGEkU3BlY2llcykKCiMgUGxvdCBmZWF0dXJlIGltcG9ydGFuY2UKdmFySW1wUGxvdChyZl9tb2RlbCwgbWFpbiA9ICJGZWF0dXJlIEltcG9ydGFuY2UgaW4gUmFuZG9tIEZvcmVzdCBNb2RlbCIpCmBgYAoKVGhpcyBleGFtcGxlIGRlbW9uc3RyYXRlczoKLSBUcmFpbmluZyBhIFJhbmRvbSBGb3Jlc3QgY2xhc3NpZmllciBvbiB0aGUgaXJpcyBkYXRhc2V0Ci0gTWFraW5nIHByZWRpY3Rpb25zIG9uIGEgdGVzdCBzZXQKLSBFdmFsdWF0aW5nIHRoZSBtb2RlbCB1c2luZyBhIGNvbmZ1c2lvbiBtYXRyaXgKLSBWaXN1YWxpemluZyBmZWF0dXJlIGltcG9ydGFuY2UKCiMjIyBFeGVyY2lzZXMKCjEuIFVzaW5nIHRoZSBgbXRjYXJzYCBkYXRhc2V0LCBjcmVhdGUgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHRvIHByZWRpY3Qgd2hldGhlciBhIGNhciBoYXMgYW4gYXV0b21hdGljIG9yIG1hbnVhbCB0cmFuc21pc3Npb24gKGFtKSBiYXNlZCBvbiBvdGhlciBmZWF0dXJlcy4gU3BsaXQgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzLCBmaXQgdGhlIG1vZGVsLCBtYWtlIHByZWRpY3Rpb25zLCBhbmQgZXZhbHVhdGUgaXRzIHBlcmZvcm1hbmNlIHVzaW5nIGEgY29uZnVzaW9uIG1hdHJpeCBhbmQgUk9DIGN1cnZlLgoKMi4gV2l0aCB0aGUgYHdpbmVgIGRhdGFzZXQgZnJvbSB0aGUgcmF0dGxlIHBhY2thZ2UsIGJ1aWxkIGEgay1OZWFyZXN0IE5laWdoYm9ycyBjbGFzc2lmaWVyIHRvIHByZWRpY3QgdGhlIHdpbmUgdHlwZS4gRXhwZXJpbWVudCB3aXRoIGRpZmZlcmVudCB2YWx1ZXMgb2YgayBhbmQgdXNlIGNyb3NzLXZhbGlkYXRpb24gdG8gc2VsZWN0IHRoZSBiZXN0IG1vZGVsLiBFdmFsdWF0ZSB0aGUgZmluYWwgbW9kZWwncyBwZXJmb3JtYW5jZSBvbiBhIGhlbGQtb3V0IHRlc3Qgc2V0LgoKMy4gVXNpbmcgdGhlIGBEZWZhdWx0YCBkYXRhc2V0IGZyb20gdGhlIElTTFIgcGFja2FnZSwgY3JlYXRlIGEgZGVjaXNpb24gdHJlZSBjbGFzc2lmaWVyIHRvIHByZWRpY3Qgd2hldGhlciBhIGN1c3RvbWVyIHdpbGwgZGVmYXVsdCBvbiB0aGVpciBjcmVkaXQgY2FyZCBwYXltZW50LiBWaXN1YWxpemUgdGhlIHRyZWUsIGludGVycHJldCB0aGUgcmVzdWx0cywgYW5kIGV2YWx1YXRlIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlLiBUaGVuLCBjb21wYXJlIGl0cyBwZXJmb3JtYW5jZSB0byBhIHJhbmRvbSBmb3Jlc3QgY2xhc3NpZmllciBvbiB0aGUgc2FtZSBkYXRhLgoK