Today we’ll start looking at our first topic in spatial statistics:
point pattern analysis! Often, we might have a dataset of point
locations. These can take many forms: you might want point data of
business locations, sightings of endangered species, coordinates of
fires or other disasters, or even the exact locations of spectators in a
sports arena. For all of these examples, it would be helpful to know
some information about how the points cluster in space
library(sf)
Linking to GEOS 3.9.1, GDAL 3.4.3, PROJ 7.2.1; sf_use_s2() is TRUE
library(maptools)
Loading required package: sp
Checking rgeos availability: FALSE
Please note that 'maptools' will be retired by the end of 2023,
plan transition at your earliest convenience;
some functionality will be moved to 'sp'.
Note: when rgeos is not available, polygon geometry computations in maptools depend on gpclib,
which has a restricted licence. It is disabled by default;
to enable gpclib, type gpclibPermit()
library(spatstat)
Loading required package: spatstat.data
Loading required package: spatstat.geom
spatstat.geom 3.0-3
Loading required package: spatstat.random
spatstat.random 2.2-0
Loading required package: spatstat.core
Loading required package: nlme
Loading required package: rpart
spatstat.core 2.4-4
Loading required package: spatstat.linnet
spatstat.linnet 2.3-2
spatstat 2.3-4 (nickname: ‘Watch this space’)
For an introduction to spatstat, type ‘beginner’
library(raster)
Attaching package: ‘raster’
The following object is masked from ‘package:nlme’:
getData
library(dplyr)
Warning: package ‘dplyr’ was built under R version 4.2.2
Attaching package: ‘dplyr’
The following objects are masked from ‘package:raster’:
intersect, select, union
The following object is masked from ‘package:nlme’:
collapse
The following objects are masked from ‘package:stats’:
filter, lag
The following objects are masked from ‘package:base’:
intersect, setdiff, setequal, union
library(spData)
To access larger datasets in this package, install the
spDataLarge package with: `install.packages('spDataLarge',
repos='https://nowosad.github.io/drat/', type='source')`
library(urbnmapr)
library(ggplot2)
Warning: package ‘ggplot2’ was built under R version 4.2.3
library(tidyr)
Attaching package: ‘tidyr’
The following object is masked from ‘package:raster’:
extract
load(url("https://github.com/mgimond/Spatial/raw/main/Data/ppa.RData"))
#In this tutorial, we need our data to be in spatstat format
#the spatstat package requires a ppp type object, not an sf object
#don't worry too much about this now, but take a look at the following printout to verify that it's correct
starbucks
Planar point pattern: 171 points
window: rectangle = [648032.3, 917740.7] x [4609785, 4748107] units
We’ll get back to the analysis in a second, but if my object isn’t
already a ppp object? We’ll have to convert it, like this:
*Note that “marks” are attributes associated with a ppp object. We
won’t need any attributes in this example, so I’ve just deleted them to
avoid confusion.
#Note that we will only use this data to illustrate the ppp conversion - we can delete afterwards
setwd("~/Binghamton/geog380/NYS_Place_Points_SHP")
Warning: The working directory was changed to C:/Users/mhaller/Documents/Binghamton/geog380/NYS_Place_Points_SHP inside a notebook chunk. The working directory will be reset when the chunk is finished running. Use the knitr root.dir option in the setup chunk to change the working directory for notebook chunks.
points <- st_read("NYS_Place_Points.shp")
Reading layer `NYS_Place_Points' from data source
`C:\Users\mhaller\Documents\Binghamton\geog380\NYS_Place_Points_SHP\NYS_Place_Points.shp'
using driver `ESRI Shapefile'
Simple feature collection with 4571 features and 8 fields
Geometry type: POINT
Dimension: XY
Bounding box: xmin: 108752.5 ymin: 4484510 xmax: 756007.4 ymax: 4984062
Projected CRS: NAD83 / UTM zone 18N
points_ppp <- as.ppp(points)
Warning: only first attribute column is used for marks
#marks are attributes - we need any for this example, so set to null
marks(points_ppp) <- NULL
plot(points_ppp, main=NULL, cols=rgb(0,0,0,.2), pch=20)

Returning to the starbucks data:
#We just want the points, not the attributes attached to them
#this code removes the attributes (variables)
library(spatstat)
marks(starbucks) <- NULL
#This line allows us to bind the points to the MA boundary file
Window(starbucks) <- ma
#For simplicity, we will just use base R plotting for now
plot(starbucks, main=NULL, cols=rgb(0,0,0,.2), pch=20)

First, we’ll calculate some basic descriptives statistics using our
point data. Let’s start with the mean center of the points:
xy <- coords(starbucks)
#This allows us to calculate the mean center
mc <- apply(xy, 2, mean)
#select just the mean center
mean <- cbind(mc[1], mc[2])
#plot it
plot(starbucks, main=NULL, cols=rgb(0,0,0,.2), pch=20)
points(mean, col='red', pch = "*", cex = 3)

The red * is the central most point on the plot, based on the
distribution of points. It is calculated based on the average of the x
and y values.
Next we’ll calculate the standard distance! This is a measure of the
variance of the distance from each point to the mean center - think of
it like a standard deviation for point data.
#don't worry too much about the complicated code here
sd <- sqrt(sum((xy[,1] - mc[1])^2 + (xy[,2] - mc[2])^2) / nrow(xy))
#now we'll plot the standard distance as a circle
#don't worry about this code, I won't ask you to reproduce it
bearing <- 1:360 * pi/180
cx <- mc[1] + sd * cos(bearing)
cy <- mc[2] + sd * sin(bearing)
circle <- cbind(cx, cy)
#now we can plot it
plot(starbucks, main=NULL, cols=rgb(0,0,0,.2), pch=20)
points(mean, col='red', pch = "*", cex = 3)
lines(circle, col='red', lwd=2)

Those are two very basic descriptive statistics for point data. Next,
we’ll move on to measures of point density.
Because we’ll use the population data in a bit, we’ll first start by
transforming the data.
#Let's take a look at the population density raster, pop
hist(pop, main=NULL, las=1)

This data looks very skewed! Data that is approximately normally
distributed would work better here - let’s transform our variable
pop.lg <- log(pop)
hist(pop.lg, main=NULL, las=1)

#One way to calculate the intensity of the point process is with the summary() function
summary(starbucks)
Planar point pattern: 171 points
Average intensity 8.268627e-09 points per square unit
Coordinates are given to 1 decimal place
i.e. rounded to the nearest multiple of 0.1 units
Window: polygonal boundary
single connected closed polygon with 128 vertices
enclosing rectangle: [623157.2, 921923.8] x [4602709, 4756659] units
(298800 x 154000 units)
Window area = 20680600000 square units
Fraction of frame area: 0.45
Remember, point pattern intensity is a global or first-order
characteristic
Next, we will calculate a measure of density using a quadrat grid. In
this way, we can look at the number of points in each quadrat.
Q <- quadratcount(starbucks, nx= 6, ny=3)
plot(starbucks, pch=20, cols="grey70", main=NULL) # Plot points
plot(Q, add=TRUE) # Add quadrat grid

Here, we have quadrat point counts. Let’s calculate the density of
points in each quadrat!
# Compute the density for each quadrat
Q.d <- intensity(Q)
# Plot the density
plot(intensity(Q, image=TRUE), main=NULL, las=1) # Plot density raster
plot(starbucks, pch=20, cex=0.6, col=rgb(0,0,0,.5), add=TRUE) # Add points

In this plot, blue areas are low density, pink/purple are
intermediate, and yellow/orange are high density regions. The units here
are points per square meter. Those units aren’t very nice, are they? We
can rescale them to kilometers like this:
#rescale all of our data:
starbucks.km <- rescale(starbucks, 1000, "km")
ma.km <- rescale(ma, 1000, "km")
pop.km <- rescale(pop, 1000, "km")
pop.lg.km <- rescale(pop.lg, 1000, "km")
Now we can plot it again!
# Compute the density for each quadrat (in counts per km2)
Q <- quadratcount(starbucks.km, nx= 6, ny=3)
Q.d <- intensity(Q)
# Plot the density
plot(intensity(Q, image=TRUE), main=NULL, las=1) # Plot density raster
plot(starbucks.km, pch=20, cex=0.6, col=rgb(0,0,0,.5), add=TRUE) # Add points

Now our scale looks much nicer. Another thing to note is that we do
not have to use uniformly square quadrats. Here, I’ll show an example of
using the population density object to calculate new quadrats. The
regions defined by population density are called “tessellated
surfaces”.
brk <- c( -Inf, 4, 6, 8 , Inf) # Define the breaks
Zcut <- cut(pop.lg.km, breaks=brk, labels=1:4) # Classify the raster
E <- tess(image=Zcut) # Create a tesselated surface
#Now we'll plot it
#note that las=1 tells R to show the legend labels upright (as opposed to flipped 90 degrees to the left - that is the default)
plot(E, main="", las=1)

#Repeat the same density analysis as before, but with the new quadrats
#we just need to tell R which tessellated object to use for quadrats
Q <- quadratcount(starbucks.km, tess = E) # Tally counts
Q.d <- intensity(Q) # Compute density
# Plot the density
plot(intensity(Q, image=TRUE), main=NULL, las=1) # Plot density raster
plot(starbucks.km, pch=20, cex=0.6, col=rgb(0,0,0,.5), add=TRUE) # Add points

Advanced: Kernel Density
Another measure of density is called Kernel Density. Rather than use
quadrats, this form of density is calculated using a moving sub-region
window, called a Kernel window. This is quite similar to the focal
operations that we discussed in our raster unit. This measure of density
is nice because it gives us a more detailed picture of point density
across the study unit. We can also convert the output of the kernel
density function into a raster object, which would allow us to use
raster methods on it.
library(raster)
#This is really easy - calculate kernel density
dens <- density(starbucks.km)
#convert into a raster
rast <- raster(dens)
#use the native raster plotting method to plot it
plot(rast)

#Another cool feature of kernel density: we can plot it using contour lines
plot(dens, main=NULL, las=1)
contour(dens, add=TRUE)

We can also change the bandwidth of the kernel density estimation. By
default, R chooses the bandwidth for us, based on the size of the
plotting window. We can re-specify the bandwidth using the sigma =
argument. Here, I show an example where I change the bandwidth to 50
km.
dens2 <- density(starbucks.km, sigma=50) # Using a 50km bandwidth
plot(dens2, main=NULL, las=1)
contour(dens2, add=TRUE)

Let’s return to complete spatial randomness for a moment. If we want
to test whether our point data was generated by complete spatial
randomness, there is a very simple function that will allow us to test
this. Remember that, whenever we set up statistical tests, we always
want to set up a null and alternative hypothesis. Ours might look like
this:
Null: The point data was generated by a random spatial process
Alternative: The point data was not generated by a random spatial
process
Now we can perform our test. Since this is not a statistics class, I
won’t go into the technical details here, but it should be easy enough
to interpret the results of the test. If you want to learn more about
what is going on under the hood of this test, you can always look it up
later.
quadrat.test(starbucks.km)
Warning: Some expected counts are small; chi^2 approximation may be inaccurate
Chi-squared test of CSR using quadrat counts
data: starbucks.km
X2 = 695.5, df = 19, p-value < 2.2e-16
alternative hypothesis: two.sided
Quadrats: 20 tiles (irregular windows)
Our test statistic is a Chi-squared value - we know that we have
sufficient evidence to reject the null hypothesis if our test statistic
is large enough. With a P-value well under 0.05, we can safely reject
the null hypothesis and conclude that our point pattern was not
generated by a random spatial process.
Distance-Based Methods |
Let’s dig deeper into the pattern of clustering. The
first method we’ll look at is called the Average Nearest Neighbor method
(or ANN). ANN is essentially the average distance between all points
within a dataset and their individual nearest point (known as
Nearest-Neighbor Distance (NND)). We can compare the actual ANN pattern
in our data with the expected ANN pattern under CSR as a global
indicator of the overall clustering in our data. |
This is similar to quadrat analysis, but it uses
distance instead. |
|
|
|
r #Let's calculate the average nearest neighbor distance! mean(nndist(starbucks.km, k = 1)) |
|
|
[1] 3.275492 |
|
|
|
This tells us that each point is, on average, 3.28 km
away from its nearest neighbor |
|
|
|
```r #Next we’ll plot the ANN for different numbers of
neighbors (K) |
ANN <- apply(nndist(starbucks.km,
k=1:100),2,FUN=mean) plot(ANN ~ eval(1:100), type=“b”, main=NULL, las=1)
``` |
|
|
 |
|
|
|
This shows us that the ANN increases as the number of
neighbors increases, but it doesn’t give us too much info. ANN is
particularly helpful if we want to compare clustering in one point
dataset to another. |
Our next statistic will give us a lot more details about the pattern
of clustering in our data.
Essentially, The black line (K-pois) represents the theoretical
K-function under the null hypothesis that the points are completely
randomly distributed (CSR/IRP). Where K falls under the theoretical
K-pois line, the points are deemed more dispersed than expected at
distance r. Where K falls above the theoretical k-pois line, the points
are deemed more clustered than expected at distance r.
Calculation: Ripley’s K function essentially summarises the distance
between points for all distances using radial distance bands. The
calculation is relatively straightforward (Wilkin, 2015):
-For point event A, count the number of points inside a buffer
(radius) of a certain size. Then count the number of points inside a
slightly larger buffer (radius). -Repeat this for every point event in
the dataset. -Compute the average number of points in each buffer
(radius) and divide this by the overall point density. -Repeat this
using points drawn from a Poisson random model for the same set of
buffers (don’t worry too much about this model - you just need to be
able to interpret the plot). -Compare the observed distribution with the
distribution with the Poisson distribution.
Note that this function will be very slow for large datasets.
plot(Kest(starbucks.km, correction = "border"))

At a radius of about 0-30 km, the points are much more clustered than
we would expect, given a random spatial process. Greater than 30km, the
pattern is a bit less clear, and may even become more dispersed than
expected. Overall, this gives us a very clear picture that our data is
unlikely to have been generated randomly.
One caveat with this analysis: it assumes that our point process is
stationary (i.e. that it doesn’t change across space). This may pose
some validity concerns for our K-function (the same is also true for ANN
analysis).
Another issue is that it can also be challenging to see small
differences between the actual and predicted values. The L-function is a
corrected version of the K-function that transforms the K function into
a straight line - this should make the difference easier to
visualize.
plot(Lest(starbucks.km, correction = "border"))

Resources
Dorman, M. (2022). Chapter 13 Point pattern analysis (in
preparation). Retrieved from: https://geobgu.xyz/r/point-pattern-analysis.html.
Gimond, M. (2022). Chapter 11: Point Pattern Analysis. Retrieved
from: https://mgimond.github.io/Spatial/chp11_0.html.
Peeples, M. (N. D.). Point Pattern Analysis. Retrieved from: https://www.mattpeeples.net/modules/PointPattern.html
R Spatial, (2021). Point Pattern Analysis. Retrieved from: https://rspatial.org/terra/analysis/7-pointpat.html.
Wilkin, J. (2015). 8 Analysing Spatial Patterns III: Point Pattern
Analysis. Retrieved from: https://jo-wilkin.github.io/GEOG0030/coursebook/analysing-spatial-patterns-iii-point-pattern-analysis.html.
LS0tDQp0aXRsZTogIkdlb2cgMzgwOiBQb2ludCBQYXR0ZXJuIEFuYWx5c2lzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KVG9kYXkgd2UnbGwgc3RhcnQgbG9va2luZyBhdCBvdXIgZmlyc3QgdG9waWMgaW4gc3BhdGlhbCBzdGF0aXN0aWNzOiBwb2ludCBwYXR0ZXJuIGFuYWx5c2lzISBPZnRlbiwgd2UgbWlnaHQgaGF2ZSBhIGRhdGFzZXQgb2YgcG9pbnQgbG9jYXRpb25zLiBUaGVzZSBjYW4gdGFrZSBtYW55IGZvcm1zOiB5b3UgbWlnaHQgd2FudCBwb2ludCBkYXRhIG9mIGJ1c2luZXNzIGxvY2F0aW9ucywgc2lnaHRpbmdzIG9mIGVuZGFuZ2VyZWQgc3BlY2llcywgY29vcmRpbmF0ZXMgb2YgZmlyZXMgb3Igb3RoZXIgZGlzYXN0ZXJzLCBvciBldmVuIHRoZSBleGFjdCBsb2NhdGlvbnMgb2Ygc3BlY3RhdG9ycyBpbiBhIHNwb3J0cyBhcmVuYS4gRm9yIGFsbCBvZiB0aGVzZSBleGFtcGxlcywgaXQgd291bGQgYmUgaGVscGZ1bCB0byBrbm93IHNvbWUgaW5mb3JtYXRpb24gYWJvdXQgaG93IHRoZSBwb2ludHMgY2x1c3RlciBpbiBzcGFjZQ0KDQpgYGB7cn0NCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KG1hcHRvb2xzKQ0KbGlicmFyeShzcGF0c3RhdCkNCmxpYnJhcnkocmFzdGVyKSAgIA0KbGlicmFyeShkcGx5cikgICANCmxpYnJhcnkoc3BEYXRhKSANCmxpYnJhcnkodXJibm1hcHIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRpZHlyKQ0KbG9hZCh1cmwoImh0dHBzOi8vZ2l0aHViLmNvbS9tZ2ltb25kL1NwYXRpYWwvcmF3L21haW4vRGF0YS9wcGEuUkRhdGEiKSkNCg0KDQojSW4gdGhpcyB0dXRvcmlhbCwgd2UgbmVlZCBvdXIgZGF0YSB0byBiZSBpbiBzcGF0c3RhdCBmb3JtYXQNCiN0aGUgc3BhdHN0YXQgcGFja2FnZSByZXF1aXJlcyBhIHBwcCB0eXBlIG9iamVjdCwgbm90IGFuIHNmIG9iamVjdA0KI2Rvbid0IHdvcnJ5IHRvbyBtdWNoIGFib3V0IHRoaXMgbm93LCBidXQgdGFrZSBhIGxvb2sgYXQgdGhlIGZvbGxvd2luZyBwcmludG91dCB0byB2ZXJpZnkgdGhhdCBpdCdzIGNvcnJlY3QNCnN0YXJidWNrcw0KYGBgDQpXZSdsbCBnZXQgYmFjayB0byB0aGUgYW5hbHlzaXMgaW4gYSBzZWNvbmQsIGJ1dCBpZiBteSBvYmplY3QgaXNuJ3QgYWxyZWFkeSBhIHBwcCBvYmplY3Q/IFdlJ2xsIGhhdmUgdG8gY29udmVydCBpdCwgbGlrZSB0aGlzOg0KDQoqTm90ZSB0aGF0ICJtYXJrcyIgYXJlIGF0dHJpYnV0ZXMgYXNzb2NpYXRlZCB3aXRoIGEgcHBwIG9iamVjdC4NCldlIHdvbid0IG5lZWQgYW55IGF0dHJpYnV0ZXMgaW4gdGhpcyBleGFtcGxlLCBzbyBJJ3ZlIGp1c3QgZGVsZXRlZCB0aGVtIHRvIGF2b2lkIGNvbmZ1c2lvbi4gDQpgYGB7cn0NCiNOb3RlIHRoYXQgd2Ugd2lsbCBvbmx5IHVzZSB0aGlzIGRhdGEgdG8gaWxsdXN0cmF0ZSB0aGUgcHBwIGNvbnZlcnNpb24gLSB3ZSBjYW4gZGVsZXRlIGFmdGVyd2FyZHMNCg0Kc2V0d2QoIn4vQmluZ2hhbXRvbi9nZW9nMzgwL05ZU19QbGFjZV9Qb2ludHNfU0hQIikNCnBvaW50cyA8LSBzdF9yZWFkKCJOWVNfUGxhY2VfUG9pbnRzLnNocCIpDQpwb2ludHNfcHBwIDwtIGFzLnBwcChwb2ludHMpDQojbWFya3MgYXJlIGF0dHJpYnV0ZXMgLSB3ZSBuZWVkIGFueSBmb3IgdGhpcyBleGFtcGxlLCBzbyBzZXQgdG8gbnVsbA0KbWFya3MocG9pbnRzX3BwcCkgPC0gTlVMTA0KDQpwbG90KHBvaW50c19wcHAsIG1haW49TlVMTCwgY29scz1yZ2IoMCwwLDAsLjIpLCBwY2g9MjApDQpgYGANCg0KUmV0dXJuaW5nIHRvIHRoZSBzdGFyYnVja3MgZGF0YToNCg0KYGBge3J9DQojV2UganVzdCB3YW50IHRoZSBwb2ludHMsIG5vdCB0aGUgYXR0cmlidXRlcyBhdHRhY2hlZCB0byB0aGVtDQojdGhpcyBjb2RlIHJlbW92ZXMgdGhlIGF0dHJpYnV0ZXMgKHZhcmlhYmxlcykNCg0KbGlicmFyeShzcGF0c3RhdCkNCm1hcmtzKHN0YXJidWNrcykgIDwtIE5VTEwNCg0KI1RoaXMgbGluZSBhbGxvd3MgdXMgdG8gYmluZCB0aGUgcG9pbnRzIHRvIHRoZSBNQSBib3VuZGFyeSBmaWxlDQpXaW5kb3coc3RhcmJ1Y2tzKSA8LSBtYQ0KDQojRm9yIHNpbXBsaWNpdHksIHdlIHdpbGwganVzdCB1c2UgYmFzZSBSIHBsb3R0aW5nIGZvciBub3cNCnBsb3Qoc3RhcmJ1Y2tzLCBtYWluPU5VTEwsIGNvbHM9cmdiKDAsMCwwLC4yKSwgcGNoPTIwKQ0KYGBgDQpGaXJzdCwgd2UnbGwgY2FsY3VsYXRlIHNvbWUgYmFzaWMgZGVzY3JpcHRpdmVzIHN0YXRpc3RpY3MgdXNpbmcgb3VyIHBvaW50IGRhdGEuIExldCdzIHN0YXJ0IHdpdGggdGhlIG1lYW4gY2VudGVyIG9mIHRoZSBwb2ludHM6DQoNCmBgYHtyfQ0KeHkgPC0gY29vcmRzKHN0YXJidWNrcykNCg0KI1RoaXMgYWxsb3dzIHVzIHRvIGNhbGN1bGF0ZSB0aGUgbWVhbiBjZW50ZXINCm1jIDwtIGFwcGx5KHh5LCAyLCBtZWFuKQ0KI3NlbGVjdCBqdXN0IHRoZSBtZWFuIGNlbnRlcg0KbWVhbiA8LSBjYmluZChtY1sxXSwgbWNbMl0pDQojcGxvdCBpdA0KcGxvdChzdGFyYnVja3MsIG1haW49TlVMTCwgY29scz1yZ2IoMCwwLDAsLjIpLCBwY2g9MjApDQpwb2ludHMobWVhbiwgY29sPSdyZWQnLCBwY2ggPSAiKiIsIGNleCA9IDMpDQoNCmBgYA0KDQpUaGUgcmVkICogaXMgdGhlIGNlbnRyYWwgbW9zdCBwb2ludCBvbiB0aGUgcGxvdCwgYmFzZWQgb24gdGhlIGRpc3RyaWJ1dGlvbiBvZiBwb2ludHMuIEl0IGlzIGNhbGN1bGF0ZWQgYmFzZWQgb24gdGhlIGF2ZXJhZ2Ugb2YgdGhlIHggYW5kIHkgdmFsdWVzLiANCg0KTmV4dCB3ZSdsbCBjYWxjdWxhdGUgdGhlIHN0YW5kYXJkIGRpc3RhbmNlISBUaGlzIGlzIGEgbWVhc3VyZSBvZiB0aGUgdmFyaWFuY2Ugb2YgdGhlIGRpc3RhbmNlIGZyb20gZWFjaCBwb2ludCB0byB0aGUgbWVhbiBjZW50ZXIgLSB0aGluayBvZiBpdCBsaWtlIGEgc3RhbmRhcmQgZGV2aWF0aW9uIGZvciBwb2ludCBkYXRhLiANCg0KYGBge3J9DQojZG9uJ3Qgd29ycnkgdG9vIG11Y2ggYWJvdXQgdGhlIGNvbXBsaWNhdGVkIGNvZGUgaGVyZQ0Kc2QgPC0gc3FydChzdW0oKHh5WywxXSAtIG1jWzFdKV4yICsgKHh5WywyXSAtIG1jWzJdKV4yKSAvIG5yb3coeHkpKQ0KDQojbm93IHdlJ2xsIHBsb3QgdGhlIHN0YW5kYXJkIGRpc3RhbmNlIGFzIGEgY2lyY2xlDQojZG9uJ3Qgd29ycnkgYWJvdXQgdGhpcyBjb2RlLCBJIHdvbid0IGFzayB5b3UgdG8gcmVwcm9kdWNlIGl0DQpiZWFyaW5nIDwtIDE6MzYwICogcGkvMTgwDQpjeCA8LSBtY1sxXSArIHNkICogY29zKGJlYXJpbmcpDQpjeSA8LSBtY1syXSArIHNkICogc2luKGJlYXJpbmcpDQpjaXJjbGUgPC0gY2JpbmQoY3gsIGN5KQ0KDQojbm93IHdlIGNhbiBwbG90IGl0DQpwbG90KHN0YXJidWNrcywgbWFpbj1OVUxMLCBjb2xzPXJnYigwLDAsMCwuMiksIHBjaD0yMCkNCnBvaW50cyhtZWFuLCBjb2w9J3JlZCcsIHBjaCA9ICIqIiwgY2V4ID0gMykNCmxpbmVzKGNpcmNsZSwgY29sPSdyZWQnLCBsd2Q9MikNCmBgYA0KDQpUaG9zZSBhcmUgdHdvIHZlcnkgYmFzaWMgZGVzY3JpcHRpdmUgc3RhdGlzdGljcyBmb3IgcG9pbnQgZGF0YS4gTmV4dCwgd2UnbGwgbW92ZSBvbiB0byBtZWFzdXJlcyBvZiBwb2ludCBkZW5zaXR5LiANCg0KQmVjYXVzZSB3ZSdsbCB1c2UgdGhlIHBvcHVsYXRpb24gZGF0YSBpbiBhIGJpdCwgd2UnbGwgZmlyc3Qgc3RhcnQgYnkgdHJhbnNmb3JtaW5nIHRoZSBkYXRhLiANCg0KYGBge3J9DQojTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIHBvcHVsYXRpb24gZGVuc2l0eSByYXN0ZXIsIHBvcA0KaGlzdChwb3AsIG1haW49TlVMTCwgbGFzPTEpDQpgYGANClRoaXMgZGF0YSBsb29rcyB2ZXJ5IHNrZXdlZCEgRGF0YSB0aGF0IGlzIGFwcHJveGltYXRlbHkgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgd291bGQgd29yayBiZXR0ZXIgaGVyZSAtIGxldCdzIHRyYW5zZm9ybSBvdXIgdmFyaWFibGUNCg0KYGBge3J9DQpwb3AubGcgPC0gbG9nKHBvcCkNCmhpc3QocG9wLmxnLCBtYWluPU5VTEwsIGxhcz0xKQ0KYGBgDQoNCg0KYGBge3J9DQojT25lIHdheSB0byBjYWxjdWxhdGUgdGhlIGludGVuc2l0eSBvZiB0aGUgcG9pbnQgcHJvY2VzcyBpcyB3aXRoIHRoZSBzdW1tYXJ5KCkgZnVuY3Rpb24NCnN1bW1hcnkoc3RhcmJ1Y2tzKQ0KYGBgDQoNClJlbWVtYmVyLCBwb2ludCBwYXR0ZXJuIGludGVuc2l0eSBpcyBhIGdsb2JhbCBvciBmaXJzdC1vcmRlciBjaGFyYWN0ZXJpc3RpYw0KDQpOZXh0LCB3ZSB3aWxsIGNhbGN1bGF0ZSBhIG1lYXN1cmUgb2YgZGVuc2l0eSB1c2luZyBhIHF1YWRyYXQgZ3JpZC4gSW4gdGhpcyB3YXksIHdlIGNhbiBsb29rIGF0IHRoZSBudW1iZXIgb2YgcG9pbnRzIGluIGVhY2ggcXVhZHJhdC4gDQoNCmBgYHtyfQ0KUSA8LSBxdWFkcmF0Y291bnQoc3RhcmJ1Y2tzLCBueD0gNiwgbnk9MykNCg0KcGxvdChzdGFyYnVja3MsIHBjaD0yMCwgY29scz0iZ3JleTcwIiwgbWFpbj1OVUxMKSAgIyBQbG90IHBvaW50cw0KcGxvdChRLCBhZGQ9VFJVRSkgICMgQWRkIHF1YWRyYXQgZ3JpZA0KYGBgDQpIZXJlLCB3ZSBoYXZlIHF1YWRyYXQgcG9pbnQgY291bnRzLiBMZXQncyBjYWxjdWxhdGUgdGhlIGRlbnNpdHkgb2YgcG9pbnRzIGluIGVhY2ggcXVhZHJhdCENCg0KYGBge3J9DQojIENvbXB1dGUgdGhlIGRlbnNpdHkgZm9yIGVhY2ggcXVhZHJhdA0KUS5kIDwtIGludGVuc2l0eShRKQ0KDQojIFBsb3QgdGhlIGRlbnNpdHkNCnBsb3QoaW50ZW5zaXR5KFEsIGltYWdlPVRSVUUpLCBtYWluPU5VTEwsIGxhcz0xKSAgIyBQbG90IGRlbnNpdHkgcmFzdGVyDQpwbG90KHN0YXJidWNrcywgcGNoPTIwLCBjZXg9MC42LCBjb2w9cmdiKDAsMCwwLC41KSwgYWRkPVRSVUUpICAjIEFkZCBwb2ludHMNCmBgYA0KSW4gdGhpcyBwbG90LCBibHVlIGFyZWFzIGFyZSBsb3cgZGVuc2l0eSwgcGluay9wdXJwbGUgYXJlIGludGVybWVkaWF0ZSwgYW5kIHllbGxvdy9vcmFuZ2UgYXJlIGhpZ2ggZGVuc2l0eSByZWdpb25zLiBUaGUgdW5pdHMgaGVyZSBhcmUgcG9pbnRzIHBlciBzcXVhcmUgbWV0ZXIuIFRob3NlIHVuaXRzIGFyZW4ndCB2ZXJ5IG5pY2UsIGFyZSB0aGV5PyBXZSBjYW4gcmVzY2FsZSB0aGVtIHRvIGtpbG9tZXRlcnMgbGlrZSB0aGlzOg0KDQpgYGB7cn0NCiNyZXNjYWxlIGFsbCBvZiBvdXIgZGF0YToNCg0Kc3RhcmJ1Y2tzLmttIDwtIHJlc2NhbGUoc3RhcmJ1Y2tzLCAxMDAwLCAia20iKQ0KbWEua20gPC0gcmVzY2FsZShtYSwgMTAwMCwgImttIikNCnBvcC5rbSAgICA8LSByZXNjYWxlKHBvcCwgMTAwMCwgImttIikNCnBvcC5sZy5rbSA8LSByZXNjYWxlKHBvcC5sZywgMTAwMCwgImttIikNCmBgYA0KDQpOb3cgd2UgY2FuIHBsb3QgaXQgYWdhaW4hDQoNCmBgYHtyfQ0KIyBDb21wdXRlIHRoZSBkZW5zaXR5IGZvciBlYWNoIHF1YWRyYXQgKGluIGNvdW50cyBwZXIga20yKQ0KUSAgIDwtIHF1YWRyYXRjb3VudChzdGFyYnVja3Mua20sIG54PSA2LCBueT0zKQ0KUS5kIDwtIGludGVuc2l0eShRKQ0KDQojIFBsb3QgdGhlIGRlbnNpdHkNCnBsb3QoaW50ZW5zaXR5KFEsIGltYWdlPVRSVUUpLCBtYWluPU5VTEwsIGxhcz0xKSAgIyBQbG90IGRlbnNpdHkgcmFzdGVyDQpwbG90KHN0YXJidWNrcy5rbSwgcGNoPTIwLCBjZXg9MC42LCBjb2w9cmdiKDAsMCwwLC41KSwgYWRkPVRSVUUpICAjIEFkZCBwb2ludHMNCmBgYA0KTm93IG91ciBzY2FsZSBsb29rcyBtdWNoIG5pY2VyLiBBbm90aGVyIHRoaW5nIHRvIG5vdGUgaXMgdGhhdCB3ZSBkbyBub3QgaGF2ZSB0byB1c2UgdW5pZm9ybWx5IHNxdWFyZSBxdWFkcmF0cy4gSGVyZSwgSSdsbCBzaG93IGFuIGV4YW1wbGUgb2YgdXNpbmcgdGhlIHBvcHVsYXRpb24gZGVuc2l0eSBvYmplY3QgdG8gY2FsY3VsYXRlIG5ldyBxdWFkcmF0cy4gVGhlIHJlZ2lvbnMgZGVmaW5lZCBieSBwb3B1bGF0aW9uIGRlbnNpdHkgYXJlIGNhbGxlZCAidGVzc2VsbGF0ZWQgc3VyZmFjZXMiLiANCg0KYGBge3J9DQoNCmJyayAgPC0gYyggLUluZiwgNCwgNiwgOCAsIEluZikgICMgRGVmaW5lIHRoZSBicmVha3MNClpjdXQgPC0gY3V0KHBvcC5sZy5rbSwgYnJlYWtzPWJyaywgbGFiZWxzPTE6NCkgICMgQ2xhc3NpZnkgdGhlIHJhc3Rlcg0KRSAgICA8LSB0ZXNzKGltYWdlPVpjdXQpICAjIENyZWF0ZSBhIHRlc3NlbGF0ZWQgc3VyZmFjZQ0KDQojTm93IHdlJ2xsIHBsb3QgaXQNCiNub3RlIHRoYXQgbGFzPTEgdGVsbHMgUiB0byBzaG93IHRoZSBsZWdlbmQgbGFiZWxzIHVwcmlnaHQgKGFzIG9wcG9zZWQgdG8gZmxpcHBlZCA5MCBkZWdyZWVzIHRvIHRoZSBsZWZ0IC0gdGhhdCBpcyB0aGUgZGVmYXVsdCkNCnBsb3QoRSwgbWFpbj0iIiwgbGFzPTEpDQoNCmBgYA0KDQpgYGB7cn0NCiNSZXBlYXQgdGhlIHNhbWUgZGVuc2l0eSBhbmFseXNpcyBhcyBiZWZvcmUsIGJ1dCB3aXRoIHRoZSBuZXcgcXVhZHJhdHMNCiN3ZSBqdXN0IG5lZWQgdG8gdGVsbCBSIHdoaWNoIHRlc3NlbGxhdGVkIG9iamVjdCB0byB1c2UgZm9yIHF1YWRyYXRzDQpRICAgPC0gcXVhZHJhdGNvdW50KHN0YXJidWNrcy5rbSwgdGVzcyA9IEUpICAjIFRhbGx5IGNvdW50cw0KUS5kIDwtIGludGVuc2l0eShRKSAgIyBDb21wdXRlIGRlbnNpdHkNCg0KIyBQbG90IHRoZSBkZW5zaXR5DQpwbG90KGludGVuc2l0eShRLCBpbWFnZT1UUlVFKSwgbWFpbj1OVUxMLCBsYXM9MSkgICMgUGxvdCBkZW5zaXR5IHJhc3Rlcg0KcGxvdChzdGFyYnVja3Mua20sIHBjaD0yMCwgY2V4PTAuNiwgY29sPXJnYigwLDAsMCwuNSksIGFkZD1UUlVFKSAgIyBBZGQgcG9pbnRzDQpgYGANCkFkdmFuY2VkOiBLZXJuZWwgRGVuc2l0eQ0KDQpBbm90aGVyIG1lYXN1cmUgb2YgZGVuc2l0eSBpcyBjYWxsZWQgS2VybmVsIERlbnNpdHkuIFJhdGhlciB0aGFuIHVzZSBxdWFkcmF0cywgdGhpcyBmb3JtIG9mIGRlbnNpdHkgaXMgY2FsY3VsYXRlZCB1c2luZyBhIG1vdmluZyBzdWItcmVnaW9uIHdpbmRvdywgY2FsbGVkIGEgS2VybmVsIHdpbmRvdy4gVGhpcyBpcyBxdWl0ZSBzaW1pbGFyIHRvIHRoZSBmb2NhbCBvcGVyYXRpb25zIHRoYXQgd2UgZGlzY3Vzc2VkIGluIG91ciByYXN0ZXIgdW5pdC4gVGhpcyBtZWFzdXJlIG9mIGRlbnNpdHkgaXMgbmljZSBiZWNhdXNlIGl0IGdpdmVzIHVzIGEgbW9yZSBkZXRhaWxlZCBwaWN0dXJlIG9mIHBvaW50IGRlbnNpdHkgYWNyb3NzIHRoZSBzdHVkeSB1bml0LiBXZSBjYW4gYWxzbyBjb252ZXJ0IHRoZSBvdXRwdXQgb2YgdGhlIGtlcm5lbCBkZW5zaXR5IGZ1bmN0aW9uIGludG8gYSByYXN0ZXIgb2JqZWN0LCB3aGljaCB3b3VsZCBhbGxvdyB1cyB0byB1c2UgcmFzdGVyIG1ldGhvZHMgb24gaXQuIA0KYGBge3J9DQpsaWJyYXJ5KHJhc3RlcikNCiNUaGlzIGlzIHJlYWxseSBlYXN5IC0gY2FsY3VsYXRlIGtlcm5lbCBkZW5zaXR5DQpkZW5zIDwtIGRlbnNpdHkoc3RhcmJ1Y2tzLmttKQ0KDQojY29udmVydCBpbnRvIGEgcmFzdGVyDQpyYXN0IDwtIHJhc3RlcihkZW5zKQ0KDQojdXNlIHRoZSBuYXRpdmUgcmFzdGVyIHBsb3R0aW5nIG1ldGhvZCB0byBwbG90IGl0DQpwbG90KHJhc3QpDQpgYGANCg0KYGBge3J9DQojQW5vdGhlciBjb29sIGZlYXR1cmUgb2Yga2VybmVsIGRlbnNpdHk6IHdlIGNhbiBwbG90IGl0IHVzaW5nIGNvbnRvdXIgbGluZXMNCg0KcGxvdChkZW5zLCBtYWluPU5VTEwsIGxhcz0xKQ0KY29udG91cihkZW5zLCBhZGQ9VFJVRSkNCmBgYA0KV2UgY2FuIGFsc28gY2hhbmdlIHRoZSBiYW5kd2lkdGggb2YgdGhlIGtlcm5lbCBkZW5zaXR5IGVzdGltYXRpb24uIEJ5IGRlZmF1bHQsIFIgY2hvb3NlcyB0aGUgYmFuZHdpZHRoIGZvciB1cywgYmFzZWQgb24gdGhlIHNpemUgb2YgdGhlIHBsb3R0aW5nIHdpbmRvdy4gV2UgY2FuIHJlLXNwZWNpZnkgdGhlIGJhbmR3aWR0aCB1c2luZyB0aGUgc2lnbWEgPSBhcmd1bWVudC4gSGVyZSwgSSBzaG93IGFuIGV4YW1wbGUgd2hlcmUgSSBjaGFuZ2UgdGhlIGJhbmR3aWR0aCB0byA1MCBrbS4gDQpgYGB7cn0NCmRlbnMyIDwtIGRlbnNpdHkoc3RhcmJ1Y2tzLmttLCBzaWdtYT01MCkgIyBVc2luZyBhIDUwa20gYmFuZHdpZHRoDQpwbG90KGRlbnMyLCBtYWluPU5VTEwsIGxhcz0xKQ0KY29udG91cihkZW5zMiwgYWRkPVRSVUUpDQoNCmBgYA0KDQoNCkxldCdzIHJldHVybiB0byBjb21wbGV0ZSBzcGF0aWFsIHJhbmRvbW5lc3MgZm9yIGEgbW9tZW50LiBJZiB3ZSB3YW50IHRvIHRlc3Qgd2hldGhlciBvdXIgcG9pbnQgZGF0YSB3YXMgZ2VuZXJhdGVkIGJ5IGNvbXBsZXRlIHNwYXRpYWwgcmFuZG9tbmVzcywgdGhlcmUgaXMgYSB2ZXJ5IHNpbXBsZSBmdW5jdGlvbiB0aGF0IHdpbGwgYWxsb3cgdXMgdG8gdGVzdCB0aGlzLiBSZW1lbWJlciB0aGF0LCB3aGVuZXZlciB3ZSBzZXQgdXAgc3RhdGlzdGljYWwgdGVzdHMsIHdlIGFsd2F5cyB3YW50IHRvIHNldCB1cCBhIG51bGwgYW5kIGFsdGVybmF0aXZlIGh5cG90aGVzaXMuIE91cnMgbWlnaHQgbG9vayBsaWtlIHRoaXM6DQoNCk51bGw6IFRoZSBwb2ludCBkYXRhIHdhcyBnZW5lcmF0ZWQgYnkgYSByYW5kb20gc3BhdGlhbCBwcm9jZXNzDQpBbHRlcm5hdGl2ZTogVGhlIHBvaW50IGRhdGEgd2FzIG5vdCBnZW5lcmF0ZWQgYnkgYSByYW5kb20gc3BhdGlhbCBwcm9jZXNzDQoNCk5vdyB3ZSBjYW4gcGVyZm9ybSBvdXIgdGVzdC4gU2luY2UgdGhpcyBpcyBub3QgYSBzdGF0aXN0aWNzIGNsYXNzLCBJIHdvbid0IGdvIGludG8gdGhlIHRlY2huaWNhbCBkZXRhaWxzIGhlcmUsIGJ1dCBpdCBzaG91bGQgYmUgZWFzeSBlbm91Z2ggdG8gaW50ZXJwcmV0IHRoZSByZXN1bHRzIG9mIHRoZSB0ZXN0LiBJZiB5b3Ugd2FudCB0byBsZWFybiBtb3JlIGFib3V0IHdoYXQgaXMgZ29pbmcgb24gdW5kZXIgdGhlIGhvb2Qgb2YgdGhpcyB0ZXN0LCB5b3UgY2FuIGFsd2F5cyBsb29rIGl0IHVwIGxhdGVyLg0KDQpgYGB7cn0NCnF1YWRyYXQudGVzdChzdGFyYnVja3Mua20pDQpgYGANCg0KT3VyIHRlc3Qgc3RhdGlzdGljIGlzIGEgQ2hpLXNxdWFyZWQgdmFsdWUgLSB3ZSBrbm93IHRoYXQgd2UgaGF2ZSBzdWZmaWNpZW50IGV2aWRlbmNlIHRvIHJlamVjdCB0aGUgbnVsbCBoeXBvdGhlc2lzIGlmIG91ciB0ZXN0IHN0YXRpc3RpYyBpcyBsYXJnZSBlbm91Z2guIFdpdGggYSBQLXZhbHVlIHdlbGwgdW5kZXIgMC4wNSwgd2UgY2FuIHNhZmVseSByZWplY3QgdGhlIG51bGwgaHlwb3RoZXNpcyBhbmQgY29uY2x1ZGUgdGhhdCBvdXIgcG9pbnQgcGF0dGVybiB3YXMgbm90IGdlbmVyYXRlZCBieSBhIHJhbmRvbSBzcGF0aWFsIHByb2Nlc3MuIA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KRGlzdGFuY2UtQmFzZWQgTWV0aG9kcw0KDQpMZXQncyBkaWcgZGVlcGVyIGludG8gdGhlIHBhdHRlcm4gb2YgY2x1c3RlcmluZy4gVGhlIGZpcnN0IG1ldGhvZCB3ZSdsbCBsb29rIGF0IGlzIGNhbGxlZCB0aGUgQXZlcmFnZSBOZWFyZXN0IE5laWdoYm9yIG1ldGhvZCAob3IgQU5OKS4gQU5OIGlzIGVzc2VudGlhbGx5IHRoZSBhdmVyYWdlIGRpc3RhbmNlIGJldHdlZW4gYWxsIHBvaW50cyB3aXRoaW4gYSBkYXRhc2V0IGFuZCB0aGVpciBpbmRpdmlkdWFsIG5lYXJlc3QgcG9pbnQgKGtub3duIGFzIE5lYXJlc3QtTmVpZ2hib3IgRGlzdGFuY2UgKE5ORCkpLiBXZSBjYW4gY29tcGFyZSB0aGUgYWN0dWFsIEFOTiBwYXR0ZXJuIGluIG91ciBkYXRhIHdpdGggdGhlIGV4cGVjdGVkIEFOTiBwYXR0ZXJuIHVuZGVyIENTUiBhcyBhIGdsb2JhbCBpbmRpY2F0b3Igb2YgdGhlIG92ZXJhbGwgY2x1c3RlcmluZyBpbiBvdXIgZGF0YS4gDQoNClRoaXMgaXMgc2ltaWxhciB0byBxdWFkcmF0IGFuYWx5c2lzLCBidXQgaXQgdXNlcyBkaXN0YW5jZSBpbnN0ZWFkLg0KDQpgYGB7cn0NCiNMZXQncyBjYWxjdWxhdGUgdGhlIGF2ZXJhZ2UgbmVhcmVzdCBuZWlnaGJvciBkaXN0YW5jZSENCm1lYW4obm5kaXN0KHN0YXJidWNrcy5rbSwgayA9IDEpKQ0KDQpgYGANClRoaXMgdGVsbHMgdXMgdGhhdCBlYWNoIHBvaW50IGlzLCBvbiBhdmVyYWdlLCAzLjI4IGttIGF3YXkgZnJvbSBpdHMgbmVhcmVzdCBuZWlnaGJvcg0KDQpgYGB7cn0NCiNOZXh0IHdlJ2xsIHBsb3QgdGhlIEFOTiBmb3IgZGlmZmVyZW50IG51bWJlcnMgb2YgbmVpZ2hib3JzIChLKQ0KDQpBTk4gPC0gYXBwbHkobm5kaXN0KHN0YXJidWNrcy5rbSwgaz0xOjEwMCksMixGVU49bWVhbikNCnBsb3QoQU5OIH4gZXZhbCgxOjEwMCksIHR5cGU9ImIiLCBtYWluPU5VTEwsIGxhcz0xKQ0KYGBgDQpUaGlzIHNob3dzIHVzIHRoYXQgdGhlIEFOTiBpbmNyZWFzZXMgYXMgdGhlIG51bWJlciBvZiBuZWlnaGJvcnMgaW5jcmVhc2VzLCBidXQgaXQgZG9lc24ndCBnaXZlIHVzIHRvbyBtdWNoIGluZm8uIEFOTiBpcyBwYXJ0aWN1bGFybHkgaGVscGZ1bCBpZiB3ZSB3YW50IHRvIGNvbXBhcmUgY2x1c3RlcmluZyBpbiBvbmUgcG9pbnQgZGF0YXNldCB0byBhbm90aGVyLiANCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCk91ciBuZXh0IHN0YXRpc3RpYyB3aWxsIGdpdmUgdXMgYSBsb3QgbW9yZSBkZXRhaWxzIGFib3V0IHRoZSBwYXR0ZXJuIG9mIGNsdXN0ZXJpbmcgaW4gb3VyIGRhdGEuIA0KDQpFc3NlbnRpYWxseSwgVGhlIGJsYWNrIGxpbmUgKEstcG9pcykgcmVwcmVzZW50cyB0aGUgdGhlb3JldGljYWwgIA0KSy1mdW5jdGlvbiB1bmRlciB0aGUgbnVsbCBoeXBvdGhlc2lzIHRoYXQgdGhlIHBvaW50cyBhcmUgY29tcGxldGVseSByYW5kb21seSBkaXN0cmlidXRlZCAoQ1NSL0lSUCkuIFdoZXJlICBLDQpmYWxscyB1bmRlciB0aGUgdGhlb3JldGljYWwgSy1wb2lzIGxpbmUsIHRoZSBwb2ludHMgYXJlIGRlZW1lZCBtb3JlIGRpc3BlcnNlZCB0aGFuIGV4cGVjdGVkIGF0IGRpc3RhbmNlIHIuIFdoZXJlIEsgZmFsbHMgYWJvdmUgdGhlIHRoZW9yZXRpY2FsIGstcG9pcyBsaW5lLCB0aGUgcG9pbnRzIGFyZSBkZWVtZWQgbW9yZSBjbHVzdGVyZWQgdGhhbiBleHBlY3RlZCBhdCBkaXN0YW5jZSByLg0KDQpDYWxjdWxhdGlvbjogUmlwbGV54oCZcyBLIGZ1bmN0aW9uIGVzc2VudGlhbGx5IHN1bW1hcmlzZXMgdGhlIGRpc3RhbmNlIGJldHdlZW4gcG9pbnRzIGZvciBhbGwgZGlzdGFuY2VzIHVzaW5nIHJhZGlhbCBkaXN0YW5jZSBiYW5kcy4gVGhlIGNhbGN1bGF0aW9uIGlzIHJlbGF0aXZlbHkgc3RyYWlnaHRmb3J3YXJkIChXaWxraW4sIDIwMTUpOg0KDQotRm9yIHBvaW50IGV2ZW50IEEsIGNvdW50IHRoZSBudW1iZXIgb2YgcG9pbnRzIGluc2lkZSBhIGJ1ZmZlciAocmFkaXVzKSBvZiBhIGNlcnRhaW4gc2l6ZS4gVGhlbiBjb3VudCB0aGUgbnVtYmVyIG9mIHBvaW50cyBpbnNpZGUgYSBzbGlnaHRseSBsYXJnZXIgYnVmZmVyIChyYWRpdXMpLg0KLVJlcGVhdCB0aGlzIGZvciBldmVyeSBwb2ludCBldmVudCBpbiB0aGUgZGF0YXNldC4NCi1Db21wdXRlIHRoZSBhdmVyYWdlIG51bWJlciBvZiBwb2ludHMgaW4gZWFjaCBidWZmZXIgKHJhZGl1cykgYW5kIGRpdmlkZSB0aGlzIGJ5IHRoZSBvdmVyYWxsIHBvaW50IGRlbnNpdHkuDQotUmVwZWF0IHRoaXMgdXNpbmcgcG9pbnRzIGRyYXduIGZyb20gYSBQb2lzc29uIHJhbmRvbSBtb2RlbCBmb3IgdGhlIHNhbWUgc2V0IG9mIGJ1ZmZlcnMgKGRvbid0IHdvcnJ5IHRvbyBtdWNoIGFib3V0IHRoaXMgbW9kZWwgLSB5b3UganVzdCBuZWVkIHRvIGJlIGFibGUgdG8gaW50ZXJwcmV0IHRoZSBwbG90KS4NCi1Db21wYXJlIHRoZSBvYnNlcnZlZCBkaXN0cmlidXRpb24gd2l0aCB0aGUgZGlzdHJpYnV0aW9uIHdpdGggdGhlIFBvaXNzb24gZGlzdHJpYnV0aW9uLg0KDQpOb3RlIHRoYXQgdGhpcyBmdW5jdGlvbiB3aWxsIGJlIHZlcnkgc2xvdyBmb3IgbGFyZ2UgZGF0YXNldHMuIA0KDQpgYGB7cn0NCnBsb3QoS2VzdChzdGFyYnVja3Mua20sIGNvcnJlY3Rpb24gPSAiYm9yZGVyIikpDQpgYGANCg0KQXQgYSByYWRpdXMgb2YgYWJvdXQgMC0zMCBrbSwgdGhlIHBvaW50cyBhcmUgbXVjaCBtb3JlIGNsdXN0ZXJlZCB0aGFuIHdlIHdvdWxkIGV4cGVjdCwgZ2l2ZW4gYSByYW5kb20gc3BhdGlhbCBwcm9jZXNzLiBHcmVhdGVyIHRoYW4gMzBrbSwgdGhlIHBhdHRlcm4gaXMgYSBiaXQgbGVzcyBjbGVhciwgYW5kIG1heSBldmVuIGJlY29tZSBtb3JlIGRpc3BlcnNlZCB0aGFuIGV4cGVjdGVkLiBPdmVyYWxsLCB0aGlzIGdpdmVzIHVzIGEgdmVyeSBjbGVhciBwaWN0dXJlIHRoYXQgb3VyIGRhdGEgaXMgdW5saWtlbHkgdG8gaGF2ZSBiZWVuIGdlbmVyYXRlZCByYW5kb21seS4gDQoNCk9uZSBjYXZlYXQgd2l0aCB0aGlzIGFuYWx5c2lzOiBpdCBhc3N1bWVzIHRoYXQgb3VyIHBvaW50IHByb2Nlc3MgaXMgc3RhdGlvbmFyeSAoaS5lLiB0aGF0IGl0IGRvZXNuJ3QgY2hhbmdlIGFjcm9zcyBzcGFjZSkuIFRoaXMgbWF5IHBvc2Ugc29tZSB2YWxpZGl0eSBjb25jZXJucyBmb3Igb3VyIEstZnVuY3Rpb24gKHRoZSBzYW1lIGlzIGFsc28gdHJ1ZSBmb3IgQU5OIGFuYWx5c2lzKS4gDQoNCkFub3RoZXIgaXNzdWUgaXMgdGhhdCBpdCBjYW4gYWxzbyBiZSBjaGFsbGVuZ2luZyB0byBzZWUgc21hbGwgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgYWN0dWFsIGFuZCBwcmVkaWN0ZWQgdmFsdWVzLiBUaGUgTC1mdW5jdGlvbiBpcyBhIGNvcnJlY3RlZCB2ZXJzaW9uIG9mIHRoZSBLLWZ1bmN0aW9uIHRoYXQgdHJhbnNmb3JtcyB0aGUgSyBmdW5jdGlvbiBpbnRvIGEgc3RyYWlnaHQgbGluZSAtIHRoaXMgc2hvdWxkIG1ha2UgdGhlIGRpZmZlcmVuY2UgZWFzaWVyIHRvIHZpc3VhbGl6ZS4gDQoNCmBgYHtyfQ0KcGxvdChMZXN0KHN0YXJidWNrcy5rbSwgY29ycmVjdGlvbiA9ICJib3JkZXIiKSkNCmBgYA0KDQpSZXNvdXJjZXMNCg0KRG9ybWFuLCBNLiAoMjAyMikuIENoYXB0ZXIgMTMgUG9pbnQgcGF0dGVybiBhbmFseXNpcyAoaW4gcHJlcGFyYXRpb24pLiBSZXRyaWV2ZWQgZnJvbTogaHR0cHM6Ly9nZW9iZ3UueHl6L3IvcG9pbnQtcGF0dGVybi1hbmFseXNpcy5odG1sLg0KDQpHaW1vbmQsIE0uICgyMDIyKS4gQ2hhcHRlciAxMTogUG9pbnQgUGF0dGVybiBBbmFseXNpcy4gUmV0cmlldmVkIGZyb206IGh0dHBzOi8vbWdpbW9uZC5naXRodWIuaW8vU3BhdGlhbC9jaHAxMV8wLmh0bWwuDQoNClBlZXBsZXMsIE0uIChOLiBELikuIFBvaW50IFBhdHRlcm4gQW5hbHlzaXMuIFJldHJpZXZlZCBmcm9tOiBodHRwczovL3d3dy5tYXR0cGVlcGxlcy5uZXQvbW9kdWxlcy9Qb2ludFBhdHRlcm4uaHRtbA0KDQpSIFNwYXRpYWwsICgyMDIxKS4gUG9pbnQgUGF0dGVybiBBbmFseXNpcy4gUmV0cmlldmVkIGZyb206IGh0dHBzOi8vcnNwYXRpYWwub3JnL3RlcnJhL2FuYWx5c2lzLzctcG9pbnRwYXQuaHRtbC4gDQoNCldpbGtpbiwgSi4gKDIwMTUpLiA4IEFuYWx5c2luZyBTcGF0aWFsIFBhdHRlcm5zIElJSTogUG9pbnQgUGF0dGVybiBBbmFseXNpcy4gUmV0cmlldmVkIGZyb206IGh0dHBzOi8vam8td2lsa2luLmdpdGh1Yi5pby9HRU9HMDAzMC9jb3Vyc2Vib29rL2FuYWx5c2luZy1zcGF0aWFsLXBhdHRlcm5zLWlpaS1wb2ludC1wYXR0ZXJuLWFuYWx5c2lzLmh0bWwuIA==