While we get started, let’s go ahead and install some packages that we’ll need. R packages extend the language’s base functions, and in RStudio you can manage them using the packages pane to the right. To make sure we’re all on the same page to start, we’ll just run the code below by clicking the little green arrow to the right.
install.packages("tm")
install.packages("SnowballC")
install.packages("topicmodels")
install.packages("RTextTools")
install.packages("dplyr")
install.packages("ggplot2")
library("tm")
library("SnowballC")
library("topicmodels")
library("RTextTools")
library("dplyr")
library("ggplot2")
This notebook covers basics of supervised and unsupervised machine learning.
Machine learning refers to a series of techniques through which a computer iteratively “learns” features of a dataset. For example, machine learning might be used to identify the author of a text or to classify texts by genre.
Unsupervised machine learning refers to techniques in which little or no input is given about what the algorithm should “look for” or “learn.” Topic modeling, demonstrated below, for example, assigns a topic distribution to texts, but the user does not provide examples of the topics that she is interested in finding.
Supervised techniques, on the other hand, allow a user to “supervise” the learning by providing examples of the phenomena to be identified. Classification algorithms, for example, generally require the user to provide examples of the different classes into which the data will be sorted.
Unsupervised Techniques: Topic Modeling
We’ll start with topic modeling, an unsupervised technique that assigns a topic distribution to each document in a collection. This technique assumes that each document is composed of a mixture of topics and that each topic is a mixture of words. So, if we topic modeled the entries in an encyclopedia, we might see topics like:
topic 1: animal, mammal, bird, verterbrate, invertebrate, skeleton topic 2: planets, stars, orbits, rockets, spacemen
And we might see that the model assigns an article about the use of animals in testing spaceflight a mixture of primarily topics 1 and 2.
In this example, we’ll be working with the texrt of articles published in Big Data and Society. First we’ll read in the data:
#setwd("~/Desktop/PhDigital_Computational_Data_Analysis")
articles = read.csv('big_data_articles.csv')
articles_text = as.character(articles[,3])
articles_text = gsub("‘","",articles_text)
articles_text = gsub("–","",articles_text)
articles_text = gsub("’","",articles_text)
articles_text = gsub("“","",articles_text)
The tm package works with an object type called a corpus, so the first thing we want to do is create that from our list of texts. To do this, we need to pass the Corpus() function a vector (or list) of our plot summaries.
articles_corpus = Corpus(VectorSource(articles_text))
One common task in lots of machine learning workflows is processing text. For example, we usually don’t care if a word is capitalized, and a lot of times we don’t care about words like “the” or “a”. One of the more interesting choices we make is how to process text in relation to a specific problem or domain.
The following block of code performs various text processing operations, beginning with simpler operations and ending with more extreme operations. Think about which would be beneficial to our analysis of plot summaries – you can comment out lines (preventing them from executing) by prefacing the line with a #.
#Remove whitespace
articles_corpus = tm_map(articles_corpus, stripWhitespace)
#Convert to lowercase
articles_corpus = tm_map(articles_corpus, tolower)
#Remove punctuation
articles_corpus = tm_map(articles_corpus, removePunctuation)
#Strip digits
articles_corpus = tm_map(articles_corpus, removeNumbers)
#Remove stopwords
articles_corpus = tm_map(articles_corpus, removeWords, stopwords('english'))
#Remove common words for this corpus
articles_corpus = tm_map(articles_corpus, removeWords, c("also","can","big","data","may","might","two","use","one","new","different"))
#Stem document
#articles_corpus = tm_map(articles_corpus,stemDocument)
** Need filler here.
Many textual analysis techniques work on document-term matrices (DTMs). A document-term matrix is a data format in which each row is a document, each column is a term and the values represent the number of occurrences (sometimes weighted in various ways) of a term in a document. This is the step where the documents get turned into “bags of words” – we lose the order of words in exchange for being able to compare our documents.
articles_dtm = DocumentTermMatrix(articles_corpus)
DTMs are often very “sparse” matrices – meaning that many of the values are zero. If you inspect the articles_dtm object at this point, you’ll see that the matrix probably has tens of thousands of columns – that’s a lot to work with, so we’re also going to go ahead and remove terms that don’t appear in many tweets at this point. This should get us down to a few hundred terms.
articles_dtm = removeSparseTerms(articles_dtm, .99)
articles_dtm_matrix = as.matrix(articles_dtm)
Go ahead and view the articles_dtm_matrix object by clicking on the little table icon in the Environment pane at the top right. It can be easy to just run code and forget what the data structures look like, but it’s important to keep in mind what we’re working with.
The last thing we need to do is get rid of rows that have zero in every column. We need to do this to both out DTM and our original dataset:
row_totals = apply(articles_dtm , 1, sum)
articles = articles[row_totals > 0,]
articles_text = articles_text[row_totals > 0]
articles_dtm = articles_dtm[row_totals> 0, ]
Now we can go ahead and create our topic model. At this point, we’re just going to tell it how many topics we want. Let’s start with 5:
articles_topic_model = LDA(articles_dtm, k = 5 )
We usually examine our topics informally, looking at the words most likely to appear in that topic and asking whether they seem comprehensible. We might, at this point, need to revise the number of topics we’re asking for.
terms(articles_topic_model, 10)
Each document is now described as a mixture of topics, but we’ll stop here by just looking at the top topic for each document.
articles$top_topic = topics(articles_topic_model)
articles_summary = articles[,c(2,4)]
At this point, there are lots of analysis and visualization options, depending on your methodology.
Supervised Techniques: Support Vector Machine (SVM) Classifiers
Where topic modeling doesn’t give the user any control over the model’s results (e.g., the topics identified might not be – and often aren’t – meaningful to humans), supervised techniques allow the user to provide examples of what they’re looking for. A basic example of this is a binary classifier, where observations are sorted into one of two classes. The supervised part of these techniques is that the user provides examples for the model to “learn” from. So, if we created a classifier to sort images into those that contain stop signs and those that don’t, we would provide the model with examples of pictures that contain and don’t contain stop signs. (When you “prove you’re human” to login to websites, this is roughly what you’re doing.)
Supervised machine learning typically requires a training data set – a subset of your data that you’ve classified (also referred to as labeled data).We use this labeled data in order to “train” a model, which we’ll then use to classify a larger collection of unlabeled data. Some workflows also include a test set of data that is used to measure the model’s performance, but for this example we’ll just use a training set.
In this example, we’ll be working with a series of airline tweets, and we’ll attempt to classify whether or not they’re complaints. I’ve created a training set for us to classify together. It’s available here: https://docs.google.com/spreadsheets/d/1En4YzB7yuW8GET_PLufKDhhmXb6ynON6sHhl7WyDLqc/edit?usp=sharing
The spreadsheet has a column called complaint – working in groups, we’ll fill in that column with -1 if the tweet isn’t a complaint and 1 if it is.
Once we’re done Download the spreadsheet as a CSV, rename it airline_tweets_training.csv and move it to the folder you’re working in.
Read in the training and full data:
tweets_all = read.csv('airline_tweets.csv')
tweets_training = read.csv('airlines_tweets_training.csv')
We start again by creating a DTM matrix. This gives us a feature vector for each tweet – a list of features that describes the tweet. Our features could be anything. But for this exercise – and conventionally when using SVM classifiers on text – we’re going to use a DTM to produce our feature vectors. So, the features are just word counts.
In the following steps, we’ll repeat some of the same text processing steps from the topic models example.
tweet_training_corpus = Corpus(VectorSource(tweets_training$text))
#Remove whitespace
tweet_training_corpus = tm_map(tweet_training_corpus, stripWhitespace)
#Convert to lowercase
tweet_training_corpus = tm_map(tweet_training_corpus, tolower)
#Remove punctuation
tweet_training_corpus = tm_map(tweet_training_corpus, removePunctuation)
#Strip digits
tweet_training_corpus = tm_map(tweet_training_corpus, removeNumbers)
#Remove stopwords
tweet_training_corpus = tm_map(tweet_training_corpus, removeWords, c(stopwords('english')))
# Create DTM
tweet_training_dtm = DocumentTermMatrix(tweet_training_corpus)
Now we train the SVM model:
training_container = create_container(tweet_training_dtm, tweets_training$complaint, trainSize=1:nrow(tweets_training), virgin=FALSE)
complaints_model = train_model(training_container, "SVM", kernel="linear", cost=1)
Now that we have our model, we can use it to predict whether the rest of the tweets are complaints or not. We have our full set in the tweets_all object, and we’re going to create another DTM from that. We’re going to do it a little differently this time, because we want to constrain the vocabulary of the new DTM to the vocabulary of our training set.
tweet_corpus = Corpus(VectorSource(tweets_all$text))
#Remove whitespace
tweet_corpus = tm_map(tweet_corpus, stripWhitespace)
#Convert to lowercase
tweet_corpus = tm_map(tweet_corpus, tolower)
#Remove punctuation
tweet_corpus = tm_map(tweet_corpus, removePunctuation)
#Strip digits
tweet_corpus = tm_map(tweet_corpus, removeNumbers)
#Remove stopwords
tweet_corpus = tm_map(tweet_corpus, removeWords, c(stopwords('english')))
# Create DTM
tweet_dtm = DocumentTermMatrix(tweet_corpus, control = list(dictionary=Terms(tweet_training_dtm)))
#Formatting and getting things ready....
prediction_container = create_container(tweet_dtm, labels=rep(0,nrow(tweets_all)), testSize=1:nrow(tweets_all), virgin=FALSE)
tweets_all = cbind(tweets_all, classify_model(prediction_container, complaints_model))
Again, there are lots of things we could do with these predictions.
For now, we’ll just quickly compare the different airlines based on how many complaints they get. For now, we’ll just average the label column in our tweets_all dataframe.
Note: I’m using dplyr and ggplot2 here. We’re not going to go over these packages, but if you were to learn two R packages, these would be very good candidates.
airlines_summary = tweets_all %>% group_by(airline) %>% summarise(
avg_label = mean(as.numeric(as.character(SVM_LABEL)))
)
ggplot(data=airlines_summary, aes(x=airline, y=avg_label, fill=airline)) +
geom_bar(stat="identity")

LS0tCnRpdGxlOiAiQ29tcHV0YXRpb25hbCBEYXRhIEFuYWx5c2lzIE1vZHVsZSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKV2hpbGUgd2UgZ2V0IHN0YXJ0ZWQsIGxldCdzIGdvIGFoZWFkIGFuZCBpbnN0YWxsIHNvbWUgcGFja2FnZXMgdGhhdCB3ZSdsbCBuZWVkLiBSIHBhY2thZ2VzIGV4dGVuZCB0aGUgbGFuZ3VhZ2UncyBiYXNlIGZ1bmN0aW9ucywgYW5kIGluIFJTdHVkaW8geW91IGNhbiBtYW5hZ2UgdGhlbSB1c2luZyB0aGUgcGFja2FnZXMgcGFuZSB0byB0aGUgcmlnaHQuIFRvIG1ha2Ugc3VyZSB3ZSdyZSBhbGwgb24gdGhlIHNhbWUgcGFnZSB0byBzdGFydCwgd2UnbGwganVzdCBydW4gdGhlIGNvZGUgYmVsb3cgYnkgY2xpY2tpbmcgdGhlIGxpdHRsZSBncmVlbiBhcnJvdyB0byB0aGUgcmlnaHQuIAoKYGBge3J9Cmluc3RhbGwucGFja2FnZXMoInRtIikKaW5zdGFsbC5wYWNrYWdlcygiU25vd2JhbGxDIikKaW5zdGFsbC5wYWNrYWdlcygidG9waWNtb2RlbHMiKQppbnN0YWxsLnBhY2thZ2VzKCJSVGV4dFRvb2xzIikKaW5zdGFsbC5wYWNrYWdlcygiZHBseXIiKQppbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikKCmxpYnJhcnkoInRtIikKbGlicmFyeSgiU25vd2JhbGxDIikKbGlicmFyeSgidG9waWNtb2RlbHMiKQpsaWJyYXJ5KCJSVGV4dFRvb2xzIikKbGlicmFyeSgiZHBseXIiKQpsaWJyYXJ5KCJnZ3Bsb3QyIikKYGBgCgpUaGlzIG5vdGVib29rIGNvdmVycyBiYXNpY3Mgb2Ygc3VwZXJ2aXNlZCBhbmQgdW5zdXBlcnZpc2VkIG1hY2hpbmUgbGVhcm5pbmcuCgpNYWNoaW5lIGxlYXJuaW5nIHJlZmVycyB0byBhIHNlcmllcyBvZiB0ZWNobmlxdWVzIHRocm91Z2ggd2hpY2ggYSBjb21wdXRlciBpdGVyYXRpdmVseSAibGVhcm5zIiBmZWF0dXJlcyBvZiBhIGRhdGFzZXQuIEZvciBleGFtcGxlLCBtYWNoaW5lIGxlYXJuaW5nIG1pZ2h0IGJlIHVzZWQgdG8gaWRlbnRpZnkgdGhlIGF1dGhvciBvZiBhIHRleHQgb3IgdG8gY2xhc3NpZnkgdGV4dHMgYnkgZ2VucmUuCgpVbnN1cGVydmlzZWQgbWFjaGluZSBsZWFybmluZyByZWZlcnMgdG8gdGVjaG5pcXVlcyBpbiB3aGljaCBsaXR0bGUgb3Igbm8gaW5wdXQgaXMgZ2l2ZW4gYWJvdXQgd2hhdCB0aGUgYWxnb3JpdGhtIHNob3VsZCAibG9vayBmb3IiIG9yICJsZWFybi4iIFRvcGljIG1vZGVsaW5nLCBkZW1vbnN0cmF0ZWQgYmVsb3csIGZvciBleGFtcGxlLCBhc3NpZ25zIGEgdG9waWMgZGlzdHJpYnV0aW9uIHRvIHRleHRzLCBidXQgdGhlIHVzZXIgZG9lcyBub3QgcHJvdmlkZSBleGFtcGxlcyBvZiB0aGUgdG9waWNzIHRoYXQgc2hlIGlzIGludGVyZXN0ZWQgaW4gZmluZGluZy4KClN1cGVydmlzZWQgdGVjaG5pcXVlcywgb24gdGhlIG90aGVyIGhhbmQsIGFsbG93IGEgdXNlciB0byAic3VwZXJ2aXNlIiB0aGUgbGVhcm5pbmcgYnkgcHJvdmlkaW5nIGV4YW1wbGVzIG9mIHRoZSBwaGVub21lbmEgdG8gYmUgaWRlbnRpZmllZC4gQ2xhc3NpZmljYXRpb24gYWxnb3JpdGhtcywgZm9yIGV4YW1wbGUsIGdlbmVyYWxseSByZXF1aXJlIHRoZSB1c2VyIHRvIHByb3ZpZGUgZXhhbXBsZXMgb2YgdGhlIGRpZmZlcmVudCBjbGFzc2VzIGludG8gd2hpY2ggdGhlIGRhdGEgd2lsbCBiZSBzb3J0ZWQuCgojIyBVbnN1cGVydmlzZWQgVGVjaG5pcXVlczogVG9waWMgTW9kZWxpbmcKCldlJ2xsIHN0YXJ0IHdpdGggdG9waWMgbW9kZWxpbmcsIGFuIHVuc3VwZXJ2aXNlZCB0ZWNobmlxdWUgdGhhdCBhc3NpZ25zIGEgdG9waWMgZGlzdHJpYnV0aW9uIHRvIGVhY2ggZG9jdW1lbnQgaW4gYSBjb2xsZWN0aW9uLiBUaGlzIHRlY2huaXF1ZSBhc3N1bWVzIHRoYXQgZWFjaCBkb2N1bWVudCBpcyBjb21wb3NlZCBvZiBhIG1peHR1cmUgb2YgdG9waWNzIGFuZCB0aGF0IGVhY2ggdG9waWMgaXMgYSBtaXh0dXJlIG9mIHdvcmRzLiBTbywgaWYgd2UgdG9waWMgbW9kZWxlZCB0aGUgZW50cmllcyBpbiBhbiBlbmN5Y2xvcGVkaWEsIHdlIG1pZ2h0IHNlZSB0b3BpY3MgbGlrZToKCnRvcGljIDE6IGFuaW1hbCwgbWFtbWFsLCBiaXJkLCB2ZXJ0ZXJicmF0ZSwgaW52ZXJ0ZWJyYXRlLCBza2VsZXRvbgp0b3BpYyAyOiBwbGFuZXRzLCBzdGFycywgb3JiaXRzLCByb2NrZXRzLCBzcGFjZW1lbgoKQW5kIHdlIG1pZ2h0IHNlZSB0aGF0IHRoZSBtb2RlbCBhc3NpZ25zIGFuIGFydGljbGUgYWJvdXQgdGhlIHVzZSBvZiBhbmltYWxzIGluIHRlc3Rpbmcgc3BhY2VmbGlnaHQgYSBtaXh0dXJlIG9mIHByaW1hcmlseSB0b3BpY3MgMSBhbmQgMi4KCkluIHRoaXMgZXhhbXBsZSwgd2UnbGwgYmUgd29ya2luZyB3aXRoIHRoZSB0ZXhydCBvZiBhcnRpY2xlcyBwdWJsaXNoZWQgaW4gQmlnIERhdGEgYW5kIFNvY2lldHkuIEZpcnN0IHdlJ2xsIHJlYWQgaW4gdGhlIGRhdGE6CgpgYGB7cn0KI3NldHdkKCJ+L0Rlc2t0b3AvUGhEaWdpdGFsX0NvbXB1dGF0aW9uYWxfRGF0YV9BbmFseXNpcyIpCgphcnRpY2xlcyA9IHJlYWQuY3N2KCdiaWdfZGF0YV9hcnRpY2xlcy5jc3YnKQoKYXJ0aWNsZXNfdGV4dCA9IGFzLmNoYXJhY3RlcihhcnRpY2xlc1ssM10pCmFydGljbGVzX3RleHQgPSBnc3ViKCLigJgiLCIiLGFydGljbGVzX3RleHQpCmFydGljbGVzX3RleHQgPSBnc3ViKCLigJMiLCIiLGFydGljbGVzX3RleHQpCmFydGljbGVzX3RleHQgPSBnc3ViKCLigJkiLCIiLGFydGljbGVzX3RleHQpCmFydGljbGVzX3RleHQgPSBnc3ViKCLigJwiLCIiLGFydGljbGVzX3RleHQpCmBgYAoKVGhlIHRtIHBhY2thZ2Ugd29ya3Mgd2l0aCBhbiBvYmplY3QgdHlwZSBjYWxsZWQgYSBjb3JwdXMsIHNvIHRoZSBmaXJzdCB0aGluZyB3ZSB3YW50IHRvIGRvIGlzIGNyZWF0ZSB0aGF0IGZyb20gb3VyIGxpc3Qgb2YgdGV4dHMuIFRvIGRvIHRoaXMsIHdlIG5lZWQgdG8gcGFzcyB0aGUgQ29ycHVzKCkgZnVuY3Rpb24gYSB2ZWN0b3IgKG9yIGxpc3QpIG9mIG91ciBwbG90IHN1bW1hcmllcy4KCmBgYHtyfQphcnRpY2xlc19jb3JwdXMgPSBDb3JwdXMoVmVjdG9yU291cmNlKGFydGljbGVzX3RleHQpKQpgYGAKCk9uZSBjb21tb24gdGFzayBpbiBsb3RzIG9mIG1hY2hpbmUgbGVhcm5pbmcgd29ya2Zsb3dzIGlzIHByb2Nlc3NpbmcgdGV4dC4gRm9yIGV4YW1wbGUsIHdlIHVzdWFsbHkgZG9uJ3QgY2FyZSBpZiBhIHdvcmQgaXMgY2FwaXRhbGl6ZWQsIGFuZCBhIGxvdCBvZiB0aW1lcyB3ZSBkb24ndCBjYXJlIGFib3V0IHdvcmRzIGxpa2UgInRoZSIgb3IgImEiLiBPbmUgb2YgdGhlIG1vcmUgaW50ZXJlc3RpbmcgY2hvaWNlcyB3ZSBtYWtlIGlzIGhvdyB0byBwcm9jZXNzIHRleHQgaW4gcmVsYXRpb24gdG8gYSBzcGVjaWZpYyBwcm9ibGVtIG9yIGRvbWFpbi4KClRoZSBmb2xsb3dpbmcgYmxvY2sgb2YgY29kZSBwZXJmb3JtcyB2YXJpb3VzIHRleHQgcHJvY2Vzc2luZyBvcGVyYXRpb25zLCBiZWdpbm5pbmcgd2l0aCBzaW1wbGVyIG9wZXJhdGlvbnMgYW5kIGVuZGluZyB3aXRoIG1vcmUgZXh0cmVtZSBvcGVyYXRpb25zLiBUaGluayBhYm91dCB3aGljaCB3b3VsZCBiZSBiZW5lZmljaWFsIHRvIG91ciBhbmFseXNpcyBvZiBwbG90IHN1bW1hcmllcyAtLSB5b3UgY2FuIGNvbW1lbnQgb3V0IGxpbmVzIChwcmV2ZW50aW5nIHRoZW0gZnJvbSBleGVjdXRpbmcpIGJ5IHByZWZhY2luZyB0aGUgbGluZSB3aXRoIGEgIy4KCmBgYHtyfQojUmVtb3ZlIHdoaXRlc3BhY2UKYXJ0aWNsZXNfY29ycHVzID0gdG1fbWFwKGFydGljbGVzX2NvcnB1cywgc3RyaXBXaGl0ZXNwYWNlKQojQ29udmVydCB0byBsb3dlcmNhc2UKYXJ0aWNsZXNfY29ycHVzID0gdG1fbWFwKGFydGljbGVzX2NvcnB1cywgdG9sb3dlcikKI1JlbW92ZSBwdW5jdHVhdGlvbgphcnRpY2xlc19jb3JwdXMgPSB0bV9tYXAoYXJ0aWNsZXNfY29ycHVzLCByZW1vdmVQdW5jdHVhdGlvbikKI1N0cmlwIGRpZ2l0cwphcnRpY2xlc19jb3JwdXMgPSB0bV9tYXAoYXJ0aWNsZXNfY29ycHVzLCByZW1vdmVOdW1iZXJzKQojUmVtb3ZlIHN0b3B3b3JkcwphcnRpY2xlc19jb3JwdXMgPSB0bV9tYXAoYXJ0aWNsZXNfY29ycHVzLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCdlbmdsaXNoJykpCgojUmVtb3ZlIGNvbW1vbiB3b3JkcyBmb3IgdGhpcyBjb3JwdXMKYXJ0aWNsZXNfY29ycHVzID0gdG1fbWFwKGFydGljbGVzX2NvcnB1cywgcmVtb3ZlV29yZHMsIGMoImFsc28iLCJjYW4iLCJiaWciLCJkYXRhIiwibWF5IiwibWlnaHQiLCJ0d28iLCJ1c2UiLCJvbmUiLCJuZXciLCJkaWZmZXJlbnQiKSkKCgojU3RlbSBkb2N1bWVudAojYXJ0aWNsZXNfY29ycHVzID0gdG1fbWFwKGFydGljbGVzX2NvcnB1cyxzdGVtRG9jdW1lbnQpCmBgYAoKKiogTmVlZCBmaWxsZXIgaGVyZS4KCk1hbnkgdGV4dHVhbCBhbmFseXNpcyB0ZWNobmlxdWVzIHdvcmsgb24gZG9jdW1lbnQtdGVybSBtYXRyaWNlcyAoRFRNcykuIEEgZG9jdW1lbnQtdGVybSBtYXRyaXggaXMgYSBkYXRhIGZvcm1hdCBpbiB3aGljaCBlYWNoIHJvdyBpcyBhIGRvY3VtZW50LCBlYWNoIGNvbHVtbiBpcyBhIHRlcm0gYW5kIHRoZSB2YWx1ZXMgcmVwcmVzZW50IHRoZSBudW1iZXIgb2Ygb2NjdXJyZW5jZXMgKHNvbWV0aW1lcyB3ZWlnaHRlZCBpbiB2YXJpb3VzIHdheXMpIG9mIGEgdGVybSBpbiBhIGRvY3VtZW50LiBUaGlzIGlzIHRoZSBzdGVwIHdoZXJlIHRoZSBkb2N1bWVudHMgZ2V0IHR1cm5lZCBpbnRvICJiYWdzIG9mIHdvcmRzIiAtLSB3ZSBsb3NlIHRoZSBvcmRlciBvZiB3b3JkcyBpbiBleGNoYW5nZSBmb3IgYmVpbmcgYWJsZSB0byBjb21wYXJlIG91ciBkb2N1bWVudHMuCgpgYGB7cn0KYXJ0aWNsZXNfZHRtID0gRG9jdW1lbnRUZXJtTWF0cml4KGFydGljbGVzX2NvcnB1cykKYGBgCgpEVE1zIGFyZSBvZnRlbiB2ZXJ5ICJzcGFyc2UiIG1hdHJpY2VzIC0tIG1lYW5pbmcgdGhhdCBtYW55IG9mIHRoZSB2YWx1ZXMgYXJlIHplcm8uIElmIHlvdSBpbnNwZWN0IHRoZSBhcnRpY2xlc19kdG0gb2JqZWN0IGF0IHRoaXMgcG9pbnQsIHlvdSdsbCBzZWUgdGhhdCB0aGUgbWF0cml4IHByb2JhYmx5IGhhcyB0ZW5zIG9mIHRob3VzYW5kcyBvZiBjb2x1bW5zIC0tIHRoYXQncyBhIGxvdCB0byB3b3JrIHdpdGgsIHNvIHdlJ3JlIGFsc28gZ29pbmcgdG8gZ28gYWhlYWQgYW5kIHJlbW92ZSB0ZXJtcyB0aGF0IGRvbid0IGFwcGVhciBpbiBtYW55IHR3ZWV0cyBhdCB0aGlzIHBvaW50LiBUaGlzIHNob3VsZCBnZXQgdXMgZG93biB0byBhIGZldyBodW5kcmVkIHRlcm1zLgoKYGBge3J9CmFydGljbGVzX2R0bSA9IHJlbW92ZVNwYXJzZVRlcm1zKGFydGljbGVzX2R0bSwgLjk5KQphcnRpY2xlc19kdG1fbWF0cml4ID0gYXMubWF0cml4KGFydGljbGVzX2R0bSkKYGBgCgpHbyBhaGVhZCBhbmQgdmlldyB0aGUgYXJ0aWNsZXNfZHRtX21hdHJpeCBvYmplY3QgYnkgY2xpY2tpbmcgb24gdGhlIGxpdHRsZSB0YWJsZSBpY29uIGluIHRoZSBFbnZpcm9ubWVudCBwYW5lIGF0IHRoZSB0b3AgcmlnaHQuIEl0IGNhbiBiZSBlYXN5IHRvIGp1c3QgcnVuIGNvZGUgYW5kIGZvcmdldCB3aGF0IHRoZSBkYXRhIHN0cnVjdHVyZXMgbG9vayBsaWtlLCBidXQgaXQncyBpbXBvcnRhbnQgdG8ga2VlcCBpbiBtaW5kIHdoYXQgd2UncmUgd29ya2luZyB3aXRoLgoKVGhlIGxhc3QgdGhpbmcgd2UgbmVlZCB0byBkbyBpcyBnZXQgcmlkIG9mIHJvd3MgdGhhdCBoYXZlIHplcm8gaW4gZXZlcnkgY29sdW1uLiBXZSBuZWVkIHRvIGRvIHRoaXMgdG8gYm90aCBvdXQgRFRNIGFuZCBvdXIgb3JpZ2luYWwgZGF0YXNldDoKCmBgYHtyfQpyb3dfdG90YWxzID0gYXBwbHkoYXJ0aWNsZXNfZHRtICwgMSwgc3VtKQphcnRpY2xlcyA9IGFydGljbGVzW3Jvd190b3RhbHMgPiAwLF0KYXJ0aWNsZXNfdGV4dCA9IGFydGljbGVzX3RleHRbcm93X3RvdGFscyA+IDBdCmFydGljbGVzX2R0bSA9IGFydGljbGVzX2R0bVtyb3dfdG90YWxzPiAwLCBdICAKYGBgCgpOb3cgd2UgY2FuIGdvIGFoZWFkIGFuZCBjcmVhdGUgb3VyIHRvcGljIG1vZGVsLiBBdCB0aGlzIHBvaW50LCB3ZSdyZSBqdXN0IGdvaW5nIHRvIHRlbGwgaXQgaG93IG1hbnkgdG9waWNzIHdlIHdhbnQuIExldCdzIHN0YXJ0IHdpdGggNToKCmBgYHtyfQphcnRpY2xlc190b3BpY19tb2RlbCA9IExEQShhcnRpY2xlc19kdG0sIGsgPSA1ICkKYGBgCgpXZSB1c3VhbGx5IGV4YW1pbmUgb3VyIHRvcGljcyBpbmZvcm1hbGx5LCBsb29raW5nIGF0IHRoZSB3b3JkcyBtb3N0IGxpa2VseSB0byBhcHBlYXIgaW4gdGhhdCB0b3BpYyBhbmQgYXNraW5nIHdoZXRoZXIgdGhleSBzZWVtIGNvbXByZWhlbnNpYmxlLiBXZSBtaWdodCwgYXQgdGhpcyBwb2ludCwgbmVlZCB0byByZXZpc2UgdGhlIG51bWJlciBvZiB0b3BpY3Mgd2UncmUgYXNraW5nIGZvci4gCgpgYGB7cn0KdGVybXMoYXJ0aWNsZXNfdG9waWNfbW9kZWwsIDEwKQpgYGAKCkVhY2ggZG9jdW1lbnQgaXMgbm93IGRlc2NyaWJlZCBhcyBhIG1peHR1cmUgb2YgdG9waWNzLCBidXQgd2UnbGwgc3RvcCBoZXJlIGJ5IGp1c3QgbG9va2luZyBhdCB0aGUgdG9wIHRvcGljIGZvciBlYWNoIGRvY3VtZW50LgoKYGBge3J9CmFydGljbGVzJHRvcF90b3BpYyA9IHRvcGljcyhhcnRpY2xlc190b3BpY19tb2RlbCkKYXJ0aWNsZXNfc3VtbWFyeSA9IGFydGljbGVzWyxjKDIsNCldCmBgYAoKQXQgdGhpcyBwb2ludCwgdGhlcmUgYXJlIGxvdHMgb2YgYW5hbHlzaXMgYW5kIHZpc3VhbGl6YXRpb24gb3B0aW9ucywgZGVwZW5kaW5nIG9uIHlvdXIgbWV0aG9kb2xvZ3kuCgojIyMgUmVzb3VyY2VzCi0gVGhlIG9yaWdpbmFsIENTIGFydGljbGU6IGh0dHA6Ly9qbWxyLmNzYWlsLm1pdC5lZHUvcGFwZXJzL3ZvbHVtZTMvYmxlaTAzYS9ibGVpMDNhLnBkZgotIEEgbmljZSwgc2hvcnQgaW50cm9kdWN0aW9uOiBodHRwOi8vam91cm5hbG9mZGlnaXRhbGh1bWFuaXRpZXMub3JnLzItMS90b3BpYy1tb2RlbGluZy1hLWJhc2ljLWludHJvZHVjdGlvbi1ieS1tZWdhbi1yLWJyZXR0LwotIEFub3RoZXIgbmljZSBpbnRyb2R1Y3Rpb246IGh0dHA6Ly93d3cuc2NvdHRib3QubmV0L0hJQUwvaW5kZXguaHRtbEBwPTE5MTEzLmh0bWwKLSBNQUxMRVQsIGFuIGVhc3ktaXNoIHRvb2wgZm9yIHRvcGljIG1vZGVsaW5nOiBodHRwOi8vbWFsbGV0LmNzLnVtYXNzLmVkdS9pbmRleC5waHAKLSBBbiBleHBlcmltZW50IHVzaW5nIHRvcGljIG1vZGVsaW5nIGluIGEgc29jaWFsIHNjaWVuY2UgLyBqb3VybmFsaXNtIGNvbnRleHQ6IGh0dHBzOi8vd3d3LnRhbmRmb25saW5lLmNvbS9kb2kvZnVsbC8xMC4xMDgwLzIxNjcwODExLjIwMTUuMTA5MzI3MT9zY3JvbGw9dG9wJm5lZWRBY2Nlc3M9dHJ1ZQotIEEgY29tcGFyaXNvbiBvbiB0b3BpYyBtb2RlbGluZyBhbmQgZGljdGlvbmFyeS1iYXNlZCBhcHByb2FjaGVzIGluIGEgbWFzcyBvY21tLiByZXNlYXJjaCBjb250ZXh0OiBodHRwOi8vam91cm5hbHMuc2FnZXB1Yi5jb20vZG9pL2Ficy8xMC4xMTc3LzEwNzc2OTkwMTY2MzkyMzEKCiMjIFN1cGVydmlzZWQgVGVjaG5pcXVlczogU3VwcG9ydCBWZWN0b3IgTWFjaGluZSAoU1ZNKSBDbGFzc2lmaWVycwoKV2hlcmUgdG9waWMgbW9kZWxpbmcgZG9lc24ndCBnaXZlIHRoZSB1c2VyIGFueSBjb250cm9sIG92ZXIgdGhlIG1vZGVsJ3MgcmVzdWx0cyAoZS5nLiwgdGhlIHRvcGljcyBpZGVudGlmaWVkIG1pZ2h0IG5vdCBiZSAtLSBhbmQgb2Z0ZW4gYXJlbid0IC0tIG1lYW5pbmdmdWwgdG8gaHVtYW5zKSwgc3VwZXJ2aXNlZCB0ZWNobmlxdWVzIGFsbG93IHRoZSB1c2VyIHRvIHByb3ZpZGUgZXhhbXBsZXMgb2Ygd2hhdCB0aGV5J3JlIGxvb2tpbmcgZm9yLiBBIGJhc2ljIGV4YW1wbGUgb2YgdGhpcyBpcyBhIGJpbmFyeSBjbGFzc2lmaWVyLCB3aGVyZSBvYnNlcnZhdGlvbnMgYXJlIHNvcnRlZCBpbnRvIG9uZSBvZiB0d28gY2xhc3Nlcy4gVGhlIHN1cGVydmlzZWQgcGFydCBvZiB0aGVzZSB0ZWNobmlxdWVzIGlzIHRoYXQgdGhlIHVzZXIgcHJvdmlkZXMgZXhhbXBsZXMgZm9yIHRoZSBtb2RlbCB0byAibGVhcm4iIGZyb20uIFNvLCBpZiB3ZSBjcmVhdGVkIGEgY2xhc3NpZmllciB0byBzb3J0IGltYWdlcyBpbnRvIHRob3NlIHRoYXQgY29udGFpbiBzdG9wIHNpZ25zIGFuZCB0aG9zZSB0aGF0IGRvbid0LCB3ZSB3b3VsZCBwcm92aWRlIHRoZSBtb2RlbCB3aXRoIGV4YW1wbGVzIG9mIHBpY3R1cmVzIHRoYXQgY29udGFpbiBhbmQgZG9uJ3QgY29udGFpbiBzdG9wIHNpZ25zLiAoV2hlbiB5b3UgInByb3ZlIHlvdSdyZSBodW1hbiIgdG8gbG9naW4gdG8gd2Vic2l0ZXMsIHRoaXMgaXMgcm91Z2hseSB3aGF0IHlvdSdyZSBkb2luZy4pCgpTdXBlcnZpc2VkIG1hY2hpbmUgbGVhcm5pbmcgdHlwaWNhbGx5IHJlcXVpcmVzIGEgdHJhaW5pbmcgZGF0YSBzZXQgLS0gYSBzdWJzZXQgb2YgeW91ciBkYXRhIHRoYXQgeW91J3ZlIGNsYXNzaWZpZWQgKGFsc28gcmVmZXJyZWQgdG8gYXMgbGFiZWxlZCBkYXRhKS5XZSB1c2UgdGhpcyBsYWJlbGVkIGRhdGEgaW4gb3JkZXIgdG8gInRyYWluIiBhIG1vZGVsLCB3aGljaCB3ZSdsbCB0aGVuIHVzZSB0byBjbGFzc2lmeSBhIGxhcmdlciBjb2xsZWN0aW9uIG9mIHVubGFiZWxlZCBkYXRhLiBTb21lIHdvcmtmbG93cyBhbHNvIGluY2x1ZGUgYSB0ZXN0IHNldCBvZiBkYXRhIHRoYXQgaXMgdXNlZCB0byBtZWFzdXJlIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlLCBidXQgZm9yIHRoaXMgZXhhbXBsZSB3ZSdsbCBqdXN0IHVzZSBhIHRyYWluaW5nIHNldC4gCgpJbiB0aGlzIGV4YW1wbGUsIHdlJ2xsIGJlIHdvcmtpbmcgd2l0aCBhIHNlcmllcyBvZiBhaXJsaW5lIHR3ZWV0cywgYW5kIHdlJ2xsIGF0dGVtcHQgdG8gY2xhc3NpZnkgd2hldGhlciBvciBub3QgdGhleSdyZSBjb21wbGFpbnRzLiBJJ3ZlIGNyZWF0ZWQgYSB0cmFpbmluZyBzZXQgZm9yIHVzIHRvIGNsYXNzaWZ5IHRvZ2V0aGVyLiBJdCdzIGF2YWlsYWJsZSBoZXJlOiBodHRwczovL2RvY3MuZ29vZ2xlLmNvbS9zcHJlYWRzaGVldHMvZC8xRW40WXpCN3l1VzhHRVRfUEx1ZktEaGhtWGI2eW5PTjZzSGhsN1d5RExxYy9lZGl0P3VzcD1zaGFyaW5nCgpUaGUgc3ByZWFkc2hlZXQgaGFzIGEgY29sdW1uIGNhbGxlZCBjb21wbGFpbnQgLS0gd29ya2luZyBpbiBncm91cHMsIHdlJ2xsIGZpbGwgaW4gdGhhdCBjb2x1bW4gd2l0aCAtMSBpZiB0aGUgdHdlZXQgaXNuJ3QgYSBjb21wbGFpbnQgYW5kIDEgaWYgaXQgaXMuIAoKT25jZSB3ZSdyZSBkb25lIERvd25sb2FkIHRoZSBzcHJlYWRzaGVldCBhcyBhIENTViwgcmVuYW1lIGl0IGFpcmxpbmVfdHdlZXRzX3RyYWluaW5nLmNzdiBhbmQgbW92ZSBpdCB0byB0aGUgZm9sZGVyIHlvdSdyZSB3b3JraW5nIGluLgoKUmVhZCBpbiB0aGUgdHJhaW5pbmcgYW5kIGZ1bGwgZGF0YToKCmBgYHtyfQp0d2VldHNfYWxsID0gcmVhZC5jc3YoJ2FpcmxpbmVfdHdlZXRzLmNzdicpCnR3ZWV0c190cmFpbmluZyA9IHJlYWQuY3N2KCdhaXJsaW5lc190d2VldHNfdHJhaW5pbmcuY3N2JykKYGBgCgpXZSBzdGFydCBhZ2FpbiBieSBjcmVhdGluZyBhIERUTSBtYXRyaXguIFRoaXMgZ2l2ZXMgdXMgYSBmZWF0dXJlIHZlY3RvciBmb3IgZWFjaCB0d2VldCAtLSBhIGxpc3Qgb2YgZmVhdHVyZXMgdGhhdCBkZXNjcmliZXMgdGhlIHR3ZWV0LiBPdXIgZmVhdHVyZXMgY291bGQgYmUgYW55dGhpbmcuIEJ1dCBmb3IgdGhpcyBleGVyY2lzZSAtLSBhbmQgY29udmVudGlvbmFsbHkgd2hlbiB1c2luZyBTVk0gY2xhc3NpZmllcnMgb24gdGV4dCAtLSB3ZSdyZSBnb2luZyB0byB1c2UgYSBEVE0gdG8gcHJvZHVjZSBvdXIgZmVhdHVyZSB2ZWN0b3JzLiBTbywgdGhlIGZlYXR1cmVzIGFyZSBqdXN0IHdvcmQgY291bnRzLgoKSW4gdGhlIGZvbGxvd2luZyBzdGVwcywgd2UnbGwgcmVwZWF0IHNvbWUgb2YgdGhlIHNhbWUgdGV4dCBwcm9jZXNzaW5nIHN0ZXBzIGZyb20gdGhlIHRvcGljIG1vZGVscyBleGFtcGxlLgoKYGBge3J9CnR3ZWV0X3RyYWluaW5nX2NvcnB1cyA9IENvcnB1cyhWZWN0b3JTb3VyY2UodHdlZXRzX3RyYWluaW5nJHRleHQpKQoKI1JlbW92ZSB3aGl0ZXNwYWNlCnR3ZWV0X3RyYWluaW5nX2NvcnB1cyA9IHRtX21hcCh0d2VldF90cmFpbmluZ19jb3JwdXMsIHN0cmlwV2hpdGVzcGFjZSkKI0NvbnZlcnQgdG8gbG93ZXJjYXNlCnR3ZWV0X3RyYWluaW5nX2NvcnB1cyA9IHRtX21hcCh0d2VldF90cmFpbmluZ19jb3JwdXMsIHRvbG93ZXIpCiNSZW1vdmUgcHVuY3R1YXRpb24KdHdlZXRfdHJhaW5pbmdfY29ycHVzID0gdG1fbWFwKHR3ZWV0X3RyYWluaW5nX2NvcnB1cywgcmVtb3ZlUHVuY3R1YXRpb24pCiNTdHJpcCBkaWdpdHMKdHdlZXRfdHJhaW5pbmdfY29ycHVzID0gdG1fbWFwKHR3ZWV0X3RyYWluaW5nX2NvcnB1cywgcmVtb3ZlTnVtYmVycykKI1JlbW92ZSBzdG9wd29yZHMKdHdlZXRfdHJhaW5pbmdfY29ycHVzID0gdG1fbWFwKHR3ZWV0X3RyYWluaW5nX2NvcnB1cywgcmVtb3ZlV29yZHMsIGMoc3RvcHdvcmRzKCdlbmdsaXNoJykpKQoKIyBDcmVhdGUgRFRNCnR3ZWV0X3RyYWluaW5nX2R0bSA9IERvY3VtZW50VGVybU1hdHJpeCh0d2VldF90cmFpbmluZ19jb3JwdXMpCgpgYGAKCk5vdyB3ZSB0cmFpbiB0aGUgU1ZNIG1vZGVsOgoKYGBge3J9CnRyYWluaW5nX2NvbnRhaW5lciA9IGNyZWF0ZV9jb250YWluZXIodHdlZXRfdHJhaW5pbmdfZHRtLCB0d2VldHNfdHJhaW5pbmckY29tcGxhaW50LCB0cmFpblNpemU9MTpucm93KHR3ZWV0c190cmFpbmluZyksIHZpcmdpbj1GQUxTRSkKY29tcGxhaW50c19tb2RlbCA9IHRyYWluX21vZGVsKHRyYWluaW5nX2NvbnRhaW5lciwgIlNWTSIsIGtlcm5lbD0ibGluZWFyIiwgY29zdD0xKQpgYGAKCk5vdyB0aGF0IHdlIGhhdmUgb3VyIG1vZGVsLCB3ZSBjYW4gdXNlIGl0IHRvIHByZWRpY3Qgd2hldGhlciB0aGUgcmVzdCBvZiB0aGUgdHdlZXRzIGFyZSBjb21wbGFpbnRzIG9yIG5vdC4gV2UgaGF2ZSBvdXIgZnVsbCBzZXQgaW4gdGhlIHR3ZWV0c19hbGwgb2JqZWN0LCBhbmQgd2UncmUgZ29pbmcgdG8gY3JlYXRlIGFub3RoZXIgRFRNIGZyb20gdGhhdC4gV2UncmUgZ29pbmcgdG8gZG8gaXQgYSBsaXR0bGUgZGlmZmVyZW50bHkgdGhpcyB0aW1lLCBiZWNhdXNlIHdlIHdhbnQgdG8gY29uc3RyYWluIHRoZSB2b2NhYnVsYXJ5IG9mIHRoZSBuZXcgRFRNIHRvIHRoZSB2b2NhYnVsYXJ5IG9mIG91ciB0cmFpbmluZyBzZXQuIAoKYGBge3J9CnR3ZWV0X2NvcnB1cyA9IENvcnB1cyhWZWN0b3JTb3VyY2UodHdlZXRzX2FsbCR0ZXh0KSkKCiNSZW1vdmUgd2hpdGVzcGFjZQp0d2VldF9jb3JwdXMgPSB0bV9tYXAodHdlZXRfY29ycHVzLCBzdHJpcFdoaXRlc3BhY2UpCiNDb252ZXJ0IHRvIGxvd2VyY2FzZQp0d2VldF9jb3JwdXMgPSB0bV9tYXAodHdlZXRfY29ycHVzLCB0b2xvd2VyKQojUmVtb3ZlIHB1bmN0dWF0aW9uCnR3ZWV0X2NvcnB1cyA9IHRtX21hcCh0d2VldF9jb3JwdXMsIHJlbW92ZVB1bmN0dWF0aW9uKQojU3RyaXAgZGlnaXRzCnR3ZWV0X2NvcnB1cyA9IHRtX21hcCh0d2VldF9jb3JwdXMsIHJlbW92ZU51bWJlcnMpCiNSZW1vdmUgc3RvcHdvcmRzCnR3ZWV0X2NvcnB1cyA9IHRtX21hcCh0d2VldF9jb3JwdXMsIHJlbW92ZVdvcmRzLCBjKHN0b3B3b3JkcygnZW5nbGlzaCcpKSkKCiMgQ3JlYXRlIERUTQp0d2VldF9kdG0gPSBEb2N1bWVudFRlcm1NYXRyaXgodHdlZXRfY29ycHVzLCBjb250cm9sID0gbGlzdChkaWN0aW9uYXJ5PVRlcm1zKHR3ZWV0X3RyYWluaW5nX2R0bSkpKQoKI0Zvcm1hdHRpbmcgYW5kIGdldHRpbmcgdGhpbmdzIHJlYWR5Li4uLgpwcmVkaWN0aW9uX2NvbnRhaW5lciA9IGNyZWF0ZV9jb250YWluZXIodHdlZXRfZHRtLCBsYWJlbHM9cmVwKDAsbnJvdyh0d2VldHNfYWxsKSksIHRlc3RTaXplPTE6bnJvdyh0d2VldHNfYWxsKSwgdmlyZ2luPUZBTFNFKQoKdHdlZXRzX2FsbCA9IGNiaW5kKHR3ZWV0c19hbGwsIGNsYXNzaWZ5X21vZGVsKHByZWRpY3Rpb25fY29udGFpbmVyLCBjb21wbGFpbnRzX21vZGVsKSkKCgpgYGAKCkFnYWluLCB0aGVyZSBhcmUgbG90cyBvZiB0aGluZ3Mgd2UgY291bGQgZG8gd2l0aCB0aGVzZSBwcmVkaWN0aW9ucy4gCgpGb3Igbm93LCB3ZSdsbCBqdXN0IHF1aWNrbHkgY29tcGFyZSB0aGUgZGlmZmVyZW50IGFpcmxpbmVzIGJhc2VkIG9uIGhvdyBtYW55IGNvbXBsYWludHMgdGhleSBnZXQuIEZvciBub3csIHdlJ2xsIGp1c3QgYXZlcmFnZSB0aGUgbGFiZWwgY29sdW1uIGluIG91ciB0d2VldHNfYWxsIGRhdGFmcmFtZS4KCk5vdGU6IEknbSB1c2luZyBkcGx5ciBhbmQgZ2dwbG90MiBoZXJlLiBXZSdyZSBub3QgZ29pbmcgdG8gZ28gb3ZlciB0aGVzZSBwYWNrYWdlcywgYnV0IGlmIHlvdSB3ZXJlIHRvIGxlYXJuIHR3byBSIHBhY2thZ2VzLCB0aGVzZSB3b3VsZCBiZSB2ZXJ5IGdvb2QgY2FuZGlkYXRlcy4gCgpgYGB7cn0KYWlybGluZXNfc3VtbWFyeSA9IHR3ZWV0c19hbGwgJT4lIGdyb3VwX2J5KGFpcmxpbmUpICU+JSBzdW1tYXJpc2UoCiAgICBhdmdfbGFiZWwgPSBtZWFuKGFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKFNWTV9MQUJFTCkpKQopCgpnZ3Bsb3QoZGF0YT1haXJsaW5lc19zdW1tYXJ5LCBhZXMoeD1haXJsaW5lLCB5PWF2Z19sYWJlbCwgZmlsbD1haXJsaW5lKSkgKwogICAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKQpgYGAKCgoKCiMjIyBSZXNvdXJjZXMKCi0gQW4gb3ZlcnZpZXcgb2YgY29tcHV0YXRpb25hbCB0ZWNobmlxdWVzIGluIGEgam91cm5hbGlzbSBjb250ZXh0OiBodHRwczovL3d3dy50YW5kZm9ubGluZS5jb20vZG9pL2Ficy8xMC4xMDgwLzIxNjcwODExLjIwMTUuMTA5NjU5OAotIEFuIGludGVyZXN0aW5nIGV4YW1wbGUgb2YgdXNpbmcgU1ZNIGNsYXNzaWZpZXJzIGZvciBjb25zdHJ1Y3RpbmcgZG9jdW1lbnQgc2V0cyBmb3IgcmVzZWFjaCBwdXJwb3NlczogaHR0cDovL3d3dy5qc3Rvci5vcmcvc3RhYmxlLzI0NTczMjIzCi0gUiBHcmFwaGljcyBDb29rYm9vaywgYSB1c2VmdWwgcmVzb3VyY2UgZm9yIGxlYXJuaW5nIGdncGxvdDI6IGh0dHA6Ly93d3cuY29va2Jvb2stci5jb20vR3JhcGhzLwotIEludHJvZHVjdGlvbiB0byBkcGx5cjogaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2RwbHlyL3ZpZ25ldHRlcy9kcGx5ci5odG1sCi0gVGlkeSBEYXRhIGJ5IEhhZGxleSBXaWNraGFtIChhbnl0aGluZyBieSBXaWNraGFtIGlzIHZlcnkgZ29vZCk6IGh0dHBzOi8vdml0YS5oYWQuY28ubnovcGFwZXJzL3RpZHktZGF0YS5wZGYKCgoK