In this R notebook, we’re going to learn the basics of predictive modeling.

Before you start

Save this notebook under a different name in your shhs_workshop folder. That way, you have a backup just in case.

What is an R Notebook?

An R Notebook is a way to run R code and save the output (such as graphs and tables) in the same document. Make sure that you have opened the project file (shhs_workshop.rproj) in RStudio before you do anything. This will set you up so R knows where the data is.

There are two main formats for a R Notebook. The first are known as the markdown chunks, which are mostly text, and let you add commentary about your findings. The other format is the code chunk, which is started by the three backticks (```) and a {r}, such as the code block below. Usually, we want the code block to give us an output, such as a table or a graph. However, the code isn’t run until we hit the play button. This is really important to know.

R Notebooks are presented two ways: as an editable document (which is what you have here), and a preview document. You will be able to see the preview document by hitting the Preview button. Try hitting the “Preview” button now.

Important!

Be sure to go slowly through the notebook slowly and read the code. If you just press play without reading and understanding the code, you will miss out on the learning! Talk about the code blocks with your group partner.

Getting Help

If you’re confused about a function, such as head, you can run ?head (adding a question mark to the beginning of the function) to get help about that function. Try running the command below by hitting the play button on the top right of the code chunk. The help window for the function should open on the right.

?head

First Things First

The first thing to do is to load the data into your workspace. Click the play button on the below code chunk. We first load in some packages: broom, tidyr, dplyr, and visdat. Then we’re going to load our data using the read_rds function. We use the <- to assign our data into the shhs_data object. If you want to learn more about loading data from different formats, you can check out this DataCamp article: https://www.datacamp.com/community/tutorials/r-data-import-tutorial

library(broom)
library(tidyr)
library(dplyr)
library(visdat)
library(caret)
Error in library(caret) : there is no package called ‘caret’

Look at your environment tab and click on the shhs_data line. You will be able to see a spreadsheet like view of the data.

Show the first few rows of the data

Let’s look at the first few rows of the data with the head() command. The head command will show the first few rows.

head(shhs_data)

Including an image from our Data Explorer

You can add an image by using what’s called a markdown tag into this document. For example, if I wanted to add the image in our images folder called sample_image.png, you use this little bit of code:

Overview of Data

Overview of Data

Unfortunately, the image doesn’t pop-up when we’re editing the notebook, but it will when we hit the “Preview” button on the notebook bar above. Try it out!

Data Wrangling 101: Select our covariates

Ok, now we’re going to build a simple predictive model with our covariates. We’re going to use the select function in the dplyr package to pick our variables from the larger dataset. This is important to do beforehand because we’re going to select the complete cases in our data to model (see below).

This will seem weird at first, but the %>% is what’s called a pipe and lets us flow our data from one function to another.

Think about it: which variables are we selecting? What is our outcome we’re trying to predict?

shhs_data_model <- shhs_data %>% select(any_cvd, age_s1, gender, bmi_s1)

head(shhs_data_model)

We’re going to use visdat to summarize our data again. What do you notice in our dataset?

visdat::vis_dat(shhs_data_model)

What are you going to do about NAs?

Ugh. There are NAs in our data! We’re going to use the drop_na function to remove all rows that are not complete.

shhs_data_model_filtered <- shhs_data_model %>% tidyr::drop_na()

visdat::vis_dat(shhs_data_model_filtered)

Separating out our data

Ok, now we have to separate our data into two sets: the training set and the test set.

  1. Training set: a set of data with which we build (or train) our model with.
  2. Test set: a set of data with which we test the predictive power of our model.

How do we relate the test/train set to internal validity? Why is it important to hold out some data for testing?

shhs_train_index <- createDataPartition(shhs_data_model_filtered$any_cvd, p=0.85, list=FALSE)
shhs_train_data <- shhs_data_model_filtered[shhs_train_index,]
shhs_test_data <- shhs_data_model_filtered[-shhs_train_index,]
nrow(shhs_train_data)
nrow(shhs_test_data)

A Basic Model

Here we’re going to build a basic model with any_cvd as our outcome (what we want to predict), and shhs_train_data as our data.

Take a look at how we build the model below. The first thing we need to specify is our forumla. One of the most confusing things about R is the formula interface. The thing to remember is that formulas have a certain form. If Y is our dependent variable and X1, X2 are independent variables, then the formula to predict Y has the format Y ~ X1 + X2. Usually these variables come from a data.frame, which is supplied by the data argument to the function. Note that we don’t need quotes to refer to the variables in the data.frame.

basic_model <- glm(#put your formula below
                   formula = any_cvd ~ gender + age_s1 + bmi_s1, 
                   # binomial
                   family = "binomial", 
                   #we put our data into the data argument
                   data =    shhs_train_data
                   )

Show the coefficients of the model with the tidy function from broom. Each row of this is a predictor in our model, and we focus on two columns.

Predictive model

The important thing to understand with logistic regression is that it actually calculates a probability, which is the likelihood that you are likely to have cardiovascular disease in the next 10 years. A probablility of 0.9 means that you are more likely to have CVD, and a probability of 0.1 means that you are less likely to have CVD.

Let’s plug in a couple of patients into our model.

  1. patient1 is going to be older 65, Male, and have a high BMI (44). Is this patient more likely to have cardiovascular disease or not?
  2. patient2 is going to be younger 25, Female and have a low BMI (23). Is this patient more likely to have cardiovascular disease or not?
  3. patient3 is a middle aged (42), Female patient with a middling BMI (31)? Is she more likely to have cardiovascular disease or not?
  4. patient41 is a middle aged (40), female patient, with a higher BMI (41). What do you think?

Let’s plug in these four patients into our model. First we specify our data. We have to specify each variable separately and then glue them together as a data.frame.

pat_name <- c("patient1", "patient2", "patient3", "patient4")
gender <- factor(c("Male", "Female", "Female", "Female"))
age_s1 <- c(65, 25, 42, 60)
bmi_s1 <- c(44, 23, 31, 41)

patient_table <- data.frame(pat_name, gender, age_s1, bmi_s1)
patient_table

We can pass patient_table into our model with the augment function and it will evaluate our patients. When we look at this table, we can see that the .fitted column contains our predicted probabilities.

pat_table_aug <- augment(basic_model, newdata=patient_table, type.predict = "response")
pat_table_aug

Let’s plot the predicted probability for each patient.

pat_table_aug %>% ggplot(aes(x=pat_name, y=.fitted, fill=pat_name)) + 
  geom_bar(stat="identity") + ggtitle("Predicted probability for each patient")

One thing to note: even though we thought patient 1 had a high probability of CVD, they are only predicted to have a 40% probability of having CVD. This suggests that our model is not completely predicting with 100% certainty.

Also, patient 3, which we thought might be on the cusp of the predicted probability, actually has a low predicted probability! What about patient 4?

The predictor variables aren’t all equally important in the model. They’re actually weighted in terms of importance. We can see this if we use tidy on basic_model.

tidy(basic_model)

The coefficients of our model specify the weights, or importance of our variables in calculating the predicted probablity. Another thing to note is the p.value associated with each variable in our model. For an alpha cutoff of 0.05, all three variables are highly significant predictors in the model.

Evaluating using our test set

What if we plug in our test set into the model? How are the predicted probabilities distributed?

predictions <- augment(basic_model, newdata = shhs_test_data, type.predict = "response")
predictions
predictions %>% ggplot(aes(x=.fitted)) + geom_histogram()

We see that the majority of our test patients have a lower predicted probability. Naively, let’s choose that if our patient has a predicted probability > 0.5, that they are a cardiovascular risk and if they are less than or equal to 0.5, they are not a cardiovascular risk. Let’s recode a new variable, predict_cvd, with this variable

cutoff <- 0.5

predictions2 <- predictions %>% mutate(predict_cvd = case_when(.fitted > cutoff ~ "Yes", 
                                               .fitted <= cutoff ~ "No"))

predictions2

Now we have a set of predictions and we can compare them to the true value any_cvd in our dataset.

conf_matrix <- predictions2 %>% select(any_cvd, predict_cvd) %>% table()
conf_matrix

Try adjusting the cutoff above and see how the different cells of the table change.

Accuracy versus balanced accuracy

Try adjusting cutoff below and look at what happens to Accuracy versus Balanced accuracy.

cutoff <- 0.4

predictions2 <- predictions %>% mutate(predict_cvd = case_when(.fitted > cutoff ~ "Yes", 
                                               .fitted <= cutoff ~ "No"))

conf_matrix <- predictions2 %>% select(any_cvd, predict_cvd) %>% table()

caret::confusionMatrix(data = conf_matrix, positive="Yes")
tidy(basic_model)

A New Model:

We suspect that neck20 (neck circumfrence) might be a better predictor than bmi_s1. We know that bmi_s1 is correlated with neck20 (Check it using the app!) and that neck circumfrence may be a better predicter or airway obstruction (and therefore apnea). Given our knowledge of the biology, could neck20 be a better predictor?

Do some detective work about neck20. How is it measured?

#remember to subset the proper variables!
shhs_data_filtered2 <- shhs_data %>% select(#fill in with the variables! 
  ) %>% 
#use drop_na() for now  
  drop_na()

#separate into test/train sets
shhs_train_data2 <- shhs_data_filtered2[shhs_train_index,]
shhs_train_data2 <- shhs_data_filtered2[-shhs_train_index,]

basic_model2 <- glm(any_cvd ~  ,#fill your covariates here
                    family = "binomial", 
                    #we put our data into the data argument
                    data =    shhs_train_data2
)

#evaluate basic_model2 here

Comparing models

We’re going to use AIC (Aikake Information Criterion) to evaluate our new model compared to the old. AIC is a measure of model complexity combined with predictive power. We want our model to be as simple as possible without sacrificing predictive power. This criteria is known as parsimony. A lower AIC means that our model is both simpler and more predictive. So let’s compare the two models with AIC using the glance function.

glance(basic_model)
glance(basic_model2)

Which of the models has the lower AIC? Which should we choose?

Summary

We learned a lot today in this notebook! Specifically,

  1. How to select our variables using select()
  2. How to only use complete cases using drop_na()
  3. How to separate our data into test/train sets
  4. How to build our model using glm()
  5. Testing the predictive power of our model using patients
  6. Ways to evaluate the predictive power of our model

There’s a lot of info in this notebook. Take a look at it and bring your questions for the next session!

LS0tDQp0aXRsZTogIkJ1aWxkaW5nIFlvdXIgUHJlZGljdGl2ZSBNb2RlbCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KZWRpdG9yX29wdGlvbnM6IA0KICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lDQotLS0NCg0KSW4gdGhpcyBSIG5vdGVib29rLCB3ZSdyZSBnb2luZyB0byBsZWFybiB0aGUgYmFzaWNzIG9mIHByZWRpY3RpdmUgbW9kZWxpbmcuIA0KDQojIyBCZWZvcmUgeW91IHN0YXJ0DQoNClNhdmUgdGhpcyBub3RlYm9vayB1bmRlciBhIGRpZmZlcmVudCBuYW1lIGluIHlvdXIgYHNoaHNfd29ya3Nob3BgIGZvbGRlci4gVGhhdCB3YXksIHlvdSBoYXZlIGEgYmFja3VwIGp1c3QgaW4gY2FzZS4gDQoNCiMjIFdoYXQgaXMgYW4gUiBOb3RlYm9vaz8NCg0KQW4gUiBOb3RlYm9vayBpcyBhIHdheSB0byBydW4gUiBjb2RlIGFuZCBzYXZlIHRoZSBvdXRwdXQgKHN1Y2ggYXMgZ3JhcGhzIGFuZCB0YWJsZXMpIGluIHRoZSBzYW1lIGRvY3VtZW50LiBNYWtlIHN1cmUgdGhhdCB5b3UgaGF2ZSBvcGVuZWQgdGhlIHByb2plY3QgZmlsZSAoYHNoaHNfd29ya3Nob3AucnByb2pgKSBpbiBSU3R1ZGlvIGJlZm9yZSB5b3UgZG8gYW55dGhpbmcuIFRoaXMgd2lsbCBzZXQgeW91IHVwIHNvIFIga25vd3Mgd2hlcmUgdGhlIGRhdGEgaXMuDQoNClRoZXJlIGFyZSB0d28gbWFpbiBmb3JtYXRzIGZvciBhIFIgTm90ZWJvb2suIFRoZSBmaXJzdCBhcmUga25vd24gYXMgdGhlICptYXJrZG93biBjaHVua3MqLCB3aGljaCBhcmUgbW9zdGx5IHRleHQsIGFuZCBsZXQgeW91IGFkZCBjb21tZW50YXJ5IGFib3V0IHlvdXIgZmluZGluZ3MuIFRoZSBvdGhlciBmb3JtYXQgaXMgdGhlICpjb2RlIGNodW5rKiwgd2hpY2ggaXMgc3RhcnRlZCBieSB0aGUgdGhyZWUgYmFja3RpY2tzIChcYFxgXGApIGFuZCBhIGB7cn1gLCBzdWNoIGFzIHRoZSBjb2RlIGJsb2NrIGJlbG93LiBVc3VhbGx5LCB3ZSB3YW50IHRoZSBjb2RlIGJsb2NrIHRvIGdpdmUgdXMgYW4gb3V0cHV0LCBzdWNoIGFzIGEgdGFibGUgb3IgYSBncmFwaC4gSG93ZXZlciwgdGhlIGNvZGUgaXNuJ3QgcnVuIHVudGlsIHdlIGhpdCB0aGUgcGxheSBidXR0b24uIFRoaXMgaXMgcmVhbGx5IGltcG9ydGFudCB0byBrbm93LiANCg0KUiBOb3RlYm9va3MgYXJlIHByZXNlbnRlZCB0d28gd2F5czogYXMgYW4gZWRpdGFibGUgZG9jdW1lbnQgKHdoaWNoIGlzIHdoYXQgeW91IGhhdmUgaGVyZSksIGFuZCBhIHByZXZpZXcgZG9jdW1lbnQuIFlvdSB3aWxsIGJlIGFibGUgdG8gc2VlIHRoZSBwcmV2aWV3IGRvY3VtZW50IGJ5IGhpdHRpbmcgdGhlIGBQcmV2aWV3YCBidXR0b24uIFRyeSBoaXR0aW5nIHRoZSAiUHJldmlldyIgYnV0dG9uIG5vdy4NCg0KIyMgSW1wb3J0YW50IQ0KDQpCZSBzdXJlIHRvIGdvIHNsb3dseSB0aHJvdWdoIHRoZSBub3RlYm9vayBzbG93bHkgYW5kIHJlYWQgdGhlIGNvZGUuIElmIHlvdSBqdXN0IHByZXNzIHBsYXkgd2l0aG91dCByZWFkaW5nIGFuZCB1bmRlcnN0YW5kaW5nIHRoZSBjb2RlLCB5b3Ugd2lsbCBtaXNzIG91dCBvbiB0aGUgbGVhcm5pbmchIFRhbGsgYWJvdXQgdGhlIGNvZGUgYmxvY2tzIHdpdGggeW91ciBncm91cCBwYXJ0bmVyLg0KDQojIyBHZXR0aW5nIEhlbHANCg0KSWYgeW91J3JlIGNvbmZ1c2VkIGFib3V0IGEgZnVuY3Rpb24sIHN1Y2ggYXMgYGhlYWRgLCB5b3UgY2FuIHJ1biBgP2hlYWRgIChhZGRpbmcgYSBxdWVzdGlvbiBtYXJrIHRvIHRoZSBiZWdpbm5pbmcgb2YgdGhlIGZ1bmN0aW9uKSB0byBnZXQgaGVscCBhYm91dCB0aGF0IGZ1bmN0aW9uLiBUcnkgcnVubmluZyB0aGUgY29tbWFuZCBiZWxvdyBieSBoaXR0aW5nIHRoZSBwbGF5IGJ1dHRvbiBvbiB0aGUgdG9wIHJpZ2h0IG9mIHRoZSBjb2RlIGNodW5rLiBUaGUgaGVscCB3aW5kb3cgZm9yIHRoZSBmdW5jdGlvbiBzaG91bGQgb3BlbiBvbiB0aGUgcmlnaHQuDQoNCmBgYHtyfQ0KP2hlYWQNCmBgYA0KDQojIyBGaXJzdCBUaGluZ3MgRmlyc3QNCg0KVGhlIGZpcnN0IHRoaW5nIHRvIGRvIGlzIHRvIGxvYWQgdGhlIGRhdGEgaW50byB5b3VyIHdvcmtzcGFjZS4gQ2xpY2sgdGhlIGBwbGF5YCBidXR0b24gb24gdGhlIGJlbG93IGNvZGUgY2h1bmsuIFdlIGZpcnN0IGxvYWQgaW4gc29tZSBwYWNrYWdlczogYGJyb29tYCwgYHRpZHlyYCwgYGRwbHlyYCwgYW5kIGB2aXNkYXRgLiBUaGVuIHdlJ3JlIGdvaW5nIHRvIGxvYWQgb3VyIGRhdGEgdXNpbmcgdGhlIGByZWFkX3Jkc2AgZnVuY3Rpb24uIFdlIHVzZSB0aGUgYDwtYCB0byAqYXNzaWduKiBvdXIgZGF0YSBpbnRvIHRoZSBgc2hoc19kYXRhYCBvYmplY3QuIA0KSWYgeW91IHdhbnQgdG8gbGVhcm4gbW9yZSBhYm91dCBsb2FkaW5nIGRhdGEgZnJvbSBkaWZmZXJlbnQgZm9ybWF0cywgeW91IGNhbiBjaGVjayBvdXQgdGhpcyBEYXRhQ2FtcCBhcnRpY2xlOiBodHRwczovL3d3dy5kYXRhY2FtcC5jb20vY29tbXVuaXR5L3R1dG9yaWFscy9yLWRhdGEtaW1wb3J0LXR1dG9yaWFsDQoNCmBgYHtyIHNldHVwfQ0KbGlicmFyeShicm9vbSkNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh2aXNkYXQpDQpsaWJyYXJ5KGNhcmV0KQ0KDQpzaGhzX2RhdGEgPC0gcmVhZFJEUygiZGF0YS9jb21tb25fZGF0YV9zbWFsbC5yZHMiKQ0KYGBgDQoNCkxvb2sgYXQgeW91ciBgZW52aXJvbm1lbnRgIHRhYiBhbmQgY2xpY2sgb24gdGhlIGBzaGhzX2RhdGFgIGxpbmUuIFlvdSB3aWxsIGJlIGFibGUgdG8gc2VlIGEgc3ByZWFkc2hlZXQgbGlrZSB2aWV3IG9mIHRoZSBkYXRhLg0KDQojIyBTaG93IHRoZSBmaXJzdCBmZXcgcm93cyBvZiB0aGUgZGF0YQ0KDQpMZXQncyBsb29rIGF0IHRoZSBmaXJzdCBmZXcgcm93cyBvZiB0aGUgZGF0YSB3aXRoIHRoZSBgaGVhZCgpYCBjb21tYW5kLiBUaGUgaGVhZCBjb21tYW5kIHdpbGwgc2hvdyB0aGUgZmlyc3QgZmV3IHJvd3MuDQoNCmBgYHtyfQ0KaGVhZChzaGhzX2RhdGEpDQpgYGANCg0KIyMgSW5jbHVkaW5nIGFuIGltYWdlIGZyb20gb3VyIERhdGEgRXhwbG9yZXINCg0KWW91IGNhbiBhZGQgYW4gaW1hZ2UgYnkgdXNpbmcgd2hhdCdzIGNhbGxlZCBhIG1hcmtkb3duIHRhZyBpbnRvIHRoaXMgZG9jdW1lbnQuIEZvciBleGFtcGxlLCBpZiBJIHdhbnRlZCB0byBhZGQgdGhlIGltYWdlIGluIG91ciBgaW1hZ2VzYCBmb2xkZXIgY2FsbGVkIGBzYW1wbGVfaW1hZ2UucG5nYCwgeW91IHVzZSB0aGlzIGxpdHRsZSBiaXQgb2YgY29kZToNCg0KIVtPdmVydmlldyBvZiBEYXRhXShpbWFnZXMvc2FtcGxlX2ltYWdlLnBuZykNCg0KVW5mb3J0dW5hdGVseSwgdGhlIGltYWdlIGRvZXNuJ3QgcG9wLXVwIHdoZW4gd2UncmUgZWRpdGluZyB0aGUgbm90ZWJvb2ssIGJ1dCBpdCB3aWxsIHdoZW4gd2UgaGl0IHRoZSAiUHJldmlldyIgYnV0dG9uIG9uIHRoZSBub3RlYm9vayBiYXIgYWJvdmUuIFRyeSBpdCBvdXQhDQoNCiMjIERhdGEgV3JhbmdsaW5nIDEwMTogU2VsZWN0IG91ciBjb3ZhcmlhdGVzDQoNCk9rLCBub3cgd2UncmUgZ29pbmcgdG8gYnVpbGQgYSBzaW1wbGUgcHJlZGljdGl2ZSBtb2RlbCB3aXRoIG91ciBjb3ZhcmlhdGVzLiBXZSdyZSBnb2luZyB0byB1c2UgdGhlIGBzZWxlY3RgIGZ1bmN0aW9uIGluIHRoZSBgZHBseXJgIHBhY2thZ2UgdG8gcGljayBvdXIgdmFyaWFibGVzIGZyb20gdGhlIGxhcmdlciBkYXRhc2V0LiBUaGlzIGlzIGltcG9ydGFudCB0byBkbyBiZWZvcmVoYW5kIGJlY2F1c2Ugd2UncmUgZ29pbmcgdG8gc2VsZWN0IHRoZSBjb21wbGV0ZSBjYXNlcyBpbiBvdXIgZGF0YSB0byBtb2RlbCAoc2VlIGJlbG93KS4NCg0KVGhpcyB3aWxsIHNlZW0gd2VpcmQgYXQgZmlyc3QsIGJ1dCB0aGUgYCU+JWAgaXMgd2hhdCdzIGNhbGxlZCBhIGBwaXBlYCBhbmQgbGV0cyB1cyBmbG93IG91ciBkYXRhIGZyb20gb25lIGZ1bmN0aW9uIHRvIGFub3RoZXIuIA0KDQpUaGluayBhYm91dCBpdDogd2hpY2ggdmFyaWFibGVzIGFyZSB3ZSBzZWxlY3Rpbmc/IFdoYXQgaXMgb3VyIG91dGNvbWUgd2UncmUgdHJ5aW5nIHRvIHByZWRpY3Q/DQoNCmBgYHtyfQ0Kc2hoc19kYXRhX21vZGVsIDwtIHNoaHNfZGF0YSAlPiUgc2VsZWN0KGFueV9jdmQsIGFnZV9zMSwgZ2VuZGVyLCBibWlfczEpDQoNCmhlYWQoc2hoc19kYXRhX21vZGVsKQ0KYGBgDQoNCldlJ3JlIGdvaW5nIHRvIHVzZSBgdmlzZGF0YCB0byBzdW1tYXJpemUgb3VyIGRhdGEgYWdhaW4uIFdoYXQgZG8geW91IG5vdGljZSBpbiBvdXIgZGF0YXNldD8NCg0KYGBge3J9DQp2aXNkYXQ6OnZpc19kYXQoc2hoc19kYXRhX21vZGVsKQ0KYGBgDQoNCiMjIFdoYXQgYXJlIHlvdSBnb2luZyB0byBkbyBhYm91dCBgTkFgcz8NCg0KVWdoLiBUaGVyZSBhcmUgTkFzIGluIG91ciBkYXRhISBXZSdyZSBnb2luZyB0byB1c2UgdGhlIGBkcm9wX25hYCBmdW5jdGlvbiB0byByZW1vdmUgYWxsIHJvd3MgdGhhdCBhcmUgbm90IGNvbXBsZXRlLiANCg0KYGBge3J9DQpzaGhzX2RhdGFfbW9kZWxfZmlsdGVyZWQgPC0gc2hoc19kYXRhX21vZGVsICU+JSB0aWR5cjo6ZHJvcF9uYSgpDQoNCnZpc2RhdDo6dmlzX2RhdChzaGhzX2RhdGFfbW9kZWxfZmlsdGVyZWQpDQpgYGANCg0KIyMgU2VwYXJhdGluZyBvdXQgb3VyIGRhdGENCg0KT2ssIG5vdyB3ZSBoYXZlIHRvIHNlcGFyYXRlIG91ciBkYXRhIGludG8gdHdvIHNldHM6IHRoZSAqdHJhaW5pbmcqIHNldCBhbmQgdGhlICp0ZXN0KiBzZXQuDQoNCjEuIFRyYWluaW5nIHNldDogYSBzZXQgb2YgZGF0YSB3aXRoIHdoaWNoIHdlIGJ1aWxkIChvciB0cmFpbikgb3VyIG1vZGVsIHdpdGguIA0KMi4gVGVzdCBzZXQ6IGEgc2V0IG9mIGRhdGEgd2l0aCB3aGljaCB3ZSB0ZXN0IHRoZSBwcmVkaWN0aXZlIHBvd2VyIG9mIG91ciBtb2RlbC4NCg0KSG93IGRvIHdlIHJlbGF0ZSB0aGUgdGVzdC90cmFpbiBzZXQgdG8gaW50ZXJuYWwgdmFsaWRpdHk/IFdoeSBpcyBpdCBpbXBvcnRhbnQgdG8gaG9sZCBvdXQgc29tZSBkYXRhIGZvciB0ZXN0aW5nPw0KDQpgYGB7cn0NCnNoaHNfdHJhaW5faW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihzaGhzX2RhdGFfbW9kZWxfZmlsdGVyZWQkYW55X2N2ZCwgcD0wLjg1LCBsaXN0PUZBTFNFKQ0Kc2hoc190cmFpbl9kYXRhIDwtIHNoaHNfZGF0YV9tb2RlbF9maWx0ZXJlZFtzaGhzX3RyYWluX2luZGV4LF0NCnNoaHNfdGVzdF9kYXRhIDwtIHNoaHNfZGF0YV9tb2RlbF9maWx0ZXJlZFstc2hoc190cmFpbl9pbmRleCxdDQoNCmBgYA0KDQpgYGB7cn0NCm5yb3coc2hoc190cmFpbl9kYXRhKQ0KYGBgDQoNCmBgYHtyfQ0KbnJvdyhzaGhzX3Rlc3RfZGF0YSkNCmBgYA0KDQojIyBBIEJhc2ljIE1vZGVsDQoNCkhlcmUgd2UncmUgZ29pbmcgdG8gYnVpbGQgYSBiYXNpYyBtb2RlbCB3aXRoIGBhbnlfY3ZkYCBhcyBvdXIgb3V0Y29tZSAod2hhdCB3ZSB3YW50IHRvIHByZWRpY3QpLCBhbmQgYHNoaHNfdHJhaW5fZGF0YWAgYXMgb3VyIGRhdGEuIA0KDQpUYWtlIGEgbG9vayBhdCBob3cgd2UgYnVpbGQgdGhlIG1vZGVsIGJlbG93LiBUaGUgZmlyc3QgdGhpbmcgd2UgbmVlZCB0byBzcGVjaWZ5IGlzIG91ciAqZm9ydW1sYSouIE9uZSBvZiB0aGUgbW9zdCBjb25mdXNpbmcgdGhpbmdzIGFib3V0IFIgaXMgdGhlIGZvcm11bGEgaW50ZXJmYWNlLiBUaGUgdGhpbmcgdG8gcmVtZW1iZXIgaXMgdGhhdCBmb3JtdWxhcyBoYXZlIGEgY2VydGFpbiBmb3JtLiBJZiBgWWAgaXMgb3VyIGRlcGVuZGVudCB2YXJpYWJsZSBhbmQgYFgxYCwgYFgyYCBhcmUgaW5kZXBlbmRlbnQgdmFyaWFibGVzLCB0aGVuIHRoZSBmb3JtdWxhIHRvIHByZWRpY3QgYFlgIGhhcyB0aGUgZm9ybWF0IGBZIH4gWDEgKyBYMmAuIFVzdWFsbHkgdGhlc2UgdmFyaWFibGVzIGNvbWUgZnJvbSBhIGRhdGEuZnJhbWUsIHdoaWNoIGlzIHN1cHBsaWVkIGJ5IHRoZSBgZGF0YWAgYXJndW1lbnQgdG8gdGhlIGZ1bmN0aW9uLiBOb3RlIHRoYXQgd2UgZG9uJ3QgbmVlZCBxdW90ZXMgdG8gcmVmZXIgdG8gdGhlIHZhcmlhYmxlcyBpbiB0aGUgYGRhdGEuZnJhbWVgLg0KDQpgYGB7cn0NCmJhc2ljX21vZGVsIDwtIGdsbSgjcHV0IHlvdXIgZm9ybXVsYSBiZWxvdw0KICAgICAgICAgICAgICAgICAgIGZvcm11bGEgPSBhbnlfY3ZkIH4gZ2VuZGVyICsgYWdlX3MxICsgYm1pX3MxLCANCiAgICAgICAgICAgICAgICAgICAjIGJpbm9taWFsDQogICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gImJpbm9taWFsIiwgDQogICAgICAgICAgICAgICAgICAgI3dlIHB1dCBvdXIgZGF0YSBpbnRvIHRoZSBkYXRhIGFyZ3VtZW50DQogICAgICAgICAgICAgICAgICAgZGF0YSA9ICAgIHNoaHNfdHJhaW5fZGF0YQ0KICAgICAgICAgICAgICAgICAgICkNCmBgYA0KDQpTaG93IHRoZSBjb2VmZmljaWVudHMgb2YgdGhlIG1vZGVsIHdpdGggdGhlIGB0aWR5YCBmdW5jdGlvbiBmcm9tIGBicm9vbWAuIEVhY2ggcm93IG9mIHRoaXMgaXMgYSBwcmVkaWN0b3IgaW4gb3VyIG1vZGVsLCBhbmQgd2UgZm9jdXMgb24gdHdvIGNvbHVtbnMuIA0KDQojIyBQcmVkaWN0aXZlIG1vZGVsDQoNClRoZSBpbXBvcnRhbnQgdGhpbmcgdG8gdW5kZXJzdGFuZCB3aXRoIGxvZ2lzdGljIHJlZ3Jlc3Npb24gaXMgdGhhdCBpdCBhY3R1YWxseSBjYWxjdWxhdGVzIGEgcHJvYmFiaWxpdHksIHdoaWNoIGlzIHRoZSBsaWtlbGlob29kIHRoYXQgeW91IGFyZSBsaWtlbHkgdG8gaGF2ZSBjYXJkaW92YXNjdWxhciBkaXNlYXNlIGluIHRoZSBuZXh0IDEwIHllYXJzLiBBIHByb2JhYmxpbGl0eSBvZiAwLjkgbWVhbnMgdGhhdCB5b3UgYXJlIG1vcmUgbGlrZWx5IHRvIGhhdmUgQ1ZELCBhbmQgYSBwcm9iYWJpbGl0eSBvZiAwLjEgbWVhbnMgdGhhdCB5b3UgYXJlIGxlc3MgbGlrZWx5IHRvIGhhdmUgQ1ZELg0KDQpMZXQncyBwbHVnIGluIGEgY291cGxlIG9mIHBhdGllbnRzIGludG8gb3VyIG1vZGVsLiANCg0KMS4gYHBhdGllbnQxYCBpcyBnb2luZyB0byBiZSBvbGRlciBgNjVgLCBgTWFsZWAsIGFuZCBoYXZlIGEgaGlnaCBCTUkgKGA0NGApLiBJcyB0aGlzIHBhdGllbnQgbW9yZSBsaWtlbHkgdG8gaGF2ZSBjYXJkaW92YXNjdWxhciBkaXNlYXNlIG9yIG5vdD8NCjIuIGBwYXRpZW50MmAgaXMgZ29pbmcgdG8gYmUgeW91bmdlciBgMjVgLCBgRmVtYWxlYCBhbmQgaGF2ZSBhIGxvdyBCTUkgKGAyM2ApLiBJcyB0aGlzIHBhdGllbnQgbW9yZSBsaWtlbHkgdG8gaGF2ZSBjYXJkaW92YXNjdWxhciBkaXNlYXNlIG9yIG5vdD8NCjMuIGBwYXRpZW50M2AgaXMgYSBtaWRkbGUgYWdlZCAoYDQyYCksIGBGZW1hbGVgIHBhdGllbnQgd2l0aCBhIG1pZGRsaW5nIEJNSSAoYDMxYCk/IElzIHNoZSBtb3JlIGxpa2VseSB0byBoYXZlIGNhcmRpb3Zhc2N1bGFyIGRpc2Vhc2Ugb3Igbm90Pw0KNC4gYHBhdGllbnQ0MWAgaXMgYSBtaWRkbGUgYWdlZCAoYDQwYCksIGZlbWFsZSBwYXRpZW50LCB3aXRoIGEgaGlnaGVyIEJNSSAoYDQxYCkuIFdoYXQgZG8geW91IHRoaW5rPw0KDQpMZXQncyBwbHVnIGluIHRoZXNlIGZvdXIgcGF0aWVudHMgaW50byBvdXIgbW9kZWwuIEZpcnN0IHdlIHNwZWNpZnkgb3VyIGRhdGEuIFdlIGhhdmUgdG8gc3BlY2lmeSBlYWNoIHZhcmlhYmxlIHNlcGFyYXRlbHkgYW5kIHRoZW4gZ2x1ZSB0aGVtIHRvZ2V0aGVyIGFzIGEgYGRhdGEuZnJhbWVgLg0KDQpgYGB7cn0NCnBhdF9uYW1lIDwtIGMoInBhdGllbnQxIiwgInBhdGllbnQyIiwgInBhdGllbnQzIiwgInBhdGllbnQ0IikNCmdlbmRlciA8LSBmYWN0b3IoYygiTWFsZSIsICJGZW1hbGUiLCAiRmVtYWxlIiwgIkZlbWFsZSIpKQ0KYWdlX3MxIDwtIGMoNjUsIDI1LCA0MiwgNjApDQpibWlfczEgPC0gYyg0NCwgMjMsIDMxLCA0MSkNCg0KcGF0aWVudF90YWJsZSA8LSBkYXRhLmZyYW1lKHBhdF9uYW1lLCBnZW5kZXIsIGFnZV9zMSwgYm1pX3MxKQ0KcGF0aWVudF90YWJsZQ0KYGBgDQoNCldlIGNhbiBwYXNzIGBwYXRpZW50X3RhYmxlYCBpbnRvIG91ciBtb2RlbCB3aXRoIHRoZSBgYXVnbWVudGAgZnVuY3Rpb24gYW5kIGl0IHdpbGwgZXZhbHVhdGUgb3VyIHBhdGllbnRzLiBXaGVuIHdlIGxvb2sgYXQgdGhpcyB0YWJsZSwgd2UgY2FuIHNlZSB0aGF0IHRoZSBgLmZpdHRlZGAgY29sdW1uIGNvbnRhaW5zIG91ciBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcy4NCg0KYGBge3J9DQpwYXRfdGFibGVfYXVnIDwtIGF1Z21lbnQoYmFzaWNfbW9kZWwsIG5ld2RhdGE9cGF0aWVudF90YWJsZSwgdHlwZS5wcmVkaWN0ID0gInJlc3BvbnNlIikNCnBhdF90YWJsZV9hdWcNCmBgYA0KDQpMZXQncyBwbG90IHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgZm9yIGVhY2ggcGF0aWVudC4NCg0KYGBge3J9DQpwYXRfdGFibGVfYXVnICU+JSBnZ3Bsb3QoYWVzKHg9cGF0X25hbWUsIHk9LmZpdHRlZCwgZmlsbD1wYXRfbmFtZSkpICsgDQogIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IikgKyBnZ3RpdGxlKCJQcmVkaWN0ZWQgcHJvYmFiaWxpdHkgZm9yIGVhY2ggcGF0aWVudCIpDQpgYGANCg0KT25lIHRoaW5nIHRvIG5vdGU6IGV2ZW4gdGhvdWdoIHdlIHRob3VnaHQgcGF0aWVudCAxIGhhZCBhIGhpZ2ggcHJvYmFiaWxpdHkgb2YgQ1ZELCB0aGV5IGFyZSBvbmx5IHByZWRpY3RlZCB0byBoYXZlIGEgNDAlIHByb2JhYmlsaXR5IG9mIGhhdmluZyBDVkQuIFRoaXMgc3VnZ2VzdHMgdGhhdCBvdXIgbW9kZWwgaXMgbm90IGNvbXBsZXRlbHkgcHJlZGljdGluZyB3aXRoIDEwMCUgY2VydGFpbnR5Lg0KDQpBbHNvLCBwYXRpZW50IDMsIHdoaWNoIHdlIHRob3VnaHQgbWlnaHQgYmUgb24gdGhlIGN1c3Agb2YgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0eSwgYWN0dWFsbHkgaGFzIGEgbG93IHByZWRpY3RlZCBwcm9iYWJpbGl0eSEgV2hhdCBhYm91dCBwYXRpZW50IDQ/DQoNClRoZSBwcmVkaWN0b3IgdmFyaWFibGVzIGFyZW4ndCBhbGwgZXF1YWxseSBpbXBvcnRhbnQgaW4gdGhlIG1vZGVsLiBUaGV5J3JlIGFjdHVhbGx5IHdlaWdodGVkIGluIHRlcm1zIG9mIGltcG9ydGFuY2UuIFdlIGNhbiBzZWUgdGhpcyBpZiB3ZSB1c2UgYHRpZHlgIG9uIGBiYXNpY19tb2RlbGAuDQoNCmBgYHtyfQ0KdGlkeShiYXNpY19tb2RlbCkNCmBgYA0KDQpUaGUgY29lZmZpY2llbnRzIG9mIG91ciBtb2RlbCBzcGVjaWZ5IHRoZSB3ZWlnaHRzLCBvciBpbXBvcnRhbmNlIG9mIG91ciB2YXJpYWJsZXMgaW4gY2FsY3VsYXRpbmcgdGhlIHByZWRpY3RlZCBwcm9iYWJsaXR5LiBBbm90aGVyIHRoaW5nIHRvIG5vdGUgaXMgdGhlIGBwLnZhbHVlYCBhc3NvY2lhdGVkIHdpdGggZWFjaCB2YXJpYWJsZSBpbiBvdXIgbW9kZWwuIEZvciBhbiBhbHBoYSBjdXRvZmYgb2YgMC4wNSwgYWxsIHRocmVlIHZhcmlhYmxlcyBhcmUgaGlnaGx5IHNpZ25pZmljYW50IHByZWRpY3RvcnMgaW4gdGhlIG1vZGVsLg0KDQojIyBFdmFsdWF0aW5nIHVzaW5nIG91ciB0ZXN0IHNldA0KDQpXaGF0IGlmIHdlIHBsdWcgaW4gb3VyIHRlc3Qgc2V0IGludG8gdGhlIG1vZGVsPyBIb3cgYXJlIHRoZSBwcmVkaWN0ZWQgcHJvYmFiaWxpdGllcyBkaXN0cmlidXRlZD8NCg0KYGBge3J9DQpwcmVkaWN0aW9ucyA8LSBhdWdtZW50KGJhc2ljX21vZGVsLCBuZXdkYXRhID0gc2hoc190ZXN0X2RhdGEsIHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpDQpwcmVkaWN0aW9ucw0KcHJlZGljdGlvbnMgJT4lIGdncGxvdChhZXMoeD0uZml0dGVkKSkgKyBnZW9tX2hpc3RvZ3JhbSgpDQpgYGANCg0KV2Ugc2VlIHRoYXQgdGhlIG1ham9yaXR5IG9mIG91ciB0ZXN0IHBhdGllbnRzIGhhdmUgYSBsb3dlciBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkuIE5haXZlbHksIGxldCdzIGNob29zZSB0aGF0IGlmIG91ciBwYXRpZW50IGhhcyBhIHByZWRpY3RlZCBwcm9iYWJpbGl0eSA+IDAuNSwgdGhhdCB0aGV5IGFyZSBhIGNhcmRpb3Zhc2N1bGFyIHJpc2sgYW5kIGlmIHRoZXkgYXJlIGxlc3MgdGhhbiBvciBlcXVhbCB0byAwLjUsIHRoZXkgYXJlIG5vdCBhIGNhcmRpb3Zhc2N1bGFyIHJpc2suIExldCdzIHJlY29kZSBhIG5ldyB2YXJpYWJsZSwgYHByZWRpY3RfY3ZkYCwgd2l0aCB0aGlzIHZhcmlhYmxlDQoNCmBgYHtyfQ0KY3V0b2ZmIDwtIDAuNQ0KDQpwcmVkaWN0aW9uczIgPC0gcHJlZGljdGlvbnMgJT4lIG11dGF0ZShwcmVkaWN0X2N2ZCA9IGNhc2Vfd2hlbiguZml0dGVkID4gY3V0b2ZmIH4gIlllcyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZml0dGVkIDw9IGN1dG9mZiB+ICJObyIpKQ0KDQpwcmVkaWN0aW9uczINCmBgYA0KDQpOb3cgd2UgaGF2ZSBhIHNldCBvZiBwcmVkaWN0aW9ucyBhbmQgd2UgY2FuIGNvbXBhcmUgdGhlbSB0byB0aGUgdHJ1ZSB2YWx1ZSBgYW55X2N2ZGAgaW4gb3VyIGRhdGFzZXQuIA0KDQpgYGB7cn0NCmNvbmZfbWF0cml4IDwtIHByZWRpY3Rpb25zMiAlPiUgc2VsZWN0KGFueV9jdmQsIHByZWRpY3RfY3ZkKSAlPiUgdGFibGUoKQ0KY29uZl9tYXRyaXgNCmBgYA0KDQpUcnkgYWRqdXN0aW5nIHRoZSBgY3V0b2ZmYCBhYm92ZSBhbmQgc2VlIGhvdyB0aGUgZGlmZmVyZW50IGNlbGxzIG9mIHRoZSB0YWJsZSBjaGFuZ2UuDQoNCiMjIEFjY3VyYWN5IHZlcnN1cyBiYWxhbmNlZCBhY2N1cmFjeQ0KDQpUcnkgYWRqdXN0aW5nIGBjdXRvZmZgIGJlbG93IGFuZCBsb29rIGF0IHdoYXQgaGFwcGVucyB0byBBY2N1cmFjeSB2ZXJzdXMgQmFsYW5jZWQgYWNjdXJhY3kuDQoNCmBgYHtyfQ0KY3V0b2ZmIDwtIDAuNA0KDQpwcmVkaWN0aW9uczIgPC0gcHJlZGljdGlvbnMgJT4lIG11dGF0ZShwcmVkaWN0X2N2ZCA9IGNhc2Vfd2hlbiguZml0dGVkID4gY3V0b2ZmIH4gIlllcyIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuZml0dGVkIDw9IGN1dG9mZiB+ICJObyIpKQ0KDQpjb25mX21hdHJpeCA8LSBwcmVkaWN0aW9uczIgJT4lIHNlbGVjdChhbnlfY3ZkLCBwcmVkaWN0X2N2ZCkgJT4lIHRhYmxlKCkNCg0KY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeChkYXRhID0gY29uZl9tYXRyaXgsIHBvc2l0aXZlPSJZZXMiKQ0KYGBgDQpgYGB7cn0NCnRpZHkoYmFzaWNfbW9kZWwpDQpgYGANCg0KDQojIyBBIE5ldyBNb2RlbDoNCg0KV2Ugc3VzcGVjdCB0aGF0IGBuZWNrMjBgIChuZWNrIGNpcmN1bWZyZW5jZSkgbWlnaHQgYmUgYSBiZXR0ZXIgcHJlZGljdG9yIHRoYW4gYGJtaV9zMWAuIFdlIGtub3cgdGhhdCBgYm1pX3MxYCBpcyBjb3JyZWxhdGVkIHdpdGggYG5lY2syMGAgKENoZWNrIGl0IHVzaW5nIHRoZSBhcHAhKSBhbmQgdGhhdCBuZWNrIGNpcmN1bWZyZW5jZSBtYXkgYmUgYSBiZXR0ZXIgcHJlZGljdGVyIG9yIGFpcndheSBvYnN0cnVjdGlvbiAoYW5kIHRoZXJlZm9yZSBhcG5lYSkuIEdpdmVuIG91ciBrbm93bGVkZ2Ugb2YgdGhlIGJpb2xvZ3ksIGNvdWxkIGBuZWNrMjBgIGJlIGEgYmV0dGVyIHByZWRpY3Rvcj8NCg0KRG8gc29tZSBkZXRlY3RpdmUgd29yayBhYm91dCBgbmVjazIwYC4gSG93IGlzIGl0IG1lYXN1cmVkPyANCg0KYGBge3J9DQojcmVtZW1iZXIgdG8gc3Vic2V0IHRoZSBwcm9wZXIgdmFyaWFibGVzIQ0Kc2hoc19kYXRhX2ZpbHRlcmVkMiA8LSBzaGhzX2RhdGEgJT4lIHNlbGVjdCgjZmlsbCBpbiB3aXRoIHRoZSB2YXJpYWJsZXMhIA0KICApICU+JSANCiN1c2UgZHJvcF9uYSgpIGZvciBub3cgIA0KICBkcm9wX25hKCkNCg0KI3NlcGFyYXRlIGludG8gdGVzdC90cmFpbiBzZXRzDQpzaGhzX3RyYWluX2RhdGEyIDwtIHNoaHNfZGF0YV9maWx0ZXJlZDJbc2hoc190cmFpbl9pbmRleCxdDQpzaGhzX3RyYWluX2RhdGEyIDwtIHNoaHNfZGF0YV9maWx0ZXJlZDJbLXNoaHNfdHJhaW5faW5kZXgsXQ0KDQpiYXNpY19tb2RlbDIgPC0gZ2xtKGFueV9jdmQgfiAgLCNmaWxsIHlvdXIgY292YXJpYXRlcyBoZXJlDQogICAgICAgICAgICAgICAgICAgIGZhbWlseSA9ICJiaW5vbWlhbCIsIA0KICAgICAgICAgICAgICAgICAgICAjd2UgcHV0IG91ciBkYXRhIGludG8gdGhlIGRhdGEgYXJndW1lbnQNCiAgICAgICAgICAgICAgICAgICAgZGF0YSA9ICAgIHNoaHNfdHJhaW5fZGF0YTINCikNCg0KI2V2YWx1YXRlIGJhc2ljX21vZGVsMiBoZXJlDQpgYGANCg0KIyMgQ29tcGFyaW5nIG1vZGVscw0KDQpXZSdyZSBnb2luZyB0byB1c2UgQUlDIChBaWtha2UgSW5mb3JtYXRpb24gQ3JpdGVyaW9uKSB0byBldmFsdWF0ZSBvdXIgbmV3IG1vZGVsIGNvbXBhcmVkIHRvIHRoZSBvbGQuIEFJQyBpcyBhIG1lYXN1cmUgb2YgbW9kZWwgY29tcGxleGl0eSBjb21iaW5lZCB3aXRoIHByZWRpY3RpdmUgcG93ZXIuIFdlIHdhbnQgb3VyIG1vZGVsIHRvIGJlIGFzIHNpbXBsZSBhcyBwb3NzaWJsZSB3aXRob3V0IHNhY3JpZmljaW5nIHByZWRpY3RpdmUgcG93ZXIuIFRoaXMgY3JpdGVyaWEgaXMga25vd24gYXMgKnBhcnNpbW9ueSouICBBIGxvd2VyIEFJQyBtZWFucyB0aGF0IG91ciBtb2RlbCBpcyBib3RoIHNpbXBsZXIgYW5kIG1vcmUgcHJlZGljdGl2ZS4gU28gbGV0J3MgY29tcGFyZSB0aGUgdHdvIG1vZGVscyB3aXRoIEFJQyB1c2luZyB0aGUgYGdsYW5jZWAgZnVuY3Rpb24uDQoNCmBgYHtyfQ0KZ2xhbmNlKGJhc2ljX21vZGVsKQ0KYGBgDQoNCmBgYHtyfQ0KZ2xhbmNlKGJhc2ljX21vZGVsMikNCmBgYA0KDQpXaGljaCBvZiB0aGUgbW9kZWxzIGhhcyB0aGUgbG93ZXIgQUlDPyBXaGljaCBzaG91bGQgd2UgY2hvb3NlPw0KDQojIyBTdW1tYXJ5DQoNCldlIGxlYXJuZWQgYSBsb3QgdG9kYXkgaW4gdGhpcyBub3RlYm9vayEgU3BlY2lmaWNhbGx5LA0KDQoxLiBIb3cgdG8gc2VsZWN0IG91ciB2YXJpYWJsZXMgdXNpbmcgYHNlbGVjdCgpYA0KMi4gSG93IHRvIG9ubHkgdXNlIGNvbXBsZXRlIGNhc2VzIHVzaW5nIGBkcm9wX25hKClgDQozLiBIb3cgdG8gc2VwYXJhdGUgb3VyIGRhdGEgaW50byB0ZXN0L3RyYWluIHNldHMNCjQuIEhvdyB0byBidWlsZCBvdXIgbW9kZWwgdXNpbmcgYGdsbSgpYA0KNS4gVGVzdGluZyB0aGUgcHJlZGljdGl2ZSBwb3dlciBvZiBvdXIgbW9kZWwgdXNpbmcgcGF0aWVudHMNCjUuIFdheXMgdG8gZXZhbHVhdGUgdGhlIHByZWRpY3RpdmUgcG93ZXIgb2Ygb3VyIG1vZGVsDQoNClRoZXJlJ3MgYSBsb3Qgb2YgaW5mbyBpbiB0aGlzIG5vdGVib29rLiBUYWtlIGEgbG9vayBhdCBpdCBhbmQgYnJpbmcgeW91ciBxdWVzdGlvbnMgZm9yIHRoZSBuZXh0IHNlc3Npb24h