List of R colors: http://www.stat.columbia.edu/~tzheng/files/Rcolor.pdf

Cluster Analysis - Part I

Metrics for Cluster Analysis

  • The two primary metrics used to measure the similarity of data objects are the Euclidean Metric and the Manhattan Metric.

The Euclidean Metric

Given the data objects \(\underline{x}=(x_1,x_2,\ldots,x_n)\) and \(\underline{y}=(y_1,y_2,\ldots,y_n)\), then the ``distance’’ between these data objects is given by \[ \langle \underline{x},\underline{y}\rangle_{E}=\sqrt{\sum_{i=1}^{n}(x_{i}-y_{i})^2} = \sqrt{(x_1-y_1)^2+(x_2-y_2)^2+\cdots+(x_n-y_n)^2}. \]

Remarks:

  1. The smaller the Euclidean distance between the data objects \(\underline{x}\) & \(\underline{y}\), the more similar the data objects.

  2. When the dimensions used to characterise each data object are of varying orders of magnitude, then the Euclidean metric tends to exaggerate the effects of the larger dimensions compared to the smaller dimensions. As such, it is often a good idea to re-scale all dimensions to be of the same order of magnitude.

  3. When comparing data objects, it is necessary that each object have the same number of dimensions, i.e. it does not make sense to find the separation between the data objects \(\underline{x}=(x_1,x_2,x_3,x_4)\) and \(\underline{y}=(y_1,y_2,y_3)\).

Example 1:

Given the data objects \[ \underline{x}=(3443, 561, 23.1, 0.856)\qquad \underline{y}=(5441,397,19.3, 0.472) \] answer the following:

  1. Create 2 data vectors to represent \(\underline{x}\) and \(\underline{y}\).

  2. Identify the number of dimensions in these vectors.

  3. Create and R-function to calculate the Euclidean Distance between two arbitrary vectors.

  4. Find the Euclidean distance without re-scaling any of the dimensions.

  5. Find the Euclidean distance by re-scaling the dimensions as necessary.

Solution

Part 1. The data vectors are given by

x<-c(3443,561,23.1,0.856)
y<-c(5441,397,19.3,0.472)
x
[1] 3443.000  561.000   23.100    0.856
y
[1] 5441.000  397.000   19.300    0.472

Part 2. Each vector has four entries so the dimension is 4.

Part 3. To illustrate how this function should operate, we work our way from inside to outside:

x-y
[1] -1998.000   164.000     3.800     0.384

We see that x-y returns another 4-dimensional vector, these are the differences in each dimension

  • Squaring x-y we find
(x-y)^2
[1] 3.992004e+06 2.689600e+04 1.444000e+01 1.474560e-01

This has taken the value in each dimension of x-y and squared it.

  • To add these four numbers we use the R-function, sum()
sum((x-y)^2)
[1] 4018915

This has added the four numbers in (x-y)^2.

  • The last step is to take the square root of this number, and we doe this using the R-function sqrt().
sqrt(sum((x-y)^2))
[1] 2004.723

The Euclidean distance function

Alternatively, we can create a function ED() (for Euclidean Distance) as follows

ED <- function(a,b){
  Euclidean_Distance = sqrt(sum((a-b)^2))
  return(Euclidean_Distance)
} # a and b are place-holders, you an use any symbols you wish.
  • The arguments a and b are place holders in this definition, and they represent the vectors we want to find the distance between. Any symbols may be used in place of a and b

  • The Euclidean distance between x and y is now calculate using ED()

ED(x,y)
[1] 2004.723
  • This agrees with the value we previously found.

Remark:

This distance is dominated by the distance between x and y along the first dimension.

Part 4. Next we re-scale each of the dimensions so they are all measured in thousands, and use the function we previously defined to calculate the Euclidean distance between these rescaled data objects

x1<-c(3443,5610,2310,8560)
y1<-c(5441,3970,1930,4720)
ED(x1,y1)
[1] 4644.524

Remark:

In this case the distance between the data objects is measured more evenly across all dimensions.

The Manhattan Metric

Given the data objects \(\underline{x}=(x_1,x_2,\ldots,x_n)\) and \(\underline{y}=(y_1,y_2,\ldots,y_n)\), the Manhattan distance between the data objects id given by \[ \langle \underline{x}, \underline{y}\rangle_{M} = \sum_{i=1}^{n}\left\vert x_i-y_i\right\vert=\left\vert x_1-y_1\right\vert+\left\vert x_2-y_2\right\vert+\ldots+\left\vert x_n-y_n\right\vert. \]

Remarks:

  1. Again, the smaller the Manhattan distance, the more similar the data objects.

  2. As with the Euclidean metric, the Manhattan distance only makes sense when the data objects \(\underline{x}\) and \(\underline{y}\) have the same number of dimensions.

Exercise 1:

Define a function to measure the Manhattan distance between two data objects.

Remark:

To do this you only have to modify the function ED() slightly!!

Exercise 2:

Given the data objects

\[ \underline{u}=(1415, 1843, 992, 875)\qquad \underline{v}=(2533, 1005, 329, 176) \]

answer the following:

  1. Identify the number of dimensions characterising the data objects \(\underline{u}\) and \(\underline{v}\).

  2. Use the Manhattan distance function to quantify the similarity between the two data objects \(\underline{u}\) and \(\underline{v}\) (there is no need to re-scale dimensions in this case).

Example 3

  1. Create 12 data vectors to represent each of the following. \[ \underline{a}=(3986,1943,444)\\ \underline{b}=(4271,1444,344)\\ \underline{c}=(8443,2020,415)\\ \underline{d}=(1898,1877,311)\\ \underline{e}=(2916,1253,289)\\ \underline{f}=(7476,1583,347)\\ \underline{g}=(3690,2131,491)\\ \underline{h}=(1355,1837,265)\\ \underline{i}=(7251,1492,306)\\ \underline{j}=(6498,1683,405)\\ \underline{k}=(5211,1298,367)\\ \underline{l}=(8001,1950,392)\\ \]

  2. Use the R-function rbind() to create a matrix representing these vectors in a single data structure. X

  3. Use the scale()-function, so that all vector dimensions are of similar size.

  4. Use the R-function dist() to calculate the Euclidean and Manhattan distances between these data vectors.

  5. Use these Euclidean and Manhattan distances to create a heat map to represent these distances graphically.

Solution

Part 1. We begin by creating a list of data structures corresponding to the data objects given.

a<-c(3986,1943,444)
b<-c(4271,1444,344)
c<-c(8443,2020,415)
d<-c(1898,1877,311)
e<-c(2916,1253,289)
f<-c(7476,1583,347)
g<-c(3690,2131,491)
h<-c(1355,1837,265)
i<-c(7251,1492,306)
j<-c(6498,1683,405)
k<-c(5211,1298,367)
l<-c(8001,1950,392)

Part 2. The single data structure is given by:

Data<-rbind(a,b,c,d,e,f,g,h,i,j,k,l)
Data
  [,1] [,2] [,3]
a 3986 1943  444
b 4271 1444  344
c 8443 2020  415
d 1898 1877  311
e 2916 1253  289
f 7476 1583  347
g 3690 2131  491
h 1355 1837  265
i 7251 1492  306
j 6498 1683  405
k 5211 1298  367
l 8001 1950  392

Part 4. Notice that the first two dimensions of each vector is measured in thousands, while the last dimension is measured in units of 100. To make all dimensions have the same order, we divide the first two dimensions by 1000 and the last dimension by 100. This is done using the scale()-function

Scaled_Data<-scale(Data,center=F,scale=c(1000,1000,100))
Scaled_Data
   [,1]  [,2] [,3]
a 3.986 1.943 4.44
b 4.271 1.444 3.44
c 8.443 2.020 4.15
d 1.898 1.877 3.11
e 2.916 1.253 2.89
f 7.476 1.583 3.47
g 3.690 2.131 4.91
h 1.355 1.837 2.65
i 7.251 1.492 3.06
j 6.498 1.683 4.05
k 5.211 1.298 3.67
l 8.001 1.950 3.92
attr(,"scaled:scale")
[1] 1000 1000  100

Part 4. The Euclidean distances between all of these vectors is given by:

Dist_E<- dist(Scaled_Data,method="euclidean")
Dist_E
          a         b         c         d         e         f         g         h         i         j         k
b 1.1533542                                                                                                    
c 4.4670883 4.2710022                                                                                          
d 2.4764895 2.4346495 6.6286555                                                                                
e 2.0058664 1.4747902 5.7204561 1.2141252                                                                      
f 3.6401374 3.2081531 1.2603404 5.5973315 4.6085681                                                            
g 0.5863958 1.7234935 4.8146578 2.5526026 2.3346006 4.0875054                                                  
h 3.1839436 3.0465727 7.2472914 0.7127756 1.6838578 6.1809026 3.2628609                                        
i 3.5732375 3.0045139 1.6993375 5.3670601 4.3449104 0.4764515 4.0634397 5.9202991                              
j 2.5553559 2.3213681 1.9765106 4.6990676 3.7896206 1.1414394 2.9707184 5.3323695 1.2584077                    
k 1.5841559 0.9786807 3.3462678 3.4095176 2.4243453 2.2916042 2.1318841 4.0248797 2.1380683 1.3960638          
l 4.0485397 3.7946457 0.5031541 6.1569504 5.2348767 0.7828244 4.4269156 6.7671992 1.2295788 1.5320568 2.8760570

The Manhattan distances between these vectors is given by

Dist_M<- dist(Scaled_Data,method="manhattan")
Dist_M
      a     b     c     d     e     f     g     h     i     j     k
b 1.784                                                            
c 4.824 5.458                                                      
d 3.484 3.136 7.728                                                
e 3.310 2.096 7.554 1.862                                          
f 4.820 3.374 2.084 6.232 5.470                                    
g 0.954 2.738 5.624 3.846 3.672 5.774                              
h 4.527 4.099 8.771 1.043 2.385 7.195 4.889                        
i 5.096 3.408 2.810 5.788 4.744 0.726 6.050 6.651                  
j 3.162 3.076 2.382 5.734 5.172 1.658 4.116 6.697 1.934            
k 2.640 1.316 4.434 4.452 3.120 2.750 3.594 5.415 2.844 2.052      
l 4.542 4.716 0.742 6.986 6.812 1.342 5.482 8.029 2.068 1.900 3.692

Remark:

By using Scaled_Data the distances between the 12 objects is spread more evenly across all three dimensions.

Part 5.

  • To create a heat map we first import the library factoextra. It may be necessary to install this package first by going to

    __Tools__ $\rightarrow$ __Install__ 
    
    and writing __factoextra__ in the __Packages__ cell in this dialogue box.
library("factoextra")
  • To create the heat-map for the Euclidean distance we use the function fviz_dist() as follows:
fviz_dist(Dist_E,gradient=list(low='ivory',mid='cornflowerblue',hight='midnightblue'))

  • Similarly, the heat-map for the Manhattan distances is given by
fviz_dist(Dist_M,gradient=list(low='ivory',mid='cornflowerblue',hight='red'))

  • The colours in the option gradient may be chosen freely, although colours should be chosen so that distance objects appear significantly different to nearby objects.

Exercise 3

Eight makes of car were compared using three different criteria:

  1. Price (euro) 2. Engine (cc) 3. Efficiency (km/L)

The data collected are given in the table below:

Make Price (euro) Engine (cc) Efficiency
Audi 38812 1968 22.7
BMW 35571 1995 23.7
Citroen 20451 1560 24.7
Hyundai 23620 1685 23.7
Jaguar 53693 1999 26.5
Mercedes 41909 1950 25.5
Mitsubishi 28192 2268 18.8
Toyota 27978 1995 21.6

Using the data in this table, answer the following:

  1. Create 8 data vectors to represent each car make.

  2. Combine these data vectors using the rbind() function, to create a single data structure.

  3. Rescale the dimensions of this data stricture, so each dimension is measured in the same order.

  4. Create a table of Euclidean and Manhattan distances for this re-scaled data structure.

  5. Create a heat map to represent these distances (Euclidean and Manhattan) using the function fviz_dist()

Distances and Heat Maps with Data Files

  • We can also apply the functions dist() and fvis_dist() to data which we import from .csv files.

  • However, to do so, it may be necessary to modify the imported data slightly, otherwise these functions may return errors, depending on the structure of the data file.

Example 4:

Six makes of laptop we compared using three different criteria:

  1. Storage (GB) 2. Screen (inches) 3. Ram (GB) 4. Clock-speed (GHz)

The data is available at

Moodle \(\rightarrow\) Data Visualisation \(\rightarrow\) Data Files \(\rightarrow\) LaptopData

  • Import this data using read.csv() in the usual way.
Laptops<-read.csv(file.choose())
Laptops

Using this data, answer the following:

Part 1. Try to find the Euclidean distances between these laptops using the data frame above.

  • We attempt to apply the dist() function directly to the data structure Laptops
dist(Laptops,method='euclidean')
NAs introduced by coercion
           1          2          3          4          5
2 559.034894                                            
3   8.944272 559.177979                                 
4 545.677205  19.068954 545.603897                      
5 545.686655  19.334231 545.613348   1.874166           
6 559.052815   4.473533 559.195896  19.148433  18.992762
  • Notice we get an error message (NAs introduced by coercion) on the top line. This is because dist() does not know how to find a distance between the entries in the column labelled Make.

Part 2. Modify this data structure so that it can used by the function dist().

  • Notice there are 6 rows and 5 columns in the data structure Laptops. The function dist() can only be applied to data frames consisting of numbers.

  • There are two steps to creating the necessary data structure:

    • Step 1. Extract the names of the computer makes as characters from the data structure Laptops using the function as.character()
LaptopNames<-as.character(Laptops$Make)
LaptopNames
[1] "Dell Inspiron"    "Acer Aspire"      "LG Gram"          "Lenovo Yoga"      "Google Pixelbook" "Dell i3168"      
  • Step 2. Create a new data.frame() from Laptops but exclude the first column and assign the rows of the new data frame the labels in LaptopNames
LaptopData<-as.data.frame(Laptops[,-1],row.names=LaptopNames)
LaptopData
  • The function dist() will now work with the new data frame LaptopData
dist(LaptopData, method='euclidean')
                 Dell Inspiron Acer Aspire    LG Gram Lenovo Yoga Google Pixelbook
Acer Aspire         500.016010                                                    
LG Gram               8.000000  500.143989                                        
Lenovo Yoga         488.068530   17.055791 488.002961                             
Google Pixelbook    488.076982   17.293062 488.011414    1.676305                 
Dell i3168          500.032039    4.001250 500.160014   17.126879        16.987643

Part 3. Rescale this new data structure so that all dimensions have values between 0 and 10.

  • We divide the first column by 1000, the second by 10, the third by 10 and the fourth by 1 to give the re-scaled data frame
Scaled_Laptop_Data <- scale(LaptopData,center=F,scale=c(1000,10,10,1))
Scaled_Laptop_Data
                 Storage Screen RAM ClockSpeed
Dell Inspiron      1.000   1.56 0.8        1.8
Acer Aspire        0.500   1.56 0.4        1.7
LG Gram            1.000   1.56 1.6        1.8
Lenovo Yoga        0.512   1.39 1.6        1.8
Google Pixelbook   0.512   1.23 1.6        1.3
Dell i3168         0.500   1.16 0.4        1.6
attr(,"scaled:scale")
[1] 1000   10   10    1
  • Now all the columns are of a similar size, and so no dimension will dominate when we calculate the distances between the laptops.

Part 4. Find the Euclidean and Manhattan distances in this re-scaled data structure.

The Euclidean distances are

Laptop_Distances_E<-dist(Scaled_Laptop_Data,method='euclidean')
Laptop_Distances_E
                 Dell Inspiron Acer Aspire   LG Gram Lenovo Yoga Google Pixelbook
Acer Aspire          0.6480741                                                   
LG Gram              0.8000000   1.3038405                                       
Lenovo Yoga          0.9523886   1.2161595 0.5167630                             
Google Pixelbook     1.1122248   1.3073041 0.7726862   0.5249762                 
Dell i3168           0.7810250   0.4123106 1.3747727   1.2381615        1.2389689

The Manhattan distances are

Laptop_Distances_M<-dist(Scaled_Laptop_Data,method='manhattan')
Laptop_Distances_M
                 Dell Inspiron Acer Aspire LG Gram Lenovo Yoga Google Pixelbook
Acer Aspire              1.000                                                 
LG Gram                  0.800       1.800                                     
Lenovo Yoga              1.458       1.482   0.658                             
Google Pixelbook         2.118       1.942   1.318       0.660                 
Dell i3168               1.500       0.500   2.300       1.642            1.582

Part 5. Create a heat map to represent these distances (for the Euclidean and Manhattan distances).

The heat map for the Euclidean distances is

fviz_dist(Laptop_Distances_E,gradient=list(low='ivory',mid='cornflowerblue',hight='red'))

The heat map for the Manhattan distances is

fviz_dist(Laptop_Distances_M,gradient=list(low='ivory',mid='cornflowerblue',hight='red'))

Exercise 4:

On Moodle, the file ISEQ(16Nov2017).csv contains the share price in €, the market capitalisation in € Millions, and the relative value of the company to the overall market cap. of the 20 compaines on the ISEQ index.

Moodle \(\rightarrow\) Data Visualisation \(\rightarrow\) Workbook Files \(\rightarrow\) ISEQ(16Nov2017).csv

(Source: http://www.isqe.ie)

Using this data answer the following:

  1. Import the data into this R workbook using read.csv().

  2. Create an appropriate data frame from this data file which can be used by the dist() function.

  3. Rescale the data columns of this new data frame so that all dimensions have a similar size.

  4. Find the Euclidean and Manhattan distances using this re-scaled data frame.

  5. Create a heat map to represent the Euclidean and Manhattan distances between these companies.

Exercise 5:

On Moodle, the file EuroZoneData2017.csv compares the countries of the Euro Zone (excluding Malta as not all data was avaialble), using 4 different criteria

  1. Population 2. GDP per capita (US$) 3. Total Exports (US$) 4. Total Imports (US$)

Moodle \(\rightarrow\) Data Visualisation \(\rightarrow\) Workbook Files \(\rightarrow\) EuroZoneData2017.csv

(Source: http://www.worldbank.org)

Using this data answer the following:

  1. Import the data into this R workbook using read.csv().

  2. Create an appropriate data frame from this data file which can be used by the dist() function.

  3. Rescale the data columns of this new data frame so that all dimensions have a similar size.

  4. Find the Euclidean and Manhattan distances using this re-scaled data frame.

  5. Create a heat map to represent the Euclidean and Manhattan distances between these countries.

LS0tCnRpdGxlOiAiRGF0YSBWaXN1YWxpc2F0aW9uIDIwMTkgLSBBc3NpZ25tZW50IDgiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMjIyMgIExpc3Qgb2YgUiBjb2xvcnM6IGh0dHA6Ly93d3cuc3RhdC5jb2x1bWJpYS5lZHUvfnR6aGVuZy9maWxlcy9SY29sb3IucGRmCgojIENsdXN0ZXIgQW5hbHlzaXMgLSBQYXJ0IEkKKiBDbHVzdGVyIGFuYWx5c2lzIGFsbG93cyBhIHJlc2VhcmNoZXIgdG8gdW5jb3ZlciBhIHN0cnVjdHVyZSBpbiBhIGxhcmdlIHNldCBvZiBkYXRhLCB3aXRob3V0IGV4cGxhaW5pbmcgd2h5IHRoYXQgZGF0YSBzdHJ1Y3R1cmUgZXhpc3RzLgoKKiBUaGVyZSBhcmUgbXVsdGlwbGUgYXBwcm9hY2hlcyB0byBjbHVzdGVyIGFuYWx5c2lzLCBidXQgYWxsIG9mIHRoZW0gcmVseSBvbiBjb21wYXJpbmcgX19zaW1pbGFyIG9iamVjdHNfXyB1c2luZyBfX211bHRpcGxlIGNyaXRlcmlhX18uCgoqIEVhY2ggb2JqZWN0IG1heSBiZSBjaGFyYWN0ZXJpc2VkIGJ5IG9uZSBvciBzZXZlcmFsIGZlYXR1cmVzLCBhbmQgZWFjaCBvZiB0aGVzZSBmZWF0dXJlcyBpcyBjb2xsZWN0ZWQgaW4gX19kYXRhIHZlY3Rvcl9fIAoKKiBUaGUgbnVtYmVyIG9mIGVudHJpZXMgaW4gdGhlc2UgZGF0YSB2ZWN0b3JzIGlzIGNhbGxlZCB0aGUgX19kaW1lbnNpb24gb2YgdGhlIHZlY3Rvcl9fLgoKKiBUbyBxdWFudGlmeSB0aGUgX19zaW1pbGFyaXR5X18gb2YgdHdvIGRhdGEgb2JqZWN0cywgd2UgdXNlIGEgX19tZXRyaWNfXyB0byBmaW5kIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSB0d28gb2JqZWN0cyBhbG9uZyBlYWNoIG9mIHRoZWlyIGRpbWVuc2lvbnMuCgoKCgojIyBNZXRyaWNzIGZvciBDbHVzdGVyIEFuYWx5c2lzCgoqIFRoZSB0d28gcHJpbWFyeSBtZXRyaWNzIHVzZWQgdG8gbWVhc3VyZSB0aGUgc2ltaWxhcml0eSBvZiBkYXRhIG9iamVjdHMgYXJlIHRoZSBfX0V1Y2xpZGVhbiBNZXRyaWNfXyBhbmQgdGhlIF9fTWFuaGF0dGFuIE1ldHJpY19fLgoKIyMjIFRoZSBFdWNsaWRlYW4gTWV0cmljCgpHaXZlbiB0aGUgZGF0YSBvYmplY3RzICRcdW5kZXJsaW5le3h9PSh4XzEseF8yLFxsZG90cyx4X24pJCBhbmQgJFx1bmRlcmxpbmV7eX09KHlfMSx5XzIsXGxkb3RzLHlfbikkLCB0aGVuIHRoZSBgYGRpc3RhbmNlJycgYmV0d2VlbiB0aGVzZSBkYXRhIG9iamVjdHMgaXMgZ2l2ZW4gYnkKXFsKXGxhbmdsZSBcdW5kZXJsaW5le3h9LFx1bmRlcmxpbmV7eX1ccmFuZ2xlX3tFfT1cc3FydHtcc3VtX3tpPTF9XntufSh4X3tpfS15X3tpfSleMn0gPSBcc3FydHsoeF8xLXlfMSleMisoeF8yLXlfMileMitcY2RvdHMrKHhfbi15X24pXjJ9LgpcXQoKIyMjIyBSZW1hcmtzOgoKMS4gVGhlIF9fc21hbGxlcl9fIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2UgYmV0d2VlbiB0aGUgZGF0YSBvYmplY3RzICRcdW5kZXJsaW5le3h9JCAmICRcdW5kZXJsaW5le3l9JCwgdGhlIF9fbW9yZSBzaW1pbGFyX18gdGhlIGRhdGEgb2JqZWN0cy4gCgoyLiBXaGVuIHRoZSBkaW1lbnNpb25zIHVzZWQgdG8gY2hhcmFjdGVyaXNlIGVhY2ggZGF0YSBvYmplY3QgYXJlIG9mIHZhcnlpbmcgb3JkZXJzIG9mIG1hZ25pdHVkZSwgdGhlbiB0aGUgRXVjbGlkZWFuIG1ldHJpYyB0ZW5kcyB0byBleGFnZ2VyYXRlIHRoZSBlZmZlY3RzIG9mIHRoZSBsYXJnZXIgZGltZW5zaW9ucyBjb21wYXJlZCB0byB0aGUgc21hbGxlciBkaW1lbnNpb25zLiBBcyBzdWNoLCBpdCBpcyBvZnRlbiBhIGdvb2QgaWRlYSB0byByZS1zY2FsZSBhbGwgZGltZW5zaW9ucyB0byBiZSBvZiB0aGUgc2FtZSBvcmRlciBvZiBtYWduaXR1ZGUuCgozLiBXaGVuIGNvbXBhcmluZyBkYXRhIG9iamVjdHMsIGl0IGlzIG5lY2Vzc2FyeSB0aGF0IGVhY2ggb2JqZWN0IGhhdmUgdGhlIHNhbWUgbnVtYmVyIG9mIGRpbWVuc2lvbnMsIGkuZS4gaXQgZG9lcyBub3QgbWFrZSBzZW5zZSB0byBmaW5kIHRoZSBzZXBhcmF0aW9uIGJldHdlZW4gdGhlIGRhdGEgb2JqZWN0cyAkXHVuZGVybGluZXt4fT0oeF8xLHhfMix4XzMseF80KSQgYW5kICRcdW5kZXJsaW5le3l9PSh5XzEseV8yLHlfMykkLgoKIyMjIyBFeGFtcGxlIDE6IAoKR2l2ZW4gdGhlIGRhdGEgb2JqZWN0cwpcWwpcdW5kZXJsaW5le3h9PSgzNDQzLCA1NjEsIDIzLjEsIDAuODU2KVxxcXVhZCBcdW5kZXJsaW5le3l9PSg1NDQxLDM5NywxOS4zLCAwLjQ3MikKXF0KYW5zd2VyIHRoZSBmb2xsb3dpbmc6CgoxLiBDcmVhdGUgMiBfX2RhdGEgdmVjdG9yc19fIHRvIHJlcHJlc2VudCAkXHVuZGVybGluZXt4fSQgYW5kICRcdW5kZXJsaW5le3l9JC4KCjIuIElkZW50aWZ5IHRoZSBudW1iZXIgb2YgX19kaW1lbnNpb25zX18gaW4gdGhlc2UgdmVjdG9ycy4KCjMuIENyZWF0ZSBhbmQgX19SLWZ1bmN0aW9uX18gdG8gY2FsY3VsYXRlIHRoZSBfX0V1Y2xpZGVhbiBEaXN0YW5jZV9fIGJldHdlZW4gdHdvIGFyYml0cmFyeSB2ZWN0b3JzLgoKNC4gRmluZCB0aGUgRXVjbGlkZWFuIGRpc3RhbmNlIHdpdGhvdXQgcmUtc2NhbGluZyBhbnkgb2YgdGhlIGRpbWVuc2lvbnMuCgozLiBGaW5kIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2UgYnkgcmUtc2NhbGluZyB0aGUgZGltZW5zaW9ucyBhcyBuZWNlc3NhcnkuCgojIyMjIFNvbHV0aW9uCgpfX1BhcnQgMS5fXyBUaGUgZGF0YSB2ZWN0b3JzIGFyZSBnaXZlbiBieQpgYGB7cn0KeDwtYygzNDQzLDU2MSwyMy4xLDAuODU2KQp5PC1jKDU0NDEsMzk3LDE5LjMsMC40NzIpCngKeQpgYGAKCl9fUGFydCAyLl9fIEVhY2ggdmVjdG9yIGhhcyBfX2ZvdXIgZW50cmllc19fIHNvIHRoZSBkaW1lbnNpb24gaXMgX180X18uCgpfX1BhcnQgMy5fXyAgVG8gaWxsdXN0cmF0ZSBob3cgdGhpcyBmdW5jdGlvbiBzaG91bGQgb3BlcmF0ZSwgd2Ugd29yayBvdXIgd2F5IGZyb20gaW5zaWRlIHRvIG91dHNpZGU6CmBgYHtyfQp4LXkKYGBgCldlIHNlZSB0aGF0IF9feC15X18gcmV0dXJucyBhbm90aGVyIDQtZGltZW5zaW9uYWwgdmVjdG9yLCB0aGVzZSBhcmUgdGhlIGRpZmZlcmVuY2VzIGluIF9fZWFjaCBkaW1lbnNpb25fXwoKCgoqIFNxdWFyaW5nIF9feC15X18gd2UgZmluZApgYGB7cn0KKHgteSleMgpgYGAKVGhpcyBoYXMgdGFrZW4gdGhlIHZhbHVlIGluIGVhY2ggZGltZW5zaW9uIG9mIF9feC15X18gYW5kIHNxdWFyZWQgaXQuCgoqIFRvIGFkZCB0aGVzZSBmb3VyIG51bWJlcnMgd2UgdXNlIHRoZSBfX1JfXy1mdW5jdGlvbiwgX19zdW0oKV9fCmBgYHtyfQpzdW0oKHgteSleMikKYGBgClRoaXMgaGFzIGFkZGVkIHRoZSBmb3VyIG51bWJlcnMgaW4gX18oeC15KV4yX18uCgoqIFRoZSBsYXN0IHN0ZXAgaXMgdG8gdGFrZSB0aGUgc3F1YXJlIHJvb3Qgb2YgdGhpcyBudW1iZXIsIGFuZCB3ZSBkb2UgdGhpcyB1c2luZyB0aGUgX19SX18tZnVuY3Rpb24gX19zcXJ0KClfXy4KCmBgYHtyfQpzcXJ0KHN1bSgoeC15KV4yKSkKYGBgCgoKIyMjIyBUaGUgRXVjbGlkZWFuIGRpc3RhbmNlIGZ1bmN0aW9uCgpBbHRlcm5hdGl2ZWx5LCB3ZSBjYW4gY3JlYXRlIGEgZnVuY3Rpb24gX19FRCgpX18gKGZvciBFdWNsaWRlYW4gRGlzdGFuY2UpIGFzIGZvbGxvd3MKCmBgYHtyfQpFRCA8LSBmdW5jdGlvbihhLGIpewogIEV1Y2xpZGVhbl9EaXN0YW5jZSA9IHNxcnQoc3VtKChhLWIpXjIpKQogIHJldHVybihFdWNsaWRlYW5fRGlzdGFuY2UpCn0gIyBhIGFuZCBiIGFyZSBwbGFjZS1ob2xkZXJzLCB5b3UgYW4gdXNlIGFueSBzeW1ib2xzIHlvdSB3aXNoLgpgYGAKKiBUaGUgYXJndW1lbnRzIF9hXyBhbmQgX2JfIGFyZSBwbGFjZSBob2xkZXJzIGluIHRoaXMgZGVmaW5pdGlvbiwgYW5kIHRoZXkgcmVwcmVzZW50IHRoZSB2ZWN0b3JzIHdlIHdhbnQgdG8gZmluZCB0aGUgZGlzdGFuY2UgYmV0d2Vlbi4gQW55IHN5bWJvbHMgbWF5IGJlIHVzZWQgaW4gcGxhY2Ugb2YgX2FfIGFuZCBfYl8KCiogVGhlIEV1Y2xpZGVhbiBkaXN0YW5jZSBiZXR3ZWVuIF9feF9fIGFuZCBfX3lfXyBpcyBub3cgY2FsY3VsYXRlIHVzaW5nIF9fRUQoKV9fCmBgYHtyfQpFRCh4LHkpCmBgYAoqIFRoaXMgYWdyZWVzIHdpdGggdGhlIHZhbHVlIHdlIHByZXZpb3VzbHkgZm91bmQuCgojIyMjIFJlbWFyazoKVGhpcyBkaXN0YW5jZSBpcyBkb21pbmF0ZWQgYnkgdGhlIGRpc3RhbmNlIGJldHdlZW4gX194X18gYW5kIF9feV9fIGFsb25nIHRoZSBfX2ZpcnN0IGRpbWVuc2lvbl9fLgoKCl9fUGFydCA0Ll9fIE5leHQgd2UgcmUtc2NhbGUgZWFjaCBvZiB0aGUgZGltZW5zaW9ucyBzbyB0aGV5IGFyZSBhbGwgbWVhc3VyZWQgaW4gdGhvdXNhbmRzLCBhbmQgdXNlIHRoZSBmdW5jdGlvbiB3ZSBwcmV2aW91c2x5IGRlZmluZWQgdG8gY2FsY3VsYXRlIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2UgYmV0d2VlbiB0aGVzZSByZXNjYWxlZCBkYXRhIG9iamVjdHMKCmBgYHtyfQp4MTwtYygzNDQzLDU2MTAsMjMxMCw4NTYwKQp5MTwtYyg1NDQxLDM5NzAsMTkzMCw0NzIwKQpFRCh4MSx5MSkKYGBgCiMjIyMgUmVtYXJrOgpJbiB0aGlzIGNhc2UgdGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlIGRhdGEgb2JqZWN0cyBpcyBtZWFzdXJlZCBtb3JlIGV2ZW5seSBhY3Jvc3MgYWxsIGRpbWVuc2lvbnMuCgoKIyMgVGhlIE1hbmhhdHRhbiBNZXRyaWMKR2l2ZW4gdGhlIGRhdGEgb2JqZWN0cyAkXHVuZGVybGluZXt4fT0oeF8xLHhfMixcbGRvdHMseF9uKSQgYW5kICRcdW5kZXJsaW5le3l9PSh5XzEseV8yLFxsZG90cyx5X24pJCwgdGhlIE1hbmhhdHRhbiBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBkYXRhIG9iamVjdHMgaWQgZ2l2ZW4gYnkKXFsKXGxhbmdsZSBcdW5kZXJsaW5le3h9LCBcdW5kZXJsaW5le3l9XHJhbmdsZV97TX0gPSBcc3VtX3tpPTF9XntufVxsZWZ0XHZlcnQgeF9pLXlfaVxyaWdodFx2ZXJ0PVxsZWZ0XHZlcnQgeF8xLXlfMVxyaWdodFx2ZXJ0K1xsZWZ0XHZlcnQgeF8yLXlfMlxyaWdodFx2ZXJ0K1xsZG90cytcbGVmdFx2ZXJ0IHhfbi15X25ccmlnaHRcdmVydC4KXF0KCiMjIyMgUmVtYXJrczoKCjEuIEFnYWluLCB0aGUgc21hbGxlciB0aGUgTWFuaGF0dGFuIGRpc3RhbmNlLCB0aGUgbW9yZSBzaW1pbGFyIHRoZSBkYXRhIG9iamVjdHMuCgoyLiBBcyB3aXRoIHRoZSBFdWNsaWRlYW4gbWV0cmljLCB0aGUgTWFuaGF0dGFuIGRpc3RhbmNlIG9ubHkgbWFrZXMgc2Vuc2Ugd2hlbiB0aGUgZGF0YSBvYmplY3RzICRcdW5kZXJsaW5le3h9JCBhbmQgJFx1bmRlcmxpbmV7eX0kIGhhdmUgdGhlIHNhbWUgbnVtYmVyIG9mIGRpbWVuc2lvbnMuCgojIyMgRXhlcmNpc2UgMToKCkRlZmluZSBhIGZ1bmN0aW9uIHRvIG1lYXN1cmUgdGhlIE1hbmhhdHRhbiBkaXN0YW5jZSBiZXR3ZWVuIHR3byBkYXRhIG9iamVjdHMuIAoKIyMjIyBSZW1hcms6IApUbyBkbyB0aGlzIHlvdSBvbmx5IGhhdmUgdG8gbW9kaWZ5IHRoZSBmdW5jdGlvbiBfX0VEKClfXyBzbGlnaHRseSEhCgoKCiMjIyBFeGVyY2lzZSAyOgoKR2l2ZW4gdGhlIGRhdGEgb2JqZWN0cwoKXFsKXHVuZGVybGluZXt1fT0oMTQxNSwgMTg0MywgOTkyLCA4NzUpXHFxdWFkIFx1bmRlcmxpbmV7dn09KDI1MzMsIDEwMDUsIDMyOSwgMTc2KQpcXQoKYW5zd2VyIHRoZSBmb2xsb3dpbmc6CgoxLiBJZGVudGlmeSB0aGUgbnVtYmVyIG9mIGRpbWVuc2lvbnMgY2hhcmFjdGVyaXNpbmcgdGhlIGRhdGEgb2JqZWN0cyAkXHVuZGVybGluZXt1fSQgYW5kICRcdW5kZXJsaW5le3Z9JC4KCjIuIFVzZSB0aGUgX19NYW5oYXR0YW4gZGlzdGFuY2UgZnVuY3Rpb25fXyAgdG8gcXVhbnRpZnkgdGhlIHNpbWlsYXJpdHkgYmV0d2VlbiB0aGUgdHdvIGRhdGEgb2JqZWN0cyAkXHVuZGVybGluZXt1fSQgYW5kICRcdW5kZXJsaW5le3Z9JCAodGhlcmUgaXMgbm8gbmVlZCB0byByZS1zY2FsZSBkaW1lbnNpb25zIGluIHRoaXMgY2FzZSkuCgoKICAgIAogICAgCiMjIyBFeGFtcGxlIDMKCjEuIENyZWF0ZSBfXzEyX18gZGF0YSB2ZWN0b3JzIHRvIHJlcHJlc2VudCBlYWNoIG9mIHRoZSBmb2xsb3dpbmcuClxbClx1bmRlcmxpbmV7YX09KDM5ODYsMTk0Myw0NDQpXFwKXHVuZGVybGluZXtifT0oNDI3MSwxNDQ0LDM0NClcXApcdW5kZXJsaW5le2N9PSg4NDQzLDIwMjAsNDE1KVxcClx1bmRlcmxpbmV7ZH09KDE4OTgsMTg3NywzMTEpXFwKXHVuZGVybGluZXtlfT0oMjkxNiwxMjUzLDI4OSlcXApcdW5kZXJsaW5le2Z9PSg3NDc2LDE1ODMsMzQ3KVxcClx1bmRlcmxpbmV7Z309KDM2OTAsMjEzMSw0OTEpXFwKXHVuZGVybGluZXtofT0oMTM1NSwxODM3LDI2NSlcXApcdW5kZXJsaW5le2l9PSg3MjUxLDE0OTIsMzA2KVxcClx1bmRlcmxpbmV7an09KDY0OTgsMTY4Myw0MDUpXFwKXHVuZGVybGluZXtrfT0oNTIxMSwxMjk4LDM2NylcXApcdW5kZXJsaW5le2x9PSg4MDAxLDE5NTAsMzkyKVxcClxdCgoyLiBVc2UgdGhlIF9fUl9fLWZ1bmN0aW9uIF9fcmJpbmQoKV9fICB0byBjcmVhdGUgYSBfX21hdHJpeF9fIHJlcHJlc2VudGluZyB0aGVzZSB2ZWN0b3JzIGluIGEgc2luZ2xlIGRhdGEgc3RydWN0dXJlLiAgWAoKMy4gIFVzZSB0aGUgX19zY2FsZSgpX18tZnVuY3Rpb24sIHNvIHRoYXQgYWxsIHZlY3RvciBkaW1lbnNpb25zIGFyZSBvZiBzaW1pbGFyIHNpemUuCgo0LiBVc2UgdGhlIF9fUl9fLWZ1bmN0aW9uIF9fZGlzdCgpX18gdG8gY2FsY3VsYXRlIHRoZSBFdWNsaWRlYW4gYW5kIE1hbmhhdHRhbiBkaXN0YW5jZXMgYmV0d2VlbiB0aGVzZSBkYXRhIHZlY3RvcnMuCgo1LiBVc2UgdGhlc2UgRXVjbGlkZWFuIGFuZCBNYW5oYXR0YW4gZGlzdGFuY2VzIHRvIGNyZWF0ZSBhIF9faGVhdCBtYXBfXyB0byByZXByZXNlbnQgdGhlc2UgZGlzdGFuY2VzIGdyYXBoaWNhbGx5LgoKIyMjIFNvbHV0aW9uCgpfX1BhcnQgMS5fXyBXZSBiZWdpbiBieSBjcmVhdGluZyBhIGxpc3Qgb2YgZGF0YSBzdHJ1Y3R1cmVzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGRhdGEgb2JqZWN0cyBnaXZlbi4KCmBgYHtyfQphPC1jKDM5ODYsMTk0Myw0NDQpCmI8LWMoNDI3MSwxNDQ0LDM0NCkKYzwtYyg4NDQzLDIwMjAsNDE1KQpkPC1jKDE4OTgsMTg3NywzMTEpCmU8LWMoMjkxNiwxMjUzLDI4OSkKZjwtYyg3NDc2LDE1ODMsMzQ3KQpnPC1jKDM2OTAsMjEzMSw0OTEpCmg8LWMoMTM1NSwxODM3LDI2NSkKaTwtYyg3MjUxLDE0OTIsMzA2KQpqPC1jKDY0OTgsMTY4Myw0MDUpCms8LWMoNTIxMSwxMjk4LDM2NykKbDwtYyg4MDAxLDE5NTAsMzkyKQpgYGAKCl9fUGFydCAyLl9fIFRoZSBfX3NpbmdsZV9fIGRhdGEgc3RydWN0dXJlIGlzIGdpdmVuIGJ5OgoKYGBge3J9CkRhdGE8LXJiaW5kKGEsYixjLGQsZSxmLGcsaCxpLGosayxsKQpEYXRhCmBgYAoKX19QYXJ0IDQuX18gTm90aWNlIHRoYXQgdGhlIGZpcnN0IHR3byBkaW1lbnNpb25zIG9mIGVhY2ggdmVjdG9yIGlzIG1lYXN1cmVkIGluIHRob3VzYW5kcywgd2hpbGUgdGhlIGxhc3QgZGltZW5zaW9uIGlzIG1lYXN1cmVkIGluIHVuaXRzIG9mIDEwMC4gVG8gbWFrZSBhbGwgZGltZW5zaW9ucyBoYXZlIHRoZSBzYW1lIG9yZGVyLCB3ZSBkaXZpZGUgdGhlIGZpcnN0IHR3byBkaW1lbnNpb25zIGJ5IDEwMDAgYW5kIHRoZSBsYXN0IGRpbWVuc2lvbiBieSAxMDAuIFRoaXMgaXMgZG9uZSB1c2luZyB0aGUgX19zY2FsZSgpX18tZnVuY3Rpb24KCmBgYHtyfQpTY2FsZWRfRGF0YTwtc2NhbGUoRGF0YSxjZW50ZXI9RixzY2FsZT1jKDEwMDAsMTAwMCwxMDApKQpTY2FsZWRfRGF0YQpgYGAKCl9fUGFydCA0Ll9fIFRoZSBfX0V1Y2xpZGVhbiBkaXN0YW5jZXNfXyBiZXR3ZWVuIGFsbCBvZiB0aGVzZSB2ZWN0b3JzIGlzIGdpdmVuIGJ5OgoKYGBge3J9CkRpc3RfRTwtIGRpc3QoU2NhbGVkX0RhdGEsbWV0aG9kPSJldWNsaWRlYW4iKQpEaXN0X0UKYGBgCgpUaGUgX19NYW5oYXR0YW4gZGlzdGFuY2VzX18gYmV0d2VlbiB0aGVzZSB2ZWN0b3JzIGlzIGdpdmVuIGJ5CgpgYGB7cn0KRGlzdF9NPC0gZGlzdChTY2FsZWRfRGF0YSxtZXRob2Q9Im1hbmhhdHRhbiIpCkRpc3RfTQpgYGAKCiMjIyMgUmVtYXJrOgoKQnkgdXNpbmcgX19TY2FsZWRfRGF0YV9fIHRoZSBkaXN0YW5jZXMgYmV0d2VlbiB0aGUgMTIgb2JqZWN0cyBpcyBzcHJlYWQgbW9yZSBldmVubHkgYWNyb3NzIGFsbCB0aHJlZSBkaW1lbnNpb25zLgoKX19QYXJ0IDUuX18KCiogVG8gY3JlYXRlIGEgaGVhdCBtYXAgd2UgZmlyc3QgaW1wb3J0IHRoZSBsaWJyYXJ5IF9fZmFjdG9leHRyYV9fLiBJdCBtYXkgYmUgbmVjZXNzYXJ5IHRvIGluc3RhbGwgdGhpcyBwYWNrYWdlIGZpcnN0IGJ5IGdvaW5nIHRvCgogICAgICBfX1Rvb2xzX18gJFxyaWdodGFycm93JCBfX0luc3RhbGxfXyAKICAgICAgCiAgICAgIGFuZCB3cml0aW5nIF9fZmFjdG9leHRyYV9fIGluIHRoZSBfX1BhY2thZ2VzX18gY2VsbCBpbiB0aGlzIGRpYWxvZ3VlIGJveC4KCmBgYHtyfQpsaWJyYXJ5KCJmYWN0b2V4dHJhIikKYGBgCgoqIFRvIGNyZWF0ZSB0aGUgaGVhdC1tYXAgZm9yIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2Ugd2UgdXNlIHRoZSBmdW5jdGlvbiBfX2Z2aXpfZGlzdCgpX18gYXMgZm9sbG93czoKCmBgYHtyfQpmdml6X2Rpc3QoRGlzdF9FLGdyYWRpZW50PWxpc3QobG93PSdpdm9yeScsbWlkPSdjb3JuZmxvd2VyYmx1ZScsaGlnaHQ9J21pZG5pZ2h0Ymx1ZScpKQpgYGAKCiogU2ltaWxhcmx5LCB0aGUgaGVhdC1tYXAgZm9yIHRoZSBNYW5oYXR0YW4gZGlzdGFuY2VzIGlzIGdpdmVuIGJ5CgpgYGB7cn0KZnZpel9kaXN0KERpc3RfTSxncmFkaWVudD1saXN0KGxvdz0naXZvcnknLG1pZD0nY29ybmZsb3dlcmJsdWUnLGhpZ2h0PSdyZWQnKSkKYGBgCgoqIFRoZSBjb2xvdXJzIGluIHRoZSBvcHRpb24gX19ncmFkaWVudF9fIG1heSBiZSBjaG9zZW4gZnJlZWx5LCBhbHRob3VnaCBjb2xvdXJzIHNob3VsZCBiZSBjaG9zZW4gc28gdGhhdCBkaXN0YW5jZSBvYmplY3RzIGFwcGVhciBzaWduaWZpY2FudGx5IGRpZmZlcmVudCB0byBuZWFyYnkgb2JqZWN0cy4KCiMjIyBFeGVyY2lzZSAzCgpFaWdodCBtYWtlcyBvZiBjYXIgd2VyZSBjb21wYXJlZCB1c2luZyB0aHJlZSBkaWZmZXJlbnQgY3JpdGVyaWE6CgoKICAxLiBQcmljZSAoZXVybykgICAgMi4gRW5naW5lIChjYykgICAgMy4gRWZmaWNpZW5jeSAoa20vTCkKClRoZSBkYXRhIGNvbGxlY3RlZCBhcmUgZ2l2ZW4gaW4gdGhlIHRhYmxlIGJlbG93OgoKfCBNYWtlICAgICAgIHwgUHJpY2UgKGV1cm8pIHwgRW5naW5lIChjYykgfCBFZmZpY2llbmN5IHwKfC0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLXwKfCBBdWRpICAgICAgIHwgMzg4MTIgICAgIHwgICAgIDE5NjggICAgfCAgICAgMjIuNyAgIHwKfCBCTVcgICAgICAgIHwgMzU1NzEgICAgIHwgICAgIDE5OTUgICAgfCAgICAgMjMuNyAgIHwKfCBDaXRyb2VuICAgIHwgMjA0NTEgICAgIHwgICAgIDE1NjAgICAgfCAgICAgMjQuNyAgIHwKfCBIeXVuZGFpICAgIHwgMjM2MjAgICAgIHwgICAgIDE2ODUgICAgfCAgICAgMjMuNyAgIHwKfCBKYWd1YXIgICAgIHwgNTM2OTMgICAgIHwgICAgIDE5OTkgICAgfCAgICAgMjYuNSAgIHwKfCBNZXJjZWRlcyAgIHwgNDE5MDkgICAgIHwgICAgIDE5NTAgICAgfCAgICAgMjUuNSAgIHwKfCBNaXRzdWJpc2hpIHwgMjgxOTIgICAgIHwgICAgIDIyNjggICAgfCAgICAgMTguOCAgIHwKfCBUb3lvdGEgICAgIHwgMjc5NzggICAgIHwgICAgIDE5OTUgICAgfCAgICAgMjEuNiAgIHwKClVzaW5nIHRoZSBkYXRhIGluIHRoaXMgdGFibGUsIGFuc3dlciB0aGUgZm9sbG93aW5nOgoKMS4gQ3JlYXRlIDggX19kYXRhIHZlY3RvcnNfXyB0byByZXByZXNlbnQgZWFjaCBjYXIgbWFrZS4KCjIuIENvbWJpbmUgdGhlc2UgZGF0YSB2ZWN0b3JzIHVzaW5nIHRoZSBfX3JiaW5kKClfXyBmdW5jdGlvbiwgdG8gY3JlYXRlIGEgc2luZ2xlIF9fZGF0YSBzdHJ1Y3R1cmVfXy4KCjMuIFJlc2NhbGUgdGhlIGRpbWVuc2lvbnMgb2YgdGhpcyBkYXRhIHN0cmljdHVyZSwgc28gZWFjaCBkaW1lbnNpb24gaXMgbWVhc3VyZWQgaW4gdGhlIHNhbWUgb3JkZXIuCgo0LiBDcmVhdGUgYSB0YWJsZSBvZiBFdWNsaWRlYW4gYW5kIE1hbmhhdHRhbiBkaXN0YW5jZXMgZm9yIHRoaXMgcmUtc2NhbGVkIGRhdGEgc3RydWN0dXJlLgoKNS4gQ3JlYXRlIGEgaGVhdCBtYXAgdG8gcmVwcmVzZW50IHRoZXNlIGRpc3RhbmNlcyAoRXVjbGlkZWFuIGFuZCBNYW5oYXR0YW4pIHVzaW5nIHRoZSBmdW5jdGlvbiBfX2Z2aXpfZGlzdCgpX18gCgoKCiMjIERpc3RhbmNlcyBhbmQgSGVhdCBNYXBzIHdpdGggRGF0YSBGaWxlcwoKKiBXZSBjYW4gYWxzbyBhcHBseSB0aGUgZnVuY3Rpb25zIF9fZGlzdCgpX18gYW5kIF9fZnZpc19kaXN0KClfXyB0byBkYXRhIHdoaWNoIHdlIGltcG9ydCBmcm9tIF9fLmNzdl9fIGZpbGVzLgoKKiBIb3dldmVyLCB0byBkbyBzbywgaXQgbWF5IGJlIG5lY2Vzc2FyeSB0byBtb2RpZnkgdGhlIGltcG9ydGVkIGRhdGEgc2xpZ2h0bHksIG90aGVyd2lzZSB0aGVzZSBmdW5jdGlvbnMgX21heV8gcmV0dXJuIGVycm9ycywgZGVwZW5kaW5nIG9uIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGEgZmlsZS4KCiMjIyBFeGFtcGxlIDQ6CgpTaXggbWFrZXMgb2YgbGFwdG9wIHdlIGNvbXBhcmVkIHVzaW5nIHRocmVlIGRpZmZlcmVudCBjcml0ZXJpYToKCiAgMS4gU3RvcmFnZSAoR0IpIDIuIFNjcmVlbiAoaW5jaGVzKSAgMy4gUmFtIChHQikgNC4gQ2xvY2stc3BlZWQgKEdIeikKICAKVGhlIGRhdGEgaXMgYXZhaWxhYmxlIGF0IAoKICBfX01vb2RsZV9fICRccmlnaHRhcnJvdyQgX19EYXRhIFZpc3VhbGlzYXRpb25fXyAkXHJpZ2h0YXJyb3ckIF9fRGF0YSBGaWxlc19fICRccmlnaHRhcnJvdyQgX19MYXB0b3BEYXRhX18KICAKKiBJbXBvcnQgdGhpcyBkYXRhIHVzaW5nIF9fcmVhZC5jc3YoKV9fIGluIHRoZSB1c3VhbCB3YXkuCgpgYGB7cn0KTGFwdG9wczwtcmVhZC5jc3YoZmlsZS5jaG9vc2UoKSkKTGFwdG9wcwpgYGAKClVzaW5nIHRoaXMgZGF0YSwgYW5zd2VyIHRoZSBmb2xsb3dpbmc6CgpfX1BhcnQgMS5fXyAgX19UcnlfXyB0byBmaW5kIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2VzIGJldHdlZW4gdGhlc2UgbGFwdG9wcyB1c2luZyB0aGUgZGF0YSBmcmFtZSBhYm92ZS4KCiogV2UgYXR0ZW1wdCB0byBhcHBseSB0aGUgX19kaXN0KClfXyBmdW5jdGlvbiBkaXJlY3RseSB0byB0aGUgZGF0YSBzdHJ1Y3R1cmUgX19MYXB0b3BzX18KCmBgYHtyfQpkaXN0KExhcHRvcHMsbWV0aG9kPSdldWNsaWRlYW4nKQpgYGAKKiBOb3RpY2Ugd2UgZ2V0IGFuIGVycm9yIG1lc3NhZ2UgKE5BcyBpbnRyb2R1Y2VkIGJ5IGNvZXJjaW9uKSBvbiB0aGUgdG9wIGxpbmUuIFRoaXMgaXMgYmVjYXVzZSBfX2Rpc3QoKV9fIGRvZXMgbm90IGtub3cgaG93IHRvIGZpbmQgYSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBlbnRyaWVzIGluIHRoZSBjb2x1bW4gbGFiZWxsZWQgX19NYWtlX18uCgoKX19QYXJ0IDIuX18gTW9kaWZ5IHRoaXMgZGF0YSBzdHJ1Y3R1cmUgc28gdGhhdCBpdCBjYW4gdXNlZCBieSB0aGUgZnVuY3Rpb24gX19kaXN0KClfXy4KCiogTm90aWNlIHRoZXJlIGFyZSBfXzYgcm93c19fIGFuZCBfXzUgY29sdW1uc19fIGluIHRoZSBkYXRhIHN0cnVjdHVyZSBfX0xhcHRvcHNfXy4gVGhlIGZ1bmN0aW9uIF9fZGlzdCgpX18gY2FuIG9ubHkgYmUgYXBwbGllZCB0byBkYXRhIGZyYW1lcyBjb25zaXN0aW5nIG9mIG51bWJlcnMuCgoKKiBUaGVyZSBhcmUgX190d28gc3RlcHNfXyB0byBjcmVhdGluZyB0aGUgbmVjZXNzYXJ5IGRhdGEgc3RydWN0dXJlOgogICAgCiAgKiBfX1N0ZXAgMS5fXyBFeHRyYWN0IHRoZSBuYW1lcyBvZiB0aGUgY29tcHV0ZXIgbWFrZXMgX19hcyBjaGFyYWN0ZXJzX18gZnJvbSB0aGUgZGF0YSBzdHJ1Y3R1cmUgX19MYXB0b3BzX18gdXNpbmcgdGhlIGZ1bmN0aW9uIF9fYXMuY2hhcmFjdGVyKClfXwoKYGBge3J9CkxhcHRvcE5hbWVzPC1hcy5jaGFyYWN0ZXIoTGFwdG9wcyRNYWtlKQpMYXB0b3BOYW1lcwpgYGAgICAgCiAgICAKICAgIAogICAgCiAgICAKICAgKiBfX1N0ZXAgMi5fXyBDcmVhdGUgYSBuZXcgX19kYXRhLmZyYW1lKClfXyBmcm9tIF9fTGFwdG9wc19fIGJ1dCBfX2V4Y2x1ZGVfXyB0aGUgZmlyc3QgY29sdW1uIGFuZCBhc3NpZ24gdGhlIHJvd3Mgb2YgdGhlIG5ldyBkYXRhIGZyYW1lIHRoZSBsYWJlbHMgaW4gX19MYXB0b3BOYW1lc19fCiAgICAKYGBge3J9CkxhcHRvcERhdGE8LWFzLmRhdGEuZnJhbWUoTGFwdG9wc1ssLTFdLHJvdy5uYW1lcz1MYXB0b3BOYW1lcykKTGFwdG9wRGF0YQpgYGAKCiogVGhlIGZ1bmN0aW9uIF9fZGlzdCgpX18gd2lsbCBub3cgd29yayB3aXRoIHRoZSBuZXcgZGF0YSBmcmFtZSBfX0xhcHRvcERhdGFfXwoKYGBge3J9CmRpc3QoTGFwdG9wRGF0YSwgbWV0aG9kPSdldWNsaWRlYW4nKQpgYGAKCl9fUGFydCAzLl9fIFJlc2NhbGUgdGhpcyBuZXcgZGF0YSBzdHJ1Y3R1cmUgc28gdGhhdCBhbGwgZGltZW5zaW9ucyBoYXZlIHZhbHVlcyBiZXR3ZWVuIDAgYW5kIDEwLgoKKiBXZSBkaXZpZGUgdGhlIGZpcnN0IGNvbHVtbiBieSAxMDAwLCB0aGUgc2Vjb25kIGJ5IDEwLCB0aGUgdGhpcmQgYnkgMTAgYW5kIHRoZSBmb3VydGggYnkgMSB0byBnaXZlIHRoZSByZS1zY2FsZWQgZGF0YSBmcmFtZQoKYGBge3J9ClNjYWxlZF9MYXB0b3BfRGF0YSA8LSBzY2FsZShMYXB0b3BEYXRhLGNlbnRlcj1GLHNjYWxlPWMoMTAwMCwxMCwxMCwxKSkKU2NhbGVkX0xhcHRvcF9EYXRhCmBgYAoqIE5vdyBhbGwgdGhlIGNvbHVtbnMgYXJlIG9mIGEgc2ltaWxhciBzaXplLCBhbmQgc28gbm8gZGltZW5zaW9uIHdpbGwgZG9taW5hdGUgd2hlbiB3ZSBjYWxjdWxhdGUgdGhlIGRpc3RhbmNlcyBiZXR3ZWVuIHRoZSBsYXB0b3BzLgoKX19QYXJ0IDQuX18gRmluZCB0aGUgRXVjbGlkZWFuIGFuZCBNYW5oYXR0YW4gZGlzdGFuY2VzIGluIHRoaXMgcmUtc2NhbGVkIGRhdGEgc3RydWN0dXJlLgoKVGhlIF9fRXVjbGlkZWFuIGRpc3RhbmNlc19fIGFyZQoKYGBge3J9CkxhcHRvcF9EaXN0YW5jZXNfRTwtZGlzdChTY2FsZWRfTGFwdG9wX0RhdGEsbWV0aG9kPSdldWNsaWRlYW4nKQpMYXB0b3BfRGlzdGFuY2VzX0UKYGBgCgpUaGUgX19NYW5oYXR0YW4gZGlzdGFuY2VzX18gYXJlCgpgYGB7cn0KTGFwdG9wX0Rpc3RhbmNlc19NPC1kaXN0KFNjYWxlZF9MYXB0b3BfRGF0YSxtZXRob2Q9J21hbmhhdHRhbicpCkxhcHRvcF9EaXN0YW5jZXNfTQpgYGAKCgpfX1BhcnQgNS5fXyBDcmVhdGUgYSBoZWF0IG1hcCB0byByZXByZXNlbnQgdGhlc2UgZGlzdGFuY2VzIChmb3IgdGhlIEV1Y2xpZGVhbiBhbmQgTWFuaGF0dGFuIGRpc3RhbmNlcykuCgpUaGUgaGVhdCBtYXAgZm9yIHRoZSBFdWNsaWRlYW4gZGlzdGFuY2VzIGlzIAoKYGBge3J9CmZ2aXpfZGlzdChMYXB0b3BfRGlzdGFuY2VzX0UsZ3JhZGllbnQ9bGlzdChsb3c9J2l2b3J5JyxtaWQ9J2Nvcm5mbG93ZXJibHVlJyxoaWdodD0ncmVkJykpCmBgYAoKVGhlIGhlYXQgbWFwIGZvciB0aGUgTWFuaGF0dGFuIGRpc3RhbmNlcyBpcyAKCmBgYHtyfQpmdml6X2Rpc3QoTGFwdG9wX0Rpc3RhbmNlc19NLGdyYWRpZW50PWxpc3QobG93PSdpdm9yeScsbWlkPSdjb3JuZmxvd2VyYmx1ZScsaGlnaHQ9J3JlZCcpKQpgYGAKICAgIAojIyMgRXhlcmNpc2UgNDoKCk9uIE1vb2RsZSwgdGhlIGZpbGUgX19JU0VRKDE2Tm92MjAxNykuY3N2X18gY29udGFpbnMgdGhlIHNoYXJlIHByaWNlIGluIOKCrCwgdGhlIG1hcmtldCBjYXBpdGFsaXNhdGlvbiBpbiDigqwgTWlsbGlvbnMsIGFuZCB0aGUgcmVsYXRpdmUgdmFsdWUgb2YgdGhlIGNvbXBhbnkgdG8gdGhlIG92ZXJhbGwgbWFya2V0IGNhcC4gb2YgdGhlIDIwIGNvbXBhaW5lcyBvbiB0aGUgSVNFUSBpbmRleC4KCl9fTW9vZGxlX18gJFxyaWdodGFycm93JCBfX0RhdGEgVmlzdWFsaXNhdGlvbl9fICRccmlnaHRhcnJvdyQgX19Xb3JrYm9vayBGaWxlc19fICRccmlnaHRhcnJvdyQgX19JU0VRKDE2Tm92MjAxNykuY3N2X18gCgooX1NvdXJjZTpfIGh0dHA6Ly93d3cuaXNxZS5pZSkKClVzaW5nIHRoaXMgZGF0YSBhbnN3ZXIgdGhlIGZvbGxvd2luZzoKCjEuIEltcG9ydCB0aGUgZGF0YSBpbnRvIHRoaXMgX19SX18gd29ya2Jvb2sgdXNpbmcgX19yZWFkLmNzdigpX18uCgoyLiBDcmVhdGUgYW4gYXBwcm9wcmlhdGUgZGF0YSBmcmFtZSBmcm9tIHRoaXMgZGF0YSBmaWxlIHdoaWNoIGNhbiBiZSB1c2VkIGJ5IHRoZSBfX2Rpc3QoKV9fIGZ1bmN0aW9uLgoKMy4gUmVzY2FsZSB0aGUgZGF0YSBjb2x1bW5zIG9mIHRoaXMgbmV3IGRhdGEgZnJhbWUgc28gdGhhdCBhbGwgZGltZW5zaW9ucyBoYXZlIGEgc2ltaWxhciBzaXplLgoKNC4gRmluZCB0aGUgRXVjbGlkZWFuIGFuZCBNYW5oYXR0YW4gZGlzdGFuY2VzIHVzaW5nIHRoaXMgcmUtc2NhbGVkIGRhdGEgZnJhbWUuCgo1LiBDcmVhdGUgYSBoZWF0IG1hcCB0byByZXByZXNlbnQgdGhlIEV1Y2xpZGVhbiBhbmQgTWFuaGF0dGFuIGRpc3RhbmNlcyBiZXR3ZWVuIHRoZXNlIGNvbXBhbmllcy4KCiMjIyBFeGVyY2lzZSA1OgoKT24gTW9vZGxlLCB0aGUgZmlsZSBfX0V1cm9ab25lRGF0YTIwMTcuY3N2X18gIGNvbXBhcmVzIHRoZSBjb3VudHJpZXMgb2YgdGhlIEV1cm8gWm9uZSAoZXhjbHVkaW5nIE1hbHRhIGFzIG5vdCBhbGwgZGF0YSB3YXMgYXZhaWFsYmxlKSwgdXNpbmcgNCBkaWZmZXJlbnQgY3JpdGVyaWEKCiAgMS4gUG9wdWxhdGlvbiAyLiBHRFAgcGVyIGNhcGl0YSAoVVNcJCkgMy4gVG90YWwgRXhwb3J0cyAoVVNcJCkgNC4gVG90YWwgSW1wb3J0cyAoVVNcJCkKCl9fTW9vZGxlX18gJFxyaWdodGFycm93JCBfX0RhdGEgVmlzdWFsaXNhdGlvbl9fICRccmlnaHRhcnJvdyQgX19Xb3JrYm9vayBGaWxlc19fICRccmlnaHRhcnJvdyQgX19FdXJvWm9uZURhdGEyMDE3LmNzdl9fIAoKKF9Tb3VyY2U6XyBodHRwOi8vd3d3LndvcmxkYmFuay5vcmcpCgpVc2luZyB0aGlzIGRhdGEgYW5zd2VyIHRoZSBmb2xsb3dpbmc6CgoxLiBJbXBvcnQgdGhlIGRhdGEgaW50byB0aGlzIF9fUl9fIHdvcmtib29rIHVzaW5nIF9fcmVhZC5jc3YoKV9fLgoKMi4gQ3JlYXRlIGFuIGFwcHJvcHJpYXRlIGRhdGEgZnJhbWUgZnJvbSB0aGlzIGRhdGEgZmlsZSB3aGljaCBjYW4gYmUgdXNlZCBieSB0aGUgX19kaXN0KClfXyBmdW5jdGlvbi4KCjMuIFJlc2NhbGUgdGhlIGRhdGEgY29sdW1ucyBvZiB0aGlzIG5ldyBkYXRhIGZyYW1lIHNvIHRoYXQgYWxsIGRpbWVuc2lvbnMgaGF2ZSBhIHNpbWlsYXIgc2l6ZS4KCjQuIEZpbmQgdGhlIEV1Y2xpZGVhbiBhbmQgTWFuaGF0dGFuIGRpc3RhbmNlcyB1c2luZyB0aGlzIHJlLXNjYWxlZCBkYXRhIGZyYW1lLgoKNS4gQ3JlYXRlIGEgaGVhdCBtYXAgdG8gcmVwcmVzZW50IHRoZSBFdWNsaWRlYW4gYW5kIE1hbmhhdHRhbiBkaXN0YW5jZXMgYmV0d2VlbiB0aGVzZSBjb3VudHJpZXMuCgoKCg==