In this case study, our objective is to predict the sales price of a home. This is a regression problem since the goal is to predict any real number across some spectrum ($119,201, $168,594, $301,446, etc). To predict the sales price, we will use numeric and categorical features of the home.
Throughout this case study you will learn a few new concepts:
- Vectorization and standardization of tabular features
- Adjusting batch size & epochs for training performance
- What callbacks are and how to start applying them
- Early stopping
- Controlling the learning rate
- Knowing when to adjust model capacity
Package requirements
library(keras) # for deep learning
library(testthat) # unit testing
library(tidyverse) # for dplyr, ggplot2, etc.
library(rsample) # for data splitting
library(recipes) # for feature engineering
The Ames housing dataset
For this case study we will use the Ames housing dataset provided by the AmesHousing package.
ames <- AmesHousing::make_ames()
dim(ames)
[1] 2930 81
Understanding our data
This data has been partially cleaned up and has no missing data:
sum(is.na(ames))
[1] 0
But this tabular data is a combination of numeric and categorical data that we need to address.
str(ames)
The numeric variables are on different scales. For example:
ames %>%
select(Lot_Area, Lot_Frontage, Year_Built, Gr_Liv_Area, Garage_Cars, Mo_Sold) %>%
gather(feature, value) %>%
ggplot(aes(feature, value)) +
geom_boxplot() +
scale_y_log10(labels = scales::comma)
There are categorical features that could be ordered:
ames %>%
select(matches("(Qual|Cond|QC|Qu)$")) %>%
str()
Classes ‘tbl_df’, ‘tbl’ and 'data.frame': 2930 obs. of 12 variables:
$ Overall_Qual: Factor w/ 10 levels "Very_Poor","Poor",..: 6 5 6 7 5 6 8 8 8 7 ...
$ Overall_Cond: Factor w/ 10 levels "Very_Poor","Poor",..: 5 6 6 5 5 6 5 5 5 5 ...
$ Exter_Qual : Factor w/ 4 levels "Excellent","Fair",..: 4 4 4 3 4 4 3 3 3 4 ...
$ Exter_Cond : Factor w/ 5 levels "Excellent","Fair",..: 5 5 5 5 5 5 5 5 5 5 ...
$ Bsmt_Qual : Factor w/ 6 levels "Excellent","Fair",..: 6 6 6 6 3 6 3 3 3 6 ...
$ Bsmt_Cond : Factor w/ 6 levels "Excellent","Fair",..: 3 6 6 6 6 6 6 6 6 6 ...
$ Heating_QC : Factor w/ 5 levels "Excellent","Fair",..: 2 5 5 1 3 1 1 1 1 3 ...
$ Kitchen_Qual: Factor w/ 5 levels "Excellent","Fair",..: 5 5 3 1 5 3 3 3 3 3 ...
$ Fireplace_Qu: Factor w/ 6 levels "Excellent","Fair",..: 3 4 4 6 6 3 4 4 6 6 ...
$ Garage_Qual : Factor w/ 6 levels "Excellent","Fair",..: 6 6 6 6 6 6 6 6 6 6 ...
$ Garage_Cond : Factor w/ 6 levels "Excellent","Fair",..: 6 6 6 6 6 6 6 6 6 6 ...
$ Pool_QC : Factor w/ 5 levels "Excellent","Fair",..: 4 4 4 4 4 4 4 4 4 4 ...
And some of the categorical features have many levels:
ames %>%
select_if(~ is.factor(.) & length(levels(.)) > 8) %>%
str()
Classes ‘tbl_df’, ‘tbl’ and 'data.frame': 2930 obs. of 8 variables:
$ MS_SubClass : Factor w/ 16 levels "One_Story_1946_and_Newer_All_Styles",..: 1 1 1 1 6 6 12 12 12 6 ...
$ Neighborhood: Factor w/ 28 levels "North_Ames","College_Creek",..: 1 1 1 1 7 7 17 17 17 7 ...
$ Condition_1 : Factor w/ 9 levels "Artery","Feedr",..: 3 2 3 3 3 3 3 3 3 3 ...
$ Overall_Qual: Factor w/ 10 levels "Very_Poor","Poor",..: 6 5 6 7 5 6 8 8 8 7 ...
$ Overall_Cond: Factor w/ 10 levels "Very_Poor","Poor",..: 5 6 6 5 5 6 5 5 5 5 ...
$ Exterior_1st: Factor w/ 16 levels "AsbShng","AsphShn",..: 4 14 15 4 14 14 6 7 6 14 ...
$ Exterior_2nd: Factor w/ 17 levels "AsbShng","AsphShn",..: 11 15 16 4 15 15 6 7 6 15 ...
$ Sale_Type : Factor w/ 10 levels "COD","Con","ConLD",..: 10 10 10 10 10 10 10 10 10 10 ...
Consequently, our first challenge is transforming this dataset into numeric tensors that our model can use.
Create train & test splits
One of the first things we want to do is create a train and test set as you probably noticed that we do not have a train and test set similar to how MNIST was already set up for us. We can use the rsample package to create our train and test datasets.
set.seed(123)
ames_split <- initial_split(ames, prop = 0.7)
ames_train <- analysis(ames_split)
ames_test <- assessment(ames_split)
dim(ames_train)
[1] 2051 81
dim(ames_test)
[1] 879 81
Preparing the data
All inputs and response values in a neural network must be tensors of either floating-point or integer data. Moreover, our feature values should not be relatively large compared to the randomized initial weights and all our features should take values in roughly the same range.
Consequently, we need to vectorize our data into a format conducive to neural networks ℹ️. For this data set, we’ll transform our data by:
- removing any zero-variance (or near zero-variance) features
- condensing unique levels of categorical features to “other”
- ordinal encoding the quality features
- normalize numeric feature distributions
- standardizing numeric features to mean = 0, std dev = 1
- one-hot encoding remaining categorical features
blueprint <- recipe(Sale_Price ~ ., data = ames_train) %>%
step_nzv(all_nominal()) %>%
step_other(all_nominal(), threshold = .01, other = "other") %>%
step_integer(matches("(Qual|Cond|QC|Qu)$")) %>%
step_YeoJohnson(all_numeric(), -all_outcomes()) %>%
step_center(all_numeric(), -all_outcomes()) %>%
step_scale(all_numeric(), -all_outcomes()) %>%
step_dummy(all_nominal(), -all_outcomes(), one_hot = TRUE)
blueprint
This next step computes any relavent information (mean and std deviation of numeric features, names of one-hot encoded features) on the training data so there is no information leakage from the test data.
prepare <- prep(blueprint, training = ames_train)
prepare
We can now vectorize our training and test data. If you scroll through the data you will notice that all features are now numeric and are either 0/1 (one hot encoded features) or have mean 0 and generally range between -3 and 3.
baked_train <- bake(prepare, new_data = ames_train)
baked_test <- bake(prepare, new_data = ames_test)
# unit testing to ensure all columns are numeric
expect_equal(map_lgl(baked_train, ~ !is.numeric(.)) %>% sum(), 0)
expect_equal(map_lgl(baked_test, ~ !is.numeric(.)) %>% sum(), 0)
baked_train
Lastly, we need to create the final feature and response objects for train and test data. Since keras and tensorflow require our features & labels to be seperate objects we need to separate them. In doing so, our features need to be a 2D tensor which is why we apply as.matrix
and our response needs to be a vector which is why we apply pull
.
x_train <- select(baked_train, -Sale_Price) %>% as.matrix()
y_train <- baked_train %>% pull(Sale_Price)
x_test <- select(baked_test, -Sale_Price) %>% as.matrix()
y_test <- baked_test %>% pull(Sale_Price)
# unit testing to x & y tensors have same number of observations
expect_equal(nrow(x_train), length(y_train))
expect_equal(nrow(x_test), length(y_test))
Our final feature set now has 188 input variables:
dim(x_train)
[1] 2051 188
dim(x_test)
[1] 879 188
Initial model
Our initial model looks fairly similar to the MNIST model we applied. However, note the following differences:
- Final output layer has
units = 1
and no activation function since this is a regression problem.
- We are using a different loss and metric function. The original Kaggle competition had you log the response variable and then use MSE. This is equivalent to using the MSLE loss function. ℹ️
- The batch size is much smaller
network <- keras_model_sequential() %>%
layer_dense(units = 128, activation = "relu", input_shape = ncol(x_train)) %>%
layer_dense(units = 128, activation = "relu") %>%
layer_dense(units = 1)
network %>% compile(
optimizer = "rmsprop",
loss = "msle",
metrics = c("mae")
)
summary(network)
Model: "sequential"
__________________________________________________________________________________________________________
Layer (type) Output Shape Param #
==========================================================================================================
dense (Dense) (None, 128) 24192
__________________________________________________________________________________________________________
dense_1 (Dense) (None, 128) 16512
__________________________________________________________________________________________________________
dense_2 (Dense) (None, 1) 129
==========================================================================================================
Total params: 40,833
Trainable params: 40,833
Non-trainable params: 0
__________________________________________________________________________________________________________
Our results below show two things:
- Our validation loss score has not reached a minimum.
- There are no signs of overfitting.
plot(history) + scale_y_log10()
Considerations regarding batch sizes and epochs
First, let’s discuss batch sizes and epochs as they can have a significant impact on how quickly we start reaching our minimum loss function.
Differences in performance for differing batch size largely depends on the underlying (and typically unknown) real cost function; however, here are some basic guidelines:
- batch sizes commonly take on values of \(2^s\) (i.e. 32, 64, 128, 256, 512),
- smaller batch sizes…
- can have more variability; however, this variability can help us stay out of local minimumns
- can decrease learning time
- larger batch sizes…
- have less variability but
- can increase learning time
- optimal batch sizes can be influenced by size of data:
- larger n can afford larger batch sizes (128, 256, 512)
- smaller n can afford smaller batch sizes (16, 32, 64)
- Which is best?
- I typically start with 32 or 64
- Trial and error for your specific problem
- use enough epochs so that our learning rate reaches a minimum
Here are some good articles discussing the impacts of batch size:
YOUR TURN! (5 min)
Try different batch sizes and epochs and see how model performance changes. Remember, batch sizes are typically powers of 2 (i.e. 16, 32, 64, 128, 256, 512).
network <- keras_model_sequential() %>%
layer_dense(units = 128, activation = "relu", input_shape = ____) %>%
layer_dense(units = 128, activation = "relu") %>%
layer_dense(units = ____)
network %>% compile(
optimizer = "rmsprop",
loss = "msle",
metrics = c("mae")
)
history <- network %>% fit(
x_train,
y_train,
epochs = ____,
batch_size = ____,
validation_split = 0.2
)
Early stopping
You likely noticed that a large batch size (i.e. 512) took many more epochs to start reaching a minimum versus a smaller batch size (i.e. 16). But regardless of batch size, you still had to do a fair amount of trial and error to find the right number of epochs to reach a minimum loss score.
Let’s meet our first callback, which can help in this situation ℹ️. Using early stopping allows us to crank up the number of epochs and let the training automatically stop after we experience no improvement in our loss after patience
number of epochs.
network <- keras_model_sequential() %>%
layer_dense(units = 128, activation = "relu", input_shape = ncol(x_train)) %>%
layer_dense(units = 128, activation = "relu") %>%
layer_dense(units = 1)
network %>%
compile(
optimizer = "rmsprop",
loss = "msle",
metrics = "mae"
)
history <- network %>% fit(
x_train,
y_train,
epochs = 250,
batch_size = 32,
validation_split = 0.2,
callbacks = callback_early_stopping(patience = 5, restore_best_weights = TRUE)
)
history
Trained on 1,640 samples (batch_size=32, epochs=171)
Final epoch (plot to see history):
loss: 0.01708
mae: 15,330
val_loss: 0.01508
val_mae: 16,540
cat("\nThe minimum loss score is", min(history$metrics$val_loss) %>% round(4),
"which occurred at epoch", which.min(history$metrics$val_loss))
The minimum loss score is 0.015 which occurred at epoch 166
plot(history) + scale_y_log10()
Adjustable learning rate
One thing you may notice is that there is significant learning happening for the first 15-20 epochs and then the model slowly chips away at the loss for the next ~100+ epochs. We can speed up this process with two things:
- customize our optimizer with a larger learning rate to try speed up the downhill traversal of the gradient descent
- add a callback that slowly reduces the learning rate by 20% if we don’t experience improvement in our loss for
patience
number of epochs.
Note how we are now using optimizer = optimizer_rmsprop(lr = 0.01)
instead of optimizer = "rmsprop"
so that we can customize the learning rate.
network <- keras_model_sequential() %>%
layer_dense(units = 128, activation = "relu", input_shape = ncol(x_train)) %>%
layer_dense(units = 128, activation = "relu") %>%
layer_dense(units = 1)
network %>%
compile(
optimizer = optimizer_rmsprop(lr = 0.01),
loss = "msle",
metrics = c("mae")
)
history <- network %>% fit(
x_train,
y_train,
epochs = 50,
batch_size = 32,
validation_split = 0.2,
callbacks = list(
callback_early_stopping(patience = 10, restore_best_weights = TRUE),
callback_reduce_lr_on_plateau(factor = 0.2, patience = 4)
)
)
Now we see a much faster process of model training; it only takes ~25% the number of epochs and it appears that we actually see an increase in performance!
history
Trained on 1,640 samples (batch_size=32, epochs=31)
Final epoch (plot to see history):
loss: 0.01483
mae: 14,258
val_loss: 0.01426
val_mae: 15,675
lr: 0.00008
cat("\nThe minimum loss score is", min(history$metrics$val_loss) %>% round(4),
"which occurred at epoch", which.min(history$metrics$val_loss))
The minimum loss score is 0.0142 which occurred at epoch 21
plot(history) +
scale_y_log10() +
scale_x_continuous(limits = c(0, length(history$metrics$val_loss)))
YOUR TURN! (5 min)
Try different variations of learning rate, patience parameters, and learning rate reduction factor. Here are a couple of things to keep in mind:
- optimizer learning rate ranges worth exploring differ depending on the optimizer but for RMSProp common ranges include 0.1-0.0001.
- learning rate reduction factor typically range from 0.5-0.1.
- the patience value for early stopping should not be shorter than the patience value for learning rate reduction, otherwise the learning rate will never have the opportunity to decrease.
network <- keras_model_sequential() %>%
layer_dense(units = 128, activation = ____, input_shape = ____) %>%
layer_dense(units = 128, activation = ____) %>%
layer_dense(units = ____)
network %>%
compile(
optimizer = optimizer_rmsprop(lr = ____),
loss = "msle",
metrics = c("mae")
)
history <- network %>% fit(
x_train,
y_train,
epochs = 50,
batch_size = 32,
validation_split = 0.2,
callbacks = list(
callback_early_stopping(patience = ____),
callback_reduce_lr_on_plateau(factor = ____, patience = ____)
)
)
Under capacity
Unfortunately, our model is still underfitting when we reach our minimum validation loss score. This is a classic sign that we are under-capacity. There are two ways to increase model capacity ℹ️:
- Add more units in each hidden layer
- Add more hidden layers
In the next module, we’ll look at how to dynamically assess these inputs but for now, spend the next 3 minutes adjusting the number of units in each hidden layer and/or adjusting the number of hidden layers.
YOUR TURN! (5 min)
Try different batch sizes and epochs and see how model performance changes. Remember, batch sizes are typically powers of 2 (i.e. 16, 32, 64, 128, 256, 512).
network <- keras_model_sequential() %>%
layer_dense(units = ____, activation = "relu", input_shape = ____) %>%
layer_dense(units = ____, activation = "relu") %>%
layer_dense(units = ____)
network %>%
compile(
optimizer = optimizer_rmsprop(lr = 0.01),
loss = "msle",
metrics = c("mae")
)
history <- network %>% fit(
x_train,
y_train,
epochs = 50,
batch_size = 32,
validation_split = 0.2,
callbacks = list(
callback_early_stopping(patience = 10, restore_best_weights = TRUE),
callback_reduce_lr_on_plateau(factor = 0.2, patience = 4)
)
)
What to look for
Typically, I add units and layers until I see significant overfitting or start to see high variability in our loss score or metrics and then constrain the model from there.
For example, the following model with 5 hidden layers consisting of 1024 units each overfits at the minimum validation loss score (but not much) and shows signs of loss and metric variability. From here, I would start to regularize the model by removing layers, reduce the number of units in each layer, or using an alternative regularization method until I find a happy compromise between model capacity, loss minimization & stability.
network <- keras_model_sequential() %>%
layer_dense(units = 1024, activation = "relu", input_shape = ncol(x_train)) %>%
layer_dense(units = 1024, activation = "relu") %>%
layer_dense(units = 1024, activation = "relu") %>%
layer_dense(units = 1024, activation = "relu") %>%
layer_dense(units = 1)
network %>%
compile(
optimizer = optimizer_rmsprop(lr = 0.01),
loss = "msle",
metrics = c("mae")
)
history <- network %>% fit(
x_train,
y_train,
epochs = 250,
batch_size = 32,
validation_split = 0.2,
callbacks = list(
callback_early_stopping(patience = 10, restore_best_weights = TRUE),
callback_reduce_lr_on_plateau(factor = 0.2, patience = 4)
)
)
cat("The minimum loss score is", min(history$metrics$val_loss) %>% round(4),
"which occurred at epoch", which.min(history$metrics$val_loss))
The minimum loss score is 0.0136 which occurred at epoch 25
plot(history) +
scale_y_log10() +
scale_x_continuous(limits = c(0, length(history$metrics$val_loss)))
Generalizing to small datasets
Note that finding an optimal model that generalizes well based on our validation approach may be difficult. This is because our validation data (via validation_split = 0.2
) only consists of 800+ samples. Consequently, model performance will be highly dependent on these 800+ samples.
In general, the fewer observations in our validation set, the greater variance in our loss score. As the number of observations in our validation data increases, variance in our loss score will decrease. However, we do not always have the option to just go out and get more data. So, if we want to gain a more accurate understanding of the loss score and its variance we could perform k-fold cross validation.
See the validation procedures notebook for an example of performing k-fold cross validation.
Key takeaways
- Preparing tabular data
- We need to vectorize categorical features
- We need to standardize and normalize numeric features
- Batch size & epochs
- Smaller batch sizes tend to perform best (speed and loss minimization)
- However, this is a hyperparameter you can adjust and evaluate
- Make sure you include enough epochs to reach a minimum loss
- Learning rate
- The most important hyperparameter to tune
- Typically you want to tune the learning rate and batch size together
- Callbacks
- Use early stopping to automate model training and stopping after loss has been minimized
- Use
callback_reduce_lr_on_plateau()
for more control over the learning rate.
- Knowing when to adjust model capacity
- Your minimum validation loss score should not be less than your training loss
- If so, increase model capacity until you’ve balanced overfitting and minimization of validation loss score and stability
LS0tCnRpdGxlOiAiQ2FzZSBTdHVkeSAxOiBBbWVzIC0tIFJlZ3Jlc3Npb24gdG8gcHJlZGljdCBBbWVzLCBJQSBIb21lIFNhbGVzIFByaWNlcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSkKZ2dwbG90Mjo6dGhlbWVfc2V0KGdncGxvdDI6OnRoZW1lX21pbmltYWwoKSkKYGBgCgpJbiB0aGlzIGNhc2Ugc3R1ZHksIG91ciBvYmplY3RpdmUgaXMgdG8gcHJlZGljdCB0aGUgc2FsZXMgcHJpY2Ugb2YgYSBob21lLiBUaGlzIAppcyBhIF9yZWdyZXNzaW9uXyBwcm9ibGVtIHNpbmNlIHRoZSBnb2FsIGlzIHRvIHByZWRpY3QgYW55IHJlYWwgbnVtYmVyIGFjcm9zcwpzb21lIHNwZWN0cnVtIChcJDExOSwyMDEsIFwkMTY4LDU5NCwgXCQzMDEsNDQ2LCBldGMpLiBUbyBwcmVkaWN0IHRoZSBzYWxlcyAKcHJpY2UsIHdlIHdpbGwgdXNlIG51bWVyaWMgYW5kIGNhdGVnb3JpY2FsIGZlYXR1cmVzIG9mIHRoZSBob21lLgoKVGhyb3VnaG91dCB0aGlzIGNhc2Ugc3R1ZHkgeW91IHdpbGwgbGVhcm4gYSBmZXcgbmV3IGNvbmNlcHRzOgoKKiBWZWN0b3JpemF0aW9uIGFuZCBzdGFuZGFyZGl6YXRpb24gb2YgdGFidWxhciBmZWF0dXJlcwoqIEFkanVzdGluZyBiYXRjaCBzaXplICYgZXBvY2hzIGZvciB0cmFpbmluZyBwZXJmb3JtYW5jZQoqIFdoYXQgY2FsbGJhY2tzIGFyZSBhbmQgaG93IHRvIHN0YXJ0IGFwcGx5aW5nIHRoZW0KICAgLSBFYXJseSBzdG9wcGluZwogICAtIENvbnRyb2xsaW5nIHRoZSBsZWFybmluZyByYXRlCiogS25vd2luZyB3aGVuIHRvIGFkanVzdCBtb2RlbCBjYXBhY2l0eQoKIyBQYWNrYWdlIHJlcXVpcmVtZW50cwoKYGBge3IgbG9hZC1wa2dzfQpsaWJyYXJ5KGtlcmFzKSAgICAgIyBmb3IgZGVlcCBsZWFybmluZwpsaWJyYXJ5KHRlc3R0aGF0KSAgIyB1bml0IHRlc3RpbmcKbGlicmFyeSh0aWR5dmVyc2UpICMgZm9yIGRwbHlyLCBnZ3Bsb3QyLCBldGMuCmxpYnJhcnkocnNhbXBsZSkgICAjIGZvciBkYXRhIHNwbGl0dGluZwpsaWJyYXJ5KHJlY2lwZXMpICAgIyBmb3IgZmVhdHVyZSBlbmdpbmVlcmluZwpgYGAKCgojIFRoZSBBbWVzIGhvdXNpbmcgZGF0YXNldAoKRm9yIHRoaXMgY2FzZSBzdHVkeSB3ZSB3aWxsIHVzZSB0aGUgW0FtZXMgaG91c2luZyBkYXRhc2V0XShodHRwOi8vanNlLmFtc3RhdC5vcmcvdjE5bjMvZGVjb2NrLnBkZikgCnByb3ZpZGVkIGJ5IHRoZSBfX0FtZXNIb3VzaW5nX18gcGFja2FnZS4KCmBgYHtyIGdldC1kYXRhfQphbWVzIDwtIEFtZXNIb3VzaW5nOjptYWtlX2FtZXMoKQpkaW0oYW1lcykKYGBgCgojIFVuZGVyc3RhbmRpbmcgb3VyIGRhdGEKClRoaXMgZGF0YSBoYXMgYmVlbiBwYXJ0aWFsbHkgY2xlYW5lZCB1cCBhbmQgaGFzIG5vIG1pc3NpbmcgZGF0YToKCmBgYHtyfQpzdW0oaXMubmEoYW1lcykpCmBgYAoKQnV0IHRoaXMgdGFidWxhciBkYXRhIGlzIGEgY29tYmluYXRpb24gb2YgbnVtZXJpYyBhbmQgY2F0ZWdvcmljYWwgZGF0YSB0aGF0IHdlCm5lZWQgdG8gYWRkcmVzcy4KCmBgYHtyIGFtZXMtc3RydWN0dXJlfQpzdHIoYW1lcykKYGBgCgpUaGUgbnVtZXJpYyB2YXJpYWJsZXMgYXJlIG9uIGRpZmZlcmVudCBzY2FsZXMuIEZvciBleGFtcGxlOgoKYGBge3IgbnVtZXJpYy1yYW5nZXN9CmFtZXMgJT4lCiAgc2VsZWN0KExvdF9BcmVhLCBMb3RfRnJvbnRhZ2UsIFllYXJfQnVpbHQsIEdyX0xpdl9BcmVhLCBHYXJhZ2VfQ2FycywgTW9fU29sZCkgJT4lCiAgZ2F0aGVyKGZlYXR1cmUsIHZhbHVlKSAlPiUKICBnZ3Bsb3QoYWVzKGZlYXR1cmUsIHZhbHVlKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV95X2xvZzEwKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpCmBgYAoKVGhlcmUgYXJlIGNhdGVnb3JpY2FsIGZlYXR1cmVzIHRoYXQgY291bGQgYmUgb3JkZXJlZDoKCmBgYHtyIG51bWVyaWMtY2F0ZWdvcmllc30KYW1lcyAlPiUKICBzZWxlY3QobWF0Y2hlcygiKFF1YWx8Q29uZHxRQ3xRdSkkIikpICU+JQogIHN0cigpCmBgYAoKQW5kIHNvbWUgb2YgdGhlIGNhdGVnb3JpY2FsIGZlYXR1cmVzIGhhdmUgbWFueSBsZXZlbHM6CgpgYGB7cn0KYW1lcyAlPiUKICBzZWxlY3RfaWYofiBpcy5mYWN0b3IoLikgJiBsZW5ndGgobGV2ZWxzKC4pKSA+IDgpICU+JQogIHN0cigpCmBgYAoKQ29uc2VxdWVudGx5LCBvdXIgZmlyc3QgY2hhbGxlbmdlIGlzIHRyYW5zZm9ybWluZyB0aGlzIGRhdGFzZXQgaW50byBudW1lcmljCnRlbnNvcnMgdGhhdCBvdXIgbW9kZWwgY2FuIHVzZS4KCiMgQ3JlYXRlIHRyYWluICYgdGVzdCBzcGxpdHMKCk9uZSBvZiB0aGUgZmlyc3QgdGhpbmdzIHdlIHdhbnQgdG8gZG8gaXMgY3JlYXRlIGEgdHJhaW4gYW5kIHRlc3Qgc2V0IGFzIHlvdQpwcm9iYWJseSBub3RpY2VkIHRoYXQgd2UgZG8gbm90IGhhdmUgYSB0cmFpbiBhbmQgdGVzdCBzZXQgc2ltaWxhciB0byBob3cgTU5JU1QgCndhcyBhbHJlYWR5IHNldCB1cCBmb3IgdXMuIFdlIGNhbiB1c2UgdGhlIF9fcnNhbXBsZV9fIHBhY2thZ2UgdG8gY3JlYXRlIG91cgp0cmFpbiBhbmQgdGVzdCBkYXRhc2V0cy4KCmBgYHtyfQpzZXQuc2VlZCgxMjMpCmFtZXNfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChhbWVzLCBwcm9wID0gMC43KQphbWVzX3RyYWluIDwtIGFuYWx5c2lzKGFtZXNfc3BsaXQpCmFtZXNfdGVzdCA8LSBhc3Nlc3NtZW50KGFtZXNfc3BsaXQpCgpkaW0oYW1lc190cmFpbikKZGltKGFtZXNfdGVzdCkKYGBgCgojIFByZXBhcmluZyB0aGUgZGF0YQoKQWxsIGlucHV0cyBhbmQgcmVzcG9uc2UgdmFsdWVzIGluIGEgbmV1cmFsIG5ldHdvcmsgbXVzdCBiZSB0ZW5zb3JzIG9mIGVpdGhlciAKZmxvYXRpbmctcG9pbnQgb3IgaW50ZWdlciBkYXRhLiBNb3Jlb3Zlciwgb3VyIGZlYXR1cmUgdmFsdWVzIHNob3VsZCBub3QgYmUKcmVsYXRpdmVseSBsYXJnZSBjb21wYXJlZCB0byB0aGUgcmFuZG9taXplZCBpbml0aWFsIHdlaWdodHMgX2FuZF8gYWxsIG91ciAKZmVhdHVyZXMgc2hvdWxkIHRha2UgdmFsdWVzIGluIHJvdWdobHkgdGhlIHNhbWUgcmFuZ2UuCgpDb25zZXF1ZW50bHksIHdlIG5lZWQgdG8gX19fdmVjdG9yaXplX19fIG91ciBkYXRhIGludG8gYSBmb3JtYXQgY29uZHVjaXZlIHRvIG5ldXJhbCAKbmV0d29ya3MgW+KEue+4j10oaHR0cDovL2JpdC5seS9kbC0wMiMzKS4gRm9yIHRoaXMgZGF0YSBzZXQsIHdlJ2xsIHRyYW5zZm9ybSBvdXIKZGF0YSBieToKCi0gcmVtb3ZpbmcgYW55IHplcm8tdmFyaWFuY2UgKG9yIG5lYXIgemVyby12YXJpYW5jZSkgZmVhdHVyZXMKLSBjb25kZW5zaW5nIHVuaXF1ZSBsZXZlbHMgb2YgY2F0ZWdvcmljYWwgZmVhdHVyZXMgdG8gIm90aGVyIgotIG9yZGluYWwgZW5jb2RpbmcgdGhlIHF1YWxpdHkgZmVhdHVyZXMKLSBub3JtYWxpemUgbnVtZXJpYyBmZWF0dXJlIGRpc3RyaWJ1dGlvbnMKLSBzdGFuZGFyZGl6aW5nIG51bWVyaWMgZmVhdHVyZXMgdG8gbWVhbiA9IDAsIHN0ZCBkZXYgPSAxCi0gb25lLWhvdCBlbmNvZGluZyByZW1haW5pbmcgY2F0ZWdvcmljYWwgZmVhdHVyZXMKCmBgYHtyfQpibHVlcHJpbnQgPC0gcmVjaXBlKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lc190cmFpbikgJT4lCiAgc3RlcF9uenYoYWxsX25vbWluYWwoKSkgJT4lCiAgc3RlcF9vdGhlcihhbGxfbm9taW5hbCgpLCB0aHJlc2hvbGQgPSAuMDEsIG90aGVyID0gIm90aGVyIikgJT4lCiAgc3RlcF9pbnRlZ2VyKG1hdGNoZXMoIihRdWFsfENvbmR8UUN8UXUpJCIpKSAlPiUKICBzdGVwX1llb0pvaG5zb24oYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUKICBzdGVwX2NlbnRlcihhbGxfbnVtZXJpYygpLCAtYWxsX291dGNvbWVzKCkpICU+JQogIHN0ZXBfc2NhbGUoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIC1hbGxfb3V0Y29tZXMoKSwgb25lX2hvdCA9IFRSVUUpCgpibHVlcHJpbnQKYGBgCgpUaGlzIG5leHQgc3RlcCBjb21wdXRlcyBhbnkgcmVsYXZlbnQgaW5mb3JtYXRpb24gKG1lYW4gYW5kIHN0ZCBkZXZpYXRpb24gb2YKbnVtZXJpYyBmZWF0dXJlcywgbmFtZXMgb2Ygb25lLWhvdCBlbmNvZGVkIGZlYXR1cmVzKSBvbiB0aGUgdHJhaW5pbmcgZGF0YSBzbwp0aGVyZSBpcyBubyBpbmZvcm1hdGlvbiBsZWFrYWdlIGZyb20gdGhlIHRlc3QgZGF0YS4KCmBgYHtyfQpwcmVwYXJlIDwtIHByZXAoYmx1ZXByaW50LCB0cmFpbmluZyA9IGFtZXNfdHJhaW4pCnByZXBhcmUKYGBgCgpXZSBjYW4gbm93IHZlY3Rvcml6ZSBvdXIgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YS4gSWYgeW91IHNjcm9sbCB0aHJvdWdoIHRoZSBkYXRhCnlvdSB3aWxsIG5vdGljZSB0aGF0IGFsbCBmZWF0dXJlcyBhcmUgbm93IG51bWVyaWMgYW5kIGFyZSBlaXRoZXIgMC8xIChvbmUgaG90CmVuY29kZWQgZmVhdHVyZXMpIG9yIGhhdmUgbWVhbiAwIGFuZCBnZW5lcmFsbHkgcmFuZ2UgYmV0d2VlbiAtMyBhbmQgMy4KCmBgYHtyfQpiYWtlZF90cmFpbiA8LSBiYWtlKHByZXBhcmUsIG5ld19kYXRhID0gYW1lc190cmFpbikKYmFrZWRfdGVzdCA8LSBiYWtlKHByZXBhcmUsIG5ld19kYXRhID0gYW1lc190ZXN0KQoKIyB1bml0IHRlc3RpbmcgdG8gZW5zdXJlIGFsbCBjb2x1bW5zIGFyZSBudW1lcmljCmV4cGVjdF9lcXVhbChtYXBfbGdsKGJha2VkX3RyYWluLCB+ICFpcy5udW1lcmljKC4pKSAlPiUgc3VtKCksIDApCmV4cGVjdF9lcXVhbChtYXBfbGdsKGJha2VkX3Rlc3QsIH4gIWlzLm51bWVyaWMoLikpICU+JSBzdW0oKSwgMCkKCmJha2VkX3RyYWluCmBgYAoKTGFzdGx5LCB3ZSBuZWVkIHRvIGNyZWF0ZSB0aGUgZmluYWwgZmVhdHVyZSBhbmQgcmVzcG9uc2Ugb2JqZWN0cyBmb3IgdHJhaW4gYW5kIAp0ZXN0IGRhdGEuIFNpbmNlIF9fa2VyYXNfXyBhbmQgX190ZW5zb3JmbG93X18gcmVxdWlyZSBvdXIgZmVhdHVyZXMgJiBsYWJlbHMgdG8gYmUgCnNlcGVyYXRlIG9iamVjdHMgd2UgbmVlZCB0byBzZXBhcmF0ZSB0aGVtLiBJbiBkb2luZyBzbywgb3VyIGZlYXR1cmVzIG5lZWQgdG8gYmUgCmEgMkQgdGVuc29yIHdoaWNoIGlzIHdoeSB3ZSBhcHBseSBgYXMubWF0cml4YCBhbmQgb3VyIHJlc3BvbnNlIG5lZWRzIHRvIGJlIGEgCnZlY3RvciB3aGljaCBpcyB3aHkgd2UgYXBwbHkgYHB1bGxgLgoKYGBge3J9CnhfdHJhaW4gPC0gc2VsZWN0KGJha2VkX3RyYWluLCAtU2FsZV9QcmljZSkgJT4lIGFzLm1hdHJpeCgpCnlfdHJhaW4gPC0gYmFrZWRfdHJhaW4gJT4lIHB1bGwoU2FsZV9QcmljZSkKCnhfdGVzdCA8LSBzZWxlY3QoYmFrZWRfdGVzdCwgLVNhbGVfUHJpY2UpICU+JSBhcy5tYXRyaXgoKQp5X3Rlc3QgPC0gYmFrZWRfdGVzdCAlPiUgcHVsbChTYWxlX1ByaWNlKQoKIyB1bml0IHRlc3RpbmcgdG8geCAmIHkgdGVuc29ycyBoYXZlIHNhbWUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucwpleHBlY3RfZXF1YWwobnJvdyh4X3RyYWluKSwgbGVuZ3RoKHlfdHJhaW4pKQpleHBlY3RfZXF1YWwobnJvdyh4X3Rlc3QpLCBsZW5ndGgoeV90ZXN0KSkKYGBgCgpPdXIgZmluYWwgZmVhdHVyZSBzZXQgbm93IGhhcyAxODggaW5wdXQgdmFyaWFibGVzOgoKYGBge3J9CmRpbSh4X3RyYWluKQpkaW0oeF90ZXN0KQpgYGAKCiMgSW5pdGlhbCBtb2RlbAoKT3VyIGluaXRpYWwgbW9kZWwgbG9va3MgZmFpcmx5IHNpbWlsYXIgdG8gdGhlIE1OSVNUIG1vZGVsIHdlIGFwcGxpZWQuIEhvd2V2ZXIsIApub3RlIHRoZSBmb2xsb3dpbmcgZGlmZmVyZW5jZXM6CgoqIEZpbmFsIG91dHB1dCBsYXllciBoYXMgYHVuaXRzID0gMWAgYW5kIG5vIGFjdGl2YXRpb24gZnVuY3Rpb24gc2luY2UgdGhpcyBpcyBhIAogIHJlZ3Jlc3Npb24gcHJvYmxlbS4KKiBXZSBhcmUgdXNpbmcgYSBkaWZmZXJlbnQgbG9zcyBhbmQgbWV0cmljIGZ1bmN0aW9uLiBUaGUgb3JpZ2luYWwgS2FnZ2xlCiAgY29tcGV0aXRpb24gaGFkIHlvdSBsb2cgdGhlIHJlc3BvbnNlIHZhcmlhYmxlIGFuZCB0aGVuIHVzZSBNU0UuIFRoaXMgaXMgCiAgZXF1aXZhbGVudCB0byB1c2luZyB0aGUgTVNMRSBsb3NzIGZ1bmN0aW9uLiBb4oS577iPXShodHRwOi8vYml0Lmx5LzJSNHVoMkwpCiogVGhlIGJhdGNoIHNpemUgaXMgbXVjaCBzbWFsbGVyCgpgYGB7ciBpbml0aWFsLW1vZGVsfQpuZXR3b3JrIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMjgsIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gbmNvbCh4X3RyYWluKSkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEpCgpuZXR3b3JrICU+JSBjb21waWxlKAogICAgb3B0aW1pemVyID0gInJtc3Byb3AiLAogICAgbG9zcyA9ICJtc2xlIiwKICAgIG1ldHJpY3MgPSBjKCJtYWUiKQogICkKYGBgIAoKYGBge3IgbW9kZWwtc3VtbWFyeX0Kc3VtbWFyeShuZXR3b3JrKQpgYGAgIAoKYGBge3IgdHJhaW4sIHJlc3VsdHM9J2hpZGUnfQpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICB4X3RyYWluLAogIHlfdHJhaW4sCiAgYmF0Y2hfc2l6ZSA9IDMyLAogIGVwb2NocyA9IDIwLAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIKKQpgYGAKCk91ciByZXN1bHRzIGJlbG93IHNob3cgdHdvIHRoaW5nczoKCjEuIE91ciB2YWxpZGF0aW9uIGxvc3Mgc2NvcmUgaGFzIG5vdCByZWFjaGVkIGEgbWluaW11bS4KMi4gVGhlcmUgYXJlIG5vIHNpZ25zIG9mIG92ZXJmaXR0aW5nLgoKYGBge3J9CnBsb3QoaGlzdG9yeSkgKyBzY2FsZV95X2xvZzEwKCkKYGBgCgojIENvbnNpZGVyYXRpb25zIHJlZ2FyZGluZyBiYXRjaCBzaXplcyBhbmQgZXBvY2hzCgpGaXJzdCwgbGV0J3MgZGlzY3VzcyBiYXRjaCBzaXplcyBhbmQgZXBvY2hzIGFzIHRoZXkgY2FuIGhhdmUgYSBzaWduaWZpY2FudCAKaW1wYWN0IG9uIGhvdyBxdWlja2x5IHdlIHN0YXJ0IHJlYWNoaW5nIG91ciBtaW5pbXVtIGxvc3MgZnVuY3Rpb24uCgpEaWZmZXJlbmNlcyBpbiBwZXJmb3JtYW5jZSBmb3IgZGlmZmVyaW5nIGJhdGNoIHNpemUgbGFyZ2VseSBkZXBlbmRzIG9uIHRoZSAKdW5kZXJseWluZyAoYW5kIHR5cGljYWxseSB1bmtub3duKSByZWFsIGNvc3QgZnVuY3Rpb247IGhvd2V2ZXIsIGhlcmUgYXJlIHNvbWUKYmFzaWMgZ3VpZGVsaW5lczoKCi0gYmF0Y2ggc2l6ZXMgY29tbW9ubHkgdGFrZSBvbiB2YWx1ZXMgb2YgJDJecyQgKGkuZS4gMzIsIDY0LCAxMjgsIDI1NiwgNTEyKSwKICAgLSBzbWFsbGVyIGJhdGNoIHNpemVzLi4uCiAgICAgIC0gY2FuIGhhdmUgbW9yZSB2YXJpYWJpbGl0eTsgaG93ZXZlciwgdGhpcyB2YXJpYWJpbGl0eSBjYW4gaGVscCB1cyBzdGF5IG91dAogICAgICAgIG9mIGxvY2FsIG1pbmltdW1ucyAKICAgICAgLSBjYW4gZGVjcmVhc2UgbGVhcm5pbmcgdGltZQogICAtIGxhcmdlciBiYXRjaCBzaXplcy4uLgogICAgICAtIGhhdmUgbGVzcyB2YXJpYWJpbGl0eSBidXQgCiAgICAgIC0gY2FuIGluY3JlYXNlIGxlYXJuaW5nIHRpbWUKLSBvcHRpbWFsIGJhdGNoIHNpemVzIGNhbiBiZSBpbmZsdWVuY2VkIGJ5IHNpemUgb2YgZGF0YToKICAgLSBsYXJnZXIgX25fIGNhbiBhZmZvcmQgbGFyZ2VyIGJhdGNoIHNpemVzICgxMjgsIDI1NiwgNTEyKQogICAtIHNtYWxsZXIgX25fIGNhbiBhZmZvcmQgc21hbGxlciBiYXRjaCBzaXplcyAoMTYsIDMyLCA2NCkKLSBXaGljaCBpcyBiZXN0PwogICAtIEkgdHlwaWNhbGx5IHN0YXJ0IHdpdGggMzIgb3IgNjQKICAgLSBUcmlhbCBhbmQgZXJyb3IgZm9yIHlvdXIgc3BlY2lmaWMgcHJvYmxlbQotIHVzZSBlbm91Z2ggZXBvY2hzIHNvIHRoYXQgb3VyIGxlYXJuaW5nIHJhdGUgcmVhY2hlcyBhIG1pbmltdW0KCkhlcmUgYXJlIHNvbWUgZ29vZCBhcnRpY2xlcyBkaXNjdXNzaW5nIHRoZSBpbXBhY3RzIG9mIGJhdGNoIHNpemU6CgogICAtIFtFZmZlY3Qgb2YgYmF0Y2ggc2l6ZSBvbiB0cmFpbmluZyBkeW5hbWljc10oaHR0cDovL2JpdC5seS8yclFkTzdHKQogICAtIFtPbiBMYXJnZS1CYXRjaCBUcmFpbmluZyBmb3IgRGVlcCBMZWFybmluZzogR2VuZXJhbGl6YXRpb24gR2FwIGFuZCBTaGFycCBNaW5pbWFdKGh0dHBzOi8vYXJ4aXYub3JnL2Ficy8xNjA5LjA0ODM2KQogICAtIFtEZWVwIExlYXJuaW5nIGJvb2ssIFNlY3Rpb24gOC4xLjNdKGh0dHA6Ly93d3cuZGVlcGxlYXJuaW5nYm9vay5vcmcvY29udGVudHMvb3B0aW1pemF0aW9uLmh0bWwpCiAgIC0gW0ltcGFjdCBvZiBUcmFpbmluZyBTZXQgQmF0Y2ggU2l6ZSBvbiB0aGUgUGVyZm9ybWFuY2VdKGh0dHA6Ly9iaXQubHkvMzVWV003ZSkKCiMgWU9VUiBUVVJOISAoNSBtaW4pCgpUcnkgZGlmZmVyZW50IGJhdGNoIHNpemVzIGFuZCBlcG9jaHMgYW5kIHNlZSBob3cgbW9kZWwgcGVyZm9ybWFuY2UgY2hhbmdlcy4gClJlbWVtYmVyLCBiYXRjaCBzaXplcyBhcmUgdHlwaWNhbGx5IHBvd2VycyBvZiAyIChpLmUuIDE2LCAzMiwgNjQsIDEyOCwgMjU2LCA1MTIpLgoKYGBge3IgeW91ci10dXJuLTF9Cm5ldHdvcmsgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IDEyOCwgYWN0aXZhdGlvbiA9ICJyZWx1IiwgaW5wdXRfc2hhcGUgPSBfX19fKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMjgsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gX19fXykKCm5ldHdvcmsgJT4lIGNvbXBpbGUoCiAgICBvcHRpbWl6ZXIgPSAicm1zcHJvcCIsCiAgICBsb3NzID0gIm1zbGUiLAogICAgbWV0cmljcyA9IGMoIm1hZSIpCiAgKQoKaGlzdG9yeSA8LSBuZXR3b3JrICU+JSBmaXQoCiAgeF90cmFpbiwKICB5X3RyYWluLAogIGVwb2NocyA9IF9fX18sCiAgYmF0Y2hfc2l6ZSA9IF9fX18sCiAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMgopCmBgYAoKIyBFYXJseSBzdG9wcGluZwoKWW91IGxpa2VseSBub3RpY2VkIHRoYXQgYSBsYXJnZSBiYXRjaCBzaXplIChpLmUuIDUxMikgdG9vayBtYW55IG1vcmUgZXBvY2hzIHRvCnN0YXJ0IHJlYWNoaW5nIGEgbWluaW11bSB2ZXJzdXMgYSBzbWFsbGVyIGJhdGNoIHNpemUgKGkuZS4gMTYpLiBCdXQgcmVnYXJkbGVzcwpvZiBiYXRjaCBzaXplLCB5b3Ugc3RpbGwgaGFkIHRvIGRvIGEgZmFpciBhbW91bnQgb2YgdHJpYWwgYW5kIGVycm9yIHRvIGZpbmQgdGhlCnJpZ2h0IG51bWJlciBvZiBlcG9jaHMgdG8gcmVhY2ggYSBtaW5pbXVtIGxvc3Mgc2NvcmUuCgpMZXQncyBtZWV0IG91ciBmaXJzdCBfX19jYWxsYmFja19fXywgd2hpY2ggY2FuIGhlbHAgaW4gdGhpcyBzaXR1YXRpb24gW+KEue+4j10oaHR0cDovL2JpdC5seS9kbC0wMiMxNCkuIApVc2luZyBlYXJseSBzdG9wcGluZyBhbGxvd3MgdXMgdG8gY3JhbmsgdXAgdGhlIG51bWJlciBvZiBlcG9jaHMgYW5kIGxldCB0aGUgCnRyYWluaW5nIGF1dG9tYXRpY2FsbHkgc3RvcCBhZnRlciB3ZSBleHBlcmllbmNlIG5vIGltcHJvdmVtZW50IGluIG91ciBsb3NzIGFmdGVyCmBwYXRpZW5jZWAgbnVtYmVyIG9mIGVwb2Nocy4gCiAgCmBgYHtyIGluaXRpYWwtbW9kZWwtZWFybHktc3RvcHBpbmd9Cm5ldHdvcmsgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IDEyOCwgYWN0aXZhdGlvbiA9ICJyZWx1IiwgaW5wdXRfc2hhcGUgPSBuY29sKHhfdHJhaW4pKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMjgsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSkgCgpuZXR3b3JrICU+JQogIGNvbXBpbGUoCiAgICBvcHRpbWl6ZXIgPSAicm1zcHJvcCIsCiAgICBsb3NzID0gIm1zbGUiLAogICAgbWV0cmljcyA9ICJtYWUiCiAgKQoKaGlzdG9yeSA8LSBuZXR3b3JrICU+JSBmaXQoCiAgeF90cmFpbiwKICB5X3RyYWluLAogIGVwb2NocyA9IDI1MCwKICBiYXRjaF9zaXplID0gMzIsCiAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMiwKICBjYWxsYmFja3MgPSBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IDUsIHJlc3RvcmVfYmVzdF93ZWlnaHRzID0gVFJVRSkKKQpgYGAKCmBgYHtyIGVhcmx5LXN0b3BwaW5nLW1vZGVsLXBlcmZvcm1hbmNlfQpoaXN0b3J5CgpjYXQoIlxuVGhlIG1pbmltdW0gbG9zcyBzY29yZSBpcyIsIG1pbihoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3MpICU+JSByb3VuZCg0KSwKICAgICJ3aGljaCBvY2N1cnJlZCBhdCBlcG9jaCIsIHdoaWNoLm1pbihoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3MpKQpgYGAKCgpgYGB7ciBlYXJseS1zdG9wcGluZy1wbG90fQpwbG90KGhpc3RvcnkpICsgc2NhbGVfeV9sb2cxMCgpCmBgYAoKIyBBZGp1c3RhYmxlIGxlYXJuaW5nIHJhdGUKCk9uZSB0aGluZyB5b3UgbWF5IG5vdGljZSBpcyB0aGF0IHRoZXJlIGlzIHNpZ25pZmljYW50IGxlYXJuaW5nIGhhcHBlbmluZyBmb3IgdGhlCmZpcnN0IDE1LTIwIGVwb2NocyBhbmQgdGhlbiB0aGUgbW9kZWwgc2xvd2x5IGNoaXBzIGF3YXkgYXQgdGhlIGxvc3MgZm9yIHRoZSBuZXh0Cn4xMDArIGVwb2Nocy4gIFdlIGNhbiBzcGVlZCB1cCB0aGlzIHByb2Nlc3Mgd2l0aCB0d28gdGhpbmdzOgoKMS4gY3VzdG9taXplIG91ciBvcHRpbWl6ZXIgd2l0aCBhIGxhcmdlciBsZWFybmluZyByYXRlIHRvIHRyeSBzcGVlZCB1cCB0aGUgCiAgIGRvd25oaWxsIHRyYXZlcnNhbCBvZiB0aGUgZ3JhZGllbnQgZGVzY2VudAoyLiBhZGQgYSBjYWxsYmFjayB0aGF0IHNsb3dseSByZWR1Y2VzIHRoZSBsZWFybmluZyByYXRlIGJ5IDIwJSBpZiB3ZSBkb24ndAogICBleHBlcmllbmNlIGltcHJvdmVtZW50IGluIG91ciBsb3NzIGZvciBgcGF0aWVuY2VgIG51bWJlciBvZiBlcG9jaHMuCiAgIApOb3RlIGhvdyB3ZSBhcmUgbm93IHVzaW5nIGBvcHRpbWl6ZXIgPSBvcHRpbWl6ZXJfcm1zcHJvcChsciA9IDAuMDEpYCBpbnN0ZWFkIG9mCmBvcHRpbWl6ZXIgPSAicm1zcHJvcCJgIHNvIHRoYXQgd2UgY2FuIGN1c3RvbWl6ZSB0aGUgbGVhcm5pbmcgcmF0ZS4KCmBgYHtyIGluaXRpYWwtbW9kZWwtYWRqLWxyfQpuZXR3b3JrIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMjgsIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gbmNvbCh4X3RyYWluKSkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEpIAoKbmV0d29yayAlPiUKICBjb21waWxlKAogICAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAwLjAxKSwKICAgIGxvc3MgPSAibXNsZSIsCiAgICBtZXRyaWNzID0gYygibWFlIikKICApCgpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICB4X3RyYWluLAogIHlfdHJhaW4sCiAgZXBvY2hzID0gNTAsCiAgYmF0Y2hfc2l6ZSA9IDMyLAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIsCiAgY2FsbGJhY2tzID0gbGlzdCgKICAgICAgICBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IDEwLCByZXN0b3JlX2Jlc3Rfd2VpZ2h0cyA9IFRSVUUpLAogICAgICAgIGNhbGxiYWNrX3JlZHVjZV9scl9vbl9wbGF0ZWF1KGZhY3RvciA9IDAuMiwgcGF0aWVuY2UgPSA0KQogICAgKQopCmBgYAoKTm93IHdlIHNlZSBhIG11Y2ggZmFzdGVyIHByb2Nlc3Mgb2YgbW9kZWwgdHJhaW5pbmc7IGl0IG9ubHkgdGFrZXMgfjI1JSB0aGUgCm51bWJlciBvZiBlcG9jaHMgYW5kIGl0IGFwcGVhcnMgdGhhdCB3ZSBhY3R1YWxseSBzZWUgYW4gaW5jcmVhc2UgaW4gcGVyZm9ybWFuY2UhCgpgYGB7ciBhZGotbGVhcm5pbmctcmF0ZS1wZXJmb3JtYW5jZX0KaGlzdG9yeQoKY2F0KCJcblRoZSBtaW5pbXVtIGxvc3Mgc2NvcmUgaXMiLCBtaW4oaGlzdG9yeSRtZXRyaWNzJHZhbF9sb3NzKSAlPiUgcm91bmQoNCksCiAgICAid2hpY2ggb2NjdXJyZWQgYXQgZXBvY2giLCB3aGljaC5taW4oaGlzdG9yeSRtZXRyaWNzJHZhbF9sb3NzKSkKYGBgCgoKYGBge3IgYWRqLWxlYXJuaW5nLXJhdGUtcGxvdH0KcGxvdChoaXN0b3J5KSArIAogIHNjYWxlX3lfbG9nMTAoKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgbGVuZ3RoKGhpc3RvcnkkbWV0cmljcyR2YWxfbG9zcykpKQpgYGAKCiMgWU9VUiBUVVJOISAoNSBtaW4pCgpUcnkgZGlmZmVyZW50IHZhcmlhdGlvbnMgb2YgbGVhcm5pbmcgcmF0ZSwgcGF0aWVuY2UgcGFyYW1ldGVycywgYW5kIGxlYXJuaW5nIApyYXRlIHJlZHVjdGlvbiBmYWN0b3IuICBIZXJlIGFyZSBhIGNvdXBsZSBvZiB0aGluZ3MgdG8ga2VlcCBpbiBtaW5kOgoKLSBfb3B0aW1pemVyIGxlYXJuaW5nIHJhdGVfIHJhbmdlcyB3b3J0aCBleHBsb3JpbmcgZGlmZmVyIGRlcGVuZGluZyBvbiB0aGUgCiAgb3B0aW1pemVyIGJ1dCBmb3IgUk1TUHJvcCBjb21tb24gcmFuZ2VzIGluY2x1ZGUgMC4xLTAuMDAwMS4KLSBfbGVhcm5pbmcgcmF0ZSByZWR1Y3Rpb24gZmFjdG9yXyB0eXBpY2FsbHkgcmFuZ2UgZnJvbSAwLjUtMC4xLiAgCi0gdGhlIHBhdGllbmNlIHZhbHVlIGZvciBfZWFybHkgc3RvcHBpbmdfIHNob3VsZCBub3QgYmUgc2hvcnRlciB0aGFuIHRoZSAKICBwYXRpZW5jZSB2YWx1ZSBmb3IgX2xlYXJuaW5nIHJhdGUgcmVkdWN0aW9uXywgb3RoZXJ3aXNlIHRoZSBsZWFybmluZyByYXRlIHdpbGwgCiAgbmV2ZXIgaGF2ZSB0aGUgb3Bwb3J0dW5pdHkgdG8gZGVjcmVhc2UuCgpgYGB7ciB5b3VyLXR1cm4tMn0KbmV0d29yayA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gX19fXywgaW5wdXRfc2hhcGUgPSBfX19fKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMjgsIGFjdGl2YXRpb24gPSBfX19fKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IF9fX18pIAoKbmV0d29yayAlPiUKICBjb21waWxlKAogICAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSBfX19fKSwKICAgIGxvc3MgPSAibXNsZSIsCiAgICBtZXRyaWNzID0gYygibWFlIikKICApCgpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICB4X3RyYWluLAogIHlfdHJhaW4sCiAgZXBvY2hzID0gNTAsCiAgYmF0Y2hfc2l6ZSA9IDMyLAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIsCiAgY2FsbGJhY2tzID0gbGlzdCgKICAgICAgICBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IF9fX18pLAogICAgICAgIGNhbGxiYWNrX3JlZHVjZV9scl9vbl9wbGF0ZWF1KGZhY3RvciA9IF9fX18sIHBhdGllbmNlID0gX19fXykKICAgICkKKQpgYGAKCiMgVW5kZXIgY2FwYWNpdHkKClVuZm9ydHVuYXRlbHksIG91ciBtb2RlbCBpcyBzdGlsbCB1bmRlcmZpdHRpbmcgd2hlbiB3ZSByZWFjaCBvdXIgbWluaW11bQp2YWxpZGF0aW9uIGxvc3Mgc2NvcmUuIFRoaXMgaXMgYSBjbGFzc2ljIHNpZ24gdGhhdCB3ZSBhcmUgdW5kZXItY2FwYWNpdHkuIFRoZXJlCmFyZSB0d28gd2F5cyB0byBpbmNyZWFzZSBtb2RlbCBjYXBhY2l0eSBb4oS577iPXShodHRwOi8vYml0Lmx5L2RsLTAyIzE3KToKCjEuIEFkZCBtb3JlIHVuaXRzIGluIGVhY2ggaGlkZGVuIGxheWVyCjIuIEFkZCBtb3JlIGhpZGRlbiBsYXllcnMKCkluIHRoZSBuZXh0IG1vZHVsZSwgd2UnbGwgbG9vayBhdCBob3cgdG8gZHluYW1pY2FsbHkgYXNzZXNzIHRoZXNlIGlucHV0cyBidXQgZm9yCm5vdywgc3BlbmQgdGhlIG5leHQgMyBtaW51dGVzIGFkanVzdGluZyB0aGUgbnVtYmVyIG9mIHVuaXRzIGluIGVhY2ggaGlkZGVuIGxheWVyCmFuZC9vciBhZGp1c3RpbmcgdGhlIG51bWJlciBvZiBoaWRkZW4gbGF5ZXJzLgoKIyMgWU9VUiBUVVJOISAoNSBtaW4pCgpUcnkgZGlmZmVyZW50IGJhdGNoIHNpemVzIGFuZCBlcG9jaHMgYW5kIHNlZSBob3cgbW9kZWwgcGVyZm9ybWFuY2UgY2hhbmdlcy4gClJlbWVtYmVyLCBiYXRjaCBzaXplcyBhcmUgdHlwaWNhbGx5IHBvd2VycyBvZiAyIChpLmUuIDE2LCAzMiwgNjQsIDEyOCwgMjU2LCA1MTIpLgoKYGBge3IgeW91ci10dXJuLTN9Cm5ldHdvcmsgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IF9fX18sIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gX19fXykgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gX19fXywgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSBfX19fKQoKbmV0d29yayAlPiUKICBjb21waWxlKAogICAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAwLjAxKSwKICAgIGxvc3MgPSAibXNsZSIsCiAgICBtZXRyaWNzID0gYygibWFlIikKICApCgpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICB4X3RyYWluLAogIHlfdHJhaW4sCiAgZXBvY2hzID0gNTAsCiAgYmF0Y2hfc2l6ZSA9IDMyLAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIsCiAgY2FsbGJhY2tzID0gbGlzdCgKICAgICAgICBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IDEwLCByZXN0b3JlX2Jlc3Rfd2VpZ2h0cyA9IFRSVUUpLAogICAgICAgIGNhbGxiYWNrX3JlZHVjZV9scl9vbl9wbGF0ZWF1KGZhY3RvciA9IDAuMiwgcGF0aWVuY2UgPSA0KQogICAgKQopCmBgYAoKIyMgV2hhdCB0byBsb29rIGZvcgoKVHlwaWNhbGx5LCBJIGFkZCB1bml0cyBhbmQgbGF5ZXJzIHVudGlsIEkgc2VlIHNpZ25pZmljYW50IG92ZXJmaXR0aW5nIG9yIHN0YXJ0CnRvIHNlZSBoaWdoIHZhcmlhYmlsaXR5IGluIG91ciBsb3NzIHNjb3JlIG9yIG1ldHJpY3MgYW5kIHRoZW4gY29uc3RyYWluIHRoZSBtb2RlbApmcm9tIHRoZXJlLiAKCkZvciBleGFtcGxlLCB0aGUgZm9sbG93aW5nIG1vZGVsIHdpdGggNSBoaWRkZW4gbGF5ZXJzIGNvbnNpc3Rpbmcgb2YgMTAyNCB1bml0cwplYWNoIG92ZXJmaXRzIGF0IHRoZSBtaW5pbXVtIHZhbGlkYXRpb24gbG9zcyBzY29yZSAoYnV0IG5vdCBtdWNoKSBhbmQKc2hvd3Mgc2lnbnMgb2YgbG9zcyBhbmQgbWV0cmljIHZhcmlhYmlsaXR5LiBGcm9tIGhlcmUsIEkgd291bGQgc3RhcnQgdG8KcmVndWxhcml6ZSB0aGUgbW9kZWwgYnkgcmVtb3ZpbmcgbGF5ZXJzLCByZWR1Y2UgdGhlIG51bWJlciBvZiB1bml0cyBpbiBlYWNoCmxheWVyLCBvciB1c2luZyBhbiBhbHRlcm5hdGl2ZSByZWd1bGFyaXphdGlvbiBtZXRob2QgdW50aWwgSSBmaW5kIGEgaGFwcHkKY29tcHJvbWlzZSBiZXR3ZWVuIG1vZGVsIGNhcGFjaXR5LCBsb3NzIG1pbmltaXphdGlvbiAmIHN0YWJpbGl0eS4KCmBgYHtyIG92ZXJmaXQtbW9kZWx9Cm5ldHdvcmsgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IDEwMjQsIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gbmNvbCh4X3RyYWluKSkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMDI0LCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEwMjQsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTAyNCwgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxKQoKbmV0d29yayAlPiUKICBjb21waWxlKAogICAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAwLjAxKSwKICAgIGxvc3MgPSAibXNsZSIsCiAgICBtZXRyaWNzID0gYygibWFlIikKICApCgpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICB4X3RyYWluLAogIHlfdHJhaW4sCiAgZXBvY2hzID0gMjUwLAogIGJhdGNoX3NpemUgPSAzMiwKICB2YWxpZGF0aW9uX3NwbGl0ID0gMC4yLAogIGNhbGxiYWNrcyA9IGxpc3QoCiAgICAgICAgY2FsbGJhY2tfZWFybHlfc3RvcHBpbmcocGF0aWVuY2UgPSAxMCwgcmVzdG9yZV9iZXN0X3dlaWdodHMgPSBUUlVFKSwKICAgICAgICBjYWxsYmFja19yZWR1Y2VfbHJfb25fcGxhdGVhdShmYWN0b3IgPSAwLjIsIHBhdGllbmNlID0gNCkKICAgICkKKQpgYGAKCmBgYHtyfQpjYXQoIlRoZSBtaW5pbXVtIGxvc3Mgc2NvcmUgaXMiLCBtaW4oaGlzdG9yeSRtZXRyaWNzJHZhbF9sb3NzKSAlPiUgcm91bmQoNCksCiAgICAid2hpY2ggb2NjdXJyZWQgYXQgZXBvY2giLCB3aGljaC5taW4oaGlzdG9yeSRtZXRyaWNzJHZhbF9sb3NzKSkKYGBgCgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnBsb3QoaGlzdG9yeSkgKyAKICBzY2FsZV95X2xvZzEwKCkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIGxlbmd0aChoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3MpKSkKYGBgCgoKIyBHZW5lcmFsaXppbmcgdG8gc21hbGwgZGF0YXNldHMKCk5vdGUgdGhhdCBmaW5kaW5nIGFuIG9wdGltYWwgbW9kZWwgdGhhdCBnZW5lcmFsaXplcyB3ZWxsIGJhc2VkIG9uIG91ciB2YWxpZGF0aW9uCmFwcHJvYWNoIG1heSBiZSBkaWZmaWN1bHQuIFRoaXMgaXMgYmVjYXVzZSBvdXIgdmFsaWRhdGlvbiBkYXRhICh2aWEKYHZhbGlkYXRpb25fc3BsaXQgPSAwLjJgKSBvbmx5IGNvbnNpc3RzIG9mIDgwMCsgc2FtcGxlcy4gQ29uc2VxdWVudGx5LCBtb2RlbApwZXJmb3JtYW5jZSB3aWxsIGJlIGhpZ2hseSBkZXBlbmRlbnQgb24gdGhlc2UgODAwKyBzYW1wbGVzLgoKSW4gZ2VuZXJhbCwgdGhlIGZld2VyIG9ic2VydmF0aW9ucyBpbiBvdXIgdmFsaWRhdGlvbiBzZXQsIHRoZSBncmVhdGVyIHZhcmlhbmNlCmluIG91ciBsb3NzIHNjb3JlLiAgQXMgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gb3VyIHZhbGlkYXRpb24gZGF0YSAKaW5jcmVhc2VzLCB2YXJpYW5jZSBpbiBvdXIgbG9zcyBzY29yZSB3aWxsIGRlY3JlYXNlLiBIb3dldmVyLCB3ZSBkbyBub3QgYWx3YXlzIApoYXZlIHRoZSBvcHRpb24gdG8ganVzdCBnbyBvdXQgYW5kIGdldCBtb3JlIGRhdGEuIFNvLCBpZiB3ZSB3YW50IHRvIGdhaW4gYSBtb3JlIAphY2N1cmF0ZSB1bmRlcnN0YW5kaW5nIG9mIHRoZSBsb3NzIHNjb3JlIGFuZCBpdHMgdmFyaWFuY2Ugd2UgY291bGQgcGVyZm9ybSAKX2stZm9sZCBjcm9zcyB2YWxpZGF0aW9uXy4gCgpTZWUgdGhlIFt2YWxpZGF0aW9uIHByb2NlZHVyZXMgbm90ZWJvb2tdKGh0dHBzOi8vcnN0dWRpby1jb25mLTIwMjAuZ2l0aHViLmlvL2RsLWtlcmFzLXRmL25vdGVib29rcy92YWxpZGF0aW9uLXByb2NlZHVyZXMubmIuaHRtbCkgCmZvciBhbiBleGFtcGxlIG9mIHBlcmZvcm1pbmcgay1mb2xkIGNyb3NzIHZhbGlkYXRpb24uCgojIEtleSB0YWtlYXdheXMKCiogUHJlcGFyaW5nIHRhYnVsYXIgZGF0YQogICAtIFdlIG5lZWQgdG8gdmVjdG9yaXplIGNhdGVnb3JpY2FsIGZlYXR1cmVzCiAgIC0gV2UgbmVlZCB0byBzdGFuZGFyZGl6ZSBhbmQgbm9ybWFsaXplIG51bWVyaWMgZmVhdHVyZXMKKiBCYXRjaCBzaXplICYgZXBvY2hzCiAgIC0gU21hbGxlciBiYXRjaCBzaXplcyB0ZW5kIHRvIHBlcmZvcm0gYmVzdCAoc3BlZWQgYW5kIGxvc3MgbWluaW1pemF0aW9uKQogICAtIEhvd2V2ZXIsIHRoaXMgaXMgYSBoeXBlcnBhcmFtZXRlciB5b3UgY2FuIGFkanVzdCBhbmQgZXZhbHVhdGUKICAgLSBNYWtlIHN1cmUgeW91IGluY2x1ZGUgZW5vdWdoIGVwb2NocyB0byByZWFjaCBhIG1pbmltdW0gbG9zcwoqIExlYXJuaW5nIHJhdGUKICAgLSBUaGUgbW9zdCBpbXBvcnRhbnQgaHlwZXJwYXJhbWV0ZXIgdG8gdHVuZQogICAtIFR5cGljYWxseSB5b3Ugd2FudCB0byB0dW5lIHRoZSBsZWFybmluZyByYXRlIGFuZCBiYXRjaCBzaXplIHRvZ2V0aGVyCiogQ2FsbGJhY2tzCiAgIC0gVXNlIGVhcmx5IHN0b3BwaW5nIHRvIGF1dG9tYXRlIG1vZGVsIHRyYWluaW5nIGFuZCBzdG9wcGluZyBhZnRlciBsb3NzIGhhcwogICAgIGJlZW4gbWluaW1pemVkCiAgIC0gVXNlIGBjYWxsYmFja19yZWR1Y2VfbHJfb25fcGxhdGVhdSgpYCBmb3IgbW9yZSBjb250cm9sIG92ZXIgdGhlIGxlYXJuaW5nIAogICAgIHJhdGUuCiogS25vd2luZyB3aGVuIHRvIGFkanVzdCBtb2RlbCBjYXBhY2l0eQogICAtIFlvdXIgbWluaW11bSB2YWxpZGF0aW9uIGxvc3Mgc2NvcmUgc2hvdWxkIG5vdCBiZSBsZXNzIHRoYW4geW91ciB0cmFpbmluZwogICAgIGxvc3MKICAgLSBJZiBzbywgaW5jcmVhc2UgbW9kZWwgY2FwYWNpdHkgdW50aWwgeW91J3ZlIGJhbGFuY2VkIG92ZXJmaXR0aW5nIGFuZAogICAgIG1pbmltaXphdGlvbiBvZiB2YWxpZGF0aW9uIGxvc3Mgc2NvcmUgYW5kIHN0YWJpbGl0eQ==