Day 2 Schedule

  1. Further customisation of plots
  2. Statistics
  3. Data Manipulation Techniques
  4. Programming in R
  5. Further report writing

1. Further customisation of plots

Recap

  • We have seen how to use plot(), boxplot() , hist() etc to make simple plots
  • These come with arguments that can be used to change the appearance of the plot
    • col, pch
    • main, xlab, ylab
    • etc….
  • We will now look at ways to modify the plot appearance after it has been created
  • Also, how to export the graphs

The painter’s model

  • R employs a painter’s model to construct it’s plots
  • Elements of the graph are added to the canvas one layer at a time, and the picture built up in levels.
  • Lower levels are obscured by higher levels,
    • allowing for blending, masking and overlaying of objects.
  • You can’t undo the changes you make to the plot
    • you have to re-run the code from scratch

http://www.inquisitr.com/309687/jesus-painting-restoration-goes-wrong-well-intentioned-old-lady-destroys-100-year-old-fresco/

  • We will re-use the patients data from yesterday:
patients <- read.delim("patient-info.txt")
  • Recall our patients dataset from yesterday
    • we might want to display other characteristics on the plot, e.g. gender of individual:
plot(patients$Height, patients$Weight, pch=16)

The points function

  • points() can be used to set of points to an existing plot
  • It requires a vector of x and y coordinates
    • These do not have to be the same length as the number of points in the initial plot:
      • Hence we can use points() to highlight observations
      • …or add a set of new observations
plot(patients$Height, patients$Weight, pch=16)
points(160,90, pch="X")

  • Note that axis limits of the existing plot are not altered
  • Often it is useful to create a blank ‘canvas’ with the correct labels and limits
plot(patients$Height, patients$Weight, type="n")

Adding points to differentiate gender

  • Selecting males using the == comparison we saw yesterday
    • Gives a TRUE or FALSE value
    • Can be used to index the data frame
    • Which means we can get the relevant Age and Weight values
males <- patients$Sex == "Male"
males
  [1]  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE  TRUE FALSE
 [15] FALSE  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE FALSE FALSE  TRUE  TRUE  TRUE
 [29]  TRUE  TRUE FALSE  TRUE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE
 [43] FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
 [57] FALSE FALSE  TRUE  TRUE FALSE  TRUE FALSE  TRUE  TRUE FALSE  TRUE FALSE  TRUE FALSE
 [71] FALSE  TRUE  TRUE FALSE  TRUE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE
 [85] FALSE FALSE FALSE  TRUE FALSE  TRUE  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE
 [99] FALSE FALSE
males
patients[males,]
patients$Age[males]
patients$Weight[males]
  • The points we add have to be within the x and y limits of the original plot axes, otherwise they won’t be displayed
    • R won’t give a warning or error if you attempt to plot points outside the axis limits
plot(patients$Height, patients$Weight, type="n")
points(patients$Height[males], patients$Weight[males], 
       pch=16, col="steelblue")

We can do the same for Females

females <- patients$Sex == "Female"
females
  [1] FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE FALSE FALSE  TRUE  TRUE FALSE  TRUE
 [15]  TRUE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE  TRUE FALSE FALSE FALSE
 [29] FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE
 [43]  TRUE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE
 [57]  TRUE  TRUE FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE  TRUE FALSE  TRUE FALSE  TRUE
 [71]  TRUE FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE  TRUE  TRUE  TRUE  TRUE FALSE  TRUE
 [85]  TRUE  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE  TRUE  TRUE  TRUE  TRUE
 [99]  TRUE  TRUE
patients[females,]
  • Again, we have to be careful that all the points are within the x and y limits
    • this is why creating the blank plot containing the limits of the data is useful
plot(patients$Height, patients$Weight, type="n")
points(patients$Height[males], patients$Weight[males],
       pch=16, col="steelblue")
points(patients$Height[females], patients$Weight[females],
       pch=16, col="orangered1")

  • Each set of points can have a different colour and shape
  • Axis labels and title and limits are defined by the plot
  • Once you’ve added points to a plot, they cannot be removed
plot(patients$Height, patients$Weight, type="n")
points(patients$Height[males], patients$Weight[males],
       pch=16, col="steelblue")
points(patients$Height[females], patients$Weight[females],
       pch=17, col="orangered1",
        xlab="Age of Patient",
       ylab="Weight",
       main="Relationship between Age and Weights")

## The arguments xlab, ylab, main in the points functions are not used
## Need to specify these labels when you create the plot initially

Adding a legend

  • Should also add a legend to help interpret the plot
    • use the legend function
    • can give x and y coordinates where legend will appear
    • also recognises shortcuts such as topleft and bottomright
plot(patients$Height, patients$Weight, type="n")
points(patients$Height[males], patients$Weight[males], 
       pch=16, col="steelblue")
points(patients$Height[females], patients$Weight[females],
       pch=17, col="orangered1")
legend("topleft", legend=c("M","F"), 
       col=c("steelblue","orangered1"), pch=c(16,17))

Adding text

  • Text can also be added to a plot in a similar manner
    • the labels argument specifies the text we want to add
plot(patients$Height, patients$Weight, pch=16)
  text(patients$Height, patients$Weight, labels=patients$Race)

  • In the above we used to same co-ordinates from the original plot to place the text
  • We can use arguments pos or adj to offset the positions of the labels
    • pos can be 1, 2, 3, 4 for below, left, above, right
    • adj is an adjustment in the range 0 to 1
plot(patients$Height, patients$Weight, pch=16)
  text(patients$Height, patients$Weight, labels=patients$Race,pos = 3)

plot(patients$Height, patients$Weight, pch=16)
  text(patients$Height, patients$Weight, labels=patients$Race,adj =0.1)

  • To aid our interpretation, it is often helpful to add guidelines
    • grid() is one easy way of doing this:
plot(patients$Height, patients$Weight, pch=16)
grid(col="steelblue")

  • Can also add lines that intersect the axes:
    • v = for vertical lines
    • h = for horizontal
    • can specify multiple lines in a vector
plot(patients$Height, patients$Weight, pch=16)
abline(v=160, col="red")
abline(h=c(65,70,75), col="blue")

Plot layouts

  • The par function can be used specify the appearance of a plot
  • The settings persist until the plot is closed with dev.off()
  • ?par and scroll to graphical parameters
  • One example is mfrow:
    • “multiple figures per row”
    • needs to be a vector of rows and columns:
      • e.g. a plot with one row and two columns par(mfrow=c(1,2))
      • don’t need the same kind of plot in each cell
par(mfrow=c(1,2))
plot(patients$Height, patients$Weight, pch=16)
boxplot(patients$Weight ~ patients$Sex)

  • See also mar for setting the margins:
    • par(mar=c(...))

Exporting graphs from RStudio

  • You can use the pdf() function to export one or more plots to a pdf file:
    • You will see that the plot does not appear in RStudio
  • You need to use the dev.off() to stop printing graphs to the pdf and ‘close’ the file
    • It allows you to create a pdf document with multiple pages
pdf("ExampleGraph.pdf")
boxplot(patients$Weight ~ patients$Sex)
dev.off()
null device 
          1 
  • pdf is a good choice for publication as they can be imported into Photoshop, Inkscape, etc.
    • Sometimes it is easier to edit in these tools than R!
    • If it is taking too long to customise a plot in R, consider if you should be using one of these tools instead
  • To save any graph you have created to a pdf, repeat the code you used to create the plot with pdf() before and dev.off() afterwards
    • you can have as many lines of code in-between as you like
    • each new plot will be written to a different page
pdf("mygraph.pdf")
plot(patients$Height, patients$Weight, pch=16)
abline(v=40, col="red")
abline(h=c(65,70,75), col="blue")
boxplot(patients$Weight ~ patients$Sex)
x <- 1:10
y <- x^2 + 4*x
plot(x,y)
dev.off()
null device 
          1 
  • If no plots are appearing in RStudio, it could be you are still writing to a pdf file
    • run dev.off() multiple times until you see a message cannot shut down device (the null device)
  • We can specify the dimensions of the plot, and other properties of the file (?pdf)
pdf("ExampleGraph.pdf", width=10, height=5)
boxplot(patients$Weight ~ patients$Sex)
dev.off()
null device 
          1 
  • Other formats can be created:
    • e.g. png, or others ?jpeg
    • more appropriate for email, presentations, web page
png("ExampleGraph.png")
boxplot(patients$Weight ~ patients$Sex)
dev.off()
null device 
          1 

Exercise: Exercise 5a

  • Return to the weather data from yesterday:
weather <- read.csv("ozone.csv")
  • Using the par function, create a layout with three columns
  • Plot Ozone versus Solar Radiation, Wind Speed and Temperature on separate graphs
    • use different colours and plotting characters on each plot
  • Save the plot to a pdf
  • HINT: Create the graph first in RStudio. When you’re happy with it, re-run the code preceeded by the pdf function to save to a file
    • don’t forget to use dev.off() to close the file
### Your Answer Here ###

Exercise: Exercise 5b

  • Temperature and Ozone level seem to be correlated
  • However, there are some observations that do not seem to fit the trend
    • those with Ozone level > 100
  • Modify the plot so that these outlier observations are in a different colour
  • Add a legend to help interpret the plot

HINT: You can break down the problem into the following steps

  • Create a blank plot
  • Identify observations with ozone > 100
    • plot the corresponding Temperature and Ozone values for these in red
  • Identify observations with ozone < 100
    • plot the corresponding Temperature and Ozone values for these in orange
  • Can you modify your code so that the cut-off is not hard-coded (fixed) to 100
    • e.g. Ozone > 80, Ozone, 90 etc…
    • can you re-generate the plots the minimal changes to the code
### Your Answer Here ###

An alternative, and equally-valid, solution involves creating a vector of colours which will either be red or orange depending whether on the particular value of ozone is an outlier

  • we can use the rep function to create a vector of the required length for the entire dataset
    • one vector for plotting colours, and another vector for plotting characters
weather <- read.csv("ozone.csv")
mycol <-rep("orange",nrow(weather))
mypch <- rep(17, nrow(weather))
  • now use a logical expression to identify the high ozone level, and replace the corresponding entries in the colour and plotting character vectors
highO <- which(weather$Ozone > 100)
mycol[highO] <- "red"
mypch[highO] <- 18
  • creating the plot can now be done with a single plot command
plot(weather$Temp,weather$Ozone, 
     col=mycol, pch=mypch,ylab="Ozone level",
     xlab="Temperature")
abline(h=100,lty=2)
legend("topleft", legend = c("Ozone > 100","Normal Ozone"),col=c("red","orange"),pch=17)

Other plotting features

We can choose not to show the x- and y-axis in the initial plot

plot(patients$Age, patients$Weight, pch=16,axes=FALSE)

They can be added afterwards with the axis function - first argument is whether the axis appears on the bottom (1), left (2), top (3) or right (4) - can also define where the tick marks are located, and the labels to display - the box function can be used to enclose the plot in a box

plot(patients$Age, patients$Weight, pch=16,axes=FALSE)
axis(1)
axis(4, at = c(65,75,85,95),labels = c("65kg","75kg","85kg","95kg"))
box()

Ordering the boxes in a set of boxplots

N.B. The order in which the boxes are displayed on a boxplot isn’t something that can be controlled by graphical paramaters

  • the order is derived from the order of the categories
    • in this case Female comes first alphabetically
boxplot(patients$Weight~patients$Sex)

  • a different order can be achieved by defining a new factor where the levels are in the order you want
patients$Sex <- factor(patients$Sex,levels = c("Male","Female"))
boxplot(patients$Weight~patients$Sex)

Need inspiration?

The R Graph Gallery has lots of examples (and code) showing what can be achieved with R graphics

LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIFNvbHZpbmcgQmlvbG9naWNhbCBQcm9ibGVtcyBVc2luZyBSIC0gRGF5IDIiCmF1dGhvcjogTWFyayBEdW5uaW5nLCBTdXJhaiBNZW5vbiBhbmQgQWlvcmEgWmFiYWxhLiBPcmlnaW5hbCBtYXRlcmlhbCBieSBSb2JlcnQgU3Rvam5pxIcsCiAgTGF1cmVudCBHYXR0bywgUm9iIEZveSwgSm9obiBEYXZleSwgRMOhdmlkIE1vbG7DoXIgYW5kIElhbiBSb2JlcnRzCmRhdGU6ICdgciBmb3JtYXQoU3lzLnRpbWUoKSwgIkxhc3QgbW9kaWZpZWQ6ICVkICViICVZIilgJwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwotLS0KCiMgRGF5IDIgU2NoZWR1bGUKCjEuIEZ1cnRoZXIgY3VzdG9taXNhdGlvbiBvZiBwbG90cwoyLiBTdGF0aXN0aWNzCjMuIERhdGEgTWFuaXB1bGF0aW9uIFRlY2huaXF1ZXMKNC4gUHJvZ3JhbW1pbmcgaW4gUgo1LiBGdXJ0aGVyIHJlcG9ydCB3cml0aW5nCgojMS4gRnVydGhlciBjdXN0b21pc2F0aW9uIG9mIHBsb3RzCgojIyBSZWNhcAoKLSBXZSBoYXZlIHNlZW4gaG93IHRvIHVzZSBgcGxvdCgpYCwgYGJveHBsb3QoKWAgLCBgaGlzdCgpYCBldGMgdG8gbWFrZSBzaW1wbGUgcGxvdHMKLSBUaGVzZSBjb21lIHdpdGggYXJndW1lbnRzIHRoYXQgY2FuIGJlIHVzZWQgdG8gY2hhbmdlIHRoZSBhcHBlYXJhbmNlIG9mIHRoZSBwbG90CiAgICArIGBjb2xgLCBgcGNoYAogICAgKyBgbWFpbmAsIGB4bGFiYCwgYHlsYWJgCiAgICArIGV0Yy4uLi4KLSBXZSB3aWxsIG5vdyBsb29rIGF0IHdheXMgdG8gbW9kaWZ5IHRoZSBwbG90IGFwcGVhcmFuY2UgYWZ0ZXIgaXQgaGFzIGJlZW4gY3JlYXRlZAotIEFsc28sIGhvdyB0byBleHBvcnQgdGhlIGdyYXBocyAKCgoKIyMgVGhlIHBhaW50ZXIncyBtb2RlbAoKLSBSIGVtcGxveXMgYSBwYWludGVyJ3MgbW9kZWwgdG8gY29uc3RydWN0IGl0J3MgcGxvdHMKLSBFbGVtZW50cyBvZiB0aGUgZ3JhcGggYXJlIGFkZGVkIHRvIHRoZSBjYW52YXMgb25lIGxheWVyIGF0IGEgdGltZSwgYW5kIHRoZSBwaWN0dXJlIGJ1aWx0IHVwIGluIGxldmVscy4KLSBMb3dlciBsZXZlbHMgYXJlIG9ic2N1cmVkIGJ5IGhpZ2hlciBsZXZlbHMsIAogICAgKyBhbGxvd2luZyBmb3IgYmxlbmRpbmcsIG1hc2tpbmcgYW5kIG92ZXJsYXlpbmcgb2Ygb2JqZWN0cy4KLSBZb3UgY2FuJ3QgdW5kbyB0aGUgY2hhbmdlcyB5b3UgbWFrZSB0byB0aGUgcGxvdAogICAgKyB5b3UgaGF2ZSB0byByZS1ydW4gdGhlIGNvZGUgZnJvbSBzY3JhdGNoCiAgICAKIVtdKGh0dHA6Ly9jZG4uaW5xdWlzaXRyLmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxMi8wOC9qZXN1cy1jaHJpc3QtZnJlc2NvLmdpZikKCltodHRwOi8vd3d3LmlucXVpc2l0ci5jb20vMzA5Njg3L2plc3VzLXBhaW50aW5nLXJlc3RvcmF0aW9uLWdvZXMtd3Jvbmctd2VsbC1pbnRlbnRpb25lZC1vbGQtbGFkeS1kZXN0cm95cy0xMDAteWVhci1vbGQtZnJlc2NvL10oaHR0cDovL3d3dy5pbnF1aXNpdHIuY29tLzMwOTY4Ny9qZXN1cy1wYWludGluZy1yZXN0b3JhdGlvbi1nb2VzLXdyb25nLXdlbGwtaW50ZW50aW9uZWQtb2xkLWxhZHktZGVzdHJveXMtMTAwLXllYXItb2xkLWZyZXNjby8pCgoKLSBXZSB3aWxsIHJlLXVzZSB0aGUgcGF0aWVudHMgZGF0YSBmcm9tIHllc3RlcmRheToKCmBgYHtyfQpwYXRpZW50cyA8LSByZWFkLmRlbGltKCJwYXRpZW50LWluZm8udHh0IikKYGBgCgotIFJlY2FsbCBvdXIgcGF0aWVudHMgZGF0YXNldCBmcm9tIHllc3RlcmRheQogICAgKyB3ZSBtaWdodCB3YW50IHRvIGRpc3BsYXkgb3RoZXIgY2hhcmFjdGVyaXN0aWNzIG9uIHRoZSBwbG90LCBlLmcuIGdlbmRlciBvZiBpbmRpdmlkdWFsOgoKYGBge3J9CnBsb3QocGF0aWVudHMkSGVpZ2h0LCBwYXRpZW50cyRXZWlnaHQsIHBjaD0xNikKYGBgCgojI1RoZSBwb2ludHMgZnVuY3Rpb24KCi0gYHBvaW50cygpYCBjYW4gYmUgdXNlZCB0byBzZXQgb2YgcG9pbnRzIHRvIGFuICpleGlzdGluZyogcGxvdAotIEl0IHJlcXVpcmVzIGEgdmVjdG9yIG9mIHggYW5kIHkgY29vcmRpbmF0ZXMKICAgICsgVGhlc2UgZG8gbm90IGhhdmUgdG8gYmUgdGhlIHNhbWUgbGVuZ3RoIGFzIHRoZSBudW1iZXIgb2YgcG9pbnRzIGluIHRoZSBpbml0aWFsIHBsb3Q6CiAgICAgICAgKyBIZW5jZSB3ZSBjYW4gdXNlIGBwb2ludHMoKWAgdG8gaGlnaGxpZ2h0IG9ic2VydmF0aW9ucwogICAgICAgICsgLi4ub3IgYWRkIGEgc2V0IG9mIG5ldyBvYnNlcnZhdGlvbnMKYGBge3IgfQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCBwY2g9MTYpCnBvaW50cygxNjAsOTAsIHBjaD0iWCIpCmBgYAoKLSBOb3RlIHRoYXQgYXhpcyBsaW1pdHMgb2YgdGhlIGV4aXN0aW5nIHBsb3QgYXJlIG5vdCBhbHRlcmVkCi0gT2Z0ZW4gaXQgaXMgdXNlZnVsIHRvIGNyZWF0ZSBhIGJsYW5rICdjYW52YXMnIHdpdGggdGhlIGNvcnJlY3QgbGFiZWxzIGFuZCBsaW1pdHMKCmBgYHtyfQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCB0eXBlPSJuIikKYGBgCgojIyBBZGRpbmcgcG9pbnRzIHRvIGRpZmZlcmVudGlhdGUgZ2VuZGVyCgotIFNlbGVjdGluZyBtYWxlcyB1c2luZyB0aGUgKipgPT1gKiogY29tcGFyaXNvbiB3ZSBzYXcgeWVzdGVyZGF5CiAgICArIEdpdmVzIGEgYFRSVUVgIG9yIGBGQUxTRWAgdmFsdWUKICAgICsgQ2FuIGJlIHVzZWQgdG8gaW5kZXggdGhlIGRhdGEgZnJhbWUKICAgICsgV2hpY2ggbWVhbnMgd2UgY2FuIGdldCB0aGUgcmVsZXZhbnQgQWdlIGFuZCBXZWlnaHQgdmFsdWVzCmBgYHtyfQptYWxlcyA8LSBwYXRpZW50cyRTZXggPT0gIk1hbGUiCm1hbGVzCmBgYAoKYGBge3IsIGV2YWw9RkFMU0V9Cm1hbGVzCnBhdGllbnRzW21hbGVzLF0KcGF0aWVudHMkQWdlW21hbGVzXQpwYXRpZW50cyRXZWlnaHRbbWFsZXNdCmBgYAoKCgotIFRoZSBwb2ludHMgd2UgYWRkIGhhdmUgdG8gYmUgd2l0aGluIHRoZSBgeGAgYW5kIGB5YCBsaW1pdHMgb2YgdGhlIG9yaWdpbmFsIHBsb3QgYXhlcywgb3RoZXJ3aXNlIHRoZXkgd29uJ3QgYmUgZGlzcGxheWVkCiAgICArIFIgd29uJ3QgZ2l2ZSBhIHdhcm5pbmcgb3IgZXJyb3IgaWYgeW91IGF0dGVtcHQgdG8gcGxvdCBwb2ludHMgb3V0c2lkZSB0aGUgYXhpcyBsaW1pdHMKCmBgYHtyfQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCB0eXBlPSJuIikKcG9pbnRzKHBhdGllbnRzJEhlaWdodFttYWxlc10sIHBhdGllbnRzJFdlaWdodFttYWxlc10sIAogICAgICAgcGNoPTE2LCBjb2w9InN0ZWVsYmx1ZSIpCgpgYGAKCgpXZSBjYW4gZG8gdGhlIHNhbWUgZm9yIEZlbWFsZXMKICAgIApgYGB7cn0KZmVtYWxlcyA8LSBwYXRpZW50cyRTZXggPT0gIkZlbWFsZSIKZmVtYWxlcwpwYXRpZW50c1tmZW1hbGVzLF0KYGBgCgotIEFnYWluLCB3ZSBoYXZlIHRvIGJlIGNhcmVmdWwgdGhhdCBhbGwgdGhlIHBvaW50cyBhcmUgd2l0aGluIHRoZSBgeGAgYW5kIGB5YCBsaW1pdHMKICAgICsgdGhpcyBpcyB3aHkgY3JlYXRpbmcgdGhlIGJsYW5rIHBsb3QgY29udGFpbmluZyB0aGUgbGltaXRzIG9mIHRoZSBkYXRhIGlzIHVzZWZ1bAoKYGBge3J9CnBsb3QocGF0aWVudHMkSGVpZ2h0LCBwYXRpZW50cyRXZWlnaHQsIHR5cGU9Im4iKQpwb2ludHMocGF0aWVudHMkSGVpZ2h0W21hbGVzXSwgcGF0aWVudHMkV2VpZ2h0W21hbGVzXSwKICAgICAgIHBjaD0xNiwgY29sPSJzdGVlbGJsdWUiKQpwb2ludHMocGF0aWVudHMkSGVpZ2h0W2ZlbWFsZXNdLCBwYXRpZW50cyRXZWlnaHRbZmVtYWxlc10sCiAgICAgICBwY2g9MTYsIGNvbD0ib3JhbmdlcmVkMSIpCgpgYGAKCi0gRWFjaCBzZXQgb2YgcG9pbnRzIGNhbiBoYXZlIGEgZGlmZmVyZW50IGNvbG91ciBhbmQgc2hhcGUKLSBBeGlzIGxhYmVscyBhbmQgdGl0bGUgYW5kIGxpbWl0cyBhcmUgZGVmaW5lZCBieSB0aGUgcGxvdAotIE9uY2UgeW91J3ZlIGFkZGVkIHBvaW50cyB0byBhIHBsb3QsIHRoZXkgY2Fubm90IGJlIHJlbW92ZWQKCgpgYGB7ciB9CnBsb3QocGF0aWVudHMkSGVpZ2h0LCBwYXRpZW50cyRXZWlnaHQsIHR5cGU9Im4iKQpwb2ludHMocGF0aWVudHMkSGVpZ2h0W21hbGVzXSwgcGF0aWVudHMkV2VpZ2h0W21hbGVzXSwKICAgICAgIHBjaD0xNiwgY29sPSJzdGVlbGJsdWUiKQpwb2ludHMocGF0aWVudHMkSGVpZ2h0W2ZlbWFsZXNdLCBwYXRpZW50cyRXZWlnaHRbZmVtYWxlc10sCiAgICAgICBwY2g9MTcsIGNvbD0ib3JhbmdlcmVkMSIsCiAgICAgICAgeGxhYj0iQWdlIG9mIFBhdGllbnQiLAogICAgICAgeWxhYj0iV2VpZ2h0IiwKICAgICAgIG1haW49IlJlbGF0aW9uc2hpcCBiZXR3ZWVuIEFnZSBhbmQgV2VpZ2h0cyIpCgojIyBUaGUgYXJndW1lbnRzIHhsYWIsIHlsYWIsIG1haW4gaW4gdGhlIHBvaW50cyBmdW5jdGlvbnMgYXJlIG5vdCB1c2VkCiMjIE5lZWQgdG8gc3BlY2lmeSB0aGVzZSBsYWJlbHMgd2hlbiB5b3UgY3JlYXRlIHRoZSBwbG90IGluaXRpYWxseQoKYGBgCgoKCgojIyBBZGRpbmcgYSBsZWdlbmQKCi0gU2hvdWxkIGFsc28gYWRkIGEgbGVnZW5kIHRvIGhlbHAgaW50ZXJwcmV0IHRoZSBwbG90CiAgICArIHVzZSB0aGUgYGxlZ2VuZGAgZnVuY3Rpb24KICAgICsgY2FuIGdpdmUgeCBhbmQgeSBjb29yZGluYXRlcyB3aGVyZSBsZWdlbmQgd2lsbCBhcHBlYXIKICAgICsgYWxzbyByZWNvZ25pc2VzIHNob3J0Y3V0cyBzdWNoIGFzICoqKnRvcGxlZnQqKiogYW5kICoqKmJvdHRvbXJpZ2h0KioqLi4uCgpgYGB7cn0KcGxvdChwYXRpZW50cyRIZWlnaHQsIHBhdGllbnRzJFdlaWdodCwgdHlwZT0ibiIpCnBvaW50cyhwYXRpZW50cyRIZWlnaHRbbWFsZXNdLCBwYXRpZW50cyRXZWlnaHRbbWFsZXNdLCAKICAgICAgIHBjaD0xNiwgY29sPSJzdGVlbGJsdWUiKQpwb2ludHMocGF0aWVudHMkSGVpZ2h0W2ZlbWFsZXNdLCBwYXRpZW50cyRXZWlnaHRbZmVtYWxlc10sCiAgICAgICBwY2g9MTcsIGNvbD0ib3JhbmdlcmVkMSIpCmxlZ2VuZCgidG9wbGVmdCIsIGxlZ2VuZD1jKCJNIiwiRiIpLCAKICAgICAgIGNvbD1jKCJzdGVlbGJsdWUiLCJvcmFuZ2VyZWQxIiksIHBjaD1jKDE2LDE3KSkKYGBgCgojI0FkZGluZyB0ZXh0CgotIFRleHQgY2FuIGFsc28gYmUgYWRkZWQgdG8gYSBwbG90IGluIGEgc2ltaWxhciBtYW5uZXIKICAgICsgdGhlIGBsYWJlbHNgIGFyZ3VtZW50IHNwZWNpZmllcyB0aGUgdGV4dCB3ZSB3YW50IHRvIGFkZAogICAgCmBgYHtyfQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCBwY2g9MTYpCiAgdGV4dChwYXRpZW50cyRIZWlnaHQsIHBhdGllbnRzJFdlaWdodCwgbGFiZWxzPXBhdGllbnRzJFJhY2UpCmBgYAoKLSBJbiB0aGUgYWJvdmUgd2UgdXNlZCB0byBzYW1lIGNvLW9yZGluYXRlcyBmcm9tIHRoZSBvcmlnaW5hbCBwbG90IHRvIHBsYWNlIHRoZSB0ZXh0Ci0gV2UgY2FuIHVzZSBhcmd1bWVudHMgYHBvc2Agb3IgYGFkamAgdG8gb2Zmc2V0IHRoZSBwb3NpdGlvbnMgb2YgdGhlIGxhYmVscwogICAgKyBgcG9zYCBjYW4gYmUgMSwgMiwgMywgNCBmb3IgYmVsb3csIGxlZnQsIGFib3ZlLCByaWdodAogICAgKyBgYWRqYCBpcyBhbiBhZGp1c3RtZW50IGluIHRoZSByYW5nZSAwIHRvIDEKCmBgYHtyfQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCBwY2g9MTYpCiAgdGV4dChwYXRpZW50cyRIZWlnaHQsIHBhdGllbnRzJFdlaWdodCwgbGFiZWxzPXBhdGllbnRzJFJhY2UscG9zID0gMykKYGBgCgoKYGBge3J9CnBsb3QocGF0aWVudHMkSGVpZ2h0LCBwYXRpZW50cyRXZWlnaHQsIHBjaD0xNikKICB0ZXh0KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCBsYWJlbHM9cGF0aWVudHMkUmFjZSxhZGogPTAuMSkKYGBgCgoKLSBUbyBhaWQgb3VyIGludGVycHJldGF0aW9uLCBpdCBpcyBvZnRlbiBoZWxwZnVsIHRvIGFkZCBndWlkZWxpbmVzCiAgICArIGBncmlkKClgIGlzIG9uZSBlYXN5IHdheSBvZiBkb2luZyB0aGlzOgogICAgCmBgYHtyfQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCBwY2g9MTYpCmdyaWQoY29sPSJzdGVlbGJsdWUiKQpgYGAKCgotIENhbiBhbHNvIGFkZCBsaW5lcyB0aGF0IGludGVyc2VjdCB0aGUgYXhlczoKICAgICsgYHYgPWAgZm9yIHZlcnRpY2FsIGxpbmVzCiAgICArIGBoID1gIGZvciBob3Jpem9udGFsCiAgICArIGNhbiBzcGVjaWZ5IG11bHRpcGxlIGxpbmVzIGluIGEgdmVjdG9yCiAgICAKYGBge3J9CnBsb3QocGF0aWVudHMkSGVpZ2h0LCBwYXRpZW50cyRXZWlnaHQsIHBjaD0xNikKYWJsaW5lKHY9MTYwLCBjb2w9InJlZCIpCmFibGluZShoPWMoNjUsNzAsNzUpLCBjb2w9ImJsdWUiKQpgYGAKCgojIyBQbG90IGxheW91dHMKCi0gVGhlIGBwYXJgIGZ1bmN0aW9uIGNhbiBiZSB1c2VkIHNwZWNpZnkgdGhlIGFwcGVhcmFuY2Ugb2YgYSBwbG90Ci0gVGhlIHNldHRpbmdzIHBlcnNpc3QgdW50aWwgdGhlIHBsb3QgaXMgY2xvc2VkIHdpdGggKipgZGV2Lm9mZigpYCoqCi0gYD9wYXJgIGFuZCBzY3JvbGwgdG8gKioqZ3JhcGhpY2FsIHBhcmFtZXRlcnMqKioKLSBPbmUgZXhhbXBsZSBpcyBgbWZyb3dgOgogICAgKyAibXVsdGlwbGUgZmlndXJlcyBwZXIgcm93IgogICAgKyBuZWVkcyB0byBiZSBhIHZlY3RvciBvZiByb3dzIGFuZCBjb2x1bW5zOgogICAgICAgICsgZS5nLiBhIHBsb3Qgd2l0aCBvbmUgcm93IGFuZCB0d28gY29sdW1ucyBgcGFyKG1mcm93PWMoMSwyKSlgCiAgICAgICAgKyBkb24ndCBuZWVkIHRoZSBzYW1lIGtpbmQgb2YgcGxvdCBpbiBlYWNoIGNlbGwKICAgICAgICAKYGBge3J9CnBhcihtZnJvdz1jKDEsMikpCnBsb3QocGF0aWVudHMkSGVpZ2h0LCBwYXRpZW50cyRXZWlnaHQsIHBjaD0xNikKYm94cGxvdChwYXRpZW50cyRXZWlnaHQgfiBwYXRpZW50cyRTZXgpCgpgYGAKCi0gU2VlIGFsc28gYG1hcmAgZm9yIHNldHRpbmcgdGhlIG1hcmdpbnM6CiAgICArIGBwYXIobWFyPWMoLi4uKSlgCiAgICAKIyMgRXhwb3J0aW5nIGdyYXBocyBmcm9tIFJTdHVkaW8KCi0gWW91IGNhbiB1c2UgdGhlIGBwZGYoKWAgZnVuY3Rpb24gdG8gZXhwb3J0IG9uZSBvciBtb3JlIHBsb3RzIHRvIGEgcGRmIGZpbGU6CiAgICArIFlvdSB3aWxsIHNlZSB0aGF0IHRoZSBwbG90IGRvZXMgbm90IGFwcGVhciBpbiBSU3R1ZGlvCi0gWW91IG5lZWQgdG8gdXNlIHRoZSBgZGV2Lm9mZigpYCB0byBzdG9wIHByaW50aW5nIGdyYXBocyB0byB0aGUgcGRmIGFuZCAnY2xvc2UnIHRoZSBmaWxlCiAgICArIEl0IGFsbG93cyB5b3UgdG8gY3JlYXRlIGEgcGRmIGRvY3VtZW50IHdpdGggbXVsdGlwbGUgcGFnZXMKICAgIApgYGB7cn0KcGRmKCJFeGFtcGxlR3JhcGgucGRmIikKYm94cGxvdChwYXRpZW50cyRXZWlnaHQgfiBwYXRpZW50cyRTZXgpCmRldi5vZmYoKQpgYGAKCi0gcGRmIGlzIGEgZ29vZCBjaG9pY2UgZm9yIHB1YmxpY2F0aW9uIGFzIHRoZXkgY2FuIGJlIGltcG9ydGVkIGludG8gUGhvdG9zaG9wLCBJbmtzY2FwZSwgZXRjLgogICAgLSBTb21ldGltZXMgaXQgaXMgZWFzaWVyIHRvIGVkaXQgaW4gdGhlc2UgdG9vbHMgdGhhbiBSIQogICAgLSBJZiBpdCBpcyB0YWtpbmcgdG9vIGxvbmcgdG8gY3VzdG9taXNlIGEgcGxvdCBpbiBSLCBjb25zaWRlciBpZiB5b3Ugc2hvdWxkIGJlIHVzaW5nIG9uZSBvZiB0aGVzZSB0b29scyBpbnN0ZWFkCiAgICAKCi0gVG8gc2F2ZSBhbnkgZ3JhcGggeW91IGhhdmUgY3JlYXRlZCB0byBhIHBkZiwgcmVwZWF0IHRoZSBjb2RlIHlvdSB1c2VkIHRvIGNyZWF0ZSB0aGUgcGxvdCB3aXRoIGBwZGYoKWAgYmVmb3JlIGFuZCBgZGV2Lm9mZigpYCBhZnRlcndhcmRzCiAgICArIHlvdSBjYW4gaGF2ZSBhcyBtYW55IGxpbmVzIG9mIGNvZGUgaW4tYmV0d2VlbiBhcyB5b3UgbGlrZQogICAgKyBlYWNoIG5ldyBwbG90IHdpbGwgYmUgd3JpdHRlbiB0byBhIGRpZmZlcmVudCBwYWdlCiAgICAKYGBge3J9CnBkZigibXlncmFwaC5wZGYiKQpwbG90KHBhdGllbnRzJEhlaWdodCwgcGF0aWVudHMkV2VpZ2h0LCBwY2g9MTYpCmFibGluZSh2PTQwLCBjb2w9InJlZCIpCmFibGluZShoPWMoNjUsNzAsNzUpLCBjb2w9ImJsdWUiKQpib3hwbG90KHBhdGllbnRzJFdlaWdodCB+IHBhdGllbnRzJFNleCkKeCA8LSAxOjEwCnkgPC0geF4yICsgNCp4CnBsb3QoeCx5KQpkZXYub2ZmKCkKYGBgCgotIElmIG5vIHBsb3RzIGFyZSBhcHBlYXJpbmcgaW4gUlN0dWRpbywgaXQgY291bGQgYmUgeW91IGFyZSBzdGlsbCB3cml0aW5nIHRvIGEgcGRmIGZpbGUKICAgICsgcnVuIGBkZXYub2ZmKClgIG11bHRpcGxlIHRpbWVzIHVudGlsIHlvdSBzZWUgYSBtZXNzYWdlIGBjYW5ub3Qgc2h1dCBkb3duIGRldmljZSAodGhlIG51bGwgZGV2aWNlKWAKCgotIFdlIGNhbiBzcGVjaWZ5IHRoZSBkaW1lbnNpb25zIG9mIHRoZSBwbG90LCBhbmQgb3RoZXIgcHJvcGVydGllcyBvZiB0aGUgZmlsZSAoYD9wZGZgKQoKYGBge3J9CnBkZigiRXhhbXBsZUdyYXBoLnBkZiIsIHdpZHRoPTEwLCBoZWlnaHQ9NSkKYm94cGxvdChwYXRpZW50cyRXZWlnaHQgfiBwYXRpZW50cyRTZXgpCmRldi5vZmYoKQpgYGAKCi0gT3RoZXIgZm9ybWF0cyBjYW4gYmUgY3JlYXRlZDoKICAgICsgZS5nLiAqKipwbmcqKiosIG9yIG90aGVycyBgP2pwZWdgCiAgICArIG1vcmUgYXBwcm9wcmlhdGUgZm9yIGVtYWlsLCBwcmVzZW50YXRpb25zLCB3ZWIgcGFnZQogICAgCmBgYHtyfQpwbmcoIkV4YW1wbGVHcmFwaC5wbmciKQpib3hwbG90KHBhdGllbnRzJFdlaWdodCB+IHBhdGllbnRzJFNleCkKZGV2Lm9mZigpCmBgYAoKIyNFeGVyY2lzZTogRXhlcmNpc2UgNWEKLSBSZXR1cm4gdG8gdGhlIHdlYXRoZXIgZGF0YSBmcm9tIHllc3RlcmRheToKCmBgYHtyfQp3ZWF0aGVyIDwtIHJlYWQuY3N2KCJvem9uZS5jc3YiKQpgYGAKCi0gVXNpbmcgdGhlIGBwYXJgIGZ1bmN0aW9uLCBjcmVhdGUgYSBsYXlvdXQgd2l0aCB0aHJlZSBjb2x1bW5zCi0gUGxvdCBPem9uZSB2ZXJzdXMgU29sYXIgUmFkaWF0aW9uLCBXaW5kIFNwZWVkIGFuZCBUZW1wZXJhdHVyZSBvbiBzZXBhcmF0ZSBncmFwaHMKICAgICsgdXNlIGRpZmZlcmVudCBjb2xvdXJzIGFuZCBwbG90dGluZyBjaGFyYWN0ZXJzIG9uIGVhY2ggcGxvdAotIFNhdmUgdGhlIHBsb3QgdG8gYSBwZGYKLSBISU5UOiBDcmVhdGUgdGhlIGdyYXBoIGZpcnN0IGluIFJTdHVkaW8uIFdoZW4geW91J3JlIGhhcHB5IHdpdGggaXQsIHJlLXJ1biB0aGUgY29kZSBwcmVjZWVkZWQgYnkgdGhlIGBwZGZgIGZ1bmN0aW9uIHRvIHNhdmUgdG8gYSBmaWxlCiAgICArIGRvbid0IGZvcmdldCB0byB1c2UgYGRldi5vZmYoKWAgdG8gY2xvc2UgdGhlIGZpbGUKCiFbXShpbWFnZXMvZXhlcmNpc2U1YS5wbmcpCgpgYGB7cn0KIyMjIFlvdXIgQW5zd2VyIEhlcmUgIyMjCgoKYGBgCgoKIyNFeGVyY2lzZTogRXhlcmNpc2UgNWIKLSBUZW1wZXJhdHVyZSBhbmQgT3pvbmUgbGV2ZWwgc2VlbSB0byBiZSBjb3JyZWxhdGVkCi0gSG93ZXZlciwgdGhlcmUgYXJlIHNvbWUgb2JzZXJ2YXRpb25zIHRoYXQgZG8gbm90IHNlZW0gdG8gZml0IHRoZSB0cmVuZAogICAgKyB0aG9zZSB3aXRoIE96b25lIGxldmVsID4gMTAwCi0gTW9kaWZ5IHRoZSBwbG90IHNvIHRoYXQgdGhlc2Ugb3V0bGllciBvYnNlcnZhdGlvbnMgYXJlIGluIGEgZGlmZmVyZW50IGNvbG91cgotIEFkZCBhIGxlZ2VuZCB0byBoZWxwIGludGVycHJldCB0aGUgcGxvdAoKCiFbXShpbWFnZXMvZXhlcmNpc2U1Yi5wbmcpCgoKSElOVDogWW91IGNhbiBicmVhayBkb3duIHRoZSBwcm9ibGVtIGludG8gdGhlIGZvbGxvd2luZyBzdGVwcwoKLSBDcmVhdGUgYSBibGFuayBwbG90Ci0gSWRlbnRpZnkgb2JzZXJ2YXRpb25zIHdpdGggb3pvbmUgPiAxMDAKICAgICsgcGxvdCB0aGUgY29ycmVzcG9uZGluZyBUZW1wZXJhdHVyZSBhbmQgT3pvbmUgdmFsdWVzIGZvciB0aGVzZSBpbiByZWQKLSBJZGVudGlmeSBvYnNlcnZhdGlvbnMgd2l0aCBvem9uZSA8IDEwMAogICAgKyBwbG90IHRoZSBjb3JyZXNwb25kaW5nIFRlbXBlcmF0dXJlIGFuZCBPem9uZSB2YWx1ZXMgZm9yIHRoZXNlIGluIG9yYW5nZQotIENhbiB5b3UgbW9kaWZ5IHlvdXIgY29kZSBzbyB0aGF0IHRoZSBjdXQtb2ZmIGlzIG5vdCBoYXJkLWNvZGVkIChmaXhlZCkgdG8gMTAwCiAgICArIGUuZy4gT3pvbmUgPiA4MCwgT3pvbmUsIDkwIGV0Yy4uLgogICAgKyBjYW4geW91IHJlLWdlbmVyYXRlIHRoZSBwbG90cyB0aGUgbWluaW1hbCBjaGFuZ2VzIHRvIHRoZSBjb2RlCgpgYGB7cn0KCiMjIyBZb3VyIEFuc3dlciBIZXJlICMjIwoKCmBgYAoKQW4gYWx0ZXJuYXRpdmUsIGFuZCBlcXVhbGx5LXZhbGlkLCBzb2x1dGlvbiBpbnZvbHZlcyBjcmVhdGluZyBhIHZlY3RvciBvZiBjb2xvdXJzIHdoaWNoIHdpbGwgZWl0aGVyIGJlIGByZWRgIG9yIGBvcmFuZ2VgIGRlcGVuZGluZyB3aGV0aGVyIG9uIHRoZSBwYXJ0aWN1bGFyIHZhbHVlIG9mIG96b25lIGlzIGFuIG91dGxpZXIKCi0gd2UgY2FuIHVzZSB0aGUgYHJlcGAgZnVuY3Rpb24gdG8gY3JlYXRlIGEgdmVjdG9yIG9mIHRoZSByZXF1aXJlZCBsZW5ndGggZm9yIHRoZSBlbnRpcmUgZGF0YXNldAogICAgKyBvbmUgdmVjdG9yIGZvciBwbG90dGluZyBjb2xvdXJzLCBhbmQgYW5vdGhlciB2ZWN0b3IgZm9yIHBsb3R0aW5nIGNoYXJhY3RlcnMKICAgIApgYGB7cn0Kd2VhdGhlciA8LSByZWFkLmNzdigib3pvbmUuY3N2IikKbXljb2wgPC1yZXAoIm9yYW5nZSIsbnJvdyh3ZWF0aGVyKSkKbXlwY2ggPC0gcmVwKDE3LCBucm93KHdlYXRoZXIpKQoKYGBgCgotIG5vdyB1c2UgYSBsb2dpY2FsIGV4cHJlc3Npb24gdG8gaWRlbnRpZnkgdGhlIGhpZ2ggb3pvbmUgbGV2ZWwsIGFuZCByZXBsYWNlIHRoZSBjb3JyZXNwb25kaW5nIGVudHJpZXMgaW4gdGhlIGNvbG91ciBhbmQgcGxvdHRpbmcgY2hhcmFjdGVyIHZlY3RvcnMKCmBgYHtyfQpoaWdoTyA8LSB3aGljaCh3ZWF0aGVyJE96b25lID4gMTAwKQpteWNvbFtoaWdoT10gPC0gInJlZCIKbXlwY2hbaGlnaE9dIDwtIDE4CmBgYAoKLSBjcmVhdGluZyB0aGUgcGxvdCBjYW4gbm93IGJlIGRvbmUgd2l0aCBhIHNpbmdsZSBgcGxvdGAgY29tbWFuZAoKYGBge3J9CnBsb3Qod2VhdGhlciRUZW1wLHdlYXRoZXIkT3pvbmUsIAogICAgIGNvbD1teWNvbCwgcGNoPW15cGNoLHlsYWI9Ik96b25lIGxldmVsIiwKICAgICB4bGFiPSJUZW1wZXJhdHVyZSIpCmFibGluZShoPTEwMCxsdHk9MikKbGVnZW5kKCJ0b3BsZWZ0IiwgbGVnZW5kID0gYygiT3pvbmUgPiAxMDAiLCJOb3JtYWwgT3pvbmUiKSxjb2w9YygicmVkIiwib3JhbmdlIikscGNoPTE3KQpgYGAKCiMjIE90aGVyIHBsb3R0aW5nIGZlYXR1cmVzCgpXZSBjYW4gY2hvb3NlIG5vdCB0byBzaG93IHRoZSB4LSBhbmQgeS1heGlzIGluIHRoZSBpbml0aWFsIHBsb3QKCmBgYHtyfQpwbG90KHBhdGllbnRzJEFnZSwgcGF0aWVudHMkV2VpZ2h0LCBwY2g9MTYsYXhlcz1GQUxTRSkKYGBgCgpUaGV5IGNhbiBiZSBhZGRlZCBhZnRlcndhcmRzIHdpdGggdGhlIGBheGlzYCBmdW5jdGlvbgogICAgLSBmaXJzdCBhcmd1bWVudCBpcyB3aGV0aGVyIHRoZSBheGlzIGFwcGVhcnMgb24gdGhlIGJvdHRvbSAoMSksIGxlZnQgKDIpLCB0b3AgKDMpIG9yIHJpZ2h0ICg0KQogICAgLSBjYW4gYWxzbyBkZWZpbmUgd2hlcmUgdGhlIHRpY2sgbWFya3MgYXJlIGxvY2F0ZWQsIGFuZCB0aGUgbGFiZWxzIHRvIGRpc3BsYXkKICAgIC0gdGhlIGBib3hgIGZ1bmN0aW9uIGNhbiBiZSB1c2VkIHRvIGVuY2xvc2UgdGhlIHBsb3QgaW4gYSBib3gKYGBge3J9CnBsb3QocGF0aWVudHMkQWdlLCBwYXRpZW50cyRXZWlnaHQsIHBjaD0xNixheGVzPUZBTFNFKQpheGlzKDEpCmF4aXMoNCwgYXQgPSBjKDY1LDc1LDg1LDk1KSxsYWJlbHMgPSBjKCI2NWtnIiwiNzVrZyIsIjg1a2ciLCI5NWtnIikpCmJveCgpCgpgYGAKCgoKIyMgT3JkZXJpbmcgdGhlIGJveGVzIGluIGEgc2V0IG9mIGJveHBsb3RzCgpOLkIuIFRoZSBvcmRlciBpbiB3aGljaCB0aGUgYm94ZXMgYXJlIGRpc3BsYXllZCBvbiBhIGJveHBsb3QgaXNuJ3Qgc29tZXRoaW5nIHRoYXQgY2FuIGJlIGNvbnRyb2xsZWQgYnkgZ3JhcGhpY2FsIHBhcmFtYXRlcnMKCi0gdGhlIG9yZGVyIGlzIGRlcml2ZWQgZnJvbSB0aGUgb3JkZXIgb2YgdGhlIGNhdGVnb3JpZXMKICAgICsgaW4gdGhpcyBjYXNlIGBGZW1hbGVgIGNvbWVzIGZpcnN0IGFscGhhYmV0aWNhbGx5CgpgYGB7cn0KYm94cGxvdChwYXRpZW50cyRXZWlnaHR+cGF0aWVudHMkU2V4KQpgYGAKCi0gYSBkaWZmZXJlbnQgb3JkZXIgY2FuIGJlIGFjaGlldmVkIGJ5IGRlZmluaW5nIGEgbmV3IGZhY3RvciB3aGVyZSB0aGUgYGxldmVsc2AgYXJlIGluIHRoZSBvcmRlciB5b3Ugd2FudAoKYGBge3J9CnBhdGllbnRzJFNleCA8LSBmYWN0b3IocGF0aWVudHMkU2V4LGxldmVscyA9IGMoIk1hbGUiLCJGZW1hbGUiKSkKYm94cGxvdChwYXRpZW50cyRXZWlnaHR+cGF0aWVudHMkU2V4KQpgYGAKCgojIyBOZWVkIGluc3BpcmF0aW9uPwoKVGhlIFtSIEdyYXBoIEdhbGxlcnldKGh0dHA6Ly93d3cuci1ncmFwaC1nYWxsZXJ5LmNvbS8pIGhhcyBsb3RzIG9mIGV4YW1wbGVzIChhbmQgY29kZSkgc2hvd2luZyB3aGF0IGNhbiBiZSBhY2hpZXZlZCB3aXRoIFIgZ3JhcGhpY3M=