Permutation distribution clustering consists of a clustering method for time series, in which the Hellinger square distance, a metric approximation of the Kullback-Leibler divergence, is used as a measure of dissimilarity between the permutation distribution.

To comparatively evaluate the package’s functionalities, a data set of images of SAR textures linearized by the Hilbert-Peano curves was applied. Our goal is to verify the efficiency of these techniques in the analysis of regions.

source("Band_Pompe_Analysis.R")
if(!require(pdc)) install.packages("pdc")
if(!require(NATS)) install.packages("NATS")
if(!require(raster)) install.packages("raster")
if(!require(lattice)) install.packages("lattice")
if(!require(microbenchmark)) install.packages("microbenchmark")

Importing the dataset

For this analysis, three SAR images with different regions were used, they are:

A total of 160 samples were considered during the investigation, with 40 forest regions in Guatemala, 80 ocean regions in Cape Canaveral and 40 urban regions in the city of Munich.

#Defining the number of samples from each region analyzed
ns.guatemala = 40
dimen.guatemala = matrix(nrow = ns.guatemala, ncol = 4)
ns.canaveral.behavior1 = 40
dimen.canaveral.behavior1 = matrix(nrow = ns.canaveral.behavior1, ncol = 4)
ns.canaveral.behavior2 = 40
dimen.canaveral.behavior2 = matrix(nrow = ns.canaveral.behavior2, ncol = 4)
ns.munich = 40
dimen.munich = matrix(nrow = ns.munich, ncol = 4)
n.total = (ns.guatemala + ns.canaveral.behavior1 + ns.canaveral.behavior2 + ns.munich)
#The SAR data is available on https://drive.google.com/file/d/1jtbOcYwQfysfcUp4UhoA7lSl4_tPIqfa/view?usp=sharing and
# correspond to HHHH band of an image taken from the Cape Canaveral (acquired Sep 22, 2016)
#Ocean regions in Cape Canaveral
row1 = c(50, 100, 150, 200, 250, 350, 450, 550, 650, 750)
row2 = c(50, 100, 150, 200, 250, 300, 350, 400, 450, 550)
row3 = c(50, 150, 250, 350, 450, 550, 650, 750, 800, 850)
row4 = c(250, 350, 450, 550, 650, 750, 850, 950, 1050)
row5 = c(50, 150, 250, 350, 450, 550, 650, 750, 850, 950, 1050)
row6 = c(50, 150, 250, 350, 450, 550, 650, 750, 800, 850, 950)
row7 = c(50, 150, 250, 350, 450, 550, 650, 750, 850, 950)
cols = c(1700, 1850, 1550, 1400, 1, 200, 350, 550)
#{Behavior 1}
dimen.canaveral.behavior1[1:9,] = c(row1[1:9], rep(128, 9), rep(cols[1], 9), rep(128, 9))
dimen.canaveral.behavior1[10,] = c(row1[10], 128, 1300, 128)
dimen.canaveral.behavior1[11:19,] = c(row2[1:9], rep(128, 9), rep(cols[2], 9), rep(128, 9))
dimen.canaveral.behavior1[20,] = c(row2[10], 128, 1350, 128)
dimen.canaveral.behavior1[21:30,] = c(row3, rep(128, 10), rep(cols[3], 10), rep(128, 10))
dimen.canaveral.behavior1[31:40,] = c(row3, rep(128, 10), rep(cols[4], 10), rep(128, 10))
#{Behavior 2}
dimen.canaveral.behavior2[1:9,] = c(row4, rep(128, 9), rep(cols[5], 9), rep(128, 9))
dimen.canaveral.behavior2[10:20,] = c(row5, rep(128, 11), rep(cols[6], 11), rep(128, 11))
dimen.canaveral.behavior2[21:30,] = c(row7, rep(128, 10), rep(cols[7], 10), rep(128, 10))
dimen.canaveral.behavior2[31:40,] = c(row7, rep(128, 10), rep(cols[8], 10), rep(128, 10))
#The SAR data is available on https://drive.google.com/file/d/1pO6p_UI9Cgdci9y6jVynAv8SrrAvv7K8/view?usp=sharing and
# correspond to HHHH band of an image taken from the Munich, Germany (acquired Jun 5, 2015) 
#Urban regions in Munich
row1 = seq(3000, 3950, by = 50)
row2 = rep(c(4300, 4350), 5)
row3 = c(rep(seq(2300, 2450, by = 50), 2), 2400, 2450)
cols = 400
cols2 = c(1300, 1300, 1350, 1350, 1400, 1400, 1450, 1450, 1500, 1500)
cols3 = c(rep(500, 4), rep(400, 4), rep(300, 2))
dimen.munich[1:20,] = c(row1, rep(128, 20), rep(cols, 20), rep(128, 20))
dimen.munich[21:30,] = c(row2, rep(128, 10), cols2, rep(128, 10))
dimen.munich[31:40,] = c(row3, rep(128, 10), cols3, rep(128, 10))
#Forest regions in Guatemala
row1 = seq(5150, 6100, by = 50)
row2 = seq(5200, 5650, by = 50)
row3 = seq(4100, 4200, by = 50)
row4 = seq(1000, 1150, by = 50)
cols = c(2700, 2800, 2930, 1930, 1870)
dimen.guatemala[1:20,] = c(row1, rep(128, 20), rep(cols[1], 20), rep(128, 20))
dimen.guatemala[21:30,] = c(row2, rep(128, 10), rep(cols[2], 10), rep(128, 10))
dimen.guatemala[31:33,] = c(row3, rep(128, 3), rep(cols[3], 3), rep(128, 3))
dimen.guatemala[34:37,] = c(row4, rep(128, 4), rep(cols[4], 4), rep(128, 4))
dimen.guatemala[38:40,] = c(row4[1:3], rep(128, 3), rep(cols[5], 3), rep(128, 3))
hilbertcurve = unlist(read.table("../../Data/Hilbert/HilbertCurves128.txt")) + 1
time.series = matrix(ncol = 160, nrow = 128*128)
 #Guatemala
    sar_data = raster(paste( "../../Data/guatemala", "/HHHH", ".grd", sep = ""))
    for(j in c(1:ns.guatemala)){
      img = getValuesBlock(sar_data, row = dimen.guatemala[j,1], nrows = dimen.guatemala[j,2], col = dimen.guatemala[j,3], ncols = dimen.guatemala[j,4], format = "matrix")
      time.series[,j] = img[hilbertcurve]/max(img[hilbertcurve])
    }
    #Cape Canaveral - behavior 1
    sar_data = raster(paste("../../Data/cape", "/HHHH", ".grd", sep = ""))
    for(j in c(1:ns.canaveral.behavior1)){
      img = getValuesBlock(sar_data, row = dimen.canaveral.behavior1[j,1], nrows = dimen.canaveral.behavior1[j,2], col = dimen.canaveral.behavior1[j,3], ncols = dimen.canaveral.behavior1[j,4], format = "matrix")
      time.series[,ns.guatemala + j] = img[hilbertcurve]/max(img[hilbertcurve])
    }
    #Cape Canaveral - behavior 2
    sar_data = raster(paste("../../Data/cape", "/HHHH", ".grd", sep = ""))
    for(j in c(1:ns.canaveral.behavior2)){
      img = getValuesBlock(sar_data, row = dimen.canaveral.behavior2[j,1], nrows = dimen.canaveral.behavior2[j,2], col = dimen.canaveral.behavior2[j,3], ncols = dimen.canaveral.behavior2[j,4], format = "matrix")
      time.series[,(ns.canaveral.behavior1 + ns.guatemala) + j] = img[hilbertcurve]/max(img[hilbertcurve])
    }
    #Munich
    sar_data = raster(paste("../../Data/munich", "/HHHH", ".grd", sep = ""))
    for(j in c(1:ns.munich)){
      img = getValuesBlock(sar_data, row = dimen.munich[j,1], nrows = dimen.munich[j,2], col = dimen.munich[j,3], ncols = dimen.munich[j,4], format = "matrix")
      time.series[,(ns.canaveral.behavior1 + ns.canaveral.behavior2 + ns.guatemala) + j] = img[hilbertcurve]/max(img[hilbertcurve])
    }
    

Analysing the BP distribution

In this paper, is used an explicit implementation of a sorting algorithm as a tree of if-clauses with each inner node being a pairwise comparison of items and each leaf having a unique integer label between 0 and m! representing the permutation index. Since no items have to swap places, no subroutines are called, and no extra memory is required to be reserved on the stack or the heap, this implementation is more efficient. A sample output of this algorithm is shown below:

function y = get_codeword(x)
  if x(3) < x(1)
    if x(2) < x(1)
      if x(3) < x(2)
            y=1;
      else
            y=2;
      end
    else
      y=3;
    end
  else
    if x(2) < x(1)
        y=4;
    else
        if x(3) < x(2)
              y=5;
        else
              y=6;
        end
    end
end

Below we can see a comparison of the execution time between the naive implementation of the Bandt-Pompe symbolization and the “optimized” version. We evaluate time elapsed, for the system and user. Both routines were implemented in C and linked by R through the Rcpp package.

cat("\n BP distribution performance \n\n", "NATS: ", system.time(bandt.pompe(time.series[,1], 3, 1)), "\n pdc: ", system.time(codebook(time.series[,1], 3, 1)), "\n")

 BP distribution performance 

 NATS:  0.368 0 0.368 0 0 
 pdc:  0.001 0 0.001 0 0 

Another feature present in the package that also exists in NATS is the plot of the histogram of ordinal patterns.

x = time.series[,1]
barplot(x, xlab="Permutation Distribution")

Entropy heuristic

The choice of parameters m (embedding dimension) and t (time delay) for the time-delayed embedding is crucial for the performance of the clustering. In this way, these observations are formalized in a heuristic that guides researchers in choosing an incorporation dimension, if no prior information is available. This heuristic aims to choose an incorporation dimension that is maximally expressive, that is, the DP must be maximally different from a uniform distribution and for that, permutation entropy is used.

Thus, the heuristic chooses the embedding dimension with the lowest average, normalized permutation entropy:

\[ arg\,min_{m} \sum_{P \in \mathbb{P}} \mathbb{H}(m) \]

In a similar vein, different choices of the time delay t can be selected. Therefore, the hyper-parameters selected for the analysis were:

mine = entropyHeuristic(time.series, t.min = 1, t.max = 10)
summary(mine)
Embedding dimension:  7 [ 3,4,5,6,7 ]   
Time delay:  1 [ 1,2,3,4,5,6,7,8,9,10 ]

An illustration of a grid-search on the parameters m and t to determine differences between the regions is given below:

rr plot(mine)

Hellinger Distance

Hellinger’s square distance is used to incorporate the permutation distributions of time series in a metric space. Let \(\mathbb{P} = (p_1, p_2, \dots , p_n)\) and \(\mathbb{Q} = (q_1, q_2, \dots , q_n)\) be two permutation distributions. The squared Hellinger distance is:

\[ D(P, Q) = \frac{1}{\sqrt{2}} || \sqrt{P} - \sqrt{Q} ||^2 \]

The squared Hellinger distance can be derived by means of a Taylor approximation of the KL divergence.

Helling distance between two probability distributions

Finally, the pdc package also provides the functionality to calculate the Hellinger distance, however it is done between two time series. In NATS this metric is calculated using the uniform probability distribution as a reference.

x = codebook(time.series[,1], m = 3)
y = codebook(time.series[,2], m = 3)
cat("Hellinger (x, y): ", hellingerDistance(x,y), " - Hellinger (x, UD): ", HellingerDistance(x), " - Hellinger (y, UD): ", HellingerDistance(y))
Hellinger (x, y):  0.007199873  - Hellinger (x, UD):  0.03381798  - Hellinger (y, UD):  0.03489021

Distance matrix computed by the divergence between permutation distributions of time series

A distance matrix can also be calculated. It is through this matrix that clustering is done.

D = pdcDist(time.series)
levelplot(as.matrix(D))

Heuristic to determine the number of clusters

Just as a heuristic was proposed for choosing the hyper-parameters of the Bandt-Pompe symbolization, another heuristic is also proposed for choosing the number of clusters.

Hierarchical cluster analysis for time series

Based on a dissimilarity measure, clusters are constructed by iteratively merging clusters until there is only a single top cluster left. This leads to a hierarchy of clusters.

The distances between sets of time series are calculated by the complete linkage method, which is defined as the dissimilarity between two clusters \(C_i\) and \(C_j\) :

\[ d_{complete}(C_i, C_j) = max_{x \in C_i, y \in C_j} D(x,y) \]

The resulting binary tree is typically visualized in a dendrogram depicting the tree with branch heights that reflect the distance between clusters.

clust = pdclust(time.series)
plot(clust)

Leave-one-out cross-validation scheme. The predictive accuracy of the cluster is estimated

Using loo1nn, which implements a leave-one-out cross-validation scheme, we estimate the predictive accuracy of the clustering:

truth = c(rep(1,40), rep(2,40), rep(2,40), rep(3, 40))
loo1nn(clust, truth)
[1] 78.125

Also, the convex hull of each cluster can be plotted as the shaded area in the principal coordinate space. The resulting plot is shown below and can be created using the command mdsPlot:

mdsPlot(clust, truth, col = c("lightgray", "lightblue", "lightgreen"))

LS0tCnRpdGxlOiAncGRjIHBhY2thZ2U6IGFuIGV2YWx1YXRpb24gb2YgZnVuY3Rpb25hbGl0aWVzJwphdXRob3I6ICJFZHVhcmRhIENoYWdhcyIKZGF0ZTogIkFwciAxMSwgMjAyMCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0Ci0tLQoKUGVybXV0YXRpb24gZGlzdHJpYnV0aW9uIGNsdXN0ZXJpbmcgY29uc2lzdHMgb2YgYSBjbHVzdGVyaW5nIG1ldGhvZCBmb3IgdGltZSBzZXJpZXMsIGluIHdoaWNoIHRoZSBIZWxsaW5nZXIgc3F1YXJlIGRpc3RhbmNlLCBhIG1ldHJpYyBhcHByb3hpbWF0aW9uIG9mIHRoZSBLdWxsYmFjay1MZWlibGVyIGRpdmVyZ2VuY2UsIGlzIHVzZWQgYXMgYSBtZWFzdXJlIG9mIGRpc3NpbWlsYXJpdHkgYmV0d2VlbiB0aGUgcGVybXV0YXRpb24gZGlzdHJpYnV0aW9uLgoKVG8gY29tcGFyYXRpdmVseSBldmFsdWF0ZSB0aGUgcGFja2FnZSdzIGZ1bmN0aW9uYWxpdGllcywgYSBkYXRhIHNldCBvZiBpbWFnZXMgb2YgU0FSIHRleHR1cmVzIGxpbmVhcml6ZWQgYnkgdGhlIEhpbGJlcnQtUGVhbm8gY3VydmVzIHdhcyBhcHBsaWVkLgpPdXIgZ29hbCBpcyB0byB2ZXJpZnkgdGhlIGVmZmljaWVuY3kgb2YgdGhlc2UgdGVjaG5pcXVlcyBpbiB0aGUgYW5hbHlzaXMgb2YgcmVnaW9ucy4KCmBgYHtyfQpzb3VyY2UoIkJhbmRfUG9tcGVfQW5hbHlzaXMuUiIpCmlmKCFyZXF1aXJlKHBkYykpIGluc3RhbGwucGFja2FnZXMoInBkYyIpCmlmKCFyZXF1aXJlKE5BVFMpKSBpbnN0YWxsLnBhY2thZ2VzKCJOQVRTIikKaWYoIXJlcXVpcmUocmFzdGVyKSkgaW5zdGFsbC5wYWNrYWdlcygicmFzdGVyIikKaWYoIXJlcXVpcmUobGF0dGljZSkpIGluc3RhbGwucGFja2FnZXMoImxhdHRpY2UiKQppZighcmVxdWlyZShtaWNyb2JlbmNobWFyaykpIGluc3RhbGwucGFja2FnZXMoIm1pY3JvYmVuY2htYXJrIikKYGBgCgojIyNJbXBvcnRpbmcgdGhlIGRhdGFzZXQKCkZvciB0aGlzIGFuYWx5c2lzLCB0aHJlZSBTQVIgaW1hZ2VzIHdpdGggZGlmZmVyZW50IHJlZ2lvbnMgd2VyZSB1c2VkLCB0aGV5IGFyZToKCiogU2llcnJhIGRlbCBMYWNhbmRvbiBOYXRpb25hbCBQYXJrLCBHdWF0ZW1hbGEgKHB1cmNoYXNlZCBBcHJpbCAxMCwgMjAxNSksIGF2YWlsYWJsZSBhdCBbaHR0cHM6Ly91YXZzYXIuanBsLm5hc2EuZ292L2NnaS1iaW4vcHJvZHVjdC5wbD9qb2JOYW1lPUxhY2FuZF8zMDIwMl8xNTA0M18KMDA2XzE1MDQxMF9MMDkwX0NYXzAxICMgZGF0YV0gKGh0dHBzOi8vdWF2c2FyLmpwbC5uYXNhLmdvdi9jZ2ktYmluL3Byb2R1Y3QucGw/am9iTmFtZT1MYWNhbmRfMzAyMDJfMTUwNDNfCjAwNl8xNTA0MTBfTDA5MF9DWF8wMSAjIGRhdGEpOwoKKiBPY2VhbmljIHJlZ2lvbnMgb2YgQ2FwZSBDYW5hdmVyYWwgKGFjcXVpcmVkIG9uIFNlcHRlbWJlciAyMiwgMjAxNik7CgoqIFVyYmFuIGFyZWEgb2YgdGhlIGNpdHkgb2YgTXVuaWNoLCBHZXJtYW55IChhY3F1aXJlZCBvbiBKdW5lIDUsIDIwMTUpLgoKQSB0b3RhbCBvZiAxNjAgc2FtcGxlcyB3ZXJlIGNvbnNpZGVyZWQgZHVyaW5nIHRoZSBpbnZlc3RpZ2F0aW9uLCB3aXRoIDQwIGZvcmVzdCByZWdpb25zIGluIEd1YXRlbWFsYSwgODAgb2NlYW4gcmVnaW9ucyBpbiBDYXBlIENhbmF2ZXJhbCBhbmQgNDAgdXJiYW4gcmVnaW9ucyBpbiB0aGUgY2l0eSBvZiBNdW5pY2guCgpgYGB7cn0KCiNEZWZpbmluZyB0aGUgbnVtYmVyIG9mIHNhbXBsZXMgZnJvbSBlYWNoIHJlZ2lvbiBhbmFseXplZApucy5ndWF0ZW1hbGEgPSA0MApkaW1lbi5ndWF0ZW1hbGEgPSBtYXRyaXgobnJvdyA9IG5zLmd1YXRlbWFsYSwgbmNvbCA9IDQpCm5zLmNhbmF2ZXJhbC5iZWhhdmlvcjEgPSA0MApkaW1lbi5jYW5hdmVyYWwuYmVoYXZpb3IxID0gbWF0cml4KG5yb3cgPSBucy5jYW5hdmVyYWwuYmVoYXZpb3IxLCBuY29sID0gNCkKbnMuY2FuYXZlcmFsLmJlaGF2aW9yMiA9IDQwCmRpbWVuLmNhbmF2ZXJhbC5iZWhhdmlvcjIgPSBtYXRyaXgobnJvdyA9IG5zLmNhbmF2ZXJhbC5iZWhhdmlvcjIsIG5jb2wgPSA0KQpucy5tdW5pY2ggPSA0MApkaW1lbi5tdW5pY2ggPSBtYXRyaXgobnJvdyA9IG5zLm11bmljaCwgbmNvbCA9IDQpCm4udG90YWwgPSAobnMuZ3VhdGVtYWxhICsgbnMuY2FuYXZlcmFsLmJlaGF2aW9yMSArIG5zLmNhbmF2ZXJhbC5iZWhhdmlvcjIgKyBucy5tdW5pY2gpCgojVGhlIFNBUiBkYXRhIGlzIGF2YWlsYWJsZSBvbiBodHRwczovL2RyaXZlLmdvb2dsZS5jb20vZmlsZS9kLzFqdGJPY1l3UWZ5c2ZjVXA0VWhvQTdsU2w0X3RQSXFmYS92aWV3P3VzcD1zaGFyaW5nIGFuZAojIGNvcnJlc3BvbmQgdG8gSEhISCBiYW5kIG9mIGFuIGltYWdlIHRha2VuIGZyb20gdGhlIENhcGUgQ2FuYXZlcmFsIChhY3F1aXJlZCBTZXAgMjIsIDIwMTYpCgojT2NlYW4gcmVnaW9ucyBpbiBDYXBlIENhbmF2ZXJhbApyb3cxID0gYyg1MCwgMTAwLCAxNTAsIDIwMCwgMjUwLCAzNTAsIDQ1MCwgNTUwLCA2NTAsIDc1MCkKcm93MiA9IGMoNTAsIDEwMCwgMTUwLCAyMDAsIDI1MCwgMzAwLCAzNTAsIDQwMCwgNDUwLCA1NTApCnJvdzMgPSBjKDUwLCAxNTAsIDI1MCwgMzUwLCA0NTAsIDU1MCwgNjUwLCA3NTAsIDgwMCwgODUwKQpyb3c0ID0gYygyNTAsIDM1MCwgNDUwLCA1NTAsIDY1MCwgNzUwLCA4NTAsIDk1MCwgMTA1MCkKcm93NSA9IGMoNTAsIDE1MCwgMjUwLCAzNTAsIDQ1MCwgNTUwLCA2NTAsIDc1MCwgODUwLCA5NTAsIDEwNTApCnJvdzYgPSBjKDUwLCAxNTAsIDI1MCwgMzUwLCA0NTAsIDU1MCwgNjUwLCA3NTAsIDgwMCwgODUwLCA5NTApCnJvdzcgPSBjKDUwLCAxNTAsIDI1MCwgMzUwLCA0NTAsIDU1MCwgNjUwLCA3NTAsIDg1MCwgOTUwKQpjb2xzID0gYygxNzAwLCAxODUwLCAxNTUwLCAxNDAwLCAxLCAyMDAsIDM1MCwgNTUwKQoje0JlaGF2aW9yIDF9CmRpbWVuLmNhbmF2ZXJhbC5iZWhhdmlvcjFbMTo5LF0gPSBjKHJvdzFbMTo5XSwgcmVwKDEyOCwgOSksIHJlcChjb2xzWzFdLCA5KSwgcmVwKDEyOCwgOSkpCmRpbWVuLmNhbmF2ZXJhbC5iZWhhdmlvcjFbMTAsXSA9IGMocm93MVsxMF0sIDEyOCwgMTMwMCwgMTI4KQpkaW1lbi5jYW5hdmVyYWwuYmVoYXZpb3IxWzExOjE5LF0gPSBjKHJvdzJbMTo5XSwgcmVwKDEyOCwgOSksIHJlcChjb2xzWzJdLCA5KSwgcmVwKDEyOCwgOSkpCmRpbWVuLmNhbmF2ZXJhbC5iZWhhdmlvcjFbMjAsXSA9IGMocm93MlsxMF0sIDEyOCwgMTM1MCwgMTI4KQpkaW1lbi5jYW5hdmVyYWwuYmVoYXZpb3IxWzIxOjMwLF0gPSBjKHJvdzMsIHJlcCgxMjgsIDEwKSwgcmVwKGNvbHNbM10sIDEwKSwgcmVwKDEyOCwgMTApKQpkaW1lbi5jYW5hdmVyYWwuYmVoYXZpb3IxWzMxOjQwLF0gPSBjKHJvdzMsIHJlcCgxMjgsIDEwKSwgcmVwKGNvbHNbNF0sIDEwKSwgcmVwKDEyOCwgMTApKQoKI3tCZWhhdmlvciAyfQpkaW1lbi5jYW5hdmVyYWwuYmVoYXZpb3IyWzE6OSxdID0gYyhyb3c0LCByZXAoMTI4LCA5KSwgcmVwKGNvbHNbNV0sIDkpLCByZXAoMTI4LCA5KSkKZGltZW4uY2FuYXZlcmFsLmJlaGF2aW9yMlsxMDoyMCxdID0gYyhyb3c1LCByZXAoMTI4LCAxMSksIHJlcChjb2xzWzZdLCAxMSksIHJlcCgxMjgsIDExKSkKZGltZW4uY2FuYXZlcmFsLmJlaGF2aW9yMlsyMTozMCxdID0gYyhyb3c3LCByZXAoMTI4LCAxMCksIHJlcChjb2xzWzddLCAxMCksIHJlcCgxMjgsIDEwKSkKZGltZW4uY2FuYXZlcmFsLmJlaGF2aW9yMlszMTo0MCxdID0gYyhyb3c3LCByZXAoMTI4LCAxMCksIHJlcChjb2xzWzhdLCAxMCksIHJlcCgxMjgsIDEwKSkKCiNUaGUgU0FSIGRhdGEgaXMgYXZhaWxhYmxlIG9uIGh0dHBzOi8vZHJpdmUuZ29vZ2xlLmNvbS9maWxlL2QvMXBPNnBfVUk5Q2dkY2k5eTZqVnluQXY4U3JyQXZ2N0s4L3ZpZXc/dXNwPXNoYXJpbmcgYW5kCiMgY29ycmVzcG9uZCB0byBISEhIIGJhbmQgb2YgYW4gaW1hZ2UgdGFrZW4gZnJvbSB0aGUgTXVuaWNoLCBHZXJtYW55IChhY3F1aXJlZCBKdW4gNSwgMjAxNSkgCgojVXJiYW4gcmVnaW9ucyBpbiBNdW5pY2gKcm93MSA9IHNlcSgzMDAwLCAzOTUwLCBieSA9IDUwKQpyb3cyID0gcmVwKGMoNDMwMCwgNDM1MCksIDUpCnJvdzMgPSBjKHJlcChzZXEoMjMwMCwgMjQ1MCwgYnkgPSA1MCksIDIpLCAyNDAwLCAyNDUwKQpjb2xzID0gNDAwCmNvbHMyID0gYygxMzAwLCAxMzAwLCAxMzUwLCAxMzUwLCAxNDAwLCAxNDAwLCAxNDUwLCAxNDUwLCAxNTAwLCAxNTAwKQpjb2xzMyA9IGMocmVwKDUwMCwgNCksIHJlcCg0MDAsIDQpLCByZXAoMzAwLCAyKSkKZGltZW4ubXVuaWNoWzE6MjAsXSA9IGMocm93MSwgcmVwKDEyOCwgMjApLCByZXAoY29scywgMjApLCByZXAoMTI4LCAyMCkpCmRpbWVuLm11bmljaFsyMTozMCxdID0gYyhyb3cyLCByZXAoMTI4LCAxMCksIGNvbHMyLCByZXAoMTI4LCAxMCkpCmRpbWVuLm11bmljaFszMTo0MCxdID0gYyhyb3czLCByZXAoMTI4LCAxMCksIGNvbHMzLCByZXAoMTI4LCAxMCkpCgojRm9yZXN0IHJlZ2lvbnMgaW4gR3VhdGVtYWxhCnJvdzEgPSBzZXEoNTE1MCwgNjEwMCwgYnkgPSA1MCkKcm93MiA9IHNlcSg1MjAwLCA1NjUwLCBieSA9IDUwKQpyb3czID0gc2VxKDQxMDAsIDQyMDAsIGJ5ID0gNTApCnJvdzQgPSBzZXEoMTAwMCwgMTE1MCwgYnkgPSA1MCkKY29scyA9IGMoMjcwMCwgMjgwMCwgMjkzMCwgMTkzMCwgMTg3MCkKZGltZW4uZ3VhdGVtYWxhWzE6MjAsXSA9IGMocm93MSwgcmVwKDEyOCwgMjApLCByZXAoY29sc1sxXSwgMjApLCByZXAoMTI4LCAyMCkpCmRpbWVuLmd1YXRlbWFsYVsyMTozMCxdID0gYyhyb3cyLCByZXAoMTI4LCAxMCksIHJlcChjb2xzWzJdLCAxMCksIHJlcCgxMjgsIDEwKSkKZGltZW4uZ3VhdGVtYWxhWzMxOjMzLF0gPSBjKHJvdzMsIHJlcCgxMjgsIDMpLCByZXAoY29sc1szXSwgMyksIHJlcCgxMjgsIDMpKQpkaW1lbi5ndWF0ZW1hbGFbMzQ6MzcsXSA9IGMocm93NCwgcmVwKDEyOCwgNCksIHJlcChjb2xzWzRdLCA0KSwgcmVwKDEyOCwgNCkpCmRpbWVuLmd1YXRlbWFsYVszODo0MCxdID0gYyhyb3c0WzE6M10sIHJlcCgxMjgsIDMpLCByZXAoY29sc1s1XSwgMyksIHJlcCgxMjgsIDMpKQoKaGlsYmVydGN1cnZlID0gdW5saXN0KHJlYWQudGFibGUoIi4uLy4uL0RhdGEvSGlsYmVydC9IaWxiZXJ0Q3VydmVzMTI4LnR4dCIpKSArIDEKdGltZS5zZXJpZXMgPSBtYXRyaXgobmNvbCA9IDE2MCwgbnJvdyA9IDEyOCoxMjgpCgogI0d1YXRlbWFsYQogICAgc2FyX2RhdGEgPSByYXN0ZXIocGFzdGUoICIuLi8uLi9EYXRhL2d1YXRlbWFsYSIsICIvSEhISCIsICIuZ3JkIiwgc2VwID0gIiIpKQogICAgZm9yKGogaW4gYygxOm5zLmd1YXRlbWFsYSkpewogICAgICBpbWcgPSBnZXRWYWx1ZXNCbG9jayhzYXJfZGF0YSwgcm93ID0gZGltZW4uZ3VhdGVtYWxhW2osMV0sIG5yb3dzID0gZGltZW4uZ3VhdGVtYWxhW2osMl0sIGNvbCA9IGRpbWVuLmd1YXRlbWFsYVtqLDNdLCBuY29scyA9IGRpbWVuLmd1YXRlbWFsYVtqLDRdLCBmb3JtYXQgPSAibWF0cml4IikKICAgICAgdGltZS5zZXJpZXNbLGpdID0gaW1nW2hpbGJlcnRjdXJ2ZV0vbWF4KGltZ1toaWxiZXJ0Y3VydmVdKQogICAgfQogICAgI0NhcGUgQ2FuYXZlcmFsIC0gYmVoYXZpb3IgMQogICAgc2FyX2RhdGEgPSByYXN0ZXIocGFzdGUoIi4uLy4uL0RhdGEvY2FwZSIsICIvSEhISCIsICIuZ3JkIiwgc2VwID0gIiIpKQogICAgZm9yKGogaW4gYygxOm5zLmNhbmF2ZXJhbC5iZWhhdmlvcjEpKXsKICAgICAgaW1nID0gZ2V0VmFsdWVzQmxvY2soc2FyX2RhdGEsIHJvdyA9IGRpbWVuLmNhbmF2ZXJhbC5iZWhhdmlvcjFbaiwxXSwgbnJvd3MgPSBkaW1lbi5jYW5hdmVyYWwuYmVoYXZpb3IxW2osMl0sIGNvbCA9IGRpbWVuLmNhbmF2ZXJhbC5iZWhhdmlvcjFbaiwzXSwgbmNvbHMgPSBkaW1lbi5jYW5hdmVyYWwuYmVoYXZpb3IxW2osNF0sIGZvcm1hdCA9ICJtYXRyaXgiKQogICAgICB0aW1lLnNlcmllc1ssbnMuZ3VhdGVtYWxhICsgal0gPSBpbWdbaGlsYmVydGN1cnZlXS9tYXgoaW1nW2hpbGJlcnRjdXJ2ZV0pCiAgICB9CiAgICAjQ2FwZSBDYW5hdmVyYWwgLSBiZWhhdmlvciAyCiAgICBzYXJfZGF0YSA9IHJhc3RlcihwYXN0ZSgiLi4vLi4vRGF0YS9jYXBlIiwgIi9ISEhIIiwgIi5ncmQiLCBzZXAgPSAiIikpCiAgICBmb3IoaiBpbiBjKDE6bnMuY2FuYXZlcmFsLmJlaGF2aW9yMikpewogICAgICBpbWcgPSBnZXRWYWx1ZXNCbG9jayhzYXJfZGF0YSwgcm93ID0gZGltZW4uY2FuYXZlcmFsLmJlaGF2aW9yMltqLDFdLCBucm93cyA9IGRpbWVuLmNhbmF2ZXJhbC5iZWhhdmlvcjJbaiwyXSwgY29sID0gZGltZW4uY2FuYXZlcmFsLmJlaGF2aW9yMltqLDNdLCBuY29scyA9IGRpbWVuLmNhbmF2ZXJhbC5iZWhhdmlvcjJbaiw0XSwgZm9ybWF0ID0gIm1hdHJpeCIpCiAgICAgIHRpbWUuc2VyaWVzWywobnMuY2FuYXZlcmFsLmJlaGF2aW9yMSArIG5zLmd1YXRlbWFsYSkgKyBqXSA9IGltZ1toaWxiZXJ0Y3VydmVdL21heChpbWdbaGlsYmVydGN1cnZlXSkKICAgIH0KICAgICNNdW5pY2gKICAgIHNhcl9kYXRhID0gcmFzdGVyKHBhc3RlKCIuLi8uLi9EYXRhL211bmljaCIsICIvSEhISCIsICIuZ3JkIiwgc2VwID0gIiIpKQogICAgZm9yKGogaW4gYygxOm5zLm11bmljaCkpewogICAgICBpbWcgPSBnZXRWYWx1ZXNCbG9jayhzYXJfZGF0YSwgcm93ID0gZGltZW4ubXVuaWNoW2osMV0sIG5yb3dzID0gZGltZW4ubXVuaWNoW2osMl0sIGNvbCA9IGRpbWVuLm11bmljaFtqLDNdLCBuY29scyA9IGRpbWVuLm11bmljaFtqLDRdLCBmb3JtYXQgPSAibWF0cml4IikKICAgICAgdGltZS5zZXJpZXNbLChucy5jYW5hdmVyYWwuYmVoYXZpb3IxICsgbnMuY2FuYXZlcmFsLmJlaGF2aW9yMiArIG5zLmd1YXRlbWFsYSkgKyBqXSA9IGltZ1toaWxiZXJ0Y3VydmVdL21heChpbWdbaGlsYmVydGN1cnZlXSkKICAgIH0KICAgIApgYGAKCiMjQW5hbHlzaW5nIHRoZSBCUCBkaXN0cmlidXRpb24KCkluIHRoaXMgcGFwZXIsIGlzIHVzZWQgYW4gZXhwbGljaXQgaW1wbGVtZW50YXRpb24gb2YgYSBzb3J0aW5nIGFsZ29yaXRobSBhcyBhIHRyZWUgb2YgaWYtY2xhdXNlcyB3aXRoIGVhY2ggaW5uZXIgbm9kZSBiZWluZyBhIHBhaXJ3aXNlIGNvbXBhcmlzb24gb2YgaXRlbXMgYW5kIGVhY2ggbGVhZiBoYXZpbmcgYSB1bmlxdWUgaW50ZWdlciBsYWJlbCBiZXR3ZWVuIDAgYW5kIG0hIHJlcHJlc2VudGluZyB0aGUgcGVybXV0YXRpb24gaW5kZXguIApTaW5jZSBubyBpdGVtcyBoYXZlIHRvIHN3YXAgcGxhY2VzLCBubyBzdWJyb3V0aW5lcyBhcmUgY2FsbGVkLCBhbmQgbm8gZXh0cmEgbWVtb3J5IGlzIHJlcXVpcmVkIHRvIGJlIHJlc2VydmVkIG9uIHRoZSBzdGFjayBvciB0aGUgaGVhcCwgdGhpcyBpbXBsZW1lbnRhdGlvbiBpcyBtb3JlIGVmZmljaWVudC4KQSBzYW1wbGUgb3V0cHV0IG9mIHRoaXMgYWxnb3JpdGhtIGlzIHNob3duIGJlbG93OgoKYGBge3IsIHRpZHk9RkFMU0UsIGV2YWw9RkFMU0UsIGhpZ2hsaWdodD1GQUxTRSB9CgpmdW5jdGlvbiB5ID0gZ2V0X2NvZGV3b3JkKHgpCiAgaWYgeCgzKSA8IHgoMSkKICAgIGlmIHgoMikgPCB4KDEpCiAgICAgIGlmIHgoMykgPCB4KDIpCiAgICAgICAgICAgIHk9MTsKICAgICAgZWxzZQogICAgICAgICAgICB5PTI7CiAgICAgIGVuZAogICAgZWxzZQogICAgICB5PTM7CiAgICBlbmQKICBlbHNlCiAgICBpZiB4KDIpIDwgeCgxKQogICAgICAgIHk9NDsKICAgIGVsc2UKICAgICAgICBpZiB4KDMpIDwgeCgyKQogICAgICAgICAgICAgIHk9NTsKICAgICAgICBlbHNlCiAgICAgICAgICAgICAgeT02OwogICAgICAgIGVuZAogICAgZW5kCmVuZAoKYGBgCgpCZWxvdyB3ZSBjYW4gc2VlIGEgY29tcGFyaXNvbiBvZiB0aGUgZXhlY3V0aW9uIHRpbWUgYmV0d2VlbiB0aGUgbmFpdmUgaW1wbGVtZW50YXRpb24gb2YgdGhlIEJhbmR0LVBvbXBlIHN5bWJvbGl6YXRpb24gYW5kIHRoZSAib3B0aW1pemVkIiB2ZXJzaW9uLgpXZSBldmFsdWF0ZSB0aW1lIGVsYXBzZWQsICBmb3IgdGhlIHN5c3RlbSBhbmQgdXNlci4KQm90aCByb3V0aW5lcyB3ZXJlIGltcGxlbWVudGVkIGluIEMgYW5kIGxpbmtlZCBieSBSIHRocm91Z2ggdGhlIFJjcHAgcGFja2FnZS4KCmBgYHtyfQpjYXQoIlxuIEJQIGRpc3RyaWJ1dGlvbiBwZXJmb3JtYW5jZSBcblxuIiwgIk5BVFM6ICIsIHN5c3RlbS50aW1lKGJhbmR0LnBvbXBlKHRpbWUuc2VyaWVzWywxXSwgMywgMSkpLCAiXG4gcGRjOiAiLCBzeXN0ZW0udGltZShjb2RlYm9vayh0aW1lLnNlcmllc1ssMV0sIDMsIDEpKSwgIlxuIikKYGBgCgpBbm90aGVyIGZlYXR1cmUgcHJlc2VudCBpbiB0aGUgcGFja2FnZSB0aGF0IGFsc28gZXhpc3RzIGluIE5BVFMgaXMgdGhlIHBsb3Qgb2YgdGhlIGhpc3RvZ3JhbSBvZiBvcmRpbmFsIHBhdHRlcm5zLgoKYGBge3J9CnggPSB0aW1lLnNlcmllc1ssMV0KYmFycGxvdCh4LCB4bGFiPSJQZXJtdXRhdGlvbiBEaXN0cmlidXRpb24iKQpgYGAKCiMjRW50cm9weSBoZXVyaXN0aWMKClRoZSBjaG9pY2Ugb2YgcGFyYW1ldGVycyBtIChlbWJlZGRpbmcgZGltZW5zaW9uKSBhbmQgdCAodGltZSBkZWxheSkgZm9yIHRoZSB0aW1lLWRlbGF5ZWQgZW1iZWRkaW5nIGlzIGNydWNpYWwgZm9yIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgY2x1c3RlcmluZy4KSW4gdGhpcyB3YXksIHRoZXNlIG9ic2VydmF0aW9ucyBhcmUgZm9ybWFsaXplZCBpbiBhIGhldXJpc3RpYyB0aGF0IGd1aWRlcyByZXNlYXJjaGVycyBpbiBjaG9vc2luZyBhbiBpbmNvcnBvcmF0aW9uIGRpbWVuc2lvbiwgaWYgbm8gcHJpb3IgaW5mb3JtYXRpb24gaXMgYXZhaWxhYmxlLgpUaGlzIGhldXJpc3RpYyBhaW1zIHRvIGNob29zZSBhbiBpbmNvcnBvcmF0aW9uIGRpbWVuc2lvbiB0aGF0IGlzIG1heGltYWxseSBleHByZXNzaXZlLCB0aGF0IGlzLCB0aGUgRFAgbXVzdCBiZSBtYXhpbWFsbHkgZGlmZmVyZW50IGZyb20gYSB1bmlmb3JtIGRpc3RyaWJ1dGlvbiBhbmQgZm9yIHRoYXQsIHBlcm11dGF0aW9uIGVudHJvcHkgaXMgdXNlZC4KClRodXMsIHRoZSBoZXVyaXN0aWMgY2hvb3NlcyB0aGUgZW1iZWRkaW5nIGRpbWVuc2lvbiB3aXRoIHRoZSBsb3dlc3QgYXZlcmFnZSwgbm9ybWFsaXplZCBwZXJtdXRhdGlvbiBlbnRyb3B5OgoKJCQKYXJnXCxtaW5fe219IFxzdW1fe1AgXGluIFxtYXRoYmJ7UH19IFxtYXRoYmJ7SH0obSkKJCQKCkluIGEgc2ltaWxhciB2ZWluLCBkaWZmZXJlbnQgY2hvaWNlcyBvZiB0aGUgdGltZSBkZWxheSB0IGNhbiBiZSBzZWxlY3RlZC4KVGhlcmVmb3JlLCB0aGUgaHlwZXItcGFyYW1ldGVycyBzZWxlY3RlZCBmb3IgdGhlIGFuYWx5c2lzIHdlcmU6CgpgYGB7cn0KbWluZSA9IGVudHJvcHlIZXVyaXN0aWModGltZS5zZXJpZXMsIHQubWluID0gMSwgdC5tYXggPSAxMCkKc3VtbWFyeShtaW5lKQpgYGAKCkFuIGlsbHVzdHJhdGlvbiBvZiBhIGdyaWQtc2VhcmNoIG9uIHRoZSBwYXJhbWV0ZXJzIG0gYW5kIHQgdG8gZGV0ZXJtaW5lIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIHJlZ2lvbnMgaXMgZ2l2ZW4gYmVsb3c6CgpgYGB7cn0KcGxvdChtaW5lKQpgYGAKCgojI0hlbGxpbmdlciBEaXN0YW5jZQoKSGVsbGluZ2VyJ3Mgc3F1YXJlIGRpc3RhbmNlIGlzIHVzZWQgdG8gaW5jb3Jwb3JhdGUgdGhlIHBlcm11dGF0aW9uIGRpc3RyaWJ1dGlvbnMgb2YgdGltZSBzZXJpZXMgaW4gYSBtZXRyaWMgc3BhY2UuCkxldCAkXG1hdGhiYntQfSA9IChwXzEsIHBfMiwgXGRvdHMgLCBwX24pJCBhbmQgJFxtYXRoYmJ7UX0gPSAocV8xLCBxXzIsIFxkb3RzICwgcV9uKSQgYmUgdHdvIHBlcm11dGF0aW9uIGRpc3RyaWJ1dGlvbnMuIApUaGUgc3F1YXJlZCBIZWxsaW5nZXIgZGlzdGFuY2UgaXM6CgokJApEKFAsIFEpID0gXGZyYWN7MX17XHNxcnR7Mn19IHx8IFxzcXJ0e1B9IC0gXHNxcnR7UX0gfHxeMgokJAoKVGhlIHNxdWFyZWQgSGVsbGluZ2VyIGRpc3RhbmNlIGNhbiBiZSBkZXJpdmVkIGJ5IG1lYW5zIG9mIGEgVGF5bG9yIGFwcHJveGltYXRpb24gb2YgdGhlIEtMIGRpdmVyZ2VuY2UuCgojIyNIZWxsaW5nIGRpc3RhbmNlIGJldHdlZW4gdHdvIHByb2JhYmlsaXR5IGRpc3RyaWJ1dGlvbnMKCkZpbmFsbHksIHRoZSBwZGMgcGFja2FnZSBhbHNvIHByb3ZpZGVzIHRoZSBmdW5jdGlvbmFsaXR5IHRvIGNhbGN1bGF0ZSB0aGUgSGVsbGluZ2VyIGRpc3RhbmNlLCBob3dldmVyIGl0IGlzIGRvbmUgYmV0d2VlbiB0d28gdGltZSBzZXJpZXMuCkluIE5BVFMgdGhpcyBtZXRyaWMgaXMgY2FsY3VsYXRlZCB1c2luZyB0aGUgdW5pZm9ybSBwcm9iYWJpbGl0eSBkaXN0cmlidXRpb24gYXMgYSByZWZlcmVuY2UuCgpgYGB7cn0KeCA9IGNvZGVib29rKHRpbWUuc2VyaWVzWywxXSwgbSA9IDMpCnkgPSBjb2RlYm9vayh0aW1lLnNlcmllc1ssMl0sIG0gPSAzKQpjYXQoIkhlbGxpbmdlciAoeCwgeSk6ICIsIGhlbGxpbmdlckRpc3RhbmNlKHgseSksICIgLSBIZWxsaW5nZXIgKHgsIFVEKTogIiwgSGVsbGluZ2VyRGlzdGFuY2UoeCksICIgLSBIZWxsaW5nZXIgKHksIFVEKTogIiwgSGVsbGluZ2VyRGlzdGFuY2UoeSkpCmBgYAoKIyMjRGlzdGFuY2UgbWF0cml4IGNvbXB1dGVkIGJ5IHRoZSBkaXZlcmdlbmNlIGJldHdlZW4gcGVybXV0YXRpb24gZGlzdHJpYnV0aW9ucyBvZiB0aW1lIHNlcmllcwoKQSBkaXN0YW5jZSBtYXRyaXggY2FuIGFsc28gYmUgY2FsY3VsYXRlZC4KSXQgaXMgdGhyb3VnaCB0aGlzIG1hdHJpeCB0aGF0IGNsdXN0ZXJpbmcgaXMgZG9uZS4KCmBgYHtyfQpEID0gcGRjRGlzdCh0aW1lLnNlcmllcykKbGV2ZWxwbG90KGFzLm1hdHJpeChEKSkKYGBgCgojI0hldXJpc3RpYyB0byBkZXRlcm1pbmUgdGhlIG51bWJlciBvZiBjbHVzdGVycwoKSnVzdCBhcyBhIGhldXJpc3RpYyB3YXMgcHJvcG9zZWQgZm9yIGNob29zaW5nIHRoZSBoeXBlci1wYXJhbWV0ZXJzIG9mIHRoZSBCYW5kdC1Qb21wZSBzeW1ib2xpemF0aW9uLCBhbm90aGVyIGhldXJpc3RpYyBpcyBhbHNvIHByb3Bvc2VkIGZvciBjaG9vc2luZyB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzLgoKIyMjSGllcmFyY2hpY2FsIGNsdXN0ZXIgYW5hbHlzaXMgZm9yIHRpbWUgc2VyaWVzCgpCYXNlZCBvbiBhIGRpc3NpbWlsYXJpdHkgbWVhc3VyZSwgY2x1c3RlcnMgYXJlIGNvbnN0cnVjdGVkIGJ5IGl0ZXJhdGl2ZWx5IG1lcmdpbmcgY2x1c3RlcnMgdW50aWwgdGhlcmUgaXMgb25seSBhIHNpbmdsZSB0b3AgY2x1c3RlciBsZWZ0LiAKVGhpcyBsZWFkcyB0byBhIGhpZXJhcmNoeSBvZiBjbHVzdGVycy4gCgpUaGUgZGlzdGFuY2VzIGJldHdlZW4gc2V0cyBvZiB0aW1lIHNlcmllcyBhcmUgY2FsY3VsYXRlZCBieSB0aGUgY29tcGxldGUgbGlua2FnZSBtZXRob2QsIHdoaWNoIGlzIGRlZmluZWQgYXMgdGhlIGRpc3NpbWlsYXJpdHkgYmV0d2VlbiB0d28gY2x1c3RlcnMgJENfaSQgYW5kICRDX2okIDogICAgICAKCiQkCmRfe2NvbXBsZXRlfShDX2ksIENfaikgPSBtYXhfe3ggXGluIENfaSwgeSBcaW4gQ19qfSBEKHgseSkKJCQKClRoZSByZXN1bHRpbmcgYmluYXJ5IHRyZWUgaXMgdHlwaWNhbGx5IHZpc3VhbGl6ZWQgaW4gYSBkZW5kcm9ncmFtIGRlcGljdGluZyB0aGUgdHJlZSB3aXRoIGJyYW5jaCBoZWlnaHRzIHRoYXQgcmVmbGVjdCB0aGUgZGlzdGFuY2UgYmV0d2VlbiBjbHVzdGVycy4gCgpgYGB7cn0KY2x1c3QgPSBwZGNsdXN0KHRpbWUuc2VyaWVzKQpwbG90KGNsdXN0KQpgYGAKCiMjI0xlYXZlLW9uZS1vdXQgY3Jvc3MtdmFsaWRhdGlvbiBzY2hlbWUuIFRoZSBwcmVkaWN0aXZlIGFjY3VyYWN5IG9mIHRoZSBjbHVzdGVyIGlzIGVzdGltYXRlZAoKVXNpbmcgbG9vMW5uLCB3aGljaCBpbXBsZW1lbnRzIGEgbGVhdmUtb25lLW91dCBjcm9zcy12YWxpZGF0aW9uIHNjaGVtZSwgd2UgZXN0aW1hdGUgdGhlIHByZWRpY3RpdmUgYWNjdXJhY3kgb2YgdGhlIGNsdXN0ZXJpbmc6CgpgYGB7cn0KdHJ1dGggPSBjKHJlcCgxLDQwKSwgcmVwKDIsNDApLCByZXAoMiw0MCksIHJlcCgzLCA0MCkpCmxvbzFubihjbHVzdCwgdHJ1dGgpCmBgYAoKQWxzbywgdGhlIGNvbnZleCBodWxsIG9mIGVhY2ggY2x1c3RlciBjYW4gYmUgcGxvdHRlZCBhcyB0aGUgc2hhZGVkIGFyZWEgaW4gdGhlIHByaW5jaXBhbCBjb29yZGluYXRlIHNwYWNlLiAKVGhlIHJlc3VsdGluZyBwbG90IGlzIHNob3duIGJlbG93IGFuZCBjYW4gYmUgY3JlYXRlZCB1c2luZyB0aGUgY29tbWFuZCBtZHNQbG90OgoKYGBge3J9Cm1kc1Bsb3QoY2x1c3QsIHRydXRoLCBjb2wgPSBjKCJsaWdodGdyYXkiLCAibGlnaHRibHVlIiwgImxpZ2h0Z3JlZW4iKSkKYGBg