library(ggplot2)
library(knitr)

Machine Learning (ML) sometimes seems pretty difficult as it assumes that you are comfortable with some broad ranging concepts from linear algebra, calculus, statistics, and probability. It borrows techniques from all of these disciplines, mashing them together in all kinds of different ways, and using them in problems that are often concerned with some kind of function optimisation. THe purpose of this tutorial is to provide an overview of some of the ideas that you may run into in ML, in the context of exploring Support Vector Machines. Along the way we will also explore some of the really nice things you can do in R Markdown notebooks, like formatting for math, and embedding images and videos.

There are, of course, all kinds of different algorithms that are used in ML to solve problems of classification (things like Decision Trees, KNN etc). The one we will explore here is called a Support vector machines (SVM). SVMs can actually be used for both classification or regression problems, but most of the time you will find them used for classification. If, for example, you have data that has 2 or more labeled classes, you can use an SVM as a discriminative classifier, and can define a decision boundary (sometimes called an optimal hyperplane) that provides a function which seperates the classes. When any new data points are then mapped onto the same space of the others, they will be categorized into the correct class (i.e. be on the correct side of the line or plane that is the function).

An SVM is an example of what’s called a constrained optimisation problem . It optimises the space of the margin between classes, but also seeks to constrain the extent of this margin in relation to data points. A nice visualisation to present the idea provided below. You can think of the problem in terms of how you can construct a street between the points, that is as wide as possible.

Hello docker whale!

To provide another another intuition, of the three lines you can see in the above visualisation, the dotted ones (sometimes called the “gutters” of the street) are at the extremes of the margin, or width of the street. We want to maximisise the margin (or space between the two gutters) so the classes (represented by green ovals and blue rectangles) as as far away as possible. The middle line is called the decision boundary (or optimal hyperplane) that is a centre line. The image below shows an alternative visualisation, and you can see the decision boundary / hyperplane in both two and three dimensional space.

Hello docker whale!

One of the nice things about many ML algorithms is you can extend them into arbitrary dimensions using the power of linear algebra. So you might have 400 classes, and if so, you can create a 399 dimenssion hyperplane, or decision boardary that seperates them. You can then provide a further data point to your model, and your SVM will be put it in the right space on the 399 dimensions.

You can also do this neat thing (often called the “Kernal trick”) to deal with data that does not lend itself to these straight (i.e. linear) widest road type decision boundaries (essentially by pushing the data into higher dimensions). But for now, lets keep things really simple, and just start with a few data points in two dimensions that we could divide with a linear (i.e. straight line) SVM.

To explore this, the first thing to do is create a data frame. It will have 5 rows, with each row having two features (f1 and f2), and a target. Note that I have also put something in called the biasTerm. The bias terms is kind of complicated to explain but it gives the whole model a little more wiggle room to move as it trys to fit the best decision boundary, and doesn’t constrain the function to passing through the origin (you might like to think of it as a way to move the y-intecept of a function). The code to build the data frame and the frame itself is listed below.

a <- data.frame(c(-2, 4, 1, 2, 6))
colnames(a) = "f1"
a$f2 = c(4, 1, 6, 4, 2)
a$biasTerm = c(-1, -1, -1, -1, -1)
a$target <- c(-1, -1, 1, 1, 1)



Some sample data

f1 f2 biasTerm target
-2 4 -1 -1
4 1 -1 -1
1 6 -1 1
2 4 -1 1
6 2 -1 1

To put the problem as simply as possible, the question we are really trying to solve here asks, “what are the most optimal weights I might use for f1 and f2 which will mean I will predict the target correctly?”.


Let’s start by looking at the plotted points, f1, and f2:


ggplot(a, 
       aes(x = f1, 
           y = f2, 
           color = factor(target))) + 
      geom_point()

Before we start thinking about constructing a optimal hyperplane or decision boundary, note that we could of course just provide any random decision boundary (i.e. apply no learning at all). Here we are only in two dimensions, so this is would be a one-dimensional hyperplane, or just a line that we could use. And being random, as to be expected, it is not very good. So our challenge is how to move this line so it becomes an optimal hyperplane or decision boundary for these points.

ggplot(a, 
       aes(x = f1, 
           y = f2, 
           color = factor(target))) + 
      geom_point() + 
      geom_segment(aes(x = -2.1, 
                       y = -2, 
                       xend = 6, 
                       yend = 6))

Time to check out the underlying Math

Like many algorithms you might find in ML, the basic idea here is that we want to create weights or co-efficients for our variables, then check how wrong they are, and then iterate in some way to make it more correct. To do this, we will use a few mathematical ingredients: a loss function, an objective function, an optimisation function, and we will also provide some things to track how our function is performing given different weights of f1 and f2.

Details around each of these are provided below.


So there is quiet a bit going on here: a regularizer controls the trade off between the achieving a low training error and a low testing error that is the ability to generalize your classifier to unseen data. As a regulizing parameter we choose 1/epochs, so this parameter will decrease, as the number of epochs increases. Note that a regularizer too high will overfit (large testing error) and regularizer being too low will underfit (large training error).


Code

weights <- base::rep(0, 3)
eta = 1
epochs = 10000
errors <- c()
for (epoch in 1:epochs) {
  
  
  # set current error to 0
  error <- 0
  
  # Print current weight values
  # print("Current weights:")
  # print(weights)
  
  for(i in 1:nrow(a)) {
    
    featureVector <- as.double(a[i, 1:3])
    targets <- as.double(a$target)
    currentError <- targets[i] * featureVector %*% weights
    # print("Error with current weights:")
    # print(currentError < 1)
  
    if (targets[i] * featureVector %*% weights < 1) {
      
      # misclassification, set error
       weights = weights + eta * ( (featureVector * targets[i]) + (-2  *(1/epoch)* weights) )
       error = 1
    } else {
      # correct prediction
      weights <- weights + eta * (-2  *(1/epoch)* weights)
    
    }
    
    errors <- c(errors, error)
    
  
  
} 
}
For each of our iterations, sometimes we predicted correctly, and sometimes not correctly. Then we used our derived objective function to update the weights for f1 and f2. Note how simple the actual code was, and this is largely because the math itself is doing the heavy lifting. Once we have that objective function, its just a matter of coding it up. Now Let’s plot the errors


Plot of errors

t <- data.frame(errors)
ggplot(t, 
       aes(y = errors, 
           x = seq(1:50000),
           color = "errors"
         )) + 
      geom_point()


Let’s also check out the final weights
print(paste("Optimal f1 weight: ", weights[1]))
[1] "Optimal f1 weight:  1.75353189546445"
print(paste("Optimal f2 weight: ", weights[2]))
[1] "Optimal f2 weight:  3.47498302933517"
print(paste("Optimal bias: ", weights[3]))
[1] "Optimal bias:  12.2452630147726"

So where to next?

We covered alot of ideas here but it should at least serve to give a bit of an intro into some of the main ideas that come up alot in machine learning. To get into it further here are some next steps:

LS0tDQp0aXRsZTogIlN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KDQo8YnIvPGJyLz4NCg0KDQpgYGAge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGtuaXRyKQ0KDQpgYGANCg0KDQoNCjxkaXY+TWFjaGluZSBMZWFybmluZyAoTUwpIHNvbWV0aW1lcyBzZWVtcyBwcmV0dHkgZGlmZmljdWx0IGFzIGl0IGFzc3VtZXMgdGhhdCB5b3UgYXJlIGNvbWZvcnRhYmxlIHdpdGggc29tZSBicm9hZCByYW5naW5nIGNvbmNlcHRzIGZyb20gbGluZWFyIGFsZ2VicmEsIGNhbGN1bHVzLCBzdGF0aXN0aWNzLCBhbmQgcHJvYmFiaWxpdHkuIEl0IGJvcnJvd3MgdGVjaG5pcXVlcyBmcm9tIGFsbCBvZiB0aGVzZSBkaXNjaXBsaW5lcywgbWFzaGluZyB0aGVtIHRvZ2V0aGVyIGluIGFsbCBraW5kcyBvZiBkaWZmZXJlbnQgd2F5cywgYW5kIHVzaW5nIHRoZW0gaW4gcHJvYmxlbXMgdGhhdCBhcmUgb2Z0ZW4gY29uY2VybmVkIHdpdGggc29tZSBraW5kIG9mIGZ1bmN0aW9uIG9wdGltaXNhdGlvbi4gVEhlIHB1cnBvc2Ugb2YgdGhpcyB0dXRvcmlhbCBpcyB0byBwcm92aWRlIGFuIG92ZXJ2aWV3IG9mIHNvbWUgb2YgdGhlIGlkZWFzIHRoYXQgeW91IG1heSBydW4gaW50byBpbiBNTCwgaW4gdGhlIGNvbnRleHQgb2YgZXhwbG9yaW5nIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzLiBBbG9uZyB0aGUgd2F5IHdlIHdpbGwgYWxzbyBleHBsb3JlIHNvbWUgb2YgdGhlIHJlYWxseSBuaWNlIHRoaW5ncyB5b3UgY2FuIGRvIGluIFIgTWFya2Rvd24gbm90ZWJvb2tzLCBsaWtlIGZvcm1hdHRpbmcgZm9yIG1hdGgsIGFuZCBlbWJlZGRpbmcgaW1hZ2VzIGFuZCB2aWRlb3MuDQo8L2Rpdj4gDQoNCjxkaXY+VGhlcmUgYXJlLCBvZiBjb3Vyc2UsIGFsbCBraW5kcyBvZiBkaWZmZXJlbnQgYWxnb3JpdGhtcyB0aGF0IGFyZSB1c2VkIGluIE1MIHRvIHNvbHZlIHByb2JsZW1zIG9mIGNsYXNzaWZpY2F0aW9uICh0aGluZ3MgbGlrZSBEZWNpc2lvbiBUcmVlcywgS05OIGV0YykuIFRoZSBvbmUgd2Ugd2lsbCBleHBsb3JlIGhlcmUgaXMgY2FsbGVkIGEgPHN0cm9uZz5TdXBwb3J0IHZlY3RvciBtYWNoaW5lczwvc3Ryb25nPiAoU1ZNKS4gU1ZNcyBjYW4gYWN0dWFsbHkgYmUgdXNlZCBmb3IgYm90aCBjbGFzc2lmaWNhdGlvbiBvciByZWdyZXNzaW9uIHByb2JsZW1zLCBidXQgbW9zdCBvZiB0aGUgdGltZSB5b3Ugd2lsbCBmaW5kIHRoZW0gdXNlZCBmb3IgY2xhc3NpZmljYXRpb24uIElmLCBmb3IgZXhhbXBsZSwgeW91IGhhdmUgZGF0YSB0aGF0IGhhcyAyIG9yIG1vcmUgbGFiZWxlZCBjbGFzc2VzLCB5b3UgY2FuIHVzZSBhbiBTVk0gYXMgYSBkaXNjcmltaW5hdGl2ZSBjbGFzc2lmaWVyLCBhbmQgY2FuIGRlZmluZSBhIGRlY2lzaW9uIGJvdW5kYXJ5IChzb21ldGltZXMgY2FsbGVkIGFuIG9wdGltYWwgaHlwZXJwbGFuZSkgdGhhdCBwcm92aWRlcyBhIGZ1bmN0aW9uIHdoaWNoIHNlcGVyYXRlcyB0aGUgY2xhc3Nlcy4gV2hlbiBhbnkgbmV3IGRhdGEgcG9pbnRzIGFyZSB0aGVuIG1hcHBlZCBvbnRvIHRoZSBzYW1lIHNwYWNlIG9mIHRoZSBvdGhlcnMsIHRoZXkgd2lsbCBiZSBjYXRlZ29yaXplZCBpbnRvIHRoZSBjb3JyZWN0IGNsYXNzIChpLmUuIGJlIG9uIHRoZSBjb3JyZWN0IHNpZGUgb2YgdGhlIGxpbmUgb3IgcGxhbmUgdGhhdCBpcyB0aGUgZnVuY3Rpb24pLg0KPC9kaXY+DQoNCjxkaXY+QW4gU1ZNIGlzIGFuIGV4YW1wbGUgb2Ygd2hhdCdzIGNhbGxlZCBhIDxpPmNvbnN0cmFpbmVkIG9wdGltaXNhdGlvbjwvaT4gcHJvYmxlbSAuIEl0IDxpPm9wdGltaXNlczwvaT4gdGhlIHNwYWNlIG9mIHRoZSBtYXJnaW4gYmV0d2VlbiBjbGFzc2VzLCBidXQgYWxzbyBzZWVrcyB0byA8aT5jb25zdHJhaW48L2k+IHRoZSBleHRlbnQgb2YgdGhpcyBtYXJnaW4gaW4gcmVsYXRpb24gdG8gZGF0YSBwb2ludHMuIEEgbmljZSB2aXN1YWxpc2F0aW9uIHRvIHByZXNlbnQgdGhlIGlkZWEgcHJvdmlkZWQgYmVsb3cuIFlvdSBjYW4gdGhpbmsgb2YgdGhlIHByb2JsZW0gaW4gdGVybXMgb2YgaG93IHlvdSBjYW4gY29uc3RydWN0IGEgc3RyZWV0IGJldHdlZW4gdGhlIHBvaW50cywgdGhhdCBpcyBhcyB3aWRlIGFzIHBvc3NpYmxlLjwvZGl2Pg0KDQoNCiMhW0hlbGxvIGRvY2tlciB3aGFsZSFdKHN2LnBuZykNCg0KDQo8ZGl2PlRvIHByb3ZpZGUgYW5vdGhlciBhbm90aGVyIGludHVpdGlvbiwgb2YgdGhlIHRocmVlIGxpbmVzIHlvdSBjYW4gc2VlIGluIHRoZSBhYm92ZSB2aXN1YWxpc2F0aW9uLCB0aGUgZG90dGVkIG9uZXMgKHNvbWV0aW1lcyBjYWxsZWQgdGhlICJndXR0ZXJzIiBvZiB0aGUgc3RyZWV0KSBhcmUgYXQgdGhlIGV4dHJlbWVzIG9mIHRoZSBtYXJnaW4sIG9yIHdpZHRoIG9mIHRoZSBzdHJlZXQuIFdlIHdhbnQgdG8gbWF4aW1pc2lzZSB0aGUgbWFyZ2luIChvciBzcGFjZSBiZXR3ZWVuIHRoZSB0d28gZ3V0dGVycykgc28gdGhlIGNsYXNzZXMgKHJlcHJlc2VudGVkIGJ5IGdyZWVuIG92YWxzIGFuZCBibHVlIHJlY3RhbmdsZXMpIGFzIGFzIGZhciBhd2F5IGFzIHBvc3NpYmxlLiBUaGUgbWlkZGxlIGxpbmUgaXMgY2FsbGVkIHRoZSBkZWNpc2lvbiBib3VuZGFyeSAob3Igb3B0aW1hbCBoeXBlcnBsYW5lKSB0aGF0IGlzIGEgY2VudHJlIGxpbmUuIFRoZSBpbWFnZSBiZWxvdyBzaG93cyBhbiBhbHRlcm5hdGl2ZSB2aXN1YWxpc2F0aW9uLCBhbmQgeW91IGNhbiBzZWUgdGhlIGRlY2lzaW9uIGJvdW5kYXJ5IC8gaHlwZXJwbGFuZSBpbiBib3RoIHR3byBhbmQgdGhyZWUgZGltZW5zaW9uYWwgc3BhY2UuPC9kaXY+DQoNCiMhW0hlbGxvIGRvY2tlciB3aGFsZSFdKGhwLnBuZykNCg0KDQo8ZGl2Pk9uZSBvZiB0aGUgbmljZSB0aGluZ3MgYWJvdXQgbWFueSBNTCBhbGdvcml0aG1zIGlzIHlvdSBjYW4gZXh0ZW5kIHRoZW0gaW50byBhcmJpdHJhcnkgZGltZW5zaW9ucyB1c2luZyB0aGUgcG93ZXIgb2YgbGluZWFyIGFsZ2VicmEuIFNvIHlvdSBtaWdodCBoYXZlIDQwMCBjbGFzc2VzLCBhbmQgaWYgc28sIHlvdSBjYW4gY3JlYXRlIGEgMzk5IGRpbWVuc3Npb24gaHlwZXJwbGFuZSwgb3IgZGVjaXNpb24gYm9hcmRhcnkgdGhhdCBzZXBlcmF0ZXMgdGhlbS4gWW91IGNhbiB0aGVuIHByb3ZpZGUgYSBmdXJ0aGVyIGRhdGEgcG9pbnQgdG8geW91ciBtb2RlbCwgYW5kIHlvdXIgU1ZNIHdpbGwgYmUgcHV0IGl0IGluIHRoZSByaWdodCBzcGFjZSBvbiB0aGUgMzk5IGRpbWVuc2lvbnMuIDwvZGl2Pg0KPGJyLz4NCjxkaXY+DQpZb3UgY2FuIGFsc28gZG8gdGhpcyBuZWF0IHRoaW5nIChvZnRlbiBjYWxsZWQgdGhlICJLZXJuYWwgdHJpY2siKSB0byBkZWFsIHdpdGggZGF0YSB0aGF0IGRvZXMgbm90IGxlbmQgaXRzZWxmIHRvIHRoZXNlIHN0cmFpZ2h0IChpLmUuIGxpbmVhcikgd2lkZXN0IHJvYWQgdHlwZSBkZWNpc2lvbiBib3VuZGFyaWVzIChlc3NlbnRpYWxseSBieSBwdXNoaW5nIHRoZSBkYXRhIGludG8gaGlnaGVyIGRpbWVuc2lvbnMpLiBCdXQgZm9yIG5vdywgbGV0cyBrZWVwIHRoaW5ncyByZWFsbHkgc2ltcGxlLCBhbmQganVzdCBzdGFydCB3aXRoIGEgZmV3IGRhdGEgcG9pbnRzIGluIHR3byBkaW1lbnNpb25zIHRoYXQgd2UgY291bGQgZGl2aWRlIHdpdGggYSBsaW5lYXIgKGkuZS4gc3RyYWlnaHQgbGluZSkgU1ZNLg0KPC9kaXY+DQoNCjxkaXY+DQpUbyBleHBsb3JlIHRoaXMsIHRoZSBmaXJzdCB0aGluZyB0byBkbyBpcyBjcmVhdGUgYSBkYXRhIGZyYW1lLiBJdCB3aWxsIGhhdmUgNSByb3dzLCB3aXRoIGVhY2ggcm93IGhhdmluZyB0d28gZmVhdHVyZXMgKGYxIGFuZCBmMiksIGFuZCBhIHRhcmdldC4gTm90ZSB0aGF0IEkgaGF2ZSBhbHNvIHB1dCBzb21ldGhpbmcgaW4gY2FsbGVkIHRoZSA8aT5iaWFzVGVybTwvaT4uIFRoZSBiaWFzIHRlcm1zIGlzIGtpbmQgb2YgY29tcGxpY2F0ZWQgdG8gZXhwbGFpbiBidXQgaXQgZ2l2ZXMgdGhlIHdob2xlIG1vZGVsIGEgbGl0dGxlIG1vcmUgd2lnZ2xlIHJvb20gdG8gbW92ZSBhcyBpdCB0cnlzIHRvIGZpdCB0aGUgYmVzdCBkZWNpc2lvbiBib3VuZGFyeSwgYW5kIGRvZXNuJ3QgY29uc3RyYWluIHRoZSBmdW5jdGlvbiB0byBwYXNzaW5nIHRocm91Z2ggdGhlIG9yaWdpbiAoeW91IG1pZ2h0IGxpa2UgdG8gdGhpbmsgb2YgaXQgYXMgYSB3YXkgdG8gbW92ZSB0aGUgeS1pbnRlY2VwdCBvZiBhIGZ1bmN0aW9uKS4gVGhlIGNvZGUgdG8gYnVpbGQgdGhlIGRhdGEgZnJhbWUgYW5kIHRoZSBmcmFtZSBpdHNlbGYgaXMgbGlzdGVkIGJlbG93Lg0KPC9kaXY+DQoNCg0KYGBge3J9DQphIDwtIGRhdGEuZnJhbWUoYygtMiwgNCwgMSwgMiwgNikpDQpjb2xuYW1lcyhhKSA9ICJmMSINCmEkZjIgPSBjKDQsIDEsIDYsIDQsIDIpDQphJGJpYXNUZXJtID0gYygtMSwgLTEsIC0xLCAtMSwgLTEpDQphJHRhcmdldCA8LSBjKC0xLCAtMSwgMSwgMSwgMSkNCg0KYGBgDQoNCjxici8+DQo8YnIvPg0KPHN0cm9uZz5Tb21lIHNhbXBsZSBkYXRhPC9zdHJvbmc+DQpgYGB7ciBlY2hvID0gRkFMU0UsIHJlc3VsdHM9J2FzaXMnfQ0Ka25pdHI6OmthYmxlKGEsIGNhcHRpb24gPSAiU2FtcGxlIGRhdGEgZm9yIHRoZSBTVk0iKQ0KDQpgYGANCg0KPGRpdj5UbyBwdXQgdGhlIHByb2JsZW0gYXMgc2ltcGx5IGFzIHBvc3NpYmxlLCB0aGUgcXVlc3Rpb24gd2UgYXJlIHJlYWxseSB0cnlpbmcgdG8gc29sdmUgaGVyZSBhc2tzLCA8aT4id2hhdCBhcmUgdGhlIG1vc3Qgb3B0aW1hbCB3ZWlnaHRzIEkgbWlnaHQgdXNlIGZvciBmMSBhbmQgZjIgd2hpY2ggd2lsbCBtZWFuIEkgd2lsbCBwcmVkaWN0IHRoZSB0YXJnZXQgY29ycmVjdGx5PyI8L2k+Lg0KPC9kaXY+DQo8YnIvPg0KPGRpdj5MZXQncyBzdGFydCBieSBsb29raW5nIGF0IHRoZSBwbG90dGVkIHBvaW50cywgZjEsIGFuZCBmMjogPC9kaXY+PGJyLz4NCg0KDQpgYGB7cn0NCmdncGxvdChhLCANCiAgICAgICBhZXMoeCA9IGYxLCANCiAgICAgICAgICAgeSA9IGYyLCANCiAgICAgICAgICAgY29sb3IgPSBmYWN0b3IodGFyZ2V0KSkpICsgDQogICAgICBnZW9tX3BvaW50KCkNCmBgYA0KDQo8ZGl2PkJlZm9yZSB3ZSBzdGFydCB0aGlua2luZyBhYm91dCBjb25zdHJ1Y3RpbmcgYSBvcHRpbWFsIGh5cGVycGxhbmUgb3IgZGVjaXNpb24gYm91bmRhcnksIG5vdGUgdGhhdCB3ZSBjb3VsZCBvZiBjb3Vyc2UganVzdCBwcm92aWRlIGFueSByYW5kb20gZGVjaXNpb24gYm91bmRhcnkgKGkuZS4gYXBwbHkgbm8gbGVhcm5pbmcgYXQgYWxsKS4gSGVyZSB3ZSBhcmUgb25seSBpbiB0d28gZGltZW5zaW9ucywgc28gdGhpcyBpcyB3b3VsZCBiZSBhIG9uZS1kaW1lbnNpb25hbCBoeXBlcnBsYW5lLCBvciBqdXN0IGEgbGluZSB0aGF0IHdlIGNvdWxkIHVzZS4gQW5kIGJlaW5nIHJhbmRvbSwgYXMgdG8gYmUgZXhwZWN0ZWQsIGl0IGlzIG5vdCB2ZXJ5IGdvb2QuIFNvIG91ciBjaGFsbGVuZ2UgaXMgaG93IHRvIG1vdmUgdGhpcyBsaW5lIHNvIGl0IGJlY29tZXMgYW4gb3B0aW1hbCBoeXBlcnBsYW5lIG9yIGRlY2lzaW9uIGJvdW5kYXJ5IGZvciB0aGVzZSBwb2ludHMuDQo8L2Rpdj4NCg0KYGBge3J9DQpnZ3Bsb3QoYSwgDQogICAgICAgYWVzKHggPSBmMSwgDQogICAgICAgICAgIHkgPSBmMiwgDQogICAgICAgICAgIGNvbG9yID0gZmFjdG9yKHRhcmdldCkpKSArIA0KICAgICAgZ2VvbV9wb2ludCgpICsgDQogICAgICBnZW9tX3NlZ21lbnQoYWVzKHggPSAtMi4xLCANCiAgICAgICAgICAgICAgICAgICAgICAgeSA9IC0yLCANCiAgICAgICAgICAgICAgICAgICAgICAgeGVuZCA9IDYsIA0KICAgICAgICAgICAgICAgICAgICAgICB5ZW5kID0gNikpDQoNCg0KYGBgDQoNCjxzdHJvbmc+VGltZSB0byBjaGVjayBvdXQgdGhlIHVuZGVybHlpbmcgTWF0aDwvc3Ryb25nPjxici8+PGJyLz4NCjxkaXY+TGlrZSBtYW55IGFsZ29yaXRobXMgeW91IG1pZ2h0IGZpbmQgaW4gTUwsIHRoZSBiYXNpYyBpZGVhIGhlcmUgaXMgdGhhdCB3ZSB3YW50IHRvIGNyZWF0ZSB3ZWlnaHRzIG9yIGNvLWVmZmljaWVudHMgZm9yIG91ciB2YXJpYWJsZXMsIHRoZW4gY2hlY2sgaG93IHdyb25nIHRoZXkgYXJlLCBhbmQgdGhlbiBpdGVyYXRlIGluIHNvbWUgd2F5IHRvIG1ha2UgaXQgbW9yZSBjb3JyZWN0LiBUbyBkbyB0aGlzLCB3ZSB3aWxsIHVzZSBhIGZldyBtYXRoZW1hdGljYWwgaW5ncmVkaWVudHM6IGEgbG9zcyBmdW5jdGlvbiwgYW4gb2JqZWN0aXZlIGZ1bmN0aW9uLCBhbiBvcHRpbWlzYXRpb24gZnVuY3Rpb24sIGFuZCB3ZSB3aWxsIGFsc28gcHJvdmlkZSBzb21lIHRoaW5ncyB0byB0cmFjayBob3cgb3VyIGZ1bmN0aW9uIGlzIHBlcmZvcm1pbmcgZ2l2ZW4gZGlmZmVyZW50IHdlaWdodHMgb2YgZjEgYW5kIGYyLiANCjxici8+DQo8YnIvPkRldGFpbHMgYXJvdW5kIGVhY2ggb2YgdGhlc2UgYXJlIHByb3ZpZGVkIGJlbG93LjwvZGl2Pg0KPGJyLz4NCg0KDQo8dWw+DQo8bGk+PHN0cm9uZz5Mb3NzIGZ1bmN0aW9uPC9zdHJvbmc+PGJyLz4NCiAgIDxkaXY+SW4gbWFjaGluZSBsZWFybmluZyBpdHMgY3JpdGljYWwgdGhhdCB3ZSBjYW4ga2VlcCB0cmFjayBvZiBob3cgd2VsbCBvdXIgZnVuY3Rpb24gcHJlZGljdHMgb3V0Y29tZXMuIElmIGl0IGlzIGEgYmFkIHByZWRpY3RvciBpdCBoYXMgYSBsb3RzIG9mIGxvc3MsIG9yIGEgaGlnaCBjb3N0IHRvIHVzLiBXZSB3YW50IGxvdyBjb3N0IGFuZCBsb3cgbG9zcy4gVGhlIGMoKSBmdW5jdGlvbiBiZWxvdyAoYW5kIHRoaW5rIG9mIGMgYXMgc3RhbmRpbmcgZm9yICJjb3N0IikgaXMgdGhlIGxvc3MgZnVuY3Rpb24uIEl0IHRha2VzIGludG8gYWNjb3VudCBvdXIgaW5wdXQgdmVjdG9yIChmMSwgZjIgYW5kIGJpYXMgdGVybSksIHRoZSBvdXRwdXQgZnJvbSB0aGUgd2llZ2h0cyB3ZSBoYXZlIGNob3NlbiwgYW5kIHRoZSB0cnVlIG91dHB1dCAodGhlIHRhcmdldCkuIFNvIGJlbG93IHlvdSBjYW4gc2VlIHggYXMgdGhlIHNhbXBsZSBpbnB1dCB2ZWN0b3IsIHkgYXMgdGhlIHRydWUgbGFiZWwgb3IgdGFyZ2V0LCBhbmQgZih4KSB0aGUgcHJlZGljdGVkIGxhYmVsLiBUaGVyZSBhcmUgbWFueSBkaWZmZXJlbnQgbG9zcyBmdW5jdGlvbnMgYW5kIHRoaXMgb25lIGlzIGNhbGxlZCBhICJoaW5nZSIgbG9zcyBmdW5jdGlvbi4gTG9zcyBmdW5jdGlvbnMgdHVybiB1cCBpbiBNTCBhbGdvcml0aG1zIGFzIGEgd2F5IG9mIG1lYXN1cmluZyBwZXJmb3JtYW5jZSB3aGlsZSB3ZSBzZWFyY2ggZm9yIG9wdGltYWwgd2VpZ2h0cy48L2Rpdj4NCiAgICANCiQkICBjKHgsIHksIGYoeCkpID0gDQogICBcbGVmdFx7ICANCiAgIFxiZWdpbnthcnJheX17bH0NCiAgICAwLCANCiAgICBcIFwgXCBcIFwgXCBcIFwgXCBcIFwgXCBcICBcIFwgXCBcICBcIFwgXCANCiAgICBcdGV4dHtpZn0gIFwgeSBcdGltZXMgZih4KSBcZ2V7IDF9IFwgXCBcIFxcDQogICAgMSAtIHkgXHRpbWVzIGYoeCksIA0KICAgIFwgXCBcICAgDQogICAgXHRleHR7ZWxzZX0NCiAgIFxlbmR7YXJyYXl9DQogICBccmlnaHRcfSAkJA0KPC9saT4NCjxsaT48c3Ryb25nPk9iamVjdGl2ZSBmdW5jdGlvbjwvc3Ryb25nPjxici8+DQogICAgPGRpdj5PdXIgb2JqZWN0aXZlIGZ1bmN0aW9uIGlzIHdoYXQgd2UgbmVlZCB0byBnZXQgdG8gdGhlIG9wdGltYWwgaHlwZXJwbGFuZS4gVGhpcyBtZWFucyB0aGF0LCB3aGVuIHdlIG9wdGltc2UgdGhlIGZ1bmN0aW9uIGJlbG93LCBpdCB3aWxsIGdpdmUgdXMgdGhlIGJlc3Qgd2VpZ2h0cyBzbyB3ZSBjYW4gY3JlYXRlIG91ciBoeXBlcnBsYW5lIG9yIGRlY2lzaW9uIGJvdW5kYXJ5LjwvZGl2Pg0KDQokJCBtaW5fd1xsYW1iZGEgfHwgdyB8fF4yICsgDQogICBcc3VtX3tpPTF9Xm4gKDEgLSB5X2kgXGxhbmdsZSB4X2ksIHcgXHJhbmdsZSkgJCQNCiAgIA0KICAgPGRpdj5SaWdodCBhYm91dCBub3cgaW4gbW9zdCBleHBsYW5hdGlvbnMgb2YgU1ZNLCBwZW9wbGUgc3RhcnQgZ2V0dGluZyBjb25mdXNlZC4gV2h5IHdvdWxkIHRoaXMgYmUgYSBmdW5jdGlvbiB0byB1c2UsIHRoYXQgZXhwbGFpbnMgdGhlIHBpY3R1cmUgb2YgdGhlIHByb2JsZW0gYWJvdmU/IFdoeSBpcyBub3Qgc29tZSBvdGhlciBmdW5jdGlvbi4gQW5kIG5vdGljZSB0b28gd2Ugc251Y2sgaW4gYSAkXGxhbWJkYSQgc3ltYm9sLlRPIGRlYWwgZmlyc3QgdGhlIHRoZSAkXGxhbWJkYSQsIHRoaXMgaXMgYSByZWd1bGFyaXphdGlvbiBwYXJhbWV0ZXIgYW5kIHNlcnZlcyBhcyBhIHdheSB0byB0YWtlIGludG8gYWNjb3VudCB0aGUgZGVncmVlIG9mIGltcG9ydGFuY2UgdGhhdCBpcyBnaXZlbiB0byBtaXNjbGFzc2lmaWNhdGlvbnMuIEl0cyBraW5kIG9mIGxpa2UgYSB0dW5pbmcga25vYiB3ZSBjYW4gY2hhbmdlIGR1cmluZyB0aGUgcHJvY2VzcyBvZiBjcmVhdGluZyB0aGUgZGVjaXNpb24gYm91bmRhcmllcywgYW5kIGFsc28gaGVscHMgdXMgY3JlYXRlIGEgbW9kZWwgdGhhdCB3b3JrcyBvbiBib3RoIHRyYWluaW5nIGFuZCB0ZXN0IGRhdGEgd2VsbC4gVGhpcyBpZGVhIHR1cm5zIHVwIGFsb3QgaW4gTUwuIEl0IGFkZHJlc3NlcyB0aGUgZmFjdCB0aGF0IHRoZSBTVk0gaXMgdHJ5aW5nIHRvIGJvdGggbWF4aW1pc2UgdGhlIG1hcmdpbiBiZXR3ZWVuIGJvdGggY2xhc3NlcyBhbmQgbWluaW1pemluZyB0aGUgYW1vdW50IG9mIG1pc2NsYXNzaWZpY2F0aW9ucy4gIDwvZGl2Pg0KICAgDQogICA8YnIvPg0KICAgPGRpdj5Ob3cgaW4gdGVybXMgb2YgcHJlY2lzZWx5IHdoeSB0aGUgZnVuY3Rpb24gd29ya3MsIEkgd2lsbCBkZWZlciB0aGUgZXhwbGFuYXRpb24gdG8gUHJvZmVzc29yIFBhdHJpY2sgV2luc3RvbiBTVk0gZGVyaXZhdGlvbiBpbiBoaXMgYW1hemluZyBNSVQgQXJ0aWZpY2lhbCBJbnRlbGxpZ2VuY2UgQ291cnNlIChpbiBsZWN0dXJlIDE2KSB3aGljaCBpcyBwcm92aWRlZCBiZWxvdy4gV2luc3RvbiBzdGFydHMgZnJvbSBhbiBpbnR1aXRpaW9uIG9mIHRoZSBpbWFnZSBhdCB0aGUgdG9wIG9mIHRoaXMgdHV0b3JpYWwgKHRoZSBpZGVhIG9mIHRoZSBsYXJnZXN0IHN0cmVldCkgYW5kIHRoZW4gZGVyaXZlcyB0aGUgZnVuY3Rpb24gdXNpbmcgc29tZSByZWFsbHkgZWxlZ2FudCBtYXRoZW1hdGljYWwgbW92ZXMuIE5vdGUgYWxzbyB0aGF0IDxhIGhyZWY9Imh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1ZsYWRpbWlyX1ZhcG5payI+VmxhZGltaXIgVmFwbmljazwvYT4gb3JpZ2luYWxseSBjYW1lIHVwIHRoZSBTVk0gYW5kIEkgZW5jb3VyYWdlIHlvdSB0byByZWFkIHVwIGhpbSBhcyBhbiBhbWF6aW5nIHRoaW5rZXIgaW4gdGhpcyBzcGFjZS4gQW5kIHdoZW4geW91IGdvIHRocm91Z2ggYWxsIHRoYXQgbWF0aCwgeW91IHdpbGwgaW5kZWVkIGZpbmQgdGhhdCB0aGlzIGZ1bmN0aW9uIGlzIHRoZSBvbmUgdGhhdCBjYW4gYmUgdXNlZCB0byBjcmVhdGUgYSBvcHRpbWFsIGh5cGVycGxhbmUgb3IgZGVjaXNpb24gYm91bmRhcnkgYmV0d2VlbiBhbiBhcmJpdHJhdHkgbnVtYmVyIGZvIGNsYXNzZXMuIA0KICAgPGJyLz4NCiAgIDxici8+DQogICANCiAgIDxpZnJhbWUgd2lkdGg9IjU2MCIgaGVpZ2h0PSIzMTUiIHNyYz0iaHR0cHM6Ly93d3cueW91dHViZS5jb20vZW1iZWQvX1B3aGlXeEhLOG8iIGZyYW1lYm9yZGVyPSIwIiBhbGxvdz0iYWNjZWxlcm9tZXRlcjsgYXV0b3BsYXk7IGVuY3J5cHRlZC1tZWRpYTsgZ3lyb3Njb3BlOyBwaWN0dXJlLWluLXBpY3R1cmUiIGFsbG93ZnVsbHNjcmVlbj48L2lmcmFtZT4NCiAgIA0KICAgDQogICA8YnIvPg0KICAgPGJyLz4NCiAgIA0KPC9saT4NCjxsaT48c3Ryb25nPk9wdGltaXNpbmcgdGhlIG9iamVjdGl2ZSBmdW5jdGlvbiB0aHJvdWdoIGl0cyBwYXJ0aWFsIGRlcml2YXRpdmVzPC9zdHJvbmc+PGJyLz4NCg0KPGRpdj4NCk5vdyB3aGVuZXZlciB3ZSBzdGFydCB0YWxraW5nIGFib3V0IGlkZWFzIG9mIG9wdGltaXNhdGlvbiwgd2UgZmluZCBvdXJzZWx2ZXMgaGVhZGluZyBpbnRvIHRoZSB3b3JsZCBvZiBjYWxjdWx1cyAodGhlIGJyYW5jaCBvZiBtYXRoIHRoYXQgZXhwbG9yZXMgcmF0ZXMgb2YgY2hhbmdlKS4gWW91IGNhbiBzZWUgdGhhdCBvdXIgb2JqZWN0aXZlIGZ1bmN0aW9uIGNvbnNpc3RzIG9mIHR3byB0ZXJtcy4gVGhlIGZpcnN0IGlzIHRoYXQgcmVndWxhcml6ZXIgdGhpbmcsIGFuZCB0aGUgc2Vjb25kIHRlbGxzIHVzIGFib3V0IHRoZSBsb3NzLiBUaGUgcmVndWxhcml6ZXIgcHJvdmlkZXMgYSBiYWxhbmNlIGJldHdlZW4gbWFyZ2luIG1heGltaXphdGlvbiBhbmQgbG9zcywgdG8gZW5zdXJlIG91ciBoeXBlcnBsYW5lIGlzIG9wdGltYWwgKGkuZS50aGF0IGlzIG1heGltYWxseSBmYXIgYXdheSBmcm9tIGFueSBkYXRhIHBvaW50cykuDQo8L2Rpdj4NCjxici8+DQo8ZGl2PlNvIGxldHMgcHJvdmlkZSBzb21lIGRlcml2YXRpb25zIG9mIHRoZXNlIHRlcm1zLiBJdCB0dXJucyBvdXQgdGhpcyBpcyBub3QgY29tcGxpY2F0ZWQgKGRvaW5nIGRlcml2YXRpdmVzIG9uIHZlY3RvcnMgaXMganVzdCBsaWtlIGRvaW5nIGRlcml2YXRpdmVzIG9uIHNjYWxhcnMsIHdoaWNoIGlzIG9uZSBvZiB0aGUgcmVhbGx5IG5pY2UgdGhpbmdzIGFib3V0IGxpbmVhciBhbGdlYnJhKS4gSGVyZSBpcyB0aGUgZmlyc3Qgb25lOjwvZGl2Pg0KDQokJFxmcmFje1xkZWx0YX17XGRlbHRhIHdfa30gXGxhbWJkYSB8fCB3IHx8XjIgPSAyXGxhbWJkYSB3X2sgJCQNCjxkaXY+QW5kIGhlcmUgaXMgdGhlIHNlY29uZCBvbmU6PC9kaXY+DQoNCg0KJCRcZnJhY3tcZGVsdGF9e1xkZWx0YSB3X2t9DQogICAoMSAtIHlfaSBcbGFuZ2xlIHhfaSwgdyBccmFuZ2xlICkgPSBcbGVmdFx7ICANCiAgIFxiZWdpbnthcnJheX17bH0NCiAgICAwLCANCiAgICAgXCBcIFwgXCBcIFwgXCBcICBcIFwgXCANCiAgICBcdGV4dHtpZn0gIFwgeV9pIFxsYW5nbGUgeF9pLCB3IFxyYW5nbGUgXGdleyAxfSBcIFwgIFxcDQogICAgLSB5X2l4X3tpa30gLCANCiAgICBcIFwgXCAgIA0KICAgIFx0ZXh0e2Vsc2V9DQogICBcZW5ke2FycmF5fQ0KICAgXHJpZ2h0XH0NCiAgICQkDQoNCg0KPC9saT4NCg0KPGxpPg0KPHN0cm9uZz5NaXNjbGFzc2lmaWNhdGlvbiBjb25kaXRpb248L3N0cm9uZz4NCjxici8+DQo8ZGl2PlRoaXMgYSAgY29uZGl0aW9uIHdoaWNoIHdlIGNhbiB0aGluayBvZiBhcyB0cmFja2luZyB0aGUgZnVuY3Rpb24ncyBhYmlsaXR5IHRvIHByZWRpY3QgY29ycmVjdGx5LiBJZiBvdXIgZ2l2ZW4gd2VpZ2h0cyBmb3IgZjEgYW5kIGYyIGFyZSBub3QgZ29vZCwgaXQgd2lsbCBjbGFzc2lmeSB3cm9uZ2x5LCBhbmQgd2Ugd2lsbCBnZXQgYW4gZXJyb3IsIGFuZCB3ZSB3aWxsIHRyYWNrIHRoaXMuIFlvdSB3aWxsIHNlZSB0aGlzIGluIGFjdGlvbiBpbiB0aGUgY29kZSBiZWxvdy48L2Rpdj4gDQoNCiQkIHlfaSBcbGFuZ2xlIHhfaSwgdyBccmFuZ2xlIFxsdCAxICQkDQoNCjwvbGk+DQoNCjxsaT4NCjxzdHJvbmc+QSB3YXkgdG8gdXBkYXRlIHRoZSB3ZWlnaHRzPC9zdHJvbmc+DQo8YnIvPg0KPGRpdj5Zb3Ugd2lsbCBhbHNvIHNlZSB0aGlzIGluIGFjdGlvbiBpbiB0aGUgY29kZSBiZWxvdy4gV2hldGhlciB3ZSBtYWtlIGFuIGVycm9yIG9yIG5vdCwgd2Ugd2lsbCBzdGlsbCB1cGRhdGUgb3VyIHdlaWdodHMsIGJ1dCB3aWxsIHVwZGF0ZSB0aGVtIHNsaWdodGx5IGRpZmZlcmVudGx5IGRlcGVuZGluZyBvbiBpZiB0aGVyZSBpcyBhbiBlcnJvci4gDQoNCiQkIHcgPSB3ICsgXGV0YSAoeV9pIHhfaSAtIDIgXGxhbWJkYSB3ICkgJCQNCjwvbGk+DQo8L3VsPg0KDQo8ZGl2PlNvIHRoZXJlIGlzIHF1aWV0IGEgYml0IGdvaW5nIG9uIGhlcmU6IGEgcmVndWxhcml6ZXIgY29udHJvbHMgdGhlIHRyYWRlIG9mZiBiZXR3ZWVuIHRoZSBhY2hpZXZpbmcgYSBsb3cgdHJhaW5pbmcgZXJyb3IgYW5kIGEgbG93IHRlc3RpbmcgZXJyb3IgdGhhdCBpcyB0aGUgYWJpbGl0eSB0byBnZW5lcmFsaXplIHlvdXIgY2xhc3NpZmllciB0byB1bnNlZW4gZGF0YS4gQXMgYSByZWd1bGl6aW5nIHBhcmFtZXRlciB3ZSBjaG9vc2UgMS9lcG9jaHMsIHNvIHRoaXMgcGFyYW1ldGVyIHdpbGwgZGVjcmVhc2UsIGFzIHRoZSBudW1iZXIgb2YgZXBvY2hzIGluY3JlYXNlcy4gTm90ZSB0aGF0IGEgcmVndWxhcml6ZXIgdG9vIGhpZ2ggd2lsbCBvdmVyZml0IChsYXJnZSB0ZXN0aW5nIGVycm9yKSBhbmQgcmVndWxhcml6ZXIgYmVpbmcgdG9vIGxvdyB3aWxsIHVuZGVyZml0IChsYXJnZSB0cmFpbmluZyBlcnJvcikuPC9kaXY+DQoNCjxici8+DQo8c3Ryb25nPkNvZGU8L3N0cm9uZz48YnIvPg0KYGBgIHtyfQ0Kd2VpZ2h0cyA8LSBiYXNlOjpyZXAoMCwgMykNCmV0YSA9IDENCmVwb2NocyA9IDEwMDAwDQplcnJvcnMgPC0gYygpDQoNCg0KDQpmb3IgKGVwb2NoIGluIDE6ZXBvY2hzKSB7DQogIA0KICANCiAgIyBzZXQgY3VycmVudCBlcnJvciB0byAwDQogIGVycm9yIDwtIDANCiAgDQogICMgUHJpbnQgY3VycmVudCB3ZWlnaHQgdmFsdWVzDQogICMgcHJpbnQoIkN1cnJlbnQgd2VpZ2h0czoiKQ0KICAjIHByaW50KHdlaWdodHMpDQogIA0KDQogIGZvcihpIGluIDE6bnJvdyhhKSkgew0KICAgIA0KICAgIGZlYXR1cmVWZWN0b3IgPC0gYXMuZG91YmxlKGFbaSwgMTozXSkNCiAgICB0YXJnZXRzIDwtIGFzLmRvdWJsZShhJHRhcmdldCkNCg0KICAgIGN1cnJlbnRFcnJvciA8LSB0YXJnZXRzW2ldICogZmVhdHVyZVZlY3RvciAlKiUgd2VpZ2h0cw0KICAgICMgcHJpbnQoIkVycm9yIHdpdGggY3VycmVudCB3ZWlnaHRzOiIpDQogICAgIyBwcmludChjdXJyZW50RXJyb3IgPCAxKQ0KICANCiAgICBpZiAodGFyZ2V0c1tpXSAqIGZlYXR1cmVWZWN0b3IgJSolIHdlaWdodHMgPCAxKSB7DQogICAgICANCiAgICAgICMgbWlzY2xhc3NpZmljYXRpb24sIHNldCBlcnJvcg0KICAgICAgIHdlaWdodHMgPSB3ZWlnaHRzICsgZXRhICogKCAoZmVhdHVyZVZlY3RvciAqIHRhcmdldHNbaV0pICsgKC0yICAqKDEvZXBvY2gpKiB3ZWlnaHRzKSApDQogICAgICAgZXJyb3IgPSAxDQogICAgfSBlbHNlIHsNCiAgICAgICMgY29ycmVjdCBwcmVkaWN0aW9uDQogICAgICB3ZWlnaHRzIDwtIHdlaWdodHMgKyBldGEgKiAoLTIgICooMS9lcG9jaCkqIHdlaWdodHMpDQogICAgDQogICAgfQ0KICAgIA0KICAgIGVycm9ycyA8LSBjKGVycm9ycywgZXJyb3IpDQogICAgDQogIA0KICANCn0gDQoNCn0NCg0KYGBgDQoNCg0KDQo8ZGl2PkZvciBlYWNoIG9mIG91ciBpdGVyYXRpb25zLCBzb21ldGltZXMgd2UgcHJlZGljdGVkIGNvcnJlY3RseSwgYW5kIHNvbWV0aW1lcyBub3QgY29ycmVjdGx5LiBUaGVuIHdlIHVzZWQgb3VyIGRlcml2ZWQgb2JqZWN0aXZlIGZ1bmN0aW9uIHRvIHVwZGF0ZSB0aGUgd2VpZ2h0cyBmb3IgZjEgYW5kIGYyLiBOb3RlIGhvdyBzaW1wbGUgdGhlIGFjdHVhbCBjb2RlIHdhcywgYW5kIHRoaXMgaXMgbGFyZ2VseSBiZWNhdXNlIHRoZSBtYXRoIGl0c2VsZiBpcyBkb2luZyB0aGUgaGVhdnkgbGlmdGluZy4gT25jZSB3ZSBoYXZlIHRoYXQgb2JqZWN0aXZlIGZ1bmN0aW9uLCBpdHMganVzdCBhIG1hdHRlciBvZiBjb2RpbmcgaXQgdXAuIE5vdyBMZXQncyBwbG90IHRoZSBlcnJvcnM8L2Rpdj4NCjxici8+DQoNCjxzdHJvbmc+UGxvdCBvZiBlcnJvcnM8L3N0cm9uZz4NCg0KYGBge3J9DQoNCg0KDQp0IDwtIGRhdGEuZnJhbWUoZXJyb3JzKQ0KDQoNCg0KZ2dwbG90KHQsIA0KICAgICAgIGFlcyh5ID0gZXJyb3JzLCANCiAgICAgICAgICAgeCA9IHNlcSgxOjUwMDAwKSwNCiAgICAgICAgICAgY29sb3IgPSAiZXJyb3JzIg0KICAgICAgICAgKSkgKyANCiAgICAgIGdlb21fcG9pbnQoKQ0KDQpgYGANCjxici8+DQo8ZGl2PkxldCdzIGFsc28gY2hlY2sgb3V0IHRoZSBmaW5hbCB3ZWlnaHRzPC9kaXY+DQoNCmBgYHtyfQ0KDQpwcmludChwYXN0ZSgiT3B0aW1hbCBmMSB3ZWlnaHQ6ICIsIHdlaWdodHNbMV0pKQ0KcHJpbnQocGFzdGUoIk9wdGltYWwgZjIgd2VpZ2h0OiAiLCB3ZWlnaHRzWzJdKSkNCnByaW50KHBhc3RlKCJPcHRpbWFsIGJpYXM6ICIsIHdlaWdodHNbM10pKQ0KDQoNCmBgYA0KDQoNCjxici8+DQo8c3Ryb25nPlNvIHdoZXJlIHRvIG5leHQ/PC9zdHJvbmc+DQo8YnIvPjxici8+DQo8ZGl2PldlIGNvdmVyZWQgYWxvdCBvZiBpZGVhcyBoZXJlIGJ1dCBpdCBzaG91bGQgYXQgbGVhc3Qgc2VydmUgdG8gZ2l2ZSBhIGJpdCBvZiBhbiBpbnRybyBpbnRvIHNvbWUgb2YgdGhlIG1haW4gaWRlYXMgdGhhdCBjb21lIHVwIGFsb3QgaW4gbWFjaGluZSBsZWFybmluZy4gVG8gZ2V0IGludG8gaXQgZnVydGhlciBoZXJlIGFyZSBzb21lIG5leHQgc3RlcHM6IDwvZGl2Pg0KPGJyLz4NCjx1bD4NCjxsaT5EYXRhIGNhbXAgaGFzIGEgY29vbCA8YSBocmVmPSJodHRwczovL3d3dy5kYXRhY2FtcC5jb20vY29tbXVuaXR5L3R1dG9yaWFscy9zdXBwb3J0LXZlY3Rvci1tYWNoaW5lcy1yIj5TVk0gdHV0b3JpYWw8L2E+IHVzaW5nIHRoZSBFMTA3MSBwYWNrYWdlLiBKdXN0IHBhc3RlIGluIHRoZSBjb2RlIHRvIGdldCBhIGZ1cnRoZXIgaW50dWl0aW9uIGhvdyBpdCB3b3Jrcy4gVGhleSBhbHNvIGV4dGVuZCB0aGUgaWRlYSBpbnRvIG5vbi1saW5lYXIgU1ZNcyB3aGljaCBhcmUsIG5vdCBkaWZmaWN1bHQgdG8gaW1wbG1lbnQgYnV0IGdpdmUgeW91IGFsb3QgbW9yZSBmbGV4aWJpbGl0eSB3aGVuIHlvdSBjbGFzc2VzIGFyZSBub3Qgc2VwZXJhYmxlIGluIGEgbGluZWFyIGZhc2hpb248L2xpPg0KPGJyLz4NCjxsaT5IZXJlIGlzIDxhIGhyZWY9Imh0dHA6Ly9kYXRhYXNwaXJhbnQuY29tLzIwMTcvMDEvMTkvc3VwcG9ydC12ZWN0b3ItbWFjaGluZS1jbGFzc2lmaWVyLWltcGxlbWVudGF0aW9uLXItY2FyZXQtcGFja2FnZS8iPmFub3RoZXIgdHV0b3JpYWw8L2E+IHRoYXQgaW1wbGVtZW50cyBTVk1zIHVzaW5nIHRoZSBwb3B1bGFyIE1MIGxpYnJhcnkgaW4gUiwgY2FyZXQsIHVzaW5nIG1lZGljYWwgZGF0YSB0aGF0IHByZWRpY3RzIGhlYXJ0IGRpc2Vhc2UuIEFnYWluLCBqdXN0IHBhc3RlIGluIHRvIGdldCBzb21lIGludHVpdGl0aW9uczwvbGk+DQo8YnIvPg0KPGxpPllvdSB3aWxsIHNvb24gbm90aWNlIHRoYXQgaW4gTUwsIHRoaW5ncyBzZWVtIGxpa2UgYSBiaXQgb2YgYSBibGFjayBib3guIFlvdSBwdXQgeW91ciBmZWF0dXJlcyBhbmQgdGFyZ2V0cyBpbiBzb21lIGtpbmQgb2YgTUwgbGlicmFyeSBmdW5jdGlvbi4gUHJvY2VlZGluZyBpbiB0aGlzIHdheSBtZWFucyB0aGF0IHlvdSBtYXkgYmUgYWJsZSB0byBkbyBjb29sIHN0dWZmLCBidXQgaXRzIHJpc2t5IGFzIHlvdSBkb24ndCByZWFsbHkga25vdyBob3cgaXQgd29ya3MuIFRvIGdldCBhcm91bmQgdGhpcywgZ2V0IHlvdXIgbWF0aCB1cCB0byBkYXRlLiBJIHJlY29tbWVuZCBzdGFydGluZyB3aXRoIHRoZSBLaGFuIEFjYWRlbXkgcGxheWxpc3Qgb24gbGluZWFyIGFsZ2VicmEsIGNhbGN1bHVzIGFuZCBwcm9iYWJpbGl0eSBhbmQgZ28gZnJvbSB0aGVyZS4gPC9saT48YnIvPg0KDQo8bGk+VGhpcyB0dXRvcmlhbCBzdGFydGVkIG91dCBhcyBtZSBqdXN0IGJ1aWxkaW5nIGFuIFIgdmVyc2lvbiBvZiA8YSAgaHJlZj0iaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1nOEQ1WUw2Y09TRSZ2bD1lbiI+YW5vdGhlciB0dXRvcmlhbDwvYT4gZnJvbSBSYXZlbCBTaXJhaiB3aG8gcHJlc2VudHMgYSBzaW1pbGlhciBpZGVhIGluIFB5dGhvbiAoYWxsIGhpcyBzdHVmZiBpcyBncmVhdCBzbyBiZSBzdXJlIHRvIGNoZWNrIGl0IG91dCkuIEl0IGlzIG9mIGNvdXJzZSBncmVhdCB0byBrbm93IGhvdyB0byBkbyB0aGlzIGluIFB5dGhvbiAoYW5kIHlvdSBjYW4gYWxzbyB1c2UgdGhlIHNjaWtpdGxlYXJuIGxpcmFyeSksIGFuZCBhIGdyZWF0IHdheSB0byBsZWFybiBhYm91dCBNTCBpcyBjb252ZXJ0IFB5dGhvbiBqdXB5dGVyIG5vdGVib29rIHR1dG9yaWFscyB0byBSIE1hcmtkb3duPC9saT4NCjxici8+DQo8bGk+DQpGaW5hbGx5LCBjaGVjayBvdXQgdGhpcyBhbWF6aW5nIGxlY3R1cmUgZnJvbSBWbGFkaW1pciBWYXBuaWNrLCB3aG8gY2FtZSB1cCB3aXRoIHRoZSBpZGVhIG9mIFNWTXMgaW4gdGhlIGVhcmx5IDkwcy4gSGUgaXMgYW4gYW1hemluZyBtYXRoZW1hdGljaWFuIGFuZCB0aGlua2VyIGFuZCB0aGlzIGxlY3R1cmUgY292ZXJzIGEgdGhpcyB0aGluZyB3aGljaCBoZSBjYWxscywgIlNWTSBQbHVzIi4gVGhlIGZpcnN0IDIwIG1pbnV0ZXMgb3Igc28gb2YgbWF0aCBpcyBoYXJkIGdvaW5nLCBidXQgc3RpY2sgd2l0aCBpdCBhcyBpdHMgYSBtaW5kIGJsb3dpbmcgbGVjdHVyZS4NCjwvbGk+DQo8YnIvPg0KDQoNCjxpZnJhbWUgd2lkdGg9IjU2MCIgaGVpZ2h0PSIzMTUiIHNyYz0iaHR0cHM6Ly93d3cueW91dHViZS5jb20vZW1iZWQvWVJ0Zktvc1BIZDAiIGZyYW1lYm9yZGVyPSIwIiBhbGxvdz0iYWNjZWxlcm9tZXRlcjsgYXV0b3BsYXk7IGVuY3J5cHRlZC1tZWRpYTsgZ3lyb3Njb3BlOyBwaWN0dXJlLWluLXBpY3R1cmUiIGFsbG93ZnVsbHNjcmVlbj48L2lmcmFtZT4NCg0KDQoNCjwvdWw+