Document classification

The video that accompanies this notebook is available at https://ucdavis.box.com/v/sts-205-notebook-6.

In this notebook, we will use classification algorithms to consider the party of U.S. presidents. Since the early nineteenth century, most presidents (all presidents after Andrew Johnson) have been either Democrats or Republicans, though as we know, the parties have stood for different things at different times. In this notebook, we will use classification algorithms to assign earlier presidents to one or the other of these parties, on the basis of word usage in their SOTU addresses.

Begin by loading the packages we will use, sourcing your functions, and making the sotu data frame.

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ─────────────────────────────────────── tidyverse 1.3.0 ──
✓ ggplot2 3.3.3     ✓ purrr   0.3.4
✓ tibble  3.0.6     ✓ dplyr   1.0.4
✓ tidyr   1.1.2     ✓ stringr 1.4.0
✓ readr   1.4.0     ✓ forcats 0.5.0
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
library(tidytext)
#install.packages("class")
library(class)
#install.packages("e1071")
library(e1071)
source("functions.r")
sotu <- make_sotu()

── Column specification ────────────────────────────────────────────────────────
cols(
  year = col_double(),
  pres = col_character(),
  use_last = col_logical()
)

We will need to add a column to sotu indicating whether each president was a Democrat, Republican, or Other. We will begin by making a vector of presidents in each party. It is important to spell each president’s name exactly the way it is spelled in the sotu data frame.

republicans <- c("Abraham Lincoln", "Ulysses S. Grant", "Rutherford B. Hayes", "James Garfield", 
                 "Chester A. Arthur", "Benjamin Harrison", "William McKinley", "Theodore Roosevelt", 
                 "William H. Taft", "Warren Harding", "Calvin Coolidge", "Herbert Hoover", 
                 "Dwight D. Eisenhower", "Richard Nixon", "Gerald R. Ford", "Ronald Reagan", 
                 "George H.W. Bush", "George W. Bush", "Donald J. Trump")
democrats <- c("Andrew Jackson", "Martin van Buren", "James Polk", "Franklin Pierce",
               "James Buchanan", "Grover Cleveland", "Woodrow Wilson", "Franklin D. Roosevelt",
               "Harry S. Truman", "John F. Kennedy", "Lyndon B. Johnson", "Jimmy Carter",
               "William J. Clinton", "Barack Obama")
federalists <- c("George Washington", "John Adams")
democratic_republicans <- c("Thomas Jefferson", "James Madison", "James Monroe", "John Quincy Adams")
whigs <- c("William Henry Harrison", "John Tyler", "Zachary Taylor", "Millard Fillmore")
union <- c("Andrew Johnson")

Now add the party column to sotu.

sotu <- sotu %>% mutate(party = ifelse(pres %in% republicans, "Republican", 
                                ifelse(pres %in% democrats, "Democratic", "Other")))
#Check which presidents are being classified as "Other"
unique(sotu$pres[sotu$party == "Other"])
 [1] "George Washington" "John Adams"        "Thomas Jefferson" 
 [4] "James Madison"     "James Monroe"      "John Quincy Adams"
 [7] "John Tyler"        "Zachary Taylor"    "Millard Fillmore" 
[10] "Andrew Johnson"   

Support Vector Machine

For classification, as for clustering, we need to decide on the criteria we will use. For this notebook, let’s use the 500 most frequently-used words. The first classification algorithm we will use is a Support Vector Machine (SVM). The SVM algorithm identifies a hyperplane in n-dimensional space (where n is the number of features you are using for classification; in this case 500). Unknown data points are assigned based on where they fall relative to that hyperplane. In this case, imagine a line separating the Republican addresses from the Democratic addresses.

Start by identifying the top 500 words and making a document-term matrix. We will do it manually rather than using the cast_dtm() function, which gives us a data frame that looks like a dtm but still has the year and party columns.

#Identify top 500 words
top_words <- sotu_tokenize_words() %>% count(gram) %>% top_n(500)
Selecting by n
#Make a document-term matrix of those words (rename year and party to avoid confusion)
dtm <- sotu_tokenize_words() %>% filter(gram %in% top_words$gram) %>%    
          group_by(year, party) %>% count(gram) %>% mutate(tf = n/sum(n)) %>%
          rename(year_ = year, party_ = party) %>% select(-n) %>% spread(gram, tf)
head(dtm)
#Substitute zero for missing values
dtm[is.na(dtm)] <- 0

Now we will divide the data frame into two groups: a training set (Republicans and Democrats) and a set to be classified (Others)

#Divide the dtm into a training set and a classification set
train <- dtm[dtm$party_ != "Other", ]
class <- dtm[dtm$party_ == "Other", ]
head(train)
head(class)

The svm() function from the e1071 package trains the SVM model. It takes two parameters: the features to be used for classification (columns 3 and onward of the train data frme) and the category for each known document (the party column of the train data frame).

#Train the model on the training set
#svm(location of training documents, classification of training documents)
model <- svm(train[ , 3:ncol(train)], factor(train$party_))

To classify the addresses given by presidents who were neither Democrats nor Republicans, we use the predict() function. It takes two arguments: the model (which we have saved as “model”) and the locations of the documents we want to classify.

predict(model, class[ , 3:ncol(class)])
         1          2          3          4          5          6          7 
Democratic Democratic Democratic Democratic Democratic Democratic Democratic 
         8          9         10         11         12         13         14 
Democratic Democratic Democratic Democratic Democratic Democratic Democratic 
        15         16         17         18         19         20         21 
Democratic Democratic Democratic Democratic Democratic Democratic Democratic 
        22         23         24         25         26         27         28 
Democratic Democratic Democratic Democratic Democratic Democratic Democratic 
        29         30         31         32         33         34         35 
Democratic Democratic Democratic Democratic Democratic Democratic Democratic 
        36         37         38         39         40         41         42 
Democratic Democratic Democratic Democratic Democratic Democratic Democratic 
        43         44         45         46         47         48         49 
Democratic Democratic Democratic Democratic Democratic Democratic Democratic 
        50         51         52 
Democratic Democratic Democratic 
Levels: Democratic Republican

The result is a vector of predictions, one for each row of the class data frame. To see which presidents these correspond to, we can use the vector to create a new column of the sotu data frame, where we have filtered to include only presidents who were not either Republicans or Democrats.

predicted <- sotu %>% select(-use_last, -text) %>% filter(party == "Other") %>% 
                mutate(prediction = predict(model, class[ , 3:ncol(class)]))

Now we can tally up the results by president using a two-way table.

table(predicted$pres, predicted$prediction)
                   
                    Democratic Republican
  Andrew Johnson             4          0
  George Washington          8          0
  James Madison              8          0
  James Monroe               8          0
  John Adams                 4          0
  John Quincy Adams          4          0
  John Tyler                 4          0
  Millard Fillmore           3          0
  Thomas Jefferson           8          0
  Zachary Taylor             1          0

We don’t know how accurate this classification is (in a sense, it is not at all accurate, since none of these presidents were either Democrats or Republicans), but it suggests that, when our current parties emerged, the Democratic Party offered more consistency with previous politics and the Republican Party offered something new (remember that the Republican party at that time was the pro-business and anti-slavery party).

We will get to the problem of accuracy later. For now, let’s see what happens when we use the top 100 bigrams rather than the top 500 words as our feature set for classification.

top_bigrams <- sotu_tokenize_bigrams() %>% count(gram) %>% top_n(100)
Selecting by n
dtm <- sotu_tokenize_bigrams() %>% filter(gram %in% top_bigrams$gram) %>%    
          group_by(year, party) %>% count(gram) %>% 
          mutate(tf = n/sum(n), gram = str_replace(gram, " ", "_")) %>%
          rename(year_ = year, party_ = party) %>% select(-n) %>% spread(gram, tf)
dtm[is.na(dtm)] <- 0
train <- dtm[dtm$party_ != "Other", ]
class <- dtm[dtm$party_ == "Other", ]
model <- svm(train[ , 3:ncol(train)], factor(train$party_))
predicted <- sotu %>% select(-use_last, -text) %>% filter(party == "Other") %>% 
                mutate(prediction = predict(model, class[ , 3:ncol(class)]))
svm_bigrams <- table(predicted$pres, predicted$prediction)
svm_bigrams
                   
                    Democratic Republican
  Andrew Johnson             3          1
  George Washington          8          0
  James Madison              8          0
  James Monroe               8          0
  John Adams                 4          0
  John Quincy Adams          4          0
  John Tyler                 4          0
  Millard Fillmore           3          0
  Thomas Jefferson           8          0
  Zachary Taylor             1          0

We get very similar results with this model.

K Nearest Neighbors

The next algorithm we will use is k nearest neighbors. As you may recall from the reading, KNN assigns an unknown object to a category based on the identity of its nearest neighbors in n-dimensional space, where n is the number of features being used for classification. For KNN, we will use the knn() function from the class package. We will do it with k = 3 and k = 11, but really you can use any odd number for k. Our training and classification data sets are the same as the ones we used for the bigram SVM above, so we are classifying according to the top 100 bigrams (we could also do it with individual words, trigrams, etc.). With KNN, training and prediction are done in the same step. The knn() function takes three arguments: the training data set, the classification data set, and k. It returns a vector of predictions for the classification data set.

#knn(location of training documents, location of classification documents, 
#     classification of training documents, k)
pred3 <- knn(train[, 3:ncol(train)], class[, 3:ncol(class)], factor(train$party_), k = 3)

We can convert these predictions into the same kind of table we made before.

knn3_bigrams <- sotu %>% filter(party == "Other") %>% mutate(prediction = pred3) %>% 
                group_by(pres) %>% count(prediction) %>% spread(prediction, n)
knn3_bigrams

We can write a function to do the whole process for any value of k.

predict_party <- function(k) {
  prediction <- knn(train[, 3:ncol(train)], class[, 3:ncol(class)], factor(train$party_), k = k)
  pred_words <- sotu %>% filter(party == "Other") %>% mutate(prediction = prediction) %>%
                  group_by(pres) %>% count(prediction) %>% spread(prediction, n)
  return(pred_words)
}
predict_party(11)
knn11_bigrams <- predict_party(11)

As you can see, we get similar, but not identical results with k = 11. We can look at the predictions generated by all of the bigram models side-by-side.

data.frame(svm_bigrams) %>% rename(pres = Var1) %>% spread(Var2, Freq) %>%
  rename(Dem_SVM = Democratic, Rep_SVM = Republican) %>%
  inner_join(knn3_bigrams) %>% rename(Dem_3NN = Democratic, Rep_3NN = Republican) %>%
  inner_join(knn11_bigrams) %>% rename(Dem_11NN = Democratic, Rep_11NN = Republican)
Joining, by = "pres"
Joining, by = "pres"

From this, we learn a few things. The SOTU addresses of most of the earliest presidents are consistently more similar (when considering the top 100 bigrams) to those of later Democrats than to those of later Republicans. Others (Monroe, Adams, Tyler, Taylor) are either less consistent or less easily classified.

However, we have no way of deciding which of the three models gives a better prediction. We know that all of them are wrong in the sense that none of these presidents was actually a Democrat or Republican.

Cross-validation

We can test the models by examining how well they do to classify the SOTU addresses of presidents who really were Democrats or Republicans. One way to do this is with leave one out cross-validation. The idea here is that, for as many known data points as we have (in this case, 181 SOTU addresses were given by presidents who were either Republicans or Democrats), we fit the model that many times, each time leaving out one data point. Each time we fit the model, we use it to predict the category of the one document we left out. Once we have completed this task, we have one prediction for each document in our data set, which we can then use to build a confusion matrix.

Here is how we would do it for the SVM model.

#Make an empty data frame to hold predictions
predictions <- data.frame()
#For each row in the "train" data frame
for(i in 1:nrow(train)) {
  #Build a model using all addresses except i
  model <- svm(train[-i, 3:ncol(train)], factor(train[-i, ]$party_))
  #Use the model to classify address i
  pred <- predict(model, train[i, 3:ncol(train)])
  #Attach the prediction for address i to its actual party
  predict <- train[i, 1:2] %>% ungroup %>% mutate(prediction = pred)
  #Add this prediction to the "predictions" data frame
  predictions <- rbind(predictions, predict)
}
#Create confusion matrix
table(predictions$party_, predictions$prediction)
            
             Democratic Republican
  Democratic         72         17
  Republican         25         67

Here is how we would do leave one out cross-validation for the KNN model.

loo_knn <- function(k) {
  predictions <- data.frame()
  for(i in 1:nrow(train)) {
    pred <- knn(train[-i, 3:ncol(train)], 
                train[i, 3:ncol(train)], 
                factor(train[-i, ]$party_), k = k)
    predict <- train[i, 1:2] %>% ungroup %>% mutate(prediction = pred)
    predictions <- rbind(predictions, predict)
  }
  print(table(predictions$party_, predictions$prediction))
}
loo_knn(1)
            
             Democratic Republican
  Democratic         68         21
  Republican         26         66
loo_knn(3)
            
             Democratic Republican
  Democratic         63         26
  Republican         23         69

Leave one out cross validation is a special case of a more general concept called k-fold cross-validation. In k-fold cross-validation, you split your training data into k groups and fit the model k times, each time leaving out a different group and using that group as the prediction set. This means that each document in the data set will get predicted once and will be used for training k-1 times. Leave one out cross-validation is k-fold cross validation where k is equal to the number of documents in the training set. Most commonly, k = 10, giving us 10-fold cross-validation. The coding is a bit more cumbersome, but the general idea is the same. For this notebook, we will only do 10-fold cross-validation for the SVM model.

The first thing we need to do is shuffle our documents randomly. We will do this by creating a vector of the numbers from 1 to 181 in random order.

test <- sample(1:181, 181, replace = FALSE)

Now we will make a for loop that will leave out approximately 10% of the documents each time it runs

for(i in 1:10) {
  start <- (i - 1) * 18 + 1
  end <- ifelse(i < 10, start + 17, 181)
  testing <- start:end
  print(testing)
  print(test[testing])
}
 [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18
 [1] 141  73 108  30 127 118   9 179   6  92 132 140 157  60  35  26  56 116
 [1] 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
 [1] 125  22 180 166  96  53  65 151  62 107 178  64  34   3 123  87 144  11
 [1] 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
 [1] 143  59  82  50  97  21  10  38 119  75 175 147 115 137  41  54  79 100
 [1] 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
 [1] 103  45 110 174 149 162  48  51 128  42 169  98  63 181 131  49 109  15
 [1] 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
 [1]  17  90 105  13  20  12 163  99 156 164  95  37 146 168  89 160  68 114
 [1]  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107 108
 [1]  32  24  85 172 124  25 154 133  31  83  44 139  43 122   8  36  93  29
 [1] 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
 [1]  71   2 176  46 161  94 102 112 104 148   5  86 117  72 134  58  19  18
 [1] 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
 [1] 159  74 120 111 142 145 152  77  91   4  55 129  67  47  80 101 130  40
 [1] 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
 [1]  69 170  16  81 177 106  76 155 113  88   7  84  23 153 167 138  52 135
 [1] 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
 [1]  61  78 165  28   1  39 158 173  57  14 121 126 136  66 150  33  27  70 171

Each time we test this for loop, we want to fit the SVM model using all observations except for those we are testing in that loop. The observations we are testing will always be train[test[testing], ].

for(i in 1:10) {
  start <- (i - 1) * 18 + 1
  end <- ifelse(i < 10, start + 17, 181)
  testing <- start:end
  model <- svm(train[-test[testing], 3:ncol(train)], factor(train[-test[testing], ]$party_))
  pred <- predict(model, train[test[testing], 3:ncol(train)])
  predict <- train[test[testing], 1:2] %>% ungroup %>% mutate(prediction = pred)
  print(predict)
}

Now we will just add the data frame to collect the results, and then we can make the confusion matrix.

predictions <- data.frame()
test <- sample(1:181, 181, replace = FALSE)
for(i in 1:10) {
  start <- (i - 1) * 18 + 1
  end <- ifelse(i < 10, start + 17, 181)
  testing <- start:end
  model <- svm(train[-test[testing], 3:ncol(train)], factor(train[-test[testing], ]$party_))
  pred <- predict(model, train[test[testing], 3:ncol(train)])
  predict <- train[test[testing], 1:2] %>% ungroup %>% mutate(prediction = pred)
  predictions <- rbind(predictions, predict)
}
table(predictions$party_, predictions$prediction)
            
             Democratic Republican
  Democratic         70         19
  Republican         24         68
LS0tCnRpdGxlOiAiTm90ZWJvb2sgNiAtIERvY3VtZW50IENsYXNzaWZpY2F0aW9uIgphdXRob3I6ICJFbWlseSBLbGFuY2hlciBNZXJjaGFudCIKZGF0ZTogIlNUUyAyMDUiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMgRG9jdW1lbnQgY2xhc3NpZmljYXRpb24KVGhlIHZpZGVvIHRoYXQgYWNjb21wYW5pZXMgdGhpcyBub3RlYm9vayBpcyBhdmFpbGFibGUgYXQgaHR0cHM6Ly91Y2RhdmlzLmJveC5jb20vdi9zdHMtMjA1LW5vdGVib29rLTYuCgpJbiB0aGlzIG5vdGVib29rLCB3ZSB3aWxsIHVzZSBjbGFzc2lmaWNhdGlvbiBhbGdvcml0aG1zIHRvIGNvbnNpZGVyIHRoZSBwYXJ0eSBvZiBVLlMuIHByZXNpZGVudHMuIFNpbmNlIHRoZSBlYXJseSBuaW5ldGVlbnRoIGNlbnR1cnksIG1vc3QgcHJlc2lkZW50cyAoYWxsIHByZXNpZGVudHMgYWZ0ZXIgQW5kcmV3IEpvaG5zb24pIGhhdmUgYmVlbiBlaXRoZXIgRGVtb2NyYXRzIG9yIFJlcHVibGljYW5zLCB0aG91Z2ggYXMgd2Uga25vdywgdGhlIHBhcnRpZXMgaGF2ZSBzdG9vZCBmb3IgZGlmZmVyZW50IHRoaW5ncyBhdCBkaWZmZXJlbnQgdGltZXMuIEluIHRoaXMgbm90ZWJvb2ssIHdlIHdpbGwgdXNlIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobXMgdG8gYXNzaWduIGVhcmxpZXIgcHJlc2lkZW50cyB0byBvbmUgb3IgdGhlIG90aGVyIG9mIHRoZXNlIHBhcnRpZXMsIG9uIHRoZSBiYXNpcyBvZiB3b3JkIHVzYWdlIGluIHRoZWlyIFNPVFUgYWRkcmVzc2VzLgoKQmVnaW4gYnkgbG9hZGluZyB0aGUgcGFja2FnZXMgd2Ugd2lsbCB1c2UsIHNvdXJjaW5nIHlvdXIgZnVuY3Rpb25zLCBhbmQgbWFraW5nIHRoZSBgc290dWAgZGF0YSBmcmFtZS4KYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHRpZHl0ZXh0KQojaW5zdGFsbC5wYWNrYWdlcygiY2xhc3MiKQpsaWJyYXJ5KGNsYXNzKQojaW5zdGFsbC5wYWNrYWdlcygiZTEwNzEiKQpsaWJyYXJ5KGUxMDcxKQpzb3VyY2UoImZ1bmN0aW9ucy5yIikKc290dSA8LSBtYWtlX3NvdHUoKQpgYGAKV2Ugd2lsbCBuZWVkIHRvIGFkZCBhIGNvbHVtbiB0byBgc290dWAgaW5kaWNhdGluZyB3aGV0aGVyIGVhY2ggcHJlc2lkZW50IHdhcyBhIERlbW9jcmF0LCBSZXB1YmxpY2FuLCBvciBPdGhlci4gV2Ugd2lsbCBiZWdpbiBieSBtYWtpbmcgYSB2ZWN0b3Igb2YgcHJlc2lkZW50cyBpbiBlYWNoIHBhcnR5LiBJdCBpcyBpbXBvcnRhbnQgdG8gc3BlbGwgZWFjaCBwcmVzaWRlbnQncyBuYW1lIGV4YWN0bHkgdGhlIHdheSBpdCBpcyBzcGVsbGVkIGluIHRoZSBgc290dWAgZGF0YSBmcmFtZS4KYGBge3J9CnJlcHVibGljYW5zIDwtIGMoIkFicmFoYW0gTGluY29sbiIsICJVbHlzc2VzIFMuIEdyYW50IiwgIlJ1dGhlcmZvcmQgQi4gSGF5ZXMiLCAiSmFtZXMgR2FyZmllbGQiLCAKICAgICAgICAgICAgICAgICAiQ2hlc3RlciBBLiBBcnRodXIiLCAiQmVuamFtaW4gSGFycmlzb24iLCAiV2lsbGlhbSBNY0tpbmxleSIsICJUaGVvZG9yZSBSb29zZXZlbHQiLCAKICAgICAgICAgICAgICAgICAiV2lsbGlhbSBILiBUYWZ0IiwgIldhcnJlbiBIYXJkaW5nIiwgIkNhbHZpbiBDb29saWRnZSIsICJIZXJiZXJ0IEhvb3ZlciIsIAogICAgICAgICAgICAgICAgICJEd2lnaHQgRC4gRWlzZW5ob3dlciIsICJSaWNoYXJkIE5peG9uIiwgIkdlcmFsZCBSLiBGb3JkIiwgIlJvbmFsZCBSZWFnYW4iLCAKICAgICAgICAgICAgICAgICAiR2VvcmdlIEguVy4gQnVzaCIsICJHZW9yZ2UgVy4gQnVzaCIsICJEb25hbGQgSi4gVHJ1bXAiKQpkZW1vY3JhdHMgPC0gYygiQW5kcmV3IEphY2tzb24iLCAiTWFydGluIHZhbiBCdXJlbiIsICJKYW1lcyBQb2xrIiwgIkZyYW5rbGluIFBpZXJjZSIsCiAgICAgICAgICAgICAgICJKYW1lcyBCdWNoYW5hbiIsICJHcm92ZXIgQ2xldmVsYW5kIiwgIldvb2Ryb3cgV2lsc29uIiwgIkZyYW5rbGluIEQuIFJvb3NldmVsdCIsCiAgICAgICAgICAgICAgICJIYXJyeSBTLiBUcnVtYW4iLCAiSm9obiBGLiBLZW5uZWR5IiwgIkx5bmRvbiBCLiBKb2huc29uIiwgIkppbW15IENhcnRlciIsCiAgICAgICAgICAgICAgICJXaWxsaWFtIEouIENsaW50b24iLCAiQmFyYWNrIE9iYW1hIikKZmVkZXJhbGlzdHMgPC0gYygiR2VvcmdlIFdhc2hpbmd0b24iLCAiSm9obiBBZGFtcyIpCmRlbW9jcmF0aWNfcmVwdWJsaWNhbnMgPC0gYygiVGhvbWFzIEplZmZlcnNvbiIsICJKYW1lcyBNYWRpc29uIiwgIkphbWVzIE1vbnJvZSIsICJKb2huIFF1aW5jeSBBZGFtcyIpCndoaWdzIDwtIGMoIldpbGxpYW0gSGVucnkgSGFycmlzb24iLCAiSm9obiBUeWxlciIsICJaYWNoYXJ5IFRheWxvciIsICJNaWxsYXJkIEZpbGxtb3JlIikKdW5pb24gPC0gYygiQW5kcmV3IEpvaG5zb24iKQpgYGAKTm93IGFkZCB0aGUgYHBhcnR5YCBjb2x1bW4gdG8gYHNvdHVgLgpgYGB7cn0Kc290dSA8LSBzb3R1ICU+JSBtdXRhdGUocGFydHkgPSBpZmVsc2UocHJlcyAlaW4lIHJlcHVibGljYW5zLCAiUmVwdWJsaWNhbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmZWxzZShwcmVzICVpbiUgZGVtb2NyYXRzLCAiRGVtb2NyYXRpYyIsICJPdGhlciIpKSkKI0NoZWNrIHdoaWNoIHByZXNpZGVudHMgYXJlIGJlaW5nIGNsYXNzaWZpZWQgYXMgIk90aGVyIgp1bmlxdWUoc290dSRwcmVzW3NvdHUkcGFydHkgPT0gIk90aGVyIl0pCmBgYAojIyBTdXBwb3J0IFZlY3RvciBNYWNoaW5lCgpGb3IgY2xhc3NpZmljYXRpb24sIGFzIGZvciBjbHVzdGVyaW5nLCB3ZSBuZWVkIHRvIGRlY2lkZSBvbiB0aGUgY3JpdGVyaWEgd2Ugd2lsbCB1c2UuIEZvciB0aGlzIG5vdGVib29rLCBsZXQncyB1c2UgdGhlIDUwMCBtb3N0IGZyZXF1ZW50bHktdXNlZCB3b3Jkcy4gVGhlIGZpcnN0IGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobSB3ZSB3aWxsIHVzZSBpcyBhICoqU3VwcG9ydCBWZWN0b3IgTWFjaGluZSoqIChTVk0pLiBUaGUgU1ZNIGFsZ29yaXRobSBpZGVudGlmaWVzIGEgaHlwZXJwbGFuZSBpbiBuLWRpbWVuc2lvbmFsIHNwYWNlICh3aGVyZSBuIGlzIHRoZSBudW1iZXIgb2YgZmVhdHVyZXMgeW91IGFyZSB1c2luZyBmb3IgY2xhc3NpZmljYXRpb247IGluIHRoaXMgY2FzZSA1MDApLiBVbmtub3duIGRhdGEgcG9pbnRzIGFyZSBhc3NpZ25lZCBiYXNlZCBvbiB3aGVyZSB0aGV5IGZhbGwgcmVsYXRpdmUgdG8gdGhhdCBoeXBlcnBsYW5lLiBJbiB0aGlzIGNhc2UsIGltYWdpbmUgYSBsaW5lIHNlcGFyYXRpbmcgdGhlIFJlcHVibGljYW4gYWRkcmVzc2VzIGZyb20gdGhlIERlbW9jcmF0aWMgYWRkcmVzc2VzLgoKU3RhcnQgYnkgaWRlbnRpZnlpbmcgdGhlIHRvcCA1MDAgd29yZHMgYW5kIG1ha2luZyBhIGRvY3VtZW50LXRlcm0gbWF0cml4LiBXZSB3aWxsIGRvIGl0IG1hbnVhbGx5IHJhdGhlciB0aGFuIHVzaW5nIHRoZSBgY2FzdF9kdG0oKWAgZnVuY3Rpb24sIHdoaWNoIGdpdmVzIHVzIGEgZGF0YSBmcmFtZSB0aGF0IGxvb2tzIGxpa2UgYSBkdG0gYnV0IHN0aWxsIGhhcyB0aGUgYHllYXJgIGFuZCBgcGFydHlgIGNvbHVtbnMuCmBgYHtyfQojSWRlbnRpZnkgdG9wIDUwMCB3b3Jkcwp0b3Bfd29yZHMgPC0gc290dV90b2tlbml6ZV93b3JkcygpICU+JSBjb3VudChncmFtKSAlPiUgdG9wX24oNTAwKQojTWFrZSBhIGRvY3VtZW50LXRlcm0gbWF0cml4IG9mIHRob3NlIHdvcmRzIChyZW5hbWUgeWVhciBhbmQgcGFydHkgdG8gYXZvaWQgY29uZnVzaW9uKQpkdG0gPC0gc290dV90b2tlbml6ZV93b3JkcygpICU+JSBmaWx0ZXIoZ3JhbSAlaW4lIHRvcF93b3JkcyRncmFtKSAlPiUgICAgCiAgICAgICAgICBncm91cF9ieSh5ZWFyLCBwYXJ0eSkgJT4lIGNvdW50KGdyYW0pICU+JSBtdXRhdGUodGYgPSBuL3N1bShuKSkgJT4lCiAgICAgICAgICByZW5hbWUoeWVhcl8gPSB5ZWFyLCBwYXJ0eV8gPSBwYXJ0eSkgJT4lIHNlbGVjdCgtbikgJT4lIHNwcmVhZChncmFtLCB0ZikKaGVhZChkdG0pCiNTdWJzdGl0dXRlIHplcm8gZm9yIG1pc3NpbmcgdmFsdWVzCmR0bVtpcy5uYShkdG0pXSA8LSAwCmBgYApOb3cgd2Ugd2lsbCBkaXZpZGUgdGhlIGRhdGEgZnJhbWUgaW50byB0d28gZ3JvdXBzOiBhIHRyYWluaW5nIHNldCAoUmVwdWJsaWNhbnMgYW5kIERlbW9jcmF0cykgYW5kIGEgc2V0IHRvIGJlIGNsYXNzaWZpZWQgKE90aGVycykKYGBge3J9CiNEaXZpZGUgdGhlIGR0bSBpbnRvIGEgdHJhaW5pbmcgc2V0IGFuZCBhIGNsYXNzaWZpY2F0aW9uIHNldAp0cmFpbiA8LSBkdG1bZHRtJHBhcnR5XyAhPSAiT3RoZXIiLCBdCmNsYXNzIDwtIGR0bVtkdG0kcGFydHlfID09ICJPdGhlciIsIF0KaGVhZCh0cmFpbikKaGVhZChjbGFzcykKYGBgClRoZSBgc3ZtKClgIGZ1bmN0aW9uIGZyb20gdGhlIGBlMTA3MWAgcGFja2FnZSB0cmFpbnMgdGhlIFNWTSBtb2RlbC4gSXQgdGFrZXMgdHdvIHBhcmFtZXRlcnM6IHRoZSBmZWF0dXJlcyB0byBiZSB1c2VkIGZvciBjbGFzc2lmaWNhdGlvbiAoY29sdW1ucyAzIGFuZCBvbndhcmQgb2YgdGhlIGB0cmFpbmAgZGF0YSBmcm1lKSBhbmQgdGhlIGNhdGVnb3J5IGZvciBlYWNoIGtub3duIGRvY3VtZW50ICh0aGUgYHBhcnR5YCBjb2x1bW4gb2YgdGhlIGB0cmFpbmAgZGF0YSBmcmFtZSkuCmBgYHtyfQojVHJhaW4gdGhlIG1vZGVsIG9uIHRoZSB0cmFpbmluZyBzZXQKI3N2bShsb2NhdGlvbiBvZiB0cmFpbmluZyBkb2N1bWVudHMsIGNsYXNzaWZpY2F0aW9uIG9mIHRyYWluaW5nIGRvY3VtZW50cykKbW9kZWwgPC0gc3ZtKHRyYWluWyAsIDM6bmNvbCh0cmFpbildLCBmYWN0b3IodHJhaW4kcGFydHlfKSkKYGBgClRvIGNsYXNzaWZ5IHRoZSBhZGRyZXNzZXMgZ2l2ZW4gYnkgcHJlc2lkZW50cyB3aG8gd2VyZSBuZWl0aGVyIERlbW9jcmF0cyBub3IgUmVwdWJsaWNhbnMsIHdlIHVzZSB0aGUgYHByZWRpY3QoKWAgZnVuY3Rpb24uIEl0IHRha2VzIHR3byBhcmd1bWVudHM6IHRoZSBtb2RlbCAod2hpY2ggd2UgaGF2ZSBzYXZlZCBhcyAibW9kZWwiKSBhbmQgdGhlIGxvY2F0aW9ucyBvZiB0aGUgZG9jdW1lbnRzIHdlIHdhbnQgdG8gY2xhc3NpZnkuCmBgYHtyfQpwcmVkaWN0KG1vZGVsLCBjbGFzc1sgLCAzOm5jb2woY2xhc3MpXSkKYGBgClRoZSByZXN1bHQgaXMgYSB2ZWN0b3Igb2YgcHJlZGljdGlvbnMsIG9uZSBmb3IgZWFjaCByb3cgb2YgdGhlIGBjbGFzc2AgZGF0YSBmcmFtZS4gVG8gc2VlIHdoaWNoIHByZXNpZGVudHMgdGhlc2UgY29ycmVzcG9uZCB0bywgd2UgY2FuIHVzZSB0aGUgdmVjdG9yIHRvIGNyZWF0ZSBhIG5ldyBjb2x1bW4gb2YgdGhlIGBzb3R1YCBkYXRhIGZyYW1lLCB3aGVyZSB3ZSBoYXZlIGZpbHRlcmVkIHRvIGluY2x1ZGUgb25seSBwcmVzaWRlbnRzIHdobyB3ZXJlIG5vdCBlaXRoZXIgUmVwdWJsaWNhbnMgb3IgRGVtb2NyYXRzLgpgYGB7cn0KcHJlZGljdGVkIDwtIHNvdHUgJT4lIHNlbGVjdCgtdXNlX2xhc3QsIC10ZXh0KSAlPiUgZmlsdGVyKHBhcnR5ID09ICJPdGhlciIpICU+JSAKICAgICAgICAgICAgICAgIG11dGF0ZShwcmVkaWN0aW9uID0gcHJlZGljdChtb2RlbCwgY2xhc3NbICwgMzpuY29sKGNsYXNzKV0pKQpgYGAKTm93IHdlIGNhbiB0YWxseSB1cCB0aGUgcmVzdWx0cyBieSBwcmVzaWRlbnQgdXNpbmcgYSB0d28td2F5IHRhYmxlLgpgYGB7cn0KdGFibGUocHJlZGljdGVkJHByZXMsIHByZWRpY3RlZCRwcmVkaWN0aW9uKQpgYGAKCldlIGRvbid0IGtub3cgaG93IGFjY3VyYXRlIHRoaXMgY2xhc3NpZmljYXRpb24gaXMgKGluIGEgc2Vuc2UsIGl0IGlzIG5vdCBhdCBhbGwgYWNjdXJhdGUsIHNpbmNlIG5vbmUgb2YgdGhlc2UgcHJlc2lkZW50cyB3ZXJlIGVpdGhlciBEZW1vY3JhdHMgb3IgUmVwdWJsaWNhbnMpLCBidXQgaXQgc3VnZ2VzdHMgdGhhdCwgd2hlbiBvdXIgY3VycmVudCBwYXJ0aWVzIGVtZXJnZWQsIHRoZSBEZW1vY3JhdGljIFBhcnR5IG9mZmVyZWQgbW9yZSBjb25zaXN0ZW5jeSB3aXRoIHByZXZpb3VzIHBvbGl0aWNzIGFuZCB0aGUgUmVwdWJsaWNhbiBQYXJ0eSBvZmZlcmVkIHNvbWV0aGluZyBuZXcgKHJlbWVtYmVyIHRoYXQgdGhlIFJlcHVibGljYW4gcGFydHkgYXQgdGhhdCB0aW1lIHdhcyB0aGUgcHJvLWJ1c2luZXNzIGFuZCBhbnRpLXNsYXZlcnkgcGFydHkpLiAKCldlIHdpbGwgZ2V0IHRvIHRoZSBwcm9ibGVtIG9mIGFjY3VyYWN5IGxhdGVyLiBGb3Igbm93LCBsZXQncyBzZWUgd2hhdCBoYXBwZW5zIHdoZW4gd2UgdXNlIHRoZSB0b3AgMTAwIGJpZ3JhbXMgcmF0aGVyIHRoYW4gdGhlIHRvcCA1MDAgd29yZHMgYXMgb3VyIGZlYXR1cmUgc2V0IGZvciBjbGFzc2lmaWNhdGlvbi4KYGBge3J9CnRvcF9iaWdyYW1zIDwtIHNvdHVfdG9rZW5pemVfYmlncmFtcygpICU+JSBjb3VudChncmFtKSAlPiUgdG9wX24oMTAwKQpkdG0gPC0gc290dV90b2tlbml6ZV9iaWdyYW1zKCkgJT4lIGZpbHRlcihncmFtICVpbiUgdG9wX2JpZ3JhbXMkZ3JhbSkgJT4lICAgIAogICAgICAgICAgZ3JvdXBfYnkoeWVhciwgcGFydHkpICU+JSBjb3VudChncmFtKSAlPiUgCiAgICAgICAgICBtdXRhdGUodGYgPSBuL3N1bShuKSwgZ3JhbSA9IHN0cl9yZXBsYWNlKGdyYW0sICIgIiwgIl8iKSkgJT4lCiAgICAgICAgICByZW5hbWUoeWVhcl8gPSB5ZWFyLCBwYXJ0eV8gPSBwYXJ0eSkgJT4lIHNlbGVjdCgtbikgJT4lIHNwcmVhZChncmFtLCB0ZikKZHRtW2lzLm5hKGR0bSldIDwtIDAKdHJhaW4gPC0gZHRtW2R0bSRwYXJ0eV8gIT0gIk90aGVyIiwgXQpjbGFzcyA8LSBkdG1bZHRtJHBhcnR5XyA9PSAiT3RoZXIiLCBdCm1vZGVsIDwtIHN2bSh0cmFpblsgLCAzOm5jb2wodHJhaW4pXSwgZmFjdG9yKHRyYWluJHBhcnR5XykpCnByZWRpY3RlZCA8LSBzb3R1ICU+JSBzZWxlY3QoLXVzZV9sYXN0LCAtdGV4dCkgJT4lIGZpbHRlcihwYXJ0eSA9PSAiT3RoZXIiKSAlPiUgCiAgICAgICAgICAgICAgICBtdXRhdGUocHJlZGljdGlvbiA9IHByZWRpY3QobW9kZWwsIGNsYXNzWyAsIDM6bmNvbChjbGFzcyldKSkKc3ZtX2JpZ3JhbXMgPC0gdGFibGUocHJlZGljdGVkJHByZXMsIHByZWRpY3RlZCRwcmVkaWN0aW9uKQpzdm1fYmlncmFtcwpgYGAKV2UgZ2V0IHZlcnkgc2ltaWxhciByZXN1bHRzIHdpdGggdGhpcyBtb2RlbC4KCiMjIEsgTmVhcmVzdCBOZWlnaGJvcnMKClRoZSBuZXh0IGFsZ29yaXRobSB3ZSB3aWxsIHVzZSBpcyBrIG5lYXJlc3QgbmVpZ2hib3JzLiBBcyB5b3UgbWF5IHJlY2FsbCBmcm9tIHRoZSByZWFkaW5nLCBLTk4gYXNzaWducyBhbiB1bmtub3duIG9iamVjdCB0byBhIGNhdGVnb3J5IGJhc2VkIG9uIHRoZSBpZGVudGl0eSBvZiBpdHMgbmVhcmVzdCBuZWlnaGJvcnMgaW4gbi1kaW1lbnNpb25hbCBzcGFjZSwgd2hlcmUgbiBpcyB0aGUgbnVtYmVyIG9mIGZlYXR1cmVzIGJlaW5nIHVzZWQgZm9yIGNsYXNzaWZpY2F0aW9uLiBGb3IgS05OLCB3ZSB3aWxsIHVzZSB0aGUgYGtubigpYCBmdW5jdGlvbiBmcm9tIHRoZSBgY2xhc3NgIHBhY2thZ2UuIFdlIHdpbGwgZG8gaXQgd2l0aCBrID0gMyBhbmQgayA9IDExLCBidXQgcmVhbGx5IHlvdSBjYW4gdXNlIGFueSBvZGQgbnVtYmVyIGZvciBrLiBPdXIgdHJhaW5pbmcgYW5kIGNsYXNzaWZpY2F0aW9uIGRhdGEgc2V0cyBhcmUgdGhlIHNhbWUgYXMgdGhlIG9uZXMgd2UgdXNlZCBmb3IgdGhlIGJpZ3JhbSBTVk0gYWJvdmUsIHNvIHdlIGFyZSBjbGFzc2lmeWluZyBhY2NvcmRpbmcgdG8gdGhlIHRvcCAxMDAgYmlncmFtcyAod2UgY291bGQgYWxzbyBkbyBpdCB3aXRoIGluZGl2aWR1YWwgd29yZHMsIHRyaWdyYW1zLCBldGMuKS4gV2l0aCBLTk4sIHRyYWluaW5nIGFuZCBwcmVkaWN0aW9uIGFyZSBkb25lIGluIHRoZSBzYW1lIHN0ZXAuIFRoZSBga25uKClgIGZ1bmN0aW9uIHRha2VzIHRocmVlIGFyZ3VtZW50czogdGhlIHRyYWluaW5nIGRhdGEgc2V0LCB0aGUgY2xhc3NpZmljYXRpb24gZGF0YSBzZXQsIGFuZCBrLiBJdCByZXR1cm5zIGEgdmVjdG9yIG9mIHByZWRpY3Rpb25zIGZvciB0aGUgY2xhc3NpZmljYXRpb24gZGF0YSBzZXQuCmBgYHtyfQoja25uKGxvY2F0aW9uIG9mIHRyYWluaW5nIGRvY3VtZW50cywgbG9jYXRpb24gb2YgY2xhc3NpZmljYXRpb24gZG9jdW1lbnRzLCAKIyAgICAgY2xhc3NpZmljYXRpb24gb2YgdHJhaW5pbmcgZG9jdW1lbnRzLCBrKQpwcmVkMyA8LSBrbm4odHJhaW5bLCAzOm5jb2wodHJhaW4pXSwgY2xhc3NbLCAzOm5jb2woY2xhc3MpXSwgZmFjdG9yKHRyYWluJHBhcnR5XyksIGsgPSAzKQpgYGAKV2UgY2FuIGNvbnZlcnQgdGhlc2UgcHJlZGljdGlvbnMgaW50byB0aGUgc2FtZSBraW5kIG9mIHRhYmxlIHdlIG1hZGUgYmVmb3JlLgpgYGB7cn0Ka25uM19iaWdyYW1zIDwtIHNvdHUgJT4lIGZpbHRlcihwYXJ0eSA9PSAiT3RoZXIiKSAlPiUgbXV0YXRlKHByZWRpY3Rpb24gPSBwcmVkMykgJT4lIAogICAgICAgICAgICAgICAgZ3JvdXBfYnkocHJlcykgJT4lIGNvdW50KHByZWRpY3Rpb24pICU+JSBzcHJlYWQocHJlZGljdGlvbiwgbikKa25uM19iaWdyYW1zCmBgYApXZSBjYW4gd3JpdGUgYSBmdW5jdGlvbiB0byBkbyB0aGUgd2hvbGUgcHJvY2VzcyBmb3IgYW55IHZhbHVlIG9mIGsuCmBgYHtyfQpwcmVkaWN0X3BhcnR5IDwtIGZ1bmN0aW9uKGspIHsKICBwcmVkaWN0aW9uIDwtIGtubih0cmFpblssIDM6bmNvbCh0cmFpbildLCBjbGFzc1ssIDM6bmNvbChjbGFzcyldLCBmYWN0b3IodHJhaW4kcGFydHlfKSwgayA9IGspCiAgcHJlZF93b3JkcyA8LSBzb3R1ICU+JSBmaWx0ZXIocGFydHkgPT0gIk90aGVyIikgJT4lIG11dGF0ZShwcmVkaWN0aW9uID0gcHJlZGljdGlvbikgJT4lCiAgICAgICAgICAgICAgICAgIGdyb3VwX2J5KHByZXMpICU+JSBjb3VudChwcmVkaWN0aW9uKSAlPiUgc3ByZWFkKHByZWRpY3Rpb24sIG4pCiAgcmV0dXJuKHByZWRfd29yZHMpCn0KcHJlZGljdF9wYXJ0eSgxMSkKa25uMTFfYmlncmFtcyA8LSBwcmVkaWN0X3BhcnR5KDExKQpgYGAKQXMgeW91IGNhbiBzZWUsIHdlIGdldCBzaW1pbGFyLCBidXQgbm90IGlkZW50aWNhbCByZXN1bHRzIHdpdGggayA9IDExLiBXZSBjYW4gbG9vayBhdCB0aGUgcHJlZGljdGlvbnMgZ2VuZXJhdGVkIGJ5IGFsbCBvZiB0aGUgYmlncmFtIG1vZGVscyBzaWRlLWJ5LXNpZGUuCmBgYHtyfQpkYXRhLmZyYW1lKHN2bV9iaWdyYW1zKSAlPiUgcmVuYW1lKHByZXMgPSBWYXIxKSAlPiUgc3ByZWFkKFZhcjIsIEZyZXEpICU+JQogIHJlbmFtZShEZW1fU1ZNID0gRGVtb2NyYXRpYywgUmVwX1NWTSA9IFJlcHVibGljYW4pICU+JQogIGlubmVyX2pvaW4oa25uM19iaWdyYW1zKSAlPiUgcmVuYW1lKERlbV8zTk4gPSBEZW1vY3JhdGljLCBSZXBfM05OID0gUmVwdWJsaWNhbikgJT4lCiAgaW5uZXJfam9pbihrbm4xMV9iaWdyYW1zKSAlPiUgcmVuYW1lKERlbV8xMU5OID0gRGVtb2NyYXRpYywgUmVwXzExTk4gPSBSZXB1YmxpY2FuKQpgYGAKRnJvbSB0aGlzLCB3ZSBsZWFybiBhIGZldyB0aGluZ3MuIFRoZSBTT1RVIGFkZHJlc3NlcyBvZiBtb3N0IG9mIHRoZSBlYXJsaWVzdCBwcmVzaWRlbnRzICBhcmUgY29uc2lzdGVudGx5IG1vcmUgc2ltaWxhciAod2hlbiBjb25zaWRlcmluZyB0aGUgdG9wIDEwMCBiaWdyYW1zKSB0byB0aG9zZSBvZiBsYXRlciBEZW1vY3JhdHMgdGhhbiB0byB0aG9zZSBvZiBsYXRlciBSZXB1YmxpY2Fucy4gT3RoZXJzIChNb25yb2UsIEFkYW1zLCBUeWxlciwgVGF5bG9yKSBhcmUgZWl0aGVyIGxlc3MgY29uc2lzdGVudCBvciBsZXNzIGVhc2lseSBjbGFzc2lmaWVkLgoKSG93ZXZlciwgd2UgaGF2ZSBubyB3YXkgb2YgZGVjaWRpbmcgd2hpY2ggb2YgdGhlIHRocmVlIG1vZGVscyBnaXZlcyBhIGJldHRlciBwcmVkaWN0aW9uLiBXZSBrbm93IHRoYXQgYWxsIG9mIHRoZW0gYXJlIHdyb25nIGluIHRoZSBzZW5zZSB0aGF0IG5vbmUgb2YgdGhlc2UgcHJlc2lkZW50cyB3YXMgYWN0dWFsbHkgYSBEZW1vY3JhdCBvciBSZXB1YmxpY2FuLiAKCiMjIENyb3NzLXZhbGlkYXRpb24KV2UgY2FuIHRlc3QgdGhlIG1vZGVscyBieSBleGFtaW5pbmcgaG93IHdlbGwgdGhleSBkbyB0byBjbGFzc2lmeSB0aGUgU09UVSBhZGRyZXNzZXMgb2YgcHJlc2lkZW50cyB3aG8gcmVhbGx5ICp3ZXJlKiBEZW1vY3JhdHMgb3IgUmVwdWJsaWNhbnMuIE9uZSB3YXkgdG8gZG8gdGhpcyBpcyB3aXRoICoqbGVhdmUgb25lIG91dCBjcm9zcy12YWxpZGF0aW9uKiouIFRoZSBpZGVhIGhlcmUgaXMgdGhhdCwgZm9yIGFzIG1hbnkga25vd24gZGF0YSBwb2ludHMgYXMgd2UgaGF2ZSAoaW4gdGhpcyBjYXNlLCAxODEgU09UVSBhZGRyZXNzZXMgd2VyZSBnaXZlbiBieSBwcmVzaWRlbnRzIHdobyB3ZXJlIGVpdGhlciBSZXB1YmxpY2FucyBvciBEZW1vY3JhdHMpLCB3ZSBmaXQgdGhlIG1vZGVsIHRoYXQgbWFueSB0aW1lcywgZWFjaCB0aW1lIGxlYXZpbmcgb3V0IG9uZSBkYXRhIHBvaW50LiBFYWNoIHRpbWUgd2UgZml0IHRoZSBtb2RlbCwgd2UgdXNlIGl0IHRvIHByZWRpY3QgdGhlIGNhdGVnb3J5IG9mIHRoZSBvbmUgZG9jdW1lbnQgd2UgbGVmdCBvdXQuIE9uY2Ugd2UgaGF2ZSBjb21wbGV0ZWQgdGhpcyB0YXNrLCB3ZSBoYXZlIG9uZSBwcmVkaWN0aW9uIGZvciBlYWNoIGRvY3VtZW50IGluIG91ciBkYXRhIHNldCwgd2hpY2ggd2UgY2FuIHRoZW4gdXNlIHRvIGJ1aWxkIGEgY29uZnVzaW9uIG1hdHJpeC4KCkhlcmUgaXMgaG93IHdlIHdvdWxkIGRvIGl0IGZvciB0aGUgU1ZNIG1vZGVsLgpgYGB7cn0KI01ha2UgYW4gZW1wdHkgZGF0YSBmcmFtZSB0byBob2xkIHByZWRpY3Rpb25zCnByZWRpY3Rpb25zIDwtIGRhdGEuZnJhbWUoKQojRm9yIGVhY2ggcm93IGluIHRoZSAidHJhaW4iIGRhdGEgZnJhbWUKZm9yKGkgaW4gMTpucm93KHRyYWluKSkgewogICNCdWlsZCBhIG1vZGVsIHVzaW5nIGFsbCBhZGRyZXNzZXMgZXhjZXB0IGkKICBtb2RlbCA8LSBzdm0odHJhaW5bLWksIDM6bmNvbCh0cmFpbildLCBmYWN0b3IodHJhaW5bLWksIF0kcGFydHlfKSkKICAjVXNlIHRoZSBtb2RlbCB0byBjbGFzc2lmeSBhZGRyZXNzIGkKICBwcmVkIDwtIHByZWRpY3QobW9kZWwsIHRyYWluW2ksIDM6bmNvbCh0cmFpbildKQogICNBdHRhY2ggdGhlIHByZWRpY3Rpb24gZm9yIGFkZHJlc3MgaSB0byBpdHMgYWN0dWFsIHBhcnR5CiAgcHJlZGljdCA8LSB0cmFpbltpLCAxOjJdICU+JSB1bmdyb3VwICU+JSBtdXRhdGUocHJlZGljdGlvbiA9IHByZWQpCiAgI0FkZCB0aGlzIHByZWRpY3Rpb24gdG8gdGhlICJwcmVkaWN0aW9ucyIgZGF0YSBmcmFtZQogIHByZWRpY3Rpb25zIDwtIHJiaW5kKHByZWRpY3Rpb25zLCBwcmVkaWN0KQp9CiNDcmVhdGUgY29uZnVzaW9uIG1hdHJpeAp0YWJsZShwcmVkaWN0aW9ucyRwYXJ0eV8sIHByZWRpY3Rpb25zJHByZWRpY3Rpb24pCmBgYApIZXJlIGlzIGhvdyB3ZSB3b3VsZCBkbyBsZWF2ZSBvbmUgb3V0IGNyb3NzLXZhbGlkYXRpb24gZm9yIHRoZSBLTk4gbW9kZWwuCmBgYHtyfQpsb29fa25uIDwtIGZ1bmN0aW9uKGspIHsKICBwcmVkaWN0aW9ucyA8LSBkYXRhLmZyYW1lKCkKICBmb3IoaSBpbiAxOm5yb3codHJhaW4pKSB7CiAgICBwcmVkIDwtIGtubih0cmFpblstaSwgMzpuY29sKHRyYWluKV0sIAogICAgICAgICAgICAgICAgdHJhaW5baSwgMzpuY29sKHRyYWluKV0sIAogICAgICAgICAgICAgICAgZmFjdG9yKHRyYWluWy1pLCBdJHBhcnR5XyksIGsgPSBrKQogICAgcHJlZGljdCA8LSB0cmFpbltpLCAxOjJdICU+JSB1bmdyb3VwICU+JSBtdXRhdGUocHJlZGljdGlvbiA9IHByZWQpCiAgICBwcmVkaWN0aW9ucyA8LSByYmluZChwcmVkaWN0aW9ucywgcHJlZGljdCkKICB9CiAgcHJpbnQodGFibGUocHJlZGljdGlvbnMkcGFydHlfLCBwcmVkaWN0aW9ucyRwcmVkaWN0aW9uKSkKfQpsb29fa25uKDEpCmxvb19rbm4oMykKYGBgCkxlYXZlIG9uZSBvdXQgY3Jvc3MgdmFsaWRhdGlvbiBpcyBhIHNwZWNpYWwgY2FzZSBvZiBhIG1vcmUgZ2VuZXJhbCBjb25jZXB0IGNhbGxlZCAqKmstZm9sZCBjcm9zcy12YWxpZGF0aW9uKiouIEluIGstZm9sZCBjcm9zcy12YWxpZGF0aW9uLCB5b3Ugc3BsaXQgeW91ciAqdHJhaW5pbmcqIGRhdGEgaW50byBrIGdyb3VwcyBhbmQgZml0IHRoZSBtb2RlbCBrIHRpbWVzLCBlYWNoIHRpbWUgbGVhdmluZyBvdXQgYSBkaWZmZXJlbnQgZ3JvdXAgYW5kIHVzaW5nIHRoYXQgZ3JvdXAgYXMgdGhlIHByZWRpY3Rpb24gc2V0LiBUaGlzIG1lYW5zIHRoYXQgZWFjaCBkb2N1bWVudCBpbiB0aGUgZGF0YSBzZXQgd2lsbCBnZXQgcHJlZGljdGVkIG9uY2UgYW5kIHdpbGwgYmUgdXNlZCBmb3IgdHJhaW5pbmcgay0xIHRpbWVzLiBMZWF2ZSBvbmUgb3V0IGNyb3NzLXZhbGlkYXRpb24gaXMgay1mb2xkIGNyb3NzIHZhbGlkYXRpb24gd2hlcmUgayBpcyBlcXVhbCB0byB0aGUgbnVtYmVyIG9mIGRvY3VtZW50cyBpbiB0aGUgdHJhaW5pbmcgc2V0LiBNb3N0IGNvbW1vbmx5LCBrID0gMTAsIGdpdmluZyB1cyAxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24uIFRoZSBjb2RpbmcgaXMgYSBiaXQgbW9yZSBjdW1iZXJzb21lLCBidXQgdGhlIGdlbmVyYWwgaWRlYSBpcyB0aGUgc2FtZS4gRm9yIHRoaXMgbm90ZWJvb2ssIHdlIHdpbGwgb25seSBkbyAxMC1mb2xkIGNyb3NzLXZhbGlkYXRpb24gZm9yIHRoZSBTVk0gbW9kZWwuCgpUaGUgZmlyc3QgdGhpbmcgd2UgbmVlZCB0byBkbyBpcyBzaHVmZmxlIG91ciBkb2N1bWVudHMgcmFuZG9tbHkuIFdlIHdpbGwgZG8gdGhpcyBieSBjcmVhdGluZyBhIHZlY3RvciBvZiB0aGUgbnVtYmVycyBmcm9tIDEgdG8gMTgxIGluIHJhbmRvbSBvcmRlci4KYGBge3J9CnRlc3QgPC0gc2FtcGxlKDE6MTgxLCAxODEsIHJlcGxhY2UgPSBGQUxTRSkKYGBgCk5vdyB3ZSB3aWxsIG1ha2UgYSBmb3IgbG9vcCB0aGF0IHdpbGwgbGVhdmUgb3V0IGFwcHJveGltYXRlbHkgMTAlIG9mIHRoZSBkb2N1bWVudHMgZWFjaCB0aW1lIGl0IHJ1bnMKYGBge3J9CmZvcihpIGluIDE6MTApIHsKICBzdGFydCA8LSAoaSAtIDEpICogMTggKyAxCiAgZW5kIDwtIGlmZWxzZShpIDwgMTAsIHN0YXJ0ICsgMTcsIDE4MSkKICB0ZXN0aW5nIDwtIHN0YXJ0OmVuZAogIHByaW50KHRlc3RpbmcpCiAgcHJpbnQodGVzdFt0ZXN0aW5nXSkKfQpgYGAKRWFjaCB0aW1lIHdlIHRlc3QgdGhpcyBmb3IgbG9vcCwgd2Ugd2FudCB0byBmaXQgdGhlIFNWTSBtb2RlbCB1c2luZyBhbGwgb2JzZXJ2YXRpb25zIGV4Y2VwdCBmb3IgdGhvc2Ugd2UgYXJlIHRlc3RpbmcgaW4gdGhhdCBsb29wLiBUaGUgb2JzZXJ2YXRpb25zIHdlIGFyZSB0ZXN0aW5nIHdpbGwgYWx3YXlzIGJlIGB0cmFpblt0ZXN0W3Rlc3RpbmddLCBdYC4KYGBge3J9CmZvcihpIGluIDE6MTApIHsKICBzdGFydCA8LSAoaSAtIDEpICogMTggKyAxCiAgZW5kIDwtIGlmZWxzZShpIDwgMTAsIHN0YXJ0ICsgMTcsIDE4MSkKICB0ZXN0aW5nIDwtIHN0YXJ0OmVuZAogIG1vZGVsIDwtIHN2bSh0cmFpblstdGVzdFt0ZXN0aW5nXSwgMzpuY29sKHRyYWluKV0sIGZhY3Rvcih0cmFpblstdGVzdFt0ZXN0aW5nXSwgXSRwYXJ0eV8pKQogIHByZWQgPC0gcHJlZGljdChtb2RlbCwgdHJhaW5bdGVzdFt0ZXN0aW5nXSwgMzpuY29sKHRyYWluKV0pCiAgcHJlZGljdCA8LSB0cmFpblt0ZXN0W3Rlc3RpbmddLCAxOjJdICU+JSB1bmdyb3VwICU+JSBtdXRhdGUocHJlZGljdGlvbiA9IHByZWQpCiAgcHJpbnQocHJlZGljdCkKfQpgYGAKTm93IHdlIHdpbGwganVzdCBhZGQgdGhlIGRhdGEgZnJhbWUgdG8gY29sbGVjdCB0aGUgcmVzdWx0cywgYW5kIHRoZW4gd2UgY2FuIG1ha2UgdGhlIGNvbmZ1c2lvbiBtYXRyaXguCmBgYHtyfQpwcmVkaWN0aW9ucyA8LSBkYXRhLmZyYW1lKCkKdGVzdCA8LSBzYW1wbGUoMToxODEsIDE4MSwgcmVwbGFjZSA9IEZBTFNFKQpmb3IoaSBpbiAxOjEwKSB7CiAgc3RhcnQgPC0gKGkgLSAxKSAqIDE4ICsgMQogIGVuZCA8LSBpZmVsc2UoaSA8IDEwLCBzdGFydCArIDE3LCAxODEpCiAgdGVzdGluZyA8LSBzdGFydDplbmQKICBtb2RlbCA8LSBzdm0odHJhaW5bLXRlc3RbdGVzdGluZ10sIDM6bmNvbCh0cmFpbildLCBmYWN0b3IodHJhaW5bLXRlc3RbdGVzdGluZ10sIF0kcGFydHlfKSkKICBwcmVkIDwtIHByZWRpY3QobW9kZWwsIHRyYWluW3Rlc3RbdGVzdGluZ10sIDM6bmNvbCh0cmFpbildKQogIHByZWRpY3QgPC0gdHJhaW5bdGVzdFt0ZXN0aW5nXSwgMToyXSAlPiUgdW5ncm91cCAlPiUgbXV0YXRlKHByZWRpY3Rpb24gPSBwcmVkKQogIHByZWRpY3Rpb25zIDwtIHJiaW5kKHByZWRpY3Rpb25zLCBwcmVkaWN0KQp9CnRhYmxlKHByZWRpY3Rpb25zJHBhcnR5XywgcHJlZGljdGlvbnMkcHJlZGljdGlvbikKYGBgCg==