Feedback should be send to goran.milovanovic_ext@wikimedia.de. These notebook(s) refer to Phab T285458: Generate inputs for 1st sensemaking session about ORES quality score distributions across the Wikidata classes.


0. Setup

NOTE. The usage of mean quality or mean ORES quality in this report is the following one:

  • mean ORES quality is always computed per Wikidata class,
  • by taking that the ORES scores map from A, B, C, E, and E, onto
  • 5, 4, 3, 2, and 1, respectively.

Example. If class has 5 A items, 3 B items, 0 C items, 2 D items, and 10 E items, its mean quality score is

5 * 5 + 3 * 4 + 0 * 3 + 2 * 2 + 10 * 1 = 51

divided by the number of items in that class, which happens to be

5 + 3 + 0 + 2 + 10 = 20.

In this example, the mean quality score for the given Wikidata class would be: 2.55.

### --- Setup
library(mclust)
library(data.table)
library(tidyverse)
library(snowfall)
library(ggrepel)
source('WD_ORES_ClassClustering_Functions.R')
### --- dirTree
dataDir <- paste0(getwd(), "/_data/")
reportingDir <- paste0(getwd(), "/_reporting/")

1. The Fundamental Dataset

## --- Load
# - load
rawData <- data.table::fread(paste0(dataDir, "classQualityContingency.csv"), 
                             header = T)
rownames(rawData) <- rawData$V1
rawData$V1 <- NULL
# - check if there are empty classes
w <- which(rowSums(rawData) == 0)
if (length(w) > 0) {
  print(length(w))
}

The fundamental dataset in this analysis comprises 472034 Wikidata classes and the counts of ORES A, B, C, D, and E scored items in each of them. Here are the first rows from the fundamental dataset, each row representing a Wikidata class:

head(rawData)

2. EDA: The distribution of ORES scores across Wikidata classes

2.1 The distribution of Wikidata class size

The first problem that we arrive at is the distribution of the class size in Wikidata: the number of items present in each class. Let’s take a look at the distribution of class size in Wikidata:

### --- data
dataSet <- rawData
dataSet$numItems <- rowSums(dataSet)
dataSet <- dataSet %>% 
  dplyr::arrange(desc(numItems))
summary(dataSet$numItems)
    Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
       1        1        2      197        4 37215412 

From the value of the median, 2, we can see that 50% of Wikidata classes have two or less than two items only. The 3rd quartile (75% of data) is found at 4, which means than only 25% of classes have more than four items. The maximal number of items is found at 37,215,412, and as we shall see that is the Wikidata class of scholarly article (Q13442814).

It is questionable, thus, whether it makes sense to assess the distribution of ORES quality scores across all Wikidata classes at once. Let’s take a closer look at the top 100 most populated Wikidata classes:

dataSet$class <- rownames(dataSet)
labels <- wmdedata_api_fetch_labels(items = dataSet$class[1:100], 
                                    language = "en",
                                    fallback = T)
top100WDClasses <- dataSet[1:100, c('class', 'numItems')]
top100WDClasses <- dplyr::left_join(top100WDClasses, 
                                    labels,
                                    by = c("class" = "item"))
top100WDClasses

We already know that Astronomical Object (Q6999 + its subclasses) and Scholarly Article (Q13442814 + its subclasses) account for a huge amount of Wikidata items. Let’s single out those two classes and take a look at the distribution of ORES scores inside them.

2.2 ORES quality in Astronomical Object (Q6999 + its subclasses)

Here’s the dataset for Astronomical Object (Q6999 + its subclasses) only:

 astronomicalObjects = c(
    'Q523', 'Q318', 'Q1931185', 'Q1457376', 'Q2247863', 'Q3863', 'Q83373',
    'Q2154519', 'Q726242', 'Q1153690', 'Q204107', 'Q71963409', 'Q67206691',
    'Q1151284', 'Q67206701', 'Q66619666', 'Q72802727', 'Q2168098', 'Q6243',
    'Q72802508', 'Q11282', 'Q72803170', 'Q1332364', 'Q72802977', 'Q6999',
    'Q1491746', 'Q272447', 'Q497654', 'Q204194', 'Q130019', 'Q744691',
    'Q71798532', 'Q46587', 'Q11276', 'Q71965429', 'Q5871', 'Q72803622',
    'Q72803426', 'Q3937', 'Q72803708', 'Q168845', 'Q24452', 'Q67201574',
    'Q2557101', 'Q691269', 'Q13632', 'Q10451997', 'Q28738741', 'Q22247', 'Q6999'
  )
astronomySet <- dataSet %>% 
  dplyr::filter(class %in% astronomicalObjects)
head(astronomySet)

It actually comprises 11.87% of Wikidata. Let’s take a look at the distribution of the mean per class ORES score:

astronomySet$meanQuality <- apply(astronomySet[, c('A', 'B', 'C', 'D', 'E')], 1, function(x) {
  return(sum((x * 5:1))/sum(x))
})
ggplot(data = astronomySet, 
       aes(x = meanQuality)) + 
  geom_density(alpha = .25, fill = "darkred", color = "darkred", size = .25) + 
  ggtitle("Astronomical Object (Q6999 + its subclasses)") + 
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  xlim(0, 5) +
  theme(plot.title = element_text(size = 12, hjust = .5))

summary(astronomySet$meanQuality)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.000   2.093   2.495   2.504   2.839   3.213 

2.4 ORES quality in Scholarly Article (Q13442814 + its subclasses)

Here’s the dataset for Scholarly Article (Q13442814 + its subclasses) only:

scientificPapers = c('Q7318358', 'Q2782326', 'Q18918145',
                     'Q1504425', 'Q7316896', 'Q92998777',
                     'Q10885494', 'Q15706459', 'Q58901470',
                     'Q59458414', 'Q56478376', 'Q12183006',
                     'Q82969330', 'Q58900805', 'Q13442814')
articleSet <- dataSet %>% 
  dplyr::filter(class %in% scientificPapers)
head(articleSet)

It actually comprises 42.49% of Wikidata. Let’s take a look at the distribution of the mean per class ORES score:

articleSet$meanQuality <- apply(articleSet[, c('A', 'B', 'C', 'D', 'E')], 1, function(x) {
  return(sum((x * 5:1))/sum(x))
})
ggplot(data = articleSet, 
       aes(x = meanQuality)) + 
  geom_density(alpha = .25, fill = "darkblue", color = "darkblue", size = .25) + 
  ggtitle("Scholarly Article (Q13442814 + its subclasses)") + 
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  xlim(0, 5) +
  theme(plot.title = element_text(size = 12, hjust = .5))

summary(articleSet$meanQuality)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.500   2.292   2.819   2.726   3.188   3.500 

2.5 ORES quality in Human (Q5)

Here’s the dataset for Human (Q5) only:

human = 'Q5'
humanSet <- dataSet %>% 
  dplyr::filter(class %in% human)
humanSet$meanQuality <- apply(humanSet[, c('A', 'B', 'C', 'D', 'E')], 1, function(x) {
  return(sum((x * 5:1))/sum(x))
})
head(humanSet)

It actually comprises 7.51% of Wikidata.

2.6 The distribution of ORES scores in the remaining Wikidata classes

The two large Wikidata classes - Astronomical Object (Q6999) and Scholarly Article (Q13442814), and their subclasses as well - will be removed from all following analyses.

Let’s take a look at the distribution of ORES scores in the remaining Wikidata classes:

wikidataSet <- dataSet %>% 
  dplyr::filter(!(class %in% astronomicalObjects | class %in% scientificPapers))
head(wikidataSet)
wikidataSet$meanQuality <- apply(wikidataSet[, c('A', 'B', 'C', 'D', 'E')], 1, function(x) {
  return(sum((x * 5:1))/sum(x))
})
ggplot(data = wikidataSet, 
       aes(x = meanQuality)) + 
  geom_density(alpha = .25, fill = "darkorange", color = "darkorange", size = .25) + 
  ggtitle("Wikidata - (Astronomical Object + Scholarly Article)") + 
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  xlim(0, 5) +
  theme(plot.title = element_text(size = 12, hjust = .5))

summary(wikidataSet$meanQuality)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   2.000   2.000   2.251   2.955   5.000 

Interestingly, the distribution of the mean ORES score in the remained of Wikidata is clearly bimodal, with a mean at 2.2508731. Let’s find out what are the largest Wikidata classes found in the following ranges of mean ORES scores:

  • 1.5 - 2.5
  • 2.5 - 3.5

First the lower score classes, with a range of mean quality of 1.5 - 2.5:

lowerScoreSet <- wikidataSet %>% 
  dplyr::filter(meanQuality >= 1.5 & meanQuality < 2.5) %>% 
  arrange(desc(numItems)) %>% 
  top_n(100)
higherScoreSet <- wikidataSet %>% 
  dplyr::filter(meanQuality >= 2.5 & meanQuality < 3.5)  %>% 
  arrange(desc(numItems)) %>% 
  top_n(100)
lowerScoreClasses <- wmdedata_api_fetch_labels(items = lowerScoreSet$class, 
                                               language = "en", 
                                               fallback = T)
lowerScoreClasses <- dplyr::left_join(lowerScoreClasses,
                                      dplyr::select(lowerScoreSet, class, numItems, meanQuality),
                                      by = c("item" = "class"))
lowerScoreClasses

Now for the higher score classes, with a range of mean quality of 2.5 - 3.5:

higherScoreClasses <- wmdedata_api_fetch_labels(items = higherScoreSet$class, 
                                               language = "en", 
                                               fallback = T)
higherScoreClasses <- dplyr::left_join(higherScoreClasses,
                                       dplyr::select(higherScoreSet, class, numItems, meanQuality),
                                       by = c("item" = "class"))
higherScoreClasses

Neither the lower now the higher score classes isolated in the analysis above do not seem prima facie semantically coherent.

2.7 The distribution of ORES scores in the remaining Wikidata classes: >= 1000 items

Let’s take a look at the distribution of ORES scores in the remaining Wikidata classes but taking into our consideration only classes with 1,000 or more items:

wikidataSet <- dataSet %>% 
  dplyr::filter(!(class %in% astronomicalObjects | class %in% scientificPapers)) %>% 
  dplyr::filter(numItems >= 1000)
head(wikidataSet)
wikidataSet$meanQuality <- apply(wikidataSet[, c('A', 'B', 'C', 'D', 'E')], 1, function(x) {
  return(sum((x * 5:1))/sum(x))
})
ggplot(data = wikidataSet, 
       aes(x = meanQuality)) + 
  geom_density(alpha = .25, fill = "darkorange", color = "darkorange", size = .25) + 
  ggtitle("Wikidata - (Astronomical Object + Scholarly Article)\nOnly classes w. >= 1000 items") + 
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  xlim(0, 5) +
  theme(plot.title = element_text(size = 12, hjust = .5))

summary(wikidataSet$meanQuality)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   2.000   2.038   2.209   2.448   3.999 

Interestingly, the distribution of the mean ORES score in the remained of Wikidata is clearly bimodal, with a mean at 2.2508731. Let’s find out what are the largest Wikidata classes found in the following ranges of mean ORES scores:

  • 1.5 - 2.5
  • 2.5 - 3.5

First the lower score classes, with a range of mean quality of 1.5 - 2.5:

lowerScoreSet <- wikidataSet %>% 
  dplyr::filter(meanQuality >= 1.5 & meanQuality < 2.5) %>% 
  arrange(desc(numItems)) %>% 
  top_n(100)
higherScoreSet <- wikidataSet %>% 
  dplyr::filter(meanQuality >= 2.5 & meanQuality < 3.5)  %>% 
  arrange(desc(numItems)) %>% 
  top_n(100)
lowerScoreClasses <- wmdedata_api_fetch_labels(items = lowerScoreSet$class, 
                                               language = "en", 
                                               fallback = T)
lowerScoreClasses <- dplyr::left_join(lowerScoreClasses,
                                      dplyr::select(lowerScoreSet, class, numItems, meanQuality),
                                      by = c("item" = "class"))
lowerScoreClasses

Now for the higher score classes, with a range of mean quality of 2.5 - 3.5:

higherScoreClasses <- wmdedata_api_fetch_labels(items = higherScoreSet$class, 
                                               language = "en", 
                                               fallback = T)
higherScoreClasses <- dplyr::left_join(higherScoreClasses,
                                       dplyr::select(higherScoreSet, class, numItems, meanQuality),
                                       by = c("item" = "class"))
higherScoreClasses

2.8 The distribution of ORES scores in the remaining Wikidata classes: < 1000 items

Let’s take a look at the distribution of ORES scores in the remaining Wikidata classes but taking into our consideration only classes with less than 1,000 items:

wikidataSet <- dataSet %>% 
  dplyr::filter(!(class %in% astronomicalObjects | class %in% scientificPapers)) %>% 
  dplyr::filter(numItems < 1000)
head(wikidataSet)
wikidataSet$meanQuality <- apply(wikidataSet[, c('A', 'B', 'C', 'D', 'E')], 1, function(x) {
  return(sum((x * 5:1))/sum(x))
})
ggplot(data = wikidataSet, 
       aes(x = meanQuality)) + 
  geom_density(alpha = .25, fill = "darkorange", color = "darkorange", size = .25) + 
  ggtitle("Wikidata - (Astronomical Object + Scholarly Article)\nOnly classes w. >= 1000 items") + 
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  xlim(0, 5) +
  theme(plot.title = element_text(size = 12, hjust = .5))

summary(wikidataSet$meanQuality)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   2.000   2.000   2.251   2.957   5.000 

Interestingly, the distribution of the mean ORES score in the remained of Wikidata is clearly bimodal, with a mean at 2.2508731. Let’s find out what are the largest Wikidata classes found in the following ranges of mean ORES scores:

  • 1.5 - 2.5
  • 2.5 - 3.5

First the lower score classes, with a range of mean quality of 1.5 - 2.5:

lowerScoreSet <- wikidataSet %>% 
  dplyr::filter(meanQuality >= 1.5 & meanQuality < 2.5) %>% 
  arrange(desc(numItems)) %>% 
  top_n(100)
higherScoreSet <- wikidataSet %>% 
  dplyr::filter(meanQuality >= 2.5 & meanQuality < 3.5)  %>% 
  arrange(desc(numItems)) %>% 
  top_n(100)
lowerScoreClasses <- wmdedata_api_fetch_labels(items = lowerScoreSet$class, 
                                               language = "en", 
                                               fallback = T)
lowerScoreClasses <- dplyr::left_join(lowerScoreClasses,
                                      dplyr::select(lowerScoreSet, class, numItems, meanQuality),
                                      by = c("item" = "class"))
lowerScoreClasses

Now for the higher score classes, with a range of mean quality of 2.5 - 3.5:

higherScoreClasses <- wmdedata_api_fetch_labels(items = higherScoreSet$class, 
                                               language = "en", 
                                               fallback = T)
higherScoreClasses <- dplyr::left_join(higherScoreClasses,
                                       dplyr::select(higherScoreSet, class, numItems, meanQuality),
                                       by = c("item" = "class"))
higherScoreClasses

3. Clustering the ORES scores across the Wikidata classes

We will make an attempt at a better understanding of the distribution of ORES scores content-wide Wikidata by clustering the dataset following the removal of Astronomical Object (Q6999) and Scholarly Article (Q13442814) (and their subclasses as well).

clustSet <- wikidataSet %>% 
  dplyr::select(A, B, C, D, E)
### --- Cluster with {mclust}
snowfall::sfInit(parallel = T, cpus = 23)
R Version:  R version 4.0.3 (2020-10-10) 
snowfall::sfExport('clustSet')
snowfall::sfLibrary(mclust)
Library mclust loaded.
t1 <- Sys.time()
models <- snowfall::sfClusterApplyLB(1:30, function(x) {
  mclust.options(hcUse = "STD")
  return(
    Mclust(clustSet, 
           G = 2:30, 
           initialization = list())
  )
})
t2 <- Sys.time()
print(
  paste0("mclust took: ", 
         as.character(difftime(t2, t1, units = "mins"))
  )
)
[1] "mclust took: 24.2635710159938"
snowfall::sfStop()

### --- Post-hoc model selection
maxBIC <- sapply(models, function(x) {
  max(apply(x$BIC, 2, max, na.rm = T))
})
no non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Infno non-missing arguments to max; returning -Inf
selectedModel <- which.max(maxBIC)
wd_classes_optimal <- models[[selectedModel]]
rm(models)

### --- Inspect Model
legend_args <- list(x = "bottomright", 
                    ncol = 5)
plot(wd_classes_optimal, 
     what = 'BIC', 
     legendArgs = legend_args)

We have actually run many cluster models and picked up the most optimal that we have found:

wd_classes_optimal$modelName
[1] "VII"
wd_classes_optimal$G
[1] 9
wd_classes_optimal$n
[1] 471970
wd_classes_optimal$d
[1] 5

The most optimal clustering solution that we were able to produce encompasses 12 clusters.

3.1 Clusters: number of Wikidata classes per cluster, mean ORES quality per Cluster, and the average number of items in a class per cluster

### --- Join clusters to data points (WD classes)
cluster_assign <- as.data.frame(wd_classes_optimal$classification)
colnames(cluster_assign) <- "cluster"
cluster_assign$class <- rownames(cluster_assign)
clustSet$class <- rownames(clustSet)
clustSet <- dplyr::left_join(clustSet,
                             cluster_assign,
                             by = "class")
rm(cluster_assign)

### --- Inspect clusters
clustSet$classSize <- rowSums(clustSet[, c('A', 'B', 'C', 'D', 'E')])
clustSet$meanQuality <- apply(clustSet[, c('A', 'B', 'C', 'D', 'E')], 1, function(x) {
  return(sum((x * 5:1))/sum(x))
})


# - ORES cluster quality profiles
classProfiles <- clustSet %>% 
  dplyr::select(cluster, meanQuality, classSize, A, B, C, D, E) %>% 
  dplyr::group_by(cluster) %>% 
  dplyr::summarise(meanClusterQuality = mean(meanQuality),
                   meanClusterClassSize = mean(classSize), 
                   medianClusterClassSize = median(classSize), 
                   numClasses = n(),
                   A = sum(A), 
                   B = sum(B), 
                   C = sum(C), 
                   D = sum(D), 
                   E = sum(E)) %>% 
  arrange(desc(meanClusterClassSize))
classProfiles$totalItems <- classProfiles$A + 
  classProfiles$B + 
  classProfiles$C + 
  classProfiles$D + 
  classProfiles$E

# - Analytics
# - Plot A. Mean per Cluster Class Size vs Mean per Cluster Class Quality
ggplot(data = classProfiles, 
       aes(x = meanClusterClassSize, 
           y = meanClusterQuality, 
           size = numClasses, 
           label = cluster)) + 
  geom_point(color = "red", fill = "tomato") +
  geom_text_repel(size = 4) +
  ggtitle("Mean per Cluster WD Class Size vs Mean per Cluster Class Quality") + 
  scale_x_continuous(labels = scales::comma) + 
  scale_y_continuous(labels = scales::comma) +
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(size = 10, hjust = .5))

In the chart above each bubble represents a cluster of Wikidata classes. The horizontel axis, labeled meanClusterClassSize, represents the mean size of a Wikidata class in some particular cluster: the size of the class is the number of items in it, and as each cluster encompasses a number of classes we compute the mean size of a class in a cluster. The vertical axis represents the mean ORES quality: it is the average of all Wikidata classes’ mean ORES scores, for all classes found in a particular cluster. The size of the bubble represents the number of classes found in each cluster. There is one cluster encompassing a relatively low number of Wikidata classes, labeled as Cluster 6, on the right of the plot, but that very cluster also encompasses very large Wikidata classes (and that is why it is positioned on the far right of the horizontal axis). All other clusters encompass Wikidata classes with a relatively low number of items in them (and that is why they are position on the far left of the horizontal axis). In the lower left corner of the chart we find Cluster 2, a large bubble - which means that it encompasses a large number of classes - with a relatively small average number of items per class, and relatively low mean ORES scores per class.

3.2 Clusters: the distribution of ORES scores per cluster

# - Plot B. Average number of A, B, C, D, and E ranked items per class, per Cluster
plotBFrame <- classProfiles %>% 
  dplyr::select(cluster, A, B, C, D, E) %>% 
  pivot_longer(-cluster, 
               names_to = "ORES Rank", 
               values_to = "Num Items per ORES Rank"
  ) 
plotBFrame$cluster <- factor(plotBFrame$cluster)
ggplot(data = plotBFrame, 
       aes(x = `ORES Rank`, 
           y = `Num Items per ORES Rank`,
           group = cluster,
           color = cluster,
           fill = cluster)
) + 
  geom_point() + geom_path() + 
  facet_wrap(~cluster, scales = "free") + 
  scale_y_continuous(labels = scales::comma) +
  ylab("Mean(Num.items)\n
       per ORES Rank per WD Class") +
  ggtitle("Average number of A, B, C, D, and E ranked items per WD Class, per Cluster") +
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(legend.position = "none") + 
  theme(strip.background = element_blank()) + 
  theme(plot.title = element_text(size = 10, hjust = .5)) 

The horizontal axis in all charts represents the ORES score, while the vertical axis represents the mean number of items per class in a cluster that belong to a particular ORES score category (A, B, C, D, or E); each chart represents a cluster.

If we superimpose the quality score profiles:

# - superimposed: 
ggplot(data = plotBFrame, 
       aes(x = `ORES Rank`, 
           y = log(`Num Items per ORES Rank`),
           group = cluster,
           color = cluster,
           fill = cluster)
) + 
  geom_point() + geom_path() + 
  scale_y_continuous(labels = scales::comma) +
  ylab("Mean(Num.items)\n
       per ORES Rank per WD Class") +
  ggtitle("Average number of A, B, C, D, and E ranked items per WD Class, per Cluster") +
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(legend.position = "top") + 
  theme(plot.title = element_text(size = 12))

3.3 Clusters: Describing their content

We will query the Wikidata API to obtain the English labels for top 20 most populated Wikidata classes in each cluster.

### --- Describe clusters
describeClusters <- lapply(sort(unique(clustSet$cluster)), function(x) {
  d <- clustSet[clustSet$cluster == x, ]
  d <- d %>% 
    dplyr::arrange(desc(classSize))
  return(d$class[1:20])
})
# - call wmdedata_api_fetch_labels()
describeClusters <- lapply(describeClusters, 
                           wmdedata_api_fetch_labels, 
                           language = "en", 
                           fallback = T)

However, just from looking at Wikidata class labels, it does not seem possible to provide a coherent interpretation of the clusters. It is interesting, however, to take a look at the composition of Cluster 6:

describeClusters[[6]]

This cluster (6) encompasses large Wikidata classes. It is quite possible that some of them could be excluded from further analyses (e.g. Wikimedia category, Wikimedia template, Wikimedia disambiguation page, etc). Also, some of them might need a separate treatment similar to Astronomical Objects and Scholarly Articles (e.g. Human, taxon, gene, protein, etc).

It is possible that a more comprehensive description of the clustering solution could be obtained from re-labeling the present Wikidata classes by their super-classes in the Wikidata hierarchy via P31/P279, and by looking at the distribution of the super-classes across the clusters. However, it would take a lot of computational resources to be able to describe the solution in that way.

4. Classes of highest and lowest mean ORES quality

Of course, we will take a look only at the Wikidata classes with above average number of items.

dataSet <- rawData
dataSet$classSize <- rowSums(dataSet)
dataSet$meanQuality <- apply(dataSet[, c('A', 'B', 'C', 'D', 'E')], 1, function(x) {
  return(sum((x * 5:1))/sum(x))
})
dataSet$Class <- rownames(dataSet)
dataSet <- dataSet %>% 
  dplyr::filter(classSize > mean(dataSet$classSize))
dataSet <- dataSet %>% 
  dplyr::arrange(desc(meanQuality))
topQualitySet <- head(dataSet, 100)
topQualitySetLabels <- wmdedata_api_fetch_labels(topQualitySet$Class, 
                                                 language = "en", 
                                                 fallback = T)
topQualitySetLabels <- dplyr::left_join(topQualitySetLabels,
                                        dplyr::select(topQualitySet, Class, classSize, meanQuality),
                                        by = c("item" = "Class"))

lowQualitySet <- tail(dataSet, 100)
lowQualitySetLabels <- wmdedata_api_fetch_labels(lowQualitySet$Class, 
                                                 language = "en", 
                                                 fallback = T)
lowQualitySetLabels <- dplyr::left_join(lowQualitySetLabels,
                                        dplyr::select(lowQualitySet, Class, classSize, meanQuality),
                                        by = c("item" = "Class"))

The 100 top quality Wikidata classes are:

topQualitySetLabels

The lower 100 quality Wikidata classes are:

lowQualitySetLabels

We can see that many Wikimedia* classes are found among the classes of lowest mean ORES scores in the dataset.


License: GPLv3 This Notebook is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This Notebook is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this Notebook. If not, see http://www.gnu.org/licenses/.


LS0tCnRpdGxlOiBUaGUgRGlzdHJpYnV0aW9uIG9mIE9SRVMgUXVhbGl0eSBTY29yZXMgYWNyb3NzIFdpa2lkYXRhIENsYXNzZXMKYXV0aG9yOgotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhwogIGFmZmlsaWF0aW9uOiBXaWtpbWVkaWEgRGV1dHNjaGxhbmQsIERhdGEgU2NpZW50aXN0CmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCICVZJylgIgphYnN0cmFjdDogCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0aGVtZTogc3BhY2VsYWIKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgdG9jX2RlcHRoOiA1CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDUKLS0tCgohW10oaW1nL1dpa2lkYXRhLWxvZ28tZW4ucG5nKSAKIVtdKGltZy9XaWtpbWVkaWFfRGV1dHNjaGxhbmRfTG9nb19zbWFsbC5wbmcpCgoqKioKKipGZWVkYmFjayoqIHNob3VsZCBiZSBzZW5kIHRvIGBnb3Jhbi5taWxvdmFub3ZpY19leHRAd2lraW1lZGlhLmRlYC4gClRoZXNlIG5vdGVib29rKHMpIHJlZmVyIHRvIFtQaGFiIFQyODU0NTg6IEdlbmVyYXRlIGlucHV0cyBmb3IgMXN0IHNlbnNlbWFraW5nIHNlc3Npb24gYWJvdXQgT1JFUyBxdWFsaXR5IHNjb3JlIGRpc3RyaWJ1dGlvbnMgYWNyb3NzIHRoZSBXaWtpZGF0YSBjbGFzc2VzXShodHRwczovL3BoYWJyaWNhdG9yLndpa2ltZWRpYS5vcmcvVDI4NTQ1OCkuCgoqKioKCiMjIyAwLiBTZXR1cAoKKipOT1RFLioqIFRoZSB1c2FnZSBvZiAqKm1lYW4gcXVhbGl0eSoqIG9yICoqbWVhbiBPUkVTIHF1YWxpdHkqKiBpbiB0aGlzIHJlcG9ydCBpcyB0aGUgZm9sbG93aW5nIG9uZToKCi0gbWVhbiBPUkVTIHF1YWxpdHkgaXMgYWx3YXlzIGNvbXB1dGVkIHBlciBXaWtpZGF0YSBjbGFzcywKLSBieSB0YWtpbmcgdGhhdCB0aGUgT1JFUyBzY29yZXMgbWFwIGZyb20gQSwgQiwgQywgRSwgYW5kIEUsIG9udG8KLSA1LCA0LCAzLCAyLCBhbmQgMSwgcmVzcGVjdGl2ZWx5LgoKKkV4YW1wbGUuKiBJZiBjbGFzcyBoYXMgNSBBIGl0ZW1zLCAzIEIgaXRlbXMsIDAgQyBpdGVtcywgMiBEIGl0ZW1zLCBhbmQgMTAgRSBpdGVtcywgaXRzIG1lYW4gcXVhbGl0eSBzY29yZSBpcwoKPiA1ICogNSArIDMgKiA0ICsgMCAqIDMgKyAyICogMiArIDEwICogMSA9IDUxCgpkaXZpZGVkIGJ5IHRoZSBudW1iZXIgb2YgaXRlbXMgaW4gdGhhdCBjbGFzcywgd2hpY2ggaGFwcGVucyB0byBiZQoKPiA1ICsgMyArIDAgKyAyICsgMTAgPSAyMC4KCkluIHRoaXMgZXhhbXBsZSwgdGhlIG1lYW4gcXVhbGl0eSBzY29yZSBmb3IgdGhlIGdpdmVuIFdpa2lkYXRhIGNsYXNzIHdvdWxkIGJlOiBgciA1MS8yMGAuCgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIFNldHVwCmxpYnJhcnkobWNsdXN0KQpsaWJyYXJ5KGRhdGEudGFibGUpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHNub3dmYWxsKQpsaWJyYXJ5KGdncmVwZWwpCnNvdXJjZSgnV0RfT1JFU19DbGFzc0NsdXN0ZXJpbmdfRnVuY3Rpb25zLlInKQojIyMgLS0tIGRpclRyZWUKZGF0YURpciA8LSBwYXN0ZTAoZ2V0d2QoKSwgIi9fZGF0YS8iKQpyZXBvcnRpbmdEaXIgPC0gcGFzdGUwKGdldHdkKCksICIvX3JlcG9ydGluZy8iKQpgYGAKCiMjIyAxLiBUaGUgRnVuZGFtZW50YWwgRGF0YXNldAoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQojIyAtLS0gTG9hZAojIC0gbG9hZApyYXdEYXRhIDwtIGRhdGEudGFibGU6OmZyZWFkKHBhc3RlMChkYXRhRGlyLCAiY2xhc3NRdWFsaXR5Q29udGluZ2VuY3kuY3N2IiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQpCnJvd25hbWVzKHJhd0RhdGEpIDwtIHJhd0RhdGEkVjEKcmF3RGF0YSRWMSA8LSBOVUxMCiMgLSBjaGVjayBpZiB0aGVyZSBhcmUgZW1wdHkgY2xhc3Nlcwp3IDwtIHdoaWNoKHJvd1N1bXMocmF3RGF0YSkgPT0gMCkKaWYgKGxlbmd0aCh3KSA+IDApIHsKICBwcmludChsZW5ndGgodykpCn0KYGBgCgpUaGUgZnVuZGFtZW50YWwgZGF0YXNldCBpbiB0aGlzIGFuYWx5c2lzIGNvbXByaXNlcyBgciBkaW0ocmF3RGF0YSlbMV1gIFdpa2lkYXRhIGNsYXNzZXMgYW5kIHRoZSBjb3VudHMgb2YgT1JFUyBBLCBCLCBDLCBELCBhbmQgRSBzY29yZWQgaXRlbXMgaW4gZWFjaCBvZiB0aGVtLiBIZXJlIGFyZSB0aGUgZmlyc3Qgcm93cyBmcm9tIHRoZSBmdW5kYW1lbnRhbCBkYXRhc2V0LCBlYWNoIHJvdyByZXByZXNlbnRpbmcgYSBXaWtpZGF0YSBjbGFzczoKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0KaGVhZChyYXdEYXRhKQpgYGAKCiMjIyAyLiBFREE6IFRoZSBkaXN0cmlidXRpb24gb2YgT1JFUyBzY29yZXMgYWNyb3NzIFdpa2lkYXRhIGNsYXNzZXMKCiMjIyMgMi4xIFRoZSBkaXN0cmlidXRpb24gb2YgV2lraWRhdGEgY2xhc3Mgc2l6ZQoKVGhlIGZpcnN0IHByb2JsZW0gdGhhdCB3ZSBhcnJpdmUgYXQgaXMgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgY2xhc3Mgc2l6ZSBpbiBXaWtpZGF0YTogdGhlIG51bWJlciBvZiBpdGVtcyBwcmVzZW50IGluIGVhY2ggY2xhc3MuIExldCdzIHRha2UgYSBsb29rIGF0IHRoZSBkaXN0cmlidXRpb24gb2YgY2xhc3Mgc2l6ZSBpbiBXaWtpZGF0YToKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0KIyMjIC0tLSBkYXRhCmRhdGFTZXQgPC0gcmF3RGF0YQpkYXRhU2V0JG51bUl0ZW1zIDwtIHJvd1N1bXMoZGF0YVNldCkKZGF0YVNldCA8LSBkYXRhU2V0ICU+JSAKICBkcGx5cjo6YXJyYW5nZShkZXNjKG51bUl0ZW1zKSkKc3VtbWFyeShkYXRhU2V0JG51bUl0ZW1zKQpgYGAKRnJvbSB0aGUgdmFsdWUgb2YgdGhlIG1lZGlhbiwgMiwgd2UgY2FuIHNlZSB0aGF0IDUwJSBvZiBXaWtpZGF0YSBjbGFzc2VzIGhhdmUgdHdvIG9yIGxlc3MgdGhhbiB0d28gaXRlbXMgb25seS4gVGhlIDNyZCBxdWFydGlsZSAoNzUlIG9mIGRhdGEpIGlzIGZvdW5kIGF0IDQsIHdoaWNoIG1lYW5zIHRoYW4gb25seSAyNSUgb2YgY2xhc3NlcyBoYXZlIG1vcmUgdGhhbiBmb3VyIGl0ZW1zLiBUaGUgbWF4aW1hbCBudW1iZXIgb2YgaXRlbXMgaXMgZm91bmQgYXQgYDM3LDIxNSw0MTJgLCBhbmQgYXMgd2Ugc2hhbGwgc2VlIHRoYXQgaXMgdGhlIFdpa2lkYXRhIGNsYXNzIG9mIGBzY2hvbGFybHkgYXJ0aWNsZSAoUTEzNDQyODE0KWAuCgpJdCBpcyBxdWVzdGlvbmFibGUsIHRodXMsIHdoZXRoZXIgaXQgbWFrZXMgc2Vuc2UgdG8gYXNzZXNzIHRoZSBkaXN0cmlidXRpb24gb2YgT1JFUyBxdWFsaXR5IHNjb3JlcyBhY3Jvc3MgYWxsIFdpa2lkYXRhIGNsYXNzZXMgYXQgb25jZS4gTGV0J3MgdGFrZSBhIGNsb3NlciBsb29rIGF0IHRoZSB0b3AgMTAwIG1vc3QgcG9wdWxhdGVkIFdpa2lkYXRhIGNsYXNzZXM6CgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CmRhdGFTZXQkY2xhc3MgPC0gcm93bmFtZXMoZGF0YVNldCkKbGFiZWxzIDwtIHdtZGVkYXRhX2FwaV9mZXRjaF9sYWJlbHMoaXRlbXMgPSBkYXRhU2V0JGNsYXNzWzE6MTAwXSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhbmd1YWdlID0gImVuIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFsbGJhY2sgPSBUKQp0b3AxMDBXRENsYXNzZXMgPC0gZGF0YVNldFsxOjEwMCwgYygnY2xhc3MnLCAnbnVtSXRlbXMnKV0KdG9wMTAwV0RDbGFzc2VzIDwtIGRwbHlyOjpsZWZ0X2pvaW4odG9wMTAwV0RDbGFzc2VzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoImNsYXNzIiA9ICJpdGVtIikpCnRvcDEwMFdEQ2xhc3NlcwpgYGAKCldlIGFscmVhZHkga25vdyB0aGF0ICoqQXN0cm9ub21pY2FsIE9iamVjdCoqIChgUTY5OTlgICsgaXRzIHN1YmNsYXNzZXMpIGFuZCAqKlNjaG9sYXJseSBBcnRpY2xlKiogKGBRMTM0NDI4MTRgICsgaXRzIHN1YmNsYXNzZXMpIGFjY291bnQgZm9yIGEgaHVnZSBhbW91bnQgb2YgV2lraWRhdGEgaXRlbXMuIExldCdzIHNpbmdsZSBvdXQgdGhvc2UgdHdvIGNsYXNzZXMgYW5kIHRha2UgYSBsb29rIGF0IHRoZSBkaXN0cmlidXRpb24gb2YgT1JFUyBzY29yZXMgaW5zaWRlIHRoZW0uCgojIyMjIDIuMiBPUkVTIHF1YWxpdHkgaW4gQXN0cm9ub21pY2FsIE9iamVjdCAoYFE2OTk5YCArIGl0cyBzdWJjbGFzc2VzKQoKSGVyZSdzIHRoZSBkYXRhc2V0IGZvciBBc3Ryb25vbWljYWwgT2JqZWN0IChgUTY5OTlgICsgaXRzIHN1YmNsYXNzZXMpIG9ubHk6CgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CiBhc3Ryb25vbWljYWxPYmplY3RzID0gYygKICAgICdRNTIzJywgJ1EzMTgnLCAnUTE5MzExODUnLCAnUTE0NTczNzYnLCAnUTIyNDc4NjMnLCAnUTM4NjMnLCAnUTgzMzczJywKICAgICdRMjE1NDUxOScsICdRNzI2MjQyJywgJ1ExMTUzNjkwJywgJ1EyMDQxMDcnLCAnUTcxOTYzNDA5JywgJ1E2NzIwNjY5MScsCiAgICAnUTExNTEyODQnLCAnUTY3MjA2NzAxJywgJ1E2NjYxOTY2NicsICdRNzI4MDI3MjcnLCAnUTIxNjgwOTgnLCAnUTYyNDMnLAogICAgJ1E3MjgwMjUwOCcsICdRMTEyODInLCAnUTcyODAzMTcwJywgJ1ExMzMyMzY0JywgJ1E3MjgwMjk3NycsICdRNjk5OScsCiAgICAnUTE0OTE3NDYnLCAnUTI3MjQ0NycsICdRNDk3NjU0JywgJ1EyMDQxOTQnLCAnUTEzMDAxOScsICdRNzQ0NjkxJywKICAgICdRNzE3OTg1MzInLCAnUTQ2NTg3JywgJ1ExMTI3NicsICdRNzE5NjU0MjknLCAnUTU4NzEnLCAnUTcyODAzNjIyJywKICAgICdRNzI4MDM0MjYnLCAnUTM5MzcnLCAnUTcyODAzNzA4JywgJ1ExNjg4NDUnLCAnUTI0NDUyJywgJ1E2NzIwMTU3NCcsCiAgICAnUTI1NTcxMDEnLCAnUTY5MTI2OScsICdRMTM2MzInLCAnUTEwNDUxOTk3JywgJ1EyODczODc0MScsICdRMjIyNDcnLCAnUTY5OTknCiAgKQphc3Ryb25vbXlTZXQgPC0gZGF0YVNldCAlPiUgCiAgZHBseXI6OmZpbHRlcihjbGFzcyAlaW4lIGFzdHJvbm9taWNhbE9iamVjdHMpCmhlYWQoYXN0cm9ub215U2V0KQpgYGAKCkl0IGFjdHVhbGx5IGNvbXByaXNlcyBgciByb3VuZChzdW0oYXN0cm9ub215U2V0JG51bUl0ZW1zKS9zdW0oZGF0YVNldCRudW1JdGVtcykqMTAwLCAyKWAlIG9mIFdpa2lkYXRhLiBMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBtZWFuIHBlciBjbGFzcyBPUkVTIHNjb3JlOgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQphc3Ryb25vbXlTZXQkbWVhblF1YWxpdHkgPC0gYXBwbHkoYXN0cm9ub215U2V0WywgYygnQScsICdCJywgJ0MnLCAnRCcsICdFJyldLCAxLCBmdW5jdGlvbih4KSB7CiAgcmV0dXJuKHN1bSgoeCAqIDU6MSkpL3N1bSh4KSkKfSkKZ2dwbG90KGRhdGEgPSBhc3Ryb25vbXlTZXQsIAogICAgICAgYWVzKHggPSBtZWFuUXVhbGl0eSkpICsgCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gLjI1LCBmaWxsID0gImRhcmtyZWQiLCBjb2xvciA9ICJkYXJrcmVkIiwgc2l6ZSA9IC4yNSkgKyAKICBnZ3RpdGxlKCJBc3Ryb25vbWljYWwgT2JqZWN0IChRNjk5OSArIGl0cyBzdWJjbGFzc2VzKSIpICsgCiAgdGhlbWVfYncoKSArIAogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB4bGltKDAsIDUpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgaGp1c3QgPSAuNSkpCmBgYApgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CnN1bW1hcnkoYXN0cm9ub215U2V0JG1lYW5RdWFsaXR5KQpgYGAKIyMjIyAyLjQgT1JFUyBxdWFsaXR5IGluIFNjaG9sYXJseSBBcnRpY2xlIChgUTEzNDQyODE0YCArIGl0cyBzdWJjbGFzc2VzKQoKSGVyZSdzIHRoZSBkYXRhc2V0IGZvciBTY2hvbGFybHkgQXJ0aWNsZSAoYFExMzQ0MjgxNGAgKyBpdHMgc3ViY2xhc3Nlcykgb25seToKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0Kc2NpZW50aWZpY1BhcGVycyA9IGMoJ1E3MzE4MzU4JywgJ1EyNzgyMzI2JywgJ1ExODkxODE0NScsCiAgICAgICAgICAgICAgICAgICAgICdRMTUwNDQyNScsICdRNzMxNjg5NicsICdROTI5OTg3NzcnLAogICAgICAgICAgICAgICAgICAgICAnUTEwODg1NDk0JywgJ1ExNTcwNjQ1OScsICdRNTg5MDE0NzAnLAogICAgICAgICAgICAgICAgICAgICAnUTU5NDU4NDE0JywgJ1E1NjQ3ODM3NicsICdRMTIxODMwMDYnLAogICAgICAgICAgICAgICAgICAgICAnUTgyOTY5MzMwJywgJ1E1ODkwMDgwNScsICdRMTM0NDI4MTQnKQphcnRpY2xlU2V0IDwtIGRhdGFTZXQgJT4lIAogIGRwbHlyOjpmaWx0ZXIoY2xhc3MgJWluJSBzY2llbnRpZmljUGFwZXJzKQpoZWFkKGFydGljbGVTZXQpCmBgYAoKSXQgYWN0dWFsbHkgY29tcHJpc2VzIGByIHJvdW5kKHN1bShhcnRpY2xlU2V0JG51bUl0ZW1zKS9zdW0oZGF0YVNldCRudW1JdGVtcykqMTAwLCAyKWAlIG9mIFdpa2lkYXRhLiBMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBtZWFuIHBlciBjbGFzcyBPUkVTIHNjb3JlOgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQphcnRpY2xlU2V0JG1lYW5RdWFsaXR5IDwtIGFwcGx5KGFydGljbGVTZXRbLCBjKCdBJywgJ0InLCAnQycsICdEJywgJ0UnKV0sIDEsIGZ1bmN0aW9uKHgpIHsKICByZXR1cm4oc3VtKCh4ICogNToxKSkvc3VtKHgpKQp9KQpnZ3Bsb3QoZGF0YSA9IGFydGljbGVTZXQsIAogICAgICAgYWVzKHggPSBtZWFuUXVhbGl0eSkpICsgCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gLjI1LCBmaWxsID0gImRhcmtibHVlIiwgY29sb3IgPSAiZGFya2JsdWUiLCBzaXplID0gLjI1KSArIAogIGdndGl0bGUoIlNjaG9sYXJseSBBcnRpY2xlIChRMTM0NDI4MTQgKyBpdHMgc3ViY2xhc3NlcykiKSArIAogIHRoZW1lX2J3KCkgKyAKICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsgCiAgeGxpbSgwLCA1KSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGhqdXN0ID0gLjUpKQpgYGAKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0Kc3VtbWFyeShhcnRpY2xlU2V0JG1lYW5RdWFsaXR5KQpgYGAKCiMjIyMgMi41IE9SRVMgcXVhbGl0eSBpbiBIdW1hbiAoYFE1YCkKCkhlcmUncyB0aGUgZGF0YXNldCBmb3IgSHVtYW4gKGBRNWApIG9ubHk6CgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9Cmh1bWFuID0gJ1E1JwpodW1hblNldCA8LSBkYXRhU2V0ICU+JSAKICBkcGx5cjo6ZmlsdGVyKGNsYXNzICVpbiUgaHVtYW4pCmh1bWFuU2V0JG1lYW5RdWFsaXR5IDwtIGFwcGx5KGh1bWFuU2V0WywgYygnQScsICdCJywgJ0MnLCAnRCcsICdFJyldLCAxLCBmdW5jdGlvbih4KSB7CiAgcmV0dXJuKHN1bSgoeCAqIDU6MSkpL3N1bSh4KSkKfSkKaGVhZChodW1hblNldCkKYGBgCgpJdCBhY3R1YWxseSBjb21wcmlzZXMgYHIgcm91bmQoc3VtKGh1bWFuU2V0JG51bUl0ZW1zKS9zdW0oZGF0YVNldCRudW1JdGVtcykqMTAwLCAyKWAlIG9mIFdpa2lkYXRhLgoKIyMjIyAyLjYgVGhlIGRpc3RyaWJ1dGlvbiBvZiBPUkVTIHNjb3JlcyBpbiB0aGUgcmVtYWluaW5nIFdpa2lkYXRhIGNsYXNzZXMKClRoZSB0d28gbGFyZ2UgV2lraWRhdGEgY2xhc3NlcyAtICoqQXN0cm9ub21pY2FsIE9iamVjdCoqIChgUTY5OTlgKSBhbmQgKipTY2hvbGFybHkgQXJ0aWNsZSoqIChgUTEzNDQyODE0YCksIGFuZCB0aGVpciBzdWJjbGFzc2VzIGFzIHdlbGwgLSB3aWxsIGJlIHJlbW92ZWQgZnJvbSBhbGwgZm9sbG93aW5nIGFuYWx5c2VzLgoKTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBPUkVTIHNjb3JlcyBpbiB0aGUgcmVtYWluaW5nIFdpa2lkYXRhIGNsYXNzZXM6CgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9Cndpa2lkYXRhU2V0IDwtIGRhdGFTZXQgJT4lIAogIGRwbHlyOjpmaWx0ZXIoIShjbGFzcyAlaW4lIGFzdHJvbm9taWNhbE9iamVjdHMgfCBjbGFzcyAlaW4lIHNjaWVudGlmaWNQYXBlcnMpKQpoZWFkKHdpa2lkYXRhU2V0KQpgYGAKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0Kd2lraWRhdGFTZXQkbWVhblF1YWxpdHkgPC0gYXBwbHkod2lraWRhdGFTZXRbLCBjKCdBJywgJ0InLCAnQycsICdEJywgJ0UnKV0sIDEsIGZ1bmN0aW9uKHgpIHsKICByZXR1cm4oc3VtKCh4ICogNToxKSkvc3VtKHgpKQp9KQpnZ3Bsb3QoZGF0YSA9IHdpa2lkYXRhU2V0LCAKICAgICAgIGFlcyh4ID0gbWVhblF1YWxpdHkpKSArIAogIGdlb21fZGVuc2l0eShhbHBoYSA9IC4yNSwgZmlsbCA9ICJkYXJrb3JhbmdlIiwgY29sb3IgPSAiZGFya29yYW5nZSIsIHNpemUgPSAuMjUpICsgCiAgZ2d0aXRsZSgiV2lraWRhdGEgLSAoQXN0cm9ub21pY2FsIE9iamVjdCArIFNjaG9sYXJseSBBcnRpY2xlKSIpICsgCiAgdGhlbWVfYncoKSArIAogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB4bGltKDAsIDUpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgaGp1c3QgPSAuNSkpCmBgYApgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CnN1bW1hcnkod2lraWRhdGFTZXQkbWVhblF1YWxpdHkpCmBgYApJbnRlcmVzdGluZ2x5LCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBtZWFuIE9SRVMgc2NvcmUgaW4gdGhlIHJlbWFpbmVkIG9mIFdpa2lkYXRhIGlzIGNsZWFybHkgKmJpbW9kYWwqLCB3aXRoIGEgbWVhbiBhdCBgciBtZWFuKHdpa2lkYXRhU2V0JG1lYW5RdWFsaXR5KWAuIExldCdzIGZpbmQgb3V0IHdoYXQgYXJlICp0aGUgbGFyZ2VzdCBXaWtpZGF0YSBjbGFzc2VzKiBmb3VuZCBpbiB0aGUgZm9sbG93aW5nIHJhbmdlcyBvZiBtZWFuIE9SRVMgc2NvcmVzOiAKCi0gMS41IC0gMi41Ci0gMi41IC0gMy41CgpGaXJzdCB0aGUgbG93ZXIgc2NvcmUgY2xhc3Nlcywgd2l0aCBhIHJhbmdlIG9mIG1lYW4gcXVhbGl0eSBvZiAxLjUgLSAyLjU6CgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9Cmxvd2VyU2NvcmVTZXQgPC0gd2lraWRhdGFTZXQgJT4lIAogIGRwbHlyOjpmaWx0ZXIobWVhblF1YWxpdHkgPj0gMS41ICYgbWVhblF1YWxpdHkgPCAyLjUpICU+JSAKICBhcnJhbmdlKGRlc2MobnVtSXRlbXMpKSAlPiUgCiAgdG9wX24oMTAwKQpoaWdoZXJTY29yZVNldCA8LSB3aWtpZGF0YVNldCAlPiUgCiAgZHBseXI6OmZpbHRlcihtZWFuUXVhbGl0eSA+PSAyLjUgJiBtZWFuUXVhbGl0eSA8IDMuNSkgICU+JSAKICBhcnJhbmdlKGRlc2MobnVtSXRlbXMpKSAlPiUgCiAgdG9wX24oMTAwKQpsb3dlclNjb3JlQ2xhc3NlcyA8LSB3bWRlZGF0YV9hcGlfZmV0Y2hfbGFiZWxzKGl0ZW1zID0gbG93ZXJTY29yZVNldCRjbGFzcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFuZ3VhZ2UgPSAiZW4iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmYWxsYmFjayA9IFQpCmxvd2VyU2NvcmVDbGFzc2VzIDwtIGRwbHlyOjpsZWZ0X2pvaW4obG93ZXJTY29yZUNsYXNzZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChsb3dlclNjb3JlU2V0LCBjbGFzcywgbnVtSXRlbXMsIG1lYW5RdWFsaXR5KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoIml0ZW0iID0gImNsYXNzIikpCmxvd2VyU2NvcmVDbGFzc2VzCmBgYAoKTm93IGZvciB0aGUgaGlnaGVyIHNjb3JlIGNsYXNzZXMsIHdpdGggYSByYW5nZSBvZiBtZWFuIHF1YWxpdHkgb2YgMi41IC0gMy41OgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQpoaWdoZXJTY29yZUNsYXNzZXMgPC0gd21kZWRhdGFfYXBpX2ZldGNoX2xhYmVscyhpdGVtcyA9IGhpZ2hlclNjb3JlU2V0JGNsYXNzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYW5ndWFnZSA9ICJlbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhbGxiYWNrID0gVCkKaGlnaGVyU2NvcmVDbGFzc2VzIDwtIGRwbHlyOjpsZWZ0X2pvaW4oaGlnaGVyU2NvcmVDbGFzc2VzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGhpZ2hlclNjb3JlU2V0LCBjbGFzcywgbnVtSXRlbXMsIG1lYW5RdWFsaXR5KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJpdGVtIiA9ICJjbGFzcyIpKQpoaWdoZXJTY29yZUNsYXNzZXMKYGBgCgpOZWl0aGVyIHRoZSBsb3dlciBub3cgdGhlIGhpZ2hlciBzY29yZSBjbGFzc2VzIGlzb2xhdGVkIGluIHRoZSBhbmFseXNpcyBhYm92ZSBkbyBub3Qgc2VlbSAqcHJpbWEgZmFjaWUqIHNlbWFudGljYWxseSBjb2hlcmVudC4KCgojIyMjIDIuNyBUaGUgZGlzdHJpYnV0aW9uIG9mIE9SRVMgc2NvcmVzIGluIHRoZSByZW1haW5pbmcgV2lraWRhdGEgY2xhc3NlczogPj0gMTAwMCBpdGVtcwoKTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBPUkVTIHNjb3JlcyBpbiB0aGUgcmVtYWluaW5nIFdpa2lkYXRhIGNsYXNzZXMgKipidXQgdGFraW5nIGludG8gb3VyIGNvbnNpZGVyYXRpb24gb25seSBjbGFzc2VzIHdpdGggMSwwMDAgb3IgbW9yZSBpdGVtcyoqOgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCB3YXJuaW5nID0gRn0Kd2lraWRhdGFTZXQgPC0gZGF0YVNldCAlPiUgCiAgZHBseXI6OmZpbHRlcighKGNsYXNzICVpbiUgYXN0cm9ub21pY2FsT2JqZWN0cyB8IGNsYXNzICVpbiUgc2NpZW50aWZpY1BhcGVycykpICU+JSAKICBkcGx5cjo6ZmlsdGVyKG51bUl0ZW1zID49IDEwMDApCmhlYWQod2lraWRhdGFTZXQpCmBgYAoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGLCAsIHdhcm5pbmcgPSBGfQp3aWtpZGF0YVNldCRtZWFuUXVhbGl0eSA8LSBhcHBseSh3aWtpZGF0YVNldFssIGMoJ0EnLCAnQicsICdDJywgJ0QnLCAnRScpXSwgMSwgZnVuY3Rpb24oeCkgewogIHJldHVybihzdW0oKHggKiA1OjEpKS9zdW0oeCkpCn0pCmdncGxvdChkYXRhID0gd2lraWRhdGFTZXQsIAogICAgICAgYWVzKHggPSBtZWFuUXVhbGl0eSkpICsgCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gLjI1LCBmaWxsID0gImRhcmtvcmFuZ2UiLCBjb2xvciA9ICJkYXJrb3JhbmdlIiwgc2l6ZSA9IC4yNSkgKyAKICBnZ3RpdGxlKCJXaWtpZGF0YSAtIChBc3Ryb25vbWljYWwgT2JqZWN0ICsgU2Nob2xhcmx5IEFydGljbGUpXG5Pbmx5IGNsYXNzZXMgdy4gPj0gMTAwMCBpdGVtcyIpICsgCiAgdGhlbWVfYncoKSArIAogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB4bGltKDAsIDUpICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgaGp1c3QgPSAuNSkpCmBgYApgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CnN1bW1hcnkod2lraWRhdGFTZXQkbWVhblF1YWxpdHkpCmBgYApJbnRlcmVzdGluZ2x5LCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBtZWFuIE9SRVMgc2NvcmUgaW4gdGhlIHJlbWFpbmVkIG9mIFdpa2lkYXRhIGlzIGNsZWFybHkgKmJpbW9kYWwqLCB3aXRoIGEgbWVhbiBhdCBgciBtZWFuKHdpa2lkYXRhU2V0JG1lYW5RdWFsaXR5KWAuIExldCdzIGZpbmQgb3V0IHdoYXQgYXJlICp0aGUgbGFyZ2VzdCBXaWtpZGF0YSBjbGFzc2VzKiBmb3VuZCBpbiB0aGUgZm9sbG93aW5nIHJhbmdlcyBvZiBtZWFuIE9SRVMgc2NvcmVzOiAKCi0gMS41IC0gMi41Ci0gMi41IC0gMy41CgpGaXJzdCB0aGUgbG93ZXIgc2NvcmUgY2xhc3Nlcywgd2l0aCBhIHJhbmdlIG9mIG1lYW4gcXVhbGl0eSBvZiAxLjUgLSAyLjU6CgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9Cmxvd2VyU2NvcmVTZXQgPC0gd2lraWRhdGFTZXQgJT4lIAogIGRwbHlyOjpmaWx0ZXIobWVhblF1YWxpdHkgPj0gMS41ICYgbWVhblF1YWxpdHkgPCAyLjUpICU+JSAKICBhcnJhbmdlKGRlc2MobnVtSXRlbXMpKSAlPiUgCiAgdG9wX24oMTAwKQpoaWdoZXJTY29yZVNldCA8LSB3aWtpZGF0YVNldCAlPiUgCiAgZHBseXI6OmZpbHRlcihtZWFuUXVhbGl0eSA+PSAyLjUgJiBtZWFuUXVhbGl0eSA8IDMuNSkgICU+JSAKICBhcnJhbmdlKGRlc2MobnVtSXRlbXMpKSAlPiUgCiAgdG9wX24oMTAwKQpsb3dlclNjb3JlQ2xhc3NlcyA8LSB3bWRlZGF0YV9hcGlfZmV0Y2hfbGFiZWxzKGl0ZW1zID0gbG93ZXJTY29yZVNldCRjbGFzcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFuZ3VhZ2UgPSAiZW4iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmYWxsYmFjayA9IFQpCmxvd2VyU2NvcmVDbGFzc2VzIDwtIGRwbHlyOjpsZWZ0X2pvaW4obG93ZXJTY29yZUNsYXNzZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHBseXI6OnNlbGVjdChsb3dlclNjb3JlU2V0LCBjbGFzcywgbnVtSXRlbXMsIG1lYW5RdWFsaXR5KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoIml0ZW0iID0gImNsYXNzIikpCmxvd2VyU2NvcmVDbGFzc2VzCmBgYAoKTm93IGZvciB0aGUgaGlnaGVyIHNjb3JlIGNsYXNzZXMsIHdpdGggYSByYW5nZSBvZiBtZWFuIHF1YWxpdHkgb2YgMi41IC0gMy41OgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQpoaWdoZXJTY29yZUNsYXNzZXMgPC0gd21kZWRhdGFfYXBpX2ZldGNoX2xhYmVscyhpdGVtcyA9IGhpZ2hlclNjb3JlU2V0JGNsYXNzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYW5ndWFnZSA9ICJlbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhbGxiYWNrID0gVCkKaGlnaGVyU2NvcmVDbGFzc2VzIDwtIGRwbHlyOjpsZWZ0X2pvaW4oaGlnaGVyU2NvcmVDbGFzc2VzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGhpZ2hlclNjb3JlU2V0LCBjbGFzcywgbnVtSXRlbXMsIG1lYW5RdWFsaXR5KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJpdGVtIiA9ICJjbGFzcyIpKQpoaWdoZXJTY29yZUNsYXNzZXMKYGBgCgojIyMjIDIuOCBUaGUgZGlzdHJpYnV0aW9uIG9mIE9SRVMgc2NvcmVzIGluIHRoZSByZW1haW5pbmcgV2lraWRhdGEgY2xhc3NlczogPCAxMDAwIGl0ZW1zCgpMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIE9SRVMgc2NvcmVzIGluIHRoZSByZW1haW5pbmcgV2lraWRhdGEgY2xhc3NlcyAqKmJ1dCB0YWtpbmcgaW50byBvdXIgY29uc2lkZXJhdGlvbiBvbmx5IGNsYXNzZXMgd2l0aCBsZXNzIHRoYW4gMSwwMDAgaXRlbXMqKjoKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0Kd2lraWRhdGFTZXQgPC0gZGF0YVNldCAlPiUgCiAgZHBseXI6OmZpbHRlcighKGNsYXNzICVpbiUgYXN0cm9ub21pY2FsT2JqZWN0cyB8IGNsYXNzICVpbiUgc2NpZW50aWZpY1BhcGVycykpICU+JSAKICBkcGx5cjo6ZmlsdGVyKG51bUl0ZW1zIDwgMTAwMCkKaGVhZCh3aWtpZGF0YVNldCkKYGBgCgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9Cndpa2lkYXRhU2V0JG1lYW5RdWFsaXR5IDwtIGFwcGx5KHdpa2lkYXRhU2V0WywgYygnQScsICdCJywgJ0MnLCAnRCcsICdFJyldLCAxLCBmdW5jdGlvbih4KSB7CiAgcmV0dXJuKHN1bSgoeCAqIDU6MSkpL3N1bSh4KSkKfSkKZ2dwbG90KGRhdGEgPSB3aWtpZGF0YVNldCwgCiAgICAgICBhZXMoeCA9IG1lYW5RdWFsaXR5KSkgKyAKICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAuMjUsIGZpbGwgPSAiZGFya29yYW5nZSIsIGNvbG9yID0gImRhcmtvcmFuZ2UiLCBzaXplID0gLjI1KSArIAogIGdndGl0bGUoIldpa2lkYXRhIC0gKEFzdHJvbm9taWNhbCBPYmplY3QgKyBTY2hvbGFybHkgQXJ0aWNsZSlcbk9ubHkgY2xhc3NlcyB3LiA+PSAxMDAwIGl0ZW1zIikgKyAKICB0aGVtZV9idygpICsgCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArIAogIHhsaW0oMCwgNSkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBoanVzdCA9IC41KSkKYGBgCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0Kc3VtbWFyeSh3aWtpZGF0YVNldCRtZWFuUXVhbGl0eSkKYGBgCkludGVyZXN0aW5nbHksIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIG1lYW4gT1JFUyBzY29yZSBpbiB0aGUgcmVtYWluZWQgb2YgV2lraWRhdGEgaXMgY2xlYXJseSAqYmltb2RhbCosIHdpdGggYSBtZWFuIGF0IGByIG1lYW4od2lraWRhdGFTZXQkbWVhblF1YWxpdHkpYC4gTGV0J3MgZmluZCBvdXQgd2hhdCBhcmUgKnRoZSBsYXJnZXN0IFdpa2lkYXRhIGNsYXNzZXMqIGZvdW5kIGluIHRoZSBmb2xsb3dpbmcgcmFuZ2VzIG9mIG1lYW4gT1JFUyBzY29yZXM6IAoKLSAxLjUgLSAyLjUKLSAyLjUgLSAzLjUKCkZpcnN0IHRoZSBsb3dlciBzY29yZSBjbGFzc2VzLCB3aXRoIGEgcmFuZ2Ugb2YgbWVhbiBxdWFsaXR5IG9mIDEuNSAtIDIuNToKCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0KbG93ZXJTY29yZVNldCA8LSB3aWtpZGF0YVNldCAlPiUgCiAgZHBseXI6OmZpbHRlcihtZWFuUXVhbGl0eSA+PSAxLjUgJiBtZWFuUXVhbGl0eSA8IDIuNSkgJT4lIAogIGFycmFuZ2UoZGVzYyhudW1JdGVtcykpICU+JSAKICB0b3BfbigxMDApCmhpZ2hlclNjb3JlU2V0IDwtIHdpa2lkYXRhU2V0ICU+JSAKICBkcGx5cjo6ZmlsdGVyKG1lYW5RdWFsaXR5ID49IDIuNSAmIG1lYW5RdWFsaXR5IDwgMy41KSAgJT4lIAogIGFycmFuZ2UoZGVzYyhudW1JdGVtcykpICU+JSAKICB0b3BfbigxMDApCmxvd2VyU2NvcmVDbGFzc2VzIDwtIHdtZGVkYXRhX2FwaV9mZXRjaF9sYWJlbHMoaXRlbXMgPSBsb3dlclNjb3JlU2V0JGNsYXNzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYW5ndWFnZSA9ICJlbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhbGxiYWNrID0gVCkKbG93ZXJTY29yZUNsYXNzZXMgPC0gZHBseXI6OmxlZnRfam9pbihsb3dlclNjb3JlQ2xhc3NlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGxvd2VyU2NvcmVTZXQsIGNsYXNzLCBudW1JdGVtcywgbWVhblF1YWxpdHkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygiaXRlbSIgPSAiY2xhc3MiKSkKbG93ZXJTY29yZUNsYXNzZXMKYGBgCgpOb3cgZm9yIHRoZSBoaWdoZXIgc2NvcmUgY2xhc3Nlcywgd2l0aCBhIHJhbmdlIG9mIG1lYW4gcXVhbGl0eSBvZiAyLjUgLSAzLjU6CgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CmhpZ2hlclNjb3JlQ2xhc3NlcyA8LSB3bWRlZGF0YV9hcGlfZmV0Y2hfbGFiZWxzKGl0ZW1zID0gaGlnaGVyU2NvcmVTZXQkY2xhc3MsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhbmd1YWdlID0gImVuIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFsbGJhY2sgPSBUKQpoaWdoZXJTY29yZUNsYXNzZXMgPC0gZHBseXI6OmxlZnRfam9pbihoaWdoZXJTY29yZUNsYXNzZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QoaGlnaGVyU2NvcmVTZXQsIGNsYXNzLCBudW1JdGVtcywgbWVhblF1YWxpdHkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoIml0ZW0iID0gImNsYXNzIikpCmhpZ2hlclNjb3JlQ2xhc3NlcwpgYGAKCgojIyMgMy4gQ2x1c3RlcmluZyB0aGUgT1JFUyBzY29yZXMgYWNyb3NzIHRoZSBXaWtpZGF0YSBjbGFzc2VzCgpXZSB3aWxsIG1ha2UgYW4gYXR0ZW1wdCBhdCBhIGJldHRlciB1bmRlcnN0YW5kaW5nIG9mIHRoZSBkaXN0cmlidXRpb24gb2YgT1JFUyBzY29yZXMgY29udGVudC13aWRlIFdpa2lkYXRhIGJ5IGNsdXN0ZXJpbmcgdGhlIGRhdGFzZXQgZm9sbG93aW5nIHRoZSByZW1vdmFsIG9mICoqQXN0cm9ub21pY2FsIE9iamVjdCoqIChgUTY5OTlgKSBhbmQgKipTY2hvbGFybHkgQXJ0aWNsZSoqIChgUTEzNDQyODE0YCkgKGFuZCB0aGVpciBzdWJjbGFzc2VzIGFzIHdlbGwpLgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQpjbHVzdFNldCA8LSB3aWtpZGF0YVNldCAlPiUgCiAgZHBseXI6OnNlbGVjdChBLCBCLCBDLCBELCBFKQpgYGAKCgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gQ2x1c3RlciB3aXRoIHttY2x1c3R9CnNub3dmYWxsOjpzZkluaXQocGFyYWxsZWwgPSBULCBjcHVzID0gMjMpCnNub3dmYWxsOjpzZkV4cG9ydCgnY2x1c3RTZXQnKQpzbm93ZmFsbDo6c2ZMaWJyYXJ5KG1jbHVzdCkKdDEgPC0gU3lzLnRpbWUoKQptb2RlbHMgPC0gc25vd2ZhbGw6OnNmQ2x1c3RlckFwcGx5TEIoMTozMCwgZnVuY3Rpb24oeCkgewogIG1jbHVzdC5vcHRpb25zKGhjVXNlID0gIlNURCIpCiAgcmV0dXJuKAogICAgTWNsdXN0KGNsdXN0U2V0LCAKICAgICAgICAgICBHID0gMjozMCwgCiAgICAgICAgICAgaW5pdGlhbGl6YXRpb24gPSBsaXN0KCkpCiAgKQp9KQp0MiA8LSBTeXMudGltZSgpCnByaW50KAogIHBhc3RlMCgibWNsdXN0IHRvb2s6ICIsIAogICAgICAgICBhcy5jaGFyYWN0ZXIoZGlmZnRpbWUodDIsIHQxLCB1bml0cyA9ICJtaW5zIikpCiAgKQopCnNub3dmYWxsOjpzZlN0b3AoKQoKIyMjIC0tLSBQb3N0LWhvYyBtb2RlbCBzZWxlY3Rpb24KbWF4QklDIDwtIHNhcHBseShtb2RlbHMsIGZ1bmN0aW9uKHgpIHsKICBtYXgoYXBwbHkoeCRCSUMsIDIsIG1heCwgbmEucm0gPSBUKSkKfSkKc2VsZWN0ZWRNb2RlbCA8LSB3aGljaC5tYXgobWF4QklDKQp3ZF9jbGFzc2VzX29wdGltYWwgPC0gbW9kZWxzW1tzZWxlY3RlZE1vZGVsXV0Kcm0obW9kZWxzKQoKIyMjIC0tLSBJbnNwZWN0IE1vZGVsCmxlZ2VuZF9hcmdzIDwtIGxpc3QoeCA9ICJib3R0b21yaWdodCIsIAogICAgICAgICAgICAgICAgICAgIG5jb2wgPSA1KQpwbG90KHdkX2NsYXNzZXNfb3B0aW1hbCwgCiAgICAgd2hhdCA9ICdCSUMnLCAKICAgICBsZWdlbmRBcmdzID0gbGVnZW5kX2FyZ3MpCmBgYAoKV2UgaGF2ZSBhY3R1YWxseSBydW4gbWFueSBjbHVzdGVyIG1vZGVscyBhbmQgcGlja2VkIHVwIHRoZSBtb3N0IG9wdGltYWwgdGhhdCB3ZSBoYXZlIGZvdW5kOgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQp3ZF9jbGFzc2VzX29wdGltYWwkbW9kZWxOYW1lCndkX2NsYXNzZXNfb3B0aW1hbCRHCndkX2NsYXNzZXNfb3B0aW1hbCRuCndkX2NsYXNzZXNfb3B0aW1hbCRkCmBgYApUaGUgbW9zdCBvcHRpbWFsIGNsdXN0ZXJpbmcgc29sdXRpb24gdGhhdCB3ZSB3ZXJlIGFibGUgdG8gcHJvZHVjZSBlbmNvbXBhc3NlcyAxMiBjbHVzdGVycy4KCiMjIyMgMy4xIENsdXN0ZXJzOiBudW1iZXIgb2YgV2lraWRhdGEgY2xhc3NlcyBwZXIgY2x1c3RlciwgbWVhbiBPUkVTIHF1YWxpdHkgcGVyIENsdXN0ZXIsIGFuZCB0aGUgYXZlcmFnZSBudW1iZXIgb2YgaXRlbXMgaW4gYSBjbGFzcyBwZXIgY2x1c3RlcgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQojIyMgLS0tIEpvaW4gY2x1c3RlcnMgdG8gZGF0YSBwb2ludHMgKFdEIGNsYXNzZXMpCmNsdXN0ZXJfYXNzaWduIDwtIGFzLmRhdGEuZnJhbWUod2RfY2xhc3Nlc19vcHRpbWFsJGNsYXNzaWZpY2F0aW9uKQpjb2xuYW1lcyhjbHVzdGVyX2Fzc2lnbikgPC0gImNsdXN0ZXIiCmNsdXN0ZXJfYXNzaWduJGNsYXNzIDwtIHJvd25hbWVzKGNsdXN0ZXJfYXNzaWduKQpjbHVzdFNldCRjbGFzcyA8LSByb3duYW1lcyhjbHVzdFNldCkKY2x1c3RTZXQgPC0gZHBseXI6OmxlZnRfam9pbihjbHVzdFNldCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX2Fzc2lnbiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJjbGFzcyIpCnJtKGNsdXN0ZXJfYXNzaWduKQoKIyMjIC0tLSBJbnNwZWN0IGNsdXN0ZXJzCmNsdXN0U2V0JGNsYXNzU2l6ZSA8LSByb3dTdW1zKGNsdXN0U2V0WywgYygnQScsICdCJywgJ0MnLCAnRCcsICdFJyldKQpjbHVzdFNldCRtZWFuUXVhbGl0eSA8LSBhcHBseShjbHVzdFNldFssIGMoJ0EnLCAnQicsICdDJywgJ0QnLCAnRScpXSwgMSwgZnVuY3Rpb24oeCkgewogIHJldHVybihzdW0oKHggKiA1OjEpKS9zdW0oeCkpCn0pCgoKIyAtIE9SRVMgY2x1c3RlciBxdWFsaXR5IHByb2ZpbGVzCmNsYXNzUHJvZmlsZXMgPC0gY2x1c3RTZXQgJT4lIAogIGRwbHlyOjpzZWxlY3QoY2x1c3RlciwgbWVhblF1YWxpdHksIGNsYXNzU2l6ZSwgQSwgQiwgQywgRCwgRSkgJT4lIAogIGRwbHlyOjpncm91cF9ieShjbHVzdGVyKSAlPiUgCiAgZHBseXI6OnN1bW1hcmlzZShtZWFuQ2x1c3RlclF1YWxpdHkgPSBtZWFuKG1lYW5RdWFsaXR5KSwKICAgICAgICAgICAgICAgICAgIG1lYW5DbHVzdGVyQ2xhc3NTaXplID0gbWVhbihjbGFzc1NpemUpLCAKICAgICAgICAgICAgICAgICAgIG1lZGlhbkNsdXN0ZXJDbGFzc1NpemUgPSBtZWRpYW4oY2xhc3NTaXplKSwgCiAgICAgICAgICAgICAgICAgICBudW1DbGFzc2VzID0gbigpLAogICAgICAgICAgICAgICAgICAgQSA9IHN1bShBKSwgCiAgICAgICAgICAgICAgICAgICBCID0gc3VtKEIpLCAKICAgICAgICAgICAgICAgICAgIEMgPSBzdW0oQyksIAogICAgICAgICAgICAgICAgICAgRCA9IHN1bShEKSwgCiAgICAgICAgICAgICAgICAgICBFID0gc3VtKEUpKSAlPiUgCiAgYXJyYW5nZShkZXNjKG1lYW5DbHVzdGVyQ2xhc3NTaXplKSkKY2xhc3NQcm9maWxlcyR0b3RhbEl0ZW1zIDwtIGNsYXNzUHJvZmlsZXMkQSArIAogIGNsYXNzUHJvZmlsZXMkQiArIAogIGNsYXNzUHJvZmlsZXMkQyArIAogIGNsYXNzUHJvZmlsZXMkRCArIAogIGNsYXNzUHJvZmlsZXMkRQoKIyAtIEFuYWx5dGljcwojIC0gUGxvdCBBLiBNZWFuIHBlciBDbHVzdGVyIENsYXNzIFNpemUgdnMgTWVhbiBwZXIgQ2x1c3RlciBDbGFzcyBRdWFsaXR5CmdncGxvdChkYXRhID0gY2xhc3NQcm9maWxlcywgCiAgICAgICBhZXMoeCA9IG1lYW5DbHVzdGVyQ2xhc3NTaXplLCAKICAgICAgICAgICB5ID0gbWVhbkNsdXN0ZXJRdWFsaXR5LCAKICAgICAgICAgICBzaXplID0gbnVtQ2xhc3NlcywgCiAgICAgICAgICAgbGFiZWwgPSBjbHVzdGVyKSkgKyAKICBnZW9tX3BvaW50KGNvbG9yID0gInJlZCIsIGZpbGwgPSAidG9tYXRvIikgKwogIGdlb21fdGV4dF9yZXBlbChzaXplID0gNCkgKwogIGdndGl0bGUoIk1lYW4gcGVyIENsdXN0ZXIgV0QgQ2xhc3MgU2l6ZSB2cyBNZWFuIHBlciBDbHVzdGVyIENsYXNzIFF1YWxpdHkiKSArIAogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hKSArIAogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmNvbW1hKSArCiAgdGhlbWVfYncoKSArIAogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgKyAKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCwgaGp1c3QgPSAuNSkpCmBgYApJbiB0aGUgY2hhcnQgYWJvdmUgZWFjaCBidWJibGUgcmVwcmVzZW50cyBhIGNsdXN0ZXIgb2YgV2lraWRhdGEgY2xhc3Nlcy4gVGhlIGhvcml6b250ZWwgYXhpcywgbGFiZWxlZCBgbWVhbkNsdXN0ZXJDbGFzc1NpemVgLCByZXByZXNlbnRzIHRoZSBtZWFuIHNpemUgb2YgYSBXaWtpZGF0YSBjbGFzcyBpbiBzb21lIHBhcnRpY3VsYXIgY2x1c3RlcjogdGhlIHNpemUgb2YgdGhlIGNsYXNzIGlzIHRoZSBudW1iZXIgb2YgaXRlbXMgaW4gaXQsIGFuZCBhcyBlYWNoIGNsdXN0ZXIgZW5jb21wYXNzZXMgYSBudW1iZXIgb2YgY2xhc3NlcyB3ZSBjb21wdXRlIHRoZSBtZWFuIHNpemUgb2YgYSBjbGFzcyBpbiBhIGNsdXN0ZXIuIFRoZSB2ZXJ0aWNhbCBheGlzIHJlcHJlc2VudHMgdGhlIG1lYW4gT1JFUyBxdWFsaXR5OiBpdCBpcyB0aGUgYXZlcmFnZSBvZiBhbGwgV2lraWRhdGEgY2xhc3NlcycgbWVhbiBPUkVTIHNjb3JlcywgZm9yIGFsbCBjbGFzc2VzIGZvdW5kIGluIGEgcGFydGljdWxhciBjbHVzdGVyLiBUaGUgc2l6ZSBvZiB0aGUgYnViYmxlIHJlcHJlc2VudHMgdGhlIG51bWJlciBvZiBjbGFzc2VzIGZvdW5kIGluIGVhY2ggY2x1c3Rlci4gVGhlcmUgaXMgb25lIGNsdXN0ZXIgZW5jb21wYXNzaW5nIGEgcmVsYXRpdmVseSBsb3cgbnVtYmVyIG9mIFdpa2lkYXRhIGNsYXNzZXMsIGxhYmVsZWQgYXMgQ2x1c3RlciA2LCBvbiB0aGUgcmlnaHQgb2YgdGhlIHBsb3QsIGJ1dCB0aGF0IHZlcnkgY2x1c3RlciBhbHNvIGVuY29tcGFzc2VzIHZlcnkgbGFyZ2UgV2lraWRhdGEgY2xhc3NlcyAoYW5kIHRoYXQgaXMgd2h5IGl0IGlzIHBvc2l0aW9uZWQgb24gdGhlIGZhciByaWdodCBvZiB0aGUgaG9yaXpvbnRhbCBheGlzKS4gQWxsIG90aGVyIGNsdXN0ZXJzIGVuY29tcGFzcyBXaWtpZGF0YSBjbGFzc2VzIHdpdGggYSByZWxhdGl2ZWx5IGxvdyBudW1iZXIgb2YgaXRlbXMgaW4gdGhlbSAoYW5kIHRoYXQgaXMgd2h5IHRoZXkgYXJlIHBvc2l0aW9uIG9uIHRoZSBmYXIgbGVmdCBvZiB0aGUgaG9yaXpvbnRhbCBheGlzKS4gSW4gdGhlIGxvd2VyIGxlZnQgY29ybmVyIG9mIHRoZSBjaGFydCB3ZSBmaW5kIENsdXN0ZXIgMiwgYSBsYXJnZSBidWJibGUgLSB3aGljaCBtZWFucyB0aGF0IGl0IGVuY29tcGFzc2VzIGEgbGFyZ2UgbnVtYmVyIG9mIGNsYXNzZXMgLSB3aXRoIGEgcmVsYXRpdmVseSBzbWFsbCBhdmVyYWdlIG51bWJlciBvZiBpdGVtcyBwZXIgY2xhc3MsIGFuZCByZWxhdGl2ZWx5IGxvdyBtZWFuIE9SRVMgc2NvcmVzIHBlciBjbGFzcy4KCiMjIyMgMy4yIENsdXN0ZXJzOiB0aGUgZGlzdHJpYnV0aW9uIG9mIE9SRVMgc2NvcmVzIHBlciBjbHVzdGVyCgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CiMgLSBQbG90IEIuIEF2ZXJhZ2UgbnVtYmVyIG9mIEEsIEIsIEMsIEQsIGFuZCBFIHJhbmtlZCBpdGVtcyBwZXIgY2xhc3MsIHBlciBDbHVzdGVyCnBsb3RCRnJhbWUgPC0gY2xhc3NQcm9maWxlcyAlPiUgCiAgZHBseXI6OnNlbGVjdChjbHVzdGVyLCBBLCBCLCBDLCBELCBFKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKC1jbHVzdGVyLCAKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAiT1JFUyBSYW5rIiwgCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJOdW0gSXRlbXMgcGVyIE9SRVMgUmFuayIKICApIApwbG90QkZyYW1lJGNsdXN0ZXIgPC0gZmFjdG9yKHBsb3RCRnJhbWUkY2x1c3RlcikKZ2dwbG90KGRhdGEgPSBwbG90QkZyYW1lLCAKICAgICAgIGFlcyh4ID0gYE9SRVMgUmFua2AsIAogICAgICAgICAgIHkgPSBgTnVtIEl0ZW1zIHBlciBPUkVTIFJhbmtgLAogICAgICAgICAgIGdyb3VwID0gY2x1c3RlciwKICAgICAgICAgICBjb2xvciA9IGNsdXN0ZXIsCiAgICAgICAgICAgZmlsbCA9IGNsdXN0ZXIpCikgKyAKICBnZW9tX3BvaW50KCkgKyBnZW9tX3BhdGgoKSArIAogIGZhY2V0X3dyYXAofmNsdXN0ZXIsIHNjYWxlcyA9ICJmcmVlIikgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpjb21tYSkgKwogIHlsYWIoIk1lYW4oTnVtLml0ZW1zKVxuCiAgICAgICBwZXIgT1JFUyBSYW5rIHBlciBXRCBDbGFzcyIpICsKICBnZ3RpdGxlKCJBdmVyYWdlIG51bWJlciBvZiBBLCBCLCBDLCBELCBhbmQgRSByYW5rZWQgaXRlbXMgcGVyIFdEIENsYXNzLCBwZXIgQ2x1c3RlciIpICsKICB0aGVtZV9idygpICsgCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyAKICB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwLCBoanVzdCA9IC41KSkgCmBgYApUaGUgaG9yaXpvbnRhbCBheGlzIGluIGFsbCBjaGFydHMgcmVwcmVzZW50cyB0aGUgT1JFUyBzY29yZSwgd2hpbGUgdGhlIHZlcnRpY2FsIGF4aXMgcmVwcmVzZW50cyB0aGUgbWVhbiBudW1iZXIgb2YgaXRlbXMgcGVyIGNsYXNzIGluIGEgY2x1c3RlciB0aGF0IGJlbG9uZyB0byBhIHBhcnRpY3VsYXIgT1JFUyBzY29yZSBjYXRlZ29yeSAoQSwgQiwgQywgRCwgb3IgRSk7IGVhY2ggY2hhcnQgcmVwcmVzZW50cyBhIGNsdXN0ZXIuCgpJZiB3ZSBzdXBlcmltcG9zZSB0aGUgcXVhbGl0eSBzY29yZSBwcm9maWxlczogCgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CiMgLSBzdXBlcmltcG9zZWQ6IApnZ3Bsb3QoZGF0YSA9IHBsb3RCRnJhbWUsIAogICAgICAgYWVzKHggPSBgT1JFUyBSYW5rYCwgCiAgICAgICAgICAgeSA9IGxvZyhgTnVtIEl0ZW1zIHBlciBPUkVTIFJhbmtgKSwKICAgICAgICAgICBncm91cCA9IGNsdXN0ZXIsCiAgICAgICAgICAgY29sb3IgPSBjbHVzdGVyLAogICAgICAgICAgIGZpbGwgPSBjbHVzdGVyKQopICsgCiAgZ2VvbV9wb2ludCgpICsgZ2VvbV9wYXRoKCkgKyAKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpjb21tYSkgKwogIHlsYWIoIk1lYW4oTnVtLml0ZW1zKVxuCiAgICAgICBwZXIgT1JFUyBSYW5rIHBlciBXRCBDbGFzcyIpICsKICBnZ3RpdGxlKCJBdmVyYWdlIG51bWJlciBvZiBBLCBCLCBDLCBELCBhbmQgRSByYW5rZWQgaXRlbXMgcGVyIFdEIENsYXNzLCBwZXIgQ2x1c3RlciIpICsKICB0aGVtZV9idygpICsgCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKSArIAogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyKSkKYGBgCiMjIyMgMy4zIENsdXN0ZXJzOiBEZXNjcmliaW5nIHRoZWlyIGNvbnRlbnQKCldlIHdpbGwgcXVlcnkgdGhlIFdpa2lkYXRhIEFQSSB0byBvYnRhaW4gdGhlIEVuZ2xpc2ggbGFiZWxzIGZvciB0b3AgMjAgbW9zdCBwb3B1bGF0ZWQgV2lraWRhdGEgY2xhc3NlcyBpbiBlYWNoIGNsdXN0ZXIuCgpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEZ9CiMjIyAtLS0gRGVzY3JpYmUgY2x1c3RlcnMKZGVzY3JpYmVDbHVzdGVycyA8LSBsYXBwbHkoc29ydCh1bmlxdWUoY2x1c3RTZXQkY2x1c3RlcikpLCBmdW5jdGlvbih4KSB7CiAgZCA8LSBjbHVzdFNldFtjbHVzdFNldCRjbHVzdGVyID09IHgsIF0KICBkIDwtIGQgJT4lIAogICAgZHBseXI6OmFycmFuZ2UoZGVzYyhjbGFzc1NpemUpKQogIHJldHVybihkJGNsYXNzWzE6MjBdKQp9KQojIC0gY2FsbCB3bWRlZGF0YV9hcGlfZmV0Y2hfbGFiZWxzKCkKZGVzY3JpYmVDbHVzdGVycyA8LSBsYXBwbHkoZGVzY3JpYmVDbHVzdGVycywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHdtZGVkYXRhX2FwaV9mZXRjaF9sYWJlbHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBsYW5ndWFnZSA9ICJlbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICBmYWxsYmFjayA9IFQpCmBgYAoKSG93ZXZlciwganVzdCBmcm9tIGxvb2tpbmcgYXQgV2lraWRhdGEgY2xhc3MgbGFiZWxzLCBpdCBkb2VzIG5vdCBzZWVtIHBvc3NpYmxlIHRvIHByb3ZpZGUgYSBjb2hlcmVudCBpbnRlcnByZXRhdGlvbiBvZiB0aGUgY2x1c3RlcnMuCkl0IGlzIGludGVyZXN0aW5nLCBob3dldmVyLCB0byB0YWtlIGEgbG9vayBhdCB0aGUgY29tcG9zaXRpb24gb2YgQ2x1c3RlciA2OgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQpkZXNjcmliZUNsdXN0ZXJzW1s2XV0KYGBgCgpUaGlzIGNsdXN0ZXIgKDYpIGVuY29tcGFzc2VzIGxhcmdlIFdpa2lkYXRhIGNsYXNzZXMuIEl0IGlzIHF1aXRlIHBvc3NpYmxlIHRoYXQgc29tZSBvZiB0aGVtIGNvdWxkIGJlIGV4Y2x1ZGVkIGZyb20gZnVydGhlciBhbmFseXNlcyAoZS5nLiBXaWtpbWVkaWEgY2F0ZWdvcnksIFdpa2ltZWRpYSB0ZW1wbGF0ZSwgV2lraW1lZGlhIGRpc2FtYmlndWF0aW9uIHBhZ2UsIGV0YykuIEFsc28sIHNvbWUgb2YgdGhlbSBtaWdodCBuZWVkIGEgc2VwYXJhdGUgdHJlYXRtZW50IHNpbWlsYXIgdG8gQXN0cm9ub21pY2FsIE9iamVjdHMgYW5kIFNjaG9sYXJseSBBcnRpY2xlcyAoZS5nLiBIdW1hbiwgdGF4b24sIGdlbmUsIHByb3RlaW4sIGV0YykuCgpJdCBpcyBwb3NzaWJsZSB0aGF0IGEgbW9yZSBjb21wcmVoZW5zaXZlIGRlc2NyaXB0aW9uIG9mIHRoZSBjbHVzdGVyaW5nIHNvbHV0aW9uIGNvdWxkIGJlIG9idGFpbmVkIGZyb20gcmUtbGFiZWxpbmcgdGhlIHByZXNlbnQgV2lraWRhdGEgY2xhc3NlcyBieSB0aGVpciBzdXBlci1jbGFzc2VzIGluIHRoZSBXaWtpZGF0YSBoaWVyYXJjaHkgdmlhIGBQMzEvUDI3OWAsIGFuZCBieSBsb29raW5nIGF0IHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHN1cGVyLWNsYXNzZXMgYWNyb3NzIHRoZSBjbHVzdGVycy4gSG93ZXZlciwgaXQgd291bGQgdGFrZSBhIGxvdCBvZiBjb21wdXRhdGlvbmFsIHJlc291cmNlcyB0byBiZSBhYmxlIHRvIGRlc2NyaWJlIHRoZSBzb2x1dGlvbiBpbiB0aGF0IHdheS4KCiMjIyA0LiBDbGFzc2VzIG9mIGhpZ2hlc3QgYW5kIGxvd2VzdCBtZWFuIE9SRVMgcXVhbGl0eQoKT2YgY291cnNlLCB3ZSB3aWxsIHRha2UgYSBsb29rIG9ubHkgYXQgdGhlIFdpa2lkYXRhIGNsYXNzZXMgd2l0aCBhYm92ZSBhdmVyYWdlIG51bWJlciBvZiBpdGVtcy4KCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0KZGF0YVNldCA8LSByYXdEYXRhCmRhdGFTZXQkY2xhc3NTaXplIDwtIHJvd1N1bXMoZGF0YVNldCkKZGF0YVNldCRtZWFuUXVhbGl0eSA8LSBhcHBseShkYXRhU2V0WywgYygnQScsICdCJywgJ0MnLCAnRCcsICdFJyldLCAxLCBmdW5jdGlvbih4KSB7CiAgcmV0dXJuKHN1bSgoeCAqIDU6MSkpL3N1bSh4KSkKfSkKZGF0YVNldCRDbGFzcyA8LSByb3duYW1lcyhkYXRhU2V0KQpkYXRhU2V0IDwtIGRhdGFTZXQgJT4lIAogIGRwbHlyOjpmaWx0ZXIoY2xhc3NTaXplID4gbWVhbihkYXRhU2V0JGNsYXNzU2l6ZSkpCmRhdGFTZXQgPC0gZGF0YVNldCAlPiUgCiAgZHBseXI6OmFycmFuZ2UoZGVzYyhtZWFuUXVhbGl0eSkpCmBgYAoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQp0b3BRdWFsaXR5U2V0IDwtIGhlYWQoZGF0YVNldCwgMTAwKQp0b3BRdWFsaXR5U2V0TGFiZWxzIDwtIHdtZGVkYXRhX2FwaV9mZXRjaF9sYWJlbHModG9wUXVhbGl0eVNldCRDbGFzcywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYW5ndWFnZSA9ICJlbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFsbGJhY2sgPSBUKQp0b3BRdWFsaXR5U2V0TGFiZWxzIDwtIGRwbHlyOjpsZWZ0X2pvaW4odG9wUXVhbGl0eVNldExhYmVscywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRwbHlyOjpzZWxlY3QodG9wUXVhbGl0eVNldCwgQ2xhc3MsIGNsYXNzU2l6ZSwgbWVhblF1YWxpdHkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJpdGVtIiA9ICJDbGFzcyIpKQoKbG93UXVhbGl0eVNldCA8LSB0YWlsKGRhdGFTZXQsIDEwMCkKbG93UXVhbGl0eVNldExhYmVscyA8LSB3bWRlZGF0YV9hcGlfZmV0Y2hfbGFiZWxzKGxvd1F1YWxpdHlTZXQkQ2xhc3MsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFuZ3VhZ2UgPSAiZW4iLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhbGxiYWNrID0gVCkKbG93UXVhbGl0eVNldExhYmVscyA8LSBkcGx5cjo6bGVmdF9qb2luKGxvd1F1YWxpdHlTZXRMYWJlbHMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6c2VsZWN0KGxvd1F1YWxpdHlTZXQsIENsYXNzLCBjbGFzc1NpemUsIG1lYW5RdWFsaXR5KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gYygiaXRlbSIgPSAiQ2xhc3MiKSkKYGBgCgpUaGUgMTAwIHRvcCBxdWFsaXR5IFdpa2lkYXRhIGNsYXNzZXMgYXJlOgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQp0b3BRdWFsaXR5U2V0TGFiZWxzCmBgYAoKVGhlIGxvd2VyIDEwMCBxdWFsaXR5IFdpa2lkYXRhIGNsYXNzZXMgYXJlOgoKYGBge3IgZWNobyA9IFQsIG1lc3NhZ2UgPSBGfQpsb3dRdWFsaXR5U2V0TGFiZWxzCmBgYAoKV2UgY2FuIHNlZSB0aGF0IG1hbnkgYFdpa2ltZWRpYSpgIGNsYXNzZXMgYXJlIGZvdW5kIGFtb25nIHRoZSBjbGFzc2VzIG9mIGxvd2VzdCBtZWFuIE9SRVMgc2NvcmVzIGluIHRoZSBkYXRhc2V0LgoKKioqCkxpY2Vuc2U6IFtHUEx2M10oaHR0cDovL3d3dy5nbnUub3JnL2xpY2Vuc2VzL2dwbC0zLjAudHh0KQpUaGlzIE5vdGVib29rIGlzIGZyZWUgc29mdHdhcmU6IHlvdSBjYW4gcmVkaXN0cmlidXRlIGl0IGFuZC9vciBtb2RpZnkgaXQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhcyBwdWJsaXNoZWQgYnkgdGhlIEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbiwgZWl0aGVyIHZlcnNpb24gMyBvZiB0aGUgTGljZW5zZSwgb3IgKGF0IHlvdXIgb3B0aW9uKSBhbnkgbGF0ZXIgdmVyc2lvbi4KVGhpcyBOb3RlYm9vayBpcyBkaXN0cmlidXRlZCBpbiB0aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLCBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZiBNRVJDSEFOVEFCSUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuICBTZWUgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGZvciBtb3JlIGRldGFpbHMuCllvdSBzaG91bGQgaGF2ZSByZWNlaXZlZCBhIGNvcHkgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFsb25nIHdpdGggdGhpcyBOb3RlYm9vay4gSWYgbm90LCBzZWUgPGh0dHA6Ly93d3cuZ251Lm9yZy9saWNlbnNlcy8+LgoKKioqCgoK