Brett’s GitHub Page
For other R Notebooks on our evolving ideas about geomorphology, see Brett’s GitHub Page
Overview
One of the basic measurements using in the study of gravel bed rivers is the bed surface grain size distribution. This distribution is used to compare one site to another, and to infer relative sediment supply by calculating the armour ratio. It is also often used to track changes in stream conditions following a change in sediment supply or flow regime. However, while geomorphologists often discuss bed surface texture variability, this is almost never done in the context of uncertainty of the estimates from a particular sample. Simply put, we often do not consider the statistical power of our samples, which depends on the size of the sample taken (i.e., how many stones were measured), and on the nature of the distribution being sampled.
However, binomial theory can be used to estimate the uncertainty of any estimate of the surface percentile.
Grain size data
let’s first load some data, and turn it into a data frame containing all measurements from all locations along the stream table. Since most bed surface samples are taken in the field, we will apply a 1:25 scale ratio to the data in order to translate our model grain sizes into prototype equivalent grain sizes.
rm(list = ls())
library(reshape2)
Lr = 25 #specify the scale ratio for the model: prototype relation
grain.sizes = read.csv('Data/GSD1HIGHQ_4_grainmeasurements.csv')
all.data = melt(grain.sizes, variable.name = "location", value.name = "D")
No id variables; using all as measure variables
all.data$D = all.data$D * Lr
Now lets perform a cumulative frequency distribution analysis on the data.
max.phi = ceiling(log2(max(all.data$D)))
min.phi = floor(log2(min(all.data$D)))
x = seq(from = min.phi, to = max.phi, by = 0.5)
results = hist(all.data$D,
breaks = 2^x,
plot = FALSE)
cfd = data.frame(x)
cfd$y = c(0, cumsum(results$counts))/sum(results$counts)
plot(2^cfd$x, cfd$y,
type = "b",
col = "blue",
log = "x",
xlab = "grain size (mm)",
ylab = "cum. prop. finer")
We can estimate the percentiles from these data as follows:
phi.est = approx(x = cfd$y, y = cfd$x, xout = 0.50)
phi.est$di = 2^phi.est$y
Note that we have interpolated based on the phi sizes, and then converted those values to grain sizes in mm. A comparison shows that there are small but consistent biases introduced by applying a linear interpolation to the grain size data rather than the phi data. The fact that we typically display the grain size using log2(D)
as the x axis and then often estimate grain sizes graphically implies that we believe any interpolation should linearly relate the cumulative proportion finer to log2(D)
.
size.est = approx(x = cfd$y, y = 2^cfd$x, xout = 0.50)
phi.est$di
[1] 32.86458
size.est$y
[1] 33.0196
These errors are small compared to the uncertainties associated with the estimates of each percentile, based on binomial theory.
Estimating confidence intervals from binomial theory
We can use binomial theory to estimate the uncertainty of each percentile (in terms of percentile values), and then translate these uncertainties into ranges of grain sizes using the sample data.
For example, we know that for the median size, the expected number of observations less than the median value ought to be \(np\), where \(n\) is the total number of observations and \(p\) is the percentile of interest (i.e. 0.50 in this case). However, binomial theory implies that the actual number of observations less than this value is a normally distributed quantity with a mean value of \(np\) and a standard deviation of \(\sqrt{np(1-p)}\). It is quite easily to calculate the confidence interval for any percentile in terms of the percentile values themselves.
alpha = 0.05
percentile = seq(0.1,0.9,0.1)
n = c(100,200,400)
tcrit = qt(1 - alpha/2, n)
ci.upper = matrix(data = NA, nrow = length(percentile), ncol = length(n))
ci.lower = matrix(data = NA, nrow = length(percentile), ncol = length(n))
for (i in seq_along(percentile)){
sigma = sqrt(n*percentile[i]*(1-percentile[i]))
ci.upper[i,] = round((n*percentile[i] + tcrit*sigma)/n, digits = 2)
ci.lower[i,] = round((n*percentile[i] - tcrit*sigma)/n, digits = 2)
}
ci.percentile = data.frame(percentile,
ci.lower[,1],
ci.upper[,1],
ci.lower[,2],
ci.upper[,2],
ci.lower[,3],
ci.upper[,3] )
names(ci.percentile) = c('Percentile', 'L100', 'U100', 'L200', 'U200','L400', 'U400' )
ci.percentile
For example, the 50th percentile of the distribution estimated from a sample of 100 observations could fall between the 40th percentile and the 60th percentile of the sample data. For a sample of 400 observations, the 50th percentile would be expected to fall between the 45th and the 55th percentile of the sample.
In order to estimate the confidence interval in mm, it is necessary to know something about the underlying distribution. A function that estimates the percentile size and confidence bounds for any percentile and confidence limit, \(\alpha\), can be written as follows:
ErrorBounds = function(n, p, cfd, alpha = 0.05){
tcrit = qt(1 - alpha/2, n)
sigma = sqrt(n*p*(1-p))
p.upper = (n*p + tcrit*sigma)/n
p.lower = (n*p - tcrit*sigma)/n
bounds = c(p.lower, p.upper, p)
estimates = approx(x = cfd$y, y = cfd$x, xout = bounds)
estimates$y = 2^estimates$y
return(estimates)
}
The error bounds around our sample of 500 measurements for various percentiles are easily calculated:
conf.50 = ErrorBounds(500, 0.5, cfd)
conf.84 = ErrorBounds(500, 0.84, cfd)
conf.90 = ErrorBounds(500, 0.90, cfd)
conf.95 = ErrorBounds(500, 0.95, cfd)
For such a large sample, the uncertainty range for the \(D_{50}\) is 30 – 36 mm. The error bounds around the \(D_{84}\) are larger, at about 59 – 74 mm. It is even bigger for the largest grain sizes: the uncertainty of the \(D_{90}\) is about 74 – 98 mm.
The size of the sample has a great effect on the uncertainty range. For example, consider the confidence intervals about the \(D_{50}\), calculated and plotted below.
N = seq(from = 100, to = 500, by = 5)
D50.civn = matrix(data = NA, nrow = length(N), ncol = 3)
for (i in seq_along(N)){
D50.civn[i,] = ErrorBounds(N[i], 0.50, cfd)[[2]]
}
matplot(N, D50.civn,
type = "l",
log = "y",
ylim = 2^c(min.phi, max.phi),
lty = 1)
abline(h = 2^x,
lwd = 0.5,
lty = 2)
abline(v = seq(100,500,50),
lwd = 0.5,
lty = 2)
For a sample of 100 stones, the confidence interval for \(D_{50}\) is 27 – 40 mm, but for a sample of 200 stones the confidence interval is 29 – 38 mm, and for a 400 stone sample is 30 – 36 mm. (The estimate of \(D_{50}\) in all cases is 33 mm). Generally, the confidence interval is approximately symmetrical about the estimate, but this is not always true, and depends on the underlying distribution. Thus, while the confidence interval represents about 37% of the estimate value for \(N = 100\), it represents only 18% for \(N = 400\). Clearly, the desired level of precision must be considered when designing a bed surface sampling strategy.
The issue is rather more important when estimating larger-than-average grain sizes. Consider how the uncertainty for the \(D_{84}\) varies with the size of the sample.
N = seq(from = 100, to = 500, by = 5)
D84.civn = matrix(data = NA, nrow = length(N), ncol = 3)
for (i in seq_along(N)){
D84.civn[i,] = ErrorBounds(N[i], 0.84, cfd)[[2]]
}
matplot(N, D84.civn,
type = "l",
log = "y",
ylim = 2^c(min.phi, max.phi),
lty = 1)
abline(h = 2^x,
lwd = 0.5,
lty = 2)
abline(v = seq(100,500,50),
lwd = 0.5,
lty = 2)
For a sample of 100 stones, the confidence interval for \(D_{84}\) is 55 – 92 mm, but for a 400 stone sample is 59 – 75 mm. (The estimate of \(D_{84}\) is 63 mm). Because of the underlying distribution, the confidence interval is not symmetrical about the estimate, particularly for small sample sizes. In this case the confidence interval represents about 58% of the estimate value for \(N = 100\), and 26% for \(N = 400\).
For the \(D_{95}\), the analysis looks like this:
N = seq(from = 100, to = 500, by = 5)
D95.civn = matrix(data = NA, nrow = length(N), ncol = 3)
for (i in seq_along(N)){
D95.civn[i,] = ErrorBounds(N[i], 0.95, cfd)[[2]]
}
matplot(N, D95.civn,
type = "l",
log = "y",
ylim = 2^c(min.phi, max.phi),
lty = 1)
abline(h = 2^x,
lwd = 0.5,
lty = 2)
abline(v = seq(100,500,50),
lwd = 0.5,
lty = 2)
For a sample of 100 stones, the confidence interval for \(D_{95}\) is 89 – 173 mm, but for a 400 stone sample is 99 – 121 mm. (The estimate of \(D_{95}\) is 109 mm). For small values of \(N\), the confidence interval is not symmetrical about the estimate. In this case the confidence interval represents about 77% of the estimate value for \(N = 100\), and 20% for \(N = 400\).
Choosing a sample size
In order to choose an appropriate sample size, it is first necessary to have a sense of the shape of the distribution, and to know what percentiles are of interest. For example, if we assume the data used herein is reasonably representative of the bed surface of interest (in terms of distribution shape, at least), then we can easily model the relation between the confidence interval width and the number of measurements taken.
ci.width50 = 100*(D50.civn[,2] - D50.civn[,1])/D50.civn[,3]
plot(N, ci.width50,
type = "l",
ylab = "C.I. Width (% of est.)",
col = "blue")
abline(h = seq(5,95,5),
lwd = 0.5,
lty = 2)
abline(v = seq(100,500,50),
lwd = 0.5,
lty = 2)
If we wish to have a confidence interval no wider than about 30% of the estimate of the \(D_{50}\), then we would need a sample size of 155.
However, if we wish to have equivalent confidence in an estimate of \(D_{90}\), our sample size would have to be larger.
D90.civn = matrix(data = NA, nrow = length(N), ncol = 3)
for (i in seq_along(N)){
D90.civn[i,] = ErrorBounds(N[i], 0.90, cfd)[[2]]
}
ci.width90 = 100*(D90.civn[,2] - D90.civn[,1])/D90.civn[,3]
plot(N, ci.width90,
type = "l",
ylab = "C.I. Width (% of est.)",
col = "blue")
abline(h = seq(5,95,5),
lwd = 0.5,
lty = 2)
abline(v = seq(100,500,50),
lwd = 0.5,
lty = 2)
In fact, we would need a sample of size \(N =\) 415 have a confidence interval no greater than 30% of the estimate.
Actual Sample Variability
We can use our sample of 500 measurements to estimate this sampling effect by subsetting the data, and using the subset to calculate the median grain size. In this case, we assume that our sample of 500 measurements represents the underlying population of grains, and that the true median value is 33 mm. Since the sample of 500 measurements is in fact a sample itself of the underlying population, this assumption is not strictly true, and could introduce some small distortions in the comparision.
k = 100 #set the sample size
m = 1000 #set the number of times to sample the data
sample.50 = vector(mode = "numeric", length = m)
for(i in seq_along(sample.50)){
sample.data = sample(all.data$D, size = k)
sample.hist = hist(sample.data,
breaks = 2^x,
plot = FALSE)
sample.cfd = c(0, cumsum(sample.hist$counts))/sum(sample.hist$counts)
sample.50[i] = 2^approx(x = sample.cfd, y = x, xout = 0.50)[[2]]
}
boxplot(sample.50,
ylim = D50.civn[which(N == k),1:2]*c(0.8, 1.2))
abline(h = D50.civn[which(N == k),],
col = "red",
lty = 2)
On the plot, the confidence bounds estimated from binomial theory and the median based on all data are shown as dashed red lines, and the boxplot presents the distribution of estimates based on sampling the original distribution with a sample size of 100. There are two points to be made: (1) the effect of sample size does introduce significant uncertainty into the estimate of the median size; and (2) the binonmial theory captures this uncertainty very well.
For this analysis, we find 13 observations that are outside the 95% confidence intervals predicted by binomial theory. Based on the sample size, we would expect 50 observations to fall outside of the confidence intervals.
For fun, lets see what happens with a larger sample size.
k = 200 #set the sample size
m = 1000 #set the number of times to sample the data
sample.50 = vector(mode = "numeric", length = m)
for(i in seq_along(sample.50)){
sample.data = sample(all.data$D, size = k)
sample.hist = hist(sample.data,
breaks = 2^x,
plot = FALSE)
sample.cfd = c(0, cumsum(sample.hist$counts))/sum(sample.hist$counts)
sample.50[i] = 2^approx(x = sample.cfd, y = x, xout = 0.50)[[2]]
}
boxplot(sample.50,
ylim = D50.civn[which(N == k),1:2]*c(0.8, 1.2))
abline(h = D50.civn[which(N == k),1:2],
col = "red",
lty = 2)
We find that 13 observations fall outside the 95% confidence intervals predicted by binomial theory; this is presumably related to the fact that samples of 200 observations taken from a population of 500 observations will naturally have a smaller variability than if they were taken from the true grain size population (i.e., the samples can hardly be considered to be independent). Nevertheless, there is still substantial variability in the estimate of the median, and it is similar to the range predicted by binomial theory.
And what about larger grain sizes, like the \(D_{84}\)?
k = 100 #set the sample size
m = 1000 #set the number of times to sample the data
sample.84 = vector(mode = "numeric", length = m)
for(i in seq_along(sample.84)){
sample.data = sample(all.data$D, size = k)
sample.hist = hist(sample.data,
breaks = 2^x,
plot = FALSE)
sample.cfd = c(0, cumsum(sample.hist$counts))/sum(sample.hist$counts)
sample.84[i] = 2^approx(x = sample.cfd, y = x, xout = 0.84)[[2]]
}
boxplot(sample.84,
ylim = D84.civn[which(N == k),1:2]*c(0.8, 1.2))
abline(h = D84.civn[which(N == k),],
col = "red",
lty = 2)
For this grain size, We find that 13 observations fall outside the 95% confidence intervals.
LS0tCnRpdGxlOiAiRXN0aW1hdGluZyB1bmNlcnRhaW50eSBpbiBiZWQgc3VyZmFjZSBncmFpbiBzaXplcyBmcm9tIGJpbm9taWFsIHRoZW9yeSIKYXV0aG9yOiAiRGFuIE1vb3JlLCBMdWN5IE1hY0tlbnppZSwgQnJldHQgRWF0b24sIEdlb2dyYXBoeSwgVUJDIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHllcwogIGh0bWxfbm90ZWJvb2s6CiAgICBkZl9wcmludDogcGFnZWQKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0aGVtZTogc3BhY2VsYWIKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwotLS0KI0JyZXR0J3MgR2l0SHViIFBhZ2UKRm9yIG90aGVyIFIgTm90ZWJvb2tzIG9uIG91ciBldm9sdmluZyBpZGVhcyBhYm91dCBnZW9tb3JwaG9sb2d5LCBzZWUgW0JyZXR0J3MgR2l0SHViIFBhZ2VdKGh0dHBzOi8vYmNlYXRvbi5naXRodWIuaW8pCgojT3ZlcnZpZXcKCk9uZSBvZiB0aGUgYmFzaWMgbWVhc3VyZW1lbnRzIHVzaW5nIGluIHRoZSBzdHVkeSBvZiBncmF2ZWwgYmVkIHJpdmVycyBpcyB0aGUgYmVkIHN1cmZhY2UgZ3JhaW4gc2l6ZSBkaXN0cmlidXRpb24uIFRoaXMgZGlzdHJpYnV0aW9uIGlzIHVzZWQgdG8gY29tcGFyZSBvbmUgc2l0ZSB0byBhbm90aGVyLCBhbmQgdG8gaW5mZXIgcmVsYXRpdmUgc2VkaW1lbnQgc3VwcGx5IGJ5IGNhbGN1bGF0aW5nIHRoZSBhcm1vdXIgcmF0aW8uIEl0IGlzIGFsc28gb2Z0ZW4gdXNlZCB0byB0cmFjayBjaGFuZ2VzIGluIHN0cmVhbSBjb25kaXRpb25zIGZvbGxvd2luZyBhIGNoYW5nZSBpbiBzZWRpbWVudCBzdXBwbHkgb3IgZmxvdyByZWdpbWUuIEhvd2V2ZXIsIHdoaWxlIGdlb21vcnBob2xvZ2lzdHMgb2Z0ZW4gZGlzY3VzcyBiZWQgc3VyZmFjZSB0ZXh0dXJlIHZhcmlhYmlsaXR5LCB0aGlzIGlzIGFsbW9zdCBuZXZlciBkb25lIGluIHRoZSBjb250ZXh0IG9mIHVuY2VydGFpbnR5IG9mIHRoZSBlc3RpbWF0ZXMgZnJvbSBhIHBhcnRpY3VsYXIgc2FtcGxlLiBTaW1wbHkgcHV0LCB3ZSBvZnRlbiBkbyBub3QgY29uc2lkZXIgdGhlIHN0YXRpc3RpY2FsIHBvd2VyIG9mIG91ciBzYW1wbGVzLCB3aGljaCBkZXBlbmRzIG9uIHRoZSBzaXplIG9mIHRoZSBzYW1wbGUgdGFrZW4gKGkuZS4sIGhvdyBtYW55IHN0b25lcyB3ZXJlIG1lYXN1cmVkKSwgYW5kIG9uIHRoZSBuYXR1cmUgb2YgdGhlIGRpc3RyaWJ1dGlvbiBiZWluZyBzYW1wbGVkLgoKSG93ZXZlciwgYmlub21pYWwgdGhlb3J5IGNhbiBiZSB1c2VkIHRvIGVzdGltYXRlIHRoZSB1bmNlcnRhaW50eSBvZiBhbnkgZXN0aW1hdGUgb2YgdGhlIHN1cmZhY2UgcGVyY2VudGlsZS4KCiNHcmFpbiBzaXplIGRhdGEKbGV0J3MgZmlyc3QgbG9hZCBzb21lIGRhdGEsIGFuZCB0dXJuIGl0IGludG8gYSBkYXRhIGZyYW1lIGNvbnRhaW5pbmcgYWxsIG1lYXN1cmVtZW50cyBmcm9tIGFsbCBsb2NhdGlvbnMgYWxvbmcgdGhlIHN0cmVhbSB0YWJsZS4gU2luY2UgbW9zdCBiZWQgc3VyZmFjZSBzYW1wbGVzIGFyZSB0YWtlbiBpbiB0aGUgZmllbGQsIHdlIHdpbGwgYXBwbHkgYSAxOjI1IHNjYWxlIHJhdGlvIHRvIHRoZSBkYXRhIGluIG9yZGVyIHRvIHRyYW5zbGF0ZSBvdXIgbW9kZWwgZ3JhaW4gc2l6ZXMgaW50byBwcm90b3R5cGUgZXF1aXZhbGVudCBncmFpbiBzaXplcy4KYGBge3J9CnJtKGxpc3QgPSBscygpKQpsaWJyYXJ5KHJlc2hhcGUyKQpMciA9IDI1ICAjc3BlY2lmeSB0aGUgc2NhbGUgcmF0aW8gZm9yIHRoZSBtb2RlbDogcHJvdG90eXBlIHJlbGF0aW9uCmdyYWluLnNpemVzID0gcmVhZC5jc3YoJ0RhdGEvR1NEMUhJR0hRXzRfZ3JhaW5tZWFzdXJlbWVudHMuY3N2JykKYWxsLmRhdGEgPSBtZWx0KGdyYWluLnNpemVzLCB2YXJpYWJsZS5uYW1lID0gImxvY2F0aW9uIiwgdmFsdWUubmFtZSA9ICJEIikKYWxsLmRhdGEkRCA9IGFsbC5kYXRhJEQgKiBMcgpgYGAKCk5vdyBsZXRzIHBlcmZvcm0gYSBjdW11bGF0aXZlIGZyZXF1ZW5jeSBkaXN0cmlidXRpb24gYW5hbHlzaXMgb24gdGhlIGRhdGEuCmBgYHtyfQptYXgucGhpID0gY2VpbGluZyhsb2cyKG1heChhbGwuZGF0YSREKSkpCm1pbi5waGkgPSBmbG9vcihsb2cyKG1pbihhbGwuZGF0YSREKSkpCnggPSBzZXEoZnJvbSA9IG1pbi5waGksIHRvID0gbWF4LnBoaSwgYnkgPSAwLjUpCnJlc3VsdHMgPSBoaXN0KGFsbC5kYXRhJEQsIAogICAgICAgICAgICAgICBicmVha3MgPSAyXngsCiAgICAgICAgICAgICAgIHBsb3QgPSBGQUxTRSkKY2ZkID0gZGF0YS5mcmFtZSh4KQpjZmQkeSA9IGMoMCwgY3Vtc3VtKHJlc3VsdHMkY291bnRzKSkvc3VtKHJlc3VsdHMkY291bnRzKQoKcGxvdCgyXmNmZCR4LCBjZmQkeSwKICAgICB0eXBlID0gImIiLAogICAgIGNvbCA9ICJibHVlIiwKICAgICBsb2cgPSAieCIsCiAgICAgeGxhYiA9ICJncmFpbiBzaXplIChtbSkiLAogICAgIHlsYWIgPSAiY3VtLiBwcm9wLiBmaW5lciIpCmBgYAoKV2UgY2FuIGVzdGltYXRlIHRoZSBwZXJjZW50aWxlcyBmcm9tIHRoZXNlIGRhdGEgYXMgZm9sbG93czoKCmBgYHtyfQpwaGkuZXN0ID0gYXBwcm94KHggPSBjZmQkeSwgeSA9IGNmZCR4LCB4b3V0ID0gMC41MCkKcGhpLmVzdCRkaSA9IDJecGhpLmVzdCR5CmBgYAoKTm90ZSB0aGF0IHdlIGhhdmUgaW50ZXJwb2xhdGVkIGJhc2VkIG9uIHRoZSBwaGkgc2l6ZXMsIGFuZCB0aGVuIGNvbnZlcnRlZCB0aG9zZSB2YWx1ZXMgdG8gZ3JhaW4gc2l6ZXMgaW4gbW0uIEEgY29tcGFyaXNvbiBzaG93cyB0aGF0IHRoZXJlIGFyZSBzbWFsbCBidXQgY29uc2lzdGVudCBiaWFzZXMgaW50cm9kdWNlZCBieSBhcHBseWluZyBhIGxpbmVhciBpbnRlcnBvbGF0aW9uIHRvIHRoZSBncmFpbiBzaXplIGRhdGEgcmF0aGVyIHRoYW4gdGhlIHBoaSBkYXRhLiBUaGUgZmFjdCB0aGF0IHdlIHR5cGljYWxseSBkaXNwbGF5IHRoZSBncmFpbiBzaXplIHVzaW5nIGBsb2cyKEQpYCBhcyB0aGUgeCBheGlzIGFuZCB0aGVuIG9mdGVuIGVzdGltYXRlIGdyYWluIHNpemVzIGdyYXBoaWNhbGx5IGltcGxpZXMgdGhhdCB3ZSBiZWxpZXZlIGFueSBpbnRlcnBvbGF0aW9uIHNob3VsZCBsaW5lYXJseSByZWxhdGUgdGhlIGN1bXVsYXRpdmUgcHJvcG9ydGlvbiBmaW5lciB0byBgbG9nMihEKWAuCgpgYGB7cn0Kc2l6ZS5lc3QgPSBhcHByb3goeCA9IGNmZCR5LCB5ID0gMl5jZmQkeCwgeG91dCA9IDAuNTApCnBoaS5lc3QkZGkKc2l6ZS5lc3QkeQpgYGAKClRoZXNlIGVycm9ycyBhcmUgc21hbGwgY29tcGFyZWQgdG8gdGhlIHVuY2VydGFpbnRpZXMgYXNzb2NpYXRlZCB3aXRoIHRoZSBlc3RpbWF0ZXMgb2YgZWFjaCBwZXJjZW50aWxlLCBiYXNlZCBvbiBiaW5vbWlhbCB0aGVvcnkuIAoKI0VzdGltYXRpbmcgY29uZmlkZW5jZSBpbnRlcnZhbHMgZnJvbSBiaW5vbWlhbCB0aGVvcnkKV2UgY2FuIHVzZSBiaW5vbWlhbCB0aGVvcnkgdG8gZXN0aW1hdGUgdGhlIHVuY2VydGFpbnR5IG9mIGVhY2ggcGVyY2VudGlsZSAoaW4gdGVybXMgb2YgcGVyY2VudGlsZSB2YWx1ZXMpLCBhbmQgdGhlbiB0cmFuc2xhdGUgdGhlc2UgdW5jZXJ0YWludGllcyBpbnRvIHJhbmdlcyBvZiBncmFpbiBzaXplcyB1c2luZyB0aGUgc2FtcGxlIGRhdGEuCgpGb3IgZXhhbXBsZSwgd2Uga25vdyB0aGF0IGZvciB0aGUgbWVkaWFuIHNpemUsIHRoZSBleHBlY3RlZCBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGxlc3MgdGhhbiB0aGUgbWVkaWFuIHZhbHVlIG91Z2h0IHRvIGJlICRucCQsIHdoZXJlICRuJCBpcyB0aGUgdG90YWwgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBhbmQgJHAkIGlzIHRoZSBwZXJjZW50aWxlIG9mIGludGVyZXN0IChpLmUuIDAuNTAgaW4gdGhpcyBjYXNlKS4gSG93ZXZlciwgYmlub21pYWwgdGhlb3J5IGltcGxpZXMgdGhhdCB0aGUgYWN0dWFsIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgbGVzcyB0aGFuIHRoaXMgdmFsdWUgaXMgYSBub3JtYWxseSBkaXN0cmlidXRlZCBxdWFudGl0eSB3aXRoIGEgbWVhbiB2YWx1ZSBvZiAkbnAkIGFuZCBhIHN0YW5kYXJkIGRldmlhdGlvbiBvZiAkXHNxcnR7bnAoMS1wKX0kLiAKSXQgaXMgcXVpdGUgZWFzaWx5IHRvIGNhbGN1bGF0ZSB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbCBmb3IgYW55IHBlcmNlbnRpbGUgaW4gdGVybXMgb2YgdGhlIHBlcmNlbnRpbGUgdmFsdWVzIHRoZW1zZWx2ZXMuIAoKYGBge3J9CmFscGhhID0gMC4wNQpwZXJjZW50aWxlID0gc2VxKDAuMSwwLjksMC4xKQpuID0gYygxMDAsMjAwLDQwMCkKdGNyaXQgPSBxdCgxIC0gYWxwaGEvMiwgbikKY2kudXBwZXIgPSBtYXRyaXgoZGF0YSA9IE5BLCBucm93ID0gbGVuZ3RoKHBlcmNlbnRpbGUpLCBuY29sID0gbGVuZ3RoKG4pKQpjaS5sb3dlciA9IG1hdHJpeChkYXRhID0gTkEsIG5yb3cgPSBsZW5ndGgocGVyY2VudGlsZSksIG5jb2wgPSBsZW5ndGgobikpCmZvciAoaSBpbiBzZXFfYWxvbmcocGVyY2VudGlsZSkpewogIHNpZ21hID0gc3FydChuKnBlcmNlbnRpbGVbaV0qKDEtcGVyY2VudGlsZVtpXSkpCiAgY2kudXBwZXJbaSxdID0gcm91bmQoKG4qcGVyY2VudGlsZVtpXSArIHRjcml0KnNpZ21hKS9uLCBkaWdpdHMgPSAyKQogIGNpLmxvd2VyW2ksXSA9IHJvdW5kKChuKnBlcmNlbnRpbGVbaV0gLSB0Y3JpdCpzaWdtYSkvbiwgZGlnaXRzID0gMikKfQpjaS5wZXJjZW50aWxlID0gZGF0YS5mcmFtZShwZXJjZW50aWxlLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjaS5sb3dlclssMV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNpLnVwcGVyWywxXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgY2kubG93ZXJbLDJdLAogICAgICAgICAgICAgICAgICAgICAgICAgICBjaS51cHBlclssMl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNpLmxvd2VyWywzXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgY2kudXBwZXJbLDNdICkKCm5hbWVzKGNpLnBlcmNlbnRpbGUpID0gIGMoJ1BlcmNlbnRpbGUnLCAnTDEwMCcsICdVMTAwJywgJ0wyMDAnLCAnVTIwMCcsJ0w0MDAnLCAnVTQwMCcgKQpjaS5wZXJjZW50aWxlCmBgYApGb3IgZXhhbXBsZSwgdGhlIDUwdGggcGVyY2VudGlsZSBvZiB0aGUgZGlzdHJpYnV0aW9uIGVzdGltYXRlZCBmcm9tIGEgc2FtcGxlIG9mIDEwMCBvYnNlcnZhdGlvbnMgY291bGQgZmFsbCBiZXR3ZWVuIHRoZSA0MHRoIHBlcmNlbnRpbGUgYW5kIHRoZSA2MHRoIHBlcmNlbnRpbGUgb2YgdGhlIHNhbXBsZSBkYXRhLiBGb3IgYSBzYW1wbGUgb2YgNDAwIG9ic2VydmF0aW9ucywgdGhlIDUwdGggcGVyY2VudGlsZSB3b3VsZCBiZSBleHBlY3RlZCB0byBmYWxsIGJldHdlZW4gdGhlIDQ1dGggYW5kIHRoZSA1NXRoIHBlcmNlbnRpbGUgb2YgdGhlIHNhbXBsZS4gCgpJbiBvcmRlciB0byBlc3RpbWF0ZSB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbCBpbiBtbSwgaXQgaXMgbmVjZXNzYXJ5IHRvIGtub3cgc29tZXRoaW5nIGFib3V0IHRoZSB1bmRlcmx5aW5nIGRpc3RyaWJ1dGlvbi4gQSBmdW5jdGlvbiB0aGF0IGVzdGltYXRlcyB0aGUgcGVyY2VudGlsZSBzaXplIGFuZCBjb25maWRlbmNlIGJvdW5kcyBmb3IgYW55IHBlcmNlbnRpbGUgYW5kIGNvbmZpZGVuY2UgbGltaXQsICRcYWxwaGEkLCBjYW4gYmUgd3JpdHRlbiBhcyBmb2xsb3dzOgoKYGBge3J9CgpFcnJvckJvdW5kcyA9IGZ1bmN0aW9uKG4sIHAsIGNmZCwgYWxwaGEgPSAwLjA1KXsKICB0Y3JpdCA9IHF0KDEgLSBhbHBoYS8yLCBuKQogIHNpZ21hID0gc3FydChuKnAqKDEtcCkpCiAgcC51cHBlciA9IChuKnAgKyB0Y3JpdCpzaWdtYSkvbgogIHAubG93ZXIgPSAobipwIC0gdGNyaXQqc2lnbWEpL24KICBib3VuZHMgPSBjKHAubG93ZXIsIHAudXBwZXIsIHApCiAgZXN0aW1hdGVzID0gYXBwcm94KHggPSBjZmQkeSwgeSA9IGNmZCR4LCB4b3V0ID0gYm91bmRzKQogIGVzdGltYXRlcyR5ID0gMl5lc3RpbWF0ZXMkeQpyZXR1cm4oZXN0aW1hdGVzKQp9CgpgYGAKClRoZSBlcnJvciBib3VuZHMgYXJvdW5kIG91ciBzYW1wbGUgb2YgNTAwIG1lYXN1cmVtZW50cyBmb3IgdmFyaW91cyBwZXJjZW50aWxlcyBhcmUgZWFzaWx5IGNhbGN1bGF0ZWQ6CgpgYGB7cn0KY29uZi41MCA9IEVycm9yQm91bmRzKDUwMCwgMC41LCBjZmQpCmNvbmYuODQgPSBFcnJvckJvdW5kcyg1MDAsIDAuODQsIGNmZCkKY29uZi45MCA9IEVycm9yQm91bmRzKDUwMCwgMC45MCwgY2ZkKQpjb25mLjk1ID0gRXJyb3JCb3VuZHMoNTAwLCAwLjk1LCBjZmQpCmBgYAoKRm9yIHN1Y2ggYSBsYXJnZSBzYW1wbGUsIHRoZSB1bmNlcnRhaW50eSByYW5nZSBmb3IgdGhlICREX3s1MH0kIGlzIApgciByb3VuZCgoY29uZi41MCR5WzFdKSlgIC0tIGByIHJvdW5kKChjb25mLjUwJHlbMl0pKWAgbW0uIApUaGUgZXJyb3IgYm91bmRzIGFyb3VuZCB0aGUgJERfezg0fSQgYXJlIGxhcmdlciwgYXQgYWJvdXQgCmByIHJvdW5kKChjb25mLjg0JHlbMV0pKWAgLS0gYHIgcm91bmQoKGNvbmYuODQkeVsyXSkpYCBtbS4gCkl0IGlzIGV2ZW4gYmlnZ2VyIGZvciB0aGUgbGFyZ2VzdCBncmFpbiBzaXplczogdGhlIHVuY2VydGFpbnR5IG9mIHRoZSAkRF97OTB9JCBpcyBhYm91dCAKYHIgcm91bmQoKGNvbmYuOTAkeVsxXSkpYCAtLSBgciByb3VuZCgoY29uZi45MCR5WzJdKSlgIG1tLgoKVGhlIHNpemUgb2YgdGhlIHNhbXBsZSBoYXMgYSBncmVhdCBlZmZlY3Qgb24gdGhlIHVuY2VydGFpbnR5IHJhbmdlLiBGb3IgZXhhbXBsZSwgY29uc2lkZXIgdGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzIGFib3V0IHRoZSAkRF97NTB9JCwgY2FsY3VsYXRlZCBhbmQgcGxvdHRlZCBiZWxvdy4KCmBgYHtyfQpOID0gc2VxKGZyb20gPSAxMDAsIHRvID0gNTAwLCBieSA9IDUpCkQ1MC5jaXZuID0gbWF0cml4KGRhdGEgPSBOQSwgbnJvdyA9IGxlbmd0aChOKSwgbmNvbCA9IDMpCmZvciAoaSBpbiBzZXFfYWxvbmcoTikpewogIEQ1MC5jaXZuW2ksXSA9IEVycm9yQm91bmRzKE5baV0sIDAuNTAsIGNmZClbWzJdXQp9Cm1hdHBsb3QoTiwgRDUwLmNpdm4sCiAgICAgdHlwZSA9ICJsIiwKICAgICBsb2cgPSAieSIsCiAgICAgeWxpbSA9IDJeYyhtaW4ucGhpLCBtYXgucGhpKSwKICAgICBsdHkgPSAxKQphYmxpbmUoaCA9IDJeeCwKICAgICAgIGx3ZCA9IDAuNSwKICAgICAgIGx0eSA9IDIpCmFibGluZSh2ID0gc2VxKDEwMCw1MDAsNTApLAogICAgICAgbHdkID0gMC41LAogICAgICAgbHR5ID0gMikKYGBgCgpGb3IgYSBzYW1wbGUgb2YgMTAwIHN0b25lcywgdGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgZm9yICREX3s1MH0kIGlzIApgciByb3VuZChENTAuY2l2blt3aGljaChOPT0xMDApLDFdKWAgLS0gIGByIHJvdW5kKEQ1MC5jaXZuW3doaWNoKE49PTEwMCksMl0pYCAKbW0sIGJ1dCBmb3IgYSBzYW1wbGUgb2YgMjAwIHN0b25lcyB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbCBpcyAKYHIgcm91bmQoRDUwLmNpdm5bd2hpY2goTj09MjAwKSwxXSlgIC0tICBgciByb3VuZChENTAuY2l2blt3aGljaChOPT0yMDApLDJdKWAgCm1tLCBhbmQgZm9yIGEgNDAwIHN0b25lIHNhbXBsZSBpcyAKYHIgcm91bmQoRDUwLmNpdm5bd2hpY2goTj09NDAwKSwxXSlgIC0tICBgciByb3VuZChENTAuY2l2blt3aGljaChOPT00MDApLDJdKWAgbW0uIChUaGUgZXN0aW1hdGUgb2YgJERfezUwfSQgaW4gYWxsIGNhc2VzIGlzICBgciByb3VuZChENTAuY2l2blt3aGljaChOPT0xMDApLDNdKWAgbW0pLiBHZW5lcmFsbHksIHRoZSBjb25maWRlbmNlIGludGVydmFsIGlzIGFwcHJveGltYXRlbHkgc3ltbWV0cmljYWwgYWJvdXQgdGhlIGVzdGltYXRlLCBidXQgdGhpcyBpcyBub3QgYWx3YXlzIHRydWUsIGFuZCBkZXBlbmRzIG9uIHRoZSB1bmRlcmx5aW5nIGRpc3RyaWJ1dGlvbi4gVGh1cywgd2hpbGUgdGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgcmVwcmVzZW50cyBhYm91dCAKYHIgcm91bmQoMTAwKihENTAuY2l2blt3aGljaChOPT0xMDApLDJdIC0gRDUwLmNpdm5bd2hpY2goTj09MTAwKSwxXSkgL0Q1MC5jaXZuW3doaWNoKE49PTEwMCksM10pYCUgb2YgdGhlIGVzdGltYXRlIHZhbHVlIGZvciAkTiA9IDEwMCQsIGl0IHJlcHJlc2VudHMgb25seSAKYHIgcm91bmQoMTAwKihENTAuY2l2blt3aGljaChOPT00MDApLDJdIC0gRDUwLmNpdm5bd2hpY2goTj09NDAwKSwxXSkgL0Q1MC5jaXZuW3doaWNoKE49PTQwMCksM10pYCUgZm9yICROID0gNDAwJC4gQ2xlYXJseSwgdGhlIGRlc2lyZWQgbGV2ZWwgb2YgcHJlY2lzaW9uIG11c3QgYmUgY29uc2lkZXJlZCB3aGVuIGRlc2lnbmluZyBhIGJlZCBzdXJmYWNlIHNhbXBsaW5nIHN0cmF0ZWd5LgoKVGhlIGlzc3VlIGlzIHJhdGhlciBtb3JlIGltcG9ydGFudCB3aGVuIGVzdGltYXRpbmcgbGFyZ2VyLXRoYW4tYXZlcmFnZSBncmFpbiBzaXplcy4gQ29uc2lkZXIgaG93IHRoZSB1bmNlcnRhaW50eSBmb3IgdGhlICREX3s4NH0kIHZhcmllcyB3aXRoIHRoZSBzaXplIG9mIHRoZSBzYW1wbGUuCgpgYGB7cn0KTiA9IHNlcShmcm9tID0gMTAwLCB0byA9IDUwMCwgYnkgPSA1KQpEODQuY2l2biA9IG1hdHJpeChkYXRhID0gTkEsIG5yb3cgPSBsZW5ndGgoTiksIG5jb2wgPSAzKQpmb3IgKGkgaW4gc2VxX2Fsb25nKE4pKXsKICBEODQuY2l2bltpLF0gPSBFcnJvckJvdW5kcyhOW2ldLCAwLjg0LCBjZmQpW1syXV0KfQptYXRwbG90KE4sIEQ4NC5jaXZuLAogICAgIHR5cGUgPSAibCIsCiAgICAgbG9nID0gInkiLAogICAgIHlsaW0gPSAyXmMobWluLnBoaSwgbWF4LnBoaSksCiAgICAgbHR5ID0gMSkKYWJsaW5lKGggPSAyXngsCiAgICAgICBsd2QgPSAwLjUsCiAgICAgICBsdHkgPSAyKQphYmxpbmUodiA9IHNlcSgxMDAsNTAwLDUwKSwKICAgICAgIGx3ZCA9IDAuNSwKICAgICAgIGx0eSA9IDIpCmBgYAoKRm9yIGEgc2FtcGxlIG9mIDEwMCBzdG9uZXMsIHRoZSBjb25maWRlbmNlIGludGVydmFsIGZvciAkRF97ODR9JCBpcyAKYHIgcm91bmQoRDg0LmNpdm5bd2hpY2goTj09MTAwKSwxXSlgIC0tICBgciByb3VuZChEODQuY2l2blt3aGljaChOPT0xMDApLDJdKWAgCm1tLCBidXQgZm9yIGEgNDAwIHN0b25lIHNhbXBsZSBpcyAKYHIgcm91bmQoRDg0LmNpdm5bd2hpY2goTj09NDAwKSwxXSlgIC0tICBgciByb3VuZChEODQuY2l2blt3aGljaChOPT00MDApLDJdKWAgbW0uIChUaGUgZXN0aW1hdGUgb2YgJERfezg0fSQgaXMgIGByIHJvdW5kKEQ4NC5jaXZuW3doaWNoKE49PTEwMCksM10pYCBtbSkuIEJlY2F1c2Ugb2YgdGhlIHVuZGVybHlpbmcgZGlzdHJpYnV0aW9uLCB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbCBpcyBub3Qgc3ltbWV0cmljYWwgYWJvdXQgdGhlIGVzdGltYXRlLCBwYXJ0aWN1bGFybHkgZm9yIHNtYWxsIHNhbXBsZSBzaXplcy4gSW4gdGhpcyBjYXNlIHRoZSBjb25maWRlbmNlIGludGVydmFsIHJlcHJlc2VudHMgYWJvdXQgCmByIHJvdW5kKDEwMCooRDg0LmNpdm5bd2hpY2goTj09MTAwKSwyXSAtIEQ4NC5jaXZuW3doaWNoKE49PTEwMCksMV0pIC9EODQuY2l2blt3aGljaChOPT0xMDApLDNdKWAlIG9mIHRoZSBlc3RpbWF0ZSB2YWx1ZSBmb3IgJE4gPSAxMDAkLCBhbmQKYHIgcm91bmQoMTAwKihEODQuY2l2blt3aGljaChOPT00MDApLDJdIC0gRDg0LmNpdm5bd2hpY2goTj09NDAwKSwxXSkgL0Q4NC5jaXZuW3doaWNoKE49PTQwMCksM10pYCUgZm9yICROID0gNDAwJC4KCkZvciB0aGUgJERfezk1fSQsIHRoZSBhbmFseXNpcyBsb29rcyBsaWtlIHRoaXM6CgpgYGB7cn0KTiA9IHNlcShmcm9tID0gMTAwLCB0byA9IDUwMCwgYnkgPSA1KQpEOTUuY2l2biA9IG1hdHJpeChkYXRhID0gTkEsIG5yb3cgPSBsZW5ndGgoTiksIG5jb2wgPSAzKQpmb3IgKGkgaW4gc2VxX2Fsb25nKE4pKXsKICBEOTUuY2l2bltpLF0gPSBFcnJvckJvdW5kcyhOW2ldLCAwLjk1LCBjZmQpW1syXV0KfQptYXRwbG90KE4sIEQ5NS5jaXZuLAogICAgIHR5cGUgPSAibCIsCiAgICAgbG9nID0gInkiLAogICAgIHlsaW0gPSAyXmMobWluLnBoaSwgbWF4LnBoaSksCiAgICAgbHR5ID0gMSkKYWJsaW5lKGggPSAyXngsCiAgICAgICBsd2QgPSAwLjUsCiAgICAgICBsdHkgPSAyKQphYmxpbmUodiA9IHNlcSgxMDAsNTAwLDUwKSwKICAgICAgIGx3ZCA9IDAuNSwKICAgICAgIGx0eSA9IDIpCmBgYAoKRm9yIGEgc2FtcGxlIG9mIDEwMCBzdG9uZXMsIHRoZSBjb25maWRlbmNlIGludGVydmFsIGZvciAkRF97OTV9JCBpcyAKYHIgcm91bmQoRDk1LmNpdm5bd2hpY2goTj09MTAwKSwxXSlgIC0tICBgciByb3VuZChEOTUuY2l2blt3aGljaChOPT0xMDApLDJdKWAgCm1tLCBidXQgZm9yIGEgNDAwIHN0b25lIHNhbXBsZSBpcyAKYHIgcm91bmQoRDk1LmNpdm5bd2hpY2goTj09NDAwKSwxXSlgIC0tICBgciByb3VuZChEOTUuY2l2blt3aGljaChOPT00MDApLDJdKWAgbW0uIChUaGUgZXN0aW1hdGUgb2YgJERfezk1fSQgaXMgIGByIHJvdW5kKEQ5NS5jaXZuW3doaWNoKE49PTEwMCksM10pYCBtbSkuIEZvciBzbWFsbCB2YWx1ZXMgb2YgJE4kLCB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbCBpcyBub3Qgc3ltbWV0cmljYWwgYWJvdXQgdGhlIGVzdGltYXRlLiBJbiB0aGlzIGNhc2UgdGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgcmVwcmVzZW50cyBhYm91dCAKYHIgcm91bmQoMTAwKihEOTUuY2l2blt3aGljaChOPT0xMDApLDJdIC0gRDk1LmNpdm5bd2hpY2goTj09MTAwKSwxXSkgL0Q5NS5jaXZuW3doaWNoKE49PTEwMCksM10pYCUgb2YgdGhlIGVzdGltYXRlIHZhbHVlIGZvciAkTiA9IDEwMCQsIGFuZApgciByb3VuZCgxMDAqKEQ5NS5jaXZuW3doaWNoKE49PTQwMCksMl0gLSBEOTUuY2l2blt3aGljaChOPT00MDApLDFdKSAvRDk1LmNpdm5bd2hpY2goTj09NDAwKSwzXSlgJSBmb3IgJE4gPSA0MDAkLgoKI0Nob29zaW5nIGEgc2FtcGxlIHNpemUKSW4gb3JkZXIgdG8gY2hvb3NlIGFuIGFwcHJvcHJpYXRlIHNhbXBsZSBzaXplLCBpdCBpcyBmaXJzdCBuZWNlc3NhcnkgdG8gaGF2ZSBhIHNlbnNlIG9mIHRoZSBzaGFwZSBvZiB0aGUgZGlzdHJpYnV0aW9uLCBhbmQgdG8ga25vdyB3aGF0IHBlcmNlbnRpbGVzIGFyZSBvZiBpbnRlcmVzdC4gRm9yIGV4YW1wbGUsIGlmIHdlIGFzc3VtZSB0aGUgZGF0YSB1c2VkIGhlcmVpbiBpcyByZWFzb25hYmx5IHJlcHJlc2VudGF0aXZlIG9mIHRoZSBiZWQgc3VyZmFjZSBvZiBpbnRlcmVzdCAoaW4gdGVybXMgb2YgZGlzdHJpYnV0aW9uIHNoYXBlLCBhdCBsZWFzdCksIHRoZW4gd2UgY2FuIGVhc2lseSBtb2RlbCB0aGUgcmVsYXRpb24gYmV0d2VlbiB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbCB3aWR0aCBhbmQgdGhlIG51bWJlciBvZiBtZWFzdXJlbWVudHMgdGFrZW4uCgpgYGB7cn0KY2kud2lkdGg1MCA9IDEwMCooRDUwLmNpdm5bLDJdIC0gRDUwLmNpdm5bLDFdKS9ENTAuY2l2blssM10KcGxvdChOLCBjaS53aWR0aDUwLCAKICAgICB0eXBlID0gImwiLAogICAgIHlsYWIgPSAiQy5JLiBXaWR0aCAoJSBvZiBlc3QuKSIsCiAgICAgY29sID0gImJsdWUiKQphYmxpbmUoaCA9IHNlcSg1LDk1LDUpLAogICAgICAgbHdkID0gMC41LAogICAgICAgbHR5ID0gMikKYWJsaW5lKHYgPSBzZXEoMTAwLDUwMCw1MCksCiAgICAgICBsd2QgPSAwLjUsCiAgICAgICBsdHkgPSAyKQpgYGAKCklmIHdlIHdpc2ggdG8gaGF2ZSBhIGNvbmZpZGVuY2UgaW50ZXJ2YWwgbm8gd2lkZXIgdGhhbiBhYm91dCAzMCUgb2YgdGhlIGVzdGltYXRlIG9mIHRoZSAkRF97NTB9JCwgdGhlbiB3ZSB3b3VsZCBuZWVkIGEgc2FtcGxlIHNpemUgb2YgYHIgTlt3aGljaChjaS53aWR0aDUwIDw9IDMwKVsxXV1gLiAKCkhvd2V2ZXIsIGlmIHdlIHdpc2ggdG8gaGF2ZSBlcXVpdmFsZW50IGNvbmZpZGVuY2UgaW4gYW4gZXN0aW1hdGUgb2YgJERfezkwfSQsIG91ciBzYW1wbGUgc2l6ZSB3b3VsZCBoYXZlIHRvIGJlIGxhcmdlci4KCmBgYHtyfQoKRDkwLmNpdm4gPSBtYXRyaXgoZGF0YSA9IE5BLCBucm93ID0gbGVuZ3RoKE4pLCBuY29sID0gMykKZm9yIChpIGluIHNlcV9hbG9uZyhOKSl7CiAgRDkwLmNpdm5baSxdID0gRXJyb3JCb3VuZHMoTltpXSwgMC45MCwgY2ZkKVtbMl1dCn0KCmNpLndpZHRoOTAgPSAxMDAqKEQ5MC5jaXZuWywyXSAtIEQ5MC5jaXZuWywxXSkvRDkwLmNpdm5bLDNdCnBsb3QoTiwgY2kud2lkdGg5MCwgCiAgICAgdHlwZSA9ICJsIiwKICAgICB5bGFiID0gIkMuSS4gV2lkdGggKCUgb2YgZXN0LikiLAogICAgIGNvbCA9ICJibHVlIikKYWJsaW5lKGggPSBzZXEoNSw5NSw1KSwKICAgICAgIGx3ZCA9IDAuNSwKICAgICAgIGx0eSA9IDIpCmFibGluZSh2ID0gc2VxKDEwMCw1MDAsNTApLAogICAgICAgbHdkID0gMC41LAogICAgICAgbHR5ID0gMikKYGBgCgpJbiBmYWN0LCB3ZSB3b3VsZCBuZWVkIGEgc2FtcGxlIG9mIHNpemUgJE4gPSQgYHIgTlt3aGljaChjaS53aWR0aDkwIDw9IDMwKVsxXV1gIGhhdmUgYSBjb25maWRlbmNlIGludGVydmFsIG5vIGdyZWF0ZXIgdGhhbiAzMCUgb2YgdGhlIGVzdGltYXRlLiAKCiMgQWN0dWFsIFNhbXBsZSBWYXJpYWJpbGl0eQpXZSBjYW4gdXNlIG91ciBzYW1wbGUgb2YgNTAwIG1lYXN1cmVtZW50cyB0byBlc3RpbWF0ZSB0aGlzIHNhbXBsaW5nIGVmZmVjdCBieSBzdWJzZXR0aW5nIHRoZSBkYXRhLCBhbmQgdXNpbmcgdGhlIHN1YnNldCB0byBjYWxjdWxhdGUgdGhlIG1lZGlhbiBncmFpbiBzaXplLiBJbiB0aGlzIGNhc2UsIHdlIGFzc3VtZSB0aGF0IG91ciBzYW1wbGUgb2YgNTAwIG1lYXN1cmVtZW50cyByZXByZXNlbnRzIHRoZSB1bmRlcmx5aW5nIHBvcHVsYXRpb24gb2YgZ3JhaW5zLCBhbmQgdGhhdCB0aGUgdHJ1ZSBtZWRpYW4gdmFsdWUgaXMgYHIgcm91bmQoY29uZi41MCR5WzNdKWAgbW0uIFNpbmNlIHRoZSBzYW1wbGUgb2YgNTAwIG1lYXN1cmVtZW50cyBpcyBpbiBmYWN0IGEgc2FtcGxlIGl0c2VsZiBvZiB0aGUgdW5kZXJseWluZyBwb3B1bGF0aW9uLCB0aGlzIGFzc3VtcHRpb24gaXMgbm90IHN0cmljdGx5IHRydWUsIGFuZCBjb3VsZCBpbnRyb2R1Y2Ugc29tZSBzbWFsbCBkaXN0b3J0aW9ucyBpbiB0aGUgY29tcGFyaXNpb24uCgpgYGB7cn0KayA9IDEwMCAgI3NldCB0aGUgc2FtcGxlIHNpemUKbSA9IDEwMDAgICNzZXQgdGhlIG51bWJlciBvZiB0aW1lcyB0byBzYW1wbGUgdGhlIGRhdGEKc2FtcGxlLjUwID0gdmVjdG9yKG1vZGUgPSAibnVtZXJpYyIsIGxlbmd0aCA9IG0pCmZvcihpIGluIHNlcV9hbG9uZyhzYW1wbGUuNTApKXsKICBzYW1wbGUuZGF0YSA9IHNhbXBsZShhbGwuZGF0YSRELCBzaXplID0gaykKICBzYW1wbGUuaGlzdCA9IGhpc3Qoc2FtcGxlLmRhdGEsIAogICAgICAgICAgICAgICBicmVha3MgPSAyXngsCiAgICAgICAgICAgICAgIHBsb3QgPSBGQUxTRSkKICBzYW1wbGUuY2ZkID0gYygwLCBjdW1zdW0oc2FtcGxlLmhpc3QkY291bnRzKSkvc3VtKHNhbXBsZS5oaXN0JGNvdW50cykKICBzYW1wbGUuNTBbaV0gPSAyXmFwcHJveCh4ID0gc2FtcGxlLmNmZCwgeSA9IHgsIHhvdXQgPSAwLjUwKVtbMl1dCn0KYm94cGxvdChzYW1wbGUuNTAsCiAgICAgICAgeWxpbSA9IEQ1MC5jaXZuW3doaWNoKE4gPT0gayksMToyXSpjKDAuOCwgMS4yKSkKYWJsaW5lKGggPSBENTAuY2l2blt3aGljaChOID09IGspLF0sCiAgICAgICBjb2wgPSAicmVkIiwKICAgICAgIGx0eSA9IDIpCmBgYAoKT24gdGhlIHBsb3QsIHRoZSBjb25maWRlbmNlIGJvdW5kcyBlc3RpbWF0ZWQgZnJvbSBiaW5vbWlhbCB0aGVvcnkgYW5kIHRoZSBtZWRpYW4gYmFzZWQgb24gYWxsIGRhdGEgYXJlIHNob3duIGFzIGRhc2hlZCByZWQgbGluZXMsIGFuZCB0aGUgYm94cGxvdCBwcmVzZW50cyB0aGUgZGlzdHJpYnV0aW9uIG9mIGVzdGltYXRlcyBiYXNlZCBvbiBzYW1wbGluZyB0aGUgb3JpZ2luYWwgZGlzdHJpYnV0aW9uIHdpdGggYSBzYW1wbGUgc2l6ZSBvZiAxMDAuIFRoZXJlIGFyZSB0d28gcG9pbnRzIHRvIGJlIG1hZGU6ICgxKSB0aGUgZWZmZWN0IG9mIHNhbXBsZSBzaXplIGRvZXMgaW50cm9kdWNlIHNpZ25pZmljYW50IHVuY2VydGFpbnR5IGludG8gdGhlIGVzdGltYXRlIG9mIHRoZSBtZWRpYW4gc2l6ZTsgYW5kICgyKSB0aGUgYmlub25taWFsIHRoZW9yeSBjYXB0dXJlcyB0aGlzIHVuY2VydGFpbnR5IHZlcnkgd2VsbC4KCkZvciB0aGlzIGFuYWx5c2lzLCB3ZSBmaW5kCmByIHN1bShzYW1wbGUuNTAgPCBENTAuY2l2blt3aGljaChOID09IGspLDFdIHwgc2FtcGxlLjUwID4gRDUwLmNpdm5bd2hpY2goTiA9PSBrKSwyXSApIGAKb2JzZXJ2YXRpb25zIHRoYXQgYXJlIG91dHNpZGUgdGhlIDk1JSBjb25maWRlbmNlIGludGVydmFscyBwcmVkaWN0ZWQgYnkgYmlub21pYWwgdGhlb3J5LiBCYXNlZCBvbiB0aGUgc2FtcGxlIHNpemUsIHdlIHdvdWxkIGV4cGVjdApgciBhbHBoYSptYApvYnNlcnZhdGlvbnMgdG8gZmFsbCBvdXRzaWRlIG9mIHRoZSBjb25maWRlbmNlIGludGVydmFscy4KCkZvciBmdW4sIGxldHMgc2VlIHdoYXQgaGFwcGVucyB3aXRoIGEgbGFyZ2VyIHNhbXBsZSBzaXplLgoKCmBgYHtyfQprID0gMjAwICAjc2V0IHRoZSBzYW1wbGUgc2l6ZQptID0gMTAwMCAgI3NldCB0aGUgbnVtYmVyIG9mIHRpbWVzIHRvIHNhbXBsZSB0aGUgZGF0YQpzYW1wbGUuNTAgPSB2ZWN0b3IobW9kZSA9ICJudW1lcmljIiwgbGVuZ3RoID0gbSkKZm9yKGkgaW4gc2VxX2Fsb25nKHNhbXBsZS41MCkpewogIHNhbXBsZS5kYXRhID0gc2FtcGxlKGFsbC5kYXRhJEQsIHNpemUgPSBrKQogIHNhbXBsZS5oaXN0ID0gaGlzdChzYW1wbGUuZGF0YSwgCiAgICAgICAgICAgICAgIGJyZWFrcyA9IDJeeCwKICAgICAgICAgICAgICAgcGxvdCA9IEZBTFNFKQogIHNhbXBsZS5jZmQgPSBjKDAsIGN1bXN1bShzYW1wbGUuaGlzdCRjb3VudHMpKS9zdW0oc2FtcGxlLmhpc3QkY291bnRzKQogIHNhbXBsZS41MFtpXSA9IDJeYXBwcm94KHggPSBzYW1wbGUuY2ZkLCB5ID0geCwgeG91dCA9IDAuNTApW1syXV0KfQpib3hwbG90KHNhbXBsZS41MCwKICAgICAgICB5bGltID0gRDUwLmNpdm5bd2hpY2goTiA9PSBrKSwxOjJdKmMoMC44LCAxLjIpKQphYmxpbmUoaCA9IEQ1MC5jaXZuW3doaWNoKE4gPT0gayksXSwKICAgICAgIGNvbCA9ICJyZWQiLAogICAgICAgbHR5ID0gMikKYGBgCgpXZSBmaW5kIHRoYXQKYHIgc3VtKHNhbXBsZS41MCA8IEQ1MC5jaXZuW3doaWNoKE4gPT0gayksMV0gfCBzYW1wbGUuNTAgPiBENTAuY2l2blt3aGljaChOID09IGspLDJdIClgCm9ic2VydmF0aW9ucyBmYWxsIG91dHNpZGUgdGhlIDk1JSBjb25maWRlbmNlIGludGVydmFscyBwcmVkaWN0ZWQgYnkgYmlub21pYWwgdGhlb3J5OyB0aGlzIGlzIHByZXN1bWFibHkgcmVsYXRlZCB0byB0aGUgZmFjdCB0aGF0IHNhbXBsZXMgb2YgMjAwIG9ic2VydmF0aW9ucyB0YWtlbiBmcm9tIGEgcG9wdWxhdGlvbiBvZiA1MDAgb2JzZXJ2YXRpb25zIHdpbGwgbmF0dXJhbGx5IGhhdmUgYSBzbWFsbGVyIHZhcmlhYmlsaXR5IHRoYW4gaWYgdGhleSB3ZXJlIHRha2VuIGZyb20gdGhlIHRydWUgZ3JhaW4gc2l6ZSBwb3B1bGF0aW9uIChpLmUuLCB0aGUgc2FtcGxlcyBjYW4gaGFyZGx5IGJlIGNvbnNpZGVyZWQgdG8gYmUgaW5kZXBlbmRlbnQpLiBOZXZlcnRoZWxlc3MsIHRoZXJlIGlzIHN0aWxsIHN1YnN0YW50aWFsIHZhcmlhYmlsaXR5IGluIHRoZSBlc3RpbWF0ZSBvZiB0aGUgbWVkaWFuLCBhbmQgaXQgaXMgc2ltaWxhciB0byB0aGUgcmFuZ2UgcHJlZGljdGVkIGJ5IGJpbm9taWFsIHRoZW9yeS4KCkFuZCB3aGF0IGFib3V0IGxhcmdlciBncmFpbiBzaXplcywgbGlrZSB0aGUgJERfezg0fSQ/CgoKYGBge3J9CmsgPSAxMDAgICNzZXQgdGhlIHNhbXBsZSBzaXplCm0gPSAxMDAwICAjc2V0IHRoZSBudW1iZXIgb2YgdGltZXMgdG8gc2FtcGxlIHRoZSBkYXRhCnNhbXBsZS44NCA9IHZlY3Rvcihtb2RlID0gIm51bWVyaWMiLCBsZW5ndGggPSBtKQpmb3IoaSBpbiBzZXFfYWxvbmcoc2FtcGxlLjg0KSl7CiAgc2FtcGxlLmRhdGEgPSBzYW1wbGUoYWxsLmRhdGEkRCwgc2l6ZSA9IGspCiAgc2FtcGxlLmhpc3QgPSBoaXN0KHNhbXBsZS5kYXRhLCAKICAgICAgICAgICAgICAgYnJlYWtzID0gMl54LAogICAgICAgICAgICAgICBwbG90ID0gRkFMU0UpCiAgc2FtcGxlLmNmZCA9IGMoMCwgY3Vtc3VtKHNhbXBsZS5oaXN0JGNvdW50cykpL3N1bShzYW1wbGUuaGlzdCRjb3VudHMpCiAgc2FtcGxlLjg0W2ldID0gMl5hcHByb3goeCA9IHNhbXBsZS5jZmQsIHkgPSB4LCB4b3V0ID0gMC44NClbWzJdXQp9CmJveHBsb3Qoc2FtcGxlLjg0LAogICAgICAgIHlsaW0gPSBEODQuY2l2blt3aGljaChOID09IGspLDE6Ml0qYygwLjgsIDEuMikpCmFibGluZShoID0gRDg0LmNpdm5bd2hpY2goTiA9PSBrKSxdLAogICAgICAgY29sID0gInJlZCIsCiAgICAgICBsdHkgPSAyKQpgYGAKCkZvciB0aGlzIGdyYWluIHNpemUsIFdlIGZpbmQgdGhhdApgciBzdW0oc2FtcGxlLjg0IDwgRDg0LmNpdm5bd2hpY2goTiA9PSBrKSwxXSB8IHNhbXBsZS44NCA+IEQ4NC5jaXZuW3doaWNoKE4gPT0gayksMl0gKWAKb2JzZXJ2YXRpb25zIGZhbGwgb3V0c2lkZSB0aGUgOTUlIGNvbmZpZGVuY2UgaW50ZXJ2YWxzLgo=