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

  • It is defined by:

\[ 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

  • It is defined by:

\[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.\]

  • Conversely, we have:

\[\|\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

From Kaufman & Rousseeuw, p. 6-8

  • 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.

Second individual
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.
Coefficient \(s(\mathbf{ x},\mathbf{y})\) \(d(\mathbf{x},\mathbf {y})=1-s(\mathbf{x},\mathbf{y})\)
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

KAUFMAN and ROUSSEEUW, p. 296 KAUFMAN and ROUSSEEUW p. 297

Exercice 10

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.

From: GAN et al. (2007)

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:
  1. Winters: binary, indicates whether the plant may be left in the garden when it freezes.
  2. Shadow: binary, shows whether the plant needs to stand in the shadow.
  3. Tubers (Tubercule): asymmetric binary, distinguishes between plants with tubers and plants that grow in any other way.
  4. Color: nominal, specifies the flower’s color (1=white, 2=yellow, 3= pink, 4=red, 5= blue).
  5. Soil: ordinal, indicates whether the plant grows in dry (1), normal (2), or wet (3) soil.
  6. Preference: ordinal, someone’s preference ranking, going from 1 to 18.
  7. Height: interval scaled, the plant’s height in centimeters.
  8. 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:

Begonia

Genêt

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.
  1. dist() function accepts only numeric data.
  2. get_dist() function [factoextra package] accepts only numeric data. it supports correlation-based distance measures.
  3. 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:
  1. 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.
  2. 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.
  3. 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.

Data

  • 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:

  1. (BR) (BB BS) (BC BH)
  2. (BR) (BS) (BB BC BH)
  3. (BB) (BR BS) (BC BH)
  4. (BB) (BS) (BR BC BH)
  5. (BB BR BS) O (BC BH)
  6. (BB BR) O (BS BC BH)
  7. (BB BR BC) (BS) (BH)
  8. (BB BR) (BS BC) (BH)
  9. (BB BR BH) (BS) (BC)
  10. (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\)).

From Kaufman & Rousseeuw, p. 68-72

  • 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