Required packages
knitr::opts_chunk$set(echo = TRUE)
#install.packages("dplyr","ade4","magrittr","cluster",
#"factoextra","cluster.datasets","xtable","kableExtra",
#"knitr","summarytools")
Definition of a distance
A distance function or a metric on \(\mathbb{R}^m,\:m\geq 1\), is a function \(d:\mathbb{R}^m\times\mathbb{R}^m\rightarrow \mathbb{R}\).
A distance function must satisfy some required properties or axioms.
There are three main axioms.
A1. \(d(\mathbf{x},\mathbf{y})= 0\iff \mathbf{x}=\mathbf{y}\) (identity of indiscernibles);
A2. \(d(\mathbf{x},\mathbf{y})= d(\mathbf{y},\mathbf{x})\) (symmetry);
A3. \(d(\mathbf{x},\mathbf{z})\leq d(\mathbf{x},\mathbf{y})+d(\mathbf{y},\mathbf{z})\) (triangle inequality), where \(\mathbf{x}=(x_1,\cdots,x_m)\), \(\mathbf{y}=(y_1,\cdots,y_m)\) and \(\mathbf{z}=(z_1,\cdots,z_m)\) are all vectors of \(\mathbb{R}^m\).
We should use the term dissimilarity rather than distance when not all the three axioms A1-A3 are valid.
Most of the time, we shall use, with some abuse of vocabulary, the term distance.
Exercice 1
- Prove that the three axioms A1-A3 imply the non-negativity condition: \[d(\mathbf{x},\mathbf{y})\geq 0.\]
Euclidean distance
\[
d(\mathbf{x},\mathbf{y})=\sqrt{\sum_{j=1}^m (x_j-y_j)^2}.
\]
- A1-A2 are obvious.
- The proof of A3 is provided below.
Exercice 2
- Is the squared Euclidian distance a true distance?
Manhattan distance
- The Manhattan distance also called taxi-cab metric or city-block metric is defined by:
\[d(\mathbf{x},\mathbf{y})
=\sum_{j=1}^m |x_j-y_j|.\]
- A1-A2 hold.
- A3 also holds using the fact that \(|a+b|\leq |a|+|b|\) for any reals \(a,b\).
- There exists also a weighted version of the Manhattan distance called the Canberra distance.

x = c(0, 0)
y = c(6,6)
dist(rbind(x, y), method = "euclidian")
## x
## y 8.485281
dist(rbind(x, y), method = "euclidian",diag=T,upper=T)
## x y
## x 0.000000 8.485281
## y 8.485281 0.000000
6*sqrt(2)
## [1] 8.485281
dist(rbind(x, y), method = "manhattan")
## x
## y 12
dist(rbind(x, y), method = "manhattan",diag=T,upper=T)
## x y
## x 0 12
## y 12 0
Canberra distance
\[d(\mathbf{x},\mathbf{y})
=\sum_{j=1}^m
\frac{|x_j-y_j|}{|x_j|+|y_j|}.\]
- Note that the term \(|x_j-y_j|/(|x_j|+|y_j|)\) is not properly defined as: \(x_j=y_j=0\).
- By convention we set that term to be zero in that case.
- The Canberra distance is specially sensitive to small changes near zero.
x = c(0, 0)
y = c(6,6)
dist(rbind(x, y), method = "canberra")
## x
## y 2
6/6+6/6
## [1] 2
Exercice 3
- Prove that the Canberra distance is a true distance, i.e. that it satisfies A1-A3.
Minkowski distance
- Both the Euclidian and the Manattan distances are special cases of the Minkowski distance which is defined, for \(p\geq 1\), by: \[
d(\mathbf{x},\mathbf{y})=
\left[\sum_{j=1} |x_j-y_j|^{p}\right]^{1/p}.
\]
- For \(p=1\), we get the Manhattan distance.
- For \(p=2\), we get the Euclidian distance.
- Let us also define: \[\|\mathbf{x}\|_p\equiv\left[\sum_{j=1}^m |x_j|^{p}\right]^{1/p},\] where \(\|\mathbf{\cdot}\|_p\) is known as the \(p\)-norm or Minkowski norm.
- Note that the Minkowski distance and norm are related by:
\[d(\mathbf{x},\mathbf{y})=\|\mathbf{x}-\mathbf{y}\|_p.\]
\[\|\mathbf{x}\|_p=d(\mathbf{x},\mathbf{0}),\]
where \(\mathbf{0}\) is the null-vetor of \(\mathbb{R}^m\).
library("ggplot2")
x = c(0, 0)
y = c(6,6)
MinkowDist=c() # Initialiser à vide la liste
for (p in seq(1,30,.01))
{
MinkowDist=c(MinkowDist,dist(rbind(x, y), method = "minkowski", p = p))
}
ggplot(data =data.frame(x = seq(1,30,.01), y=MinkowDist ) , mapping = aes( x=x, y= y))+
geom_point(size=.1,color="red")+
xlab("p")+ylab("Minkowski Distance")+ggtitle("Minkowski distance wrt p")

Exercice 4
Produce a similar graph using “The Economist” theme. Indicate on the graph the Manhattan, the Euclidian distances as well as the Chebyshev distance introduced below.
Chebyshev distance
At the limit, we get the Chebyshev distance which is defined by: \[
d(\mathbf{x},\mathbf{y})=\max_{j=1,\cdots,n}(|x_j-y_j|)=\lim_{p\rightarrow\infty}
\left[\sum_{j=1} |x_j-y_j|^{p}\right]^{1/p}.
\]
The corresponding norm is:
\[
|\\mathbf{x}\|_\infty=\max_{j=1,\cdots,n}(|x_j|).
\]
Minkowski inequality
The proof of the triangular inequality A3 is based on the Minkowski inequality:
For any nonnegative real numbers \(a_1,\cdots,a_m\); \(b_1,\cdots,b_m\), and for any \(p\geq 1\), we have: \[
\left[\sum_{j=1}^m (a_j+b_j)^{p}\right]^{1/p}\leq
\left[\sum_{j=1}^m a_j^{p}\right]^{1/p}
+\left[\sum_{j=1}^m b_j^{p}\right]^{1/p}.
\]
To prove that the Minkowski distance satisfies A3, notice that \[
\sum_{j=1}^m|x_j-z_j|^{p}= \sum_{j=1}^m|(x_j-y_j)+(y_j-z_j)|^{p}.
\]
Since for any reals \(x,y\), we have: \(|x+y|\leq |x|+|y|\), and using the fact that \(x^p\) is increasing in \(x\geq 0\), we obtain: \[
\sum_{j=1}^m|x_j-z_j|^{p}\leq \sum_{j=1}^m(|x_j-y_j|+|y_j-z_j|)^{p}.
\]
Applying the Minkowski inequality with \(a_j=|x_j-y_j|\) and \(b_j=|y_j-z_j|\), \(j=1,\cdots,n\), we get: \[
\sum_{j=1}^m|x_j-z_j|^{p}\leq \left(\sum_{j=1}^m |x_j-y_j|^{p}\right)^{1/p}+\left(\sum_{j=1}^m |y_j-z_j|^{p}\right)^{1/p}.
\]
Exercice 5
To illustrate the Minkowski inequality, draw \(100\) times two lists of \(100\) draws from the lognormal distribution with mean \(1600\) and standard-deviation \(300\). Illustrate with a graph the gap between the two drawn lists.
Hölder inequality
- The proof of the Minkowski inequality itself requires the Hölder inequality:
- For any nonnegative real numbers \(a_1,\cdots,a_m\); \(b_1,\cdots,b_m\), and any \(p,q>1\) with \(1/p+1/q=1\), we have: \[
\sum_{j=1}^m a_jb_j\leq
\left[\sum_{j=1}^m a_j^{p}\right]^{1/p}
\left[\sum_{j=1}^m b_j^{q}\right]^{1/q}
\]
- The proof of the Hölder inequality relies on the Young inequality:
- For any \(a,b>0\), we have \[
ab\leq \frac{a^p}{p}+\frac{b^q}{q},
\] with equality occuring iff: \(a^p=b^q\).
- To prove the Young inequality, one can use the (strict) convexity of the exponential function.
- For any reals \(x,y\), we have: \[
\exp(\frac{x}{p}+\frac{y}{q})\leq \frac{e^{x}}{p}+\frac{e^{y}}{q}.
\]
- We then set: \(x=p\ln a\) and \(y=q\ln b\) to get the Young inequality.
- A good reference on inequalities is: Z. Cvetkovski, Inequalities: theorems, techniques and selected problems, 2012, Springer Science & Business Media.
# Cauchy-Schwartz inequality
- Note that the triangular inequality for the Minkowski distance implies: \[
\sum_{j=1}^m |x_j|\leq
\left[\sum_{j=1}^m |x_j|^{p}\right]^{1/p}.
\]
- Note that for \(p=2\), we have \(q=2\). The Hölder inequality implies for that special case \[
\sum_{j=1}^m|x_jy_j|\leq\sqrt{\sum_{j=1}^m x_j^2}\sqrt{\sum_{j=1}^m y_j^2}.
\]
- Since the LHS od thes above inequality is greater then \(|\sum_{j=1}^mx_jy_j|\), we get the Cauchy-Schwartz inequality \[
|\sum_{j=1}^mx_jy_j|\leq\sqrt{\sum_{j=1}^m x_j^2}\sqrt{\sum_{j=1}^m y_j^2}.
\]
- Using the dot product notation called also scalar product notation: \(\mathbf{x\cdot y}=\sum_{j=1}^mx_jy_j\), and the norm notation \(\|\mathbf{\cdot}\|_2\), the Cauchy-Schwartz inequality is: \[
|\mathbf{x\cdot y} | \leq \|\mathbf{x}\|_2 \| \mathbf{y}\|_2.
\]
Pearson correlation distance
The Pearson correlation coefficient is a similarity measure on \(\mathbb{R}^m\) defined by: \[
\rho(\mathbf{x},\mathbf{y})=
\frac{\sum_{j=1}^m (x_j-\bar{\mathbf{x}})(y_j-\bar{\mathbf{y}})}{{\sqrt{\sum_{j=1}^m (x_j-\bar{\mathbf{x}})^2\sum_{j=1}^m (y_j-\bar{\mathbf{y}})^2}}},
\] where \(\bar{\mathbf{x}}\) is the mean of the vector \(\mathbf{x}\) defined by: \[\bar{\mathbf{x}}=\frac{1}{n}\sum_{j=1}^m x_j,\]
Note that the Pearson correlation coefficient satisfies P2 and is invariant to any positive linear transformation, i.e.: \[\rho(\alpha\mathbf{x},\mathbf{y})=\rho(\mathbf{x},\mathbf{y}),\] for any \(\alpha>0\).
The Pearson distance (or correlation distance) is defined by: \[
d(\mathbf{x},\mathbf{y})=1-\rho(\mathbf{x},\mathbf{y}).
\]
Note that the Pearson distance does not satisfy A1 since \(d(\mathbf{x},\mathbf{x})=0\) for any non-zero vector \(\mathbf{x}\). It neither satisfies the triangle inequality. However, the symmetry property is fullfilled.
Cosine correlation distance
- The cosine of the angle \(\theta\) between two vectors \(\mathbf{x}\) and \(\mathbf{y}\) is a measure of similarity given by: \[
\cos(\theta)=\frac{\mathbf{x}\cdot \mathbf{y}}{\|\mathbf{x}\|_2\|\mathbf{y}\|_2}=\frac{\sum_{j=1}^m x_j y_j}{{\sqrt{\sum_{j=1}^m x_j^2\sum_{j=1}^m y_j^2}}}.
\]
- Note that the cosine of the angle between the two centred vectors \(\mathbf{x}-\bar{\mathbf{x}}\mathbf{1}\) and \(\mathbf{y}-\bar{\mathbf{y}}\mathbf{1}\) coincides with the Pearson correlation coefficient of \(\mathbf{x}\) and \(\mathbf{y}\), where \(\mathbf{1}\) is a vector of units of \(\mathbb{R}^m\).
- The cosine correlation distance is defined by: \[
d(\mathbf{x},\mathbf{y})=1-\cos(\theta).
\]
- It shares similar properties than the Pearson correlation distance. Likewise, Axioms A1 and A3 are not satisfied.
Spearman correlation distance
- To calculate the Spearman’s rank-order correlation, we need to map seperately each of the vectors to ranked data values: \[\mathbf{x}\rightarrow \text{rank}(\mathbf{x})=(x_1^r,\cdots,x_m^r).\]
- Here, \(x_j^r\) is the rank of \(x_j\) among the set of values of \(\mathbf{x}\).
- We illustrate this transformation with a simple example:
- If \(\mathbf{x}=(3, 1, 4, 15, 92)\), then the rank-order vector is \(\text{rank}(\mathbf{x})=(2,1,3,4,5)\).
x=c(3, 1, 4, 15, 92)
rank(x)
## [1] 2 1 3 4 5
- The Spearman’s rank correlation of two numerical vectors \(\mathbf{x}\) and \(\mathbf{y}\) is simply the Pearson correlation of the two correspnding rank-order vectors \(\text{rank}(\mathbf{x})\) and \(\text{rank}(\mathbf{y})\), i.e. \(\rho(\text{rank}(\mathbf{x}),\text{rank}(\mathbf{y}))\). This measure is is useful because it is more robust against outliers than the Pearson correlation.
- If all the \(n\) ranks are distinct, it can be computed using the following formula: \[
\rho(\text{rank}(\mathbf{x}),\text{rank}(\mathbf{y}))=1-\frac{6\sum_{j=1}^m d_j^2}{n(n^2-1)},
\] where \(d_j=x_j^r-y_j^r,\:j=1,\cdots,n\).
- The spearman distance is then defined by: \[
d(\mathbf{x},\mathbf{y})=1-\rho(\text{rank}(\mathbf{x}),\text{rank}(\mathbf{y})).
\]
- It can be shown that easaly that it is not a proper distance.
- If all the \(n\) ranks are distinct, we get: \[
d(\mathbf{x},\mathbf{y})=\frac{6\sum_{j=1}^m d_j^2}{n(n^2-1)}.
\]
x=c(3, 1, 4, 15, 92)
rank(x)
## [1] 2 1 3 4 5
y=c(30,2 , 9, 20, 48)
rank(y)
## [1] 4 1 2 3 5
d=rank(x)-rank(y)
d
## [1] -2 0 1 1 0
cor(rank(x),rank(y))
## [1] 0.7
1-6*sum(d^2)/(5*(5^2-1))
## [1] 0.7
Exercice 6
- For the two vectors \(\mathbf{x}=(22,34,1,12,25,56,7)\) and \(\mathbf{y}=(2,64,12,2,22,5,8)\) :
- Calculate the ranks for each vector.
- Deduce the Spearman correlation distance from that ranks.
- Deduce the Spearman correlation distance from the above dispalyed alternative equation.
- Calculate the Spearman correlation distance using the R function.
Kendall tau distance
- The Kendall rank correlation coefficient is calculated from the number of correspondances between the rankings of \(\mathbf{x}\) and the rankings of \(\mathbf{y}\).
- The number of pairs of observations among \(n\) observations or values is: \[{n \choose 2} =\frac{n(n-1)}{2}.\]
- The pairs of observations \((x_{i},x_{j})\) and \((y_{i},y_{j})\) are said to be concordant if: \[\text{sign}(x_j-x_j)=\text{sign}(y_j-y_j),\] and to be discordant if: \[\text{sign}(x_j-x_j)=-\text{sign}(y_j-y_j),\] where \(\text{sign}(\cdot)\) returns \(1\) for positive numbers and \(-1\) negative numbers and \(0\) otherwise.
- If \(x_j=x_j\) or \(y_j=y_j\) (or both), there is a tie.
- The Kendall \(\tau\) coefficient is defined by (neglecting ties): \[\tau =\frac {1}{n(n-1)}\sum_{j=1}^{n}\sum_{j=1}^m\text{sign}(x_j-x_j)\text{sign}(y_j-y_j).\]
- Let \(n_c\) (resp. \(n_d\)) be the number of concordant (resp. discordant) pairs, we have \[\tau =\frac {2(n_c-n_d)}{n(n-1)}.\]
- The Kendall tau distance is then: \[d(\mathbf{x},\mathbf{y})=1-\tau. \]
- Remark: the triangular inequality may fail in cases where there are ties.
x=c(3, 1, 4, 15, 92)
y=c(30,2 , 9, 20, 48)
tau=0
for (i in 1:5)
{
tau=tau+sign(x -x[i])%*%sign(y -y[i])
}
tau=tau/(5*4)
tau
## [,1]
## [1,] 0.6
cor(x,y, method="kendall")
## [1] 0.6
Exercice 7
- For the two vectors \(\mathbf{x}=(22,34,1,12,25,56,7)\) and \(\mathbf{y}=(2,64,12,2,22,5,8)\):
- List all pairs of coordinates.
- How many pairs are there?
- For each pair and each cector, compute the signs of the differences in coordinates.
- Deduce the Kendall tau coefficient using the above computations.
- Calculate the Kendall tau coefficient using the R function.
Standardization
- Variables or measurements are often standardized before calculating dissimilarities.
- Standardization converts the original variables into uniteless variables.
- A well known method is the z-score transformation.
- Let \(\mathbf{v}\equiv (v_1,\cdots,v_n )\) a vector of measurements recrded for \(n\) individuals or objects. \[
\mathbf{v}\rightarrow (\frac{v_1-\bar{\mathbf{v}}}{s_\mathbf{v}},\cdots,\frac{v_n-\bar{\mathbf{v}}}{s_\mathbf{v}}),
\] where \(\bar{\mathbf{v}},s_\mathbf{v}\) are the sample mean and standard-deviation, respectively, given by: \[
\bar{\mathbf{v}}=\frac{1}{n}\sum_{i=1}^n v_i,\:
s_\mathbf{v}=\frac{1}{n-1}\sum_{i=1}^n(v_i-\bar{\mathbf{v}})^2.
\]
- The transformed variable will have a mean of \(0\) and a variance of \(1\).
- The result obtained with Pearson correlation measures and standardized Euclidean distances are comparable.
- For other methods, see: Milligan, G. W., & Cooper, M. C. (1988). A study of standardization of variables in cluster analysis. Journal of classification, 5(2), 181-204
v=c(3, 1, 4, 15, 92)
w=c(30,2 , 9, 20, 48)
(v-mean(v))/sd(v)
## [1] -0.5134116 -0.5647527 -0.4877410 -0.2053646 1.7712699
scale(v)
## [,1]
## [1,] -0.5134116
## [2,] -0.5647527
## [3,] -0.4877410
## [4,] -0.2053646
## [5,] 1.7712699
## attr(,"scaled:center")
## [1] 23
## attr(,"scaled:scale")
## [1] 38.9551
(w-mean(w))/sd(w)
## [1] 0.45263128 -1.09293895 -0.70654639 -0.09935809 1.44621214
scale(w)
## [,1]
## [1,] 0.45263128
## [2,] -1.09293895
## [3,] -0.70654639
## [4,] -0.09935809
## [5,] 1.44621214
## attr(,"scaled:center")
## [1] 21.8
## attr(,"scaled:scale")
## [1] 18.11629
Exercice 8
- Consider the following example
- Plot the data using a nice scatter plot.
- Transform the Height from centimeters (cm) into feet (ft).
- Display your data in a table.
- Plot the data within a new scatter plot.
- What do you observe?
- Standardize the two variables Age and Height.
- Display your data in a table.
- Plot the standardized data within a new scatter plot.
- Conclude.
Similarity measures for binary data
A common simple situation occurs when all information is of the presence/absence of 2-level qualitative characters.
We assume there are \(n\) characters.
*The presence of the character is coded by \(1\) and the absence by 0.
We have have at our disposal two vectors.
\(\mathbf{x}\) is observed for a first individual (or object).
\(\mathbf{y}\) is observed for a second individual.
We can then calculate the following four statistics:
\(a=\mathbf{x\cdot y}=\sum_{j=1}^mx_jy_j.\)
\(b=\mathbf{x\cdot (1-y)}=\sum_{j=1}^mx_j(1-y_j).\)
\(c=\mathbf{(1-x)\cdot y}=\sum_{j=1}^m(1-x_j)y_j.\)
\(d=\mathbf{(1-x)\cdot (1-y)}=\sum_{j=1}^m(1-x_j)(1-y_j).\)
The counts of matches are \(a\) for \((1,1)\) and \(d\) for \((0,0)\);
The counts of mismatches are \(b\) for \((1,0)\) and \(c\) for \((0,1)\).
Note that obviously: \(a+b+c+d= n\).
This gives a very useful \(2 \times 2\) association table.
|
|
1 |
0 |
Totals |
First individual |
1 |
\(a\) |
\(b\) |
\(a+b\) |
|
0 |
\(c\) |
\(d\) |
\(c+d\) |
Totals |
|
\(a+c\) |
\(b+d\) |
\(n\) |
- The data shows \(8\) people (individuals) and \(10\) binary variables:
- Sex, Married, Fair Hair, Blue Eyes, Wears Glasses, Round Face, Pessimist, Evening Type, Is an Only Child, Left-Handed.
data=c(
1,0,1,1,0,0,1,0,0,0,
0,1,0,0,1,0,0,0,0,0,
0,0,1,0,0,0,1,0,0,1,
0,1,0,0,0,0,0,1,1,0,
1,1,0,0,1,1,0,1,1,0,
1,1,0,0,1,0,1,1,0,0,
0,0,0,1,0,1,0,0,0,0,
0,0,0,1,0,1,0,0,0,0
)
data=data.frame(matrix(data, nrow=8,byrow=T))
row.names(data)=c("Ilan","Jacqueline","Kim","Lieve","Leon","Peter","Talia","Tina")
names(data)=c("Sex", "Married", "Fair Hair", "Blue Eyes", "Wears Glasses", "Round Face", "Pessimist", "Evening Type", "Is an Only Child", "Left-Handed")
- We are comparing the records for Ilan with Talia.
library(knitr)
library(xtable)
library(stargazer)
library(texreg)
library(kableExtra)
library(summarytools)
## Warning in fun(libname, pkgname): couldn't connect to display ":0"
set.seed(893)
datat<-as.data.frame(t(data))
datat=lapply(datat,as.factor)
Ilan=datat$Ilan
Talia =datat$Talia
print(ctable(Ilan,Talia,prop = 'n',style = "rmarkdown"))
Cross-Tabulation
Ilan * Talia
|
Talia |
0 |
1 |
Total |
Ilan |
|
|
|
|
0 |
|
5 |
1 |
6 |
1 |
|
3 |
1 |
4 |
Total |
|
8 |
2 |
10 |
- Therefore: \(a = 1,\:b = 3,\: c = 1,\: d = 5\).
- Note that interchanging Ilan and Talia would permute \(b\) and \(c\) while leaving \(a\) and \(d\) unchanged.
- A good similarity or dissimilarity coefficient must treat \(b\) and \(c\) symmetrically.
- A similarity measure is denoted by: \(s(\mathbf{x},\mathbf{y})\).
- The corresponding distance is then defined as: \[d(\mathbf{x},\mathbf{y})=1-s(\mathbf{x},\mathbf{y}).\]
- Alternatively, we have: \[d(\mathbf{x},\mathbf{y})=\sqrt{1-s(\mathbf{x},\mathbf{y})}.\]
- A list of some of the similarity measures \(s(\mathbf{x},\mathbf{y})\) that have been suggested for binary data is shown below.
- An more complete list can be found in: Gower, J. C., & Legendre, P. (1986). Metric and Euclidean properties of dissimilarity coefficients. Journal of classification, 3(1), 5-48.
Simple matching |
\(\frac {a+d}{a+b+c+d}\) |
\(\frac{b+c}{a+b+c+d}\) |
Jaccard |
\(\
frac{a}{a+b+c}\) |
\(\frac{b+c}{a+b+c}\) |
Rogers and Tanimoto (1960) |
\(\frac{a+ d}{a+2(b+c)+d}\) |
\(\frac{2(b+c)}{a+2(b+c)+d}\) |
Gower and Legendre (1986) |
\(\frac{2(a+d )}{2(a+d)+b+c}\) |
\(\frac{b+c}{2(a+d)+b+c}]\) |
Gower and Legendre (1986) |
\(\fr ac{2a}{2a+b+c}\) |
\(\frac{b+c}{2a+b+c}\) |
To calculate these coefficients, we use the function: dist.binary(). available in the ade4 package.
All the distances in the ade4 package are of type \(d(\mathbf{x}.\mathbf{y})= \sqrt{1 - s(\mathbf{x}.\mathbf{y})}\).
library(ade4)
a=1
b=3
c=1
d=5
dist.binary(data[c("Ilan","Talia"),],method=2)^2
Ilan
Talia 0.4
1-(a+d )/(a+b+c+d)
[1] 0.4
dist.binary(data[c("Ilan","Talia"),],method=1)^2
Ilan
Talia 0.8
1-a/(a+b+c)
[1] 0.8
dist.binary(data[c("Ilan","Talia"),],method=4)^2
Ilan
Talia 0.5714286
1-(a+d )/(a+2*(b+c)+d)
[1] 0.5714286
# One Gower coefficient is missing
dist.binary(data[c("Ilan","Talia"),],method=5)^2
Ilan
Talia 0.6666667
1-2*a/(2*a+b+c)
[1] 0.6666667
- The reason for such a large number of possible measures has to do with the apparent uncertainty as to how to deal with the count of zero-zero matches \(d\).
- The measues embedding \(d\) are sometimes called symmetrical.
- The other measues are called assymmetrical.
- In some cases, of course, zero_zero matches are completely equivalent to one–one matches, and therefore should be included in the calculated similarity measure.
- An example is gender, where there is no preference as to which of the two categories should be coded zero or one.
- But in other cases the inclusion or otherwise of \(d\) is more problematic; for example, when the zero category corresponds to the genuine absence of some property, such as wings in a study of insects.
Exercice 9

Nominal variables
- We previously studied above binary variables which can only take on two states coded \(0,1\).
- We generalize this approach to nominal variables which may take on more than two states.
- Eye’s color may have for example four states: blue, brown, green, grey.
- Le \(M\) be the number of states and code the outcomes as \(1, \cdots, M\).
- We may choose \(1 =\text{blue},\) \(2 =\text{brown},\) \(3 =\text{green},\) and \(4 =\text{grey}\).
- These states are not ordered in any way
- One strategy would be creating a new binary variable for each of the \(M\) nominal states.
- Then to put it equal to \(1\) if the corresponding state occurs and to \(0\) otherwise.
- After that, one could resort to one of the dissimilarity coefficients of the previous subsection.
- The most common way of measuring the similarity or dissimilarity between two objects through categorial variables is the simple matching approach.
- If \(\mathbf{x},\mathbf{y},\) are both \(m\) nominal records for two individuals,
- Let define the function: \[\delta(x_j,y_j)\equiv \begin{cases}0,
\text{ if } x_j=y_j;\\1,\text{ if } x_j \neq y_j.\end{cases}\]
- Let \(N_{a+d}\) be the number of attributes of the two individuals on which the two records match: \[N_{a+d}=\sum_{j=1}^m[1-\delta(x_j,y_j)] .\]
- Let \(N_{b+c}\) be the number of attributes on which the two records do not match: \[N_{b+c}= \sum_{j=1}^m\delta(x_j,y_j).\]
- Let \(N_d\) be the number of attributes on which the two records match in a “not applicable” category.
- The distance corresponding to the simple matching approach is: \[
d(\mathbf{x},\mathbf{y})=\frac{\sum_{j=1}^m\delta(x_j,y_j)}{n}=\frac{N_{a+d}}{N_{a+d}+N_{b+c}}.
\]
- Note that simple matching has exactly the same meaning as in the preceding section.
Gower’s dissimilarity
Gower’s coefficient is a dissimilarity measure specifically designed for handling mixed attribute types or variables.
See: GOWER, John C. A general coefficient of similarity and some of its properties. Biometrics, 1971, p. 857-871.
The coefficient is calculated as the weighted average of attribute contributions.
Weights usually used only to indicate which attribute values could actually be compared meaningfully.
The formula is: \[
d(\mathbf{x},\mathbf{y})=\frac{\sum_{j=1}^m w_j \delta(x_j,y_j)}{\sum_{j=1}^m w_j}.
\]
The wheight \(w_j\) is put equal to \(1\) when both measurements \(x_j\) and \(y_j\) are nonmissing,
The number \(\delta(x_j,y_j)\) is the contribution of the \(j\)th measure or variable to the dissimilarity measure.
If the \(j\)th measure is nominal, we take
\[
\delta(x_j,y_j)\equiv \begin{cases}0,
\text{ if } x_j=y_j;\\1,\text{ if } x_j \neq y_j.\end{cases}
\]
If the \(j\)th measure is interval-scaled, we take instead: \[
\delta(x_j,y_j)\equiv \frac{|x_j-y_j|}{R_j},
\] where \(R_j\) is the range of variable \(j\) over the available data.
Consider the following data set:

From: Struyf, A., Hubert, M., & Rousseeuw, P. (1997). Clustering in an object-oriented environment. Journal of Statistical Software, 1(4), 1-30.
- The dataset contains 18 flowers and 8 characteristics:
- Winters: binary, indicates whether the plant may be left in the garden when it freezes.
- Shadow: binary, shows whether the plant needs to stand in the shadow.
- Tubers (Tubercule): asymmetric binary, distinguishes between plants with tubers and plants that grow in any other way.
- Color: nominal, specifies the flower’s color (1=white, 2=yellow, 3= pink, 4=red, 5= blue).
- Soil: ordinal, indicates whether the plant grows in dry (1), normal (2), or wet (3) soil.
- Preference: ordinal, someone’s preference ranking, going from 1 to 18.
- Height: interval scaled, the plant’s height in centimeters.
- Distance: interval scaled, the distance in centimeters that should be left between the plants.
- The dissimilarity between Begonia and Broom (Genêt) can be calculated as follows:
library(cluster)
library(dplyr)
data <-flower %>%
rename(Winters=V1,Shadow=V2,Tubers=V3,Color=V4,Soil=V5,Preference=V6,Height=V7,Distance=V8) %>%
mutate(Winters=recode(Winters,"1"="Yes","0"="No"),
Shadow=recode(Shadow,"1"="Yes","0"="No"),
Tubers=recode(Tubers,"1"="Yes","0"="No"),
Color=recode(Color,"1"="white", "2"="yellow", "3"= "pink", "4"="red", "5"="blue"),
Soil=recode(Soil,"1"="dry", "2"="normal", "3"= "wet")
)
row.names(data)=c("Begonia","Broom","Camellia","Dahlia","Forget-me-not","Fuchsia",
"Geranium", "Gladiolus","Heather","Hydrangea","Iris","Lily","Lily-of-the-valley",
"Peony","Pink Carnation","Red Rose","Scotch Rose","Tulip")
res=lapply(data,class)
res=as.data.frame(res)
res[1,] %>%
knitr::kable()
Winters
|
Shadow
|
Tubers
|
Color
|
Soil
|
Preference
|
Height
|
Distance
|
factor
|
factor
|
factor
|
factor
|
ordered
|
ordered
|
numeric
|
numeric
|
flower[1:2,]
## V1 V2 V3 V4 V5 V6 V7 V8
## 1 0 1 1 4 3 15 25 15
## 2 1 0 0 2 1 3 150 50
max(data$Height)-min(data$Height)
## [1] 180
max(data$Distance)-min(data$Distance)
## [1] 50
\[
\frac{|1-0|+|0-1|+|0-1|+1+|1-3|/2+|3-15|/17+|150-25|/180+|50-15|/50}{8}\approx 0.8875408
\]
Daisy function
Cluster package description available at this link.
library(cluster)
(abs(1-0)+abs(0-1)+abs(0-1)+1+abs(1-3)/2+abs(3-15)/17+abs(150-25)/180+abs(50-15)/50)/8
## [1] 0.8875408
dist<-daisy(data[,1:8],metric = "Gower")
as.matrix(dist)[1:2,1:2]
## Begonia Broom
## Begonia 0.0000000 0.8875408
## Broom 0.8875408 0.0000000
More on distance matrix computation

- We use a subset of the data by taking 15 random rows among the 50 rows in the data set.
- We are using the function sample().
- We standardize the data using the function scale().
knitr::kable(USArrests,caption = "USArrest data set",
digits = 1,align=c("c","c","c","c"),booktabs = TRUE)%>%
kable_styling(latex_options = "HOLD_position")
USArrest data set
|
Murder
|
Assault
|
UrbanPop
|
Rape
|
Alabama
|
13.2
|
236
|
58
|
21.2
|
Alaska
|
10.0
|
263
|
48
|
44.5
|
Arizona
|
8.1
|
294
|
80
|
31.0
|
Arkansas
|
8.8
|
190
|
50
|
19.5
|
California
|
9.0
|
276
|
91
|
40.6
|
Colorado
|
7.9
|
204
|
78
|
38.7
|
Connecticut
|
3.3
|
110
|
77
|
11.1
|
Delaware
|
5.9
|
238
|
72
|
15.8
|
Florida
|
15.4
|
335
|
80
|
31.9
|
Georgia
|
17.4
|
211
|
60
|
25.8
|
Hawaii
|
5.3
|
46
|
83
|
20.2
|
Idaho
|
2.6
|
120
|
54
|
14.2
|
Illinois
|
10.4
|
249
|
83
|
24.0
|
Indiana
|
7.2
|
113
|
65
|
21.0
|
Iowa
|
2.2
|
56
|
57
|
11.3
|
Kansas
|
6.0
|
115
|
66
|
18.0
|
Kentucky
|
9.7
|
109
|
52
|
16.3
|
Louisiana
|
15.4
|
249
|
66
|
22.2
|
Maine
|
2.1
|
83
|
51
|
7.8
|
Maryland
|
11.3
|
300
|
67
|
27.8
|
Massachusetts
|
4.4
|
149
|
85
|
16.3
|
Michigan
|
12.1
|
255
|
74
|
35.1
|
Minnesota
|
2.7
|
72
|
66
|
14.9
|
Mississippi
|
16.1
|
259
|
44
|
17.1
|
Missouri
|
9.0
|
178
|
70
|
28.2
|
Montana
|
6.0
|
109
|
53
|
16.4
|
Nebraska
|
4.3
|
102
|
62
|
16.5
|
Nevada
|
12.2
|
252
|
81
|
46.0
|
New Hampshire
|
2.1
|
57
|
56
|
9.5
|
New Jersey
|
7.4
|
159
|
89
|
18.8
|
New Mexico
|
11.4
|
285
|
70
|
32.1
|
New York
|
11.1
|
254
|
86
|
26.1
|
North Carolina
|
13.0
|
337
|
45
|
16.1
|
North Dakota
|
0.8
|
45
|
44
|
7.3
|
Ohio
|
7.3
|
120
|
75
|
21.4
|
Oklahoma
|
6.6
|
151
|
68
|
20.0
|
Oregon
|
4.9
|
159
|
67
|
29.3
|
Pennsylvania
|
6.3
|
106
|
72
|
14.9
|
Rhode Island
|
3.4
|
174
|
87
|
8.3
|
South Carolina
|
14.4
|
279
|
48
|
22.5
|
South Dakota
|
3.8
|
86
|
45
|
12.8
|
Tennessee
|
13.2
|
188
|
59
|
26.9
|
Texas
|
12.7
|
201
|
80
|
25.5
|
Utah
|
3.2
|
120
|
80
|
22.9
|
Vermont
|
2.2
|
48
|
32
|
11.2
|
Virginia
|
8.5
|
156
|
63
|
20.7
|
Washington
|
4.0
|
145
|
73
|
26.2
|
West Virginia
|
5.7
|
81
|
39
|
9.3
|
Wisconsin
|
2.6
|
53
|
66
|
10.8
|
Wyoming
|
6.8
|
161
|
60
|
15.6
|
## kable_classic(latex_options = "hold_position")
set.seed(123)
ss <- sample(1:50,15)
df <- USArrests[ss, ]
df.scaled <- scale(df)
knitr::kable(df.scaled,caption = "Sample of USArrest data set",digits = 4,align=c("c","c","c"),booktabs = TRUE)%>%
kable_classic(latex_options = "hold_position")
Sample of USArrest data set
|
Murder
|
Assault
|
UrbanPop
|
Rape
|
New Mexico
|
0.5851
|
1.0230
|
0.2251
|
0.6110
|
Iowa
|
-1.7022
|
-1.5476
|
-0.6892
|
-1.4389
|
Indiana
|
-0.4591
|
-0.9078
|
-0.1266
|
-0.4829
|
Arizona
|
-0.2354
|
1.1240
|
0.9284
|
0.5026
|
Tennessee
|
1.0326
|
-0.0659
|
-0.5486
|
0.0986
|
Texas
|
0.9083
|
0.0801
|
0.9284
|
-0.0394
|
Oregon
|
-1.0309
|
-0.3914
|
0.0141
|
0.3351
|
West Virginia
|
-0.8320
|
-1.2670
|
-1.9552
|
-1.6360
|
Missouri
|
-0.0116
|
-0.1781
|
0.2251
|
0.2267
|
Montana
|
-0.7575
|
-0.9527
|
-0.9706
|
-0.9362
|
Nebraska
|
-1.1801
|
-1.0312
|
-0.3376
|
-0.9264
|
California
|
-0.0116
|
0.9220
|
1.7020
|
1.4487
|
South Carolina
|
1.3309
|
0.9557
|
-1.3222
|
-0.3351
|
Nevada
|
0.7840
|
0.6526
|
0.9987
|
1.9809
|
Florida
|
1.5796
|
1.5843
|
0.9284
|
0.5913
|
- The R functions for computing distances.
- dist() function accepts only numeric data.
- get_dist() function [factoextra package] accepts only numeric data. it supports correlation-based distance measures.
- daisy() function [cluster package] is able to handle other variable types (nominal, ordinal, …).
Remark: All these functions compute distances between rows of the data.
Remark: If we want to compute pairwise distances between variables, we must transpose the data to have variables in the rows.
We first compute Euclidian distances
dist.eucl <- dist(df.scaled, method = "euclidean",upper = TRUE)
#stargazer(as.data.frame(as.matrix(dist.eucl)[1:3, 1:3]),header=TRUE, type='html',summary=FALSE,digits=1)
round(sqrt(sum((df.scaled["New Mexico",]-df.scaled["Iowa",])^2)),1)
[1] 4.1
round(sqrt(sum((df.scaled["New Mexico",]-df.scaled["Indiana",])^2)),1)
[1] 2.5
round(sqrt(sum((df.scaled["Iowa",]
-df.scaled["Indiana",])^2)),1)
[1] 1.8
- We also compute correlation based distances.
library("factoextra")
dist.cor <- get_dist(df.scaled, method = "pearson")
round(as.matrix(dist.cor)[1:3, 1:3], 1)
## New Mexico Iowa Indiana
## New Mexico 0.0 1.7 2.0
## Iowa 1.7 0.0 0.3
## Indiana 2.0 0.3 0.0
round(1-cor(df.scaled["New Mexico",],df.scaled["Iowa",]),1)
## [1] 1.7
round(1-cor(df.scaled["New Mexico",],df.scaled["Indiana",]),1)
## [1] 2
round(1-cor(df.scaled["Iowa",],df.scaled["Indiana",]),1)
## [1] 0.3
Visualizing distance matrices
- A simple solution for visualizing the distance matrices is to use the function fviz_dist() [factoextra package].
- Other specialized methods will be described later on.
library(factoextra)
fviz_dist(dist.eucl)

Partitioning Clustering
- Partitioning clustering are clustering methods used to classify observations within a data set, into multiple groups based on their similarity.
- The algorithms require the analyst to specify the number of clusters to be generated.
- We describe the commonly used partitioning clustering, including:
- K-means clustering (MacQueen, 1967), in which, each cluster is represented by the center or means of the data points belonging to the cluster. The K-means method is sensitive to anomalous data points and outliers.
- K-medoids clustering or PAM (Partitioning Around Medoids, Kaufman & Rousseeuw, 1990), in which, each cluster is represented by one of the objects in the cluster. PAM is less sensitive to outliers compared to k-means.
- CLARA algorithm (Clustering Large Applications), which is an extension to PAM adapted for large data sets.
K-Means Clustering
- The description of the algorithm is based on:
- HARTIGAN, John A. Clustering algorithms. John Wiley & Sons, Inc., 1975.
- The data used by the author are provided below.

- The principal nutrients in meat, fish, and fowl are listed.
- Recall that 1oz= 28.34952g.
- Estimated daily dietary allowances are: food energy (3200 cal), protein (70 g), calcium (0.8 g), and iron (10 mg).
- Table 4.2 convents the variables (with the exception of Fat) in percentage of food delivery.
- For e.g., the first (BB) ligne is obtained in the following way:
- \(340/3200=11\%\text{(Food Energy)}\).
- \(20/70=29\%\text{(Protein)}\).
- \({0.009}/{0.8}=1\%\text{ (Calcium)}\).
- \({2.6}/{10}= 26\%\text{ (Iron)}\).
- An argument could be made that iron is less important than calories or protein and so should be given less weight or ignored entirely.
- There are \(n\) objects and \(k\) clusters, \(k\leq n\).
- Our purpose is to partition the \(n\) objects (here foods) so that objects within clusters are close and objects in different clusters are distant.
- Each cluster contains at least one object and each object belongs to only one cluster.
- There is a very large number of possible partitions.
Exercice 11
- What is the number of possible partitions?
- Hint use the Stirling numbers of the second kind Wikipedia.
- Example of functions in R.
library(multicool)
## Loading required package: Rcpp
Stirling2(8,3)
## [1] 966
Stirling2(8,1)
## [1] 1
Stirling2(3,2)
## [1] 3
K-Means
- The discordance between the data and a given partition is denoted by \(e\).
- We use the technique of local optimization.
- A neighborhood of partitions is defined for each ption.
- Starting from an initial partition, search through a set of partitions at each step.
- Move from the partition to a partition in its neighborhood for which \(e\) is minim.
- If the neighborhoods are very large, it is cheaper computationally to move to the first partition discovered in the neighborhood where \(e\) is reduced from its present value.
- A number of stopping rules are possible.
- For example, the search stops when \(e\) is not reduced by movement to the neighborhood.
- The present partition is locally optimal in that it is the best partition in its neighborhood.
- Consider partitions of the five (\(n=5\)) beef foods \(\{\text{BB, BR,BS,BC,BH}\}\) into three clusters (\(k=3\)).
- Totally, there are 25 such partitions.
library(gmp)
Stirling2(5,3)
## Big Integer ('bigz') :
## [1] 25
A plausible neighborhood for a partition is the set of partitions obtained by transferring an object from one cluster to another.
For the partition (BB BR) (BS) (BC BH), the neighborhood consists of the following ten partitions:
- (BR) (BB BS) (BC BH)
- (BR) (BS) (BB BC BH)
- (BB) (BR BS) (BC BH)
- (BB) (BS) (BR BC BH)
- (BB BR BS) O (BC BH)
- (BB BR) O (BS BC BH)
- (BB BR BC) (BS) (BH)
- (BB BR) (BS BC) (BH)
- (BB BR BH) (BS) (BC)
- (BB BR) (BS BH) (BC)
K-Means Algorithm
Let \(\mathbf{x}_i\equiv (x_i^1,\cdots,x_i^m)\) the vector of values for the object \(i\), \(i=1,\cdots ,n.\)
The variables are assumed scaled.
The partition has \(k\) disjoint clusters: \(C_1,\cdots,C_k\), which are the indices of the objects in the various clusters.
Let \(n_l\) be the number of objects in cluster \(C_l\).
Each of the \(n\) objects lies in just one of the \(k\) clusters.
Note that \(\sum_{l=1}^k n_l=n\).
The vector of means over the objects in cluster \(C_l\) is denoted by \(\bar{\mathbf{x}}_{l}\), with \[
\bar{\mathbf{x}}_{l}\equiv\frac{1}{n_l}\sum_{i\in C_l}\mathbf{x}_{i}=(\bar{x}_l^1,\cdots,\bar{x}_l^m),\:l=1,\cdots, k,
\] where \[
\bar{x}_l^j\equiv \frac{\sum_{i\in C_l}x_i^{j}}{n_l},\:j=1,\cdots, m; \:l=1,\cdots,k.
\]
The distance between the object \(j\) and the cluster \(l\) is \(d(\mathbf{x}_i,\bar{\mathbf{x}}_l)\), where \(d\) is taken to be the Euclidian distance \[
d(\mathbf{x}_i,\bar{\mathbf{x}}_l)=||\mathbf{x}_i-\bar{\mathbf{x}}_l||=\bigg[\sum_{j=1}^m(x_i^j-\bar{x}_l^j)^2\bigg]^{1/2},\:i=1,\cdots,m;\:l=1,\cdots,k.
\] where \(||\mathbf{\cdot}||\) is the Euclidian norm.
The error of the partition is taken to be
\[
e=
\sum_{l=1}^{k}\sum_{i\in C_l}
||\mathbf{x}_i-\bar{\mathbf{x}}_l||^2.
\]
- Alternatively, we have \[
e=\sum_{i=1}^{n}\sum_{j=1}^m||\mathbf{x}_i-\bar{\mathbf{x}}_{l(j)}||^2,
\]
where \(l(i)\) is the index of the cluster of object \(i\).
- The general procedure is to search for a partition with a small error \(e\) by moving cases from one cluster to another.
- The search ends when no such movement reduces \(e\).
- STEP 1. Assume initial clusters. Compute the cluster means \(\bar{\mathbf{x}}_l\) and the initial error \(e\).
- STEP 2. For the first object, compute for every cluster \(l\)
\[
\Delta e=
\frac{n_ld^2(\mathbf{x}_1,\bar{\mathbf{x}}_l)}{n_l+1}-\frac{n_{l(1)}d^2(\mathbf{x}_1,\bar{\mathbf{x}}_{l(1)})}{n_{l(1)}-1},\:l=1,\cdots, k,\:l\neq l(1).
\]
- It corresponds to the error variation in transferring the first object from cluster \(l(1)\) to which it belongs to cluster \(l\).
- If the minimum of this quantity over all \(l\neq l(1)\) is negative, transfer the first case from cluster \(l(1)\) to this minimal \(l\).
- Adjust the cluster means of \(l(1)\) and the minimal \(l\) and add the error variation (which is negative) to \(e\).
- STEP 3. Repeat STEP 2 for each object \(i\) such that \(2\leq i \leq n\).
- STEP 4. lf no movement of an object from one cluster to another occurs for any case, stop. Otherwise, return to STEP 2.
Exercice 12
Prove that the error variation is indeed given by:
\[
\Delta e=
\frac{n_ld^2(\mathbf{x}_1,\bar{\mathbf{x}}_l)}{n_l+1}-\frac{n_{l(1)}d^2(\mathbf{x}_1,\bar{\mathbf{x}}_{l(1)})}{n_{l(1)}-1},\:l=1,\cdots, k,\:l\neq l(1).
\]
K-MEANS APPLIED TO FOOD NUTRIENT DATA
- Only the first eight foods will be considered.
- Only three variables, food energy, protein, and calcium as a percentage of recommended daily allowances are used.
- The eight foods \((m=8)\) are partitioned into three clusters (\(k=3\)).
Exercice 13
- Explain in details the k-means algorithm based on the following pages of Hartigan (1975).



#library("cluster.datasets")
#write.csv(rda.meat.fish.fowl.1959,"Hartigandat%a1.csv")
df<-read.csv("Hartigandata1.csv")
print(df)
## X name energy protein fat calcium iron
## 1 1 Braised beef 11 29 28 1 26
## 2 2 Hamburger 8 30 17 1 27
## 3 3 Roast beef 18 21 39 1 20
## 4 4 Beefsteak 12 27 32 1 26
## 5 5 Canned beef 6 31 10 2 37
## 6 6 Broiled chicken 8 29 3 1 14
## 7 7 Canned chicken 5 36 7 2 15
## 8 8 Beef heart 5 37 5 2 59
## 9 9 Roast lamb leg 8 29 20 1 26
## 10 10 Roast lamb shoulder 9 26 25 1 23
## 11 11 Smoked ham 11 29 28 1 25
## 12 12 Pork roast 11 27 29 1 25
## 13 13 Pork simmered 11 27 30 1 24
## 14 14 Beef tongue 6 26 14 1 25
## 15 15 Veal cutlet 6 33 9 1 27
## 16 16 Baked bluefish 4 31 4 3 6
## 17 17 Raw clams 2 16 1 10 60
## 18 18 Canned clams 1 10 1 9 54
## 19 19 Canned crabmeat 3 20 2 5 8
## 20 20 Fried haddock 4 23 5 2 5
## 21 21 Broiled mackerel 6 27 13 1 10
## 22 22 Canned mackerel 5 23 9 20 18
## 23 23 Fried perch 6 23 11 2 13
## 24 24 Canned salmon 4 24 5 20 7
## 25 25 Canned sardines 6 31 9 46 25
## 26 26 Canned tuna 5 36 7 1 12
## 27 27 Canned shrimp 3 33 1 12 26
df<-df[1:8,c(3,4,6)]
df
## energy protein calcium
## 1 11 29 1
## 2 8 30 1
## 3 18 21 1
## 4 12 27 1
## 5 6 31 2
## 6 8 29 1
## 7 5 36 2
## 8 5 37 2
# The data set contains some errors
df[3,1]<-13 # Error in line 3
df[6,1]<-4 # Error at line 6
df[7,3]<-1 # Error at line 7
df
## energy protein calcium
## 1 11 29 1
## 2 8 30 1
## 3 13 21 1
## 4 12 27 1
## 5 6 31 2
## 6 4 29 1
## 7 5 36 1
## 8 5 37 2
rownames(df)<-c("BB","HR","BR","BS","BC","CB","CC","BH")
df
## energy protein calcium
## BB 11 29 1
## HR 8 30 1
## BR 13 21 1
## BS 12 27 1
## BC 6 31 2
## CB 4 29 1
## CC 5 36 1
## BH 5 37 2
colnames(df)<-c("Energy","Protein","Calcium")
df
## Energy Protein Calcium
## BB 11 29 1
## HR 8 30 1
## BR 13 21 1
## BS 12 27 1
## BC 6 31 2
## CB 4 29 1
## CC 5 36 1
## BH 5 37 2
More on kmeans() output
km.res<-kmeans(df[1:8,],3,iter.max = 100)
km.res$cluster
## BB HR BR BS BC CB CC BH
## 3 3 2 3 1 1 1 1
km.res$centers
## Energy Protein Calcium
## 1 5.00000 33.25000 1.5
## 2 13.00000 21.00000 1.0
## 3 10.33333 28.66667 1.0
km.res$totss
## [1] 267.5
sum((df[1:8,]$Energy-mean(df[1:8,]$Energy))^2)+
sum((df[1:8,]$Protein-mean(df[1:8,]$Protein))^2)+
sum((df[1:8,]$Calcium-mean(df[1:8,]$Calcium))^2)
## [1] 267.5
7*var(df[1:8,]$Energy)+7*var(df[1:8,]$Protein)+7*var(df[1:8,]$Calcium)
## [1] 267.5
km.res$withinss
## [1] 47.75000 0.00000 13.33333
km.res$tot.withinss
## [1] 61.08333
km.res$betweenss
## [1] 206.4167
km.res$size
## [1] 4 1 3
km.res$iter
## [1] 1
Exercice 14
- Produce a heatmap for the data by rearranging the lines according to the found clusters (k=3).
Exercice 15
- Produce the Table 4.4 of Hartigan (1975).

Exercice 16
- Use ggplot2 to draw an x-y plot with the number of clusters on the x-axis and the error on the y-axis.
Remark: The location of a knee is generally considered as an appropriate number of clusters.
Visualizing k-means results
- The results are visualized using fviz_cluster() function.
- It draws a scatter plot of data points colored by cluster numbers.
- If the data contains more than 2 variables, the Principal Component Analysis (PCA) algorithm is used to reduce the dimensionality of the data.
- The first two principal dimensions are used to plot the data.
library(ggplot2)
library(factoextra)
fviz_cluster(km.res, df, ellipse.type = "norm")
## Too few points to calculate an ellipse
## Too few points to calculate an ellipse

fviz_cluster(km.res, data = df[1:8,],
palette = c("#2E9FDF", "#00AFBB", "#E7B800"),
ellipse.type = "euclid", # Concentration ellipse
star.plot = TRUE, # Add segments from centroids to items
repel = TRUE, # Avoid label overplotting (slow)
ggtheme = theme_minimal()
)
## Too few points to calculate an ellipse
## Too few points to calculate an ellipse

pca=prcomp(df[1:8,], scale = TRUE)
summary(pca)
## Importance of components:
## PC1 PC2 PC3
## Standard deviation 1.4655 0.7938 0.47119
## Proportion of Variance 0.7159 0.2100 0.07401
## Cumulative Proportion 0.7159 0.9260 1.00000
More on PCA
data(decathlon2)
decathlon.active <- decathlon2[1:23, 1:10]
res.pca <- prcomp(decathlon.active, scale = TRUE)
summary(res.pca)
## Importance of components:
## PC1 PC2 PC3 PC4 PC5 PC6 PC7
## Standard deviation 2.0308 1.3559 1.1132 0.90523 0.83759 0.65029 0.55007
## Proportion of Variance 0.4124 0.1839 0.1239 0.08194 0.07016 0.04229 0.03026
## Cumulative Proportion 0.4124 0.5963 0.7202 0.80213 0.87229 0.91458 0.94483
## PC8 PC9 PC10
## Standard deviation 0.52390 0.39398 0.3492
## Proportion of Variance 0.02745 0.01552 0.0122
## Cumulative Proportion 0.97228 0.98780 1.0000
fviz_pca_biplot(res.pca)

Silhouette
Assume the data are clustered into \(k\) clusters.
For \(i=1,\cdots,n\), let: \[
a_i=\frac{1}{n_{l(i)}-1}\sum_{i^\prime \in i\in C_{l(i)}\setminus\{i\}}d(i,i^\prime),
\] be the mean distance between \(i\) and all the points of the same cluster.
If \(n_{l(i)}=1\), we set \(a_i=0\).
It is interpreted as a measure of how well \(i\) is assigned to its cluster (smaller the value, better is the assignment).
Let also \[
b_i=\min_{l\neq l(i)}{\frac {1}{|n_{l}|}}\sum_{i^\prime\in C_l}d(i,i^\prime),
\] be the “neighboring cluster” of \(i\).
We now define a silhouette of \(i\) as \[
s_i=\frac {b_i-a_i}{\max(a_i,b_i)} \text{ if }n_{l(i)}>1,
\] and \[
s_i=0\text{ if }n_{l(i)}=1.
\]
Alternatively, we have \[
s_i=
\begin{cases}
1-a_i/b_i,&\:\text{if } a_i<b_i;\\
0,&\:\text{if } a_i=b_i;\\
b_i/a_i-1,&\:\text{if } a_i>b_i.
\end{cases}
\]
From the above definition it is clear that \[ -1\leq s_i\leq 1\]
For \(s_i\) to be close to 1 we require \(a_i<<b_i\).
If \(s_i\) is close to \(-1\), \(i\) is more appropriately clustered in its neighbouring cluster.
An \(s_i\) near zero means that the \(i\) is on the border of two natural clusters.
Silhouette example
library(knitr)
knitr::opts_chunk$set(echo = TRUE)
km.res<-kmeans(df[1:8,],3,iter.max = 100)
slobj<-silhouette(km.res$cluster,dist(df[1:8,]))
row.names(slobj)<-row.names(df[1:8,])
knitr::kable(slobj[,], caption = "Silhouettes values for the food example",digits = 4,col.names = c("Cluster","Neighbor","Silhouette width"),align=c("c","c","c"),booktabs = TRUE)%>%
kable_classic(latex_options = "hold_position")%>%
row_spec(1:2,background = "#2EFEF7")%>%
row_spec(3:5,background = "#F3F781")%>%
row_spec(6:8,background = "#FA58F4")
Silhouettes values for the food example
|
Cluster
|
Neighbor
|
Silhouette width
|
BB
|
2
|
1
|
-0.0053
|
HR
|
1
|
2
|
0.4659
|
BR
|
2
|
1
|
0.3785
|
BS
|
2
|
1
|
0.3921
|
BC
|
1
|
3
|
0.5168
|
CB
|
1
|
3
|
0.5312
|
CC
|
3
|
1
|
0.7764
|
BH
|
3
|
1
|
0.8062
|
Silhouette graph
silhouette(km.res$cluster,dist(df[1:8,]))%>%
fviz_silhouette()
## cluster size ave.sil.width
## 1 1 3 0.50
## 2 2 3 0.26
## 3 3 2 0.79

Exercice 17

- The author obtain the following two figures.


- Your task is to produce the same figures using modern dataviz tools.
- What did you learn from theses graphs?
Exercice 18
- Provide a Silhouette graph for the Hartigan (75) data
Exercice 19
- Provide a k-means clustering analysis of the US Arrest data
Partitioning Around Medoids (PAM, k-medoids)
- The algorithm used in the program PAM search for \(k\) representative objects among \(n\) objects.
- These objects should represent various aspects of the structure of the data.
- In the PAM algorithm, the representative objects are called medoids.
- After finding a set of \(k\) representative objects, the \(k\) clusters are constructed by assigning each object of the data set to the nearest representative object.
- To illustrate algorithm, consider a data set of ten objects (\(n = 10\)) and two variables (\(m = 2\)).
- Suppose the data set must be divided into two clusters (\(k = 2\)).
- The algorithm first considers possible choices of two representative objects.
- Then, it constructs the clusters around these representative objects.

- As an example, suppose objects 1 and 5 are the selected representative objects.
- In Table 2, the Euclidean distance from each of the objects to the two selected objects are given, as well as the smallest of these two dissimilarities and the corresponding representative object.
- The average dissimilarity is 9.37.

- In Table 3, the assignment is carried out for the case objects 4 and 8 are selected as representative objects.


- The average dissimilarity for the case objects 4 and 8 are selected is 2.30, which is considerably less than the value of 9.37, found when 1 and 5 were the representative objects.
- Alternatively a PAM program can be used by entering a matrix of dissimilarities between objects.
- The algorithms are rather sophisticated and are not detailed here.
x=c(1,5,5,5,10,25,25,25,25,29)
y=c(4,1,2,4,4,4,6,7,8,7)
df<-data.frame(x,y)
ggplot(df,aes(x=x,y=y,label=row.names(df)))+geom_point()+geom_text(label=row.names(df),nudge_x = 0.25, nudge_y = 0.25,
check_overlap = T)

dist(df,upper=T,diag=T)%>%
as.matrix()->M
cbind(M[,c(1,5)],apply(M[,c(1,5)],1,min))%>%
data.frame()->T2
T2$val=0
T2$val[which(T2[,1]==apply(M[,c(1,5)],1,min))]<-1
T2$val[which(T2[,2]==apply(M[,c(1,5)],1,min))]<-5
names(T2)<-c("Dist. wrt 1","Dist. wrt 5","Min. dist.","Closest rep. obj.")
kable(T2,digit =2,align = "lccc",booktabs = TRUE)%>%
kable_classic(latex_options = "hold_position")
Dist. wrt 1
|
Dist. wrt 5
|
Min. dist.
|
Closest rep. obj.
|
0.00
|
9.00
|
0.00
|
1
|
5.00
|
5.83
|
5.00
|
1
|
4.47
|
5.39
|
4.47
|
1
|
4.00
|
5.00
|
4.00
|
1
|
9.00
|
0.00
|
0.00
|
5
|
24.00
|
15.00
|
15.00
|
5
|
24.08
|
15.13
|
15.13
|
5
|
24.19
|
15.30
|
15.30
|
5
|
24.33
|
15.52
|
15.52
|
5
|
28.16
|
19.24
|
19.24
|
5
|
mean(T2$`Min. dist.`)
## [1] 9.36615
dist(df,upper=T,diag=T)%>%
as.matrix()->M
cbind(M[,c(4,8)],apply(M[,c(4,8)],1,min))%>%
data.frame()->T3
T3$val=0
T3$val[which(T3[,1]==apply(M[,c(4,8)],1,min))]<-1
T3$val[which(T3[,2]==apply(M[,c(4,8)],1,min))]<-5
names(T3)<-c("Dist. wrt 4","Dist. wrt 8","Min. dist.","Closest rep. obj.")
kable(T3,digit =2,align = "lccc",booktabs = TRUE)%>%
kable_classic(latex_options = "hold_position")
Dist. wrt 4
|
Dist. wrt 8
|
Min. dist.
|
Closest rep. obj.
|
4.00
|
24.19
|
4
|
1
|
3.00
|
20.88
|
3
|
1
|
2.00
|
20.62
|
2
|
1
|
0.00
|
20.22
|
0
|
1
|
5.00
|
15.30
|
5
|
1
|
20.00
|
3.00
|
3
|
5
|
20.10
|
1.00
|
1
|
5
|
20.22
|
0.00
|
0
|
5
|
20.40
|
1.00
|
1
|
5
|
24.19
|
4.00
|
4
|
5
|
mean(T3$`Min. dist.`)
## [1] 2.3
PAM<-pam(df,2)
PAM$medoids
## x y
## [1,] 5 2
## [2,] 25 7
PAM$id.med
## [1] 3 8
PAM$clustering
## [1] 1 1 1 1 1 2 2 2 2 2
PAM$objective
## build swap
## 3.421612 2.185730
PAM$isolation
## 1 2
## L* L*
## Levels: no L L*
\(L\) and \(L^\star\) clusters

Dist. wrt 3
|
Dist. wrt 8
|
Min. dist.
|
Closest rep. obj.
|
4.47
|
24.19
|
4.47
|
3
|
1.00
|
20.88
|
1.00
|
3
|
0.00
|
20.62
|
0.00
|
3
|
2.00
|
20.22
|
2.00
|
3
|
5.39
|
15.30
|
5.39
|
3
|
20.10
|
3.00
|
3.00
|
8
|
20.40
|
1.00
|
1.00
|
8
|
20.62
|
0.00
|
0.00
|
8
|
20.88
|
1.00
|
1.00
|
8
|
24.52
|
4.00
|
4.00
|
8
|
## [1] 2.18573
## 1 2 3 4 5
## 9.000000 5.830952 5.385165 5.000000 9.000000
## 1 2 3 4 5
## 24.00000 20.22375 20.09975 20.00000 15.00000
## 1 2 3 4 5
## TRUE TRUE TRUE TRUE TRUE
## [1] 9
## [1] 15
## [1] TRUE
## 6 7 8 9 10
## 5.000000 4.123106 4.000000 4.123106 5.000000
## 6 7 8 9 10
## 15.00000 15.13275 15.29706 15.52417 19.23538
## 6 7 8 9 10
## TRUE TRUE TRUE TRUE TRUE
## [1] 5
## [1] 15
## [1] TRUE
Multidimensional scaling


- The role of MDS is to construct a map like the one in the following Figure for those who do not know the “geography” in certain areas.

df<-read.csv("FlyingMileage.csv",sep = ";")
df%>%kable(booktabs = TRUE)%>%
kable_classic(latex_options = "hold_position")
X
|
Atl
|
Chi
|
Den
|
Hou
|
LA
|
Mia
|
NY
|
SF
|
Sea
|
DC
|
Atl
|
0
|
587
|
1212
|
701
|
1936
|
604
|
748
|
2139
|
2182
|
543
|
Chi
|
587
|
0
|
920
|
940
|
1745
|
1188
|
713
|
1858
|
1737
|
597
|
Den
|
1212
|
920
|
0
|
879
|
831
|
1726
|
1631
|
949
|
1021
|
1494
|
Hou
|
701
|
940
|
879
|
0
|
1374
|
968
|
1420
|
1645
|
1891
|
1220
|
LA
|
1936
|
1745
|
831
|
1374
|
0
|
2339
|
2451
|
347
|
959
|
2300
|
Mia
|
604
|
1188
|
1726
|
968
|
2339
|
0
|
1092
|
2594
|
2734
|
923
|
NY
|
748
|
713
|
1631
|
1420
|
2451
|
1092
|
0
|
2571
|
2408
|
205
|
SF
|
2139
|
1858
|
949
|
1645
|
347
|
2594
|
2571
|
0
|
678
|
2442
|
Sea
|
2182
|
1737
|
1021
|
1891
|
959
|
2734
|
2408
|
678
|
0
|
2329
|
DC
|
543
|
597
|
1494
|
1220
|
2300
|
923
|
205
|
2442
|
2329
|
0
|
We denote this matrix of distances (or dissimilarities) by \(D\) with compnents \(d_{i,i^\prime},\:i,i^\prime=1\cdots n.\).
The puprpose of the the basic MDS method is to build \(n\) points in the \(\mathbb{R}^p\) space, or using \(p\) dimensions.
The coordinates of the point \(i\) are \((x_i^1,\cdots,x_i^p)\). such as the matrix of distance between the points fits the observed matrix distances \(D\).
The MDS distance between points are assumed to be \[\hat{d}_{i,i^\prime}=\sqrt{\sum_{l=1}^p(x_{i^\prime}^l-x_{i}^l)^2}.\]
Kruskal’s STRESS formula one is given by
\[
S_1=\sqrt{\frac{\sum_{i,i^\prime}(\hat{d}_{i,i^\prime}-d_{i,i^\prime})^2}{\sum_{i,i^\prime}d_{i,i^\prime}^2}}
\] * The numerator is squared error, indicating the sum of differences between the observed distance and model-derived distance. The denominator is normalizing factor. * We want to find vectors which produce the smallest possible value for stress.
mds<-df%>%select(2:11)%>%
cmdscale(,2)
mds<-mds$points%>% as_tibble()
## Warning: The `x` argument of `as_tibble.matrix()` must have unique column names if `.name_repair` is omitted as of tibble 2.0.0.
## Using compatibility `.name_repair`.
colnames(mds) <- c("Dim.1", "Dim.2")
ggplot(mds,aes(x = Dim.1,y=Dim.2))+
geom_point()+geom_text(label=df$X,,hjust=0.5, vjust=3, size=2)

LS0tCnRpdGxlOiBDbHVzdGVyIEFuYWx5c2lzCmF1dGhvcjogS2FyaW0gS2lsYW5pCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlICAKICBwZGZfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwogIHdvcmRfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwplZGl0b3Jfb3B0aW9uczoKICBjaHVua19vdXRwdXRfdHlwZTogaW5saW5lCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKLS0tCgojIyBMaW5rIHRvIHRoZSBkYXRhIHNldHMKCkNvcHkgdGhpcyBsaW5rIGFuZCBvcGVuIGl0IGluIGEgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+IG5ldyB3aW5kb3c8L3NwYW4+LiBUaGUgZGlyZWN0IGFjY2VzcyB0byB0aGUgR29vZ2xlIERyaXZlIGxpbmsgaXMgPHNwYW4gc3R5bGU9ImNvbG9yOnJlZCI+IGZvcmJpZGRlbiB3aXRoaW4gKipScHViKio8L3NwYW4+LiAKCgo8aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2RyaXZlL2ZvbGRlcnMvMXJhLUc4SlA4TmNiaXloU0ppOXQtVXBQSFRBdWR3M2ZoP3VzcD1zaGFyaW5nPgoKCgoKCiMjIFJlcXVpcmVkIHBhY2thZ2VzCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1UUlVFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCiNpbnN0YWxsLnBhY2thZ2VzKCJkcGx5ciIsImFkZTQiLCJtYWdyaXR0ciIsImNsdXN0ZXIiLAojImZhY3RvZXh0cmEiLCJjbHVzdGVyLmRhdGFzZXRzIiwieHRhYmxlIiwia2FibGVFeHRyYSIsCiMia25pdHIiLCJzdW1tYXJ5dG9vbHMiKQpgYGAKCiMgRGVmaW5pdGlvbiBvZiBhIGRpc3RhbmNlCgotICAgQSBkaXN0YW5jZSBmdW5jdGlvbiBvciBhIG1ldHJpYyBvbiAkXG1hdGhiYntSfV5tLFw6bVxnZXEgMSQsIGlzIGEKICAgIGZ1bmN0aW9uICRkOlxtYXRoYmJ7Un1ebVx0aW1lc1xtYXRoYmJ7Un1ebVxyaWdodGFycm93IFxtYXRoYmJ7Un0kLgoKLSAgIEEgZGlzdGFuY2UgZnVuY3Rpb24gbXVzdCBzYXRpc2Z5IHNvbWUgcmVxdWlyZWQgcHJvcGVydGllcyBvciBheGlvbXMuCgotICAgVGhlcmUgYXJlIHRocmVlIG1haW4gYXhpb21zLgoKLSAgIEExLiAkZChcbWF0aGJme3h9LFxtYXRoYmZ7eX0pPSAwXGlmZiBcbWF0aGJme3h9PVxtYXRoYmZ7eX0kCiAgICAoaWRlbnRpdHkgb2YgaW5kaXNjZXJuaWJsZXMpOwoKLSAgIEEyLiAkZChcbWF0aGJme3h9LFxtYXRoYmZ7eX0pPSBkKFxtYXRoYmZ7eX0sXG1hdGhiZnt4fSkkIChzeW1tZXRyeSk7CgotICAgQTMuCiAgICAkZChcbWF0aGJme3h9LFxtYXRoYmZ7en0pXGxlcSBkKFxtYXRoYmZ7eH0sXG1hdGhiZnt5fSkrZChcbWF0aGJme3l9LFxtYXRoYmZ7en0pJAogICAgKHRyaWFuZ2xlIGluZXF1YWxpdHkpLCB3aGVyZSAkXG1hdGhiZnt4fT0oeF8xLFxjZG90cyx4X20pJCwKICAgICRcbWF0aGJme3l9PSh5XzEsXGNkb3RzLHlfbSkkIGFuZCAkXG1hdGhiZnt6fT0oel8xLFxjZG90cyx6X20pJCBhcmUKICAgIGFsbCB2ZWN0b3JzIG9mICRcbWF0aGJie1J9Xm0kLgoKLSAgIFdlIHNob3VsZCB1c2UgdGhlIHRlcm0gKmRpc3NpbWlsYXJpdHkqIHJhdGhlciB0aGFuICpkaXN0YW5jZSogd2hlbgogICAgbm90IGFsbCB0aGUgdGhyZWUgYXhpb21zIEExLUEzIGFyZSB2YWxpZC4KCi0gICBNb3N0IG9mIHRoZSB0aW1lLCB3ZSBzaGFsbCB1c2UsIHdpdGggc29tZSBhYnVzZSBvZiB2b2NhYnVsYXJ5LCB0aGUKICAgIHRlcm0gZGlzdGFuY2UuCgojIEV4ZXJjaWNlIDEKCi0gICBQcm92ZSB0aGF0IHRoZSB0aHJlZSBheGlvbXMgQTEtQTMgaW1wbHkgdGhlIG5vbi1uZWdhdGl2aXR5CiAgICBjb25kaXRpb246ICQkZChcbWF0aGJme3h9LFxtYXRoYmZ7eX0pXGdlcSAwLiQkCgojIEV1Y2xpZGVhbiBkaXN0YW5jZQoKLSAgIEl0IGlzIGRlZmluZWQgYnk6CgokJApkKFxtYXRoYmZ7eH0sXG1hdGhiZnt5fSk9XHNxcnR7XHN1bV97aj0xfV5tICh4X2oteV9qKV4yfS4KJCQKCi0gICBBMS1BMiBhcmUgb2J2aW91cy4KLSAgIFRoZSBwcm9vZiBvZiBBMyBpcyBwcm92aWRlZCBiZWxvdy4KCiMgRXhlcmNpY2UgMgoKLSAgIElzIHRoZSBzcXVhcmVkIEV1Y2xpZGlhbiBkaXN0YW5jZSBhIHRydWUgZGlzdGFuY2U/CgojIE1hbmhhdHRhbiBkaXN0YW5jZQoKLSAgIFRoZSBNYW5oYXR0YW4gZGlzdGFuY2UgYWxzbyBjYWxsZWQgdGF4aS1jYWIgbWV0cmljIG9yIGNpdHktYmxvY2sKICAgIG1ldHJpYyBpcyBkZWZpbmVkIGJ5OgoKJCRkKFxtYXRoYmZ7eH0sXG1hdGhiZnt5fSkKPVxzdW1fe2o9MX1ebSB8eF9qLXlfanwuJCQKCi0gICBBMS1BMiBob2xkLgotICAgQTMgYWxzbyBob2xkcyB1c2luZyB0aGUgZmFjdCB0aGF0ICR8YStifFxsZXEgfGF8K3xifCQgZm9yIGFueSByZWFscwogICAgJGEsYiQuCi0gICBUaGVyZSBleGlzdHMgYWxzbyBhIHdlaWdodGVkIHZlcnNpb24gb2YgdGhlIE1hbmhhdHRhbiBkaXN0YW5jZQogICAgY2FsbGVkIHRoZSBDYW5iZXJyYSBkaXN0YW5jZS4KCiFbXShNYW5oYXR0YW4uanBnKXt3aWR0aD0iODAlIn0KCmBgYHtyfQp4ID0gYygwLCAwKQp5ID0gYyg2LDYpCmRpc3QocmJpbmQoeCwgeSksIG1ldGhvZCA9ICJldWNsaWRpYW4iKQpkaXN0KHJiaW5kKHgsIHkpLCBtZXRob2QgPSAiZXVjbGlkaWFuIixkaWFnPVQsdXBwZXI9VCkKNipzcXJ0KDIpCmRpc3QocmJpbmQoeCwgeSksIG1ldGhvZCA9ICJtYW5oYXR0YW4iKQpkaXN0KHJiaW5kKHgsIHkpLCBtZXRob2QgPSAibWFuaGF0dGFuIixkaWFnPVQsdXBwZXI9VCkKCmBgYAoKIyBDYW5iZXJyYSBkaXN0YW5jZQoKLSAgIEl0IGlzIGRlZmluZWQgYnk6CgokJGQoXG1hdGhiZnt4fSxcbWF0aGJme3l9KQo9XHN1bV97aj0xfV5tCiBcZnJhY3t8eF9qLXlfanx9e3x4X2p8K3x5X2p8fS4kJAoKLSAgIE5vdGUgdGhhdCB0aGUgdGVybSAkfHhfai15X2p8Lyh8eF9qfCt8eV9qfCkkIGlzIG5vdCBwcm9wZXJseSBkZWZpbmVkCiAgICBhczogJHhfaj15X2o9MCQuCi0gICBCeSBjb252ZW50aW9uIHdlIHNldCB0aGF0IHRlcm0gdG8gYmUgemVybyBpbiB0aGF0IGNhc2UuCi0gICBUaGUgQ2FuYmVycmEgZGlzdGFuY2UgaXMgc3BlY2lhbGx5IHNlbnNpdGl2ZSB0byBzbWFsbCBjaGFuZ2VzIG5lYXIKICAgIHplcm8uCgpgYGB7cn0KeCA9IGMoMCwgMCkKeSA9IGMoNiw2KQpkaXN0KHJiaW5kKHgsIHkpLCBtZXRob2QgPSAiY2FuYmVycmEiKQo2LzYrNi82CmBgYAoKIyBFeGVyY2ljZSAzCgotICAgUHJvdmUgdGhhdCB0aGUgQ2FuYmVycmEgZGlzdGFuY2UgaXMgYSB0cnVlIGRpc3RhbmNlLCBpLmUuIHRoYXQgaXQKICAgIHNhdGlzZmllcyBBMS1BMy4KCiMgTWlua293c2tpIGRpc3RhbmNlCgotICAgQm90aCB0aGUgRXVjbGlkaWFuIGFuZCB0aGUgTWFuYXR0YW4gZGlzdGFuY2VzIGFyZSBzcGVjaWFsIGNhc2VzIG9mCiAgICB0aGUgTWlua293c2tpIGRpc3RhbmNlIHdoaWNoIGlzIGRlZmluZWQsIGZvciAkcFxnZXEgMSQsIGJ5OiAkJAogICAgZChcbWF0aGJme3h9LFxtYXRoYmZ7eX0pPQogICAgXGxlZnRbXHN1bV97aj0xfSB8eF9qLXlfanxee3B9XHJpZ2h0XV57MS9wfS4KICAgICQkCi0gICBGb3IgJHA9MSQsIHdlIGdldCB0aGUgTWFuaGF0dGFuIGRpc3RhbmNlLgotICAgRm9yICRwPTIkLCB3ZSBnZXQgdGhlIEV1Y2xpZGlhbiBkaXN0YW5jZS4KLSAgIExldCB1cyBhbHNvIGRlZmluZToKICAgICQkXHxcbWF0aGJme3h9XHxfcFxlcXVpdlxsZWZ0W1xzdW1fe2o9MX1ebSB8eF9qfF57cH1ccmlnaHRdXnsxL3B9LCQkCiAgICB3aGVyZSAkXHxcbWF0aGJme1xjZG90fVx8X3AkIGlzIGtub3duIGFzIHRoZSAkcCQtbm9ybSBvciBNaW5rb3dza2kKICAgIG5vcm0uCi0gICBOb3RlIHRoYXQgdGhlIE1pbmtvd3NraSBkaXN0YW5jZSBhbmQgbm9ybSBhcmUgcmVsYXRlZCBieToKCiQkZChcbWF0aGJme3h9LFxtYXRoYmZ7eX0pPVx8XG1hdGhiZnt4fS1cbWF0aGJme3l9XHxfcC4kJAoKLSAgIENvbnZlcnNlbHksIHdlIGhhdmU6CgokJFx8XG1hdGhiZnt4fVx8X3A9ZChcbWF0aGJme3h9LFxtYXRoYmZ7MH0pLCQkCgp3aGVyZSAkXG1hdGhiZnswfSQgaXMgdGhlIG51bGwtdmV0b3Igb2YgJFxtYXRoYmJ7Un1ebSQuCgpgYGB7cn0KbGlicmFyeSgiZ2dwbG90MiIpCnggPSBjKDAsIDApCnkgPSBjKDYsNikKTWlua293RGlzdD1jKCkgIyBJbml0aWFsaXNlciDDoCB2aWRlIGxhIGxpc3RlCmZvciAocCBpbiBzZXEoMSwzMCwuMDEpKQp7Ck1pbmtvd0Rpc3Q9YyhNaW5rb3dEaXN0LGRpc3QocmJpbmQoeCwgeSksIG1ldGhvZCA9ICJtaW5rb3dza2kiLCBwID0gcCkpICAgICAKfQoKZ2dwbG90KGRhdGEgPWRhdGEuZnJhbWUoeCA9IHNlcSgxLDMwLC4wMSksIHk9TWlua293RGlzdCApICwgbWFwcGluZyA9IGFlcyggeD14LCB5PSB5KSkrCiAgZ2VvbV9wb2ludChzaXplPS4xLGNvbG9yPSJyZWQiKSsKICB4bGFiKCJwIikreWxhYigiTWlua293c2tpIERpc3RhbmNlIikrZ2d0aXRsZSgiTWlua293c2tpIGRpc3RhbmNlIHdydCBwIikKYGBgCgojIEV4ZXJjaWNlIDQKClByb2R1Y2UgYSBzaW1pbGFyIGdyYXBoIHVzaW5nICJUaGUgRWNvbm9taXN0IiB0aGVtZS4gSW5kaWNhdGUgb24gdGhlCmdyYXBoIHRoZSBNYW5oYXR0YW4sIHRoZSBFdWNsaWRpYW4gZGlzdGFuY2VzIGFzIHdlbGwgYXMgdGhlIENoZWJ5c2hldgpkaXN0YW5jZSBpbnRyb2R1Y2VkIGJlbG93LgoKIyBDaGVieXNoZXYgZGlzdGFuY2UKCi0gICBBdCB0aGUgbGltaXQsIHdlIGdldCB0aGUgQ2hlYnlzaGV2IGRpc3RhbmNlIHdoaWNoIGlzIGRlZmluZWQgYnk6ICQkCiAgICBkKFxtYXRoYmZ7eH0sXG1hdGhiZnt5fSk9XG1heF97aj0xLFxjZG90cyxufSh8eF9qLXlfanwpPVxsaW1fe3BccmlnaHRhcnJvd1xpbmZ0eX0KICAgIFxsZWZ0W1xzdW1fe2o9MX0gfHhfai15X2p8XntwfVxyaWdodF1eezEvcH0uCiAgICAkJAoKLSAgIFRoZSBjb3JyZXNwb25kaW5nIG5vcm0gaXM6CgokJAp8XFxtYXRoYmZ7eH1cfF9caW5mdHk9XG1heF97aj0xLFxjZG90cyxufSh8eF9qfCkuCiQkCgojIE1pbmtvd3NraSBpbmVxdWFsaXR5CgotICAgVGhlIHByb29mIG9mIHRoZSB0cmlhbmd1bGFyIGluZXF1YWxpdHkgQTMgaXMgYmFzZWQgb24gdGhlIE1pbmtvd3NraQogICAgaW5lcXVhbGl0eToKCi0gICBGb3IgYW55IG5vbm5lZ2F0aXZlIHJlYWwgbnVtYmVycyAkYV8xLFxjZG90cyxhX20kOyAkYl8xLFxjZG90cyxiX20kLAogICAgYW5kIGZvciBhbnkgJHBcZ2VxIDEkLCB3ZSBoYXZlOiAkJAogICAgXGxlZnRbXHN1bV97aj0xfV5tIChhX2orYl9qKV57cH1ccmlnaHRdXnsxL3B9XGxlcQogICAgXGxlZnRbXHN1bV97aj0xfV5tIGFfal57cH1ccmlnaHRdXnsxL3B9CiAgICArXGxlZnRbXHN1bV97aj0xfV5tIGJfal57cH1ccmlnaHRdXnsxL3B9LgogICAgJCQKCi0gICBUbyBwcm92ZSB0aGF0IHRoZSBNaW5rb3dza2kgZGlzdGFuY2Ugc2F0aXNmaWVzIEEzLCBub3RpY2UgdGhhdCAkJAogICAgIFxzdW1fe2o9MX1ebXx4X2otel9qfF57cH09IFxzdW1fe2o9MX1ebXwoeF9qLXlfaikrKHlfai16X2opfF57cH0uCiAgICAkJAoKLSAgIFNpbmNlIGZvciBhbnkgcmVhbHMgJHgseSQsIHdlIGhhdmU6ICR8eCt5fFxsZXEgfHh8K3x5fCQsIGFuZCB1c2luZwogICAgdGhlIGZhY3QgdGhhdCAkeF5wJCBpcyBpbmNyZWFzaW5nIGluICR4XGdlcSAwJCwgd2Ugb2J0YWluOiAkJAogICAgIFxzdW1fe2o9MX1ebXx4X2otel9qfF57cH1cbGVxIFxzdW1fe2o9MX1ebSh8eF9qLXlfanwrfHlfai16X2p8KV57cH0uCiAgICAkJAoKLSAgIEFwcGx5aW5nIHRoZSBNaW5rb3dza2kgaW5lcXVhbGl0eSB3aXRoICRhX2o9fHhfai15X2p8JCBhbmQKICAgICRiX2o9fHlfai16X2p8JCwgJGo9MSxcY2RvdHMsbiQsIHdlIGdldDogJCQKICAgICBcc3VtX3tqPTF9Xm18eF9qLXpfanxee3B9XGxlcSBcbGVmdChcc3VtX3tqPTF9Xm0gfHhfai15X2p8XntwfVxyaWdodCleezEvcH0rXGxlZnQoXHN1bV97aj0xfV5tIHx5X2otel9qfF57cH1ccmlnaHQpXnsxL3B9LgogICAgJCQKCiMgRXhlcmNpY2UgNQoKVG8gaWxsdXN0cmF0ZSB0aGUgTWlua293c2tpIGluZXF1YWxpdHksIGRyYXcgJDEwMCQgdGltZXMgdHdvIGxpc3RzIG9mCiQxMDAkIGRyYXdzIGZyb20gdGhlIGxvZ25vcm1hbCBkaXN0cmlidXRpb24gd2l0aCBtZWFuICQxNjAwJCBhbmQKc3RhbmRhcmQtZGV2aWF0aW9uICQzMDAkLiBJbGx1c3RyYXRlIHdpdGggYSBncmFwaCB0aGUgZ2FwIGJldHdlZW4gdGhlCnR3byBkcmF3biBsaXN0cy4KCiMgSMO2bGRlciBpbmVxdWFsaXR5CgotICAgVGhlIHByb29mIG9mIHRoZSBNaW5rb3dza2kgaW5lcXVhbGl0eSBpdHNlbGYgcmVxdWlyZXMgdGhlIEjDtmxkZXIKICAgIGluZXF1YWxpdHk6Ci0gICBGb3IgYW55IG5vbm5lZ2F0aXZlIHJlYWwgbnVtYmVycyAkYV8xLFxjZG90cyxhX20kOyAkYl8xLFxjZG90cyxiX20kLAogICAgYW5kIGFueSAkcCxxPjEkIHdpdGggJDEvcCsxL3E9MSQsIHdlIGhhdmU6ICQkCiAgICBcc3VtX3tqPTF9Xm0gYV9qYl9qXGxlcQogICAgXGxlZnRbXHN1bV97aj0xfV5tIGFfal57cH1ccmlnaHRdXnsxL3B9CiAgICBcbGVmdFtcc3VtX3tqPTF9Xm0gYl9qXntxfVxyaWdodF1eezEvcX0KICAgICQkCi0gICBUaGUgcHJvb2Ygb2YgdGhlIEjDtmxkZXIgaW5lcXVhbGl0eSByZWxpZXMgb24gdGhlIFlvdW5nIGluZXF1YWxpdHk6Ci0gICBGb3IgYW55ICRhLGI+MCQsIHdlIGhhdmUgJCQKICAgIGFiXGxlcSBcZnJhY3thXnB9e3B9K1xmcmFje2JecX17cX0sCiAgICAkJCB3aXRoIGVxdWFsaXR5IG9jY3VyaW5nIGlmZjogJGFecD1iXnEkLgotICAgVG8gcHJvdmUgdGhlIFlvdW5nIGluZXF1YWxpdHksIG9uZSBjYW4gdXNlIHRoZSAoc3RyaWN0KSBjb252ZXhpdHkgb2YKICAgIHRoZSBleHBvbmVudGlhbCBmdW5jdGlvbi4KLSAgIEZvciBhbnkgcmVhbHMgJHgseSQsIHdlIGhhdmU6ICQkCiAgICBcZXhwKFxmcmFje3h9e3B9K1xmcmFje3l9e3F9KVxsZXEgXGZyYWN7ZV57eH19e3B9K1xmcmFje2Vee3l9fXtxfS4gCiAgICAkJAotICAgV2UgdGhlbiBzZXQ6ICR4PXBcbG4gYSQgYW5kICR5PXFcbG4gYiQgdG8gZ2V0IHRoZSBZb3VuZyBpbmVxdWFsaXR5LgotICAgQSBnb29kIHJlZmVyZW5jZSBvbiBpbmVxdWFsaXRpZXMgaXM6IFtaLiBDdmV0a292c2tpLCBJbmVxdWFsaXRpZXM6CiAgICB0aGVvcmVtcywgdGVjaG5pcXVlcyBhbmQgc2VsZWN0ZWQgcHJvYmxlbXMsIDIwMTIsIFNwcmluZ2VyIFNjaWVuY2UgJgogICAgQnVzaW5lc3MgTWVkaWFdKGh0dHBzOi8vYm9vazR5b3Uub3JnL2Jvb2svMTIyODM5NC8zZWMwZmIpLgoKXCMgQ2F1Y2h5LVNjaHdhcnR6IGluZXF1YWxpdHkKCi0gICBOb3RlIHRoYXQgdGhlIHRyaWFuZ3VsYXIgaW5lcXVhbGl0eSBmb3IgdGhlIE1pbmtvd3NraSBkaXN0YW5jZQogICAgaW1wbGllczogJCQKICAgIFxzdW1fe2o9MX1ebSB8eF9qfFxsZXEKICAgIFxsZWZ0W1xzdW1fe2o9MX1ebSB8eF9qfF57cH1ccmlnaHRdXnsxL3B9LgogICAgJCQKLSAgIE5vdGUgdGhhdCBmb3IgJHA9MiQsIHdlIGhhdmUgJHE9MiQuIFRoZSBIw7ZsZGVyIGluZXF1YWxpdHkgaW1wbGllcwogICAgZm9yIHRoYXQgc3BlY2lhbCBjYXNlICQkCiAgICBcc3VtX3tqPTF9Xm18eF9qeV9qfFxsZXFcc3FydHtcc3VtX3tqPTF9Xm0geF9qXjJ9XHNxcnR7XHN1bV97aj0xfV5tIHlfal4yfS4gCiAgICAkJAotICAgU2luY2UgdGhlIExIUyBvZCB0aGVzIGFib3ZlIGluZXF1YWxpdHkgaXMgZ3JlYXRlciB0aGVuCiAgICAkfFxzdW1fe2o9MX1ebXhfanlfanwkLCB3ZSBnZXQgdGhlIENhdWNoeS1TY2h3YXJ0eiBpbmVxdWFsaXR5ICQkCiAgICB8XHN1bV97aj0xfV5teF9qeV9qfFxsZXFcc3FydHtcc3VtX3tqPTF9Xm0geF9qXjJ9XHNxcnR7XHN1bV97aj0xfV5tIHlfal4yfS4gCiAgICAkJAotICAgVXNpbmcgdGhlIGRvdCBwcm9kdWN0IG5vdGF0aW9uIGNhbGxlZCBhbHNvIHNjYWxhciBwcm9kdWN0IG5vdGF0aW9uOgogICAgJFxtYXRoYmZ7eFxjZG90IHl9PVxzdW1fe2o9MX1ebXhfanlfaiQsIGFuZCB0aGUgbm9ybSBub3RhdGlvbgogICAgJFx8XG1hdGhiZntcY2RvdH1cfF8yJCwgdGhlIENhdWNoeS1TY2h3YXJ0eiBpbmVxdWFsaXR5IGlzOiAkJAogICAgfFxtYXRoYmZ7eFxjZG90IHl9IHwgXGxlcSBcfFxtYXRoYmZ7eH1cfF8yIFx8IFxtYXRoYmZ7eX1cfF8yLgogICAgJCQKCiMgUGVhcnNvbiBjb3JyZWxhdGlvbiBkaXN0YW5jZQoKLSAgIFRoZSBQZWFyc29uIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50IGlzIGEgc2ltaWxhcml0eSBtZWFzdXJlIG9uCiAgICAkXG1hdGhiYntSfV5tJCBkZWZpbmVkIGJ5OiAkJAogICAgXHJobyhcbWF0aGJme3h9LFxtYXRoYmZ7eX0pPQogICAgXGZyYWN7XHN1bV97aj0xfV5tICh4X2otXGJhcntcbWF0aGJme3h9fSkoeV9qLVxiYXJ7XG1hdGhiZnt5fX0pfXt7XHNxcnR7XHN1bV97aj0xfV5tICh4X2otXGJhcntcbWF0aGJme3h9fSleMlxzdW1fe2o9MX1ebSAoeV9qLVxiYXJ7XG1hdGhiZnt5fX0pXjJ9fX0sCiAgICAkJCB3aGVyZSAkXGJhcntcbWF0aGJme3h9fSQgaXMgdGhlIG1lYW4gb2YgdGhlIHZlY3RvciAkXG1hdGhiZnt4fSQKICAgIGRlZmluZWQgYnk6ICQkXGJhcntcbWF0aGJme3h9fT1cZnJhY3sxfXtufVxzdW1fe2o9MX1ebSB4X2osJCQKCi0gICBOb3RlIHRoYXQgdGhlIFBlYXJzb24gY29ycmVsYXRpb24gY29lZmZpY2llbnQgc2F0aXNmaWVzIFAyIGFuZCBpcwogICAgaW52YXJpYW50IHRvIGFueSBwb3NpdGl2ZSBsaW5lYXIgdHJhbnNmb3JtYXRpb24sIGkuZS46CiAgICAkJFxyaG8oXGFscGhhXG1hdGhiZnt4fSxcbWF0aGJme3l9KT1ccmhvKFxtYXRoYmZ7eH0sXG1hdGhiZnt5fSksJCQKICAgIGZvciBhbnkgJFxhbHBoYT4wJC4KCi0gICBUaGUgUGVhcnNvbiBkaXN0YW5jZSAob3IgY29ycmVsYXRpb24gZGlzdGFuY2UpIGlzIGRlZmluZWQgYnk6ICQkCiAgICBkKFxtYXRoYmZ7eH0sXG1hdGhiZnt5fSk9MS1ccmhvKFxtYXRoYmZ7eH0sXG1hdGhiZnt5fSkuCiAgICAkJAoKLSAgIE5vdGUgdGhhdCB0aGUgUGVhcnNvbiBkaXN0YW5jZSBkb2VzIG5vdCBzYXRpc2Z5IEExIHNpbmNlCiAgICAkZChcbWF0aGJme3h9LFxtYXRoYmZ7eH0pPTAkIGZvciBhbnkgbm9uLXplcm8gdmVjdG9yICRcbWF0aGJme3h9JC4KICAgIEl0IG5laXRoZXIgc2F0aXNmaWVzIHRoZSB0cmlhbmdsZSBpbmVxdWFsaXR5LiBIb3dldmVyLCB0aGUgc3ltbWV0cnkKICAgIHByb3BlcnR5IGlzIGZ1bGxmaWxsZWQuCgojIENvc2luZSBjb3JyZWxhdGlvbiBkaXN0YW5jZQoKLSAgIFRoZSBjb3NpbmUgb2YgdGhlIGFuZ2xlICRcdGhldGEkIGJldHdlZW4gdHdvIHZlY3RvcnMgJFxtYXRoYmZ7eH0kCiAgICBhbmQgJFxtYXRoYmZ7eX0kIGlzIGEgbWVhc3VyZSBvZiBzaW1pbGFyaXR5IGdpdmVuIGJ5OiAkJAogICAgXGNvcyhcdGhldGEpPVxmcmFje1xtYXRoYmZ7eH1cY2RvdCBcbWF0aGJme3l9fXtcfFxtYXRoYmZ7eH1cfF8yXHxcbWF0aGJme3l9XHxfMn09XGZyYWN7XHN1bV97aj0xfV5tIHhfaiB5X2p9e3tcc3FydHtcc3VtX3tqPTF9Xm0geF9qXjJcc3VtX3tqPTF9Xm0geV9qXjJ9fX0uCiAgICAkJAotICAgTm90ZSB0aGF0IHRoZSBjb3NpbmUgb2YgdGhlIGFuZ2xlIGJldHdlZW4gdGhlIHR3byBjZW50cmVkIHZlY3RvcnMKICAgICRcbWF0aGJme3h9LVxiYXJ7XG1hdGhiZnt4fX1cbWF0aGJmezF9JCBhbmQKICAgICRcbWF0aGJme3l9LVxiYXJ7XG1hdGhiZnt5fX1cbWF0aGJmezF9JCBjb2luY2lkZXMgd2l0aCB0aGUgUGVhcnNvbgogICAgY29ycmVsYXRpb24gY29lZmZpY2llbnQgb2YgJFxtYXRoYmZ7eH0kIGFuZCAkXG1hdGhiZnt5fSQsIHdoZXJlCiAgICAkXG1hdGhiZnsxfSQgaXMgYSB2ZWN0b3Igb2YgdW5pdHMgb2YgJFxtYXRoYmJ7Un1ebSQuCi0gICBUaGUgY29zaW5lIGNvcnJlbGF0aW9uIGRpc3RhbmNlIGlzIGRlZmluZWQgYnk6ICQkCiAgICBkKFxtYXRoYmZ7eH0sXG1hdGhiZnt5fSk9MS1cY29zKFx0aGV0YSkuCiAgICAkJAotICAgSXQgc2hhcmVzIHNpbWlsYXIgcHJvcGVydGllcyB0aGFuIHRoZSBQZWFyc29uIGNvcnJlbGF0aW9uIGRpc3RhbmNlLgogICAgTGlrZXdpc2UsIEF4aW9tcyBBMSBhbmQgQTMgYXJlIG5vdCBzYXRpc2ZpZWQuCgojIFNwZWFybWFuIGNvcnJlbGF0aW9uIGRpc3RhbmNlCgotICAgVG8gY2FsY3VsYXRlIHRoZSBTcGVhcm1hbidzIHJhbmstb3JkZXIgY29ycmVsYXRpb24sIHdlIG5lZWQgdG8gbWFwCiAgICBzZXBlcmF0ZWx5IGVhY2ggb2YgdGhlIHZlY3RvcnMgdG8gcmFua2VkIGRhdGEgdmFsdWVzOgogICAgJCRcbWF0aGJme3h9XHJpZ2h0YXJyb3cgXHRleHR7cmFua30oXG1hdGhiZnt4fSk9KHhfMV5yLFxjZG90cyx4X21ecikuJCQKLSAgIEhlcmUsICR4X2peciQgaXMgdGhlIHJhbmsgb2YgJHhfaiQgYW1vbmcgdGhlIHNldCBvZiB2YWx1ZXMgb2YKICAgICRcbWF0aGJme3h9JC4KLSAgIFdlIGlsbHVzdHJhdGUgdGhpcyB0cmFuc2Zvcm1hdGlvbiB3aXRoIGEgc2ltcGxlIGV4YW1wbGU6Ci0gICBJZiAkXG1hdGhiZnt4fT0oMywgMSwgNCwgMTUsIDkyKSQsIHRoZW4gdGhlIHJhbmstb3JkZXIgdmVjdG9yIGlzCiAgICAkXHRleHR7cmFua30oXG1hdGhiZnt4fSk9KDIsMSwzLDQsNSkkLgoKYGBge3J9Cng9YygzLCAxLCA0LCAxNSwgOTIpCnJhbmsoeCkKYGBgCgotICAgVGhlIFNwZWFybWFuJ3MgcmFuayBjb3JyZWxhdGlvbiBvZiB0d28gbnVtZXJpY2FsIHZlY3RvcnMKICAgICRcbWF0aGJme3h9JCBhbmQgJFxtYXRoYmZ7eX0kIGlzIHNpbXBseSB0aGUgUGVhcnNvbiBjb3JyZWxhdGlvbiBvZgogICAgdGhlIHR3byBjb3JyZXNwbmRpbmcgcmFuay1vcmRlciB2ZWN0b3JzICRcdGV4dHtyYW5rfShcbWF0aGJme3h9KSQKICAgIGFuZCAkXHRleHR7cmFua30oXG1hdGhiZnt5fSkkLCBpLmUuCiAgICAkXHJobyhcdGV4dHtyYW5rfShcbWF0aGJme3h9KSxcdGV4dHtyYW5rfShcbWF0aGJme3l9KSkkLiBUaGlzCiAgICBtZWFzdXJlIGlzIGlzIHVzZWZ1bCBiZWNhdXNlIGl0IGlzIG1vcmUgcm9idXN0IGFnYWluc3Qgb3V0bGllcnMgdGhhbgogICAgdGhlIFBlYXJzb24gY29ycmVsYXRpb24uCi0gICBJZiBhbGwgdGhlICRuJCByYW5rcyBhcmUgZGlzdGluY3QsIGl0IGNhbiBiZSBjb21wdXRlZCB1c2luZyB0aGUKICAgIGZvbGxvd2luZyBmb3JtdWxhOiAkJAogICAgXHJobyhcdGV4dHtyYW5rfShcbWF0aGJme3h9KSxcdGV4dHtyYW5rfShcbWF0aGJme3l9KSk9MS1cZnJhY3s2XHN1bV97aj0xfV5tIGRfal4yfXtuKG5eMi0xKX0sCiAgICAkJCB3aGVyZSAkZF9qPXhfal5yLXlfal5yLFw6aj0xLFxjZG90cyxuJC4KLSAgIFRoZSBzcGVhcm1hbiBkaXN0YW5jZSBpcyB0aGVuIGRlZmluZWQgYnk6ICQkCiAgICBkKFxtYXRoYmZ7eH0sXG1hdGhiZnt5fSk9MS1ccmhvKFx0ZXh0e3Jhbmt9KFxtYXRoYmZ7eH0pLFx0ZXh0e3Jhbmt9KFxtYXRoYmZ7eX0pKS4KICAgICQkCi0gICBJdCBjYW4gYmUgc2hvd24gdGhhdCBlYXNhbHkgdGhhdCBpdCBpcyBub3QgYSBwcm9wZXIgZGlzdGFuY2UuCi0gICBJZiBhbGwgdGhlICRuJCByYW5rcyBhcmUgZGlzdGluY3QsIHdlIGdldDogJCQKICAgIGQoXG1hdGhiZnt4fSxcbWF0aGJme3l9KT1cZnJhY3s2XHN1bV97aj0xfV5tIGRfal4yfXtuKG5eMi0xKX0uCiAgICAkJAoKYGBge3J9Cng9YygzLCAxLCA0LCAxNSwgOTIpCnJhbmsoeCkKeT1jKDMwLDIgLCA5LCAyMCwgNDgpCnJhbmsoeSkKZD1yYW5rKHgpLXJhbmsoeSkKZApjb3IocmFuayh4KSxyYW5rKHkpKQoxLTYqc3VtKGReMikvKDUqKDVeMi0xKSkKYGBgCgojIEV4ZXJjaWNlIDYKCi0gICBGb3IgdGhlIHR3byB2ZWN0b3JzICRcbWF0aGJme3h9PSgyMiwzNCwxLDEyLDI1LDU2LDcpJCBhbmQKICAgICRcbWF0aGJme3l9PSgyLDY0LDEyLDIsMjIsNSw4KSQgOgotICAgQ2FsY3VsYXRlIHRoZSByYW5rcyBmb3IgZWFjaCB2ZWN0b3IuCi0gICBEZWR1Y2UgdGhlIFNwZWFybWFuIGNvcnJlbGF0aW9uIGRpc3RhbmNlIGZyb20gdGhhdCByYW5rcy4KLSAgIERlZHVjZSB0aGUgU3BlYXJtYW4gY29ycmVsYXRpb24gZGlzdGFuY2UgZnJvbSB0aGUgYWJvdmUgZGlzcGFseWVkCiAgICBhbHRlcm5hdGl2ZSBlcXVhdGlvbi4KLSAgIENhbGN1bGF0ZSB0aGUgU3BlYXJtYW4gY29ycmVsYXRpb24gZGlzdGFuY2UgdXNpbmcgdGhlICoqUioqCiAgICBmdW5jdGlvbi4KCiMgS2VuZGFsbCB0YXUgZGlzdGFuY2UKCi0gICBUaGUgS2VuZGFsbCByYW5rIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50IGlzIGNhbGN1bGF0ZWQgZnJvbSB0aGUKICAgIG51bWJlciBvZiBjb3JyZXNwb25kYW5jZXMgYmV0d2VlbiB0aGUgcmFua2luZ3Mgb2YgJFxtYXRoYmZ7eH0kIGFuZAogICAgdGhlIHJhbmtpbmdzIG9mICRcbWF0aGJme3l9JC4KLSAgIFRoZSBudW1iZXIgb2YgcGFpcnMgb2Ygb2JzZXJ2YXRpb25zIGFtb25nICRuJCBvYnNlcnZhdGlvbnMgb3IgdmFsdWVzCiAgICBpczogJCR7biBcY2hvb3NlIDJ9ID1cZnJhY3tuKG4tMSl9ezJ9LiQkCi0gICBUaGUgcGFpcnMgb2Ygb2JzZXJ2YXRpb25zICQoeF97aX0seF97an0pJCBhbmQgJCh5X3tpfSx5X3tqfSkkIGFyZQogICAgc2FpZCB0byBiZSAqY29uY29yZGFudCogaWY6CiAgICAkJFx0ZXh0e3NpZ259KHhfai14X2opPVx0ZXh0e3NpZ259KHlfai15X2opLCQkIGFuZCB0byBiZQogICAgKmRpc2NvcmRhbnQqIGlmOiAkJFx0ZXh0e3NpZ259KHhfai14X2opPS1cdGV4dHtzaWdufSh5X2oteV9qKSwkJAogICAgd2hlcmUgJFx0ZXh0e3NpZ259KFxjZG90KSQgcmV0dXJucyAkMSQgZm9yIHBvc2l0aXZlIG51bWJlcnMgYW5kICQtMSQKICAgIG5lZ2F0aXZlIG51bWJlcnMgYW5kICQwJCBvdGhlcndpc2UuCi0gICBJZiAkeF9qPXhfaiQgb3IgJHlfaj15X2okIChvciBib3RoKSwgdGhlcmUgaXMgYSB0aWUuCi0gICBUaGUgS2VuZGFsbCAkXHRhdSQgY29lZmZpY2llbnQgaXMgZGVmaW5lZCBieSAobmVnbGVjdGluZyB0aWVzKToKICAgICQkXHRhdSA9XGZyYWMgezF9e24obi0xKX1cc3VtX3tqPTF9XntufVxzdW1fe2o9MX1ebVx0ZXh0e3NpZ259KHhfai14X2opXHRleHR7c2lnbn0oeV9qLXlfaikuJCQKLSAgIExldCAkbl9jJCAocmVzcC4gJG5fZCQpIGJlIHRoZSBudW1iZXIgb2YgY29uY29yZGFudCAocmVzcC4KICAgIGRpc2NvcmRhbnQpIHBhaXJzLCB3ZSBoYXZlICQkXHRhdSA9XGZyYWMgezIobl9jLW5fZCl9e24obi0xKX0uJCQKLSAgIFRoZSBLZW5kYWxsIHRhdSBkaXN0YW5jZSBpcyB0aGVuOgogICAgJCRkKFxtYXRoYmZ7eH0sXG1hdGhiZnt5fSk9MS1cdGF1LiAkJAotICAgUmVtYXJrOiB0aGUgdHJpYW5ndWxhciBpbmVxdWFsaXR5IG1heSBmYWlsIGluIGNhc2VzIHdoZXJlIHRoZXJlIGFyZQogICAgdGllcy4KCmBgYHtyfQp4PWMoMywgMSwgNCwgMTUsIDkyKQp5PWMoMzAsMiAsIDksIDIwLCA0OCkKdGF1PTAKZm9yIChpIGluIDE6NSkKeyAgCnRhdT10YXUrc2lnbih4IC14W2ldKSUqJXNpZ24oeSAteVtpXSkKfQp0YXU9dGF1Lyg1KjQpCnRhdQpjb3IoeCx5LCBtZXRob2Q9ImtlbmRhbGwiKQpgYGAKCiMgRXhlcmNpY2UgNwoKLSAgIEZvciB0aGUgdHdvIHZlY3RvcnMgJFxtYXRoYmZ7eH09KDIyLDM0LDEsMTIsMjUsNTYsNykkIGFuZAogICAgJFxtYXRoYmZ7eX09KDIsNjQsMTIsMiwyMiw1LDgpJDoKLSAgIExpc3QgYWxsIHBhaXJzIG9mIGNvb3JkaW5hdGVzLgotICAgSG93IG1hbnkgcGFpcnMgYXJlIHRoZXJlPwotICAgRm9yIGVhY2ggcGFpciBhbmQgZWFjaCBjZWN0b3IsIGNvbXB1dGUgdGhlIHNpZ25zIG9mIHRoZSBkaWZmZXJlbmNlcwogICAgaW4gY29vcmRpbmF0ZXMuCi0gICBEZWR1Y2UgdGhlIEtlbmRhbGwgdGF1IGNvZWZmaWNpZW50IHVzaW5nIHRoZSBhYm92ZSBjb21wdXRhdGlvbnMuCi0gICBDYWxjdWxhdGUgdGhlIEtlbmRhbGwgdGF1IGNvZWZmaWNpZW50IHVzaW5nIHRoZSBSIGZ1bmN0aW9uLgoKIyBTdGFuZGFyZGl6YXRpb24KCi0gICBWYXJpYWJsZXMgb3IgbWVhc3VyZW1lbnRzIGFyZSBvZnRlbiBzdGFuZGFyZGl6ZWQgYmVmb3JlIGNhbGN1bGF0aW5nCiAgICBkaXNzaW1pbGFyaXRpZXMuCi0gICBTdGFuZGFyZGl6YXRpb24gY29udmVydHMgdGhlIG9yaWdpbmFsIHZhcmlhYmxlcyBpbnRvIHVuaXRlbGVzcwogICAgdmFyaWFibGVzLgotICAgQSB3ZWxsIGtub3duIG1ldGhvZCBpcyB0aGUgei1zY29yZSB0cmFuc2Zvcm1hdGlvbi4KLSAgIExldCAkXG1hdGhiZnt2fVxlcXVpdiAodl8xLFxjZG90cyx2X24gKSQgYSB2ZWN0b3Igb2YgbWVhc3VyZW1lbnRzCiAgICByZWNyZGVkIGZvciAkbiQgaW5kaXZpZHVhbHMgb3Igb2JqZWN0cy4gJCQKICAgIFxtYXRoYmZ7dn1ccmlnaHRhcnJvdyAoXGZyYWN7dl8xLVxiYXJ7XG1hdGhiZnt2fX19e3NfXG1hdGhiZnt2fX0sXGNkb3RzLFxmcmFje3Zfbi1cYmFye1xtYXRoYmZ7dn19fXtzX1xtYXRoYmZ7dn19KSwKICAgICQkIHdoZXJlICRcYmFye1xtYXRoYmZ7dn19LHNfXG1hdGhiZnt2fSQgYXJlIHRoZSBzYW1wbGUgbWVhbiBhbmQKICAgIHN0YW5kYXJkLWRldmlhdGlvbiwgcmVzcGVjdGl2ZWx5LCBnaXZlbiBieTogJCQKICAgIFxiYXJ7XG1hdGhiZnt2fX09XGZyYWN7MX17bn1cc3VtX3tpPTF9Xm4gdl9pLFw6CiAgICBzX1xtYXRoYmZ7dn09XGZyYWN7MX17bi0xfVxzdW1fe2k9MX1ebih2X2ktXGJhcntcbWF0aGJme3Z9fSleMi4KICAgICQkCi0gICBUaGUgdHJhbnNmb3JtZWQgdmFyaWFibGUgd2lsbCBoYXZlIGEgbWVhbiBvZiAkMCQgYW5kIGEgdmFyaWFuY2Ugb2YKICAgICQxJC4KLSAgIFRoZSByZXN1bHQgb2J0YWluZWQgd2l0aCBQZWFyc29uIGNvcnJlbGF0aW9uIG1lYXN1cmVzIGFuZAogICAgc3RhbmRhcmRpemVkIEV1Y2xpZGVhbiBkaXN0YW5jZXMgYXJlIGNvbXBhcmFibGUuCi0gICBGb3Igb3RoZXIgbWV0aG9kcywgc2VlOiBbTWlsbGlnYW4sIEcuIFcuLCAmIENvb3BlciwgTS4gQy4gKDE5ODgpLiBBCiAgICBzdHVkeSBvZiBzdGFuZGFyZGl6YXRpb24gb2YgdmFyaWFibGVzIGluIGNsdXN0ZXIgYW5hbHlzaXMuICpKb3VybmFsCiAgICBvZiBjbGFzc2lmaWNhdGlvbiosICo1KigyKSwKICAgIDE4MS0yMDRdKGh0dHBzOi8vYm9va3NjLm9yZy9ib29rLzY3NTU1NjMvNjYzNThhKQoKYGBge3J9CnY9YygzLCAxLCA0LCAxNSwgOTIpCnc9YygzMCwyICwgOSwgMjAsIDQ4KQoodi1tZWFuKHYpKS9zZCh2KQpzY2FsZSh2KQoody1tZWFuKHcpKS9zZCh3KQpzY2FsZSh3KQpgYGAKCiMgRXhlcmNpY2UgOAoKLSAgIENvbnNpZGVyIHRoZSBmb2xsb3dpbmcgZXhhbXBsZQoKIVtGcm9tIEthdWZtYW4gJiBSb3Vzc2VldXcsIHAuCjYtOF0oRXhlcmNpY2VrYXVmZm1hblNjYWxlLmpwZyl7d2lkdGg9IjUwJSJ9CgotICAgUGxvdCB0aGUgZGF0YSB1c2luZyBhIG5pY2Ugc2NhdHRlciBwbG90LgotICAgVHJhbnNmb3JtIHRoZSBIZWlnaHQgZnJvbSBjZW50aW1ldGVycyAoY20pIGludG8gZmVldCAoZnQpLgotICAgRGlzcGxheSB5b3VyIGRhdGEgaW4gYSB0YWJsZS4KLSAgIFBsb3QgdGhlIGRhdGEgd2l0aGluIGEgbmV3IHNjYXR0ZXIgcGxvdC4KLSAgIFdoYXQgZG8geW91IG9ic2VydmU/Ci0gICBTdGFuZGFyZGl6ZSB0aGUgdHdvIHZhcmlhYmxlcyBBZ2UgYW5kIEhlaWdodC4KLSAgIERpc3BsYXkgeW91ciBkYXRhIGluIGEgdGFibGUuCi0gICBQbG90IHRoZSBzdGFuZGFyZGl6ZWQgZGF0YSB3aXRoaW4gYSBuZXcgc2NhdHRlciBwbG90LgotICAgQ29uY2x1ZGUuCgojIFNpbWlsYXJpdHkgbWVhc3VyZXMgZm9yIGJpbmFyeSBkYXRhCgotICAgQSBjb21tb24gc2ltcGxlIHNpdHVhdGlvbiBvY2N1cnMgd2hlbiBhbGwgaW5mb3JtYXRpb24gaXMgb2YgdGhlCiAgICBwcmVzZW5jZS9hYnNlbmNlIG9mIDItbGV2ZWwgcXVhbGl0YXRpdmUgY2hhcmFjdGVycy4KCi0gICBXZSBhc3N1bWUgdGhlcmUgYXJlICRuJCBjaGFyYWN0ZXJzLgoKLSAgIFwqVGhlIHByZXNlbmNlIG9mIHRoZSBjaGFyYWN0ZXIgaXMgY29kZWQgYnkgJDEkIGFuZCB0aGUgYWJzZW5jZQogICAgYnkgMC4KCi0gICBXZSBoYXZlIGhhdmUgYXQgb3VyIGRpc3Bvc2FsIHR3byB2ZWN0b3JzLgoKLSAgICRcbWF0aGJme3h9JCBpcyBvYnNlcnZlZCBmb3IgYSBmaXJzdCBpbmRpdmlkdWFsIChvciBvYmplY3QpLgoKLSAgICRcbWF0aGJme3l9JCBpcyBvYnNlcnZlZCBmb3IgYSBzZWNvbmQgaW5kaXZpZHVhbC4KCi0gICBXZSBjYW4gdGhlbiBjYWxjdWxhdGUgdGhlIGZvbGxvd2luZyBmb3VyIHN0YXRpc3RpY3M6CgogICAgJGE9XG1hdGhiZnt4XGNkb3QgeX09XHN1bV97aj0xfV5teF9qeV9qLiQKCiAgICAkYj1cbWF0aGJme3hcY2RvdCAoMS15KX09XHN1bV97aj0xfV5teF9qKDEteV9qKS4kCgogICAgJGM9XG1hdGhiZnsoMS14KVxjZG90IHl9PVxzdW1fe2o9MX1ebSgxLXhfail5X2ouJAoKICAgICRkPVxtYXRoYmZ7KDEteClcY2RvdCAoMS15KX09XHN1bV97aj0xfV5tKDEteF9qKSgxLXlfaikuJAoKLSAgIFRoZSBjb3VudHMgb2YgbWF0Y2hlcyBhcmUgJGEkIGZvciAkKDEsMSkkIGFuZCAkZCQgZm9yICQoMCwwKSQ7CgotICAgVGhlIGNvdW50cyBvZiBtaXNtYXRjaGVzIGFyZSAkYiQgZm9yICQoMSwwKSQgYW5kICRjJCBmb3IgJCgwLDEpJC4KCi0gICBOb3RlIHRoYXQgb2J2aW91c2x5OiAkYStiK2MrZD0gbiQuCgotICAgVGhpcyBnaXZlcyBhIHZlcnkgdXNlZnVsICQyIFx0aW1lcyAyJCBhc3NvY2lhdGlvbiB0YWJsZS4KCnwgICAgICAgICAgICAgICAgICAgICAgfCAgICAgfCBTZWNvbmQgaW5kaXZpZHVhbCB8ICAgICAgIHwgICAgICAgICAgfAp8Oi0tLS0tLS0tLS0tLS0tLS0tLS0tLXwtLS0tLXw6LS0tLS0tLS0tLS0tLS0tLS06fDotLS0tLTp8Oi0tLS0tLS0tOnwKfCAgICAgICAgICAgICAgICAgICAgICB8ICAgICB8ICAgICAgICAgMSAgICAgICAgIHwgICAwICAgfCAqVG90YWxzKiB8CnwgKipGaXJzdCBpbmRpdmlkdWFsKiogfCAxICAgfCAgICAgICAgJGEkICAgICAgICB8ICAkYiQgIHwgICRhK2IkICAgfAp8ICAgICAgICAgICAgICAgICAgICAgIHwgMCAgIHwgICAgICAgICRjJCAgICAgICAgfCAgJGQkICB8ICAkYytkJCAgIHwKfCAqVG90YWxzKiAgICAgICAgICAgICB8ICAgICB8ICAgICAgICRhK2MkICAgICAgIHwgJGIrZCQgfCAgICRuJCAgICB8CgohW1RhYmxlIGZyb20gW0thdWZtYW4sIEwuLCAmIFJvdXNzZWV1dywgUC4gSi4gKDIwMDkpLiBGaW5kaW5nIGdyb3VwcyBpbgpkYXRhOiBhbiBpbnRyb2R1Y3Rpb24gdG8gY2x1c3RlciBhbmFseXNpcyAoVm9sLiAzNDQpLiBKb2huIFdpbGV5ICYKU29uc10oaHR0cHM6Ly9ib29rNHlvdS5vcmcvYm9vay82NjkyNzcvZDJjZjU5KV0oS0FVRk1BTkJpbmFyeWRhdGEuanBnKXt3aWR0aD0iNTAlIn0KCi0gICBUaGUgZGF0YSBzaG93cyAkOCQgcGVvcGxlIChpbmRpdmlkdWFscykgYW5kICQxMCQgYmluYXJ5IHZhcmlhYmxlczoKLSAgIFNleCwgTWFycmllZCwgRmFpciBIYWlyLCBCbHVlIEV5ZXMsIFdlYXJzIEdsYXNzZXMsIFJvdW5kIEZhY2UsCiAgICBQZXNzaW1pc3QsIEV2ZW5pbmcgVHlwZSwgSXMgYW4gT25seSBDaGlsZCwgTGVmdC1IYW5kZWQuCgpgYGB7cn0KZGF0YT1jKAoxLDAsMSwxLDAsMCwxLDAsMCwwLAowLDEsMCwwLDEsMCwwLDAsMCwwLAowLDAsMSwwLDAsMCwxLDAsMCwxLAowLDEsMCwwLDAsMCwwLDEsMSwwLAoxLDEsMCwwLDEsMSwwLDEsMSwwLAoxLDEsMCwwLDEsMCwxLDEsMCwwLAowLDAsMCwxLDAsMSwwLDAsMCwwLAowLDAsMCwxLDAsMSwwLDAsMCwwCikKZGF0YT1kYXRhLmZyYW1lKG1hdHJpeChkYXRhLCBucm93PTgsYnlyb3c9VCkpCnJvdy5uYW1lcyhkYXRhKT1jKCJJbGFuIiwiSmFjcXVlbGluZSIsIktpbSIsIkxpZXZlIiwiTGVvbiIsIlBldGVyIiwiVGFsaWEiLCJUaW5hIikKbmFtZXMoZGF0YSk9YygiU2V4IiwgIk1hcnJpZWQiLCAiRmFpciBIYWlyIiwgIkJsdWUgRXllcyIsICJXZWFycyBHbGFzc2VzIiwgIlJvdW5kIEZhY2UiLCAiUGVzc2ltaXN0IiwgIkV2ZW5pbmcgVHlwZSIsICJJcyBhbiBPbmx5IENoaWxkIiwgIkxlZnQtSGFuZGVkIikKYGBgCgotICAgV2UgYXJlIGNvbXBhcmluZyB0aGUgcmVjb3JkcyBmb3IgSWxhbiB3aXRoIFRhbGlhLgoKYGBge3IsIGVjaG89VFJVRSxyZXN1bHRzPSdhc2lzJyxtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KHh0YWJsZSkKbGlicmFyeShzdGFyZ2F6ZXIpCmxpYnJhcnkodGV4cmVnKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkoc3VtbWFyeXRvb2xzKQoKc2V0LnNlZWQoODkzKQpkYXRhdDwtYXMuZGF0YS5mcmFtZSh0KGRhdGEpKQpkYXRhdD1sYXBwbHkoZGF0YXQsYXMuZmFjdG9yKQpJbGFuPWRhdGF0JElsYW4KVGFsaWEgPWRhdGF0JFRhbGlhCnByaW50KGN0YWJsZShJbGFuLFRhbGlhLHByb3AgPSAnbicsc3R5bGUgPSAicm1hcmtkb3duIikpCgpgYGAKCi0gICBUaGVyZWZvcmU6ICRhID0gMSxcOmIgPSAzLFw6IGMgPSAxLFw6IGQgPSA1JC4KLSAgIE5vdGUgdGhhdCBpbnRlcmNoYW5naW5nIElsYW4gYW5kIFRhbGlhIHdvdWxkIHBlcm11dGUgJGIkIGFuZCAkYyQKICAgIHdoaWxlIGxlYXZpbmcgJGEkIGFuZCAkZCQgdW5jaGFuZ2VkLgotICAgQSBnb29kIHNpbWlsYXJpdHkgb3IgZGlzc2ltaWxhcml0eSBjb2VmZmljaWVudCBtdXN0IHRyZWF0ICRiJCBhbmQKICAgICRjJCBzeW1tZXRyaWNhbGx5LgotICAgQSBzaW1pbGFyaXR5IG1lYXN1cmUgaXMgZGVub3RlZCBieTogJHMoXG1hdGhiZnt4fSxcbWF0aGJme3l9KSQuCi0gICBUaGUgY29ycmVzcG9uZGluZyBkaXN0YW5jZSBpcyB0aGVuIGRlZmluZWQgYXM6CiAgICAkJGQoXG1hdGhiZnt4fSxcbWF0aGJme3l9KT0xLXMoXG1hdGhiZnt4fSxcbWF0aGJme3l9KS4kJAotICAgQWx0ZXJuYXRpdmVseSwgd2UgaGF2ZToKICAgICQkZChcbWF0aGJme3h9LFxtYXRoYmZ7eX0pPVxzcXJ0ezEtcyhcbWF0aGJme3h9LFxtYXRoYmZ7eX0pfS4kJAotICAgQSBsaXN0IG9mIHNvbWUgb2YgdGhlIHNpbWlsYXJpdHkgbWVhc3VyZXMgJHMoXG1hdGhiZnt4fSxcbWF0aGJme3l9KSQKICAgIHRoYXQgaGF2ZSBiZWVuIHN1Z2dlc3RlZCBmb3IgYmluYXJ5IGRhdGEgaXMgc2hvd24gYmVsb3cuCi0gICBBbiBtb3JlIGNvbXBsZXRlIGxpc3QgY2FuIGJlIGZvdW5kIGluOiBbR293ZXIsIEouIEMuLCAmIExlZ2VuZHJlLCBQLgogICAgKDE5ODYpLiBNZXRyaWMgYW5kIEV1Y2xpZGVhbiBwcm9wZXJ0aWVzIG9mIGRpc3NpbWlsYXJpdHkKICAgIGNvZWZmaWNpZW50cy4gKkpvdXJuYWwgb2YgY2xhc3NpZmljYXRpb24qLCAqMyooMSksCiAgICA1LTQ4XShodHRwczovL2Jvb2tzYy5vcmcvYm9vay82NzU1MzUzL2M0NDE5OCkuCgorLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKwp8IENvZWZmaWNpZW50ICAgIHwgJHMoXG1hdGhiZnsgICAgIHwgJGQoXG1hdGhiZnt4fSxcbWF0aGJmICAgICAgICAgICAgfAp8ICAgICAgICAgICAgICAgIHwgeH0sXG1hdGhiZnt5fSkkIHwge3l9KT0xLXMoXG1hdGhiZnt4fSxcbWF0aGJme3l9KSQgfAorOj09PT09PT09PT09PT09PSs6PT09PT09PT09PT09PT09Ois6PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT06Kwp8IFNpbXBsZSAgICAgICAgIHwgJFxmcmFjICAgICAgICAgIHwgJFxmcmFje2IrY317YStiK2MrZH0kICAgICAgICAgICAgfAp8IG1hdGNoaW5nICAgICAgIHwge2ErZH17YStiK2MrZH0kIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAorLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKwp8IEphY2NhcmQgICAgICAgIHwgJFwgICAgICAgICAgICAgIHwgJFxmcmFje2IrY317YStiK2N9JCAgICAgICAgICAgICAgfAp8ICAgICAgICAgICAgICAgIHwgZnJhY3thfXthK2IrY30kIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAorLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKwp8IFJvZ2VycyBhbmQgICAgIHwgJFxmcmFje2ErICAgICAgIHwgJFxmcmFjezIoYitjKX17YSsyKGIrYykrZH0kICAgICAgfAp8IFRhbmltb3RvICAgICAgIHwgZH17YSsyKGIrYykrZH0kIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICgxOTYwKSAgICAgICAgIHwgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAorLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKwp8IEdvd2VyIGFuZCAgICAgIHwgJFxmcmFjezIoYStkICAgIHwgJFxmcmFje2IrY317MihhK2QpK2IrY31dJCAgICAgICAgfAp8IExlZ2VuZHJlICAgICAgIHwgKX17MihhK2QpK2IrY30kIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICgxOTg2KSAgICAgICAgIHwgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAorLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKwp8IEdvd2VyIGFuZCAgICAgIHwgJFxmciAgICAgICAgICAgIHwgJFxmcmFje2IrY317MmErYitjfSQgICAgICAgICAgICAgfAp8IExlZ2VuZHJlICAgICAgIHwgYWN7MmF9ezJhK2IrY30kIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAp8ICgxOTg2KSAgICAgICAgIHwgICAgICAgICAgICAgICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfAorLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLSstLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKwoKLSAgIFRvIGNhbGN1bGF0ZSB0aGVzZSBjb2VmZmljaWVudHMsIHdlIHVzZSB0aGUgZnVuY3Rpb246CiAgICBbZGlzdC5iaW5hcnkoKS5dKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9hZGU0L3ZlcnNpb25zLzEuNy0xNi90b3BpY3MvZGlzdC5iaW5hcnkpCiAgICBhdmFpbGFibGUgaW4gdGhlICoqYWRlNCoqIHBhY2thZ2UuCgotICAgQWxsIHRoZSBkaXN0YW5jZXMgaW4gdGhlICoqYWRlNCoqIHBhY2thZ2UgYXJlIG9mIHR5cGUKICAgICRkKFxtYXRoYmZ7eH0uXG1hdGhiZnt5fSk9IFxzcXJ0ezEgLSBzKFxtYXRoYmZ7eH0uXG1hdGhiZnt5fSl9JC4KCmBgYHtyLGVjaG89VCxyZXN1bHRzPSdhc2lzJ30KbGlicmFyeShhZGU0KQphPTEKYj0zCmM9MQpkPTUKZGlzdC5iaW5hcnkoZGF0YVtjKCJJbGFuIiwiVGFsaWEiKSxdLG1ldGhvZD0yKV4yCjEtKGErZCApLyhhK2IrYytkKQpkaXN0LmJpbmFyeShkYXRhW2MoIklsYW4iLCJUYWxpYSIpLF0sbWV0aG9kPTEpXjIKMS1hLyhhK2IrYykKZGlzdC5iaW5hcnkoZGF0YVtjKCJJbGFuIiwiVGFsaWEiKSxdLG1ldGhvZD00KV4yCjEtKGErZCApLyhhKzIqKGIrYykrZCkKIyBPbmUgR293ZXIgY29lZmZpY2llbnQgaXMgbWlzc2luZwpkaXN0LmJpbmFyeShkYXRhW2MoIklsYW4iLCJUYWxpYSIpLF0sbWV0aG9kPTUpXjIKMS0yKmEvKDIqYStiK2MpCmBgYAoKLSAgIFRoZSByZWFzb24gZm9yIHN1Y2ggYSBsYXJnZSBudW1iZXIgb2YgcG9zc2libGUgbWVhc3VyZXMgaGFzIHRvIGRvCiAgICB3aXRoIHRoZSBhcHBhcmVudCB1bmNlcnRhaW50eSBhcyB0byBob3cgdG8gZGVhbCB3aXRoIHRoZSBjb3VudCBvZgogICAgemVyby16ZXJvIG1hdGNoZXMgJGQkLgotICAgVGhlIG1lYXN1ZXMgZW1iZWRkaW5nICRkJCBhcmUgc29tZXRpbWVzIGNhbGxlZCBzeW1tZXRyaWNhbC4KLSAgIFRoZSBvdGhlciBtZWFzdWVzIGFyZSBjYWxsZWQgYXNzeW1tZXRyaWNhbC4KLSAgIEluIHNvbWUgY2FzZXMsIG9mIGNvdXJzZSwgemVyb196ZXJvIG1hdGNoZXMgYXJlIGNvbXBsZXRlbHkKICAgIGVxdWl2YWxlbnQgdG8gb25lLS1vbmUgbWF0Y2hlcywgYW5kIHRoZXJlZm9yZSBzaG91bGQgYmUgaW5jbHVkZWQgaW4KICAgIHRoZSBjYWxjdWxhdGVkIHNpbWlsYXJpdHkgbWVhc3VyZS4KLSAgIEFuIGV4YW1wbGUgaXMgZ2VuZGVyLCB3aGVyZSB0aGVyZSBpcyBubyBwcmVmZXJlbmNlIGFzIHRvIHdoaWNoIG9mCiAgICB0aGUgdHdvIGNhdGVnb3JpZXMgc2hvdWxkIGJlIGNvZGVkIHplcm8gb3Igb25lLgotICAgQnV0IGluIG90aGVyIGNhc2VzIHRoZSBpbmNsdXNpb24gb3Igb3RoZXJ3aXNlIG9mICRkJCBpcyBtb3JlCiAgICBwcm9ibGVtYXRpYzsgZm9yIGV4YW1wbGUsIHdoZW4gdGhlIHplcm8gY2F0ZWdvcnkgY29ycmVzcG9uZHMgdG8gdGhlCiAgICBnZW51aW5lIGFic2VuY2Ugb2Ygc29tZSBwcm9wZXJ0eSwgc3VjaCBhcyB3aW5ncyBpbiBhIHN0dWR5IG9mCiAgICBpbnNlY3RzLgoKIyBFeGVyY2ljZSA5CgotICAgVXNlIHRoZSBkYXRhIHNldCAqYW5pbWFscyogYXZhaWxhYmxlIGluIHRoZSBwYWNrYWdlICpjbHVzdGVyKi4KLSAgIFRoaXMgZGF0YSBzZXQgd2FzIGZpcnN0IHVzZWQgaW4gdGhpcyB0ZXh0Ym9vayBbS0FVRk1BTiwgTGVvbmFyZCBldAogICAgUk9VU1NFRVVXLCBQZXRlciBKLiBGaW5kaW5nIGdyb3VwcyBpbiBkYXRhOiBhbiBpbnRyb2R1Y3Rpb24gdG8KICAgIGNsdXN0ZXIgYW5hbHlzaXMuIEpvaG4gV2lsZXkgJiBTb25zLAogICAgMjAwOV0oaHR0cHM6Ly9ib29rNHlvdS5vcmcvYm9vay82NjkyNzcvZDJjZjU5KS4KLSAgIElkZW50aWZ5IHRoZSBtaXNzaW5nIG1lYXN1cmVtZW50cy4KLSAgIEV4cGxhaW4gdGhlIHdheSBob3cgS0FVRk1BTiBhbmQgUk9VU1NFRVVXLCBwcC4gMjk2LTI5NyB0cmVhdCB0aGUKICAgIG1pc3NpbmcgbWVhc3VyZW1lbnRzLgotICAgQ29tcHV0ZSBhIGRpc3RhbmNlIG1hdHJpeCBmb3IgdGhlIGNvbXBsZXRlZCBkYXRhLgotICAgUHJvcG9zZSBhIGdyYXBoaWNhbCB3YXkgdG8gcmVwcmVzZW50IHRoYXQgZGlzdGFuY2UgbWF0cml4LgotICAgV2hpY2ggZ3JvdXAgb2YgYW5pbWFscyBsb29rIGNsb3NlPwotICAgQ2hhbmdlIHRoZSBtZXRob2Qgb2YgY2FsY3VsYXRpbmcgYW5kIG9ic2VydmUgaWYgaXQgaGFzIHNvbWUgZWZmZWN0CiAgICBvZiB0aGUgZ3JhcGguCgohW0tBVUZNQU4gYW5kIFJPVVNTRUVVVywgcC4gMjk2XShFeGVyY2ljZUthdWZmbWFuMS5qcGcpICFbS0FVRk1BTiBhbmQKUk9VU1NFRVVXIHAuIDI5N10oRXhlcmNpY2VLYXVmZm1hbjIuanBnKQoKXHBhZ2VicmVhawoKIyBFeGVyY2ljZSAxMAoKLSAgIFByb3ZlIHRoYXQgdGhlIGRpc3RhbmNlcyBiYXNlZCBvbiB0aGUgU2ltcGxlIE1hdGNoaW5nIGNvZWZmaWNpZW50CiAgICBhbmQgdGhlIEphY2NhcmQgY29lZmZpY2llbnQgc2F0aXNmeSBBMy4KLSAgIFByb3ZlIHRoYXQgdGhlIGRpc3RhbmNlcyBwcm9wb3NlZCBieSBHb3dlciBhbmQgTGVnZW5kcmUgKDE5ODYpIGRvCiAgICBub3Qgc2F0aXNmeSBBMy4KLSAgIEhpbnQ6IFByb29mcyBhbmQgY291bnRlcmV4YW1wbGVzIGhhdmUgdG8gYmUgYWRhcHRlZCBmcm9tIGluIHRoZQogICAgcGFwZXI6IFtHb3dlciwgSi4gQy4sICYgTGVnZW5kcmUsIFAuICgxOTg2KS4gTWV0cmljIGFuZCBFdWNsaWRlYW4KICAgIHByb3BlcnRpZXMgb2YgZGlzc2ltaWxhcml0eSBjb2VmZmljaWVudHMuICpKb3VybmFsIG9mCiAgICBjbGFzc2lmaWNhdGlvbiosICozKigxKSwKICAgIDUtNDhdKGh0dHBzOi8vYm9va3NjLm9yZy9ib29rLzY3NTUzNTMvYzQ0MTk4KS4KCiMgTm9taW5hbCB2YXJpYWJsZXMKCi0gICBXZSBwcmV2aW91c2x5IHN0dWRpZWQgYWJvdmUgYmluYXJ5IHZhcmlhYmxlcyB3aGljaCBjYW4gb25seSB0YWtlIG9uCiAgICB0d28gc3RhdGVzIGNvZGVkICQwLDEkLgotICAgV2UgZ2VuZXJhbGl6ZSB0aGlzIGFwcHJvYWNoIHRvIG5vbWluYWwgdmFyaWFibGVzIHdoaWNoIG1heSB0YWtlIG9uCiAgICBtb3JlIHRoYW4gdHdvIHN0YXRlcy4KLSAgIEV5ZSdzIGNvbG9yIG1heSBoYXZlIGZvciBleGFtcGxlIGZvdXIgc3RhdGVzOiBibHVlLCBicm93biwgZ3JlZW4sCiAgICBncmV5LgotICAgTGUgJE0kIGJlIHRoZSBudW1iZXIgb2Ygc3RhdGVzIGFuZCBjb2RlIHRoZSBvdXRjb21lcyBhcwogICAgJDEsIFxjZG90cywgTSQuCi0gICBXZSBtYXkgY2hvb3NlICQxID1cdGV4dHtibHVlfSwkICQyID1cdGV4dHticm93bn0sJAogICAgJDMgPVx0ZXh0e2dyZWVufSwkIGFuZCAkNCA9XHRleHR7Z3JleX0kLgotICAgVGhlc2Ugc3RhdGVzIGFyZSBub3Qgb3JkZXJlZCBpbiBhbnkgd2F5Ci0gICBPbmUgc3RyYXRlZ3kgd291bGQgYmUgY3JlYXRpbmcgYSBuZXcgYmluYXJ5IHZhcmlhYmxlIGZvciBlYWNoIG9mIHRoZQogICAgJE0kIG5vbWluYWwgc3RhdGVzLgotICAgVGhlbiB0byBwdXQgaXQgZXF1YWwgdG8gJDEkIGlmIHRoZSBjb3JyZXNwb25kaW5nIHN0YXRlIG9jY3VycyBhbmQgdG8KICAgICQwJCBvdGhlcndpc2UuCi0gICBBZnRlciB0aGF0LCBvbmUgY291bGQgcmVzb3J0IHRvIG9uZSBvZiB0aGUgZGlzc2ltaWxhcml0eQogICAgY29lZmZpY2llbnRzIG9mIHRoZSBwcmV2aW91cyBzdWJzZWN0aW9uLgotICAgVGhlIG1vc3QgY29tbW9uIHdheSBvZiBtZWFzdXJpbmcgdGhlIHNpbWlsYXJpdHkgb3IgZGlzc2ltaWxhcml0eQogICAgYmV0d2VlbiB0d28gb2JqZWN0cyB0aHJvdWdoIGNhdGVnb3JpYWwgdmFyaWFibGVzIGlzIHRoZSBzaW1wbGUKICAgIG1hdGNoaW5nIGFwcHJvYWNoLgotICAgSWYgJFxtYXRoYmZ7eH0sXG1hdGhiZnt5fSwkIGFyZSBib3RoICRtJCBub21pbmFsIHJlY29yZHMgZm9yIHR3bwogICAgaW5kaXZpZHVhbHMsCi0gICBMZXQgZGVmaW5lIHRoZSBmdW5jdGlvbjogJCRcZGVsdGEoeF9qLHlfailcZXF1aXYgXGJlZ2lue2Nhc2VzfTAsCiAgICBcdGV4dHsgaWYgfSB4X2o9eV9qO1xcMSxcdGV4dHsgaWYgfSB4X2ogXG5lcSB5X2ouXGVuZHtjYXNlc30kJAotICAgTGV0ICROX3thK2R9JCBiZSB0aGUgbnVtYmVyIG9mIGF0dHJpYnV0ZXMgb2YgdGhlIHR3byBpbmRpdmlkdWFscyBvbgogICAgd2hpY2ggdGhlIHR3byByZWNvcmRzIG1hdGNoOgogICAgJCROX3thK2R9PVxzdW1fe2o9MX1ebVsxLVxkZWx0YSh4X2oseV9qKV0gLiQkCi0gICBMZXQgJE5fe2IrY30kIGJlIHRoZSBudW1iZXIgb2YgYXR0cmlidXRlcyBvbiB3aGljaCB0aGUgdHdvIHJlY29yZHMKICAgIGRvIG5vdCBtYXRjaDogJCROX3tiK2N9PSBcc3VtX3tqPTF9Xm1cZGVsdGEoeF9qLHlfaikuJCQKLSAgIExldCAkTl9kJCBiZSB0aGUgbnVtYmVyIG9mIGF0dHJpYnV0ZXMgb24gd2hpY2ggdGhlIHR3byByZWNvcmRzIG1hdGNoCiAgICBpbiBhICJub3QgYXBwbGljYWJsZSIgY2F0ZWdvcnkuCi0gICBUaGUgZGlzdGFuY2UgY29ycmVzcG9uZGluZyB0byB0aGUgc2ltcGxlIG1hdGNoaW5nIGFwcHJvYWNoIGlzOiAkJAogICAgZChcbWF0aGJme3h9LFxtYXRoYmZ7eX0pPVxmcmFje1xzdW1fe2o9MX1ebVxkZWx0YSh4X2oseV9qKX17bn09XGZyYWN7Tl97YStkfX17Tl97YStkfStOX3tiK2N9fS4KICAgICQkCi0gICBOb3RlIHRoYXQgc2ltcGxlIG1hdGNoaW5nIGhhcyBleGFjdGx5IHRoZSBzYW1lIG1lYW5pbmcgYXMgaW4gdGhlCiAgICBwcmVjZWRpbmcgc2VjdGlvbi4KCiFbRnJvbTogR0FOIGV0IGFsLiAoMjAwNyldKEdhbk1hWXVUYWJsZTIuanBnKQoKLSAgIEZvciBtb3JlIGRldGFpbHMsIHNlZSBbR0FOLCBHdW9qdW4sIE1BLCBDaGFvcXVuLCBldCBXVSwgSmlhbmhvbmcuCiAgICBEYXRhIGNsdXN0ZXJpbmc6IHRoZW9yeSwgYWxnb3JpdGhtcywgYW5kIGFwcGxpY2F0aW9ucy4gU29jaWV0eSBmb3IKICAgIEluZHVzdHJpYWwgYW5kIEFwcGxpZWQKICAgIE1hdGhlbWF0aWNzLCAyMDIwLl0oaHR0cHM6Ly9ib29rNHlvdS5vcmcvYm9vay83MTY3NDQvYzhjNTFmKQogICAgXHBhZ2VicmVhawoKIyBHb3dlcidzIGRpc3NpbWlsYXJpdHkKCi0gICBHb3dlcidzIGNvZWZmaWNpZW50IGlzIGEgZGlzc2ltaWxhcml0eSBtZWFzdXJlIHNwZWNpZmljYWxseSBkZXNpZ25lZAogICAgZm9yIGhhbmRsaW5nIG1peGVkIGF0dHJpYnV0ZSB0eXBlcyBvciB2YXJpYWJsZXMuCgotICAgU2VlOiBHT1dFUiwgSm9obiBDLiBBIGdlbmVyYWwgY29lZmZpY2llbnQgb2Ygc2ltaWxhcml0eSBhbmQgc29tZSBvZgogICAgaXRzIHByb3BlcnRpZXMuICpCaW9tZXRyaWNzKiwgMTk3MSwgcC4gODU3LTg3MS4KCi0gICBUaGUgY29lZmZpY2llbnQgaXMgY2FsY3VsYXRlZCBhcyB0aGUgd2VpZ2h0ZWQgYXZlcmFnZSBvZiBhdHRyaWJ1dGUKICAgIGNvbnRyaWJ1dGlvbnMuCgotICAgV2VpZ2h0cyB1c3VhbGx5IHVzZWQgb25seSB0byBpbmRpY2F0ZSB3aGljaCBhdHRyaWJ1dGUgdmFsdWVzIGNvdWxkCiAgICBhY3R1YWxseSBiZSBjb21wYXJlZCBtZWFuaW5nZnVsbHkuCgotICAgVGhlIGZvcm11bGEgaXM6ICQkCiAgICBkKFxtYXRoYmZ7eH0sXG1hdGhiZnt5fSk9XGZyYWN7XHN1bV97aj0xfV5tIHdfaiBcZGVsdGEoeF9qLHlfail9e1xzdW1fe2o9MX1ebSB3X2p9LgogICAgJCQKCi0gICBUaGUgd2hlaWdodCAkd19qJCBpcyBwdXQgZXF1YWwgdG8gJDEkIHdoZW4gYm90aCBtZWFzdXJlbWVudHMgJHhfaiQKICAgIGFuZCAkeV9qJCBhcmUgbm9ubWlzc2luZywKCi0gICBUaGUgbnVtYmVyICRcZGVsdGEoeF9qLHlfaikkIGlzIHRoZSBjb250cmlidXRpb24gb2YgdGhlICRqJHRoCiAgICBtZWFzdXJlIG9yIHZhcmlhYmxlIHRvIHRoZSBkaXNzaW1pbGFyaXR5IG1lYXN1cmUuCgotICAgSWYgdGhlICRqJHRoIG1lYXN1cmUgaXMgbm9taW5hbCwgd2UgdGFrZVwKICAgICQkCiAgICBcZGVsdGEoeF9qLHlfailcZXF1aXYgXGJlZ2lue2Nhc2VzfTAsCiAgICBcdGV4dHsgaWYgfSB4X2o9eV9qO1xcMSxcdGV4dHsgaWYgfSB4X2ogXG5lcSB5X2ouXGVuZHtjYXNlc30KICAgICQkCgotICAgSWYgdGhlICRqJHRoIG1lYXN1cmUgaXMgaW50ZXJ2YWwtc2NhbGVkLCB3ZSB0YWtlIGluc3RlYWQ6ICQkCiAgICBcZGVsdGEoeF9qLHlfailcZXF1aXYgXGZyYWN7fHhfai15X2p8fXtSX2p9LAogICAgJCQgd2hlcmUgJFJfaiQgaXMgdGhlIHJhbmdlIG9mIHZhcmlhYmxlICRqJCBvdmVyIHRoZSBhdmFpbGFibGUgZGF0YS4KCi0gICBDb25zaWRlciB0aGUgZm9sbG93aW5nIGRhdGEgc2V0OgoKIVtdKGZsb3dlcnMuanBnKXt3aWR0aD0iNTAlIn0KCkZyb206IFtTdHJ1eWYsIEEuLCBIdWJlcnQsIE0uLCAmIFJvdXNzZWV1dywgUC4gKDE5OTcpLiBDbHVzdGVyaW5nIGluIGFuCm9iamVjdC1vcmllbnRlZCBlbnZpcm9ubWVudC4gKkpvdXJuYWwgb2YgU3RhdGlzdGljYWwgU29mdHdhcmUqLCAqMSooNCksCjEtMzBdKGh0dHBzOi8vd3d3LmpzdGF0c29mdC5vcmcvYXJ0aWNsZS92aWV3L3YwMDFpMDQpLgoKLSAgIFRoZSBkYXRhc2V0IGNvbnRhaW5zIDE4IGZsb3dlcnMgYW5kIDggY2hhcmFjdGVyaXN0aWNzOgoKMS4gIFdpbnRlcnM6IGJpbmFyeSwgaW5kaWNhdGVzIHdoZXRoZXIgdGhlIHBsYW50IG1heSBiZSBsZWZ0IGluIHRoZQogICAgZ2FyZGVuIHdoZW4gaXQgZnJlZXplcy4KMi4gIFNoYWRvdzogYmluYXJ5LCBzaG93cyB3aGV0aGVyIHRoZSBwbGFudCBuZWVkcyB0byBzdGFuZCBpbiB0aGUKICAgIHNoYWRvdy4KMy4gIFR1YmVycyAoVHViZXJjdWxlKTogYXN5bW1ldHJpYyBiaW5hcnksIGRpc3Rpbmd1aXNoZXMgYmV0d2VlbiBwbGFudHMKICAgIHdpdGggdHViZXJzIGFuZCBwbGFudHMgdGhhdCBncm93IGluIGFueSBvdGhlciB3YXkuCjQuICBDb2xvcjogbm9taW5hbCwgc3BlY2lmaWVzIHRoZSBmbG93ZXIncyBjb2xvciAoMT13aGl0ZSwgMj15ZWxsb3csIDM9CiAgICBwaW5rLCA0PXJlZCwgNT0gYmx1ZSkuCjUuICBTb2lsOiBvcmRpbmFsLCBpbmRpY2F0ZXMgd2hldGhlciB0aGUgcGxhbnQgZ3Jvd3MgaW4gZHJ5ICgxKSwgbm9ybWFsCiAgICAoMiksIG9yIHdldCAoMykgc29pbC4KNi4gIFByZWZlcmVuY2U6IG9yZGluYWwsIHNvbWVvbmUncyBwcmVmZXJlbmNlIHJhbmtpbmcsIGdvaW5nIGZyb20gMQogICAgdG8gMTguCjcuICBIZWlnaHQ6IGludGVydmFsIHNjYWxlZCwgdGhlIHBsYW50J3MgaGVpZ2h0IGluIGNlbnRpbWV0ZXJzLgo4LiAgRGlzdGFuY2U6IGludGVydmFsIHNjYWxlZCwgdGhlIGRpc3RhbmNlIGluIGNlbnRpbWV0ZXJzIHRoYXQgc2hvdWxkCiAgICBiZSBsZWZ0IGJldHdlZW4gdGhlIHBsYW50cy4KCi0gICBUaGUgZGlzc2ltaWxhcml0eSBiZXR3ZWVuIEJlZ29uaWEgYW5kIEJyb29tIChHZW7DqnQpIGNhbiBiZQogICAgY2FsY3VsYXRlZCBhcyBmb2xsb3dzOgoKIVtCZWdvbmlhXShCZWdvbmlhLmpwZyl7d2lkdGg9IjUwJSJ9CgohW0dlbsOqdF0oR2VuZXQuanBlZyl7d2lkdGg9IjUwJSJ9CgpgYGB7ciwgbWVzc2FnZSA9IEZBTFNFfQpsaWJyYXJ5KGNsdXN0ZXIpCmxpYnJhcnkoZHBseXIpCmRhdGEgPC1mbG93ZXIgJT4lIApyZW5hbWUoV2ludGVycz1WMSxTaGFkb3c9VjIsVHViZXJzPVYzLENvbG9yPVY0LFNvaWw9VjUsUHJlZmVyZW5jZT1WNixIZWlnaHQ9VjcsRGlzdGFuY2U9VjgpICU+JQptdXRhdGUoV2ludGVycz1yZWNvZGUoV2ludGVycywiMSI9IlllcyIsIjAiPSJObyIpLAogICAgICBTaGFkb3c9cmVjb2RlKFNoYWRvdywiMSI9IlllcyIsIjAiPSJObyIpLAogICAgICBUdWJlcnM9cmVjb2RlKFR1YmVycywiMSI9IlllcyIsIjAiPSJObyIpLAogICAgICBDb2xvcj1yZWNvZGUoQ29sb3IsIjEiPSJ3aGl0ZSIsICIyIj0ieWVsbG93IiwgIjMiPSAicGluayIsICI0Ij0icmVkIiwgIjUiPSJibHVlIiksCiAgICAgIFNvaWw9cmVjb2RlKFNvaWwsIjEiPSJkcnkiLCAiMiI9Im5vcm1hbCIsICIzIj0gIndldCIpCiAgICAgICkgCnJvdy5uYW1lcyhkYXRhKT1jKCJCZWdvbmlhIiwiQnJvb20iLCJDYW1lbGxpYSIsIkRhaGxpYSIsIkZvcmdldC1tZS1ub3QiLCJGdWNoc2lhIiwKICJHZXJhbml1bSIsICJHbGFkaW9sdXMiLCJIZWF0aGVyIiwiSHlkcmFuZ2VhIiwiSXJpcyIsIkxpbHkiLCJMaWx5LW9mLXRoZS12YWxsZXkiLAogIlBlb255IiwiUGluayBDYXJuYXRpb24iLCJSZWQgUm9zZSIsIlNjb3RjaCBSb3NlIiwiVHVsaXAiKQogIApgYGAKCmBgYHtyLHJlc3VsdCA9ICdhc2lzJ30KcmVzPWxhcHBseShkYXRhLGNsYXNzKSAgCnJlcz1hcy5kYXRhLmZyYW1lKHJlcykKcmVzWzEsXSAlPiUgCmtuaXRyOjprYWJsZSgpCmBgYAoKYGBge3J9CmZsb3dlclsxOjIsXQpgYGAKCmBgYHtyfQptYXgoZGF0YSRIZWlnaHQpLW1pbihkYXRhJEhlaWdodCkKbWF4KGRhdGEkRGlzdGFuY2UpLW1pbihkYXRhJERpc3RhbmNlKQpgYGAKCiQkClxmcmFje3wxLTB8K3wwLTF8K3wwLTF8KzErfDEtM3wvMit8My0xNXwvMTcrfDE1MC0yNXwvMTgwK3w1MC0xNXwvNTB9ezh9XGFwcHJveCAwLjg4NzU0MDgKJCQKCiMgRGFpc3kgZnVuY3Rpb24KCiFbXShEYWlzeS5qcGcpIFtDbHVzdGVyIHBhY2thZ2UgZGVzY3JpcHRpb24gYXZhaWxhYmxlIGF0IHRoaXMKbGlua10oaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS9zZWFyY2g/cT1kYWlzeStwYWNrYWdlK3Imb3E9ZGFpc3krJmFxcz1jaHJvbWUuMS42OWk1N2ozNWkxOWkzOWozNWkzOWo2OWk1OWo0Nmk2N2k0MzMuNDQ2NmowajcmY2xpZW50PXRhYmxldC1hbmRyb2lkLXNhbXN1bmctbmYtcmV2MSZzb3VyY2VpZD1jaHJvbWUtbW9iaWxlJmllPVVURi04KS4KCmBgYHtyLCBtZXNzYWdlPSBGLCBlY2hvPVQsd2FybmluZz1GIH0KbGlicmFyeShjbHVzdGVyKQooYWJzKDEtMCkrYWJzKDAtMSkrYWJzKDAtMSkrMSthYnMoMS0zKS8yK2FicygzLTE1KS8xNythYnMoMTUwLTI1KS8xODArYWJzKDUwLTE1KS81MCkvOApkaXN0PC1kYWlzeShkYXRhWywxOjhdLG1ldHJpYyA9ICJHb3dlciIpCmFzLm1hdHJpeChkaXN0KVsxOjIsMToyXQpgYGAKCiMgTW9yZSBvbiBkaXN0YW5jZSBtYXRyaXggY29tcHV0YXRpb24KCiFbXShVU0FBcnJlc3QuanBnKXt3aWR0aD0iNTAlIn0KCi0gICBXZSB1c2UgYSBzdWJzZXQgb2YgdGhlIGRhdGEgYnkgdGFraW5nIDE1IHJhbmRvbSByb3dzIGFtb25nIHRoZSA1MAogICAgcm93cyBpbiB0aGUgZGF0YSBzZXQuCi0gICBXZSBhcmUgdXNpbmcgdGhlIGZ1bmN0aW9uIHNhbXBsZSgpLgotICAgV2Ugc3RhbmRhcmRpemUgdGhlIGRhdGEgdXNpbmcgdGhlIGZ1bmN0aW9uIHNjYWxlKCkuCgpgYGB7cixyZXN1bHRzPSdhc2lzJyxtZXNzYWdlID0gRkFMU0V9CmtuaXRyOjprYWJsZShVU0FycmVzdHMsY2FwdGlvbiA9ICJVU0FycmVzdCBkYXRhIHNldCIsCmRpZ2l0cyA9IDEsYWxpZ249YygiYyIsImMiLCJjIiwiYyIpLGJvb2t0YWJzID0gVFJVRSklPiUKa2FibGVfc3R5bGluZyhsYXRleF9vcHRpb25zID0gIkhPTERfcG9zaXRpb24iKQoKIyMgIGthYmxlX2NsYXNzaWMobGF0ZXhfb3B0aW9ucyA9ICJob2xkX3Bvc2l0aW9uIikKYGBgCgpgYGB7cixyZXN1bHRzPSdhc2lzJywgZWNobyA9IFRSVUUsIGV2YWw9VFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0Kc2V0LnNlZWQoMTIzKQpzcyA8LSBzYW1wbGUoMTo1MCwxNSkgCmRmIDwtIFVTQXJyZXN0c1tzcywgXSAKZGYuc2NhbGVkIDwtIHNjYWxlKGRmKQprbml0cjo6a2FibGUoZGYuc2NhbGVkLGNhcHRpb24gPSAiU2FtcGxlIG9mIFVTQXJyZXN0IGRhdGEgc2V0IixkaWdpdHMgPSA0LGFsaWduPWMoImMiLCJjIiwiYyIpLGJvb2t0YWJzID0gVFJVRSklPiUKa2FibGVfY2xhc3NpYyhsYXRleF9vcHRpb25zID0gImhvbGRfcG9zaXRpb24iKQpgYGAKCi0gICBUaGUgUiBmdW5jdGlvbnMgZm9yIGNvbXB1dGluZyBkaXN0YW5jZXMuCgoxLiAgZGlzdCgpIGZ1bmN0aW9uIGFjY2VwdHMgb25seSBudW1lcmljIGRhdGEuCjIuICBnZXRfZGlzdCgpIGZ1bmN0aW9uIFtmYWN0b2V4dHJhIHBhY2thZ2VdIGFjY2VwdHMgb25seSBudW1lcmljIGRhdGEuCiAgICBpdCBzdXBwb3J0cyBjb3JyZWxhdGlvbi1iYXNlZCBkaXN0YW5jZSBtZWFzdXJlcy4KMy4gIGRhaXN5KCkgZnVuY3Rpb24gW2NsdXN0ZXIgcGFja2FnZV0gaXMgYWJsZSB0byBoYW5kbGUgb3RoZXIgdmFyaWFibGUKICAgIHR5cGVzIChub21pbmFsLCBvcmRpbmFsLCAuLi4pLgoKLSAgIFJlbWFyazogQWxsIHRoZXNlIGZ1bmN0aW9ucyBjb21wdXRlIGRpc3RhbmNlcyBiZXR3ZWVuIHJvd3Mgb2YgdGhlCiAgICBkYXRhLgoKLSAgIFJlbWFyazogSWYgd2Ugd2FudCB0byBjb21wdXRlIHBhaXJ3aXNlIGRpc3RhbmNlcyBiZXR3ZWVuIHZhcmlhYmxlcywKICAgIHdlIG11c3QgdHJhbnNwb3NlIHRoZSBkYXRhIHRvIGhhdmUgdmFyaWFibGVzIGluIHRoZSByb3dzLgoKLSAgIFdlIGZpcnN0IGNvbXB1dGUgRXVjbGlkaWFuIGRpc3RhbmNlcwoKYGBge3IscmVzdWx0cz0nYXNpcycsIGVjaG8gPSBUUlVFLCBldmFsPVRSVUUsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CmRpc3QuZXVjbCA8LSBkaXN0KGRmLnNjYWxlZCwgbWV0aG9kID0gImV1Y2xpZGVhbiIsdXBwZXIgPSBUUlVFKQoKCiNzdGFyZ2F6ZXIoYXMuZGF0YS5mcmFtZShhcy5tYXRyaXgoZGlzdC5ldWNsKVsxOjMsIDE6M10pLGhlYWRlcj1UUlVFLCB0eXBlPSdodG1sJyxzdW1tYXJ5PUZBTFNFLGRpZ2l0cz0xKQoKCnJvdW5kKHNxcnQoc3VtKChkZi5zY2FsZWRbIk5ldyBNZXhpY28iLF0tZGYuc2NhbGVkWyJJb3dhIixdKV4yKSksMSkKcm91bmQoc3FydChzdW0oKGRmLnNjYWxlZFsiTmV3IE1leGljbyIsXS1kZi5zY2FsZWRbIkluZGlhbmEiLF0pXjIpKSwxKQpyb3VuZChzcXJ0KHN1bSgoZGYuc2NhbGVkWyJJb3dhIixdCi1kZi5zY2FsZWRbIkluZGlhbmEiLF0pXjIpKSwxKQoKYGBgCgotICAgV2UgYWxzbyBjb21wdXRlIGNvcnJlbGF0aW9uIGJhc2VkIGRpc3RhbmNlcy4KCmBgYHtyLG1lc3NhZ2U9Rn0KbGlicmFyeSgiZmFjdG9leHRyYSIpCmRpc3QuY29yIDwtIGdldF9kaXN0KGRmLnNjYWxlZCwgbWV0aG9kID0gInBlYXJzb24iKQpyb3VuZChhcy5tYXRyaXgoZGlzdC5jb3IpWzE6MywgMTozXSwgMSkKcm91bmQoMS1jb3IoZGYuc2NhbGVkWyJOZXcgTWV4aWNvIixdLGRmLnNjYWxlZFsiSW93YSIsXSksMSkKcm91bmQoMS1jb3IoZGYuc2NhbGVkWyJOZXcgTWV4aWNvIixdLGRmLnNjYWxlZFsiSW5kaWFuYSIsXSksMSkKcm91bmQoMS1jb3IoZGYuc2NhbGVkWyJJb3dhIixdLGRmLnNjYWxlZFsiSW5kaWFuYSIsXSksMSkKCmBgYAoKIyBWaXN1YWxpemluZyBkaXN0YW5jZSBtYXRyaWNlcwoKLSAgIEEgc2ltcGxlIHNvbHV0aW9uIGZvciB2aXN1YWxpemluZyB0aGUgZGlzdGFuY2UgbWF0cmljZXMgaXMgdG8gdXNlCiAgICB0aGUgZnVuY3Rpb24gZnZpel9kaXN0KCkgW2ZhY3RvZXh0cmEgcGFja2FnZV0uXAotICAgT3RoZXIgc3BlY2lhbGl6ZWQgbWV0aG9kcyB3aWxsIGJlIGRlc2NyaWJlZCBsYXRlciBvbi4KCmBgYHtyfQpsaWJyYXJ5KGZhY3RvZXh0cmEpCmZ2aXpfZGlzdChkaXN0LmV1Y2wpCmBgYAoKIyBQYXJ0aXRpb25pbmcgQ2x1c3RlcmluZwoKLSAgIFBhcnRpdGlvbmluZyBjbHVzdGVyaW5nIGFyZSBjbHVzdGVyaW5nIG1ldGhvZHMgdXNlZCB0byBjbGFzc2lmeQogICAgb2JzZXJ2YXRpb25zIHdpdGhpbiBhIGRhdGEgc2V0LCBpbnRvIG11bHRpcGxlIGdyb3VwcyBiYXNlZCBvbiB0aGVpcgogICAgc2ltaWxhcml0eS4KLSAgIFRoZSBhbGdvcml0aG1zIHJlcXVpcmUgdGhlIGFuYWx5c3QgdG8gc3BlY2lmeSB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzCiAgICB0byBiZSBnZW5lcmF0ZWQuCi0gICBXZSBkZXNjcmliZSB0aGUgY29tbW9ubHkgdXNlZCBwYXJ0aXRpb25pbmcgY2x1c3RlcmluZywgaW5jbHVkaW5nOgoKMS4gIEstbWVhbnMgY2x1c3RlcmluZyAoTWFjUXVlZW4sIDE5NjcpLCBpbiB3aGljaCwgZWFjaCBjbHVzdGVyIGlzCiAgICByZXByZXNlbnRlZCBieSB0aGUgY2VudGVyIG9yIG1lYW5zIG9mIHRoZSBkYXRhIHBvaW50cyBiZWxvbmdpbmcgdG8KICAgIHRoZSBjbHVzdGVyLiBUaGUgSy1tZWFucyBtZXRob2QgaXMgc2Vuc2l0aXZlIHRvIGFub21hbG91cyBkYXRhCiAgICBwb2ludHMgYW5kIG91dGxpZXJzLgoyLiAgSy1tZWRvaWRzIGNsdXN0ZXJpbmcgb3IgUEFNIChQYXJ0aXRpb25pbmcgQXJvdW5kIE1lZG9pZHMsIEthdWZtYW4gJgogICAgUm91c3NlZXV3LCAxOTkwKSwgaW4gd2hpY2gsIGVhY2ggY2x1c3RlciBpcyByZXByZXNlbnRlZCBieSBvbmUgb2YKICAgIHRoZSBvYmplY3RzIGluIHRoZSBjbHVzdGVyLiBQQU0gaXMgbGVzcyBzZW5zaXRpdmUgdG8gb3V0bGllcnMKICAgIGNvbXBhcmVkIHRvIGstbWVhbnMuCjMuICBDTEFSQSBhbGdvcml0aG0gKENsdXN0ZXJpbmcgTGFyZ2UgQXBwbGljYXRpb25zKSwgd2hpY2ggaXMgYW4KICAgIGV4dGVuc2lvbiB0byBQQU0gYWRhcHRlZCBmb3IgbGFyZ2UgZGF0YSBzZXRzLgoKIyBLLU1lYW5zIENsdXN0ZXJpbmcKCi0gICBUaGUgZGVzY3JpcHRpb24gb2YgdGhlIGFsZ29yaXRobSBpcyBiYXNlZCBvbjoKLSAgIEhBUlRJR0FOLCBKb2huIEEuICpDbHVzdGVyaW5nIGFsZ29yaXRobXMqLiBKb2huIFdpbGV5ICYgU29ucywKICAgIEluYy4sIDE5NzUuCi0gICBUaGUgZGF0YSB1c2VkIGJ5IHRoZSBhdXRob3IgYXJlIHByb3ZpZGVkIGJlbG93LgoKIVtdKEhhcnRpZ2FuVGFibGU0ZG90MS5qcGcpe3dpZHRoPSI1MCUifQoKLSAgIFRoZSBwcmluY2lwYWwgbnV0cmllbnRzIGluIG1lYXQsIGZpc2gsIGFuZCBmb3dsIGFyZSBsaXN0ZWQuCi0gICBSZWNhbGwgdGhhdCAxb3o9IDI4LjM0OTUyZy4KLSAgIEVzdGltYXRlZCBkYWlseSBkaWV0YXJ5IGFsbG93YW5jZXMgYXJlOiBmb29kIGVuZXJneSAoMzIwMCBjYWwpLAogICAgcHJvdGVpbiAoNzAgZyksIGNhbGNpdW0gKDAuOCBnKSwgYW5kIGlyb24gKDEwIG1nKS4KLSAgIFRhYmxlIDQuMiBjb252ZW50cyB0aGUgdmFyaWFibGVzICh3aXRoIHRoZSBleGNlcHRpb24gb2YgRmF0KSBpbgogICAgcGVyY2VudGFnZSBvZiBmb29kIGRlbGl2ZXJ5LgoKIVtEYXRhXShIYXJ0aWdhblRhYmxlNGRvdDIuanBnKXt3aWR0aD0iNTAlIn0KCi0gICBGb3IgZS5nLiwgdGhlIGZpcnN0IChCQikgbGlnbmUgaXMgb2J0YWluZWQgaW4gdGhlIGZvbGxvd2luZyB3YXk6Ci0gICAkMzQwLzMyMDA9MTFcJVx0ZXh0eyhGb29kIEVuZXJneSl9JC4KLSAgICQyMC83MD0yOVwlXHRleHR7KFByb3RlaW4pfSQuCi0gICAkezAuMDA5fS97MC44fT0xXCVcdGV4dHsgKENhbGNpdW0pfSQuCi0gICAkezIuNn0vezEwfT0gMjZcJVx0ZXh0eyAoSXJvbil9JC4KLSAgIEFuIGFyZ3VtZW50IGNvdWxkIGJlIG1hZGUgdGhhdCBpcm9uIGlzIGxlc3MgaW1wb3J0YW50IHRoYW4gY2Fsb3JpZXMKICAgIG9yIHByb3RlaW4gYW5kIHNvIHNob3VsZCBiZSBnaXZlbiBsZXNzIHdlaWdodCBvciBpZ25vcmVkIGVudGlyZWx5LgotICAgVGhlcmUgYXJlICRuJCBvYmplY3RzIGFuZCAkayQgY2x1c3RlcnMsICRrXGxlcSBuJC4KLSAgIE91ciBwdXJwb3NlIGlzIHRvIHBhcnRpdGlvbiB0aGUgJG4kIG9iamVjdHMgKGhlcmUgZm9vZHMpIHNvIHRoYXQKICAgIG9iamVjdHMgd2l0aGluIGNsdXN0ZXJzIGFyZSBjbG9zZSBhbmQgb2JqZWN0cyBpbiBkaWZmZXJlbnQgY2x1c3RlcnMKICAgIGFyZSBkaXN0YW50LgotICAgRWFjaCBjbHVzdGVyIGNvbnRhaW5zIGF0IGxlYXN0IG9uZSBvYmplY3QgYW5kIGVhY2ggb2JqZWN0IGJlbG9uZ3MgdG8KICAgIG9ubHkgb25lIGNsdXN0ZXIuCi0gICBUaGVyZSBpcyBhIHZlcnkgbGFyZ2UgbnVtYmVyIG9mIHBvc3NpYmxlIHBhcnRpdGlvbnMuCgojIEV4ZXJjaWNlIDExCgotICAgV2hhdCBpcyB0aGUgbnVtYmVyIG9mIHBvc3NpYmxlIHBhcnRpdGlvbnM/Ci0gICBIaW50IHVzZSB0aGUgU3RpcmxpbmcgbnVtYmVycyBvZiB0aGUgc2Vjb25kIGtpbmQKICAgIFtXaWtpcGVkaWFdKGh0dHBzOi8vZW4ubS53aWtpcGVkaWEub3JnL3dpa2kvU3RpcmxpbmdfbnVtYmVyc19vZl90aGVfc2Vjb25kX2tpbmQpLgotICAgRXhhbXBsZSBvZiBmdW5jdGlvbnMgaW4gUi4KCmBgYHtyfQpsaWJyYXJ5KG11bHRpY29vbCkKU3RpcmxpbmcyKDgsMykKU3RpcmxpbmcyKDgsMSkKU3RpcmxpbmcyKDMsMikKCmBgYAoKIyBLLU1lYW5zCgotICAgVGhlIGRpc2NvcmRhbmNlIGJldHdlZW4gdGhlIGRhdGEgYW5kIGEgZ2l2ZW4gcGFydGl0aW9uIGlzIGRlbm90ZWQgYnkKICAgICRlJC4KLSAgIFdlIHVzZSB0aGUgdGVjaG5pcXVlIG9mIGxvY2FsIG9wdGltaXphdGlvbi4KLSAgIEEgbmVpZ2hib3Job29kIG9mIHBhcnRpdGlvbnMgaXMgZGVmaW5lZCBmb3IgZWFjaCBwdGlvbi4KLSAgIFN0YXJ0aW5nIGZyb20gYW4gaW5pdGlhbCBwYXJ0aXRpb24sIHNlYXJjaCB0aHJvdWdoIGEgc2V0IG9mCiAgICBwYXJ0aXRpb25zIGF0IGVhY2ggc3RlcC4KLSAgIE1vdmUgZnJvbSB0aGUgcGFydGl0aW9uIHRvIGEgcGFydGl0aW9uIGluIGl0cyBuZWlnaGJvcmhvb2QgZm9yIHdoaWNoCiAgICAkZSQgaXMgbWluaW0uCi0gICBJZiB0aGUgbmVpZ2hib3Job29kcyBhcmUgdmVyeSBsYXJnZSwgaXQgaXMgY2hlYXBlciBjb21wdXRhdGlvbmFsbHkKICAgIHRvIG1vdmUgdG8gdGhlIGZpcnN0IHBhcnRpdGlvbiBkaXNjb3ZlcmVkIGluIHRoZSBuZWlnaGJvcmhvb2Qgd2hlcmUKICAgICRlJCBpcyByZWR1Y2VkIGZyb20gaXRzIHByZXNlbnQgdmFsdWUuCi0gICBBIG51bWJlciBvZiBzdG9wcGluZyBydWxlcyBhcmUgcG9zc2libGUuCi0gICBGb3IgZXhhbXBsZSwgdGhlIHNlYXJjaCBzdG9wcyB3aGVuICRlJCBpcyBub3QgcmVkdWNlZCBieSBtb3ZlbWVudCB0bwogICAgdGhlIG5laWdoYm9yaG9vZC4KLSAgIFRoZSBwcmVzZW50IHBhcnRpdGlvbiBpcyBsb2NhbGx5IG9wdGltYWwgaW4gdGhhdCBpdCBpcyB0aGUgYmVzdAogICAgcGFydGl0aW9uIGluIGl0cyBuZWlnaGJvcmhvb2QuCi0gICBDb25zaWRlciBwYXJ0aXRpb25zIG9mIHRoZSBmaXZlICgkbj01JCkgYmVlZiBmb29kcwogICAgJFx7XHRleHR7QkIsIEJSLEJTLEJDLEJIfVx9JCBpbnRvIHRocmVlIGNsdXN0ZXJzICgkaz0zJCkuCi0gICBUb3RhbGx5LCB0aGVyZSBhcmUgMjUgc3VjaCBwYXJ0aXRpb25zLgoKYGBge3Isd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KGdtcCkKU3RpcmxpbmcyKDUsMykKYGBgCgotICAgQSBwbGF1c2libGUgbmVpZ2hib3Job29kIGZvciBhIHBhcnRpdGlvbiBpcyB0aGUgc2V0IG9mIHBhcnRpdGlvbnMKICAgIG9idGFpbmVkIGJ5IHRyYW5zZmVycmluZyBhbiBvYmplY3QgZnJvbSBvbmUgY2x1c3RlciB0byBhbm90aGVyLgoKLSAgIEZvciB0aGUgcGFydGl0aW9uIChCQiBCUikgKEJTKSAoQkMgQkgpLCB0aGUgbmVpZ2hib3Job29kIGNvbnNpc3RzIG9mCiAgICB0aGUgZm9sbG93aW5nIHRlbiBwYXJ0aXRpb25zOgoKMS4gIChCUikgKEJCIEJTKSAoQkMgQkgpCjIuICAoQlIpIChCUykgKEJCIEJDIEJIKQozLiAgKEJCKSAoQlIgQlMpIChCQyBCSCkKNC4gIChCQikgKEJTKSAoQlIgQkMgQkgpCjUuICAoQkIgQlIgQlMpIE8gKEJDIEJIKQo2LiAgKEJCIEJSKSBPIChCUyBCQyBCSCkKNy4gIChCQiBCUiBCQykgKEJTKSAoQkgpCjguICAoQkIgQlIpIChCUyBCQykgKEJIKQo5LiAgKEJCIEJSIEJIKSAoQlMpIChCQykKMTAuIChCQiBCUikgKEJTIEJIKSAoQkMpCgojIEstTWVhbnMgQWxnb3JpdGhtCgotICAgTGV0ICRcbWF0aGJme3h9X2lcZXF1aXYgKHhfaV4xLFxjZG90cyx4X2lebSkkIHRoZSB2ZWN0b3Igb2YgdmFsdWVzCiAgICBmb3IgdGhlIG9iamVjdCAkaSQsICRpPTEsXGNkb3RzICxuLiQKCi0gICBUaGUgdmFyaWFibGVzIGFyZSBhc3N1bWVkIHNjYWxlZC4KCi0gICBUaGUgcGFydGl0aW9uIGhhcyAkayQgZGlzam9pbnQgY2x1c3RlcnM6ICRDXzEsXGNkb3RzLENfayQsIHdoaWNoIGFyZQogICAgdGhlIGluZGljZXMgb2YgdGhlIG9iamVjdHMgaW4gdGhlIHZhcmlvdXMgY2x1c3RlcnMuCgotICAgTGV0ICRuX2wkIGJlIHRoZSBudW1iZXIgb2Ygb2JqZWN0cyBpbiBjbHVzdGVyICRDX2wkLgoKLSAgIEVhY2ggb2YgdGhlICRuJCBvYmplY3RzIGxpZXMgaW4ganVzdCBvbmUgb2YgdGhlICRrJCBjbHVzdGVycy4KCi0gICBOb3RlIHRoYXQgJFxzdW1fe2w9MX1eayBuX2w9biQuCgotICAgVGhlIHZlY3RvciBvZiBtZWFucyBvdmVyIHRoZSBvYmplY3RzIGluIGNsdXN0ZXIgJENfbCQgaXMgZGVub3RlZCBieQogICAgJFxiYXJ7XG1hdGhiZnt4fX1fe2x9JCwgd2l0aCAkJAogICAgXGJhcntcbWF0aGJme3h9fV97bH1cZXF1aXZcZnJhY3sxfXtuX2x9XHN1bV97aVxpbiBDX2x9XG1hdGhiZnt4fV97aX09KFxiYXJ7eH1fbF4xLFxjZG90cyxcYmFye3h9X2xebSksXDpsPTEsXGNkb3RzLCBrLAogICAgJCQgd2hlcmUgJCQKICAgIFxiYXJ7eH1fbF5qXGVxdWl2IFxmcmFje1xzdW1fe2lcaW4gQ19sfXhfaV57an19e25fbH0sXDpqPTEsXGNkb3RzLCBtOyBcOmw9MSxcY2RvdHMsay4KICAgICQkCgotICAgVGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlIG9iamVjdCAkaiQgYW5kIHRoZSBjbHVzdGVyICRsJCBpcwogICAgJGQoXG1hdGhiZnt4fV9pLFxiYXJ7XG1hdGhiZnt4fX1fbCkkLCB3aGVyZSAkZCQgaXMgdGFrZW4gdG8gYmUgdGhlCiAgICBFdWNsaWRpYW4gZGlzdGFuY2UgJCQKICAgIGQoXG1hdGhiZnt4fV9pLFxiYXJ7XG1hdGhiZnt4fX1fbCk9fHxcbWF0aGJme3h9X2ktXGJhcntcbWF0aGJme3h9fV9sfHw9XGJpZ2dbXHN1bV97aj0xfV5tKHhfaV5qLVxiYXJ7eH1fbF5qKV4yXGJpZ2ddXnsxLzJ9LFw6aT0xLFxjZG90cyxtO1w6bD0xLFxjZG90cyxrLgogICAgJCQgd2hlcmUgJHx8XG1hdGhiZntcY2RvdH18fCQgaXMgdGhlIEV1Y2xpZGlhbiBub3JtLgoKLSAgIFRoZSBlcnJvciBvZiB0aGUgcGFydGl0aW9uIGlzIHRha2VuIHRvIGJlCgokJAplPQpcc3VtX3tsPTF9XntrfVxzdW1fe2lcaW4gQ19sfQp8fFxtYXRoYmZ7eH1faS1cYmFye1xtYXRoYmZ7eH19X2x8fF4yLgokJAoKLSAgIEFsdGVybmF0aXZlbHksIHdlIGhhdmUgJCQKICAgIGU9XHN1bV97aT0xfV57bn1cc3VtX3tqPTF9Xm18fFxtYXRoYmZ7eH1faS1cYmFye1xtYXRoYmZ7eH19X3tsKGopfXx8XjIsCiAgICAkJAoKd2hlcmUgJGwoaSkkIGlzIHRoZSBpbmRleCBvZiB0aGUgY2x1c3RlciBvZiBvYmplY3QgJGkkLgoKLSAgIFRoZSBnZW5lcmFsIHByb2NlZHVyZSBpcyB0byBzZWFyY2ggZm9yIGEgcGFydGl0aW9uIHdpdGggYSBzbWFsbAogICAgZXJyb3IgJGUkIGJ5IG1vdmluZyBjYXNlcyBmcm9tIG9uZSBjbHVzdGVyIHRvIGFub3RoZXIuCi0gICBUaGUgc2VhcmNoIGVuZHMgd2hlbiBubyBzdWNoIG1vdmVtZW50IHJlZHVjZXMgJGUkLgotICAgKipTVEVQIDEqKi4gQXNzdW1lIGluaXRpYWwgY2x1c3RlcnMuIENvbXB1dGUgdGhlIGNsdXN0ZXIgbWVhbnMKICAgICRcYmFye1xtYXRoYmZ7eH19X2wkIGFuZCB0aGUgaW5pdGlhbCBlcnJvciAkZSQuCi0gICAqKlNURVAgMioqLiBGb3IgdGhlIGZpcnN0IG9iamVjdCwgY29tcHV0ZSBmb3IgZXZlcnkgY2x1c3RlciAkbCQKCiQkClxEZWx0YSBlPQpcZnJhY3tuX2xkXjIoXG1hdGhiZnt4fV8xLFxiYXJ7XG1hdGhiZnt4fX1fbCl9e25fbCsxfS1cZnJhY3tuX3tsKDEpfWReMihcbWF0aGJme3h9XzEsXGJhcntcbWF0aGJme3h9fV97bCgxKX0pfXtuX3tsKDEpfS0xfSxcOmw9MSxcY2RvdHMsIGssXDpsXG5lcSBsKDEpLgokJAoKLSAgIEl0IGNvcnJlc3BvbmRzIHRvIHRoZSBlcnJvciB2YXJpYXRpb24gaW4gdHJhbnNmZXJyaW5nIHRoZSBmaXJzdAogICAgb2JqZWN0IGZyb20gY2x1c3RlciAkbCgxKSQgdG8gd2hpY2ggaXQgYmVsb25ncyB0byBjbHVzdGVyICRsJC4KLSAgIElmIHRoZSBtaW5pbXVtIG9mIHRoaXMgcXVhbnRpdHkgb3ZlciBhbGwgJGxcbmVxIGwoMSkkIGlzIG5lZ2F0aXZlLAogICAgdHJhbnNmZXIgdGhlIGZpcnN0IGNhc2UgZnJvbSBjbHVzdGVyICRsKDEpJCB0byB0aGlzIG1pbmltYWwgJGwkLgotICAgQWRqdXN0IHRoZSBjbHVzdGVyIG1lYW5zIG9mICRsKDEpJCBhbmQgdGhlIG1pbmltYWwgJGwkIGFuZCBhZGQgdGhlCiAgICBlcnJvciB2YXJpYXRpb24gKHdoaWNoIGlzIG5lZ2F0aXZlKSB0byAkZSQuCi0gICAqKlNURVAgMyoqLiBSZXBlYXQgU1RFUCAyIGZvciBlYWNoIG9iamVjdCAkaSQgc3VjaCB0aGF0CiAgICAkMlxsZXEgaSBcbGVxIG4kLgotICAgKipTVEVQIDQqKi4gbGYgbm8gbW92ZW1lbnQgb2YgYW4gb2JqZWN0IGZyb20gb25lIGNsdXN0ZXIgdG8gYW5vdGhlcgogICAgb2NjdXJzIGZvciBhbnkgY2FzZSwgc3RvcC4gT3RoZXJ3aXNlLCByZXR1cm4gdG8gU1RFUCAyLgoKIyBFeGVyY2ljZSAxMgoKUHJvdmUgdGhhdCB0aGUgZXJyb3IgdmFyaWF0aW9uIGlzIGluZGVlZCBnaXZlbiBieToKCiQkClxEZWx0YSBlPQpcZnJhY3tuX2xkXjIoXG1hdGhiZnt4fV8xLFxiYXJ7XG1hdGhiZnt4fX1fbCl9e25fbCsxfS1cZnJhY3tuX3tsKDEpfWReMihcbWF0aGJme3h9XzEsXGJhcntcbWF0aGJme3h9fV97bCgxKX0pfXtuX3tsKDEpfS0xfSxcOmw9MSxcY2RvdHMsIGssXDpsXG5lcSBsKDEpLgokJAoKIyBLLU1FQU5TIEFQUExJRUQgVE8gRk9PRCBOVVRSSUVOVCBEQVRBCgotICAgT25seSB0aGUgZmlyc3QgZWlnaHQgZm9vZHMgd2lsbCBiZSBjb25zaWRlcmVkLgotICAgT25seSB0aHJlZSB2YXJpYWJsZXMsIGZvb2QgZW5lcmd5LCBwcm90ZWluLCBhbmQgY2FsY2l1bSBhcyBhCiAgICBwZXJjZW50YWdlIG9mIHJlY29tbWVuZGVkIGRhaWx5IGFsbG93YW5jZXMgYXJlIHVzZWQuCi0gICBUaGUgZWlnaHQgZm9vZHMgJChtPTgpJCBhcmUgcGFydGl0aW9uZWQgaW50byB0aHJlZSBjbHVzdGVycyAoJGs9MyQpLgoKIyBFeGVyY2ljZSAxMwoKLSAgIEV4cGxhaW4gaW4gZGV0YWlscyB0aGUgay1tZWFucyBhbGdvcml0aG0gYmFzZWQgb24gdGhlIGZvbGxvd2luZwogICAgcGFnZXMgb2YgSGFydGlnYW4gKDE5NzUpLgoKIVtdKEhhcnRpZ2FuVGFibGU0ZG90My5qcGcpe3dpZHRoPSI1MCUifQoKIVtdKEhhcnRpZ2FuQWxnbzEuanBnKXt3aWR0aD0iNTAlIn0KCiFbXShIYXJ0aWdhbkFsZ28yLmpwZyl7d2lkdGg9IjUwJSJ9CgpgYGB7cn0KI2xpYnJhcnkoImNsdXN0ZXIuZGF0YXNldHMiKQojd3JpdGUuY3N2KHJkYS5tZWF0LmZpc2guZm93bC4xOTU5LCJIYXJ0aWdhbmRhdCVhMS5jc3YiKQpkZjwtcmVhZC5jc3YoIkhhcnRpZ2FuZGF0YTEuY3N2IikKcHJpbnQoZGYpCmRmPC1kZlsxOjgsYygzLDQsNildCmRmCmBgYAoKYGBge3J9CiMgVGhlIGRhdGEgc2V0IGNvbnRhaW5zIHNvbWUgZXJyb3JzIApkZlszLDFdPC0xMyAjIEVycm9yIGluIGxpbmUgMwpkZls2LDFdPC00ICMgRXJyb3IgYXQgbGluZSA2CmRmWzcsM108LTEgIyBFcnJvciBhdCBsaW5lIDcKZGYKcm93bmFtZXMoZGYpPC1jKCJCQiIsIkhSIiwiQlIiLCJCUyIsIkJDIiwiQ0IiLCJDQyIsIkJIIikKZGYKY29sbmFtZXMoZGYpPC1jKCJFbmVyZ3kiLCJQcm90ZWluIiwiQ2FsY2l1bSIpCmRmCmBgYAoKIyBNb3JlIG9uIGttZWFucygpIG91dHB1dAoKYGBge3J9CmttLnJlczwta21lYW5zKGRmWzE6OCxdLDMsaXRlci5tYXggPSAxMDApCmttLnJlcyRjbHVzdGVyCmttLnJlcyRjZW50ZXJzCmttLnJlcyR0b3RzcwpzdW0oKGRmWzE6OCxdJEVuZXJneS1tZWFuKGRmWzE6OCxdJEVuZXJneSkpXjIpKwpzdW0oKGRmWzE6OCxdJFByb3RlaW4tbWVhbihkZlsxOjgsXSRQcm90ZWluKSleMikrCnN1bSgoZGZbMTo4LF0kQ2FsY2l1bS1tZWFuKGRmWzE6OCxdJENhbGNpdW0pKV4yKQo3KnZhcihkZlsxOjgsXSRFbmVyZ3kpKzcqdmFyKGRmWzE6OCxdJFByb3RlaW4pKzcqdmFyKGRmWzE6OCxdJENhbGNpdW0pCmttLnJlcyR3aXRoaW5zcwprbS5yZXMkdG90LndpdGhpbnNzCmttLnJlcyRiZXR3ZWVuc3MKa20ucmVzJHNpemUKa20ucmVzJGl0ZXIKYGBgCgojIEV4ZXJjaWNlIDE0CgotICAgUHJvZHVjZSBhIGhlYXRtYXAgZm9yIHRoZSBkYXRhIGJ5IHJlYXJyYW5naW5nIHRoZSBsaW5lcyBhY2NvcmRpbmcgdG8KICAgIHRoZSBmb3VuZCBjbHVzdGVycyAoaz0zKS4KCiMgRXhlcmNpY2UgMTUKCi0gICBQcm9kdWNlIHRoZSBUYWJsZSA0LjQgb2YgSGFydGlnYW4gKDE5NzUpLgoKIVtdKEhhcnRpZ2FuQWxnbzMuanBnKXt3aWR0aD0iNTAlIn0KCiMgRXhlcmNpY2UgMTYKCi0gICBVc2UgZ2dwbG90MiB0byBkcmF3IGFuIHgteSBwbG90IHdpdGggdGhlIG51bWJlciBvZiBjbHVzdGVycyBvbiB0aGUKICAgIHgtYXhpcyBhbmQgdGhlIGVycm9yIG9uIHRoZSB5LWF4aXMuCgoqKlJlbWFyazoqKiBUaGUgbG9jYXRpb24gb2YgYSBrbmVlIGlzIGdlbmVyYWxseSBjb25zaWRlcmVkIGFzIGFuCmFwcHJvcHJpYXRlIG51bWJlciBvZiBjbHVzdGVycy4KCiMgVmlzdWFsaXppbmcgay1tZWFucyByZXN1bHRzCgotICAgVGhlIHJlc3VsdHMgYXJlIHZpc3VhbGl6ZWQgdXNpbmcgZnZpel9jbHVzdGVyKCkgZnVuY3Rpb24uCi0gICBJdCBkcmF3cyBhIHNjYXR0ZXIgcGxvdCBvZiBkYXRhIHBvaW50cyBjb2xvcmVkIGJ5IGNsdXN0ZXIgbnVtYmVycy4KLSAgIElmIHRoZSBkYXRhIGNvbnRhaW5zIG1vcmUgdGhhbiAyIHZhcmlhYmxlcywgdGhlIFByaW5jaXBhbCBDb21wb25lbnQKICAgIEFuYWx5c2lzIChQQ0EpIGFsZ29yaXRobSBpcyB1c2VkIHRvIHJlZHVjZSB0aGUgZGltZW5zaW9uYWxpdHkgb2YgdGhlCiAgICBkYXRhLgotICAgVGhlIGZpcnN0IHR3byBwcmluY2lwYWwgZGltZW5zaW9ucyBhcmUgdXNlZCB0byBwbG90IHRoZSBkYXRhLgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShmYWN0b2V4dHJhKQoKZnZpel9jbHVzdGVyKGttLnJlcywgZGYsIGVsbGlwc2UudHlwZSA9ICJub3JtIikKZnZpel9jbHVzdGVyKGttLnJlcywgZGF0YSA9IGRmWzE6OCxdLApwYWxldHRlID0gYygiIzJFOUZERiIsICIjMDBBRkJCIiwgIiNFN0I4MDAiKSwKZWxsaXBzZS50eXBlID0gImV1Y2xpZCIsICMgQ29uY2VudHJhdGlvbiBlbGxpcHNlCnN0YXIucGxvdCA9IFRSVUUsICMgQWRkIHNlZ21lbnRzIGZyb20gY2VudHJvaWRzIHRvIGl0ZW1zCnJlcGVsID0gVFJVRSwgIyBBdm9pZCBsYWJlbCBvdmVycGxvdHRpbmcgKHNsb3cpCmdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkKKQpgYGAKCmBgYHtyfQpwY2E9cHJjb21wKGRmWzE6OCxdLCBzY2FsZSA9IFRSVUUpCnN1bW1hcnkocGNhKQoKYGBgCgojIE1vcmUgb24gUENBCgpgYGB7cn0KZGF0YShkZWNhdGhsb24yKQpkZWNhdGhsb24uYWN0aXZlIDwtIGRlY2F0aGxvbjJbMToyMywgMToxMF0KcmVzLnBjYSA8LSBwcmNvbXAoZGVjYXRobG9uLmFjdGl2ZSwgc2NhbGUgPSBUUlVFKQpzdW1tYXJ5KHJlcy5wY2EpCmZ2aXpfcGNhX2JpcGxvdChyZXMucGNhKQpgYGAKCiMgU2lsaG91ZXR0ZQoKLSAgIEFzc3VtZSB0aGUgZGF0YSBhcmUgY2x1c3RlcmVkIGludG8gJGskIGNsdXN0ZXJzLgoKLSAgIEZvciAkaT0xLFxjZG90cyxuJCwgbGV0OiAkJAogICAgYV9pPVxmcmFjezF9e25fe2woaSl9LTF9XHN1bV97aV5ccHJpbWUgXGluIGlcaW4gQ197bChpKX1cc2V0bWludXNce2lcfX1kKGksaV5ccHJpbWUpLAogICAgJCQgYmUgdGhlIG1lYW4gZGlzdGFuY2UgYmV0d2VlbiAkaSQgYW5kIGFsbCB0aGUgcG9pbnRzIG9mIHRoZSBzYW1lCiAgICBjbHVzdGVyLgoKLSAgIElmICRuX3tsKGkpfT0xJCwgd2Ugc2V0ICRhX2k9MCQuCgotICAgSXQgaXMgaW50ZXJwcmV0ZWQgYXMgYSBtZWFzdXJlIG9mIGhvdyB3ZWxsICRpJCBpcyBhc3NpZ25lZCB0byBpdHMKICAgIGNsdXN0ZXIgKHNtYWxsZXIgdGhlIHZhbHVlLCBiZXR0ZXIgaXMgdGhlIGFzc2lnbm1lbnQpLgoKLSAgIExldCBhbHNvICQkCiAgICBiX2k9XG1pbl97bFxuZXEgbChpKX17XGZyYWMgezF9e3xuX3tsfXx9fVxzdW1fe2leXHByaW1lXGluIENfbH1kKGksaV5ccHJpbWUpLAogICAgJCQgYmUgdGhlICJuZWlnaGJvcmluZyBjbHVzdGVyIiBvZiAkaSQuCgotICAgV2Ugbm93IGRlZmluZSBhICpzaWxob3VldHRlKiBvZiAkaSQgYXMgJCQKICAgIHNfaT1cZnJhYyB7Yl9pLWFfaX17XG1heChhX2ksYl9pKX0gXHRleHR7IGlmIH1uX3tsKGkpfT4xLAogICAgJCQgYW5kICQkCiAgICBzX2k9MFx0ZXh0eyBpZiB9bl97bChpKX09MS4KICAgICQkCgotICAgQWx0ZXJuYXRpdmVseSwgd2UgaGF2ZSAkJAogICAgc19pPQogICAgXGJlZ2lue2Nhc2VzfQogICAgMS1hX2kvYl9pLCZcOlx0ZXh0e2lmIH0gYV9pPGJfaTtcXAogICAgMCwmXDpcdGV4dHtpZiB9IGFfaT1iX2k7XFwKICAgIGJfaS9hX2ktMSwmXDpcdGV4dHtpZiB9IGFfaT5iX2kuCiAgICBcZW5ke2Nhc2VzfQogICAgJCQKCi0gICBGcm9tIHRoZSBhYm92ZSBkZWZpbml0aW9uIGl0IGlzIGNsZWFyIHRoYXQgJCQgLTFcbGVxIHNfaVxsZXEgMSQkCgotICAgRm9yICRzX2kkIHRvIGJlIGNsb3NlIHRvIDEgd2UgcmVxdWlyZSAkYV9pPDxiX2kkLgoKLSAgIElmICRzX2kkIGlzIGNsb3NlIHRvICQtMSQsICRpJCBpcyBtb3JlIGFwcHJvcHJpYXRlbHkgY2x1c3RlcmVkIGluCiAgICBpdHMgbmVpZ2hib3VyaW5nIGNsdXN0ZXIuCgotICAgQW4gJHNfaSQgbmVhciB6ZXJvIG1lYW5zIHRoYXQgdGhlICRpJCBpcyBvbiB0aGUgYm9yZGVyIG9mIHR3bwogICAgbmF0dXJhbCBjbHVzdGVycy4KCiMgU2lsaG91ZXR0ZSBleGFtcGxlCgpgYGB7cixyZXN1bHRzPSdhc2lzJ30KbGlicmFyeShrbml0cikKa25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQprbS5yZXM8LWttZWFucyhkZlsxOjgsXSwzLGl0ZXIubWF4ID0gMTAwKQpzbG9iajwtc2lsaG91ZXR0ZShrbS5yZXMkY2x1c3RlcixkaXN0KGRmWzE6OCxdKSkKcm93Lm5hbWVzKHNsb2JqKTwtcm93Lm5hbWVzKGRmWzE6OCxdKQoKa25pdHI6OmthYmxlKHNsb2JqWyxdLCBjYXB0aW9uID0gIlNpbGhvdWV0dGVzIHZhbHVlcyBmb3IgdGhlIGZvb2QgZXhhbXBsZSIsZGlnaXRzID0gNCxjb2wubmFtZXMgPSBjKCJDbHVzdGVyIiwiTmVpZ2hib3IiLCJTaWxob3VldHRlIHdpZHRoIiksYWxpZ249YygiYyIsImMiLCJjIiksYm9va3RhYnMgPSBUUlVFKSU+JQprYWJsZV9jbGFzc2ljKGxhdGV4X29wdGlvbnMgPSAiaG9sZF9wb3NpdGlvbiIpJT4lCnJvd19zcGVjKDE6MixiYWNrZ3JvdW5kID0gIiMyRUZFRjciKSU+JQpyb3dfc3BlYygzOjUsYmFja2dyb3VuZCA9ICIjRjNGNzgxIiklPiUKcm93X3NwZWMoNjo4LGJhY2tncm91bmQgPSAiI0ZBNThGNCIpCgpgYGAKCiMjIFNpbGhvdWV0dGUgZ3JhcGgKCmBgYHtyfQpzaWxob3VldHRlKGttLnJlcyRjbHVzdGVyLGRpc3QoZGZbMTo4LF0pKSU+JQpmdml6X3NpbGhvdWV0dGUoKQpgYGAKCiMgRXhlcmNpY2UgMTcKCi0gICBXZSBhcmUgdXNpbmcgdGhlIGRhdGEgb2YgdGhlIHBhcGVyIHdoaWNoIGludHJvZHVjZXMgdGhlCiAgICAqKlNpbGhvdWV0dGUqKiBtZXRob2Qgc29tZSB5ZWFycyBhZ286IFtST1VTU0VFVVcsIFBldGVyIEouCiAgICBTaWxob3VldHRlczogYSBncmFwaGljYWwgYWlkIHRvIHRoZSBpbnRlcnByZXRhdGlvbiBhbmQgdmFsaWRhdGlvbiBvZgogICAgY2x1c3RlciBhbmFseXNpcy4gSm91cm5hbCBvZiBjb21wdXRhdGlvbmFsIGFuZCBhcHBsaWVkIG1hdGhlbWF0aWNzLAogICAgMTk4Nywgdm9sLiAyMCwgcC4gNTMtNjUuXShodHRwczovL2Jvb2tzYy5vcmcvYm9vay8zMDA2NjE2LzUyODMwOSkuCgohW10oU2lsaG91ZXR0ZVRhYmxlMS5qcGcpe3dpZHRoPSI1MCUifQoKLSAgIFRoZSBhdXRob3Igb2J0YWluIHRoZSBmb2xsb3dpbmcgdHdvIGZpZ3VyZXMuCgohW10oU2lsaG91ZXR0ZUZpZzIuanBnKXt3aWR0aD0iNTAlIn0KCiFbXShTaWxob3VldHRlRmlnMy5qcGcpe3dpZHRoPSI1MCUifQoKLSAgIFlvdXIgdGFzayBpcyB0byBwcm9kdWNlIHRoZSBzYW1lIGZpZ3VyZXMgdXNpbmcgbW9kZXJuIGRhdGF2aXogdG9vbHMuCi0gICBXaGF0IGRpZCB5b3UgbGVhcm4gZnJvbSB0aGVzZXMgZ3JhcGhzPwoKIyBFeGVyY2ljZSAxOAoKLSAgIFByb3ZpZGUgYSBTaWxob3VldHRlIGdyYXBoIGZvciB0aGUgSGFydGlnYW4gKDc1KSBkYXRhCgojIEV4ZXJjaWNlIDE5CgotICAgUHJvdmlkZSBhIGstbWVhbnMgY2x1c3RlcmluZyBhbmFseXNpcyBvZiB0aGUgVVMgQXJyZXN0IGRhdGEKCiMgUGFydGl0aW9uaW5nIEFyb3VuZCBNZWRvaWRzIChQQU0sIGstbWVkb2lkcykKCi0gICBUaGUgYWxnb3JpdGhtIHVzZWQgaW4gdGhlIHByb2dyYW0gUEFNIHNlYXJjaCBmb3IgJGskIHJlcHJlc2VudGF0aXZlCiAgICBvYmplY3RzIGFtb25nICRuJCBvYmplY3RzLgotICAgVGhlc2Ugb2JqZWN0cyBzaG91bGQgcmVwcmVzZW50IHZhcmlvdXMgYXNwZWN0cyBvZiB0aGUgc3RydWN0dXJlIG9mCiAgICB0aGUgZGF0YS4KLSAgIEluIHRoZSBQQU0gYWxnb3JpdGhtLCB0aGUgcmVwcmVzZW50YXRpdmUgb2JqZWN0cyBhcmUgY2FsbGVkCiAgICAqbWVkb2lkcyouCi0gICBBZnRlciBmaW5kaW5nIGEgc2V0IG9mICRrJCByZXByZXNlbnRhdGl2ZSBvYmplY3RzLCB0aGUgJGskIGNsdXN0ZXJzCiAgICBhcmUgY29uc3RydWN0ZWQgYnkgYXNzaWduaW5nIGVhY2ggb2JqZWN0IG9mIHRoZSBkYXRhIHNldCB0byB0aGUKICAgIG5lYXJlc3QgcmVwcmVzZW50YXRpdmUgb2JqZWN0LgotICAgVG8gaWxsdXN0cmF0ZSBhbGdvcml0aG0sIGNvbnNpZGVyIGEgZGF0YSBzZXQgb2YgdGVuIG9iamVjdHMKICAgICgkbiA9IDEwJCkgYW5kIHR3byB2YXJpYWJsZXMgKCRtID0gMiQpLgoKIVtGcm9tIEthdWZtYW4gJiBSb3Vzc2VldXcsIHAuIDY4LTcyXShLYXVmbWFuVGFibGUxLmpwZyl7d2lkdGg9IjUwJSJ9CgotICAgU3VwcG9zZSB0aGUgZGF0YSBzZXQgbXVzdCBiZSBkaXZpZGVkIGludG8gdHdvIGNsdXN0ZXJzICgkayA9IDIkKS4KLSAgIFRoZSBhbGdvcml0aG0gZmlyc3QgY29uc2lkZXJzIHBvc3NpYmxlIGNob2ljZXMgb2YgdHdvIHJlcHJlc2VudGF0aXZlCiAgICBvYmplY3RzLgotICAgVGhlbiwgaXQgY29uc3RydWN0cyB0aGUgY2x1c3RlcnMgYXJvdW5kIHRoZXNlIHJlcHJlc2VudGF0aXZlCiAgICBvYmplY3RzLgoKIVtdKEthdWZtYW5GaWd1cmUxLmpwZyl7d2lkdGg9IjUwJSJ9CgotICAgQXMgYW4gZXhhbXBsZSwgc3VwcG9zZSBvYmplY3RzIDEgYW5kIDUgYXJlIHRoZSBzZWxlY3RlZAogICAgcmVwcmVzZW50YXRpdmUgb2JqZWN0cy4KLSAgIEluIFRhYmxlIDIsIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2UgZnJvbSBlYWNoIG9mIHRoZSBvYmplY3RzIHRvIHRoZQogICAgdHdvIHNlbGVjdGVkIG9iamVjdHMgYXJlIGdpdmVuLCBhcyB3ZWxsIGFzIHRoZSBzbWFsbGVzdCBvZiB0aGVzZSB0d28KICAgIGRpc3NpbWlsYXJpdGllcyBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgcmVwcmVzZW50YXRpdmUgb2JqZWN0LgotICAgVGhlIGF2ZXJhZ2UgZGlzc2ltaWxhcml0eSBpcyA5LjM3LgoKIVtdKEthdWZtYW5UYWJsZTIuanBnKXt3aWR0aD0iNTAlIn0KCi0gICBJbiBUYWJsZSAzLCB0aGUgYXNzaWdubWVudCBpcyBjYXJyaWVkIG91dCBmb3IgdGhlIGNhc2Ugb2JqZWN0cyA0IGFuZAogICAgOCBhcmUgc2VsZWN0ZWQgYXMgcmVwcmVzZW50YXRpdmUgb2JqZWN0cy4KCiFbXShLYXVmbWFuVGFibGUzLmpwZyl7d2lkdGg9IjUwJSJ9CgohW10oS2F1Zm1hbkZpZ3VyZTIuanBnKXt3aWR0aD0iNTAlIn0KCi0gICBUaGUgYXZlcmFnZSBkaXNzaW1pbGFyaXR5IGZvciB0aGUgY2FzZSBvYmplY3RzIDQgYW5kIDggYXJlIHNlbGVjdGVkCiAgICBpcyAyLjMwLCB3aGljaCBpcyBjb25zaWRlcmFibHkgbGVzcyB0aGFuIHRoZSB2YWx1ZSBvZiA5LjM3LCBmb3VuZAogICAgd2hlbiAxIGFuZCA1IHdlcmUgdGhlIHJlcHJlc2VudGF0aXZlIG9iamVjdHMuCi0gICBBbHRlcm5hdGl2ZWx5IGEgUEFNIHByb2dyYW0gY2FuIGJlIHVzZWQgYnkgZW50ZXJpbmcgYSBtYXRyaXggb2YKICAgIGRpc3NpbWlsYXJpdGllcyBiZXR3ZWVuIG9iamVjdHMuCi0gICBUaGUgYWxnb3JpdGhtcyBhcmUgcmF0aGVyIHNvcGhpc3RpY2F0ZWQgYW5kIGFyZSBub3QgZGV0YWlsZWQgaGVyZS4KCmBgYHtyfQp4PWMoMSw1LDUsNSwxMCwyNSwyNSwyNSwyNSwyOSkKeT1jKDQsMSwyLDQsNCw0LDYsNyw4LDcpCmRmPC1kYXRhLmZyYW1lKHgseSkKCmdncGxvdChkZixhZXMoeD14LHk9eSxsYWJlbD1yb3cubmFtZXMoZGYpKSkrZ2VvbV9wb2ludCgpK2dlb21fdGV4dChsYWJlbD1yb3cubmFtZXMoZGYpLG51ZGdlX3ggPSAwLjI1LCBudWRnZV95ID0gMC4yNSwgCiAgICBjaGVja19vdmVybGFwID0gVCkKYGBgCgpgYGB7cn0KCmRpc3QoZGYsdXBwZXI9VCxkaWFnPVQpJT4lCmFzLm1hdHJpeCgpLT5NCmNiaW5kKE1bLGMoMSw1KV0sYXBwbHkoTVssYygxLDUpXSwxLG1pbikpJT4lCmRhdGEuZnJhbWUoKS0+VDIKVDIkdmFsPTAKVDIkdmFsW3doaWNoKFQyWywxXT09YXBwbHkoTVssYygxLDUpXSwxLG1pbikpXTwtMQpUMiR2YWxbd2hpY2goVDJbLDJdPT1hcHBseShNWyxjKDEsNSldLDEsbWluKSldPC01Cm5hbWVzKFQyKTwtYygiRGlzdC4gd3J0IDEiLCJEaXN0LiB3cnQgNSIsIk1pbi4gZGlzdC4iLCJDbG9zZXN0IHJlcC4gb2JqLiIpCgoKa2FibGUoVDIsZGlnaXQgPTIsYWxpZ24gPSAibGNjYyIsYm9va3RhYnMgPSBUUlVFKSU+JQprYWJsZV9jbGFzc2ljKGxhdGV4X29wdGlvbnMgPSAiaG9sZF9wb3NpdGlvbiIpCgoKbWVhbihUMiRgTWluLiBkaXN0LmApCgpgYGAKCmBgYHtyfQoKZGlzdChkZix1cHBlcj1ULGRpYWc9VCklPiUKYXMubWF0cml4KCktPk0KY2JpbmQoTVssYyg0LDgpXSxhcHBseShNWyxjKDQsOCldLDEsbWluKSklPiUKZGF0YS5mcmFtZSgpLT5UMwpUMyR2YWw9MApUMyR2YWxbd2hpY2goVDNbLDFdPT1hcHBseShNWyxjKDQsOCldLDEsbWluKSldPC0xClQzJHZhbFt3aGljaChUM1ssMl09PWFwcGx5KE1bLGMoNCw4KV0sMSxtaW4pKV08LTUKbmFtZXMoVDMpPC1jKCJEaXN0LiB3cnQgNCIsIkRpc3QuIHdydCA4IiwiTWluLiBkaXN0LiIsIkNsb3Nlc3QgcmVwLiBvYmouIikKCgprYWJsZShUMyxkaWdpdCA9MixhbGlnbiA9ICJsY2NjIixib29rdGFicyA9IFRSVUUpJT4lCmthYmxlX2NsYXNzaWMobGF0ZXhfb3B0aW9ucyA9ICJob2xkX3Bvc2l0aW9uIikKCgptZWFuKFQzJGBNaW4uIGRpc3QuYCkKCmBgYAoKYGBge3J9ClBBTTwtcGFtKGRmLDIpClBBTSRtZWRvaWRzClBBTSRpZC5tZWQKUEFNJGNsdXN0ZXJpbmcKUEFNJG9iamVjdGl2ZQpQQU0kaXNvbGF0aW9uCgpgYGAKCiMgJEwkIGFuZCAkTF5cc3RhciQgY2x1c3RlcnMKCiFbXShpc29sYXRlZC5qcGcpe3dpZHRoPSI1MCUifQoKYGBge3IsZWNobyA9IEZBTFNFfQpkaXN0KGRmLHVwcGVyPVQsZGlhZz1UKSU+JQphcy5tYXRyaXgoKS0+TQpjYmluZChNWyxjKDMsOCldLGFwcGx5KE1bLGMoMyw4KV0sMSxtaW4pKSU+JQpkYXRhLmZyYW1lKCktPlQ0ClQ0JHZhbD0wClQ0JHZhbFt3aGljaChUNFssMV09PWFwcGx5KE1bLGMoMyw4KV0sMSxtaW4pKV08LTMKVDQkdmFsW3doaWNoKFQ0WywyXT09YXBwbHkoTVssYygzLDgpXSwxLG1pbikpXTwtOApuYW1lcyhUNCk8LWMoIkRpc3QuIHdydCAzIiwiRGlzdC4gd3J0IDgiLCJNaW4uIGRpc3QuIiwiQ2xvc2VzdCByZXAuIG9iai4iKQoKCmthYmxlKFQ0LGRpZ2l0ID0yLGFsaWduID0gImxjY2MiLGJvb2t0YWJzID0gVFJVRSklPiUKa2FibGVfY2xhc3NpYyhsYXRleF9vcHRpb25zID0gImhvbGRfcG9zaXRpb24iKQoKCm1lYW4oVDQkYE1pbi4gZGlzdC5gKQojIyBGb3IgdGhlIGNsdXN0ZXIgb2Ygb2JqZWN0IDMKTUNsdXM9TVtUNCRgQ2xvc2VzdCByZXAuIG9iai5gPT0zLFQ0JGBDbG9zZXN0IHJlcC4gb2JqLmA9PTNdCgoKTUhDbHVzPU1bVDQkYENsb3Nlc3QgcmVwLiBvYmouYD09OCxUNCRgQ2xvc2VzdCByZXAuIG9iai5gPT0zXQphcHBseShNQ2x1cywyLG1heCkKYXBwbHkoTUhDbHVzLDIsbWluKQoKKGFwcGx5KE1DbHVzLDIsbWF4KSAgCjxhcHBseShNSENsdXMsMixtaW4pICkgCm1heChhcHBseShNQ2x1cywyLG1heCkpCm1pbihhcHBseShNSENsdXMsMixtaW4pKQoKbWF4KGFwcGx5KE1DbHVzLDIsbWF4KSk8bWluKGFwcGx5KE1IQ2x1cywyLG1pbikpCgoKCiMjIEZvciB0aGUgY2x1c3RlciBvZiBvYmplY3QgOApNQ2x1cz1NW1Q0JGBDbG9zZXN0IHJlcC4gb2JqLmA9PTgsVDQkYENsb3Nlc3QgcmVwLiBvYmouYD09OF0KCgpNSENsdXM9TVtUNCRgQ2xvc2VzdCByZXAuIG9iai5gPT0zLFQ0JGBDbG9zZXN0IHJlcC4gb2JqLmA9PThdCmFwcGx5KE1DbHVzLDIsbWF4KQphcHBseShNSENsdXMsMixtaW4pCgooYXBwbHkoTUNsdXMsMixtYXgpICAKPGFwcGx5KE1IQ2x1cywyLG1pbikgKSAKbWF4KGFwcGx5KE1DbHVzLDIsbWF4KSkKbWluKGFwcGx5KE1IQ2x1cywyLG1pbikpCgptYXgoYXBwbHkoTUNsdXMsMixtYXgpKTxtaW4oYXBwbHkoTUhDbHVzLDIsbWluKSkKCgoKCmBgYAoKIyBNdWx0aWRpbWVuc2lvbmFsIHNjYWxpbmcKCi0gICBJdCBpcyBvdGVuIHVzZWQgdG8gcmVwcmVzZW50IHRoZSBjbHVzdGVycyBpbiBhIHR3by1kaW1lbnNpb25hbAogICAgc3BhY2UuCi0gICBMZXQgdGhlIGRhdGEgb2YgZGlzdGFuY2VzIGJldHdlZW4gdGVuIFVTIGNpdGVzIFtzZWUgS1JVU0tBTCwKICAgIEpvc2VwaCBCLiBNdWx0aWRpbWVuc2lvbmFsIHNjYWxpbmcuIFNhZ2UsCiAgICAxOTc4XShodHRwczovL2Jvb2s0eW91Lm9yZy9ib29rLzEwNjM3NjYvNDI0YTBjKSBhbmQgW1RBS0FORSwgWW9zaGlvLgogICAgMTEgYXBwbGljYXRpb25zIG9mIG11bHRpZGltZW5zaW9uYWwgc2NhbGluZyBpbiBwc3ljaG9tZXRyaWNzLgogICAgSGFuZGJvb2sgb2Ygc3RhdGlzdGljcywgMjAwNiwgdm9sLiAyNiwgcHAuCiAgICAzNTktNDAwXShodHRwczovL2Jvb2s0eW91Lm9yZy9ib29rLzUwNTYwOC81OGIyMTQpLgoKIVtdKFRha2FuZWRhdGEuanBnKQoKIVtdKEtydXNrYWxNYXAuanBnKQoKLSAgIFRoZSByb2xlIG9mIE1EUyBpcyB0byBjb25zdHJ1Y3QgYSBtYXAgbGlrZSB0aGUgb25lIGluIHRoZSBmb2xsb3dpbmcKICAgIEZpZ3VyZSBmb3IgdGhvc2Ugd2hvIGRvIG5vdCBrbm93IHRoZSAiZ2VvZ3JhcGh5IiBpbiBjZXJ0YWluIGFyZWFzLgoKIVtdKEtydXNrYWxNRFMuanBnKQoKYGBge3J9CmRmPC1yZWFkLmNzdigiRmx5aW5nTWlsZWFnZS5jc3YiLHNlcCA9ICI7IikKZGYlPiVrYWJsZShib29rdGFicyA9IFRSVUUpJT4lCmthYmxlX2NsYXNzaWMobGF0ZXhfb3B0aW9ucyA9ICJob2xkX3Bvc2l0aW9uIikKCmBgYAoKLSAgIFdlIGRlbm90ZSB0aGlzIG1hdHJpeCBvZiBkaXN0YW5jZXMgKG9yIGRpc3NpbWlsYXJpdGllcykgYnkgJEQkIHdpdGgKICAgIGNvbXBuZW50cyAkZF97aSxpXlxwcmltZX0sXDppLGleXHByaW1lPTFcY2RvdHMgbi4kLgoKLSAgIFRoZSBwdXBycG9zZSBvZiB0aGUgdGhlIGJhc2ljIE1EUyBtZXRob2QgaXMgdG8gYnVpbGQgJG4kIHBvaW50cyBpbgogICAgdGhlICRcbWF0aGJie1J9XnAkIHNwYWNlLCBvciB1c2luZyAkcCQgZGltZW5zaW9ucy4KCi0gICBUaGUgY29vcmRpbmF0ZXMgb2YgdGhlIHBvaW50ICRpJCBhcmUgJCh4X2leMSxcY2RvdHMseF9pXnApJC4gc3VjaCBhcwogICAgdGhlIG1hdHJpeCBvZiBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBwb2ludHMgZml0cyB0aGUgb2JzZXJ2ZWQgbWF0cml4CiAgICBkaXN0YW5jZXMgJEQkLgoKLSAgIFRoZSBNRFMgZGlzdGFuY2UgYmV0d2VlbiBwb2ludHMgYXJlIGFzc3VtZWQgdG8gYmUKICAgICQkXGhhdHtkfV97aSxpXlxwcmltZX09XHNxcnR7XHN1bV97bD0xfV5wKHhfe2leXHByaW1lfV5sLXhfe2l9XmwpXjJ9LiQkCgotICAgS3J1c2thbCdzIFNUUkVTUyBmb3JtdWxhIG9uZSBpcyBnaXZlbiBieQoKJCQKU18xPVxzcXJ0e1xmcmFje1xzdW1fe2ksaV5ccHJpbWV9KFxoYXR7ZH1fe2ksaV5ccHJpbWV9LWRfe2ksaV5ccHJpbWV9KV4yfXtcc3VtX3tpLGleXHByaW1lfWRfe2ksaV5ccHJpbWV9XjJ9fQokJCBcKiBUaGUgbnVtZXJhdG9yIGlzIHNxdWFyZWQgZXJyb3IsIGluZGljYXRpbmcgdGhlIHN1bSBvZiBkaWZmZXJlbmNlcwpiZXR3ZWVuIHRoZSBvYnNlcnZlZCBkaXN0YW5jZSBhbmQgbW9kZWwtZGVyaXZlZCBkaXN0YW5jZS4gVGhlCmRlbm9taW5hdG9yIGlzIG5vcm1hbGl6aW5nIGZhY3Rvci4gXCogV2Ugd2FudCB0byBmaW5kIHZlY3RvcnMgd2hpY2gKcHJvZHVjZSB0aGUgc21hbGxlc3QgcG9zc2libGUgdmFsdWUgZm9yIHN0cmVzcy4KCmBgYHtyfQptZHM8LWRmJT4lc2VsZWN0KDI6MTEpJT4lCmNtZHNjYWxlKCwyKQptZHM8LW1kcyRwb2ludHMlPiUgYXNfdGliYmxlKCkKY29sbmFtZXMobWRzKSA8LSBjKCJEaW0uMSIsICJEaW0uMiIpCmdncGxvdChtZHMsYWVzKHggPSBEaW0uMSx5PURpbS4yKSkrCmdlb21fcG9pbnQoKStnZW9tX3RleHQobGFiZWw9ZGYkWCwsaGp1c3Q9MC41LCB2anVzdD0zLCBzaXplPTIpCgpgYGAK