1 Aim and how to run this file

We will solve the example in the supplied slides using two methods:

  1. K-means, with two clusters and starting centres at A and B.
  2. 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.

2 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)
The original observations, before assigning any clusters.

The original observations, before assigning any clusters.

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.

3 Solve the example with k-means

3.1 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().

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

3.3 Read the cluster assignments and centres

km$cluster
#> 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.

km$centers
#>     x   y
#> 1 1.5 1.0
#> 2 4.5 3.5
km$size
#> [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.

3.4 (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"
)
K-means assigns A and B to one cluster and C and D to the other. Crosses show the final centres.

K-means assigns A and B to one cluster and C and D to the other. Crosses show the final centres.

4 (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.

4.1 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\).

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

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

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

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

5 Solve the example with single-linkage hierarchical clustering

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

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

5.3 Understand the three merges

There are four observations, so there are three merges before everything belongs to one cluster.

Step Clusters merged Merge height Closest pair
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.

5.4 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
)
Single linkage merges A with B at height 1 and C with D at height sqrt(2). Cutting at height 2 gives two clusters.

Single linkage merges A with B at height 1 and C with D at height sqrt(2). Cutting at height 2 gives two clusters.

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.

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

cutree(hc_single, h = 2)
#> 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.

6 Compare the two methods

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

6.2 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)
Both methods identify the same two pairs when two clusters are requested.

Both methods identify the same two pairs when two clusters are requested.

par(old_par)

The crosses in the left panel are k-means centres. The right panel shows the partition obtained by cutting the hierarchical tree.

6.3 What is different about the methods?

Feature K-means used here Single linkage used here
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.

7 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
km_AD$centers
#>     x   y
#> 1 1.5 1.0
#> 2 4.5 3.5
km_AD$tot.withinss
#> [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.

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

R.version.string
#> [1] "R version 4.4.1 (2024-06-14 ucrt)"
LS0tDQp0aXRsZTogIkstbWVhbnMgYW5kIFNpbmdsZS1MaW5rYWdlIEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nIg0Kc3VidGl0bGU6ICJUaGUgZm91ciBvYnNlcnZhdGlvbiBleGFtcGxlIGZyb20gSy1tZWFucyBieSBoYW5kIg0KaW5zdGl0dXRlOiAiU0RVRkUgKyBEYWxob3VzaWUgVW5pdmVyc2l0eSwgTWF0aCAmIFN0YXRzIg0KYXV0aG9yOiAiQWRlbCBNLiINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0aGVtZTogZmxhdGx5DQogICAgaGlnaGxpZ2h0OiB0YW5nbw0KICAgIHNlbGZfY29udGFpbmVkOiB0cnVlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICBlY2hvID0gVFJVRSwNCiAgbWVzc2FnZSA9IEZBTFNFLA0KICB3YXJuaW5nID0gVFJVRSwNCiAgZXJyb3IgPSBGQUxTRSwNCiAgY29tbWVudCA9ICIjPiIsDQogIGZpZy53aWR0aCA9IDYuNSwNCiAgZmlnLmhlaWdodCA9IDUsDQogIGZpZy5hbGlnbiA9ICJjZW50ZXIiLA0KICBkcGkgPSAxMjANCikNCm9wdGlvbnMod2lkdGggPSA4MCwgZGlnaXRzID0gNikNCmBgYA0KDQojIEFpbSBhbmQgaG93IHRvIHJ1biB0aGlzIGZpbGUNCg0KV2Ugd2lsbCBzb2x2ZSB0aGUgZXhhbXBsZSBpbiB0aGUgc3VwcGxpZWQgc2xpZGVzIHVzaW5nIHR3byBtZXRob2RzOg0KDQoxLiAqKkstbWVhbnMqKiwgd2l0aCB0d28gY2x1c3RlcnMgYW5kIHN0YXJ0aW5nIGNlbnRyZXMgYXQgQSBhbmQgQi4NCjIuICoqSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgd2l0aCBzaW5nbGUgbGlua2FnZSoqLCBmb2xsb3dlZCBieSBhIGN1dCB0aGF0IGdpdmVzIHR3byBjbHVzdGVycy4NCg0KVGhlIGRhdGEgYXJlIGluY2x1ZGVkIGJlbG93LiBPcGVuIHRoaXMgZmlsZSBpbiBSU3R1ZGlvIGFuZCBjbGljayAqKktuaXQqKiB0byBwcm9kdWNlIGFuIEhUTUwgZG9jdW1lbnQgY29udGFpbmluZyB0aGUgUiBjb21tYW5kcywgdGhlaXIgb3V0cHV0LCBhbmQgdGhlIHBsb3RzLiBZb3UgY2FuIGFsc28gcnVuIHRoZSBjb2RlIGNodW5rcyBpbiBvcmRlci4NCg0KVGhlIGNsdXN0ZXJpbmcgYW5kIHBsb3R0aW5nIGZ1bmN0aW9ucyBjb21lIHdpdGggUi4gS25pdHRpbmcgcmVxdWlyZXMgdGhlIGBybWFya2Rvd25gIHBhY2thZ2UgYW5kIGl0cyBkZXBlbmRlbmNpZXMuIElmIG5lY2Vzc2FyeSwgcnVuIHRoZSBmb2xsb3dpbmcgKipvbmNlIGluIHRoZSBSIGNvbnNvbGUqKjoNCg0KYGBge3IgaW5zdGFsbC1vbmNlLCBldmFsPUZBTFNFfQ0KaW5zdGFsbC5wYWNrYWdlcygicm1hcmtkb3duIikNCmBgYA0KDQpUaGUgZmluYWwgYW5zd2VyIGlzICoqe0EsIEJ9IGFuZCB7QywgRH0gZm9yIGJvdGggbWV0aG9kcyoqLiBUaGUgc2VjdGlvbnMgYmVsb3cgZXhwbGFpbiBob3cgZWFjaCBtZXRob2QgcmVhY2hlcyB0aGF0IGFuc3dlci4NCg0KIyBFbnRlciBhbmQgcGxvdCB0aGUgZGF0YQ0KDQpUaGUgZm91ciBvYnNlcnZhdGlvbnMgYXJlIEEgPSAoMSwgMSksIEIgPSAoMiwgMSksIEMgPSAoNCwgMyksIGFuZCBEID0gKDUsIDQpLCBleGFjdGx5IGFzIGluIHRoZSBzbGlkZXMuDQoNCmBgYHtyIGVudGVyLWRhdGF9DQpYIDwtIGRhdGEuZnJhbWUoDQogIHggPSBjKDEsIDIsIDQsIDUpLA0KICB5ID0gYygxLCAxLCAzLCA0KSwNCiAgcm93Lm5hbWVzID0gYygiQSIsICJCIiwgIkMiLCAiRCIpDQopDQoNClgNCmBgYA0KDQpFYWNoIHJvdyBpcyBvbmUgb2JzZXJ2YXRpb24uIFRoZSBjb2x1bW5zIGB4YCBhbmQgYHlgIGFyZSB0aGUgdHdvIG51bWVyaWNhbCBmZWF0dXJlcyB1c2VkIGZvciBjbHVzdGVyaW5nLiBBLCBCLCBDLCBhbmQgRCBhcmUgb2JzZXJ2YXRpb24gbmFtZXMuIFRoZXkgYXJlICoqbm90IGtub3duIGNsYXNzIGxhYmVscyoqLg0KDQpUaGlzIGlzIGFuICoqdW5zdXBlcnZpc2VkIGxlYXJuaW5nKiogcHJvYmxlbTogd2UgdXNlIHRoZSBudW1lcmljYWwgZmVhdHVyZXMgdG8gZmluZCBncm91cHMgd2l0aG91dCBiZWluZyBnaXZlbiB0aGUgY29ycmVjdCBncm91cCBmb3IgZWFjaCBvYnNlcnZhdGlvbi4NCg0KYGBge3IgcGxvdC1vcmlnaW5hbC1kYXRhLCBmaWcuY2FwPSJUaGUgb3JpZ2luYWwgb2JzZXJ2YXRpb25zLCBiZWZvcmUgYXNzaWduaW5nIGFueSBjbHVzdGVycy4ifQ0KcGxvdCgNCiAgWCR4LCBYJHksDQogIHBjaCA9IDE5LCBjZXggPSAxLjYsIGNvbCA9ICIjNTI2NDc3IiwNCiAgeGxpbSA9IGMoMCwgNiksIHlsaW0gPSBjKDAsIDUpLCBhc3AgPSAxLA0KICB4bGFiID0gIngiLCB5bGFiID0gInkiLA0KICBtYWluID0gIkZvdXIgb2JzZXJ2YXRpb25zIg0KKQ0KdGV4dChYJHgsIFgkeSwgbGFiZWxzID0gcm93bmFtZXMoWCksIHBvcyA9IDMsIGNleCA9IDEuMSkNCmBgYA0KDQoqKkxvb2sgYXQgdGhlIHBsb3Q6KiogQSBhbmQgQiBhcmUgY2xvc2UgdG8gZWFjaCBvdGhlciwgYW5kIEMgYW5kIEQgYXJlIGNsb3NlIHRvIGVhY2ggb3RoZXIuDQoNCldlIHVzZSB0aGUgb3JpZ2luYWwgY29vcmRpbmF0ZXMgdGhyb3VnaG91dCBzbyB0aGUgZGlzdGFuY2VzIG1hdGNoIHRoZSBzbGlkZXMuIEZvciByZWFsIGRhdGEsIGNvbnNpZGVyIHRoZSB1bml0cyBhbmQgc2NhbGVzIG9mIHRoZSB2YXJpYWJsZXM6IGNoYW5naW5nIGEgdmFyaWFibGUncyBzY2FsZSBjaGFuZ2VzIGl0cyBjb250cmlidXRpb24gdG8gZGlzdGFuY2UuDQoNCiMgU29sdmUgdGhlIGV4YW1wbGUgd2l0aCBrLW1lYW5zDQoNCiMjIENob29zZSB0aGUgc2FtZSBzdGFydGluZyBjZW50cmVzIGFzIHRoZSBzbGlkZXMNCg0KV2UgY2hvb3NlICRLID0gMiQgYW5kIHN0YXJ0IHdpdGgNCg0KJCQNCmNfMSA9ICgxLDEpIFxxdWFkXHRleHR7YW5kfVxxdWFkIGNfMiA9ICgyLDEpLg0KJCQNCg0KVGhlc2UgYXJlIHRoZSBjb29yZGluYXRlcyBvZiBBIGFuZCBCLg0KDQpgYGB7ciBzdGFydGluZy1jZW50cmVzfQ0KaW5pdGlhbF9jZW50cmVzIDwtIGFzLm1hdHJpeChYW2MoIkEiLCAiQiIpLCBdKQ0KaW5pdGlhbF9jZW50cmVzDQpgYGANCg0KYFhbYygiQSIsICJCIiksIF1gIHNlbGVjdHMgcm93cyBBIGFuZCBCIGFuZCBib3RoIGNvbHVtbnMuIFRoZSByZXN1bHQgc3VwcGxpZXMgdGhlIGFjdHVhbCBzdGFydGluZyBjZW50cmVzIHRvIGBrbWVhbnMoKWAuDQoNCiMjIFJ1biBrbWVhbnMoKQ0KDQpgYGB7ciBmaXQta21lYW5zfQ0Ka20gPC0ga21lYW5zKA0KICBYLA0KICBjZW50ZXJzID0gaW5pdGlhbF9jZW50cmVzLA0KICBhbGdvcml0aG0gPSAiTGxveWQiLA0KICBpdGVyLm1heCA9IDEwMA0KKQ0KYGBgDQoNClRoZSBhcmd1bWVudHMgbWVhbjoNCg0KLSBgWGA6IHRoZSBudW1lcmljYWwgZGF0YSB0byBjbHVzdGVyLg0KLSBgY2VudGVycyA9IGluaXRpYWxfY2VudHJlc2A6IHN0YXJ0IGF0IEEgYW5kIEI7IHRoZSB0d28gcm93cyBzcGVjaWZ5IHR3byBjbHVzdGVycy4NCi0gYGFsZ29yaXRobSA9ICJMbG95ZCJgOiBhbHRlcm5hdGUgYmV0d2VlbiBhc3NpZ25pbmcgb2JzZXJ2YXRpb25zIHRvIHRoZWlyIG5lYXJlc3QgY2VudHJlIGFuZCB1cGRhdGluZyB0aGUgY2VudHJlcyB0byB0aGUgZ3JvdXAgbWVhbnMsIGFzIGluIHRoZSBzbGlkZXMuDQotIGBpdGVyLm1heCA9IDEwMGA6IGFsbG93IHVwIHRvIDEwMCBpdGVyYXRpb25zOyB0aGlzIGlzIGEgbGltaXQsIG5vdCBhIHJlcXVlc3QgdG8gcGVyZm9ybSAxMDAgaXRlcmF0aW9ucy4NCg0KUidzIGRlZmF1bHQgay1tZWFucyBhbGdvcml0aG0gaXMgSGFydGlnYW4tV29uZy4gV2UgZXhwbGljaXRseSBzZWxlY3QgTGxveWQncyBhbGdvcml0aG0gdG8gbWF0Y2ggdGhlIGhhbmQgY2FsY3VsYXRpb24uIFN1cHBseWluZyB0aGUgc3RhcnRpbmcgY2VudHJlcyBtYWtlcyB0aGlzIGV4YW1wbGUgcmVwcm9kdWNpYmxlIHdpdGhvdXQgcmFuZG9tIGluaXRpYWxpc2F0aW9uLiBTZWUgdGhlIFtvZmZpY2lhbCBrbWVhbnMgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9zdGF0LmV0aHouY2gvUi1tYW51YWwvUi1kZXZlbC9saWJyYXJ5L3N0YXRzL2h0bWwva21lYW5zLmh0bWwpLg0KDQojIyBSZWFkIHRoZSBjbHVzdGVyIGFzc2lnbm1lbnRzIGFuZCBjZW50cmVzDQoNCmBgYHtyIGttZWFucy1hc3NpZ25tZW50c30NCmttJGNsdXN0ZXINCg0KZGF0YS5mcmFtZShYLCBLX21lYW5zX2NsdXN0ZXIgPSBrbSRjbHVzdGVyKQ0KYGBgDQoNClRoZSBhc3NpZ25tZW50IGlzIEEgYW5kIEIgaW4gb25lIGNsdXN0ZXIsIGFuZCBDIGFuZCBEIGluIHRoZSBvdGhlci4NCg0KYGBge3Iga21lYW5zLWNlbnRyZXN9DQprbSRjZW50ZXJzDQprbSRzaXplDQpgYGANCg0KVGhlIGZpbmFsIGNlbnRyZXMgYXJlDQoNCiQkDQpjXzEgPSBcbGVmdChcZnJhY3sxKzJ9ezJ9LFxmcmFjezErMX17Mn1ccmlnaHQpID0gKDEuNSwxKSwNClxxcXVhZA0KY18yID0gXGxlZnQoXGZyYWN7NCs1fXsyfSxcZnJhY3szKzR9ezJ9XHJpZ2h0KSA9ICg0LjUsMy41KS4NCiQkDQoNCkVhY2ggY2x1c3RlciBjb250YWlucyB0d28gb2JzZXJ2YXRpb25zLiBUaGUgYCRgIHN5bWJvbCBleHRyYWN0cyBhIG5hbWVkIHBhcnQgb2YgdGhlIHNhdmVkIHJlc3VsdDogZm9yIGV4YW1wbGUsIGBrbSRjZW50ZXJzYCBleHRyYWN0cyB0aGUgY2VudHJlcy4NCg0KDQojIyAoT3B0aW9uYWwpIFBsb3QgdGhlIGstbWVhbnMgc29sdXRpb24NCg0KYGBge3IgcGxvdC1rbWVhbnMsIGZpZy5jYXA9IkstbWVhbnMgYXNzaWducyBBIGFuZCBCIHRvIG9uZSBjbHVzdGVyIGFuZCBDIGFuZCBEIHRvIHRoZSBvdGhlci4gQ3Jvc3NlcyBzaG93IHRoZSBmaW5hbCBjZW50cmVzLiJ9DQpjbHVzdGVyX2NvbG91cnMgPC0gYygiIzAwNzJCMiIsICIjRDU1RTAwIikNCg0KcGxvdCgNCiAgWCR4LCBYJHksDQogIGNvbCA9IGNsdXN0ZXJfY29sb3Vyc1trbSRjbHVzdGVyXSwNCiAgcGNoID0gMTksIGNleCA9IDEuNiwNCiAgeGxpbSA9IGMoMCwgNiksIHlsaW0gPSBjKDAsIDUpLCBhc3AgPSAxLA0KICB4bGFiID0gIngiLCB5bGFiID0gInkiLA0KICBtYWluID0gIkstbWVhbnM6IHR3byBjbHVzdGVycyINCikNCnRleHQoWCR4LCBYJHksIGxhYmVscyA9IHJvd25hbWVzKFgpLCBwb3MgPSAzLCBjZXggPSAxLjEpDQpwb2ludHMoDQogIGttJGNlbnRlcnNbLCAieCJdLCBrbSRjZW50ZXJzWywgInkiXSwNCiAgcGNoID0gNCwgY2V4ID0gMiwgbHdkID0gMiwNCiAgY29sID0gY2x1c3Rlcl9jb2xvdXJzDQopDQp0ZXh0KA0KICBrbSRjZW50ZXJzWywgIngiXSwga20kY2VudGVyc1ssICJ5Il0sDQogIGxhYmVscyA9IGMoImMxIiwgImMyIiksIHBvcyA9IDEsDQogIGNvbCA9IGNsdXN0ZXJfY29sb3Vycw0KKQ0KbGVnZW5kKA0KICAidG9wbGVmdCIsDQogIGxlZ2VuZCA9IGMoIkNsdXN0ZXIgMSIsICJDbHVzdGVyIDIiLCAiQ2VudHJlIiksDQogIGNvbCA9IGMoY2x1c3Rlcl9jb2xvdXJzLCAiYmxhY2siKSwNCiAgcGNoID0gYygxOSwgMTksIDQpLCBidHkgPSAibiINCikNCmBgYA0KDQojIChPcHRpb25hbCkgQ29ubmVjdCB0aGUgUiBzb2x1dGlvbiB0byB0aGUgaGFuZCBjYWxjdWxhdGlvbnMNCg0KVGhpcyBzZWN0aW9uIHJlcHJvZHVjZXMgdGhlIGFzc2lnbm1lbnQgYW5kIHVwZGF0ZSBzdGVwcyBpbiB0aGUgc2xpZGVzLiBJdCBjYW4gYmUgdXNlZCB0byBleHBsYWluIHdoYXQgdGhlIGBrbWVhbnMoKWAgY2FsbCBkb2VzLg0KDQpGb3IgYW4gb2JzZXJ2YXRpb24gJFA9KHgseSkkIGFuZCBhIGNlbnRyZSAkYz0oYSxiKSQsIHRoZSBzcXVhcmVkIEV1Y2xpZGVhbiBkaXN0YW5jZSBpcw0KDQokJA0KZF4yKFAsYykgPSAoeC1hKV4yICsgKHktYileMi4NCiQkDQoNCkNvbXBhcmluZyBzcXVhcmVkIGRpc3RhbmNlcyBnaXZlcyB0aGUgc2FtZSBuZWFyZXN0IGNlbnRyZSBhcyBjb21wYXJpbmcgdGhlaXIgc3F1YXJlIHJvb3RzLg0KDQojIyBSb3VuZCAxOiBhc3NpZ24gcG9pbnRzIHRvIHRoZSBpbml0aWFsIGNlbnRyZXMNCg0KYGBge3Igcm91bmQxLWFzc2lnbn0NCmQyX2MxIDwtIChYJHggLSBpbml0aWFsX2NlbnRyZXNbMSwgIngiXSleMiArDQogICAgICAgICAoWCR5IC0gaW5pdGlhbF9jZW50cmVzWzEsICJ5Il0pXjINCg0KZDJfYzIgPC0gKFgkeCAtIGluaXRpYWxfY2VudHJlc1syLCAieCJdKV4yICsNCiAgICAgICAgIChYJHkgLSBpbml0aWFsX2NlbnRyZXNbMiwgInkiXSleMg0KDQpyb3VuZDEgPC0gZGF0YS5mcmFtZSgNCiAgU3F1YXJlZF90b19jMSA9IGQyX2MxLA0KICBTcXVhcmVkX3RvX2MyID0gZDJfYzIsDQogIENsdXN0ZXIgPSBpZmVsc2UoZDJfYzEgPD0gZDJfYzIsIDEsIDIpLA0KICByb3cubmFtZXMgPSByb3duYW1lcyhYKQ0KKQ0KDQpyb3VuZDENCmBgYA0KDQpgaWZlbHNlKGQyX2MxIDw9IGQyX2MyLCAxLCAyKWAgYXNzaWducyBhIHBvaW50IHRvIGNsdXN0ZXIgMSB3aGVuIGl0cyBmaXJzdCBzcXVhcmVkIGRpc3RhbmNlIGlzIHNtYWxsZXIsIGFuZCB0byBjbHVzdGVyIDIgb3RoZXJ3aXNlLiBUaGlzIGNvZGUgYXNzaWducyBhbiBleGFjdCB0aWUgdG8gY2x1c3RlciAxOyBubyB0aWVzIG9jY3VyIGluIHRoaXMgZXhhbXBsZS4NCg0KVGhlIGZpcnN0IGFzc2lnbm1lbnQgaXMgKip7QX0gYW5kIHtCLCBDLCBEfSoqLiBJbiBwYXJ0aWN1bGFyLCBDIGpvaW5zIGNsdXN0ZXIgMiBiZWNhdXNlICQ4IDwgMTMkLg0KDQojIyBSb3VuZCAxOiB1cGRhdGUgdGhlIGNlbnRyZXMNCg0KYGNvbE1lYW5zKClgIGNhbGN1bGF0ZXMgdGhlIG1lYW4gb2YgZWFjaCBjb29yZGluYXRlIHdpdGhpbiBhIGNsdXN0ZXIuDQoNCmBgYHtyIHJvdW5kMS11cGRhdGV9DQpjZW50cmVzX3JvdW5kMSA8LSByYmluZCgNCiAgYzEgPSBjb2xNZWFucyhYW3JvdW5kMSRDbHVzdGVyID09IDEsICwgZHJvcCA9IEZBTFNFXSksDQogIGMyID0gY29sTWVhbnMoWFtyb3VuZDEkQ2x1c3RlciA9PSAyLCAsIGRyb3AgPSBGQUxTRV0pDQopDQoNCmNlbnRyZXNfcm91bmQxDQpgYGANCg0KVGhlIHVwZGF0ZWQgY2VudHJlcyBhcmUgJGNfMT0oMSwxKSQgYW5kICRjXzI9KDExLzMsOC8zKSQuIFRoZSBjb25kaXRpb24gYHJvdW5kMSRDbHVzdGVyID09IDFgIHNlbGVjdHMgdGhlIG9ic2VydmF0aW9ucyBhc3NpZ25lZCB0byBjbHVzdGVyIDEuIGBkcm9wID0gRkFMU0VgIGtlZXBzIHRoZSBzZWxlY3RlZCBkYXRhIGluIGEgdHdvLWNvbHVtbiB0YWJsZSwgaW5jbHVkaW5nIHdoZW4gYSBjbHVzdGVyIGhhcyBqdXN0IG9uZSBvYnNlcnZhdGlvbi4NCg0KS2VlcCB0aGUgc3RvcmVkIHZhbHVlcyBhdCBmdWxsIHByZWNpc2lvbiBmb3IgdGhlIG5leHQgY2FsY3VsYXRpb24uDQoNCiMjIFJvdW5kIDI6IGFzc2lnbiBwb2ludHMgYWdhaW4NCg0KYGBge3Igcm91bmQyLWFzc2lnbn0NCmQyX2MxIDwtIChYJHggLSBjZW50cmVzX3JvdW5kMVsxLCAieCJdKV4yICsNCiAgICAgICAgIChYJHkgLSBjZW50cmVzX3JvdW5kMVsxLCAieSJdKV4yDQoNCmQyX2MyIDwtIChYJHggLSBjZW50cmVzX3JvdW5kMVsyLCAieCJdKV4yICsNCiAgICAgICAgIChYJHkgLSBjZW50cmVzX3JvdW5kMVsyLCAieSJdKV4yDQoNCnJvdW5kMiA8LSBkYXRhLmZyYW1lKA0KICBTcXVhcmVkX3RvX2MxID0gZDJfYzEsDQogIFNxdWFyZWRfdG9fYzIgPSBkMl9jMiwNCiAgQ2x1c3RlciA9IGlmZWxzZShkMl9jMSA8PSBkMl9jMiwgMSwgMiksDQogIHJvdy5uYW1lcyA9IHJvd25hbWVzKFgpDQopDQoNCnJvdW5kKHJvdW5kMiwgNCkNCmBgYA0KDQpUaGUgc2Vjb25kIHNxdWFyZWQtZGlzdGFuY2UgY29sdW1uIGlzICQ4OS85JCwgJDUwLzkkLCAkMi85JCwgYW5kICQzMi85JC4gKipCIGNoYW5nZXMgdG8gY2x1c3RlciAxKiogYmVjYXVzZSAkMSA8IDUwLzkkLiBUaGUgZ3JvdXBzIGFyZSBub3cgKip7QSwgQn0gYW5kIHtDLCBEfSoqLg0KDQpgcm91bmQocm91bmQyLCA0KWAgcm91bmRzIHRoZSBkaXNwbGF5ZWQgdGFibGU7IGl0IGRvZXMgbm90IHJlcGxhY2UgdGhlIHN0b3JlZCwgdW5yb3VuZGVkIHZhbHVlcy4NCg0KIyMgUm91bmQgMjogdXBkYXRlIHRoZSBjZW50cmVzDQoNCmBgYHtyIHJvdW5kMi11cGRhdGV9DQpjZW50cmVzX3JvdW5kMiA8LSByYmluZCgNCiAgYzEgPSBjb2xNZWFucyhYW3JvdW5kMiRDbHVzdGVyID09IDEsICwgZHJvcCA9IEZBTFNFXSksDQogIGMyID0gY29sTWVhbnMoWFtyb3VuZDIkQ2x1c3RlciA9PSAyLCAsIGRyb3AgPSBGQUxTRV0pDQopDQoNCmNlbnRyZXNfcm91bmQyDQpgYGANCg0KVGhlIG5ldyBjZW50cmVzIGFyZSAqKigxLjUsIDEpKiogYW5kICoqKDQuNSwgMy41KSoqLCBtYXRjaGluZyB0aGUgYGttZWFucygpYCBzb2x1dGlvbi4NCg0KIyMgRmluYWwgY2hlY2s6IGRvIGFueSBhc3NpZ25tZW50cyBjaGFuZ2U/DQoNCmBgYHtyIGZpbmFsLWFzc2lnbm1lbnQtY2hlY2t9DQpkMl9jMSA8LSAoWCR4IC0gY2VudHJlc19yb3VuZDJbMSwgIngiXSleMiArDQogICAgICAgICAoWCR5IC0gY2VudHJlc19yb3VuZDJbMSwgInkiXSleMg0KDQpkMl9jMiA8LSAoWCR4IC0gY2VudHJlc19yb3VuZDJbMiwgIngiXSleMiArDQogICAgICAgICAoWCR5IC0gY2VudHJlc19yb3VuZDJbMiwgInkiXSleMg0KDQpmaW5hbF9jaGVjayA8LSBkYXRhLmZyYW1lKA0KICBTcXVhcmVkX3RvX2MxID0gZDJfYzEsDQogIFNxdWFyZWRfdG9fYzIgPSBkMl9jMiwNCiAgUHJldmlvdXNfY2x1c3RlciA9IHJvdW5kMiRDbHVzdGVyLA0KICBOZXdfY2x1c3RlciA9IGlmZWxzZShkMl9jMSA8PSBkMl9jMiwgMSwgMiksDQogIHJvdy5uYW1lcyA9IHJvd25hbWVzKFgpDQopDQoNCmZpbmFsX2NoZWNrDQpgYGANCg0KTm8gYXNzaWdubWVudHMgY2hhbmdlLiBUaGVyZWZvcmUsIHJlY2FsY3VsYXRpbmcgdGhlIG1lYW5zIHdvdWxkIGxlYXZlIHRoZSBjZW50cmVzIHVuY2hhbmdlZCwgYW5kIHRoZSBhbGdvcml0aG0gc3RvcHMuIFRoZXJlIGFyZSAqKnR3byByb3VuZHMgdGhhdCBjaGFuZ2UgdGhlIGNlbnRyZXMsIGZvbGxvd2VkIGJ5IGEgZmluYWwgYXNzaWdubWVudCBjaGVjayoqLg0KDQojIFNvbHZlIHRoZSBleGFtcGxlIHdpdGggc2luZ2xlLWxpbmthZ2UgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcNCg0KIyMgQ2FsY3VsYXRlIEV1Y2xpZGVhbiBkaXN0YW5jZXMgYmV0d2VlbiBvYnNlcnZhdGlvbnMNCg0KSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgc3RhcnRzIHdpdGggZWFjaCBvYnNlcnZhdGlvbiBpbiBpdHMgb3duIGNsdXN0ZXIuIEF0IGVhY2ggc3RlcCBpdCBtZXJnZXMgdHdvIGNsdXN0ZXJzLg0KDQpXZSBmaXJzdCBjYWxjdWxhdGUgdGhlIG9yZGluYXJ5IEV1Y2xpZGVhbiBkaXN0YW5jZXMgYmV0d2VlbiBhbGwgcGFpcnMgb2Ygb2JzZXJ2YXRpb25zOg0KDQokJA0KZChQLFEpID0gXHNxcnR7KHhfUC14X1EpXjIgKyAoeV9QLXlfUSleMn0uDQokJA0KDQpgYGB7ciBwYWlyd2lzZS1kaXN0YW5jZXN9DQpkIDwtIGRpc3QoWCwgbWV0aG9kID0gImV1Y2xpZGVhbiIpDQpkaXN0YW5jZV9tYXRyaXggPC0gYXMubWF0cml4KGQpDQoNCnJvdW5kKGRpc3RhbmNlX21hdHJpeCwgNCkNCmBgYA0KDQpGb3IgZXhhbXBsZSwNCg0KJCQNCmQoQSxCKT0xLFxxcXVhZCBkKEMsRCk9XHNxcnR7Mn0sXHFxdWFkIGQoQixDKT1cc3FydHs4fS4NCiQkDQoNCmBkaXN0KClgIGNyZWF0ZXMgYSBkaXN0YW5jZSBvYmplY3QuIGBhcy5tYXRyaXgoKWAgZGlzcGxheXMgaXQgYXMgYSBmdWxsIHRhYmxlLiBUaGUgZGlhZ29uYWwgZW50cmllcyBhcmUgemVybywgYW5kIHRoZSB0YWJsZSBpcyBzeW1tZXRyaWMuIFRoZXNlIGFyZSAqKkV1Y2xpZGVhbiBkaXN0YW5jZXMqKiwgd2hpbGUgdGhlIGVhcmxpZXIgay1tZWFucyBhc3NpZ25tZW50IHRhYmxlcyBzaG93ICoqc3F1YXJlZCBkaXN0YW5jZXMqKi4gU2VlIHRoZSBbb2ZmaWNpYWwgZGlzdCBkb2N1bWVudGF0aW9uXShodHRwczovL3N0YXQuZXRoei5jaC9SLW1hbnVhbC9SLWRldmVsL2xpYnJhcnkvc3RhdHMvaHRtbC9kaXN0Lmh0bWwpLg0KDQojIyBBcHBseSBzaW5nbGUgbGlua2FnZQ0KDQpUaGUgc2luZ2xlLWxpbmthZ2UgZGlzdGFuY2UgYmV0d2VlbiB0d28gY2x1c3RlcnMgaXMgdGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlaXIgKipjbG9zZXN0IHBhaXIgb2Ygb2JzZXJ2YXRpb25zKiosIHdpdGggb25lIG9ic2VydmF0aW9uIGluIGVhY2ggY2x1c3RlcjoNCg0KJCQNCmRfe1xtYXRocm17c2luZ2xlfX0oR18xLEdfMikNCj0gXG1pbl97UFxpbiBHXzEsXCBRXGluIEdfMn0gZChQLFEpLg0KJCQNCg0KYGBge3IgZml0LXNpbmdsZS1saW5rYWdlfQ0KaGNfc2luZ2xlIDwtIGhjbHVzdChkLCBtZXRob2QgPSAic2luZ2xlIikNCmBgYA0KDQpUaGUgYXJndW1lbnQgbXVzdCBiZSBgbWV0aG9kID0gInNpbmdsZSJgIHRvIHJlcXVlc3Qgc2luZ2xlIGxpbmthZ2UuIGBoY2x1c3QoKWAgdXNlcyBjb21wbGV0ZSBsaW5rYWdlIGJ5IGRlZmF1bHQuIFNlZSB0aGUgW29mZmljaWFsIGhjbHVzdCBkb2N1bWVudGF0aW9uXShodHRwczovL3N0YXQuZXRoei5jaC9SLW1hbnVhbC9SLWRldmVsL2xpYnJhcnkvc3RhdHMvaHRtbC9oY2x1c3QuaHRtbCkuDQoNCiMjIFVuZGVyc3RhbmQgdGhlIHRocmVlIG1lcmdlcw0KDQpUaGVyZSBhcmUgZm91ciBvYnNlcnZhdGlvbnMsIHNvIHRoZXJlIGFyZSB0aHJlZSBtZXJnZXMgYmVmb3JlIGV2ZXJ5dGhpbmcgYmVsb25ncyB0byBvbmUgY2x1c3Rlci4NCg0KfCBTdGVwIHwgQ2x1c3RlcnMgbWVyZ2VkIHwgTWVyZ2UgaGVpZ2h0IHwgQ2xvc2VzdCBwYWlyIHwNCnw6LS18Oi0tfDotLXw6LS18DQp8IDEgfCB7QX0gYW5kIHtCfSB8ICQxJCB8IEEgYW5kIEIgfA0KfCAyIHwge0N9IGFuZCB7RH0gfCAkXHNxcnR7Mn1cYXBwcm94IDEuNDE0MiQgfCBDIGFuZCBEIHwNCnwgMyB8IHtBLCBCfSBhbmQge0MsIER9IHwgJFxzcXJ0ezh9XGFwcHJveCAyLjgyODQkIHwgQiBhbmQgQyB8DQoNClIgc3RvcmVzIHRoZSBtZXJnZSBoZWlnaHRzIGluIHRoZSBmaXR0ZWQgb2JqZWN0Og0KDQpgYGB7ciBtZXJnZS1oZWlnaHRzfQ0KZGF0YS5mcmFtZSgNCiAgU3RlcCA9IDE6MywNCiAgSGVpZ2h0ID0gaGNfc2luZ2xlJGhlaWdodA0KKQ0KYGBgDQoNCkFmdGVyIHRoZSBmaXJzdCBtZXJnZSwgdGhlIGRpc3RhbmNlcyBiZXR3ZWVuIHRoZSByZW1haW5pbmcgY2x1c3RlcnMgYXJlDQoNCiQkDQpcYmVnaW57YWxpZ25lZH0NCmRfe1xtYXRocm17c2luZ2xlfX0oXHtBLEJcfSxce0NcfSkgJj0gXG1pbihcc3FydHsxM30sXHNxcnR7OH0pPVxzcXJ0ezh9LFxcDQpkX3tcbWF0aHJte3NpbmdsZX19KFx7QSxCXH0sXHtEXH0pICY9IFxtaW4oNSxcc3FydHsxOH0pPVxzcXJ0ezE4fSxcXA0KZChce0NcfSxce0RcfSkgJj0gXHNxcnR7Mn0uDQpcZW5ke2FsaWduZWR9DQokJA0KDQpUaHVzIEMgYW5kIEQgbWVyZ2UgbmV4dC4gRm9yIHRoZSBmaW5hbCBtZXJnZSwgZXhhbWluZSBhbGwgZm91ciBkaXN0YW5jZXMgYmV0d2VlbiB0aGUgdHdvIHJlbWFpbmluZyBjbHVzdGVyczoNCg0KYGBge3IgZmluYWwtc2luZ2xlLWxpbmthZ2UtZGlzdGFuY2V9DQpiZXR3ZWVuX2NsdXN0ZXJfZGlzdGFuY2VzIDwtIGRpc3RhbmNlX21hdHJpeFsNCiAgYygiQSIsICJCIiksIGMoIkMiLCAiRCIpDQpdDQoNCnJvdW5kKGJldHdlZW5fY2x1c3Rlcl9kaXN0YW5jZXMsIDQpDQptaW4oYmV0d2Vlbl9jbHVzdGVyX2Rpc3RhbmNlcykNCmBgYA0KDQpUaGUgbWluaW11bSBpcyB0aGUgZGlzdGFuY2UgYmV0d2VlbiBCIGFuZCBDLCAkXHNxcnR7OH0kLiAqKlNpbmdsZSBsaW5rYWdlIHVzZXMgdGhlIG5lYXJlc3QgcGFpciBhY3Jvc3MgdGhlIHR3byBjbHVzdGVycy4qKiBJdCBkb2VzIG5vdCBjYWxjdWxhdGUgYSBkaXN0YW5jZSBiZXR3ZWVuIHRoZWlyIG1lYW5zLg0KDQojIyBEcmF3IHRoZSBkZW5kcm9ncmFtDQoNCmBgYHtyIHNpbmdsZS1saW5rYWdlLWRlbmRyb2dyYW0sIGZpZy5jYXA9IlNpbmdsZSBsaW5rYWdlIG1lcmdlcyBBIHdpdGggQiBhdCBoZWlnaHQgMSBhbmQgQyB3aXRoIEQgYXQgaGVpZ2h0IHNxcnQoMikuIEN1dHRpbmcgYXQgaGVpZ2h0IDIgZ2l2ZXMgdHdvIGNsdXN0ZXJzLiJ9DQpwbG90KA0KICBoY19zaW5nbGUsDQogIGhhbmcgPSAtMSwNCiAgbWFpbiA9ICJTaW5nbGUtbGlua2FnZSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyIsDQogIHhsYWIgPSAiT2JzZXJ2YXRpb25zIiwNCiAgeWxhYiA9ICJNZXJnZSBoZWlnaHQgKEV1Y2xpZGVhbiBkaXN0YW5jZSkiLA0KICBzdWIgPSAiIg0KKQ0KcmVjdC5oY2x1c3QoaGNfc2luZ2xlLCBrID0gMiwgYm9yZGVyID0gY2x1c3Rlcl9jb2xvdXJzKQ0KYWJsaW5lKGggPSAyLCBsdHkgPSAyLCBjb2wgPSAiIzY2NjY2NiIpDQpsZWdlbmQoDQogICJ0b3BsZWZ0IiwgbGVnZW5kID0gIkN1dCBhdCBoZWlnaHQgMiIsDQogIGx0eSA9IDIsIGNvbCA9ICIjNjY2NjY2IiwgYnR5ID0gIm4iLCBjZXggPSAwLjkNCikNCmBgYA0KDQpSZWFkIHRoZSBkZW5kcm9ncmFtIGZyb20gdGhlIGJvdHRvbSB1cHdhcmQuIFRoZSBoZWlnaHQgb2YgZWFjaCBob3Jpem9udGFsIGpvaW5pbmcgbGluZSBpcyB0aGUgZGlzdGFuY2UgYXQgd2hpY2ggdGhhdCBtZXJnZSBvY2N1cnMuIFdpdGggYGhhbmcgPSAtMWAsIHRoZSBvYnNlcnZhdGlvbiBsYWJlbHMgc2hhcmUgYSBjb21tb24gYmFzZWxpbmUuDQoNClRoZSBkYXNoZWQgbGluZSBpcyBhdCBoZWlnaHQgMi4gU2luY2UgJFxzcXJ0ezJ9PDI8XHNxcnR7OH0kLCB0aGUgdHdvIGxvd2VyIG1lcmdlcyBoYXZlIG9jY3VycmVkIGFuZCB0aGUgZmluYWwgbWVyZ2UgaGFzIG5vdC4gVGhlIHJlc3VsdCBpcyB0d28gY2x1c3RlcnMuDQoNCiMjIEN1dCB0aGUgdHJlZSBpbnRvIHR3byBjbHVzdGVycw0KDQpgYGB7ciBjdXQtdHJlZS10d28tY2x1c3RlcnN9DQpoY19ncm91cHMgPC0gY3V0cmVlKGhjX3NpbmdsZSwgayA9IDIpDQpoY19ncm91cHMNCg0KZGF0YS5mcmFtZShYLCBTaW5nbGVfbGlua2FnZV9jbHVzdGVyID0gaGNfZ3JvdXBzKQ0KYGBgDQoNCldlIGNhbiBhbHNvIHNwZWNpZnkgdGhlIGN1dCBoZWlnaHQ6DQoNCmBgYHtyIGN1dC10cmVlLWJ5LWhlaWdodH0NCmN1dHJlZShoY19zaW5nbGUsIGggPSAyKQ0KYGBgDQoNCkJvdGggY29tbWFuZHMgZ2l2ZSAqKntBLCBCfSBhbmQge0MsIER9KiouIGBoY2x1c3QoKWAgYnVpbGRzIHRoZSBmdWxsIGhpZXJhcmNoeTsgYGN1dHJlZSgpYCBleHRyYWN0cyBhIHBhcnRpdGlvbiB3aXRoIGEgY2hvc2VuIG51bWJlciBvZiBncm91cHMgb3IgYSBjaG9zZW4gY3V0IGhlaWdodC4gU2VlIHRoZSBbb2ZmaWNpYWwgY3V0cmVlIGRvY3VtZW50YXRpb25dKGh0dHBzOi8vc3RhdC5ldGh6LmNoL1ItbWFudWFsL1ItZGV2ZWwvbGlicmFyeS9zdGF0cy9odG1sL2N1dHJlZS5odG1sKS4NCg0KIyBDb21wYXJlIHRoZSB0d28gbWV0aG9kcw0KDQojIyBDb21wYXJlIG1lbWJlcnNoaXBzDQoNCmBgYHtyIGNvbXBhcmUtYXNzaWdubWVudHN9DQpjb21wYXJpc29uIDwtIGRhdGEuZnJhbWUoDQogIFgsDQogIEtfbWVhbnMgPSBrbSRjbHVzdGVyLA0KICBTaW5nbGVfbGlua2FnZSA9IGhjX2dyb3Vwcw0KKQ0KDQpjb21wYXJpc29uDQp0YWJsZShLX21lYW5zID0ga20kY2x1c3RlciwgU2luZ2xlX2xpbmthZ2UgPSBoY19ncm91cHMpDQpgYGANCg0KVGhlIHRhYmxlIHNob3dzIHRoZSBzYW1lIHR3byBwYWlycyBmb3IgdGhpcyBleGFtcGxlOiBBIHdpdGggQiwgYW5kIEMgd2l0aCBELiBUaGUgY3Jvc3MtdGFidWxhdGlvbiBjb3VudHMgaG93IG1hbnkgb2JzZXJ2YXRpb25zIGJlbG9uZyB0byBlYWNoIGNvbWJpbmF0aW9uIG9mIGNsdXN0ZXJzLg0KDQoqKkNsdXN0ZXIgbnVtYmVycyBhcmUgbmFtZXMuKiogVGhlIGFzc2lnbm1lbnRzIGAoMSwgMSwgMiwgMilgIGFuZCBgKDIsIDIsIDEsIDEpYCBkZXNjcmliZSB0aGUgc2FtZSBncm91cGluZy4gQWdyZWVtZW50IGNvbmNlcm5zIHdoaWNoIG9ic2VydmF0aW9ucyBzdGF5IHRvZ2V0aGVyLCBldmVuIHdoZW4gdGhlIG51bWVyaWNhbCBsYWJlbHMgYXJlIGRpZmZlcmVudC4NCg0KIyMgUGxvdCBib3RoIHBhcnRpdGlvbnMNCg0KYGBge3IgY29tcGFyZS1wbG90cywgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTQuNiwgZmlnLmNhcD0iQm90aCBtZXRob2RzIGlkZW50aWZ5IHRoZSBzYW1lIHR3byBwYWlycyB3aGVuIHR3byBjbHVzdGVycyBhcmUgcmVxdWVzdGVkLiJ9DQpvbGRfcGFyIDwtIHBhcihtZnJvdyA9IGMoMSwgMiksIG1hciA9IGMoNCwgNCwgMywgMSkpDQoNCnBsb3QoDQogIFgkeCwgWCR5LA0KICBjb2wgPSBjbHVzdGVyX2NvbG91cnNba20kY2x1c3Rlcl0sIHBjaCA9IDE5LCBjZXggPSAxLjYsDQogIHhsaW0gPSBjKDAsIDYpLCB5bGltID0gYygwLCA1KSwgYXNwID0gMSwNCiAgeGxhYiA9ICJ4IiwgeWxhYiA9ICJ5IiwgbWFpbiA9ICJLLW1lYW5zIg0KKQ0KdGV4dChYJHgsIFgkeSwgbGFiZWxzID0gcm93bmFtZXMoWCksIHBvcyA9IDMpDQpwb2ludHMoDQogIGttJGNlbnRlcnNbLCAieCJdLCBrbSRjZW50ZXJzWywgInkiXSwNCiAgcGNoID0gNCwgY2V4ID0gMS44LCBsd2QgPSAyLCBjb2wgPSBjbHVzdGVyX2NvbG91cnMNCikNCg0KcGxvdCgNCiAgWCR4LCBYJHksDQogIGNvbCA9IGNsdXN0ZXJfY29sb3Vyc1toY19ncm91cHNdLCBwY2ggPSAxOSwgY2V4ID0gMS42LA0KICB4bGltID0gYygwLCA2KSwgeWxpbSA9IGMoMCwgNSksIGFzcCA9IDEsDQogIHhsYWIgPSAieCIsIHlsYWIgPSAieSIsIG1haW4gPSAiU2luZ2xlIGxpbmthZ2U6IGN1dCBhdCBLID0gMiINCikNCnRleHQoWCR4LCBYJHksIGxhYmVscyA9IHJvd25hbWVzKFgpLCBwb3MgPSAzKQ0KDQpwYXIob2xkX3BhcikNCmBgYA0KDQpUaGUgY3Jvc3NlcyBpbiB0aGUgbGVmdCBwYW5lbCBhcmUgay1tZWFucyBjZW50cmVzLiBUaGUgcmlnaHQgcGFuZWwgc2hvd3MgdGhlIHBhcnRpdGlvbiBvYnRhaW5lZCBieSBjdXR0aW5nIHRoZSBoaWVyYXJjaGljYWwgdHJlZS4NCg0KIyMgV2hhdCBpcyBkaWZmZXJlbnQgYWJvdXQgdGhlIG1ldGhvZHM/DQoNCnwgRmVhdHVyZSB8IEstbWVhbnMgdXNlZCBoZXJlIHwgU2luZ2xlIGxpbmthZ2UgdXNlZCBoZXJlIHwNCnw6LS18Oi0tfDotLXwNCnwgU3RhcnRpbmcgcG9pbnQgfCBUd28gc3VwcGxpZWQgY2VudHJlcywgQSBhbmQgQiB8IEVhY2ggb2JzZXJ2YXRpb24gaW4gaXRzIG93biBjbHVzdGVyIHwNCnwgTWFpbiBzdGVwIHwgQXNzaWduIHRvIG5lYXJlc3QgY2VudHJlLCB0aGVuIHVwZGF0ZSBtZWFucyB8IE1lcmdlIHRoZSBjbHVzdGVycyB3aXRoIHRoZSBzbWFsbGVzdCBzaW5nbGUtbGlua2FnZSBkaXN0YW5jZSB8DQp8IERpc3RhbmNlIHVzZWQgaW4gdGhlIHN0ZXAgfCBPYnNlcnZhdGlvbiB0byBjZW50cmUgfCBDbG9zZXN0IHBhaXIgb2Ygb2JzZXJ2YXRpb25zIGFjcm9zcyBjbHVzdGVycyB8DQp8IE51bWJlciBvZiBjbHVzdGVycyB8IENob29zZSAkSz0yJCBiZWZvcmUgZml0dGluZyB8IEJ1aWxkIHRoZSB0cmVlLCB0aGVuIGN1dCBpdCBpbnRvIHR3byBjbHVzdGVycyB8DQp8IE1haW4gcmVzdWx0IHwgQ2x1c3RlciBhc3NpZ25tZW50cyBhbmQgY2VudHJlcyB8IEEgaGllcmFyY2h5LCByZXByZXNlbnRlZCBieSBhIGRlbmRyb2dyYW0gfA0KDQpUaGVzZSBtZXRob2RzIGdpdmUgdGhlIHNhbWUgcGFydGl0aW9uIGZvciB0aGlzIHNtYWxsIGV4YW1wbGUuIFRoZXkgY2FuIHByb2R1Y2UgZGlmZmVyZW50IHBhcnRpdGlvbnMgZm9yIG90aGVyIGRhdGEuIFRoaXMgZXhhbXBsZSBkb2VzIG5vdCBlc3RhYmxpc2ggdGhhdCB0d28gY2x1c3RlcnMgYXJlIG9wdGltYWwgZm9yIGV2ZXJ5IHByb2JsZW0uDQoNCiMgT3B0aW9uYWw6IHVzZSB0aGUgYWx0ZXJuYXRpdmUgc3RhcnQgZnJvbSB0aGUgc2xpZGVzDQoNClRoZSBzbGlkZXMgYWxzbyBhc2sgd2hhdCBoYXBwZW5zIHdoZW4gdGhlIHN0YXJ0aW5nIGNlbnRyZXMgYXJlIEEgYW5kIEQuDQoNCmBgYHtyIGFsdGVybmF0aXZlLXN0YXJ0fQ0KYWx0ZXJuYXRpdmVfY2VudHJlcyA8LSBhcy5tYXRyaXgoWFtjKCJBIiwgIkQiKSwgXSkNCg0Ka21fQUQgPC0ga21lYW5zKA0KICBYLA0KICBjZW50ZXJzID0gYWx0ZXJuYXRpdmVfY2VudHJlcywNCiAgYWxnb3JpdGhtID0gIkxsb3lkIiwNCiAgaXRlci5tYXggPSAxMDANCikNCg0Ka21fQUQkY2x1c3Rlcg0Ka21fQUQkY2VudGVycw0Ka21fQUQkdG90LndpdGhpbnNzDQpgYGANCg0KVGhpcyBzdGFydCBwcm9kdWNlcyB0aGUgc2FtZSB0d28gZ3JvdXBzLCB0aGUgc2FtZSBjZW50cmVzLCBhbmQgdG90YWwgV1NTIDEuNS4gVGhlIGluaXRpYWwgYXNzaWdubWVudCBhbHJlYWR5IGdpdmVzIHtBLCBCfSBhbmQge0MsIER9OyBvbmUgY2VudHJlIHVwZGF0ZSByZWFjaGVzIHRoZSBmaW5hbCBjZW50cmVzLCBmb2xsb3dlZCBieSBhIGNoZWNrIHRoYXQgdGhlIGFzc2lnbm1lbnRzIHJlbWFpbiB1bmNoYW5nZWQuIERpZmZlcmVudCBzdGFydGluZyBjZW50cmVzIGNhbiBhZmZlY3Qgay1tZWFucyByZXN1bHRzIG9uIG90aGVyIGRhdGFzZXRzLg0KDQojIFNvdXJjZXMgYW5kIHJlcHJvZHVjaWJpbGl0eQ0KDQotICoqRXhhbXBsZSBkYXRhIGFuZCBrLW1lYW5zIGhhbmQgY2FsY3VsYXRpb25zOioqIEFkZWwgTS4sICpLLW1lYW5zIGJ5IGhhbmQqLCBzdXBwbGllZCBmaWxlIGBLTWVhbnNfQnlfSGFuZC0xLnBkZmAsIHNsaWRlcyAyLTEzLg0KLSAqKlIgZnVuY3Rpb24gcmVmZXJlbmNlczoqKiBba21lYW5zXShodHRwczovL3N0YXQuZXRoei5jaC9SLW1hbnVhbC9SLWRldmVsL2xpYnJhcnkvc3RhdHMvaHRtbC9rbWVhbnMuaHRtbCksIFtkaXN0XShodHRwczovL3N0YXQuZXRoei5jaC9SLW1hbnVhbC9SLWRldmVsL2xpYnJhcnkvc3RhdHMvaHRtbC9kaXN0Lmh0bWwpLCBbaGNsdXN0XShodHRwczovL3N0YXQuZXRoei5jaC9SLW1hbnVhbC9SLWRldmVsL2xpYnJhcnkvc3RhdHMvaHRtbC9oY2x1c3QuaHRtbCksIGFuZCBbY3V0cmVlXShodHRwczovL3N0YXQuZXRoei5jaC9SLW1hbnVhbC9SLWRldmVsL2xpYnJhcnkvc3RhdHMvaHRtbC9jdXRyZWUuaHRtbCkuDQoNCkFsbCBjYWxjdWxhdGlvbnMgYW5kIGZpZ3VyZXMgaW4gZXhlY3V0YWJsZSBjaHVua3MgYXJlIGdlbmVyYXRlZCBmcm9tIHRoZSBmb3VyIG9ic2VydmF0aW9ucyBlbnRlcmVkIGF0IHRoZSBiZWdpbm5pbmcuIE5vIGV4dGVybmFsIGRhdGEgZmlsZXMgYXJlIHJlcXVpcmVkLiBWYWx1ZXMgaW4gdGhlIGV4cGxhbmF0b3J5IHRleHQgcmVmZXIgdG8gdGhpcyBmaXhlZCBmb3VyLXBvaW50IGV4YW1wbGUuDQoNClRvIHJlbmRlciBmcm9tIHRoZSBSIGNvbnNvbGUgYWZ0ZXIgc2F2aW5nIHRoaXMgZmlsZSBpbiB5b3VyIGN1cnJlbnQgd29ya2luZyBkaXJlY3Rvcnk6DQoNCmBgYHtyIHJlbmRlci1pbnN0cnVjdGlvbnMsIGV2YWw9RkFMU0V9DQpybWFya2Rvd246OnJlbmRlcigiS01lYW5zX2FuZF9TaW5nbGVfTGlua2FnZS5SbWQiKQ0KYGBgDQoNClRoZSBmb2xsb3dpbmcgcmVjb3JkcyB0aGUgUiB2ZXJzaW9uIHVzZWQgd2hlbiB5b3Uga25pdCB0aGUgZG9jdW1lbnQ6DQoNCmBgYHtyIHItdmVyc2lvbn0NClIudmVyc2lvbi5zdHJpbmcNCmBgYA0K