This tutorial will demonstrate the use of various MDS algorithms for:

  1. geographical distances

  2. perceptual space.

The logic follows this paper https://cran.r-project.org/web/packages/smacof/vignettes/smacof.pdf and these vignettes closely: https://rdrr.io/cran/smacof/f/vignettes/mdsnutshell.Rmd and https://rdrr.io/cran/smacof/f/vignettes/unfoldingnutshell.Rmd.

Geographical Distances

Let’s pick a couple of locations and draw distances between them in a 2D space, keeping the distances as close as possible to the original.

Take Paris, London, Yuzhno-Sakhalinsk, and Singapore, and calculate distances between them by plane + in minutes.

For converting time to minutes, I used https://www.calculateme.com/time/hours-minutes-seconds/to-minutes/.

I used a GoogleSheet/Excel file, uploaded it to R, and transformed the table into a diagonal matrix of distances:

distdata <- read.csv(choose.files())
geo <- distdata[1:4, 2:5]
row.names(geo) <- distdata[1:4, 1]
geo
##                   london paris yuzhno.sakhalinsk singapore
## london                 0                                  
## paris                343     0                            
## yuzhno-sakhalinsk   8527  8763                 0          
## singapore          10888 10727              6302         0
geo2 <- dist(geo, diag = T)
geo2
##                      london     paris yuzhno-sakhalinsk singapore
## london                0.000                                      
## paris               686.000     0.000                            
## yuzhno-sakhalinsk 17054.000 16956.888             0.000          
## singapore         21776.000 21272.779          8095.004     0.000
time <- distdata[8:11, 2:5]
row.names(time) <- distdata[8:11, 1]
time
##                   london paris yuzhno.sakhalinsk singapore
## london                 0                                  
## paris                136     0                            
## yuzhno-sakhalinsk   1310  1035                 0          
## singapore            790   777               690         0
time2 <- dist(time, diag = T)
time2
##                     london    paris yuzhno-sakhalinsk singapore
## london               0.000                                     
## paris              272.000    0.000                            
## yuzhno-sakhalinsk 2620.000 2213.369             0.000          
## singapore         1580.000 1436.276          1041.194     0.000
# install.packages("smacof")
library(smacof)
mds_geo <- mds(geo2)
summary(mds_geo) # new coordinates + % distortion, aka Stress Per Point, SPP
## 
## Configurations:
##                        D1      D2
## london             0.5953  0.0058
## paris              0.5757  0.0462
## yuzhno-sakhalinsk -0.4346 -0.2256
## singapore         -0.7363  0.1736
## 
## 
## Stress per point (in %):
##            london             paris yuzhno-sakhalinsk         singapore 
##             29.62             29.30              7.45             33.63
mds_geo
## 
## Call:
## mds(delta = geo2)
## 
## Model: Symmetric SMACOF 
## Number of objects: 4 
## Stress-1 value: 0.003 
## Number of iterations: 5
plot(mds_geo)

mds_time <- mds(time2)
summary(mds_time)
## 
## Configurations:
##                        D1      D2
## london            -0.6453 -0.0016
## paris             -0.4882 -0.0849
## yuzhno-sakhalinsk  0.8441 -0.0926
## singapore          0.2894  0.1791
## 
## 
## Stress per point (in %):
##            london             paris yuzhno-sakhalinsk         singapore 
##             31.19             21.91             34.24             12.67
mds_time
## 
## Call:
## mds(delta = time2)
## 
## Model: Symmetric SMACOF 
## Number of objects: 4 
## Stress-1 value: 0.028 
## Number of iterations: 17
plot(mds_time)

Now let’s try plotting the ‘geo’ version but treat distances as ordinal. Pay attention to the goodness-of-fit (stress) metric.

mds_geo2 <- mds(geo2, type = 'ordinal')
summary(mds_geo2) # new coordinates + % distortion
## 
## Configurations:
##                        D1      D2
## london             0.5946 -0.0012
## paris              0.5761  0.0532
## yuzhno-sakhalinsk -0.4344 -0.2256
## singapore         -0.7362  0.1735
## 
## 
## Stress per point (in %):
##            london             paris yuzhno-sakhalinsk         singapore 
##               NaN               NaN               NaN               NaN
mds_geo2
## 
## Call:
## mds(delta = geo2, type = "ordinal")
## 
## Model: Symmetric SMACOF 
## Number of objects: 4 
## Stress-1 value: 0 
## Number of iterations: 2
plot(mds_geo2)

The goodness-of-fit value is always better for ordinal values as the task is to keep the order of distances correct. However, one pays by losing the information about the exact distances.

Finally, let’s draw the MDS map with usual gg-tools:

library(ggplot2)
conf_geo <- as.data.frame(mds_geo$conf)
p <-
  ggplot(conf_geo, aes(
    x = D1,
    y = D2,
    label = rownames(conf_geo)
  ))
p + geom_point(size = 0.9) + 
    geom_text(size = 3.5, vjust = -0.8) +
    coord_fixed(xlim = c(-1, 1), ylim = c(-0.3, 0.3)) + 
    ggtitle("Geographic Configuration Between Cities To Visit") +
    theme_classic()

Now let’s put the two configuration plots next to each other to make conclusions about socioeconomic vs. flight distances:

library(patchwork)
library(ggrepel)
a <- p + geom_point(size = 0.9) + 
    geom_text_repel(size = 3.5, vjust = -0.8) +
    coord_fixed(xlim = c(-1, 1), ylim = c(-0.3, 0.3)) + 
  # "It is important to fix the aspect ratio to 1 such that the distances in the plot are Euclidean." 
    ggtitle("Geographic Configuration Between Cities To Visit") + 
    xlab("") +
    ylab("") +
    theme_bw()
conf_time <- as.data.frame(mds_time$conf)
q <-
  ggplot(conf_time, aes(
    x = D1,
    y = D2,
    label = rownames(conf_time)
  )) 
b <- q + geom_point(size = 0.9) + 
    geom_text(size = 3.5, vjust = -0.8) +
    coord_fixed(xlim = c(-1, 1), ylim = c(-0.3, 0.3)) + 
    ggtitle("Temporal Configuration Between Cities To Visit") +
    xlab("") +
    ylab("") +
    theme_bw()
a /  b

Important: to flip and reverse the plot in ggplot (for a more effective comparison), see http://www.sthda.com/english/wiki/ggplot2-rotate-a-graph-reverse-and-flip-the-plot

Next up:

Perception Distances

So far, we have dealt with examples of distance data, using a lower triangle of the distance matrix. This matrix contains distances between the objects, therefore, it will always have the same number (n) of rows and columns. This kind of matrix is also called the square matrix.

The following example, by contrast, uses the so called rectangular matrix. It is the type of data where n subjects evaluate m objects. Subjects are called ‘ideal points’, objects are ‘object points.’

Square matrix -> mds()

Rectangular matrix -> unfolding()

Unfolding is an algorithm close to the MDS family. It produces a configuration plot that shows both row and column points. This is because it is a ‘dual scaling’ algorithm that combines two plots in one. The distance units for the X and Y axes are always the same, therefore, it is possible to compare and evaluate distances not only between rows or columns but also from row points to column points.

The input data for unfolding must be non-negative dissimilarities.

There are two common ways to obtain such data:

  1. preferences. E.g., people ranking 15 breakfast items where 1 is the most desirable and 15 the least desirable item (the person is the “ideal point” and numbers serve as dissimilarities)

  2. people rank some objects by some scales, then numbers are averaged into ratings of objects. E.g., n respondents evaluate 26 cars along 10 quality scales (typically, 1-5 or 1-6 where 1 is “not at all” and 5/6 is “absolutely so”). Then average values for each car along the 10 scales are computed and converted into dissimilarities. Subjects: cars, objects: quality scales.

Breakfast preferences example

head(breakfast)
##   toast butoast engmuff jdonut cintoast bluemuff hrolls toastmarm butoastj
## 1    13      12       7      3        5        4      8        11       10
## 2    15      11       6      3       10        5     14         8        9
## 3    15      10      12     14        3        2      9         8        7
## 4     6      14      11      3        7        8     12        10        9
## 5    15       9       6     14       13        2     12         8        7
## 6     9      11      14      4        7        6     15        10        8
##   toastmarg cinbun danpastry gdonut cofcake cornmuff
## 1        15      2         1      6       9       14
## 2        12      7         1      4       2       13
## 3        11      1         6      4       5       13
## 4        15      4         1      2       5       13
## 5        10     11         1      4       3        5
## 6        12      5         2      3       1       13
un_breakfast <- unfolding(breakfast)
un_breakfast
## 
## Call: unfolding(delta = breakfast)
## 
## Model:               Rectangular smacof 
## Number of subjects:  42 
## Number of objects:   15 
## Transformation:      none 
## Conditionality:      matrix 
## 
## Stress-1 value:    0.308625 
## Penalized Stress:  3.525172 
## Number of iterations: 50
plot(un_breakfast, main = "Configuration Breakfast Data")

Interpretation from the vignette:

# library(ggplot2)
# library(ggrepel)
conf_items <- as.data.frame(un_breakfast$conf.col)
conf_persons <- as.data.frame(un_breakfast$conf.row)
br <- ggplot(conf_persons, aes(x = D1, y = D2)) 
br + geom_point(size = 1, colour = "red", alpha = 0.5) + 
  coord_fixed(xlim = c(-1.5, 1.5), ylim = c(-1.5, 1.5)) + 
  xlab("") +
  ylab("") +
  geom_point(aes(x = D1, y = D2), conf_items, colour = "cadetblue") + 
  geom_text_repel(aes(x = D1, y = D2, label = rownames(conf_items)), 
            conf_items, colour = "cadetblue", vjust = -0.8) + 
  ggtitle("Unfolding Configuration Breakfast Items") +
  annotate("text", x = 0, y = -1.5, label = "soft bread", color = "navy") +
  annotate("text", x = 0, y = 1.5, label = "crunchy bread", color = "navy") +
  annotate("text", x = -1.5, y = 0, label = "sweet", angle = 90, color = "navy") +
  annotate("text", x = 1.5, y = 0, label = "savoury", angle = 270, color = "navy") +
  theme_bw()

“Users should not judge the goodness-of-fit by solely relying on this stress value but rather use several diagnostic tools in combination (see Mair, Borg, and Rusch 2016 for details).” https://www.researchgate.net/publication/309617943_Goodness-of-Fit_Assessment_in_Multidimensional_Scaling_and_Unfolding

Options for model comparison (both MDS and Unfolding):

Example:

plot(un_breakfast, plot.type = "stressplot")

Re-run the analysis without the ‘noisiest’ points (they are in the middle of the plot): 39, 13, 21. Optionally, call the summary(un_breakfast) for full numbers.

un_breakfast2 <- unfolding(breakfast[c(-39, -13, -21), ])
un_breakfast2$stress
## [1] 0.2949098

Compared to the original value, the fit is a little better–but not radically better:

un_breakfast$stress
## [1] 0.3086254
plot(un_breakfast, plot.type = "Shepard")

un_breakfast_sp <- unfolding(breakfast, type = "mspline")
un_breakfast_sp$stress # worse fit here
## [1] 0.3436307
plot(un_breakfast_sp, plot.type = "Shepard")

un_breakfast_o <- unfolding(breakfast, type = "ordinal")
un_breakfast_o$stress # worse fit here
## [1] 0.3268706
plot(un_breakfast_o, plot.type = "Shepard")

Another possibility is the permutation test (available in smacof for both MDS and Unfolding). H0: the stress/configuration are obtained from a random permutation of dissimilarities.

permtest(un_breakfast, breakfast, nrep = 100, verbose = F)
## 
## Call: permtest.smacofR(object = un_breakfast, data = breakfast, nrep = 100, 
##     verbose = F)
## 
## SMACOF Permutation Test
## Number of objects: 42 
## Number of replications (permutations): 100 
## 
## Observed stress value: 0.309 
## p-value: <0.001

We reject the H0 here: the observed configuration is not random, but has some structure within.

For the breakfast data, I would either collect more data or exclude those noisiest data points (or offered them another type of breakfast). Otherwise, the plot already provides some structure and even dimensions: D1 is about sugary/fattening vs. savoury items, D2 is about soft vs. crunchy bread.

Now, can unfolding apply to the type of data that Semantic Differential produces?

Since the SD also features objects and their values at scales, it is possible.

What you need to do first though is to transform values into dissimilarities.

Example:

sd <- matrix(c(-1,2,3,
               -3,-3,-3,
               3,-2,1), byrow = T, nrow = 3)
row.names(sd) <- c("real Self", "anti-Self", "rational Self")
colnames(sd) <- c("evaluation", "strength", "activity")
sd
##               evaluation strength activity
## real Self             -1        2        3
## anti-Self             -3       -3       -3
## rational Self          3       -2        1
sd1 <- sd + 3 # make them non-negative
sd1
##               evaluation strength activity
## real Self              2        5        6
## anti-Self              0        0        0
## rational Self          6        1        4

Use the smacof::sim2diss() function to convert any similarities to dissimilarities (several methods of transformation are available):

sd2 <- sim2diss(sd1, method = "reverse")
sd2
##               evaluation strength activity
## real Self              4        1        0
## anti-Self              6        6        6
## rational Self          0        5        2
sd_unf <- unfolding(sd2)
plot(sd_unf)

# library(ggplot2)
# library(ggrepel)
conf_items <- as.data.frame(sd_unf$conf.col)
conf_perc <- as.data.frame(sd_unf$conf.row)
p <- ggplot(conf_perc, aes(x = D1, y = D2)) 
p + geom_point(size = 1, colour = "red", alpha = 0.5) + 
  coord_fixed(xlim = c(-1.5, 1.5), ylim = c(-1.5, 1.5)) + 
  xlab("") +
  ylab("") +
  geom_point(aes(x = D1, y = D2), conf_items, colour = "cadetblue") + 
  geom_text_repel(aes(x = D1, y = D2, label = rownames(conf_perc)), 
            conf_perc, colour = "red", vjust = -0.5) + 
  geom_text_repel(aes(x = D1, y = D2, label = rownames(conf_items)), 
            conf_items, colour = "cadetblue", vjust = 0.8) +
  ggtitle("Unfolding for Semantic Differential") +
  theme_bw()

p1 <- p + geom_point(size = 1, colour = "red", alpha = 0.5) + 
  coord_fixed(xlim = c(-1.5, 1.5), ylim = c(-1.5, 1.5)) + 
  xlab("") +
  ylab("") +
  geom_point(aes(x = D1, y = D2), conf_items, colour = "cadetblue") + 
  geom_text_repel(aes(x = D1, y = D2, label = rownames(conf_perc)), 
            conf_perc, colour = "red", vjust = -0.5) + 
  #geom_text_repel(aes(x = D1, y = D2, label = rownames(conf_items)), 
  #          conf_items, colour = "cadetblue", vjust = 0.8) +
  ggtitle("Unfolding for Semantic Differential") +
  theme_bw()

The configuration plot above shows both the original scales and the objects.

In contrast, the MDS configuration plot will only show the objects due to the fact that it is based on distances. Compare:

sd_dist <- dist(sd, method = "euclidean", diag = T)
sd_dist
##               real Self anti-Self rational Self
## real Self      0.000000                        
## anti-Self      8.062258  0.000000              
## rational Self  6.000000  7.280110      0.000000
sd_mds <- mds(sd_dist)
plot(sd_mds)

conf_sd <- as.data.frame(sd_mds$conf)
pm <- ggplot(conf_sd, aes(
    x = D1,
    y = D2,
    label = rownames(conf_sd)
  ))
p2 <- pm + geom_point(size = 0.9) + 
    geom_text_repel(size = 3.5, vjust = -0.8) +
    coord_fixed(xlim = c(-1.5, 1.5), ylim = c(-1.5, 1.5)) + 
    ggtitle("MDS for Semantic Differential") + 
    xlab("") +
    ylab("") +
    theme_bw()
library(patchwork)
p1 | p2

The Schwartz Basic Values circle

The Schwartz circle (from https://maksimrudnev.com/)

I recommend reading the paper “Measuring the 4 Higher-Order Values in Schwartz’s Theory: A Validation of a 17-Item Inventory” https://psyarxiv.com/xmh5: MDS is one of the 8 steps of analysis there. The goal is to make sure that the shorter version of the questionnaire follows the quasi-circumplex shape of the usual Schwartz circle.

Look (p.17) how elegantly/briefly the MDS solution is discussed.

Summary of MDS vs. PCA and CoA:

  • MDS keeps the distances between objects interpretable, especially when there are both rows and columns in the plot. => Find and interpret clusters of points. However, this is not possible in symmetric correspondence analysis biplots.

  • Distances are #1 but dimensions are only occasionally meaningful in MDS. Sometimes they also make sense and can be summarized beautifully (e.g., Schwartz higher-order values). By contrast, dimensions are important for understanding correspondence plot biplots and some PCA biplots.

  • Labelled or not, dimensions are equally important in MDS. As a result, configuration plots can be flipped in whatever direction they look better for you. By contrast, the 1st dimension is the most important one in PCA and CoA alike.

  • Finally, MDS takes as input distance matrices, or evaluations like ratings and ranks. However, CoA can deal with categorical variables of equal importance (vs. “ideal points and object points” in MDS).

  • MDS can also handle correlation matrices, like PCA. But their purposes are different. MDS aims to plot the perceptual, low-dimensional space and distances between objects, while PCA captures maximum variance in the lowest suitable number of (not necessarily two) components.

Compare the biplots from https://rpubs.com/shirokaner/pca and the following solutions:

data_raw <- read.csv("THE2021.csv")
library(tidyverse)
library(corrplot)
# data_raw[,3:11] %>% 
#   cor() %>% 
#   corrplot() 
data_raw <- data_raw %>% 
  relocate(stats_pc_intl_students, .after = scores_international_outlook)
data_cor <- data_raw[ , 3:11] %>% cor() 
data_uni <- sim2diss(data_cor, method = "corr", to.dist = T)
uni <- mds(data_uni)
uni # 0.214
## 
## Call:
## mds(delta = data_uni)
## 
## Model: Symmetric SMACOF 
## Number of objects: 9 
## Stress-1 value: 0.214 
## Number of iterations: 26
#plot(uni)
uni2 <- mds(data_uni, type = "mspline")
uni2
## 
## Call:
## mds(delta = data_uni, type = "mspline")
## 
## Model: Symmetric SMACOF 
## Number of objects: 9 
## Stress-1 value: 0.107 
## Number of iterations: 18
plot(uni2)

#head(data_raw)
row.names(data_raw) <- data_raw$name
data_uni2 <- sim2diss(data_raw[ , 3:11], method = 100)
#head(data_uni2)
min(data_uni2[ , 7])
## [1] -222002
data_uni2[ ,7] <- data_uni2[ ,7] + 222002
min(data_uni2[ ,8])
## [1] -9.1
data_uni2[ ,8] <- data_uni2[ ,8] + 9.1
#summary(data_raw)
#summary(data_uni2)
uni3 <- unfolding(data_uni2)
uni3
## 
## Call: unfolding(delta = data_uni2)
## 
## Model:               Rectangular smacof 
## Number of subjects:  1448 
## Number of objects:   9 
## Transformation:      none 
## Conditionality:      matrix 
## 
## Stress-1 value:    0.096464 
## Penalized Stress:  85.22537 
## Number of iterations: 5
#plot(uni3)
# data_uni3 <- data_uni2[ , -7]
# uni4 <- unfolding(data_uni3)
# uni4
# plot(uni4)

To explore/validate the basic values circle on the individual level, see “Example II: Unfolding on personal values”, pp.784-785 in: Multivariate Behav Res . 2016 Nov-Dec;51(6):772-789. doi: 10.1080/00273171.2016.1235966. Epub 2016 Nov 1. https://www.researchgate.net/profile/Patrick-Mair-2/publication/309617943_Goodness-of-Fit_Assessment_in_Multidimensional_Scaling_and_Unfolding/links/59f4c6f5aca272607e2a8706/Goodness-of-Fit-Assessment-in-Multidimensional-Scaling-and-Unfolding.pdf

(Optionally: compare to the vector model of unfolding - “individuals (rows) represented as vectors in the biplot”): https://cran.r-project.org/web/packages/smacof/vignettes/smacof.pdf

#plot(vmu(sd2))

(Optionally) See also multidimensional preference analysis from the “pmr” package. pmr is a package for all kinds of analyses for ranking data. https://bmcmedresmethodol.biomedcentral.com/articles/10.1186/1471-2288-13-65

See also: Finch, Analyzing ranked data (2022) https://scholarworks.umass.edu/cgi/viewcontent.cgi?article=1589&context=pare

LS0tDQp0aXRsZTogIk11bHRpZGltZW5zaW9uYWwgU2NhbGluZyAoTURTKSINCmF1dGhvcjogIkFubmEgU2hpcm9rYW5vdmEiDQpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEYpDQpgYGANCg0KVGhpcyB0dXRvcmlhbCB3aWxsIGRlbW9uc3RyYXRlIHRoZSB1c2Ugb2YgdmFyaW91cyBNRFMgYWxnb3JpdGhtcyBmb3I6DQoNCihhKSBnZW9ncmFwaGljYWwgZGlzdGFuY2VzDQoNCihiKSBwZXJjZXB0dWFsIHNwYWNlLg0KDQpUaGUgbG9naWMgZm9sbG93cyB0aGlzIHBhcGVyIDxodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvc21hY29mL3ZpZ25ldHRlcy9zbWFjb2YucGRmPiBhbmQgdGhlc2UgdmlnbmV0dGVzIGNsb3NlbHk6DQo8aHR0cHM6Ly9yZHJyLmlvL2NyYW4vc21hY29mL2YvdmlnbmV0dGVzL21kc251dHNoZWxsLlJtZD4NCmFuZCA8aHR0cHM6Ly9yZHJyLmlvL2NyYW4vc21hY29mL2YvdmlnbmV0dGVzL3VuZm9sZGluZ251dHNoZWxsLlJtZD4uDQoNCiMjIEdlb2dyYXBoaWNhbCBEaXN0YW5jZXMNCg0KTGV0J3MgcGljayBhIGNvdXBsZSBvZiBsb2NhdGlvbnMgYW5kIGRyYXcgZGlzdGFuY2VzIGJldHdlZW4gdGhlbSBpbiBhIDJEIHNwYWNlLCBrZWVwaW5nIHRoZSBkaXN0YW5jZXMgYXMgY2xvc2UgYXMgcG9zc2libGUgdG8gdGhlIG9yaWdpbmFsLg0KDQpUYWtlIFBhcmlzLCBMb25kb24sIFl1emhuby1TYWtoYWxpbnNrLCBhbmQgU2luZ2Fwb3JlLCBhbmQgY2FsY3VsYXRlIGRpc3RhbmNlcyBiZXR3ZWVuIHRoZW0gYnkgcGxhbmUgKyBpbiBtaW51dGVzLg0KDQpGb3IgY29udmVydGluZyB0aW1lIHRvIG1pbnV0ZXMsIEkgdXNlZCA8aHR0cHM6Ly93d3cuY2FsY3VsYXRlbWUuY29tL3RpbWUvaG91cnMtbWludXRlcy1zZWNvbmRzL3RvLW1pbnV0ZXMvPi4NCg0KSSB1c2VkIGEgR29vZ2xlU2hlZXQvRXhjZWwgZmlsZSwgdXBsb2FkZWQgaXQgdG8gUiwgYW5kIHRyYW5zZm9ybWVkIHRoZSB0YWJsZSBpbnRvIGEgZGlhZ29uYWwgbWF0cml4IG9mIGRpc3RhbmNlczoNCg0KYGBge3J9DQpkaXN0ZGF0YSA8LSByZWFkLmNzdihjaG9vc2UuZmlsZXMoKSkNCmdlbyA8LSBkaXN0ZGF0YVsxOjQsIDI6NV0NCnJvdy5uYW1lcyhnZW8pIDwtIGRpc3RkYXRhWzE6NCwgMV0NCmdlbw0KZ2VvMiA8LSBkaXN0KGdlbywgZGlhZyA9IFQpDQpnZW8yDQpgYGANCmBgYHtyfQ0KdGltZSA8LSBkaXN0ZGF0YVs4OjExLCAyOjVdDQpyb3cubmFtZXModGltZSkgPC0gZGlzdGRhdGFbODoxMSwgMV0NCnRpbWUNCnRpbWUyIDwtIGRpc3QodGltZSwgZGlhZyA9IFQpDQp0aW1lMg0KYGBgDQoNCmBgYHtyfQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJzbWFjb2YiKQ0KbGlicmFyeShzbWFjb2YpDQpgYGANCg0KYGBge3J9DQptZHNfZ2VvIDwtIG1kcyhnZW8yKQ0Kc3VtbWFyeShtZHNfZ2VvKSAjIG5ldyBjb29yZGluYXRlcyArICUgZGlzdG9ydGlvbiwgYWthIFN0cmVzcyBQZXIgUG9pbnQsIFNQUA0KbWRzX2dlbw0KcGxvdChtZHNfZ2VvKQ0KYGBgDQpgYGB7cn0NCm1kc190aW1lIDwtIG1kcyh0aW1lMikNCnN1bW1hcnkobWRzX3RpbWUpDQptZHNfdGltZQ0KcGxvdChtZHNfdGltZSkNCmBgYA0KDQpOb3cgbGV0J3MgdHJ5IHBsb3R0aW5nIHRoZSAnZ2VvJyB2ZXJzaW9uIGJ1dCB0cmVhdCBkaXN0YW5jZXMgYXMgb3JkaW5hbC4gUGF5IGF0dGVudGlvbiB0byB0aGUgZ29vZG5lc3Mtb2YtZml0IChzdHJlc3MpIG1ldHJpYy4NCg0KYGBge3J9DQptZHNfZ2VvMiA8LSBtZHMoZ2VvMiwgdHlwZSA9ICdvcmRpbmFsJykNCnN1bW1hcnkobWRzX2dlbzIpICMgbmV3IGNvb3JkaW5hdGVzICsgJSBkaXN0b3J0aW9uDQptZHNfZ2VvMg0KcGxvdChtZHNfZ2VvMikNCmBgYA0KDQpUaGUgZ29vZG5lc3Mtb2YtZml0IHZhbHVlIGlzIGFsd2F5cyBiZXR0ZXIgZm9yIG9yZGluYWwgdmFsdWVzIGFzIHRoZSB0YXNrIGlzIHRvIGtlZXAgdGhlIG9yZGVyIG9mIGRpc3RhbmNlcyBjb3JyZWN0LiBIb3dldmVyLCBvbmUgcGF5cyBieSBsb3NpbmcgdGhlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBleGFjdCBkaXN0YW5jZXMuDQoNCkZpbmFsbHksIGxldCdzIGRyYXcgdGhlIE1EUyBtYXAgd2l0aCB1c3VhbCBnZy10b29sczoNCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpjb25mX2dlbyA8LSBhcy5kYXRhLmZyYW1lKG1kc19nZW8kY29uZikNCnAgPC0NCiAgZ2dwbG90KGNvbmZfZ2VvLCBhZXMoDQogICAgeCA9IEQxLA0KICAgIHkgPSBEMiwNCiAgICBsYWJlbCA9IHJvd25hbWVzKGNvbmZfZ2VvKQ0KICApKQ0KcCArIGdlb21fcG9pbnQoc2l6ZSA9IDAuOSkgKyANCiAgICBnZW9tX3RleHQoc2l6ZSA9IDMuNSwgdmp1c3QgPSAtMC44KSArDQogICAgY29vcmRfZml4ZWQoeGxpbSA9IGMoLTEsIDEpLCB5bGltID0gYygtMC4zLCAwLjMpKSArIA0KICAgIGdndGl0bGUoIkdlb2dyYXBoaWMgQ29uZmlndXJhdGlvbiBCZXR3ZWVuIENpdGllcyBUbyBWaXNpdCIpICsNCiAgICB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQpOb3cgbGV0J3MgcHV0IHRoZSB0d28gY29uZmlndXJhdGlvbiBwbG90cyBuZXh0IHRvIGVhY2ggb3RoZXIgdG8gbWFrZSBjb25jbHVzaW9ucyBhYm91dCBzb2Npb2Vjb25vbWljIHZzLiBmbGlnaHQgZGlzdGFuY2VzOg0KYGBge3J9DQpsaWJyYXJ5KHBhdGNod29yaykNCmxpYnJhcnkoZ2dyZXBlbCkNCmEgPC0gcCArIGdlb21fcG9pbnQoc2l6ZSA9IDAuOSkgKyANCiAgICBnZW9tX3RleHRfcmVwZWwoc2l6ZSA9IDMuNSwgdmp1c3QgPSAtMC44KSArDQogICAgY29vcmRfZml4ZWQoeGxpbSA9IGMoLTEsIDEpLCB5bGltID0gYygtMC4zLCAwLjMpKSArIA0KICAjICJJdCBpcyBpbXBvcnRhbnQgdG8gZml4IHRoZSBhc3BlY3QgcmF0aW8gdG8gMSBzdWNoIHRoYXQgdGhlIGRpc3RhbmNlcyBpbiB0aGUgcGxvdCBhcmUgRXVjbGlkZWFuLiIgDQogICAgZ2d0aXRsZSgiR2VvZ3JhcGhpYyBDb25maWd1cmF0aW9uIEJldHdlZW4gQ2l0aWVzIFRvIFZpc2l0IikgKyANCiAgICB4bGFiKCIiKSArDQogICAgeWxhYigiIikgKw0KICAgIHRoZW1lX2J3KCkNCmNvbmZfdGltZSA8LSBhcy5kYXRhLmZyYW1lKG1kc190aW1lJGNvbmYpDQpxIDwtDQogIGdncGxvdChjb25mX3RpbWUsIGFlcygNCiAgICB4ID0gRDEsDQogICAgeSA9IEQyLA0KICAgIGxhYmVsID0gcm93bmFtZXMoY29uZl90aW1lKQ0KICApKSANCmIgPC0gcSArIGdlb21fcG9pbnQoc2l6ZSA9IDAuOSkgKyANCiAgICBnZW9tX3RleHQoc2l6ZSA9IDMuNSwgdmp1c3QgPSAtMC44KSArDQogICAgY29vcmRfZml4ZWQoeGxpbSA9IGMoLTEsIDEpLCB5bGltID0gYygtMC4zLCAwLjMpKSArIA0KICAgIGdndGl0bGUoIlRlbXBvcmFsIENvbmZpZ3VyYXRpb24gQmV0d2VlbiBDaXRpZXMgVG8gVmlzaXQiKSArDQogICAgeGxhYigiIikgKw0KICAgIHlsYWIoIiIpICsNCiAgICB0aGVtZV9idygpDQphIC8gIGINCmBgYA0KDQpJbXBvcnRhbnQ6IHRvIGZsaXAgYW5kIHJldmVyc2UgdGhlIHBsb3QgaW4gZ2dwbG90IChmb3IgYSBtb3JlIGVmZmVjdGl2ZSBjb21wYXJpc29uKSwgc2VlIDxodHRwOi8vd3d3LnN0aGRhLmNvbS9lbmdsaXNoL3dpa2kvZ2dwbG90Mi1yb3RhdGUtYS1ncmFwaC1yZXZlcnNlLWFuZC1mbGlwLXRoZS1wbG90Pg0KDQpOZXh0IHVwOg0KDQojIyBQZXJjZXB0aW9uIERpc3RhbmNlcw0KDQpTbyBmYXIsIHdlIGhhdmUgZGVhbHQgd2l0aCBleGFtcGxlcyBvZiBkaXN0YW5jZSBkYXRhLCB1c2luZyBhIGxvd2VyIHRyaWFuZ2xlIG9mIHRoZSBkaXN0YW5jZSBtYXRyaXguIFRoaXMgbWF0cml4IGNvbnRhaW5zIGRpc3RhbmNlcyBiZXR3ZWVuIHRoZSBvYmplY3RzLCB0aGVyZWZvcmUsIGl0IHdpbGwgYWx3YXlzIGhhdmUgdGhlIHNhbWUgbnVtYmVyICgqKm4qKikgb2Ygcm93cyBhbmQgY29sdW1ucy4gVGhpcyBraW5kIG9mIG1hdHJpeCBpcyBhbHNvIGNhbGxlZCB0aGUgKnNxdWFyZSBtYXRyaXgqLg0KDQpUaGUgZm9sbG93aW5nIGV4YW1wbGUsIGJ5IGNvbnRyYXN0LCB1c2VzIHRoZSBzbyBjYWxsZWQgKnJlY3Rhbmd1bGFyIG1hdHJpeCouIEl0IGlzIHRoZSB0eXBlIG9mIGRhdGEgd2hlcmUgKipuKiogc3ViamVjdHMgZXZhbHVhdGUgKiptKiogb2JqZWN0cy4gU3ViamVjdHMgYXJlIGNhbGxlZCAqJ2lkZWFsIHBvaW50cycqLCBvYmplY3RzIGFyZSAqJ29iamVjdCBwb2ludHMuJyoNCg0KU3F1YXJlIG1hdHJpeCAtPiBgbWRzKClgDQoNClJlY3Rhbmd1bGFyIG1hdHJpeCAtPiBgdW5mb2xkaW5nKClgDQoNClVuZm9sZGluZyBpcyBhbiBhbGdvcml0aG0gY2xvc2UgdG8gdGhlIE1EUyBmYW1pbHkuIEl0IHByb2R1Y2VzIGEgY29uZmlndXJhdGlvbiBwbG90IHRoYXQgc2hvd3MgYm90aCByb3cgYW5kIGNvbHVtbiBwb2ludHMuIFRoaXMgaXMgYmVjYXVzZSBpdCBpcyBhICdkdWFsIHNjYWxpbmcnIGFsZ29yaXRobSB0aGF0IGNvbWJpbmVzIHR3byBwbG90cyBpbiBvbmUuIFRoZSBkaXN0YW5jZSB1bml0cyBmb3IgdGhlIFggYW5kIFkgYXhlcyBhcmUgYWx3YXlzIHRoZSBzYW1lLCB0aGVyZWZvcmUsIGl0IGlzIHBvc3NpYmxlIHRvIGNvbXBhcmUgYW5kIGV2YWx1YXRlIGRpc3RhbmNlcyBub3Qgb25seSBiZXR3ZWVuIHJvd3Mgb3IgY29sdW1ucyBidXQgYWxzbyBmcm9tIHJvdyBwb2ludHMgdG8gY29sdW1uIHBvaW50cy4NCg0KVGhlIGlucHV0IGRhdGEgZm9yIHVuZm9sZGluZyBtdXN0IGJlIG5vbi1uZWdhdGl2ZSBkaXNzaW1pbGFyaXRpZXMuDQoNClRoZXJlIGFyZSB0d28gY29tbW9uIHdheXMgdG8gb2J0YWluIHN1Y2ggZGF0YToNCg0KKDEpIHByZWZlcmVuY2VzLiBFLmcuLCBwZW9wbGUgcmFua2luZyAxNSBicmVha2Zhc3QgaXRlbXMgd2hlcmUgMSBpcyB0aGUgbW9zdCBkZXNpcmFibGUgYW5kIDE1IHRoZSBsZWFzdCBkZXNpcmFibGUgaXRlbSAodGhlIHBlcnNvbiBpcyB0aGUgImlkZWFsIHBvaW50IiBhbmQgbnVtYmVycyBzZXJ2ZSBhcyBkaXNzaW1pbGFyaXRpZXMpDQoNCigyKSBwZW9wbGUgcmFuayBzb21lIG9iamVjdHMgYnkgc29tZSBzY2FsZXMsIHRoZW4gbnVtYmVycyBhcmUgYXZlcmFnZWQgaW50byByYXRpbmdzIG9mIG9iamVjdHMuIEUuZy4sIG4gcmVzcG9uZGVudHMgZXZhbHVhdGUgMjYgY2FycyBhbG9uZyAxMCBxdWFsaXR5IHNjYWxlcyAodHlwaWNhbGx5LCAxLTUgb3IgMS02IHdoZXJlIDEgaXMgIm5vdCBhdCBhbGwiIGFuZCA1LzYgaXMgImFic29sdXRlbHkgc28iKS4gVGhlbiBhdmVyYWdlIHZhbHVlcyBmb3IgZWFjaCBjYXIgYWxvbmcgdGhlIDEwIHNjYWxlcyBhcmUgY29tcHV0ZWQgYW5kIGNvbnZlcnRlZCBpbnRvIGRpc3NpbWlsYXJpdGllcy4gU3ViamVjdHM6IGNhcnMsIG9iamVjdHM6IHF1YWxpdHkgc2NhbGVzLg0KDQpCcmVha2Zhc3QgcHJlZmVyZW5jZXMgZXhhbXBsZQ0KDQpgYGB7cn0NCmhlYWQoYnJlYWtmYXN0KQ0KdW5fYnJlYWtmYXN0IDwtIHVuZm9sZGluZyhicmVha2Zhc3QpDQp1bl9icmVha2Zhc3QNCmBgYA0KDQoNCmBgYHtyfQ0KcGxvdCh1bl9icmVha2Zhc3QsIG1haW4gPSAiQ29uZmlndXJhdGlvbiBCcmVha2Zhc3QgRGF0YSIpDQpgYGANCg0KKipJbnRlcnByZXRhdGlvbiBmcm9tIHRoZSB2aWduZXR0ZToqKg0KDQotIEJyZWFrZmFzdCBpdGVtcyBjbG9zZSB0byBlYWNoIG90aGVyIGFyZSBzaW1pbGFybHkgcHJlZmVycmVkOw0KDQotIGluZGl2aWR1YWxzIGNsb3NlIHRvIGVhY2ggb3RoZXIgaGF2ZSBzaW1pbGFyIGJyZWFrZmFzdCBwcmVmZXJlbmNlczsgDQoNCi0gdGhlIGNsb3NlciBhIGJyZWFrZmFzdCBpdGVtIHRvIGFuIGluZGl2aWR1YWwsIHRoZSBoaWdoZXIgdGhlIGluZGl2aWR1YWxz4oCZIHByZWZlcmVuY2UgZm9yIHRoaXMgaXRlbS4gDQoNCi0gT2YgY291cnNlLCB0byB3aGljaCBkZWdyZWUgdGhpcyBpbnRlcnByZXRhdGlvbiByZWZsZWN0cyB0aGUgYWN0dWFsIHByZWZlcmVuY2VzIGluIHRoZSBkYXRhIGRlcGVuZHMgb24gdGhlIGdvb2RuZXNzLW9mLWZpdCBvZiB0aGUgc29sdXRpb24uIA0KDQotIFNvbWV0aW1lcyBpdCBpcyBhbHNvIHBvc3NpYmxlIHRvIGludGVycHJldCB0aGUgZGltZW5zaW9ucyBidXQsIGp1c3QgYXMgaW4gTURTLCB0aGlzIGlzIG5vdCBhcyBjcnVjaWFsLg0KDQpgYGB7cn0NCiMgbGlicmFyeShnZ3Bsb3QyKQ0KIyBsaWJyYXJ5KGdncmVwZWwpDQpjb25mX2l0ZW1zIDwtIGFzLmRhdGEuZnJhbWUodW5fYnJlYWtmYXN0JGNvbmYuY29sKQ0KY29uZl9wZXJzb25zIDwtIGFzLmRhdGEuZnJhbWUodW5fYnJlYWtmYXN0JGNvbmYucm93KQ0KYnIgPC0gZ2dwbG90KGNvbmZfcGVyc29ucywgYWVzKHggPSBEMSwgeSA9IEQyKSkgDQpiciArIGdlb21fcG9pbnQoc2l6ZSA9IDEsIGNvbG91ciA9ICJyZWQiLCBhbHBoYSA9IDAuNSkgKyANCiAgY29vcmRfZml4ZWQoeGxpbSA9IGMoLTEuNSwgMS41KSwgeWxpbSA9IGMoLTEuNSwgMS41KSkgKyANCiAgeGxhYigiIikgKw0KICB5bGFiKCIiKSArDQogIGdlb21fcG9pbnQoYWVzKHggPSBEMSwgeSA9IEQyKSwgY29uZl9pdGVtcywgY29sb3VyID0gImNhZGV0Ymx1ZSIpICsgDQogIGdlb21fdGV4dF9yZXBlbChhZXMoeCA9IEQxLCB5ID0gRDIsIGxhYmVsID0gcm93bmFtZXMoY29uZl9pdGVtcykpLCANCiAgICAgICAgICAgIGNvbmZfaXRlbXMsIGNvbG91ciA9ICJjYWRldGJsdWUiLCB2anVzdCA9IC0wLjgpICsgDQogIGdndGl0bGUoIlVuZm9sZGluZyBDb25maWd1cmF0aW9uIEJyZWFrZmFzdCBJdGVtcyIpICsNCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gMCwgeSA9IC0xLjUsIGxhYmVsID0gInNvZnQgYnJlYWQiLCBjb2xvciA9ICJuYXZ5IikgKw0KICBhbm5vdGF0ZSgidGV4dCIsIHggPSAwLCB5ID0gMS41LCBsYWJlbCA9ICJjcnVuY2h5IGJyZWFkIiwgY29sb3IgPSAibmF2eSIpICsNCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gLTEuNSwgeSA9IDAsIGxhYmVsID0gInN3ZWV0IiwgYW5nbGUgPSA5MCwgY29sb3IgPSAibmF2eSIpICsNCiAgYW5ub3RhdGUoInRleHQiLCB4ID0gMS41LCB5ID0gMCwgbGFiZWwgPSAic2F2b3VyeSIsIGFuZ2xlID0gMjcwLCBjb2xvciA9ICJuYXZ5IikgKw0KICB0aGVtZV9idygpDQpgYGANCg0KIlVzZXJzIHNob3VsZCBub3QganVkZ2UgdGhlIGdvb2RuZXNzLW9mLWZpdCBieSBzb2xlbHkgcmVseWluZyBvbiB0aGlzIHN0cmVzcyB2YWx1ZSBidXQgcmF0aGVyIHVzZSBzZXZlcmFsIGRpYWdub3N0aWMgdG9vbHMgaW4gY29tYmluYXRpb24gKHNlZSBNYWlyLCBCb3JnLCBhbmQgUnVzY2ggMjAxNiBmb3IgZGV0YWlscykuIg0KPGh0dHBzOi8vd3d3LnJlc2VhcmNoZ2F0ZS5uZXQvcHVibGljYXRpb24vMzA5NjE3OTQzX0dvb2RuZXNzLW9mLUZpdF9Bc3Nlc3NtZW50X2luX011bHRpZGltZW5zaW9uYWxfU2NhbGluZ19hbmRfVW5mb2xkaW5nPg0KDQpPcHRpb25zIGZvciBtb2RlbCBjb21wYXJpc29uIChib3RoIE1EUyBhbmQgVW5mb2xkaW5nKToNCg0KLSBtZXRyaWMgLyBvcmRpbmFsIC8gbXNwbGluZSB0eXBlIG9mIGZ1bmN0aW9uICh3aXRoIG1ldHJpYyBkaXN0YW5jZXMgYW5kIHJhdGluZ3MsIG9yZGluYWwgdHlwZSBtYXkgaW1wcm92ZSB0aGUgcXVhbGl0eSBvZiBzb2x1dGlvbikNCg0KLSBleGFtaW5lIHN0cmVzcyBwZXIgcG9pbnQgKFNQUCkgYW5kIHJlLXJ1biB3aXRob3V0IHRoZSBtb3N0ICJub2lzeSIgcG9pbnRzIChhbHRlcm5hdGl2ZWx5LCB1c2UgdGhlIGphY2trbmlmZSB0ZWNobmlxdWUgZnJvbSB0aGUgcGFja2FnZSkNCg0KRXhhbXBsZToNCg0KYGBge3J9DQpwbG90KHVuX2JyZWFrZmFzdCwgcGxvdC50eXBlID0gInN0cmVzc3Bsb3QiKQ0KYGBgDQoNClJlLXJ1biB0aGUgYW5hbHlzaXMgd2l0aG91dCB0aGUgJ25vaXNpZXN0JyBwb2ludHMgKHRoZXkgYXJlIGluIHRoZSBtaWRkbGUgb2YgdGhlIHBsb3QpOiAzOSwgMTMsIDIxLiBPcHRpb25hbGx5LCBjYWxsIHRoZSBgc3VtbWFyeSh1bl9icmVha2Zhc3QpYCBmb3IgZnVsbCBudW1iZXJzLg0KDQpgYGB7cn0NCnVuX2JyZWFrZmFzdDIgPC0gdW5mb2xkaW5nKGJyZWFrZmFzdFtjKC0zOSwgLTEzLCAtMjEpLCBdKQ0KdW5fYnJlYWtmYXN0MiRzdHJlc3MNCmBgYA0KDQoNCkNvbXBhcmVkIHRvIHRoZSBvcmlnaW5hbCB2YWx1ZSwgdGhlIGZpdCBpcyBhIGxpdHRsZSBiZXR0ZXItLWJ1dCBub3QgcmFkaWNhbGx5IGJldHRlcjoNCg0KDQpgYGB7cn0NCnVuX2JyZWFrZmFzdCRzdHJlc3MNCnBsb3QodW5fYnJlYWtmYXN0LCBwbG90LnR5cGUgPSAiU2hlcGFyZCIpDQpgYGANCg0KYGBge3J9DQp1bl9icmVha2Zhc3Rfc3AgPC0gdW5mb2xkaW5nKGJyZWFrZmFzdCwgdHlwZSA9ICJtc3BsaW5lIikNCnVuX2JyZWFrZmFzdF9zcCRzdHJlc3MgIyB3b3JzZSBmaXQgaGVyZQ0KcGxvdCh1bl9icmVha2Zhc3Rfc3AsIHBsb3QudHlwZSA9ICJTaGVwYXJkIikNCnVuX2JyZWFrZmFzdF9vIDwtIHVuZm9sZGluZyhicmVha2Zhc3QsIHR5cGUgPSAib3JkaW5hbCIpDQp1bl9icmVha2Zhc3RfbyRzdHJlc3MgIyB3b3JzZSBmaXQgaGVyZQ0KcGxvdCh1bl9icmVha2Zhc3RfbywgcGxvdC50eXBlID0gIlNoZXBhcmQiKQ0KYGBgDQpBbm90aGVyIHBvc3NpYmlsaXR5IGlzIHRoZSBwZXJtdXRhdGlvbiB0ZXN0IChhdmFpbGFibGUgaW4gYHNtYWNvZmAgZm9yIGJvdGggTURTIGFuZCBVbmZvbGRpbmcpLiBIMDogdGhlIHN0cmVzcy9jb25maWd1cmF0aW9uIGFyZSBvYnRhaW5lZCBmcm9tIGEgcmFuZG9tIHBlcm11dGF0aW9uIG9mIGRpc3NpbWlsYXJpdGllcy4NCg0KDQpgYGB7cn0NCnBlcm10ZXN0KHVuX2JyZWFrZmFzdCwgYnJlYWtmYXN0LCBucmVwID0gMTAwLCB2ZXJib3NlID0gRikNCmBgYA0KDQpXZSByZWplY3QgdGhlIEgwIGhlcmU6IHRoZSBvYnNlcnZlZCBjb25maWd1cmF0aW9uIGlzIG5vdCByYW5kb20sIGJ1dCBoYXMgc29tZSBzdHJ1Y3R1cmUgd2l0aGluLg0KDQoNCkZvciB0aGUgYnJlYWtmYXN0IGRhdGEsIEkgd291bGQgZWl0aGVyIGNvbGxlY3QgbW9yZSBkYXRhIG9yIGV4Y2x1ZGUgdGhvc2Ugbm9pc2llc3QgZGF0YSBwb2ludHMgKG9yIG9mZmVyZWQgdGhlbSBhbm90aGVyIHR5cGUgb2YgYnJlYWtmYXN0KS4gT3RoZXJ3aXNlLCB0aGUgcGxvdCBhbHJlYWR5IHByb3ZpZGVzIHNvbWUgc3RydWN0dXJlIGFuZCBldmVuIGRpbWVuc2lvbnM6IEQxIGlzIGFib3V0IHN1Z2FyeS9mYXR0ZW5pbmcgdnMuIHNhdm91cnkgaXRlbXMsIEQyIGlzIGFib3V0IHNvZnQgdnMuIGNydW5jaHkgYnJlYWQuDQoNCg0KIyMjIE5vdywgY2FuIHVuZm9sZGluZyBhcHBseSB0byB0aGUgdHlwZSBvZiBkYXRhIHRoYXQgU2VtYW50aWMgRGlmZmVyZW50aWFsIHByb2R1Y2VzPyANCg0KU2luY2UgdGhlIFNEIGFsc28gZmVhdHVyZXMgb2JqZWN0cyBhbmQgdGhlaXIgdmFsdWVzIGF0IHNjYWxlcywgaXQgaXMgcG9zc2libGUuDQoNCldoYXQgeW91IG5lZWQgdG8gZG8gZmlyc3QgdGhvdWdoIGlzIHRvIHRyYW5zZm9ybSB2YWx1ZXMgaW50byBkaXNzaW1pbGFyaXRpZXMuDQoNCkV4YW1wbGU6DQoNCmBgYHtyfQ0Kc2QgPC0gbWF0cml4KGMoLTEsMiwzLA0KICAgICAgICAgICAgICAgLTMsLTMsLTMsDQogICAgICAgICAgICAgICAzLC0yLDEpLCBieXJvdyA9IFQsIG5yb3cgPSAzKQ0Kcm93Lm5hbWVzKHNkKSA8LSBjKCJyZWFsIFNlbGYiLCAiYW50aS1TZWxmIiwgInJhdGlvbmFsIFNlbGYiKQ0KY29sbmFtZXMoc2QpIDwtIGMoImV2YWx1YXRpb24iLCAic3RyZW5ndGgiLCAiYWN0aXZpdHkiKQ0Kc2QNCnNkMSA8LSBzZCArIDMgIyBtYWtlIHRoZW0gbm9uLW5lZ2F0aXZlDQpzZDENCmBgYA0KDQpVc2UgdGhlIGBzbWFjb2Y6OnNpbTJkaXNzKClgIGZ1bmN0aW9uIHRvIGNvbnZlcnQgYW55IHNpbWlsYXJpdGllcyB0byBkaXNzaW1pbGFyaXRpZXMgKHNldmVyYWwgbWV0aG9kcyBvZiB0cmFuc2Zvcm1hdGlvbiBhcmUgYXZhaWxhYmxlKToNCg0KYGBge3J9DQpzZDIgPC0gc2ltMmRpc3Moc2QxLCBtZXRob2QgPSAicmV2ZXJzZSIpDQpzZDINCnNkX3VuZiA8LSB1bmZvbGRpbmcoc2QyKQ0KcGxvdChzZF91bmYpDQpgYGANCg0KDQpgYGB7cn0NCiMgbGlicmFyeShnZ3Bsb3QyKQ0KIyBsaWJyYXJ5KGdncmVwZWwpDQpjb25mX2l0ZW1zIDwtIGFzLmRhdGEuZnJhbWUoc2RfdW5mJGNvbmYuY29sKQ0KY29uZl9wZXJjIDwtIGFzLmRhdGEuZnJhbWUoc2RfdW5mJGNvbmYucm93KQ0KcCA8LSBnZ3Bsb3QoY29uZl9wZXJjLCBhZXMoeCA9IEQxLCB5ID0gRDIpKSANCnAgKyBnZW9tX3BvaW50KHNpemUgPSAxLCBjb2xvdXIgPSAicmVkIiwgYWxwaGEgPSAwLjUpICsgDQogIGNvb3JkX2ZpeGVkKHhsaW0gPSBjKC0xLjUsIDEuNSksIHlsaW0gPSBjKC0xLjUsIDEuNSkpICsgDQogIHhsYWIoIiIpICsNCiAgeWxhYigiIikgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gRDEsIHkgPSBEMiksIGNvbmZfaXRlbXMsIGNvbG91ciA9ICJjYWRldGJsdWUiKSArIA0KICBnZW9tX3RleHRfcmVwZWwoYWVzKHggPSBEMSwgeSA9IEQyLCBsYWJlbCA9IHJvd25hbWVzKGNvbmZfcGVyYykpLCANCiAgICAgICAgICAgIGNvbmZfcGVyYywgY29sb3VyID0gInJlZCIsIHZqdXN0ID0gLTAuNSkgKyANCiAgZ2VvbV90ZXh0X3JlcGVsKGFlcyh4ID0gRDEsIHkgPSBEMiwgbGFiZWwgPSByb3duYW1lcyhjb25mX2l0ZW1zKSksIA0KICAgICAgICAgICAgY29uZl9pdGVtcywgY29sb3VyID0gImNhZGV0Ymx1ZSIsIHZqdXN0ID0gMC44KSArDQogIGdndGl0bGUoIlVuZm9sZGluZyBmb3IgU2VtYW50aWMgRGlmZmVyZW50aWFsIikgKw0KICB0aGVtZV9idygpDQpwMSA8LSBwICsgZ2VvbV9wb2ludChzaXplID0gMSwgY29sb3VyID0gInJlZCIsIGFscGhhID0gMC41KSArIA0KICBjb29yZF9maXhlZCh4bGltID0gYygtMS41LCAxLjUpLCB5bGltID0gYygtMS41LCAxLjUpKSArIA0KICB4bGFiKCIiKSArDQogIHlsYWIoIiIpICsNCiAgZ2VvbV9wb2ludChhZXMoeCA9IEQxLCB5ID0gRDIpLCBjb25mX2l0ZW1zLCBjb2xvdXIgPSAiY2FkZXRibHVlIikgKyANCiAgZ2VvbV90ZXh0X3JlcGVsKGFlcyh4ID0gRDEsIHkgPSBEMiwgbGFiZWwgPSByb3duYW1lcyhjb25mX3BlcmMpKSwgDQogICAgICAgICAgICBjb25mX3BlcmMsIGNvbG91ciA9ICJyZWQiLCB2anVzdCA9IC0wLjUpICsgDQogICNnZW9tX3RleHRfcmVwZWwoYWVzKHggPSBEMSwgeSA9IEQyLCBsYWJlbCA9IHJvd25hbWVzKGNvbmZfaXRlbXMpKSwgDQogICMgICAgICAgICAgY29uZl9pdGVtcywgY29sb3VyID0gImNhZGV0Ymx1ZSIsIHZqdXN0ID0gMC44KSArDQogIGdndGl0bGUoIlVuZm9sZGluZyBmb3IgU2VtYW50aWMgRGlmZmVyZW50aWFsIikgKw0KICB0aGVtZV9idygpDQpgYGANCg0KVGhlIGNvbmZpZ3VyYXRpb24gcGxvdCBhYm92ZSBzaG93cyBib3RoIHRoZSBvcmlnaW5hbCBzY2FsZXMgYW5kIHRoZSBvYmplY3RzLg0KDQpJbiBjb250cmFzdCwgdGhlIE1EUyBjb25maWd1cmF0aW9uIHBsb3Qgd2lsbCBvbmx5IHNob3cgdGhlIG9iamVjdHMgZHVlIHRvIHRoZSBmYWN0IHRoYXQgaXQgaXMgYmFzZWQgb24gZGlzdGFuY2VzLiBDb21wYXJlOg0KDQpgYGB7cn0NCnNkX2Rpc3QgPC0gZGlzdChzZCwgbWV0aG9kID0gImV1Y2xpZGVhbiIsIGRpYWcgPSBUKQ0Kc2RfZGlzdA0Kc2RfbWRzIDwtIG1kcyhzZF9kaXN0KQ0KcGxvdChzZF9tZHMpDQpgYGANCg0KYGBge3J9DQpjb25mX3NkIDwtIGFzLmRhdGEuZnJhbWUoc2RfbWRzJGNvbmYpDQpwbSA8LSBnZ3Bsb3QoY29uZl9zZCwgYWVzKA0KICAgIHggPSBEMSwNCiAgICB5ID0gRDIsDQogICAgbGFiZWwgPSByb3duYW1lcyhjb25mX3NkKQ0KICApKQ0KcDIgPC0gcG0gKyBnZW9tX3BvaW50KHNpemUgPSAwLjkpICsgDQogICAgZ2VvbV90ZXh0X3JlcGVsKHNpemUgPSAzLjUsIHZqdXN0ID0gLTAuOCkgKw0KICAgIGNvb3JkX2ZpeGVkKHhsaW0gPSBjKC0xLjUsIDEuNSksIHlsaW0gPSBjKC0xLjUsIDEuNSkpICsgDQogICAgZ2d0aXRsZSgiTURTIGZvciBTZW1hbnRpYyBEaWZmZXJlbnRpYWwiKSArIA0KICAgIHhsYWIoIiIpICsNCiAgICB5bGFiKCIiKSArDQogICAgdGhlbWVfYncoKQ0KbGlicmFyeShwYXRjaHdvcmspDQpwMSB8IHAyDQpgYGANCg0KDQoNCiMjIFRoZSBTY2h3YXJ0eiBCYXNpYyBWYWx1ZXMgY2lyY2xlIA0KDQohW1RoZSBTY2h3YXJ0eiBjaXJjbGUgKGZyb20gaHR0cHM6Ly9tYWtzaW1ydWRuZXYuY29tLyldKGh0dHBzOi8vbWFrc2ltcnVkbmV2LmNvbS93cC1jb250ZW50L3VwbG9hZHMvMjAxOC8wMy9jaXJjbGUucG5nKQ0KDQpJIHJlY29tbWVuZCByZWFkaW5nIHRoZSBwYXBlciAiTWVhc3VyaW5nIHRoZSA0IEhpZ2hlci1PcmRlciBWYWx1ZXMgaW4gU2Nod2FydHoncyBUaGVvcnk6IEEgVmFsaWRhdGlvbiBvZiBhIDE3LUl0ZW0gSW52ZW50b3J5IiA8aHR0cHM6Ly9wc3lhcnhpdi5jb20veG1oNT46IE1EUyBpcyBvbmUgb2YgdGhlIDggc3RlcHMgb2YgYW5hbHlzaXMgdGhlcmUuIFRoZSBnb2FsIGlzIHRvIG1ha2Ugc3VyZSB0aGF0IHRoZSBzaG9ydGVyIHZlcnNpb24gb2YgdGhlIHF1ZXN0aW9ubmFpcmUgZm9sbG93cyB0aGUgcXVhc2ktY2lyY3VtcGxleCBzaGFwZSBvZiB0aGUgdXN1YWwgU2Nod2FydHogY2lyY2xlLg0KDQpMb29rIChwLjE3KSBob3cgZWxlZ2FudGx5L2JyaWVmbHkgdGhlIE1EUyBzb2x1dGlvbiBpcyBkaXNjdXNzZWQuDQoNCiFbXShtZHNfMTcucG5nKQ0KDQoNCiMjIyBTdW1tYXJ5IG9mIE1EUyB2cy4gUENBIGFuZCBDb0E6DQoNCi0gTURTIGtlZXBzIHRoZSBkaXN0YW5jZXMgYmV0d2VlbiBvYmplY3RzIGludGVycHJldGFibGUsIGVzcGVjaWFsbHkgd2hlbiB0aGVyZSBhcmUgYm90aCByb3dzIGFuZCBjb2x1bW5zIGluIHRoZSBwbG90LiA9PiBGaW5kIGFuZCBpbnRlcnByZXQgY2x1c3RlcnMgb2YgcG9pbnRzLiBIb3dldmVyLCB0aGlzIGlzIG5vdCBwb3NzaWJsZSBpbiBzeW1tZXRyaWMgY29ycmVzcG9uZGVuY2UgYW5hbHlzaXMgYmlwbG90cy4NCg0KLSBEaXN0YW5jZXMgYXJlICMxIGJ1dCBkaW1lbnNpb25zIGFyZSBvbmx5IG9jY2FzaW9uYWxseSBtZWFuaW5nZnVsIGluIE1EUy4gU29tZXRpbWVzIHRoZXkgYWxzbyBtYWtlIHNlbnNlIGFuZCBjYW4gYmUgc3VtbWFyaXplZCBiZWF1dGlmdWxseSAoZS5nLiwgU2Nod2FydHogaGlnaGVyLW9yZGVyIHZhbHVlcykuIEJ5IGNvbnRyYXN0LCBkaW1lbnNpb25zIGFyZSBpbXBvcnRhbnQgZm9yIHVuZGVyc3RhbmRpbmcgY29ycmVzcG9uZGVuY2UgcGxvdCBiaXBsb3RzIGFuZCBzb21lIFBDQSBiaXBsb3RzLg0KDQotIExhYmVsbGVkIG9yIG5vdCwgZGltZW5zaW9ucyBhcmUgZXF1YWxseSBpbXBvcnRhbnQgaW4gTURTLiBBcyBhIHJlc3VsdCwgY29uZmlndXJhdGlvbiBwbG90cyBjYW4gYmUgZmxpcHBlZCBpbiB3aGF0ZXZlciBkaXJlY3Rpb24gdGhleSBsb29rIGJldHRlciBmb3IgeW91LiBCeSBjb250cmFzdCwgdGhlIDFzdCBkaW1lbnNpb24gaXMgdGhlIG1vc3QgaW1wb3J0YW50IG9uZSBpbiBQQ0EgYW5kIENvQSBhbGlrZS4NCg0KLSBGaW5hbGx5LCBNRFMgdGFrZXMgYXMgaW5wdXQgZGlzdGFuY2UgbWF0cmljZXMsIG9yIGV2YWx1YXRpb25zIGxpa2UgcmF0aW5ncyBhbmQgcmFua3MuIEhvd2V2ZXIsIENvQSBjYW4gZGVhbCB3aXRoIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyBvZiBlcXVhbCBpbXBvcnRhbmNlICh2cy4gImlkZWFsIHBvaW50cyBhbmQgb2JqZWN0IHBvaW50cyIgaW4gTURTKS4NCg0KLSBNRFMgY2FuIGFsc28gaGFuZGxlIGNvcnJlbGF0aW9uIG1hdHJpY2VzLCBsaWtlIFBDQS4gQnV0IHRoZWlyIHB1cnBvc2VzIGFyZSBkaWZmZXJlbnQuIE1EUyBhaW1zIHRvIHBsb3QgdGhlIHBlcmNlcHR1YWwsIGxvdy1kaW1lbnNpb25hbCBzcGFjZSBhbmQgZGlzdGFuY2VzIGJldHdlZW4gb2JqZWN0cywgd2hpbGUgUENBIGNhcHR1cmVzIG1heGltdW0gdmFyaWFuY2UgaW4gdGhlIGxvd2VzdCBzdWl0YWJsZSBudW1iZXIgb2YgKG5vdCBuZWNlc3NhcmlseSB0d28pIGNvbXBvbmVudHMuDQoNCkNvbXBhcmUgdGhlIGJpcGxvdHMgZnJvbSA8aHR0cHM6Ly9ycHVicy5jb20vc2hpcm9rYW5lci9wY2E+IGFuZCB0aGUgZm9sbG93aW5nIHNvbHV0aW9uczoNCg0KYGBge3J9DQpkYXRhX3JhdyA8LSByZWFkLmNzdigiVEhFMjAyMS5jc3YiKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGNvcnJwbG90KQ0KIyBkYXRhX3Jhd1ssMzoxMV0gJT4lIA0KIyAgIGNvcigpICU+JSANCiMgICBjb3JycGxvdCgpIA0KZGF0YV9yYXcgPC0gZGF0YV9yYXcgJT4lIA0KICByZWxvY2F0ZShzdGF0c19wY19pbnRsX3N0dWRlbnRzLCAuYWZ0ZXIgPSBzY29yZXNfaW50ZXJuYXRpb25hbF9vdXRsb29rKQ0KZGF0YV9jb3IgPC0gZGF0YV9yYXdbICwgMzoxMV0gJT4lIGNvcigpIA0KZGF0YV91bmkgPC0gc2ltMmRpc3MoZGF0YV9jb3IsIG1ldGhvZCA9ICJjb3JyIiwgdG8uZGlzdCA9IFQpDQp1bmkgPC0gbWRzKGRhdGFfdW5pKQ0KdW5pICMgMC4yMTQNCiNwbG90KHVuaSkNCnVuaTIgPC0gbWRzKGRhdGFfdW5pLCB0eXBlID0gIm1zcGxpbmUiKQ0KdW5pMg0KcGxvdCh1bmkyKQ0KI2hlYWQoZGF0YV9yYXcpDQpyb3cubmFtZXMoZGF0YV9yYXcpIDwtIGRhdGFfcmF3JG5hbWUNCmRhdGFfdW5pMiA8LSBzaW0yZGlzcyhkYXRhX3Jhd1sgLCAzOjExXSwgbWV0aG9kID0gMTAwKQ0KI2hlYWQoZGF0YV91bmkyKQ0KbWluKGRhdGFfdW5pMlsgLCA3XSkNCmRhdGFfdW5pMlsgLDddIDwtIGRhdGFfdW5pMlsgLDddICsgMjIyMDAyDQptaW4oZGF0YV91bmkyWyAsOF0pDQpkYXRhX3VuaTJbICw4XSA8LSBkYXRhX3VuaTJbICw4XSArIDkuMQ0KI3N1bW1hcnkoZGF0YV9yYXcpDQojc3VtbWFyeShkYXRhX3VuaTIpDQp1bmkzIDwtIHVuZm9sZGluZyhkYXRhX3VuaTIpDQp1bmkzDQojcGxvdCh1bmkzKQ0KIyBkYXRhX3VuaTMgPC0gZGF0YV91bmkyWyAsIC03XQ0KIyB1bmk0IDwtIHVuZm9sZGluZyhkYXRhX3VuaTMpDQojIHVuaTQNCiMgcGxvdCh1bmk0KQ0KYGBgDQoNCg0KDQpUbyBleHBsb3JlL3ZhbGlkYXRlIHRoZSBiYXNpYyB2YWx1ZXMgY2lyY2xlIG9uIHRoZSBpbmRpdmlkdWFsIGxldmVsLCBzZWUgIkV4YW1wbGUgSUk6IFVuZm9sZGluZyBvbiBwZXJzb25hbCB2YWx1ZXMiLCBwcC43ODQtNzg1IGluOiBNdWx0aXZhcmlhdGUgQmVoYXYgUmVzDQouIDIwMTYgTm92LURlYzs1MSg2KTo3NzItNzg5LiBkb2k6IDEwLjEwODAvMDAyNzMxNzEuMjAxNi4xMjM1OTY2LiBFcHViIDIwMTYgTm92IDEuIDxodHRwczovL3d3dy5yZXNlYXJjaGdhdGUubmV0L3Byb2ZpbGUvUGF0cmljay1NYWlyLTIvcHVibGljYXRpb24vMzA5NjE3OTQzX0dvb2RuZXNzLW9mLUZpdF9Bc3Nlc3NtZW50X2luX011bHRpZGltZW5zaW9uYWxfU2NhbGluZ19hbmRfVW5mb2xkaW5nL2xpbmtzLzU5ZjRjNmY1YWNhMjcyNjA3ZTJhODcwNi9Hb29kbmVzcy1vZi1GaXQtQXNzZXNzbWVudC1pbi1NdWx0aWRpbWVuc2lvbmFsLVNjYWxpbmctYW5kLVVuZm9sZGluZy5wZGY+DQoNCg0KDQooT3B0aW9uYWxseTogY29tcGFyZSB0byB0aGUgdmVjdG9yIG1vZGVsIG9mIHVuZm9sZGluZyAtICJpbmRpdmlkdWFscyAocm93cykgcmVwcmVzZW50ZWQgYXMgdmVjdG9ycyBpbiB0aGUgYmlwbG90Iik6DQo8aHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3NtYWNvZi92aWduZXR0ZXMvc21hY29mLnBkZj4NCg0KYGBge3J9DQojcGxvdCh2bXUoc2QyKSkNCmBgYA0KDQooT3B0aW9uYWxseSkgU2VlIGFsc28gIG11bHRpZGltZW5zaW9uYWwgcHJlZmVyZW5jZSBhbmFseXNpcyBmcm9tIHRoZSAicG1yIiBwYWNrYWdlLg0KYHBtcmAgaXMgYSBwYWNrYWdlIGZvciBhbGwga2luZHMgb2YgYW5hbHlzZXMgZm9yIHJhbmtpbmcgZGF0YS4gPGh0dHBzOi8vYm1jbWVkcmVzbWV0aG9kb2wuYmlvbWVkY2VudHJhbC5jb20vYXJ0aWNsZXMvMTAuMTE4Ni8xNDcxLTIyODgtMTMtNjU+DQoNCg0KDQpTZWUgYWxzbzogRmluY2gsIEFuYWx5emluZyByYW5rZWQgZGF0YSAoMjAyMikgPGh0dHBzOi8vc2Nob2xhcndvcmtzLnVtYXNzLmVkdS9jZ2kvdmlld2NvbnRlbnQuY2dpP2FydGljbGU9MTU4OSZjb250ZXh0PXBhcmU+DQo=