Aim and how to run this
file
We will solve the example in the supplied slides using two
methods:
- K-means, with two clusters and starting centres at
A and B.
- Hierarchical clustering with single linkage,
followed by a cut that gives two clusters.
The data are included below. Open this file in RStudio and click
Knit to produce an HTML document containing the R
commands, their output, and the plots. You can also run the code chunks
in order.
The clustering and plotting functions come with R. Knitting requires
the rmarkdown package and its dependencies. If necessary,
run the following once in the R console:
install.packages("rmarkdown")
The final answer is {A, B} and {C, D} for both
methods. The sections below explain how each method reaches
that answer.
Enter and plot the
data
The four observations are A = (1, 1), B = (2, 1), C = (4, 3), and D =
(5, 4), exactly as in the slides.
X <- data.frame(
x = c(1, 2, 4, 5),
y = c(1, 1, 3, 4),
row.names = c("A", "B", "C", "D")
)
X
#> x y
#> A 1 1
#> B 2 1
#> C 4 3
#> D 5 4
Each row is one observation. The columns x and
y are the two numerical features used for clustering. A, B,
C, and D are observation names. They are not known class
labels.
This is an unsupervised learning problem: we use the
numerical features to find groups without being given the correct group
for each observation.
plot(
X$x, X$y,
pch = 19, cex = 1.6, col = "#526477",
xlim = c(0, 6), ylim = c(0, 5), asp = 1,
xlab = "x", ylab = "y",
main = "Four observations"
)
text(X$x, X$y, labels = rownames(X), pos = 3, cex = 1.1)
Look at the plot: A and B are close to each other,
and C and D are close to each other.
We use the original coordinates throughout so the distances match the
slides. For real data, consider the units and scales of the variables:
changing a variable’s scale changes its contribution to distance.
Solve the example with
k-means
Choose the same
starting centres as the slides
We choose \(K = 2\) and start
with
\[
c_1 = (1,1) \quad\text{and}\quad c_2 = (2,1).
\]
These are the coordinates of A and B.
initial_centres <- as.matrix(X[c("A", "B"), ])
initial_centres
#> x y
#> A 1 1
#> B 2 1
X[c("A", "B"), ] selects rows A and B and both columns.
The result supplies the actual starting centres to
kmeans().
Run kmeans()
km <- kmeans(
X,
centers = initial_centres,
algorithm = "Lloyd",
iter.max = 100
)
The arguments mean:
X: the numerical data to cluster.
centers = initial_centres: start at A and B; the two
rows specify two clusters.
algorithm = "Lloyd": alternate between assigning
observations to their nearest centre and updating the centres to the
group means, as in the slides.
iter.max = 100: allow up to 100 iterations; this is a
limit, not a request to perform 100 iterations.
R’s default k-means algorithm is Hartigan-Wong. We explicitly select
Lloyd’s algorithm to match the hand calculation. Supplying the starting
centres makes this example reproducible without random initialisation.
See the official
kmeans documentation.
Read the cluster
assignments and centres
#> A B C D
#> 1 1 2 2
data.frame(X, K_means_cluster = km$cluster)
#> x y K_means_cluster
#> A 1 1 1
#> B 2 1 1
#> C 4 3 2
#> D 5 4 2
The assignment is A and B in one cluster, and C and D in the
other.
#> x y
#> 1 1.5 1.0
#> 2 4.5 3.5
#> [1] 2 2
The final centres are
\[
c_1 = \left(\frac{1+2}{2},\frac{1+1}{2}\right) = (1.5,1),
\qquad
c_2 = \left(\frac{4+5}{2},\frac{3+4}{2}\right) = (4.5,3.5).
\]
Each cluster contains two observations. The $ symbol
extracts a named part of the saved result: for example,
km$centers extracts the centres.
(Optional) Plot the
k-means solution
cluster_colours <- c("#0072B2", "#D55E00")
plot(
X$x, X$y,
col = cluster_colours[km$cluster],
pch = 19, cex = 1.6,
xlim = c(0, 6), ylim = c(0, 5), asp = 1,
xlab = "x", ylab = "y",
main = "K-means: two clusters"
)
text(X$x, X$y, labels = rownames(X), pos = 3, cex = 1.1)
points(
km$centers[, "x"], km$centers[, "y"],
pch = 4, cex = 2, lwd = 2,
col = cluster_colours
)
text(
km$centers[, "x"], km$centers[, "y"],
labels = c("c1", "c2"), pos = 1,
col = cluster_colours
)
legend(
"topleft",
legend = c("Cluster 1", "Cluster 2", "Centre"),
col = c(cluster_colours, "black"),
pch = c(19, 19, 4), bty = "n"
)
(Optional) Connect the
R solution to the hand calculations
This section reproduces the assignment and update steps in the
slides. It can be used to explain what the kmeans() call
does.
For an observation \(P=(x,y)\) and a
centre \(c=(a,b)\), the squared
Euclidean distance is
\[
d^2(P,c) = (x-a)^2 + (y-b)^2.
\]
Comparing squared distances gives the same nearest centre as
comparing their square roots.
Round 1: assign
points to the initial centres
d2_c1 <- (X$x - initial_centres[1, "x"])^2 +
(X$y - initial_centres[1, "y"])^2
d2_c2 <- (X$x - initial_centres[2, "x"])^2 +
(X$y - initial_centres[2, "y"])^2
round1 <- data.frame(
Squared_to_c1 = d2_c1,
Squared_to_c2 = d2_c2,
Cluster = ifelse(d2_c1 <= d2_c2, 1, 2),
row.names = rownames(X)
)
round1
#> Squared_to_c1 Squared_to_c2 Cluster
#> A 0 1 1
#> B 1 0 2
#> C 13 8 2
#> D 25 18 2
ifelse(d2_c1 <= d2_c2, 1, 2) assigns a point to
cluster 1 when its first squared distance is smaller, and to cluster 2
otherwise. This code assigns an exact tie to cluster 1; no ties occur in
this example.
The first assignment is {A} and {B, C, D}. In
particular, C joins cluster 2 because \(8 <
13\).
Round 1: update the
centres
colMeans() calculates the mean of each coordinate within
a cluster.
centres_round1 <- rbind(
c1 = colMeans(X[round1$Cluster == 1, , drop = FALSE]),
c2 = colMeans(X[round1$Cluster == 2, , drop = FALSE])
)
centres_round1
#> x y
#> c1 1.00000 1.00000
#> c2 3.66667 2.66667
The updated centres are \(c_1=(1,1)\) and \(c_2=(11/3,8/3)\). The condition
round1$Cluster == 1 selects the observations assigned to
cluster 1. drop = FALSE keeps the selected data in a
two-column table, including when a cluster has just one observation.
Keep the stored values at full precision for the next
calculation.
Round 2: assign
points again
d2_c1 <- (X$x - centres_round1[1, "x"])^2 +
(X$y - centres_round1[1, "y"])^2
d2_c2 <- (X$x - centres_round1[2, "x"])^2 +
(X$y - centres_round1[2, "y"])^2
round2 <- data.frame(
Squared_to_c1 = d2_c1,
Squared_to_c2 = d2_c2,
Cluster = ifelse(d2_c1 <= d2_c2, 1, 2),
row.names = rownames(X)
)
round(round2, 4)
#> Squared_to_c1 Squared_to_c2 Cluster
#> A 0 9.8889 1
#> B 1 5.5556 1
#> C 13 0.2222 2
#> D 25 3.5556 2
The second squared-distance column is \(89/9\), \(50/9\), \(2/9\), and \(32/9\). B changes to cluster
1 because \(1 < 50/9\). The
groups are now {A, B} and {C, D}.
round(round2, 4) rounds the displayed table; it does not
replace the stored, unrounded values.
Round 2: update the
centres
centres_round2 <- rbind(
c1 = colMeans(X[round2$Cluster == 1, , drop = FALSE]),
c2 = colMeans(X[round2$Cluster == 2, , drop = FALSE])
)
centres_round2
#> x y
#> c1 1.5 1.0
#> c2 4.5 3.5
The new centres are (1.5, 1) and (4.5,
3.5), matching the kmeans() solution.
Final check: do any
assignments change?
d2_c1 <- (X$x - centres_round2[1, "x"])^2 +
(X$y - centres_round2[1, "y"])^2
d2_c2 <- (X$x - centres_round2[2, "x"])^2 +
(X$y - centres_round2[2, "y"])^2
final_check <- data.frame(
Squared_to_c1 = d2_c1,
Squared_to_c2 = d2_c2,
Previous_cluster = round2$Cluster,
New_cluster = ifelse(d2_c1 <= d2_c2, 1, 2),
row.names = rownames(X)
)
final_check
#> Squared_to_c1 Squared_to_c2 Previous_cluster New_cluster
#> A 0.25 18.5 1 1
#> B 0.25 12.5 1 1
#> C 10.25 0.5 2 2
#> D 21.25 0.5 2 2
No assignments change. Therefore, recalculating the means would leave
the centres unchanged, and the algorithm stops. There are two
rounds that change the centres, followed by a final assignment
check.
Solve the example with
single-linkage hierarchical clustering
Calculate Euclidean
distances between observations
Hierarchical clustering starts with each observation in its own
cluster. At each step it merges two clusters.
We first calculate the ordinary Euclidean distances between all pairs
of observations:
\[
d(P,Q) = \sqrt{(x_P-x_Q)^2 + (y_P-y_Q)^2}.
\]
d <- dist(X, method = "euclidean")
distance_matrix <- as.matrix(d)
round(distance_matrix, 4)
#> A B C D
#> A 0.0000 1.0000 3.6056 5.0000
#> B 1.0000 0.0000 2.8284 4.2426
#> C 3.6056 2.8284 0.0000 1.4142
#> D 5.0000 4.2426 1.4142 0.0000
For example,
\[
d(A,B)=1,\qquad d(C,D)=\sqrt{2},\qquad d(B,C)=\sqrt{8}.
\]
dist() creates a distance object.
as.matrix() displays it as a full table. The diagonal
entries are zero, and the table is symmetric. These are
Euclidean distances, while the earlier k-means
assignment tables show squared distances. See the official
dist documentation.
Apply single
linkage
The single-linkage distance between two clusters is the distance
between their closest pair of observations, with one
observation in each cluster:
\[
d_{\mathrm{single}}(G_1,G_2)
= \min_{P\in G_1,\ Q\in G_2} d(P,Q).
\]
hc_single <- hclust(d, method = "single")
The argument must be method = "single" to request single
linkage. hclust() uses complete linkage by default. See the
official
hclust documentation.
Understand the three
merges
There are four observations, so there are three merges before
everything belongs to one cluster.
| 1 |
{A} and {B} |
\(1\) |
A and B |
| 2 |
{C} and {D} |
\(\sqrt{2}\approx
1.4142\) |
C and D |
| 3 |
{A, B} and {C, D} |
\(\sqrt{8}\approx
2.8284\) |
B and C |
R stores the merge heights in the fitted object:
data.frame(
Step = 1:3,
Height = hc_single$height
)
#> Step Height
#> 1 1 1.00000
#> 2 2 1.41421
#> 3 3 2.82843
After the first merge, the distances between the remaining clusters
are
\[
\begin{aligned}
d_{\mathrm{single}}(\{A,B\},\{C\}) &=
\min(\sqrt{13},\sqrt{8})=\sqrt{8},\\
d_{\mathrm{single}}(\{A,B\},\{D\}) &= \min(5,\sqrt{18})=\sqrt{18},\\
d(\{C\},\{D\}) &= \sqrt{2}.
\end{aligned}
\]
Thus C and D merge next. For the final merge, examine all four
distances between the two remaining clusters:
between_cluster_distances <- distance_matrix[
c("A", "B"), c("C", "D")
]
round(between_cluster_distances, 4)
#> C D
#> A 3.6056 5.0000
#> B 2.8284 4.2426
min(between_cluster_distances)
#> [1] 2.82843
The minimum is the distance between B and C, \(\sqrt{8}\). Single linkage uses the
nearest pair across the two clusters. It does not calculate a
distance between their means.
Draw the
dendrogram
plot(
hc_single,
hang = -1,
main = "Single-linkage hierarchical clustering",
xlab = "Observations",
ylab = "Merge height (Euclidean distance)",
sub = ""
)
rect.hclust(hc_single, k = 2, border = cluster_colours)
abline(h = 2, lty = 2, col = "#666666")
legend(
"topleft", legend = "Cut at height 2",
lty = 2, col = "#666666", bty = "n", cex = 0.9
)
Read the dendrogram from the bottom upward. The height of each
horizontal joining line is the distance at which that merge occurs. With
hang = -1, the observation labels share a common
baseline.
The dashed line is at height 2. Since \(\sqrt{2}<2<\sqrt{8}\), the two lower
merges have occurred and the final merge has not. The result is two
clusters.
Cut the tree into two
clusters
hc_groups <- cutree(hc_single, k = 2)
hc_groups
#> A B C D
#> 1 1 2 2
data.frame(X, Single_linkage_cluster = hc_groups)
#> x y Single_linkage_cluster
#> A 1 1 1
#> B 2 1 1
#> C 4 3 2
#> D 5 4 2
We can also specify the cut height:
#> A B C D
#> 1 1 2 2
Both commands give {A, B} and {C, D}.
hclust() builds the full hierarchy; cutree()
extracts a partition with a chosen number of groups or a chosen cut
height. See the official
cutree documentation.
Compare the two
methods
Compare
memberships
comparison <- data.frame(
X,
K_means = km$cluster,
Single_linkage = hc_groups
)
comparison
#> x y K_means Single_linkage
#> A 1 1 1 1
#> B 2 1 1 1
#> C 4 3 2 2
#> D 5 4 2 2
table(K_means = km$cluster, Single_linkage = hc_groups)
#> Single_linkage
#> K_means 1 2
#> 1 2 0
#> 2 0 2
The table shows the same two pairs for this example: A with B, and C
with D. The cross-tabulation counts how many observations belong to each
combination of clusters.
Cluster numbers are names. The assignments
(1, 1, 2, 2) and (2, 2, 1, 1) describe the
same grouping. Agreement concerns which observations stay together, even
when the numerical labels are different.
Plot both
partitions
old_par <- par(mfrow = c(1, 2), mar = c(4, 4, 3, 1))
plot(
X$x, X$y,
col = cluster_colours[km$cluster], pch = 19, cex = 1.6,
xlim = c(0, 6), ylim = c(0, 5), asp = 1,
xlab = "x", ylab = "y", main = "K-means"
)
text(X$x, X$y, labels = rownames(X), pos = 3)
points(
km$centers[, "x"], km$centers[, "y"],
pch = 4, cex = 1.8, lwd = 2, col = cluster_colours
)
plot(
X$x, X$y,
col = cluster_colours[hc_groups], pch = 19, cex = 1.6,
xlim = c(0, 6), ylim = c(0, 5), asp = 1,
xlab = "x", ylab = "y", main = "Single linkage: cut at K = 2"
)
text(X$x, X$y, labels = rownames(X), pos = 3)
The crosses in the left panel are k-means centres. The right panel
shows the partition obtained by cutting the hierarchical tree.
What is different
about the methods?
| Starting point |
Two supplied centres, A and B |
Each observation in its own cluster |
| Main step |
Assign to nearest centre, then update means |
Merge the clusters with the smallest single-linkage
distance |
| Distance used in the step |
Observation to centre |
Closest pair of observations across clusters |
| Number of clusters |
Choose \(K=2\) before
fitting |
Build the tree, then cut it into two clusters |
| Main result |
Cluster assignments and centres |
A hierarchy, represented by a dendrogram |
These methods give the same partition for this small example. They
can produce different partitions for other data. This example does not
establish that two clusters are optimal for every problem.
Optional: use the
alternative start from the slides
The slides also ask what happens when the starting centres are A and
D.
alternative_centres <- as.matrix(X[c("A", "D"), ])
km_AD <- kmeans(
X,
centers = alternative_centres,
algorithm = "Lloyd",
iter.max = 100
)
km_AD$cluster
#> A B C D
#> 1 1 2 2
#> x y
#> 1 1.5 1.0
#> 2 4.5 3.5
#> [1] 1.5
This start produces the same two groups, the same centres, and total
WSS 1.5. The initial assignment already gives {A, B} and {C, D}; one
centre update reaches the final centres, followed by a check that the
assignments remain unchanged. Different starting centres can affect
k-means results on other datasets.
Sources and
reproducibility
- Example data and k-means hand calculations: Adel
M., K-means by hand, supplied file
KMeans_By_Hand-1.pdf, slides 2-13.
- R function references: kmeans,
dist,
hclust,
and cutree.
All calculations and figures in executable chunks are generated from
the four observations entered at the beginning. No external data files
are required. Values in the explanatory text refer to this fixed
four-point example.
To render from the R console after saving this file in your current
working directory:
rmarkdown::render("KMeans_and_Single_Linkage.Rmd")
The following records the R version used when you knit the
document:
#> [1] "R version 4.4.1 (2024-06-14 ucrt)"
LS0tDQp0aXRsZTogIkstbWVhbnMgYW5kIFNpbmdsZS1MaW5rYWdlIEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nIg0Kc3VidGl0bGU6ICJUaGUgZm91ciBvYnNlcnZhdGlvbiBleGFtcGxlIGZyb20gSy1tZWFucyBieSBoYW5kIg0KaW5zdGl0dXRlOiAiU0RVRkUgKyBEYWxob3VzaWUgVW5pdmVyc2l0eSwgTWF0aCAmIFN0YXRzIg0KYXV0aG9yOiAiQWRlbCBNLiINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0aGVtZTogZmxhdGx5DQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KICAgIHNlbGZfY29udGFpbmVkOiB0cnVlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICBlY2hvID0gVFJVRSwNCiAgbWVzc2FnZSA9IEZBTFNFLA0KICB3YXJuaW5nID0gVFJVRSwNCiAgZXJyb3IgPSBGQUxTRSwNCiAgY29tbWVudCA9ICIjPiIsDQogIGZpZy53aWR0aCA9IDYuNSwNCiAgZmlnLmhlaWdodCA9IDUsDQogIGZpZy5hbGlnbiA9ICJjZW50ZXIiLA0KICBkcGkgPSAxMjANCikNCm9wdGlvbnMod2lkdGggPSA4MCwgZGlnaXRzID0gNikNCmBgYA0KDQojIEFpbSBhbmQgaG93IHRvIHJ1biB0aGlzIGZpbGUNCg0KV2Ugd2lsbCBzb2x2ZSB0aGUgZXhhbXBsZSBpbiB0aGUgc3VwcGxpZWQgc2xpZGVzIHVzaW5nIHR3byBtZXRob2RzOg0KDQoxLiAqKkstbWVhbnMqKiwgd2l0aCB0d28gY2x1c3RlcnMgYW5kIHN0YXJ0aW5nIGNlbnRyZXMgYXQgQSBhbmQgQi4NCjIuICoqSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgd2l0aCBzaW5nbGUgbGlua2FnZSoqLCBmb2xsb3dlZCBieSBhIGN1dCB0aGF0IGdpdmVzIHR3byBjbHVzdGVycy4NCg0KVGhlIGRhdGEgYXJlIGluY2x1ZGVkIGJlbG93LiBPcGVuIHRoaXMgZmlsZSBpbiBSU3R1ZGlvIGFuZCBjbGljayAqKktuaXQqKiB0byBwcm9kdWNlIGFuIEhUTUwgZG9jdW1lbnQgY29udGFpbmluZyB0aGUgUiBjb21tYW5kcywgdGhlaXIgb3V0cHV0LCBhbmQgdGhlIHBsb3RzLiBZb3UgY2FuIGFsc28gcnVuIHRoZSBjb2RlIGNodW5rcyBpbiBvcmRlci4NCg0KVGhlIGNsdXN0ZXJpbmcgYW5kIHBsb3R0aW5nIGZ1bmN0aW9ucyBjb21lIHdpdGggUi4gS25pdHRpbmcgcmVxdWlyZXMgdGhlIGBybWFya2Rvd25gIHBhY2thZ2UgYW5kIGl0cyBkZXBlbmRlbmNpZXMuIElmIG5lY2Vzc2FyeSwgcnVuIHRoZSBmb2xsb3dpbmcgKipvbmNlIGluIHRoZSBSIGNvbnNvbGUqKjoNCg0KYGBge3IgaW5zdGFsbC1vbmNlLCBldmFsPUZBTFNFfQ0KaW5zdGFsbC5wYWNrYWdlcygicm1hcmtkb3duIikNCmBgYA0KDQpUaGUgZmluYWwgYW5zd2VyIGlzICoqe0EsIEJ9IGFuZCB7QywgRH0gZm9yIGJvdGggbWV0aG9kcyoqLiBUaGUgc2VjdGlvbnMgYmVsb3cgZXhwbGFpbiBob3cgZWFjaCBtZXRob2QgcmVhY2hlcyB0aGF0IGFuc3dlci4NCg0KIyBFbnRlciBhbmQgcGxvdCB0aGUgZGF0YQ0KDQpUaGUgZm91ciBvYnNlcnZhdGlvbnMgYXJlIEEgPSAoMSwgMSksIEIgPSAoMiwgMSksIEMgPSAoNCwgMyksIGFuZCBEID0gKDUsIDQpLCBleGFjdGx5IGFzIGluIHRoZSBzbGlkZXMuDQoNCmBgYHtyIGVudGVyLWRhdGF9DQpYIDwtIGRhdGEuZnJhbWUoDQogIHggPSBjKDEsIDIsIDQsIDUpLA0KICB5ID0gYygxLCAxLCAzLCA0KSwNCiAgcm93Lm5hbWVzID0gYygiQSIsICJCIiwgIkMiLCAiRCIpDQopDQoNClgNCmBgYA0KDQpFYWNoIHJvdyBpcyBvbmUgb2JzZXJ2YXRpb24uIFRoZSBjb2x1bW5zIGB4YCBhbmQgYHlgIGFyZSB0aGUgdHdvIG51bWVyaWNhbCBmZWF0dXJlcyB1c2VkIGZvciBjbHVzdGVyaW5nLiBBLCBCLCBDLCBhbmQgRCBhcmUgb2JzZXJ2YXRpb24gbmFtZXMuIFRoZXkgYXJlICoqbm90IGtub3duIGNsYXNzIGxhYmVscyoqLg0KDQpUaGlzIGlzIGFuICoqdW5zdXBlcnZpc2VkIGxlYXJuaW5nKiogcHJvYmxlbTogd2UgdXNlIHRoZSBudW1lcmljYWwgZmVhdHVyZXMgdG8gZmluZCBncm91cHMgd2l0aG91dCBiZWluZyBnaXZlbiB0aGUgY29ycmVjdCBncm91cCBmb3IgZWFjaCBvYnNlcnZhdGlvbi4NCg0KYGBge3IgcGxvdC1vcmlnaW5hbC1kYXRhLCBmaWcuY2FwPSJUaGUgb3JpZ2luYWwgb2JzZXJ2YXRpb25zLCBiZWZvcmUgYXNzaWduaW5nIGFueSBjbHVzdGVycy4ifQ0KcGxvdCgNCiAgWCR4LCBYJHksDQogIHBjaCA9IDE5LCBjZXggPSAxLjYsIGNvbCA9ICIjNTI2NDc3IiwNCiAgeGxpbSA9IGMoMCwgNiksIHlsaW0gPSBjKDAsIDUpLCBhc3AgPSAxLA0KICB4bGFiID0gIngiLCB5bGFiID0gInkiLA0KICBtYWluID0gIkZvdXIgb2JzZXJ2YXRpb25zIg0KKQ0KdGV4dChYJHgsIFgkeSwgbGFiZWxzID0gcm93bmFtZXMoWCksIHBvcyA9IDMsIGNleCA9IDEuMSkNCmBgYA0KDQoqKkxvb2sgYXQgdGhlIHBsb3Q6KiogQSBhbmQgQiBhcmUgY2xvc2UgdG8gZWFjaCBvdGhlciwgYW5kIEMgYW5kIEQgYXJlIGNsb3NlIHRvIGVhY2ggb3RoZXIuDQoNCldlIHVzZSB0aGUgb3JpZ2luYWwgY29vcmRpbmF0ZXMgdGhyb3VnaG91dCBzbyB0aGUgZGlzdGFuY2VzIG1hdGNoIHRoZSBzbGlkZXMuIEZvciByZWFsIGRhdGEsIGNvbnNpZGVyIHRoZSB1bml0cyBhbmQgc2NhbGVzIG9mIHRoZSB2YXJpYWJsZXM6IGNoYW5naW5nIGEgdmFyaWFibGUncyBzY2FsZSBjaGFuZ2VzIGl0cyBjb250cmlidXRpb24gdG8gZGlzdGFuY2UuDQoNCiMgU29sdmUgdGhlIGV4YW1wbGUgd2l0aCBrLW1lYW5zDQoNCiMjIENob29zZSB0aGUgc2FtZSBzdGFydGluZyBjZW50cmVzIGFzIHRoZSBzbGlkZXMNCg0KV2UgY2hvb3NlICRLID0gMiQgYW5kIHN0YXJ0IHdpdGgNCg0KJCQNCmNfMSA9ICgxLDEpIFxxdWFkXHRleHR7YW5kfVxxdWFkIGNfMiA9ICgyLDEpLg0KJCQNCg0KVGhlc2UgYXJlIHRoZSBjb29yZGluYXRlcyBvZiBBIGFuZCBCLg0KDQpgYGB7ciBzdGFydGluZy1jZW50cmVzfQ0KaW5pdGlhbF9jZW50cmVzIDwtIGFzLm1hdHJpeChYW2MoIkEiLCAiQiIpLCBdKQ0KaW5pdGlhbF9jZW50cmVzDQpgYGANCg0KYFhbYygiQSIsICJCIiksIF1gIHNlbGVjdHMgcm93cyBBIGFuZCBCIGFuZCBib3RoIGNvbHVtbnMuIFRoZSByZXN1bHQgc3VwcGxpZXMgdGhlIGFjdHVhbCBzdGFydGluZyBjZW50cmVzIHRvIGBrbWVhbnMoKWAuDQoNCiMjIFJ1biBrbWVhbnMoKQ0KDQpgYGB7ciBmaXQta21lYW5zfQ0Ka20gPC0ga21lYW5zKA0KICBYLA0KICBjZW50ZXJzID0gaW5pdGlhbF9jZW50cmVzLA0KICBhbGdvcml0aG0gPSAiTGxveWQiLA0KICBpdGVyLm1heCA9IDEwMA0KKQ0KYGBgDQoNClRoZSBhcmd1bWVudHMgbWVhbjoNCg0KLSBgWGA6IHRoZSBudW1lcmljYWwgZGF0YSB0byBjbHVzdGVyLg0KLSBgY2VudGVycyA9IGluaXRpYWxfY2VudHJlc2A6IHN0YXJ0IGF0IEEgYW5kIEI7IHRoZSB0d28gcm93cyBzcGVjaWZ5IHR3byBjbHVzdGVycy4NCi0gYGFsZ29yaXRobSA9ICJMbG95ZCJgOiBhbHRlcm5hdGUgYmV0d2VlbiBhc3NpZ25pbmcgb2JzZXJ2YXRpb25zIHRvIHRoZWlyIG5lYXJlc3QgY2VudHJlIGFuZCB1cGRhdGluZyB0aGUgY2VudHJlcyB0byB0aGUgZ3JvdXAgbWVhbnMsIGFzIGluIHRoZSBzbGlkZXMuDQotIGBpdGVyLm1heCA9IDEwMGA6IGFsbG93IHVwIHRvIDEwMCBpdGVyYXRpb25zOyB0aGlzIGlzIGEgbGltaXQsIG5vdCBhIHJlcXVlc3QgdG8gcGVyZm9ybSAxMDAgaXRlcmF0aW9ucy4NCg0KUidzIGRlZmF1bHQgay1tZWFucyBhbGdvcml0aG0gaXMgSGFydGlnYW4tV29uZy4gV2UgZXhwbGljaXRseSBzZWxlY3QgTGxveWQncyBhbGdvcml0aG0gdG8gbWF0Y2ggdGhlIGhhbmQgY2FsY3VsYXRpb24uIFN1cHBseWluZyB0aGUgc3RhcnRpbmcgY2VudHJlcyBtYWtlcyB0aGlzIGV4YW1wbGUgcmVwcm9kdWNpYmxlIHdpdGhvdXQgcmFuZG9tIGluaXRpYWxpc2F0aW9uLiBTZWUgdGhlIFtvZmZpY2lhbCBrbWVhbnMgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9zdGF0LmV0aHouY2gvUi1tYW51YWwvUi1kZXZlbC9saWJyYXJ5L3N0YXRzL2h0bWwva21lYW5zLmh0bWwpLg0KDQojIyBSZWFkIHRoZSBjbHVzdGVyIGFzc2lnbm1lbnRzIGFuZCBjZW50cmVzDQoNCmBgYHtyIGttZWFucy1hc3NpZ25tZW50c30NCmttJGNsdXN0ZXINCg0KZGF0YS5mcmFtZShYLCBLX21lYW5zX2NsdXN0ZXIgPSBrbSRjbHVzdGVyKQ0KYGBgDQoNClRoZSBhc3NpZ25tZW50IGlzIEEgYW5kIEIgaW4gb25lIGNsdXN0ZXIsIGFuZCBDIGFuZCBEIGluIHRoZSBvdGhlci4NCg0KYGBge3Iga21lYW5zLWNlbnRyZXN9DQprbSRjZW50ZXJzDQprbSRzaXplDQpgYGANCg0KVGhlIGZpbmFsIGNlbnRyZXMgYXJlDQoNCiQkDQpjXzEgPSBcbGVmdChcZnJhY3sxKzJ9ezJ9LFxmcmFjezErMX17Mn1ccmlnaHQpID0gKDEuNSwxKSwNClxxcXVhZA0KY18yID0gXGxlZnQoXGZyYWN7NCs1fXsyfSxcZnJhY3szKzR9ezJ9XHJpZ2h0KSA9ICg0LjUsMy41KS4NCiQkDQoNCkVhY2ggY2x1c3RlciBjb250YWlucyB0d28gb2JzZXJ2YXRpb25zLiBUaGUgYCRgIHN5bWJvbCBleHRyYWN0cyBhIG5hbWVkIHBhcnQgb2YgdGhlIHNhdmVkIHJlc3VsdDogZm9yIGV4YW1wbGUsIGBrbSRjZW50ZXJzYCBleHRyYWN0cyB0aGUgY2VudHJlcy4NCg0KDQojIyAoT3B0aW9uYWwpIFBsb3QgdGhlIGstbWVhbnMgc29sdXRpb24NCg0KYGBge3IgcGxvdC1rbWVhbnMsIGZpZy5jYXA9IkstbWVhbnMgYXNzaWducyBBIGFuZCBCIHRvIG9uZSBjbHVzdGVyIGFuZCBDIGFuZCBEIHRvIHRoZSBvdGhlci4gQ3Jvc3NlcyBzaG93IHRoZSBmaW5hbCBjZW50cmVzLiJ9DQpjbHVzdGVyX2NvbG91cnMgPC0gYygiIzAwNzJCMiIsICIjRDU1RTAwIikNCg0KcGxvdCgNCiAgWCR4LCBYJHksDQogIGNvbCA9IGNsdXN0ZXJfY29sb3Vyc1trbSRjbHVzdGVyXSwNCiAgcGNoID0gMTksIGNleCA9IDEuNiwNCiAgeGxpbSA9IGMoMCwgNiksIHlsaW0gPSBjKDAsIDUpLCBhc3AgPSAxLA0KICB4bGFiID0gIngiLCB5bGFiID0gInkiLA0KICBtYWluID0gIkstbWVhbnM6IHR3byBjbHVzdGVycyINCikNCnRleHQoWCR4LCBYJHksIGxhYmVscyA9IHJvd25hbWVzKFgpLCBwb3MgPSAzLCBjZXggPSAxLjEpDQpwb2ludHMoDQogIGttJGNlbnRlcnNbLCAieCJdLCBrbSRjZW50ZXJzWywgInkiXSwNCiAgcGNoID0gNCwgY2V4ID0gMiwgbHdkID0gMiwNCiAgY29sID0gY2x1c3Rlcl9jb2xvdXJzDQopDQp0ZXh0KA0KICBrbSRjZW50ZXJzWywgIngiXSwga20kY2VudGVyc1ssICJ5Il0sDQogIGxhYmVscyA9IGMoImMxIiwgImMyIiksIHBvcyA9IDEsDQogIGNvbCA9IGNsdXN0ZXJfY29sb3Vycw0KKQ0KbGVnZW5kKA0KICAidG9wbGVmdCIsDQogIGxlZ2VuZCA9IGMoIkNsdXN0ZXIgMSIsICJDbHVzdGVyIDIiLCAiQ2VudHJlIiksDQogIGNvbCA9IGMoY2x1c3Rlcl9jb2xvdXJzLCAiYmxhY2siKSwNCiAgcGNoID0gYygxOSwgMTksIDQpLCBidHkgPSAibiINCikNCmBgYA0KDQojIChPcHRpb25hbCkgQ29ubmVjdCB0aGUgUiBzb2x1dGlvbiB0byB0aGUgaGFuZCBjYWxjdWxhdGlvbnMNCg0KVGhpcyBzZWN0aW9uIHJlcHJvZHVjZXMgdGhlIGFzc2lnbm1lbnQgYW5kIHVwZGF0ZSBzdGVwcyBpbiB0aGUgc2xpZGVzLiBJdCBjYW4gYmUgdXNlZCB0byBleHBsYWluIHdoYXQgdGhlIGBrbWVhbnMoKWAgY2FsbCBkb2VzLg0KDQpGb3IgYW4gb2JzZXJ2YXRpb24gJFA9KHgseSkkIGFuZCBhIGNlbnRyZSAkYz0oYSxiKSQsIHRoZSBzcXVhcmVkIEV1Y2xpZGVhbiBkaXN0YW5jZSBpcw0KDQokJA0KZF4yKFAsYykgPSAoeC1hKV4yICsgKHktYileMi4NCiQkDQoNCkNvbXBhcmluZyBzcXVhcmVkIGRpc3RhbmNlcyBnaXZlcyB0aGUgc2FtZSBuZWFyZXN0IGNlbnRyZSBhcyBjb21wYXJpbmcgdGhlaXIgc3F1YXJlIHJvb3RzLg0KDQojIyBSb3VuZCAxOiBhc3NpZ24gcG9pbnRzIHRvIHRoZSBpbml0aWFsIGNlbnRyZXMNCg0KYGBge3Igcm91bmQxLWFzc2lnbn0NCmQyX2MxIDwtIChYJHggLSBpbml0aWFsX2NlbnRyZXNbMSwgIngiXSleMiArDQogICAgICAgICAoWCR5IC0gaW5pdGlhbF9jZW50cmVzWzEsICJ5Il0pXjINCg0KZDJfYzIgPC0gKFgkeCAtIGluaXRpYWxfY2VudHJlc1syLCAieCJdKV4yICsNCiAgICAgICAgIChYJHkgLSBpbml0aWFsX2NlbnRyZXNbMiwgInkiXSleMg0KDQpyb3VuZDEgPC0gZGF0YS5mcmFtZSgNCiAgU3F1YXJlZF90b19jMSA9IGQyX2MxLA0KICBTcXVhcmVkX3RvX2MyID0gZDJfYzIsDQogIENsdXN0ZXIgPSBpZmVsc2UoZDJfYzEgPD0gZDJfYzIsIDEsIDIpLA0KICByb3cubmFtZXMgPSByb3duYW1lcyhYKQ0KKQ0KDQpyb3VuZDENCmBgYA0KDQpgaWZlbHNlKGQyX2MxIDw9IGQyX2MyLCAxLCAyKWAgYXNzaWducyBhIHBvaW50IHRvIGNsdXN0ZXIgMSB3aGVuIGl0cyBmaXJzdCBzcXVhcmVkIGRpc3RhbmNlIGlzIHNtYWxsZXIsIGFuZCB0byBjbHVzdGVyIDIgb3RoZXJ3aXNlLiBUaGlzIGNvZGUgYXNzaWducyBhbiBleGFjdCB0aWUgdG8gY2x1c3RlciAxOyBubyB0aWVzIG9jY3VyIGluIHRoaXMgZXhhbXBsZS4NCg0KVGhlIGZpcnN0IGFzc2lnbm1lbnQgaXMgKip7QX0gYW5kIHtCLCBDLCBEfSoqLiBJbiBwYXJ0aWN1bGFyLCBDIGpvaW5zIGNsdXN0ZXIgMiBiZWNhdXNlICQ4IDwgMTMkLg0KDQojIyBSb3VuZCAxOiB1cGRhdGUgdGhlIGNlbnRyZXMNCg0KYGNvbE1lYW5zKClgIGNhbGN1bGF0ZXMgdGhlIG1lYW4gb2YgZWFjaCBjb29yZGluYXRlIHdpdGhpbiBhIGNsdXN0ZXIuDQoNCmBgYHtyIHJvdW5kMS11cGRhdGV9DQpjZW50cmVzX3JvdW5kMSA8LSByYmluZCgNCiAgYzEgPSBjb2xNZWFucyhYW3JvdW5kMSRDbHVzdGVyID09IDEsICwgZHJvcCA9IEZBTFNFXSksDQogIGMyID0gY29sTWVhbnMoWFtyb3VuZDEkQ2x1c3RlciA9PSAyLCAsIGRyb3AgPSBGQUxTRV0pDQopDQoNCmNlbnRyZXNfcm91bmQxDQpgYGANCg0KVGhlIHVwZGF0ZWQgY2VudHJlcyBhcmUgJGNfMT0oMSwxKSQgYW5kICRjXzI9KDExLzMsOC8zKSQuIFRoZSBjb25kaXRpb24gYHJvdW5kMSRDbHVzdGVyID09IDFgIHNlbGVjdHMgdGhlIG9ic2VydmF0aW9ucyBhc3NpZ25lZCB0byBjbHVzdGVyIDEuIGBkcm9wID0gRkFMU0VgIGtlZXBzIHRoZSBzZWxlY3RlZCBkYXRhIGluIGEgdHdvLWNvbHVtbiB0YWJsZSwgaW5jbHVkaW5nIHdoZW4gYSBjbHVzdGVyIGhhcyBqdXN0IG9uZSBvYnNlcnZhdGlvbi4NCg0KS2VlcCB0aGUgc3RvcmVkIHZhbHVlcyBhdCBmdWxsIHByZWNpc2lvbiBmb3IgdGhlIG5leHQgY2FsY3VsYXRpb24uDQoNCiMjIFJvdW5kIDI6IGFzc2lnbiBwb2ludHMgYWdhaW4NCg0KYGBge3Igcm91bmQyLWFzc2lnbn0NCmQyX2MxIDwtIChYJHggLSBjZW50cmVzX3JvdW5kMVsxLCAieCJdKV4yICsNCiAgICAgICAgIChYJHkgLSBjZW50cmVzX3JvdW5kMVsxLCAieSJdKV4yDQoNCmQyX2MyIDwtIChYJHggLSBjZW50cmVzX3JvdW5kMVsyLCAieCJdKV4yICsNCiAgICAgICAgIChYJHkgLSBjZW50cmVzX3JvdW5kMVsyLCAieSJdKV4yDQoNCnJvdW5kMiA8LSBkYXRhLmZyYW1lKA0KICBTcXVhcmVkX3RvX2MxID0gZDJfYzEsDQogIFNxdWFyZWRfdG9fYzIgPSBkMl9jMiwNCiAgQ2x1c3RlciA9IGlmZWxzZShkMl9jMSA8PSBkMl9jMiwgMSwgMiksDQogIHJvdy5uYW1lcyA9IHJvd25hbWVzKFgpDQopDQoNCnJvdW5kKHJvdW5kMiwgNCkNCmBgYA0KDQpUaGUgc2Vjb25kIHNxdWFyZWQtZGlzdGFuY2UgY29sdW1uIGlzICQ4OS85JCwgJDUwLzkkLCAkMi85JCwgYW5kICQzMi85JC4gKipCIGNoYW5nZXMgdG8gY2x1c3RlciAxKiogYmVjYXVzZSAkMSA8IDUwLzkkLiBUaGUgZ3JvdXBzIGFyZSBub3cgKip7QSwgQn0gYW5kIHtDLCBEfSoqLg0KDQpgcm91bmQocm91bmQyLCA0KWAgcm91bmRzIHRoZSBkaXNwbGF5ZWQgdGFibGU7IGl0IGRvZXMgbm90IHJlcGxhY2UgdGhlIHN0b3JlZCwgdW5yb3VuZGVkIHZhbHVlcy4NCg0KIyMgUm91bmQgMjogdXBkYXRlIHRoZSBjZW50cmVzDQoNCmBgYHtyIHJvdW5kMi11cGRhdGV9DQpjZW50cmVzX3JvdW5kMiA8LSByYmluZCgNCiAgYzEgPSBjb2xNZWFucyhYW3JvdW5kMiRDbHVzdGVyID09IDEsICwgZHJvcCA9IEZBTFNFXSksDQogIGMyID0gY29sTWVhbnMoWFtyb3VuZDIkQ2x1c3RlciA9PSAyLCAsIGRyb3AgPSBGQUxTRV0pDQopDQoNCmNlbnRyZXNfcm91bmQyDQpgYGANCg0KVGhlIG5ldyBjZW50cmVzIGFyZSAqKigxLjUsIDEpKiogYW5kICoqKDQuNSwgMy41KSoqLCBtYXRjaGluZyB0aGUgYGttZWFucygpYCBzb2x1dGlvbi4NCg0KIyMgRmluYWwgY2hlY2s6IGRvIGFueSBhc3NpZ25tZW50cyBjaGFuZ2U/DQoNCmBgYHtyIGZpbmFsLWFzc2lnbm1lbnQtY2hlY2t9DQpkMl9jMSA8LSAoWCR4IC0gY2VudHJlc19yb3VuZDJbMSwgIngiXSleMiArDQogICAgICAgICAoWCR5IC0gY2VudHJlc19yb3VuZDJbMSwgInkiXSleMg0KDQpkMl9jMiA8LSAoWCR4IC0gY2VudHJlc19yb3VuZDJbMiwgIngiXSleMiArDQogICAgICAgICAoWCR5IC0gY2VudHJlc19yb3VuZDJbMiwgInkiXSleMg0KDQpmaW5hbF9jaGVjayA8LSBkYXRhLmZyYW1lKA0KICBTcXVhcmVkX3RvX2MxID0gZDJfYzEsDQogIFNxdWFyZWRfdG9fYzIgPSBkMl9jMiwNCiAgUHJldmlvdXNfY2x1c3RlciA9IHJvdW5kMiRDbHVzdGVyLA0KICBOZXdfY2x1c3RlciA9IGlmZWxzZShkMl9jMSA8PSBkMl9jMiwgMSwgMiksDQogIHJvdy5uYW1lcyA9IHJvd25hbWVzKFgpDQopDQoNCmZpbmFsX2NoZWNrDQpgYGANCg0KTm8gYXNzaWdubWVudHMgY2hhbmdlLiBUaGVyZWZvcmUsIHJlY2FsY3VsYXRpbmcgdGhlIG1lYW5zIHdvdWxkIGxlYXZlIHRoZSBjZW50cmVzIHVuY2hhbmdlZCwgYW5kIHRoZSBhbGdvcml0aG0gc3RvcHMuIFRoZXJlIGFyZSAqKnR3byByb3VuZHMgdGhhdCBjaGFuZ2UgdGhlIGNlbnRyZXMsIGZvbGxvd2VkIGJ5IGEgZmluYWwgYXNzaWdubWVudCBjaGVjayoqLg0KDQojIFNvbHZlIHRoZSBleGFtcGxlIHdpdGggc2luZ2xlLWxpbmthZ2UgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcNCg0KIyMgQ2FsY3VsYXRlIEV1Y2xpZGVhbiBkaXN0YW5jZXMgYmV0d2VlbiBvYnNlcnZhdGlvbnMNCg0KSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgc3RhcnRzIHdpdGggZWFjaCBvYnNlcnZhdGlvbiBpbiBpdHMgb3duIGNsdXN0ZXIuIEF0IGVhY2ggc3RlcCBpdCBtZXJnZXMgdHdvIGNsdXN0ZXJzLg0KDQpXZSBmaXJzdCBjYWxjdWxhdGUgdGhlIG9yZGluYXJ5IEV1Y2xpZGVhbiBkaXN0YW5jZXMgYmV0d2VlbiBhbGwgcGFpcnMgb2Ygb2JzZXJ2YXRpb25zOg0KDQokJA0KZChQLFEpID0gXHNxcnR7KHhfUC14X1EpXjIgKyAoeV9QLXlfUSleMn0uDQokJA0KDQpgYGB7ciBwYWlyd2lzZS1kaXN0YW5jZXN9DQpkIDwtIGRpc3QoWCwgbWV0aG9kID0gImV1Y2xpZGVhbiIpDQpkaXN0YW5jZV9tYXRyaXggPC0gYXMubWF0cml4KGQpDQoNCnJvdW5kKGRpc3RhbmNlX21hdHJpeCwgNCkNCmBgYA0KDQpGb3IgZXhhbXBsZSwNCg0KJCQNCmQoQSxCKT0xLFxxcXVhZCBkKEMsRCk9XHNxcnR7Mn0sXHFxdWFkIGQoQixDKT1cc3FydHs4fS4NCiQkDQoNCmBkaXN0KClgIGNyZWF0ZXMgYSBkaXN0YW5jZSBvYmplY3QuIGBhcy5tYXRyaXgoKWAgZGlzcGxheXMgaXQgYXMgYSBmdWxsIHRhYmxlLiBUaGUgZGlhZ29uYWwgZW50cmllcyBhcmUgemVybywgYW5kIHRoZSB0YWJsZSBpcyBzeW1tZXRyaWMuIFRoZXNlIGFyZSAqKkV1Y2xpZGVhbiBkaXN0YW5jZXMqKiwgd2hpbGUgdGhlIGVhcmxpZXIgay1tZWFucyBhc3NpZ25tZW50IHRhYmxlcyBzaG93ICoqc3F1YXJlZCBkaXN0YW5jZXMqKi4gU2VlIHRoZSBbb2ZmaWNpYWwgZGlzdCBkb2N1bWVudGF0aW9uXShodHRwczovL3N0YXQuZXRoei5jaC9SLW1hbnVhbC9SLWRldmVsL2xpYnJhcnkvc3RhdHMvaHRtbC9kaXN0Lmh0bWwpLg0KDQojIyBBcHBseSBzaW5nbGUgbGlua2FnZQ0KDQpUaGUgc2luZ2xlLWxpbmthZ2UgZGlzdGFuY2UgYmV0d2VlbiB0d28gY2x1c3RlcnMgaXMgdGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlaXIgKipjbG9zZXN0IHBhaXIgb2Ygb2JzZXJ2YXRpb25zKiosIHdpdGggb25lIG9ic2VydmF0aW9uIGluIGVhY2ggY2x1c3RlcjoNCg0KJCQNCmRfe1xtYXRocm17c2luZ2xlfX0oR18xLEdfMikNCj0gXG1pbl97UFxpbiBHXzEsXCBRXGluIEdfMn0gZChQLFEpLg0KJCQNCg0KYGBge3IgZml0LXNpbmdsZS1saW5rYWdlfQ0KaGNfc2luZ2xlIDwtIGhjbHVzdChkLCBtZXRob2QgPSAic2luZ2xlIikNCmBgYA0KDQpUaGUgYXJndW1lbnQgbXVzdCBiZSBgbWV0aG9kID0gInNpbmdsZSJgIHRvIHJlcXVlc3Qgc2luZ2xlIGxpbmthZ2UuIGBoY2x1c3QoKWAgdXNlcyBjb21wbGV0ZSBsaW5rYWdlIGJ5IGRlZmF1bHQuIFNlZSB0aGUgW29mZmljaWFsIGhjbHVzdCBkb2N1bWVudGF0aW9uXShodHRwczovL3N0YXQuZXRoei5jaC9SLW1hbnVhbC9SLWRldmVsL2xpYnJhcnkvc3RhdHMvaHRtbC9oY2x1c3QuaHRtbCkuDQoNCiMjIFVuZGVyc3RhbmQgdGhlIHRocmVlIG1lcmdlcw0KDQpUaGVyZSBhcmUgZm91ciBvYnNlcnZhdGlvbnMsIHNvIHRoZXJlIGFyZSB0aHJlZSBtZXJnZXMgYmVmb3JlIGV2ZXJ5dGhpbmcgYmVsb25ncyB0byBvbmUgY2x1c3Rlci4NCg0KfCBTdGVwIHwgQ2x1c3RlcnMgbWVyZ2VkIHwgTWVyZ2UgaGVpZ2h0IHwgQ2xvc2VzdCBwYWlyIHwNCnw6LS18Oi0tfDotLXw6LS18DQp8IDEgfCB7QX0gYW5kIHtCfSB8ICQxJCB8IEEgYW5kIEIgfA0KfCAyIHwge0N9IGFuZCB7RH0gfCAkXHNxcnR7Mn1cYXBwcm94IDEuNDE0MiQgfCBDIGFuZCBEIHwNCnwgMyB8IHtBLCBCfSBhbmQge0MsIER9IHwgJFxzcXJ0ezh9XGFwcHJveCAyLjgyODQkIHwgQiBhbmQgQyB8DQoNClIgc3RvcmVzIHRoZSBtZXJnZSBoZWlnaHRzIGluIHRoZSBmaXR0ZWQgb2JqZWN0Og0KDQpgYGB7ciBtZXJnZS1oZWlnaHRzfQ0KZGF0YS5mcmFtZSgNCiAgU3RlcCA9IDE6MywNCiAgSGVpZ2h0ID0gaGNfc2luZ2xlJGhlaWdodA0KKQ0KYGBgDQoNCkFmdGVyIHRoZSBmaXJzdCBtZXJnZSwgdGhlIGRpc3RhbmNlcyBiZXR3ZWVuIHRoZSByZW1haW5pbmcgY2x1c3RlcnMgYXJlDQoNCiQkDQpcYmVnaW57YWxpZ25lZH0NCmRfe1xtYXRocm17c2luZ2xlfX0oXHtBLEJcfSxce0NcfSkgJj0gXG1pbihcc3FydHsxM30sXHNxcnR7OH0pPVxzcXJ0ezh9LFxcDQpkX3tcbWF0aHJte3NpbmdsZX19KFx7QSxCXH0sXHtEXH0pICY9IFxtaW4oNSxcc3FydHsxOH0pPVxzcXJ0ezE4fSxcXA0KZChce0NcfSxce0RcfSkgJj0gXHNxcnR7Mn0uDQpcZW5ke2FsaWduZWR9DQokJA0KDQpUaHVzIEMgYW5kIEQgbWVyZ2UgbmV4dC4gRm9yIHRoZSBmaW5hbCBtZXJnZSwgZXhhbWluZSBhbGwgZm91ciBkaXN0YW5jZXMgYmV0d2VlbiB0aGUgdHdvIHJlbWFpbmluZyBjbHVzdGVyczoNCg0KYGBge3IgZmluYWwtc2luZ2xlLWxpbmthZ2UtZGlzdGFuY2V9DQpiZXR3ZWVuX2NsdXN0ZXJfZGlzdGFuY2VzIDwtIGRpc3RhbmNlX21hdHJpeFsNCiAgYygiQSIsICJCIiksIGMoIkMiLCAiRCIpDQpdDQoNCnJvdW5kKGJldHdlZW5fY2x1c3Rlcl9kaXN0YW5jZXMsIDQpDQptaW4oYmV0d2Vlbl9jbHVzdGVyX2Rpc3RhbmNlcykNCmBgYA0KDQpUaGUgbWluaW11bSBpcyB0aGUgZGlzdGFuY2UgYmV0d2VlbiBCIGFuZCBDLCAkXHNxcnR7OH0kLiAqKlNpbmdsZSBsaW5rYWdlIHVzZXMgdGhlIG5lYXJlc3QgcGFpciBhY3Jvc3MgdGhlIHR3byBjbHVzdGVycy4qKiBJdCBkb2VzIG5vdCBjYWxjdWxhdGUgYSBkaXN0YW5jZSBiZXR3ZWVuIHRoZWlyIG1lYW5zLg0KDQojIyBEcmF3IHRoZSBkZW5kcm9ncmFtDQoNCmBgYHtyIHNpbmdsZS1saW5rYWdlLWRlbmRyb2dyYW0sIGZpZy5jYXA9IlNpbmdsZSBsaW5rYWdlIG1lcmdlcyBBIHdpdGggQiBhdCBoZWlnaHQgMSBhbmQgQyB3aXRoIEQgYXQgaGVpZ2h0IHNxcnQoMikuIEN1dHRpbmcgYXQgaGVpZ2h0IDIgZ2l2ZXMgdHdvIGNsdXN0ZXJzLiJ9DQpwbG90KA0KICBoY19zaW5nbGUsDQogIGhhbmcgPSAtMSwNCiAgbWFpbiA9ICJTaW5nbGUtbGlua2FnZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyIsDQogIHhsYWIgPSAiT2JzZXJ2YXRpb25zIiwNCiAgeWxhYiA9ICJNZXJnZSBoZWlnaHQgKEV1Y2xpZGVhbiBkaXN0YW5jZSkiLA0KICBzdWIgPSAiIg0KKQ0KcmVjdC5oY2x1c3QoaGNfc2luZ2xlLCBrID0gMiwgYm9yZGVyID0gY2x1c3Rlcl9jb2xvdXJzKQ0KYWJsaW5lKGggPSAyLCBsdHkgPSAyLCBjb2wgPSAiIzY2NjY2NiIpDQpsZWdlbmQoDQogICJ0b3BsZWZ0IiwgbGVnZW5kID0gIkN1dCBhdCBoZWlnaHQgMiIsDQogIGx0eSA9IDIsIGNvbCA9ICIjNjY2NjY2IiwgYnR5ID0gIm4iLCBjZXggPSAwLjkNCikNCmBgYA0KDQpSZWFkIHRoZSBkZW5kcm9ncmFtIGZyb20gdGhlIGJvdHRvbSB1cHdhcmQuIFRoZSBoZWlnaHQgb2YgZWFjaCBob3Jpem9udGFsIGpvaW5pbmcgbGluZSBpcyB0aGUgZGlzdGFuY2UgYXQgd2hpY2ggdGhhdCBtZXJnZSBvY2N1cnMuIFdpdGggYGhhbmcgPSAtMWAsIHRoZSBvYnNlcnZhdGlvbiBsYWJlbHMgc2hhcmUgYSBjb21tb24gYmFzZWxpbmUuDQoNClRoZSBkYXNoZWQgbGluZSBpcyBhdCBoZWlnaHQgMi4gU2luY2UgJFxzcXJ0ezJ9PDI8XHNxcnR7OH0kLCB0aGUgdHdvIGxvd2VyIG1lcmdlcyBoYXZlIG9jY3VycmVkIGFuZCB0aGUgZmluYWwgbWVyZ2UgaGFzIG5vdC4gVGhlIHJlc3VsdCBpcyB0d28gY2x1c3RlcnMuDQoNCiMjIEN1dCB0aGUgdHJlZSBpbnRvIHR3byBjbHVzdGVycw0KDQpgYGB7ciBjdXQtdHJlZS10d28tY2x1c3RlcnN9DQpoY19ncm91cHMgPC0gY3V0cmVlKGhjX3NpbmdsZSwgayA9IDIpDQpoY19ncm91cHMNCg0KZGF0YS5mcmFtZShYLCBTaW5nbGVfbGlua2FnZV9jbHVzdGVyID0gaGNfZ3JvdXBzKQ0KYGBgDQoNCldlIGNhbiBhbHNvIHNwZWNpZnkgdGhlIGN1dCBoZWlnaHQ6DQoNCmBgYHtyIGN1dC10cmVlLWJ5LWhlaWdodH0NCmN1dHJlZShoY19zaW5nbGUsIGggPSAyKQ0KYGBgDQoNCkJvdGggY29tbWFuZHMgZ2l2ZSAqKntBLCBCfSBhbmQge0MsIER9KiouIGBoY2x1c3QoKWAgYnVpbGRzIHRoZSBmdWxsIGhpZXJhcmNoeTsgYGN1dHJlZSgpYCBleHRyYWN0cyBhIHBhcnRpdGlvbiB3aXRoIGEgY2hvc2VuIG51bWJlciBvZiBncm91cHMgb3IgYSBjaG9zZW4gY3V0IGhlaWdodC4gU2VlIHRoZSBbb2ZmaWNpYWwgY3V0cmVlIGRvY3VtZW50YXRpb25dKGh0dHBzOi8vc3RhdC5ldGh6LmNoL1ItbWFudWFsL1ItZGV2ZWwvbGlicmFyeS9zdGF0cy9odG1sL2N1dHJlZS5odG1sKS4NCg0KIyBDb21wYXJlIHRoZSB0d28gbWV0aG9kcw0KDQojIyBDb21wYXJlIG1lbWJlcnNoaXBzDQoNCmBgYHtyIGNvbXBhcmUtYXNzaWdubWVudHN9DQpjb21wYXJpc29uIDwtIGRhdGEuZnJhbWUoDQogIFgsDQogIEtfbWVhbnMgPSBrbSRjbHVzdGVyLA0KICBTaW5nbGVfbGlua2FnZSA9IGhjX2dyb3Vwcw0KKQ0KDQpjb21wYXJpc29uDQp0YWJsZShLX21lYW5zID0ga20kY2x1c3RlciwgU2luZ2xlX2xpbmthZ2UgPSBoY19ncm91cHMpDQpgYGANCg0KVGhlIHRhYmxlIHNob3dzIHRoZSBzYW1lIHR3byBwYWlycyBmb3IgdGhpcyBleGFtcGxlOiBBIHdpdGggQiwgYW5kIEMgd2l0aCBELiBUaGUgY3Jvc3MtdGFidWxhdGlvbiBjb3VudHMgaG93IG1hbnkgb2JzZXJ2YXRpb25zIGJlbG9uZyB0byBlYWNoIGNvbWJpbmF0aW9uIG9mIGNsdXN0ZXJzLg0KDQoqKkNsdXN0ZXIgbnVtYmVycyBhcmUgbmFtZXMuKiogVGhlIGFzc2lnbm1lbnRzIGAoMSwgMSwgMiwgMilgIGFuZCBgKDIsIDIsIDEsIDEpYCBkZXNjcmliZSB0aGUgc2FtZSBncm91cGluZy4gQWdyZWVtZW50IGNvbmNlcm5zIHdoaWNoIG9ic2VydmF0aW9ucyBzdGF5IHRvZ2V0aGVyLCBldmVuIHdoZW4gdGhlIG51bWVyaWNhbCBsYWJlbHMgYXJlIGRpZmZlcmVudC4NCg0KIyMgUGxvdCBib3RoIHBhcnRpdGlvbnMNCg0KYGBge3IgY29tcGFyZS1wbG90cywgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTQuNiwgZmlnLmNhcD0iQm90aCBtZXRob2RzIGlkZW50aWZ5IHRoZSBzYW1lIHR3byBwYWlycyB3aGVuIHR3byBjbHVzdGVycyBhcmUgcmVxdWVzdGVkLiJ9DQpvbGRfcGFyIDwtIHBhcihtZnJvdyA9IGMoMSwgMiksIG1hciA9IGMoNCwgNCwgMywgMSkpDQoNCnBsb3QoDQogIFgkeCwgWCR5LA0KICBjb2wgPSBjbHVzdGVyX2NvbG91cnNba20kY2x1c3Rlcl0sIHBjaCA9IDE5LCBjZXggPSAxLjYsDQogIHhsaW0gPSBjKDAsIDYpLCB5bGltID0gYygwLCA1KSwgYXNwID0gMSwNCiAgeGxhYiA9ICJ4IiwgeWxhYiA9ICJ5IiwgbWFpbiA9ICJLLW1lYW5zIg0KKQ0KdGV4dChYJHgsIFgkeSwgbGFiZWxzID0gcm93bmFtZXMoWCksIHBvcyA9IDMpDQpwb2ludHMoDQogIGttJGNlbnRlcnNbLCAieCJdLCBrbSRjZW50ZXJzWywgInkiXSwNCiAgcGNoID0gNCwgY2V4ID0gMS44LCBsd2QgPSAyLCBjb2wgPSBjbHVzdGVyX2NvbG91cnMNCikNCg0KcGxvdCgNCiAgWCR4LCBYJHksDQogIGNvbCA9IGNsdXN0ZXJfY29sb3Vyc1toY19ncm91cHNdLCBwY2ggPSAxOSwgY2V4ID0gMS42LA0KICB4bGltID0gYygwLCA2KSwgeWxpbSA9IGMoMCwgNSksIGFzcCA9IDEsDQogIHhsYWIgPSAieCIsIHlsYWIgPSAieSIsIG1haW4gPSAiU2luZ2xlIGxpbmthZ2U6IGN1dCBhdCBLID0gMiINCikNCnRleHQoWCR4LCBYJHksIGxhYmVscyA9IHJvd25hbWVzKFgpLCBwb3MgPSAzKQ0KDQpwYXIob2xkX3BhcikNCmBgYA0KDQpUaGUgY3Jvc3NlcyBpbiB0aGUgbGVmdCBwYW5lbCBhcmUgay1tZWFucyBjZW50cmVzLiBUaGUgcmlnaHQgcGFuZWwgc2hvd3MgdGhlIHBhcnRpdGlvbiBvYnRhaW5lZCBieSBjdXR0aW5nIHRoZSBoaWVyYXJjaGljYWwgdHJlZS4NCg0KIyMgV2hhdCBpcyBkaWZmZXJlbnQgYWJvdXQgdGhlIG1ldGhvZHM/DQoNCnwgRmVhdHVyZSB8IEstbWVhbnMgdXNlZCBoZXJlIHwgU2luZ2xlIGxpbmthZ2UgdXNlZCBoZXJlIHwNCnw6LS18Oi0tfDotLXwNCnwgU3RhcnRpbmcgcG9pbnQgfCBUd28gc3VwcGxpZWQgY2VudHJlcywgQSBhbmQgQiB8IEVhY2ggb2JzZXJ2YXRpb24gaW4gaXRzIG93biBjbHVzdGVyIHwNCnwgTWFpbiBzdGVwIHwgQXNzaWduIHRvIG5lYXJlc3QgY2VudHJlLCB0aGVuIHVwZGF0ZSBtZWFucyB8IE1lcmdlIHRoZSBjbHVzdGVycyB3aXRoIHRoZSBzbWFsbGVzdCBzaW5nbGUtbGlua2FnZSBkaXN0YW5jZSB8DQp8IERpc3RhbmNlIHVzZWQgaW4gdGhlIHN0ZXAgfCBPYnNlcnZhdGlvbiB0byBjZW50cmUgfCBDbG9zZXN0IHBhaXIgb2Ygb2JzZXJ2YXRpb25zIGFjcm9zcyBjbHVzdGVycyB8DQp8IE51bWJlciBvZiBjbHVzdGVycyB8IENob29zZSAkSz0yJCBiZWZvcmUgZml0dGluZyB8IEJ1aWxkIHRoZSB0cmVlLCB0aGVuIGN1dCBpdCBpbnRvIHR3byBjbHVzdGVycyB8DQp8IE1haW4gcmVzdWx0IHwgQ2x1c3RlciBhc3NpZ25tZW50cyBhbmQgY2VudHJlcyB8IEEgaGllcmFyY2h5LCByZXByZXNlbnRlZCBieSBhIGRlbmRyb2dyYW0gfA0KDQpUaGVzZSBtZXRob2RzIGdpdmUgdGhlIHNhbWUgcGFydGl0aW9uIGZvciB0aGlzIHNtYWxsIGV4YW1wbGUuIFRoZXkgY2FuIHByb2R1Y2UgZGlmZmVyZW50IHBhcnRpdGlvbnMgZm9yIG90aGVyIGRhdGEuIFRoaXMgZXhhbXBsZSBkb2VzIG5vdCBlc3RhYmxpc2ggdGhhdCB0d28gY2x1c3RlcnMgYXJlIG9wdGltYWwgZm9yIGV2ZXJ5IHByb2JsZW0uDQoNCiMgT3B0aW9uYWw6IHVzZSB0aGUgYWx0ZXJuYXRpdmUgc3RhcnQgZnJvbSB0aGUgc2xpZGVzDQoNClRoZSBzbGlkZXMgYWxzbyBhc2sgd2hhdCBoYXBwZW5zIHdoZW4gdGhlIHN0YXJ0aW5nIGNlbnRyZXMgYXJlIEEgYW5kIEQuDQoNCmBgYHtyIGFsdGVybmF0aXZlLXN0YXJ0fQ0KYWx0ZXJuYXRpdmVfY2VudHJlcyA8LSBhcy5tYXRyaXgoWFtjKCJBIiwgIkQiKSwgXSkNCg0Ka21fQUQgPC0ga21lYW5zKA0KICBYLA0KICBjZW50ZXJzID0gYWx0ZXJuYXRpdmVfY2VudHJlcywNCiAgYWxnb3JpdGhtID0gIkxsb3lkIiwNCiAgaXRlci5tYXggPSAxMDANCikNCg0Ka21fQUQkY2x1c3Rlcg0Ka21fQUQkY2VudGVycw0Ka21fQUQkdG90LndpdGhpbnNzDQpgYGANCg0KVGhpcyBzdGFydCBwcm9kdWNlcyB0aGUgc2FtZSB0d28gZ3JvdXBzLCB0aGUgc2FtZSBjZW50cmVzLCBhbmQgdG90YWwgV1NTIDEuNS4gVGhlIGluaXRpYWwgYXNzaWdubWVudCBhbHJlYWR5IGdpdmVzIHtBLCBCfSBhbmQge0MsIER9OyBvbmUgY2VudHJlIHVwZGF0ZSByZWFjaGVzIHRoZSBmaW5hbCBjZW50cmVzLCBmb2xsb3dlZCBieSBhIGNoZWNrIHRoYXQgdGhlIGFzc2lnbm1lbnRzIHJlbWFpbiB1bmNoYW5nZWQuIERpZmZlcmVudCBzdGFydGluZyBjZW50cmVzIGNhbiBhZmZlY3Qgay1tZWFucyByZXN1bHRzIG9uIG90aGVyIGRhdGFzZXRzLg0KDQojIFNvdXJjZXMgYW5kIHJlcHJvZHVjaWJpbGl0eQ0KDQotICoqRXhhbXBsZSBkYXRhIGFuZCBrLW1lYW5zIGhhbmQgY2FsY3VsYXRpb25zOioqIEFkZWwgTS4sICpLLW1lYW5zIGJ5IGhhbmQqLCBzdXBwbGllZCBmaWxlIGBLTWVhbnNfQnlfSGFuZC0xLnBkZmAsIHNsaWRlcyAyLTEzLg0KLSAqKlIgZnVuY3Rpb24gcmVmZXJlbmNlczoqKiBba21lYW5zXShodHRwczovL3N0YXQuZXRoei5jaC9SLW1hbnVhbC9SLWRldmVsL2xpYnJhcnkvc3RhdHMvaHRtbC9rbWVhbnMuaHRtbCksIFtkaXN0XShodHRwczovL3N0YXQuZXRoei5jaC9SLW1hbnVhbC9SLWRldmVsL2xpYnJhcnkvc3RhdHMvaHRtbC9kaXN0Lmh0bWwpLCBbaGNsdXN0XShodHRwczovL3N0YXQuZXRoei5jaC9SLW1hbnVhbC9SLWRldmVsL2xpYnJhcnkvc3RhdHMvaHRtbC9oY2x1c3QuaHRtbCksIGFuZCBbY3V0cmVlXShodHRwczovL3N0YXQuZXRoei5jaC9SLW1hbnVhbC9SLWRldmVsL2xpYnJhcnkvc3RhdHMvaHRtbC9jdXRyZWUuaHRtbCkuDQoNCkFsbCBjYWxjdWxhdGlvbnMgYW5kIGZpZ3VyZXMgaW4gZXhlY3V0YWJsZSBjaHVua3MgYXJlIGdlbmVyYXRlZCBmcm9tIHRoZSBmb3VyIG9ic2VydmF0aW9ucyBlbnRlcmVkIGF0IHRoZSBiZWdpbm5pbmcuIE5vIGV4dGVybmFsIGRhdGEgZmlsZXMgYXJlIHJlcXVpcmVkLiBWYWx1ZXMgaW4gdGhlIGV4cGxhbmF0b3J5IHRleHQgcmVmZXIgdG8gdGhpcyBmaXhlZCBmb3VyLXBvaW50IGV4YW1wbGUuDQoNClRvIHJlbmRlciBmcm9tIHRoZSBSIGNvbnNvbGUgYWZ0ZXIgc2F2aW5nIHRoaXMgZmlsZSBpbiB5b3VyIGN1cnJlbnQgd29ya2luZyBkaXJlY3Rvcnk6DQoNCmBgYHtyIHJlbmRlci1pbnN0cnVjdGlvbnMsIGV2YWw9RkFMU0V9DQpybWFya2Rvd246OnJlbmRlcigiS01lYW5zX2FuZF9TaW5nbGVfTGlua2FnZS5SbWQiKQ0KYGBgDQoNClRoZSBmb2xsb3dpbmcgcmVjb3JkcyB0aGUgUiB2ZXJzaW9uIHVzZWQgd2hlbiB5b3Uga25pdCB0aGUgZG9jdW1lbnQ6DQoNCmBgYHtyIHItdmVyc2lvbn0NClIudmVyc2lvbi5zdHJpbmcNCmBgYA0K