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:

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:

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:

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:

  1. Our validation loss score has not reached a minimum.
  2. 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:

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:

  1. customize our optimizer with a larger learning rate to try speed up the downhill traversal of the gradient descent
  2. 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:

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 ℹ️:

  1. Add more units in each hidden layer
  2. 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

LS0tCnRpdGxlOiAiQ2FzZSBTdHVkeSAxOiBBbWVzIC0tIFJlZ3Jlc3Npb24gdG8gcHJlZGljdCBBbWVzLCBJQSBIb21lIFNhbGVzIFByaWNlcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSkKZ2dwbG90Mjo6dGhlbWVfc2V0KGdncGxvdDI6OnRoZW1lX21pbmltYWwoKSkKYGBgCgpJbiB0aGlzIGNhc2Ugc3R1ZHksIG91ciBvYmplY3RpdmUgaXMgdG8gcHJlZGljdCB0aGUgc2FsZXMgcHJpY2Ugb2YgYSBob21lLiBUaGlzIAppcyBhIF9yZWdyZXNzaW9uXyBwcm9ibGVtIHNpbmNlIHRoZSBnb2FsIGlzIHRvIHByZWRpY3QgYW55IHJlYWwgbnVtYmVyIGFjcm9zcwpzb21lIHNwZWN0cnVtIChcJDExOSwyMDEsIFwkMTY4LDU5NCwgXCQzMDEsNDQ2LCBldGMpLiBUbyBwcmVkaWN0IHRoZSBzYWxlcyAKcHJpY2UsIHdlIHdpbGwgdXNlIG51bWVyaWMgYW5kIGNhdGVnb3JpY2FsIGZlYXR1cmVzIG9mIHRoZSBob21lLgoKVGhyb3VnaG91dCB0aGlzIGNhc2Ugc3R1ZHkgeW91IHdpbGwgbGVhcm4gYSBmZXcgbmV3IGNvbmNlcHRzOgoKKiBWZWN0b3JpemF0aW9uIGFuZCBzdGFuZGFyZGl6YXRpb24gb2YgdGFidWxhciBmZWF0dXJlcwoqIEFkanVzdGluZyBiYXRjaCBzaXplICYgZXBvY2hzIGZvciB0cmFpbmluZyBwZXJmb3JtYW5jZQoqIFdoYXQgY2FsbGJhY2tzIGFyZSBhbmQgaG93IHRvIHN0YXJ0IGFwcGx5aW5nIHRoZW0KICAgLSBFYXJseSBzdG9wcGluZwogICAtIENvbnRyb2xsaW5nIHRoZSBsZWFybmluZyByYXRlCiogS25vd2luZyB3aGVuIHRvIGFkanVzdCBtb2RlbCBjYXBhY2l0eQoKIyBQYWNrYWdlIHJlcXVpcmVtZW50cwoKYGBge3IgbG9hZC1wa2dzfQpsaWJyYXJ5KGtlcmFzKSAgICAgIyBmb3IgZGVlcCBsZWFybmluZwpsaWJyYXJ5KHRlc3R0aGF0KSAgIyB1bml0IHRlc3RpbmcKbGlicmFyeSh0aWR5dmVyc2UpICMgZm9yIGRwbHlyLCBnZ3Bsb3QyLCBldGMuCmxpYnJhcnkocnNhbXBsZSkgICAjIGZvciBkYXRhIHNwbGl0dGluZwpsaWJyYXJ5KHJlY2lwZXMpICAgIyBmb3IgZmVhdHVyZSBlbmdpbmVlcmluZwpgYGAKCgojIFRoZSBBbWVzIGhvdXNpbmcgZGF0YXNldAoKRm9yIHRoaXMgY2FzZSBzdHVkeSB3ZSB3aWxsIHVzZSB0aGUgW0FtZXMgaG91c2luZyBkYXRhc2V0XShodHRwOi8vanNlLmFtc3RhdC5vcmcvdjE5bjMvZGVjb2NrLnBkZikgCnByb3ZpZGVkIGJ5IHRoZSBfX0FtZXNIb3VzaW5nX18gcGFja2FnZS4KCmBgYHtyIGdldC1kYXRhfQphbWVzIDwtIEFtZXNIb3VzaW5nOjptYWtlX2FtZXMoKQpkaW0oYW1lcykKYGBgCgojIFVuZGVyc3RhbmRpbmcgb3VyIGRhdGEKClRoaXMgZGF0YSBoYXMgYmVlbiBwYXJ0aWFsbHkgY2xlYW5lZCB1cCBhbmQgaGFzIG5vIG1pc3NpbmcgZGF0YToKCmBgYHtyfQpzdW0oaXMubmEoYW1lcykpCmBgYAoKQnV0IHRoaXMgdGFidWxhciBkYXRhIGlzIGEgY29tYmluYXRpb24gb2YgbnVtZXJpYyBhbmQgY2F0ZWdvcmljYWwgZGF0YSB0aGF0IHdlCm5lZWQgdG8gYWRkcmVzcy4KCmBgYHtyIGFtZXMtc3RydWN0dXJlfQpzdHIoYW1lcykKYGBgCgpUaGUgbnVtZXJpYyB2YXJpYWJsZXMgYXJlIG9uIGRpZmZlcmVudCBzY2FsZXMuIEZvciBleGFtcGxlOgoKYGBge3IgbnVtZXJpYy1yYW5nZXN9CmFtZXMgJT4lCiAgc2VsZWN0KExvdF9BcmVhLCBMb3RfRnJvbnRhZ2UsIFllYXJfQnVpbHQsIEdyX0xpdl9BcmVhLCBHYXJhZ2VfQ2FycywgTW9fU29sZCkgJT4lCiAgZ2F0aGVyKGZlYXR1cmUsIHZhbHVlKSAlPiUKICBnZ3Bsb3QoYWVzKGZlYXR1cmUsIHZhbHVlKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV95X2xvZzEwKGxhYmVscyA9IHNjYWxlczo6Y29tbWEpCmBgYAoKVGhlcmUgYXJlIGNhdGVnb3JpY2FsIGZlYXR1cmVzIHRoYXQgY291bGQgYmUgb3JkZXJlZDoKCmBgYHtyIG51bWVyaWMtY2F0ZWdvcmllc30KYW1lcyAlPiUKICBzZWxlY3QobWF0Y2hlcygiKFF1YWx8Q29uZHxRQ3xRdSkkIikpICU+JQogIHN0cigpCmBgYAoKQW5kIHNvbWUgb2YgdGhlIGNhdGVnb3JpY2FsIGZlYXR1cmVzIGhhdmUgbWFueSBsZXZlbHM6CgpgYGB7cn0KYW1lcyAlPiUKICBzZWxlY3RfaWYofiBpcy5mYWN0b3IoLikgJiBsZW5ndGgobGV2ZWxzKC4pKSA+IDgpICU+JQogIHN0cigpCmBgYAoKQ29uc2VxdWVudGx5LCBvdXIgZmlyc3QgY2hhbGxlbmdlIGlzIHRyYW5zZm9ybWluZyB0aGlzIGRhdGFzZXQgaW50byBudW1lcmljCnRlbnNvcnMgdGhhdCBvdXIgbW9kZWwgY2FuIHVzZS4KCiMgQ3JlYXRlIHRyYWluICYgdGVzdCBzcGxpdHMKCk9uZSBvZiB0aGUgZmlyc3QgdGhpbmdzIHdlIHdhbnQgdG8gZG8gaXMgY3JlYXRlIGEgdHJhaW4gYW5kIHRlc3Qgc2V0IGFzIHlvdQpwcm9iYWJseSBub3RpY2VkIHRoYXQgd2UgZG8gbm90IGhhdmUgYSB0cmFpbiBhbmQgdGVzdCBzZXQgc2ltaWxhciB0byBob3cgTU5JU1QgCndhcyBhbHJlYWR5IHNldCB1cCBmb3IgdXMuIFdlIGNhbiB1c2UgdGhlIF9fcnNhbXBsZV9fIHBhY2thZ2UgdG8gY3JlYXRlIG91cgp0cmFpbiBhbmQgdGVzdCBkYXRhc2V0cy4KCmBgYHtyfQpzZXQuc2VlZCgxMjMpCmFtZXNfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChhbWVzLCBwcm9wID0gMC43KQphbWVzX3RyYWluIDwtIGFuYWx5c2lzKGFtZXNfc3BsaXQpCmFtZXNfdGVzdCA8LSBhc3Nlc3NtZW50KGFtZXNfc3BsaXQpCgpkaW0oYW1lc190cmFpbikKZGltKGFtZXNfdGVzdCkKYGBgCgojIFByZXBhcmluZyB0aGUgZGF0YQoKQWxsIGlucHV0cyBhbmQgcmVzcG9uc2UgdmFsdWVzIGluIGEgbmV1cmFsIG5ldHdvcmsgbXVzdCBiZSB0ZW5zb3JzIG9mIGVpdGhlciAKZmxvYXRpbmctcG9pbnQgb3IgaW50ZWdlciBkYXRhLiBNb3Jlb3Zlciwgb3VyIGZlYXR1cmUgdmFsdWVzIHNob3VsZCBub3QgYmUKcmVsYXRpdmVseSBsYXJnZSBjb21wYXJlZCB0byB0aGUgcmFuZG9taXplZCBpbml0aWFsIHdlaWdodHMgX2FuZF8gYWxsIG91ciAKZmVhdHVyZXMgc2hvdWxkIHRha2UgdmFsdWVzIGluIHJvdWdobHkgdGhlIHNhbWUgcmFuZ2UuCgpDb25zZXF1ZW50bHksIHdlIG5lZWQgdG8gX19fdmVjdG9yaXplX19fIG91ciBkYXRhIGludG8gYSBmb3JtYXQgY29uZHVjaXZlIHRvIG5ldXJhbCAKbmV0d29ya3MgW+KEue+4j10oaHR0cDovL2JpdC5seS9kbC0wMiMzKS4gRm9yIHRoaXMgZGF0YSBzZXQsIHdlJ2xsIHRyYW5zZm9ybSBvdXIKZGF0YSBieToKCi0gcmVtb3ZpbmcgYW55IHplcm8tdmFyaWFuY2UgKG9yIG5lYXIgemVyby12YXJpYW5jZSkgZmVhdHVyZXMKLSBjb25kZW5zaW5nIHVuaXF1ZSBsZXZlbHMgb2YgY2F0ZWdvcmljYWwgZmVhdHVyZXMgdG8gIm90aGVyIgotIG9yZGluYWwgZW5jb2RpbmcgdGhlIHF1YWxpdHkgZmVhdHVyZXMKLSBub3JtYWxpemUgbnVtZXJpYyBmZWF0dXJlIGRpc3RyaWJ1dGlvbnMKLSBzdGFuZGFyZGl6aW5nIG51bWVyaWMgZmVhdHVyZXMgdG8gbWVhbiA9IDAsIHN0ZCBkZXYgPSAxCi0gb25lLWhvdCBlbmNvZGluZyByZW1haW5pbmcgY2F0ZWdvcmljYWwgZmVhdHVyZXMKCmBgYHtyfQpibHVlcHJpbnQgPC0gcmVjaXBlKFNhbGVfUHJpY2UgfiAuLCBkYXRhID0gYW1lc190cmFpbikgJT4lCiAgc3RlcF9uenYoYWxsX25vbWluYWwoKSkgJT4lCiAgc3RlcF9vdGhlcihhbGxfbm9taW5hbCgpLCB0aHJlc2hvbGQgPSAuMDEsIG90aGVyID0gIm90aGVyIikgJT4lCiAgc3RlcF9pbnRlZ2VyKG1hdGNoZXMoIihRdWFsfENvbmR8UUN8UXUpJCIpKSAlPiUKICBzdGVwX1llb0pvaG5zb24oYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUKICBzdGVwX2NlbnRlcihhbGxfbnVtZXJpYygpLCAtYWxsX291dGNvbWVzKCkpICU+JQogIHN0ZXBfc2NhbGUoYWxsX251bWVyaWMoKSwgLWFsbF9vdXRjb21lcygpKSAlPiUKICBzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIC1hbGxfb3V0Y29tZXMoKSwgb25lX2hvdCA9IFRSVUUpCgpibHVlcHJpbnQKYGBgCgpUaGlzIG5leHQgc3RlcCBjb21wdXRlcyBhbnkgcmVsYXZlbnQgaW5mb3JtYXRpb24gKG1lYW4gYW5kIHN0ZCBkZXZpYXRpb24gb2YKbnVtZXJpYyBmZWF0dXJlcywgbmFtZXMgb2Ygb25lLWhvdCBlbmNvZGVkIGZlYXR1cmVzKSBvbiB0aGUgdHJhaW5pbmcgZGF0YSBzbwp0aGVyZSBpcyBubyBpbmZvcm1hdGlvbiBsZWFrYWdlIGZyb20gdGhlIHRlc3QgZGF0YS4KCmBgYHtyfQpwcmVwYXJlIDwtIHByZXAoYmx1ZXByaW50LCB0cmFpbmluZyA9IGFtZXNfdHJhaW4pCnByZXBhcmUKYGBgCgpXZSBjYW4gbm93IHZlY3Rvcml6ZSBvdXIgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YS4gSWYgeW91IHNjcm9sbCB0aHJvdWdoIHRoZSBkYXRhCnlvdSB3aWxsIG5vdGljZSB0aGF0IGFsbCBmZWF0dXJlcyBhcmUgbm93IG51bWVyaWMgYW5kIGFyZSBlaXRoZXIgMC8xIChvbmUgaG90CmVuY29kZWQgZmVhdHVyZXMpIG9yIGhhdmUgbWVhbiAwIGFuZCBnZW5lcmFsbHkgcmFuZ2UgYmV0d2VlbiAtMyBhbmQgMy4KCmBgYHtyfQpiYWtlZF90cmFpbiA8LSBiYWtlKHByZXBhcmUsIG5ld19kYXRhID0gYW1lc190cmFpbikKYmFrZWRfdGVzdCA8LSBiYWtlKHByZXBhcmUsIG5ld19kYXRhID0gYW1lc190ZXN0KQoKIyB1bml0IHRlc3RpbmcgdG8gZW5zdXJlIGFsbCBjb2x1bW5zIGFyZSBudW1lcmljCmV4cGVjdF9lcXVhbChtYXBfbGdsKGJha2VkX3RyYWluLCB+ICFpcy5udW1lcmljKC4pKSAlPiUgc3VtKCksIDApCmV4cGVjdF9lcXVhbChtYXBfbGdsKGJha2VkX3Rlc3QsIH4gIWlzLm51bWVyaWMoLikpICU+JSBzdW0oKSwgMCkKCmJha2VkX3RyYWluCmBgYAoKTGFzdGx5LCB3ZSBuZWVkIHRvIGNyZWF0ZSB0aGUgZmluYWwgZmVhdHVyZSBhbmQgcmVzcG9uc2Ugb2JqZWN0cyBmb3IgdHJhaW4gYW5kIAp0ZXN0IGRhdGEuIFNpbmNlIF9fa2VyYXNfXyBhbmQgX190ZW5zb3JmbG93X18gcmVxdWlyZSBvdXIgZmVhdHVyZXMgJiBsYWJlbHMgdG8gYmUgCnNlcGVyYXRlIG9iamVjdHMgd2UgbmVlZCB0byBzZXBhcmF0ZSB0aGVtLiBJbiBkb2luZyBzbywgb3VyIGZlYXR1cmVzIG5lZWQgdG8gYmUgCmEgMkQgdGVuc29yIHdoaWNoIGlzIHdoeSB3ZSBhcHBseSBgYXMubWF0cml4YCBhbmQgb3VyIHJlc3BvbnNlIG5lZWRzIHRvIGJlIGEgCnZlY3RvciB3aGljaCBpcyB3aHkgd2UgYXBwbHkgYHB1bGxgLgoKYGBge3J9CnhfdHJhaW4gPC0gc2VsZWN0KGJha2VkX3RyYWluLCAtU2FsZV9QcmljZSkgJT4lIGFzLm1hdHJpeCgpCnlfdHJhaW4gPC0gYmFrZWRfdHJhaW4gJT4lIHB1bGwoU2FsZV9QcmljZSkKCnhfdGVzdCA8LSBzZWxlY3QoYmFrZWRfdGVzdCwgLVNhbGVfUHJpY2UpICU+JSBhcy5tYXRyaXgoKQp5X3Rlc3QgPC0gYmFrZWRfdGVzdCAlPiUgcHVsbChTYWxlX1ByaWNlKQoKIyB1bml0IHRlc3RpbmcgdG8geCAmIHkgdGVuc29ycyBoYXZlIHNhbWUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucwpleHBlY3RfZXF1YWwobnJvdyh4X3RyYWluKSwgbGVuZ3RoKHlfdHJhaW4pKQpleHBlY3RfZXF1YWwobnJvdyh4X3Rlc3QpLCBsZW5ndGgoeV90ZXN0KSkKYGBgCgpPdXIgZmluYWwgZmVhdHVyZSBzZXQgbm93IGhhcyAxODggaW5wdXQgdmFyaWFibGVzOgoKYGBge3J9CmRpbSh4X3RyYWluKQpkaW0oeF90ZXN0KQpgYGAKCiMgSW5pdGlhbCBtb2RlbAoKT3VyIGluaXRpYWwgbW9kZWwgbG9va3MgZmFpcmx5IHNpbWlsYXIgdG8gdGhlIE1OSVNUIG1vZGVsIHdlIGFwcGxpZWQuIEhvd2V2ZXIsIApub3RlIHRoZSBmb2xsb3dpbmcgZGlmZmVyZW5jZXM6CgoqIEZpbmFsIG91dHB1dCBsYXllciBoYXMgYHVuaXRzID0gMWAgYW5kIG5vIGFjdGl2YXRpb24gZnVuY3Rpb24gc2luY2UgdGhpcyBpcyBhIAogIHJlZ3Jlc3Npb24gcHJvYmxlbS4KKiBXZSBhcmUgdXNpbmcgYSBkaWZmZXJlbnQgbG9zcyBhbmQgbWV0cmljIGZ1bmN0aW9uLiBUaGUgb3JpZ2luYWwgS2FnZ2xlCiAgY29tcGV0aXRpb24gaGFkIHlvdSBsb2cgdGhlIHJlc3BvbnNlIHZhcmlhYmxlIGFuZCB0aGVuIHVzZSBNU0UuIFRoaXMgaXMgCiAgZXF1aXZhbGVudCB0byB1c2luZyB0aGUgTVNMRSBsb3NzIGZ1bmN0aW9uLiBb4oS577iPXShodHRwOi8vYml0Lmx5LzJSNHVoMkwpCiogVGhlIGJhdGNoIHNpemUgaXMgbXVjaCBzbWFsbGVyCgpgYGB7ciBpbml0aWFsLW1vZGVsfQpuZXR3b3JrIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMjgsIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gbmNvbCh4X3RyYWluKSkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEpCgpuZXR3b3JrICU+JSBjb21waWxlKAogICAgb3B0aW1pemVyID0gInJtc3Byb3AiLAogICAgbG9zcyA9ICJtc2xlIiwKICAgIG1ldHJpY3MgPSBjKCJtYWUiKQogICkKYGBgIAoKYGBge3IgbW9kZWwtc3VtbWFyeX0Kc3VtbWFyeShuZXR3b3JrKQpgYGAgIAoKYGBge3IgdHJhaW4sIHJlc3VsdHM9J2hpZGUnfQpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICB4X3RyYWluLAogIHlfdHJhaW4sCiAgYmF0Y2hfc2l6ZSA9IDMyLAogIGVwb2NocyA9IDIwLAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIKKQpgYGAKCk91ciByZXN1bHRzIGJlbG93IHNob3cgdHdvIHRoaW5nczoKCjEuIE91ciB2YWxpZGF0aW9uIGxvc3Mgc2NvcmUgaGFzIG5vdCByZWFjaGVkIGEgbWluaW11bS4KMi4gVGhlcmUgYXJlIG5vIHNpZ25zIG9mIG92ZXJmaXR0aW5nLgoKYGBge3J9CnBsb3QoaGlzdG9yeSkgKyBzY2FsZV95X2xvZzEwKCkKYGBgCgojIENvbnNpZGVyYXRpb25zIHJlZ2FyZGluZyBiYXRjaCBzaXplcyBhbmQgZXBvY2hzCgpGaXJzdCwgbGV0J3MgZGlzY3VzcyBiYXRjaCBzaXplcyBhbmQgZXBvY2hzIGFzIHRoZXkgY2FuIGhhdmUgYSBzaWduaWZpY2FudCAKaW1wYWN0IG9uIGhvdyBxdWlja2x5IHdlIHN0YXJ0IHJlYWNoaW5nIG91ciBtaW5pbXVtIGxvc3MgZnVuY3Rpb24uCgpEaWZmZXJlbmNlcyBpbiBwZXJmb3JtYW5jZSBmb3IgZGlmZmVyaW5nIGJhdGNoIHNpemUgbGFyZ2VseSBkZXBlbmRzIG9uIHRoZSAKdW5kZXJseWluZyAoYW5kIHR5cGljYWxseSB1bmtub3duKSByZWFsIGNvc3QgZnVuY3Rpb247IGhvd2V2ZXIsIGhlcmUgYXJlIHNvbWUKYmFzaWMgZ3VpZGVsaW5lczoKCi0gYmF0Y2ggc2l6ZXMgY29tbW9ubHkgdGFrZSBvbiB2YWx1ZXMgb2YgJDJecyQgKGkuZS4gMzIsIDY0LCAxMjgsIDI1NiwgNTEyKSwKICAgLSBzbWFsbGVyIGJhdGNoIHNpemVzLi4uCiAgICAgIC0gY2FuIGhhdmUgbW9yZSB2YXJpYWJpbGl0eTsgaG93ZXZlciwgdGhpcyB2YXJpYWJpbGl0eSBjYW4gaGVscCB1cyBzdGF5IG91dAogICAgICAgIG9mIGxvY2FsIG1pbmltdW1ucyAKICAgICAgLSBjYW4gZGVjcmVhc2UgbGVhcm5pbmcgdGltZQogICAtIGxhcmdlciBiYXRjaCBzaXplcy4uLgogICAgICAtIGhhdmUgbGVzcyB2YXJpYWJpbGl0eSBidXQgCiAgICAgIC0gY2FuIGluY3JlYXNlIGxlYXJuaW5nIHRpbWUKLSBvcHRpbWFsIGJhdGNoIHNpemVzIGNhbiBiZSBpbmZsdWVuY2VkIGJ5IHNpemUgb2YgZGF0YToKICAgLSBsYXJnZXIgX25fIGNhbiBhZmZvcmQgbGFyZ2VyIGJhdGNoIHNpemVzICgxMjgsIDI1NiwgNTEyKQogICAtIHNtYWxsZXIgX25fIGNhbiBhZmZvcmQgc21hbGxlciBiYXRjaCBzaXplcyAoMTYsIDMyLCA2NCkKLSBXaGljaCBpcyBiZXN0PwogICAtIEkgdHlwaWNhbGx5IHN0YXJ0IHdpdGggMzIgb3IgNjQKICAgLSBUcmlhbCBhbmQgZXJyb3IgZm9yIHlvdXIgc3BlY2lmaWMgcHJvYmxlbQotIHVzZSBlbm91Z2ggZXBvY2hzIHNvIHRoYXQgb3VyIGxlYXJuaW5nIHJhdGUgcmVhY2hlcyBhIG1pbmltdW0KCkhlcmUgYXJlIHNvbWUgZ29vZCBhcnRpY2xlcyBkaXNjdXNzaW5nIHRoZSBpbXBhY3RzIG9mIGJhdGNoIHNpemU6CgogICAtIFtFZmZlY3Qgb2YgYmF0Y2ggc2l6ZSBvbiB0cmFpbmluZyBkeW5hbWljc10oaHR0cDovL2JpdC5seS8yclFkTzdHKQogICAtIFtPbiBMYXJnZS1CYXRjaCBUcmFpbmluZyBmb3IgRGVlcCBMZWFybmluZzogR2VuZXJhbGl6YXRpb24gR2FwIGFuZCBTaGFycCBNaW5pbWFdKGh0dHBzOi8vYXJ4aXYub3JnL2Ficy8xNjA5LjA0ODM2KQogICAtIFtEZWVwIExlYXJuaW5nIGJvb2ssIFNlY3Rpb24gOC4xLjNdKGh0dHA6Ly93d3cuZGVlcGxlYXJuaW5nYm9vay5vcmcvY29udGVudHMvb3B0aW1pemF0aW9uLmh0bWwpCiAgIC0gW0ltcGFjdCBvZiBUcmFpbmluZyBTZXQgQmF0Y2ggU2l6ZSBvbiB0aGUgUGVyZm9ybWFuY2VdKGh0dHA6Ly9iaXQubHkvMzVWV003ZSkKCiMgWU9VUiBUVVJOISAoNSBtaW4pCgpUcnkgZGlmZmVyZW50IGJhdGNoIHNpemVzIGFuZCBlcG9jaHMgYW5kIHNlZSBob3cgbW9kZWwgcGVyZm9ybWFuY2UgY2hhbmdlcy4gClJlbWVtYmVyLCBiYXRjaCBzaXplcyBhcmUgdHlwaWNhbGx5IHBvd2VycyBvZiAyIChpLmUuIDE2LCAzMiwgNjQsIDEyOCwgMjU2LCA1MTIpLgoKYGBge3IgeW91ci10dXJuLTF9Cm5ldHdvcmsgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IDEyOCwgYWN0aXZhdGlvbiA9ICJyZWx1IiwgaW5wdXRfc2hhcGUgPSBfX19fKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMjgsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gX19fXykKCm5ldHdvcmsgJT4lIGNvbXBpbGUoCiAgICBvcHRpbWl6ZXIgPSAicm1zcHJvcCIsCiAgICBsb3NzID0gIm1zbGUiLAogICAgbWV0cmljcyA9IGMoIm1hZSIpCiAgKQoKaGlzdG9yeSA8LSBuZXR3b3JrICU+JSBmaXQoCiAgeF90cmFpbiwKICB5X3RyYWluLAogIGVwb2NocyA9IF9fX18sCiAgYmF0Y2hfc2l6ZSA9IF9fX18sCiAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMgopCmBgYAoKIyBFYXJseSBzdG9wcGluZwoKWW91IGxpa2VseSBub3RpY2VkIHRoYXQgYSBsYXJnZSBiYXRjaCBzaXplIChpLmUuIDUxMikgdG9vayBtYW55IG1vcmUgZXBvY2hzIHRvCnN0YXJ0IHJlYWNoaW5nIGEgbWluaW11bSB2ZXJzdXMgYSBzbWFsbGVyIGJhdGNoIHNpemUgKGkuZS4gMTYpLiBCdXQgcmVnYXJkbGVzcwpvZiBiYXRjaCBzaXplLCB5b3Ugc3RpbGwgaGFkIHRvIGRvIGEgZmFpciBhbW91bnQgb2YgdHJpYWwgYW5kIGVycm9yIHRvIGZpbmQgdGhlCnJpZ2h0IG51bWJlciBvZiBlcG9jaHMgdG8gcmVhY2ggYSBtaW5pbXVtIGxvc3Mgc2NvcmUuCgpMZXQncyBtZWV0IG91ciBmaXJzdCBfX19jYWxsYmFja19fXywgd2hpY2ggY2FuIGhlbHAgaW4gdGhpcyBzaXR1YXRpb24gW+KEue+4j10oaHR0cDovL2JpdC5seS9kbC0wMiMxNCkuIApVc2luZyBlYXJseSBzdG9wcGluZyBhbGxvd3MgdXMgdG8gY3JhbmsgdXAgdGhlIG51bWJlciBvZiBlcG9jaHMgYW5kIGxldCB0aGUgCnRyYWluaW5nIGF1dG9tYXRpY2FsbHkgc3RvcCBhZnRlciB3ZSBleHBlcmllbmNlIG5vIGltcHJvdmVtZW50IGluIG91ciBsb3NzIGFmdGVyCmBwYXRpZW5jZWAgbnVtYmVyIG9mIGVwb2Nocy4gCiAgCmBgYHtyIGluaXRpYWwtbW9kZWwtZWFybHktc3RvcHBpbmd9Cm5ldHdvcmsgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IDEyOCwgYWN0aXZhdGlvbiA9ICJyZWx1IiwgaW5wdXRfc2hhcGUgPSBuY29sKHhfdHJhaW4pKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMjgsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMSkgCgpuZXR3b3JrICU+JQogIGNvbXBpbGUoCiAgICBvcHRpbWl6ZXIgPSAicm1zcHJvcCIsCiAgICBsb3NzID0gIm1zbGUiLAogICAgbWV0cmljcyA9ICJtYWUiCiAgKQoKaGlzdG9yeSA8LSBuZXR3b3JrICU+JSBmaXQoCiAgeF90cmFpbiwKICB5X3RyYWluLAogIGVwb2NocyA9IDI1MCwKICBiYXRjaF9zaXplID0gMzIsCiAgdmFsaWRhdGlvbl9zcGxpdCA9IDAuMiwKICBjYWxsYmFja3MgPSBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IDUsIHJlc3RvcmVfYmVzdF93ZWlnaHRzID0gVFJVRSkKKQpgYGAKCmBgYHtyIGVhcmx5LXN0b3BwaW5nLW1vZGVsLXBlcmZvcm1hbmNlfQpoaXN0b3J5CgpjYXQoIlxuVGhlIG1pbmltdW0gbG9zcyBzY29yZSBpcyIsIG1pbihoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3MpICU+JSByb3VuZCg0KSwKICAgICJ3aGljaCBvY2N1cnJlZCBhdCBlcG9jaCIsIHdoaWNoLm1pbihoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3MpKQpgYGAKCgpgYGB7ciBlYXJseS1zdG9wcGluZy1wbG90fQpwbG90KGhpc3RvcnkpICsgc2NhbGVfeV9sb2cxMCgpCmBgYAoKIyBBZGp1c3RhYmxlIGxlYXJuaW5nIHJhdGUKCk9uZSB0aGluZyB5b3UgbWF5IG5vdGljZSBpcyB0aGF0IHRoZXJlIGlzIHNpZ25pZmljYW50IGxlYXJuaW5nIGhhcHBlbmluZyBmb3IgdGhlCmZpcnN0IDE1LTIwIGVwb2NocyBhbmQgdGhlbiB0aGUgbW9kZWwgc2xvd2x5IGNoaXBzIGF3YXkgYXQgdGhlIGxvc3MgZm9yIHRoZSBuZXh0Cn4xMDArIGVwb2Nocy4gIFdlIGNhbiBzcGVlZCB1cCB0aGlzIHByb2Nlc3Mgd2l0aCB0d28gdGhpbmdzOgoKMS4gY3VzdG9taXplIG91ciBvcHRpbWl6ZXIgd2l0aCBhIGxhcmdlciBsZWFybmluZyByYXRlIHRvIHRyeSBzcGVlZCB1cCB0aGUgCiAgIGRvd25oaWxsIHRyYXZlcnNhbCBvZiB0aGUgZ3JhZGllbnQgZGVzY2VudAoyLiBhZGQgYSBjYWxsYmFjayB0aGF0IHNsb3dseSByZWR1Y2VzIHRoZSBsZWFybmluZyByYXRlIGJ5IDIwJSBpZiB3ZSBkb24ndAogICBleHBlcmllbmNlIGltcHJvdmVtZW50IGluIG91ciBsb3NzIGZvciBgcGF0aWVuY2VgIG51bWJlciBvZiBlcG9jaHMuCiAgIApOb3RlIGhvdyB3ZSBhcmUgbm93IHVzaW5nIGBvcHRpbWl6ZXIgPSBvcHRpbWl6ZXJfcm1zcHJvcChsciA9IDAuMDEpYCBpbnN0ZWFkIG9mCmBvcHRpbWl6ZXIgPSAicm1zcHJvcCJgIHNvIHRoYXQgd2UgY2FuIGN1c3RvbWl6ZSB0aGUgbGVhcm5pbmcgcmF0ZS4KCmBgYHtyIGluaXRpYWwtbW9kZWwtYWRqLWxyfQpuZXR3b3JrIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMjgsIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gbmNvbCh4X3RyYWluKSkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEpIAoKbmV0d29yayAlPiUKICBjb21waWxlKAogICAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAwLjAxKSwKICAgIGxvc3MgPSAibXNsZSIsCiAgICBtZXRyaWNzID0gYygibWFlIikKICApCgpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICB4X3RyYWluLAogIHlfdHJhaW4sCiAgZXBvY2hzID0gNTAsCiAgYmF0Y2hfc2l6ZSA9IDMyLAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIsCiAgY2FsbGJhY2tzID0gbGlzdCgKICAgICAgICBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IDEwLCByZXN0b3JlX2Jlc3Rfd2VpZ2h0cyA9IFRSVUUpLAogICAgICAgIGNhbGxiYWNrX3JlZHVjZV9scl9vbl9wbGF0ZWF1KGZhY3RvciA9IDAuMiwgcGF0aWVuY2UgPSA0KQogICAgKQopCmBgYAoKTm93IHdlIHNlZSBhIG11Y2ggZmFzdGVyIHByb2Nlc3Mgb2YgbW9kZWwgdHJhaW5pbmc7IGl0IG9ubHkgdGFrZXMgfjI1JSB0aGUgCm51bWJlciBvZiBlcG9jaHMgYW5kIGl0IGFwcGVhcnMgdGhhdCB3ZSBhY3R1YWxseSBzZWUgYW4gaW5jcmVhc2UgaW4gcGVyZm9ybWFuY2UhCgpgYGB7ciBhZGotbGVhcm5pbmctcmF0ZS1wZXJmb3JtYW5jZX0KaGlzdG9yeQoKY2F0KCJcblRoZSBtaW5pbXVtIGxvc3Mgc2NvcmUgaXMiLCBtaW4oaGlzdG9yeSRtZXRyaWNzJHZhbF9sb3NzKSAlPiUgcm91bmQoNCksCiAgICAid2hpY2ggb2NjdXJyZWQgYXQgZXBvY2giLCB3aGljaC5taW4oaGlzdG9yeSRtZXRyaWNzJHZhbF9sb3NzKSkKYGBgCgoKYGBge3IgYWRqLWxlYXJuaW5nLXJhdGUtcGxvdH0KcGxvdChoaXN0b3J5KSArIAogIHNjYWxlX3lfbG9nMTAoKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgbGVuZ3RoKGhpc3RvcnkkbWV0cmljcyR2YWxfbG9zcykpKQpgYGAKCiMgWU9VUiBUVVJOISAoNSBtaW4pCgpUcnkgZGlmZmVyZW50IHZhcmlhdGlvbnMgb2YgbGVhcm5pbmcgcmF0ZSwgcGF0aWVuY2UgcGFyYW1ldGVycywgYW5kIGxlYXJuaW5nIApyYXRlIHJlZHVjdGlvbiBmYWN0b3IuICBIZXJlIGFyZSBhIGNvdXBsZSBvZiB0aGluZ3MgdG8ga2VlcCBpbiBtaW5kOgoKLSBfb3B0aW1pemVyIGxlYXJuaW5nIHJhdGVfIHJhbmdlcyB3b3J0aCBleHBsb3JpbmcgZGlmZmVyIGRlcGVuZGluZyBvbiB0aGUgCiAgb3B0aW1pemVyIGJ1dCBmb3IgUk1TUHJvcCBjb21tb24gcmFuZ2VzIGluY2x1ZGUgMC4xLTAuMDAwMS4KLSBfbGVhcm5pbmcgcmF0ZSByZWR1Y3Rpb24gZmFjdG9yXyB0eXBpY2FsbHkgcmFuZ2UgZnJvbSAwLjUtMC4xLiAgCi0gdGhlIHBhdGllbmNlIHZhbHVlIGZvciBfZWFybHkgc3RvcHBpbmdfIHNob3VsZCBub3QgYmUgc2hvcnRlciB0aGFuIHRoZSAKICBwYXRpZW5jZSB2YWx1ZSBmb3IgX2xlYXJuaW5nIHJhdGUgcmVkdWN0aW9uXywgb3RoZXJ3aXNlIHRoZSBsZWFybmluZyByYXRlIHdpbGwgCiAgbmV2ZXIgaGF2ZSB0aGUgb3Bwb3J0dW5pdHkgdG8gZGVjcmVhc2UuCgpgYGB7ciB5b3VyLXR1cm4tMn0KbmV0d29yayA8LSBrZXJhc19tb2RlbF9zZXF1ZW50aWFsKCkgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gMTI4LCBhY3RpdmF0aW9uID0gX19fXywgaW5wdXRfc2hhcGUgPSBfX19fKSAlPiUgCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMjgsIGFjdGl2YXRpb24gPSBfX19fKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IF9fX18pIAoKbmV0d29yayAlPiUKICBjb21waWxlKAogICAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSBfX19fKSwKICAgIGxvc3MgPSAibXNsZSIsCiAgICBtZXRyaWNzID0gYygibWFlIikKICApCgpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICB4X3RyYWluLAogIHlfdHJhaW4sCiAgZXBvY2hzID0gNTAsCiAgYmF0Y2hfc2l6ZSA9IDMyLAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIsCiAgY2FsbGJhY2tzID0gbGlzdCgKICAgICAgICBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IF9fX18pLAogICAgICAgIGNhbGxiYWNrX3JlZHVjZV9scl9vbl9wbGF0ZWF1KGZhY3RvciA9IF9fX18sIHBhdGllbmNlID0gX19fXykKICAgICkKKQpgYGAKCiMgVW5kZXIgY2FwYWNpdHkKClVuZm9ydHVuYXRlbHksIG91ciBtb2RlbCBpcyBzdGlsbCB1bmRlcmZpdHRpbmcgd2hlbiB3ZSByZWFjaCBvdXIgbWluaW11bQp2YWxpZGF0aW9uIGxvc3Mgc2NvcmUuIFRoaXMgaXMgYSBjbGFzc2ljIHNpZ24gdGhhdCB3ZSBhcmUgdW5kZXItY2FwYWNpdHkuIFRoZXJlCmFyZSB0d28gd2F5cyB0byBpbmNyZWFzZSBtb2RlbCBjYXBhY2l0eSBb4oS577iPXShodHRwOi8vYml0Lmx5L2RsLTAyIzE3KToKCjEuIEFkZCBtb3JlIHVuaXRzIGluIGVhY2ggaGlkZGVuIGxheWVyCjIuIEFkZCBtb3JlIGhpZGRlbiBsYXllcnMKCkluIHRoZSBuZXh0IG1vZHVsZSwgd2UnbGwgbG9vayBhdCBob3cgdG8gZHluYW1pY2FsbHkgYXNzZXNzIHRoZXNlIGlucHV0cyBidXQgZm9yCm5vdywgc3BlbmQgdGhlIG5leHQgMyBtaW51dGVzIGFkanVzdGluZyB0aGUgbnVtYmVyIG9mIHVuaXRzIGluIGVhY2ggaGlkZGVuIGxheWVyCmFuZC9vciBhZGp1c3RpbmcgdGhlIG51bWJlciBvZiBoaWRkZW4gbGF5ZXJzLgoKIyMgWU9VUiBUVVJOISAoNSBtaW4pCgpUcnkgZGlmZmVyZW50IGJhdGNoIHNpemVzIGFuZCBlcG9jaHMgYW5kIHNlZSBob3cgbW9kZWwgcGVyZm9ybWFuY2UgY2hhbmdlcy4gClJlbWVtYmVyLCBiYXRjaCBzaXplcyBhcmUgdHlwaWNhbGx5IHBvd2VycyBvZiAyIChpLmUuIDE2LCAzMiwgNjQsIDEyOCwgMjU2LCA1MTIpLgoKYGBge3IgeW91ci10dXJuLTN9Cm5ldHdvcmsgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IF9fX18sIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gX19fXykgJT4lIAogIGxheWVyX2RlbnNlKHVuaXRzID0gX19fXywgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSBfX19fKQoKbmV0d29yayAlPiUKICBjb21waWxlKAogICAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAwLjAxKSwKICAgIGxvc3MgPSAibXNsZSIsCiAgICBtZXRyaWNzID0gYygibWFlIikKICApCgpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICB4X3RyYWluLAogIHlfdHJhaW4sCiAgZXBvY2hzID0gNTAsCiAgYmF0Y2hfc2l6ZSA9IDMyLAogIHZhbGlkYXRpb25fc3BsaXQgPSAwLjIsCiAgY2FsbGJhY2tzID0gbGlzdCgKICAgICAgICBjYWxsYmFja19lYXJseV9zdG9wcGluZyhwYXRpZW5jZSA9IDEwLCByZXN0b3JlX2Jlc3Rfd2VpZ2h0cyA9IFRSVUUpLAogICAgICAgIGNhbGxiYWNrX3JlZHVjZV9scl9vbl9wbGF0ZWF1KGZhY3RvciA9IDAuMiwgcGF0aWVuY2UgPSA0KQogICAgKQopCmBgYAoKIyMgV2hhdCB0byBsb29rIGZvcgoKVHlwaWNhbGx5LCBJIGFkZCB1bml0cyBhbmQgbGF5ZXJzIHVudGlsIEkgc2VlIHNpZ25pZmljYW50IG92ZXJmaXR0aW5nIG9yIHN0YXJ0CnRvIHNlZSBoaWdoIHZhcmlhYmlsaXR5IGluIG91ciBsb3NzIHNjb3JlIG9yIG1ldHJpY3MgYW5kIHRoZW4gY29uc3RyYWluIHRoZSBtb2RlbApmcm9tIHRoZXJlLiAKCkZvciBleGFtcGxlLCB0aGUgZm9sbG93aW5nIG1vZGVsIHdpdGggNSBoaWRkZW4gbGF5ZXJzIGNvbnNpc3Rpbmcgb2YgMTAyNCB1bml0cwplYWNoIG92ZXJmaXRzIGF0IHRoZSBtaW5pbXVtIHZhbGlkYXRpb24gbG9zcyBzY29yZSAoYnV0IG5vdCBtdWNoKSBhbmQKc2hvd3Mgc2lnbnMgb2YgbG9zcyBhbmQgbWV0cmljIHZhcmlhYmlsaXR5LiBGcm9tIGhlcmUsIEkgd291bGQgc3RhcnQgdG8KcmVndWxhcml6ZSB0aGUgbW9kZWwgYnkgcmVtb3ZpbmcgbGF5ZXJzLCByZWR1Y2UgdGhlIG51bWJlciBvZiB1bml0cyBpbiBlYWNoCmxheWVyLCBvciB1c2luZyBhbiBhbHRlcm5hdGl2ZSByZWd1bGFyaXphdGlvbiBtZXRob2QgdW50aWwgSSBmaW5kIGEgaGFwcHkKY29tcHJvbWlzZSBiZXR3ZWVuIG1vZGVsIGNhcGFjaXR5LCBsb3NzIG1pbmltaXphdGlvbiAmIHN0YWJpbGl0eS4KCmBgYHtyIG92ZXJmaXQtbW9kZWx9Cm5ldHdvcmsgPC0ga2VyYXNfbW9kZWxfc2VxdWVudGlhbCgpICU+JSAKICBsYXllcl9kZW5zZSh1bml0cyA9IDEwMjQsIGFjdGl2YXRpb24gPSAicmVsdSIsIGlucHV0X3NoYXBlID0gbmNvbCh4X3RyYWluKSkgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxMDI0LCBhY3RpdmF0aW9uID0gInJlbHUiKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEwMjQsIGFjdGl2YXRpb24gPSAicmVsdSIpICU+JQogIGxheWVyX2RlbnNlKHVuaXRzID0gMTAyNCwgYWN0aXZhdGlvbiA9ICJyZWx1IikgJT4lCiAgbGF5ZXJfZGVuc2UodW5pdHMgPSAxKQoKbmV0d29yayAlPiUKICBjb21waWxlKAogICAgb3B0aW1pemVyID0gb3B0aW1pemVyX3Jtc3Byb3AobHIgPSAwLjAxKSwKICAgIGxvc3MgPSAibXNsZSIsCiAgICBtZXRyaWNzID0gYygibWFlIikKICApCgpoaXN0b3J5IDwtIG5ldHdvcmsgJT4lIGZpdCgKICB4X3RyYWluLAogIHlfdHJhaW4sCiAgZXBvY2hzID0gMjUwLAogIGJhdGNoX3NpemUgPSAzMiwKICB2YWxpZGF0aW9uX3NwbGl0ID0gMC4yLAogIGNhbGxiYWNrcyA9IGxpc3QoCiAgICAgICAgY2FsbGJhY2tfZWFybHlfc3RvcHBpbmcocGF0aWVuY2UgPSAxMCwgcmVzdG9yZV9iZXN0X3dlaWdodHMgPSBUUlVFKSwKICAgICAgICBjYWxsYmFja19yZWR1Y2VfbHJfb25fcGxhdGVhdShmYWN0b3IgPSAwLjIsIHBhdGllbmNlID0gNCkKICAgICkKKQpgYGAKCmBgYHtyfQpjYXQoIlRoZSBtaW5pbXVtIGxvc3Mgc2NvcmUgaXMiLCBtaW4oaGlzdG9yeSRtZXRyaWNzJHZhbF9sb3NzKSAlPiUgcm91bmQoNCksCiAgICAid2hpY2ggb2NjdXJyZWQgYXQgZXBvY2giLCB3aGljaC5taW4oaGlzdG9yeSRtZXRyaWNzJHZhbF9sb3NzKSkKYGBgCgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CnBsb3QoaGlzdG9yeSkgKyAKICBzY2FsZV95X2xvZzEwKCkgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIGxlbmd0aChoaXN0b3J5JG1ldHJpY3MkdmFsX2xvc3MpKSkKYGBgCgoKIyBHZW5lcmFsaXppbmcgdG8gc21hbGwgZGF0YXNldHMKCk5vdGUgdGhhdCBmaW5kaW5nIGFuIG9wdGltYWwgbW9kZWwgdGhhdCBnZW5lcmFsaXplcyB3ZWxsIGJhc2VkIG9uIG91ciB2YWxpZGF0aW9uCmFwcHJvYWNoIG1heSBiZSBkaWZmaWN1bHQuIFRoaXMgaXMgYmVjYXVzZSBvdXIgdmFsaWRhdGlvbiBkYXRhICh2aWEKYHZhbGlkYXRpb25fc3BsaXQgPSAwLjJgKSBvbmx5IGNvbnNpc3RzIG9mIDgwMCsgc2FtcGxlcy4gQ29uc2VxdWVudGx5LCBtb2RlbApwZXJmb3JtYW5jZSB3aWxsIGJlIGhpZ2hseSBkZXBlbmRlbnQgb24gdGhlc2UgODAwKyBzYW1wbGVzLgoKSW4gZ2VuZXJhbCwgdGhlIGZld2VyIG9ic2VydmF0aW9ucyBpbiBvdXIgdmFsaWRhdGlvbiBzZXQsIHRoZSBncmVhdGVyIHZhcmlhbmNlCmluIG91ciBsb3NzIHNjb3JlLiAgQXMgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gb3VyIHZhbGlkYXRpb24gZGF0YSAKaW5jcmVhc2VzLCB2YXJpYW5jZSBpbiBvdXIgbG9zcyBzY29yZSB3aWxsIGRlY3JlYXNlLiBIb3dldmVyLCB3ZSBkbyBub3QgYWx3YXlzIApoYXZlIHRoZSBvcHRpb24gdG8ganVzdCBnbyBvdXQgYW5kIGdldCBtb3JlIGRhdGEuIFNvLCBpZiB3ZSB3YW50IHRvIGdhaW4gYSBtb3JlIAphY2N1cmF0ZSB1bmRlcnN0YW5kaW5nIG9mIHRoZSBsb3NzIHNjb3JlIGFuZCBpdHMgdmFyaWFuY2Ugd2UgY291bGQgcGVyZm9ybSAKX2stZm9sZCBjcm9zcyB2YWxpZGF0aW9uXy4gCgpTZWUgdGhlIFt2YWxpZGF0aW9uIHByb2NlZHVyZXMgbm90ZWJvb2tdKGh0dHBzOi8vcnN0dWRpby1jb25mLTIwMjAuZ2l0aHViLmlvL2RsLWtlcmFzLXRmL25vdGVib29rcy92YWxpZGF0aW9uLXByb2NlZHVyZXMubmIuaHRtbCkgCmZvciBhbiBleGFtcGxlIG9mIHBlcmZvcm1pbmcgay1mb2xkIGNyb3NzIHZhbGlkYXRpb24uCgojIEtleSB0YWtlYXdheXMKCiogUHJlcGFyaW5nIHRhYnVsYXIgZGF0YQogICAtIFdlIG5lZWQgdG8gdmVjdG9yaXplIGNhdGVnb3JpY2FsIGZlYXR1cmVzCiAgIC0gV2UgbmVlZCB0byBzdGFuZGFyZGl6ZSBhbmQgbm9ybWFsaXplIG51bWVyaWMgZmVhdHVyZXMKKiBCYXRjaCBzaXplICYgZXBvY2hzCiAgIC0gU21hbGxlciBiYXRjaCBzaXplcyB0ZW5kIHRvIHBlcmZvcm0gYmVzdCAoc3BlZWQgYW5kIGxvc3MgbWluaW1pemF0aW9uKQogICAtIEhvd2V2ZXIsIHRoaXMgaXMgYSBoeXBlcnBhcmFtZXRlciB5b3UgY2FuIGFkanVzdCBhbmQgZXZhbHVhdGUKICAgLSBNYWtlIHN1cmUgeW91IGluY2x1ZGUgZW5vdWdoIGVwb2NocyB0byByZWFjaCBhIG1pbmltdW0gbG9zcwoqIExlYXJuaW5nIHJhdGUKICAgLSBUaGUgbW9zdCBpbXBvcnRhbnQgaHlwZXJwYXJhbWV0ZXIgdG8gdHVuZQogICAtIFR5cGljYWxseSB5b3Ugd2FudCB0byB0dW5lIHRoZSBsZWFybmluZyByYXRlIGFuZCBiYXRjaCBzaXplIHRvZ2V0aGVyCiogQ2FsbGJhY2tzCiAgIC0gVXNlIGVhcmx5IHN0b3BwaW5nIHRvIGF1dG9tYXRlIG1vZGVsIHRyYWluaW5nIGFuZCBzdG9wcGluZyBhZnRlciBsb3NzIGhhcwogICAgIGJlZW4gbWluaW1pemVkCiAgIC0gVXNlIGBjYWxsYmFja19yZWR1Y2VfbHJfb25fcGxhdGVhdSgpYCBmb3IgbW9yZSBjb250cm9sIG92ZXIgdGhlIGxlYXJuaW5nIAogICAgIHJhdGUuCiogS25vd2luZyB3aGVuIHRvIGFkanVzdCBtb2RlbCBjYXBhY2l0eQogICAtIFlvdXIgbWluaW11bSB2YWxpZGF0aW9uIGxvc3Mgc2NvcmUgc2hvdWxkIG5vdCBiZSBsZXNzIHRoYW4geW91ciB0cmFpbmluZwogICAgIGxvc3MKICAgLSBJZiBzbywgaW5jcmVhc2UgbW9kZWwgY2FwYWNpdHkgdW50aWwgeW91J3ZlIGJhbGFuY2VkIG92ZXJmaXR0aW5nIGFuZAogICAgIG1pbmltaXphdGlvbiBvZiB2YWxpZGF0aW9uIGxvc3Mgc2NvcmUgYW5kIHN0YWJpbGl0eQ==