Continuing our series of analysis of text from recent TED Talks with the search keyword “AI”, today’s post uses the text data to perform some simple topic modelling in R.

Some relevant questions might be:

# Some packages

library(tidyverse)
library(data.table)
library(tidytext)
library(dendextend)
library(tm)
library(topicmodels)
library(ggrepel)
library(plotly)

# Some functions

mytheme<- theme_bw() +
          theme(strip.background = element_rect(fill="grey40"),
                strip.text= element_text(face= "bold", colour= "white"))

mytSNE<- function(thematrix){
  perplex<- round(sqrt(nrow(thematrix)))
  res<- Rtsne::Rtsne(thematrix, dims= 2, perplexity= perplex)
  resdf<- data.frame(x= res$Y[,1], y= res$Y[,2])
  resdf$x<- resdf$x + rnorm(nrow(resdf),0, 0.2)  # Add some noise 
  resdf$y<- resdf$y + rnorm(nrow(resdf),0, 0.2)
  return(resdf)
}

putZeros<- function(OB) {
  OB<- OB %>% mutate_at(vars(-group_cols()),~replace(.,is.na(.),0))
  return(OB)
}

# Load data from last time
df<- readRDS("TED_data.rds")
head(df,10)

Some text preprocessing…

Before undertaking any text modelling task, we must do some pre-processing. In this case, the term AI is used a lot in several variations. I want to recognise all of these and group them together under the term “subject_ai”. I will also remove stop words, and use word stemming/completion.

# Tokenization, pre-processing, stemming
sentObj<- unnest_sentences(df, sent, full_text)

# Look through the text for references to the term "AI"
ai_patt<- "^a\\.?i | a\\.?i | a\\.?i\\.| a\\.?i,"

# Label all these as "subject_ai"
sentObj$sent<- str_replace_all(sentObj$sent, ai_patt, " subject_ai ")

wordObj<-      sentObj %>% unnest_tokens(word, sent, drop= FALSE)

outwords<- c("yeah","no","black","ll","ve","ynh","don", "isn","won","didn","ca","em", "milo", "nb","ems","t","applause", "laughter")


wordObj<- wordObj %>% mutate(LEN= nchar(word)) %>% filter(LEN > 2) %>%
  filter(!word %in% stop_words$word) %>%
  filter(!word %in% outwords) %>%
  filter(!word %like% "\\d")

wordObj$stem<- stemDocument(wordObj$word)


# Some additional manual stemming
wordObj<- wordObj %>% mutate(stem= ifelse(word %like% "chinese|china","china",stem),
                             stem= ifelse(word %like% "deepfak", "deepfake", stem),
                             stem= ifelse(word %like% "superintel","superintelligent", stem),
                             stem= ifelse(word %like% "woman","women", stem),
                             stem= ifelse(word %like% "drug", "drug", stem),
                             stem= ifelse(word %like% "medicin", "medical", stem),
                             stem= ifelse(word %like% "percent", "percent", stem),
                             stem= ifelse(word %like% "a.i", "ai", stem),
                             stem= ifelse(word %like% "accid","accid", stem))

# make a stem completion dictionary 
t1<-  wordObj %>% ungroup() %>% count(word,stem)
t2<- t1 %>% ungroup() %>% arrange(stem, desc(n)) %>% group_by(stem) %>% mutate(Order= order(desc(n)))
t2<- t2 %>% filter(Order==1) %>% select(-c(Order,n)) %>% ungroup() %>% rename(complete= word)

# Apply stem completion
wordObj<- left_join(wordObj,t2)
wordObj$word<- wordObj$complete

Any non-useful words?

Before we proceed to topic modelling, we want to check if there are any words in our data set that are too ubiquitous to be useful for discerning topics. In this case there is only one word that seems to dominate our data, this is “information”.

# Identify any dominating words
t0<- wordObj %>% ungroup() %>% count(word, sort= TRUE) %>% 
                 slice(1:10) %>% print()

ggplot(t0, aes(x= n, y=fct_reorder(word, n), fill=n)) +
  geom_col() +
  theme_bw() + 
  theme(legend.position = "none") +
  labs(x= "Count", y="",title= "TED Talks: Most Frequently Occurring Words")

common_words<- "information"
# Construct a document-term matrix
dtm <- wordObj %>% 
  count(ID,word) %>% 
  filter(!word %in% common_words) %>%
  cast_dtm(document=ID, term=word, value=n)

freq_terms<- findFreqTerms(dtm,10)
mod_dtm<- dtm[, freq_terms]

glimpse(mod_dtm)
List of 6
 $ i       : int [1:18632] 1 9 13 16 19 21 32 37 46 48 ...
 $ j       : int [1:18632] 1 1 1 1 1 1 1 1 1 1 ...
 $ v       : num [1:18632] 2 1 1 2 1 1 1 4 1 1 ...
 $ nrow    : int 88
 $ ncol    : int 1180
 $ dimnames:List of 2
  ..$ Docs : chr [1:88] "1" "2" "3" "4" ...
  ..$ Terms: chr [1:1180] "accept" "act" "advanced" "agents" ...
 - attr(*, "class")= chr [1:2] "DocumentTermMatrix" "simple_triplet_matrix"
 - attr(*, "weighting")= chr [1:2] "term frequency" "tf"

Ordinary Clustering based on Document-Term-Matrix

Before we try anything fancy, we can try a clustering of talks based on the ordinary document-term matrix

dend<- dtm %>% dist() %>% hclust(method= "ward.D") %>%
       as.dendrogram() %>%
       set_labels(df$title) %>%
       color_branches(h= 145) %>%
       color_labels(h= 145) %>%
       set("labels_cex", 0.8)
  
par(mar= c(1,1,1,25))
plot(dend, horiz= TRUE, main= "TED Talks: Clustering based on DTM")

Based on clustering with the raw Document-Term Matrix we can see four clear clusters of talks. However, because there are so many different talks in each cluster, it is relatively difficult to distinguish a clear topic for each cluster.

Latent Dirichlet Allocation (LDA)

LDA is a topic modelling technique for finding groups of words that tend to occur together - hence a topic. It’s important to note that LDA doesn’t take the meaning of the words into account, simply their frequency of co-occurence. We don’t know in advance how many topics to specify in the LDA model, so I will try a range of topic counts from 10 to 30. Here we are seeking to maximise negative Log likelihood and minimise perplexity.

# ks<- seq(10,30,1)
# LL<- rep(NA,length(ks))
# pp<- rep(NA,length(ks))
# 
# 
# for (k in 1:length(ks)){
#   cat("\n",ks[k])
#   mod <- LDA(x=mod_dtm, k=ks[k], method= "Gibbs",
#              control=list(alpha=0.001, seed=10005))
#   LL[k]<- logLik(mod) # Retrieve log-likelihood
#   pp[k]<- perplexity(object=mod, newdata=mod_dtm) # Find perplexity
# 
# }

pdata<- data.frame(Topics= rep(ks,2), value= c(LL, pp), 
                   Metric= c(rep("Negative LogLik", length(ks)),
                             rep("Perplexity", length(ks))) )

ggplot(pdata, aes(x= Topics, y= value, color= Metric)) +
  geom_line(size=1.25) +
  geom_vline(xintercept = 19, size= 0.35, lty= "dashed") +
  facet_wrap(~Metric, scales= "free_y") +
  mytheme +
  labs(title= "LDA by Number of Topics")

Across the tested number of topics, the perplexity continues to decrease monotonically so this is not a helpful criterion. The maximum -LogLik across the testing range is 29 Topics. For our purposes that is rather a lot, so instead I have decided to take the first peak point at 19 Topics.

Implement a 19-topic solution

Now let’s run the LDA model again, specifying 19 topics. Then we will use the beta matrix from the solution to explore the top terms contributing to each topic.

mod <- topicmodels::LDA(x=mod_dtm, k=19, method= "Gibbs", 
                        control=list(alpha=0.001, seed=10005, burnin= 500, 
                                     thin= 100, iter= 4000))

top_terms <- tidy(mod, matrix = "beta") %>%
              group_by(topic) %>%
              arrange(topic, desc(beta)) %>%
              slice(seq_len(6)) %>%
              arrange(topic, beta) %>%
              mutate(row = row_number()) %>%
              ungroup() %>%
              mutate(topic = paste("Topic", str_pad(topic,width= 2, pad="0"), sep = " "))

# Plot the topics

top_terms %>%
  mutate(topic = factor(topic),
         term = reorder_within(term, beta, topic)) %>%
  ggplot(aes(term, beta, fill = log(beta))) +
  geom_bar(stat = "identity", show.legend = FALSE, color= "grey20", size= 0.2) +
  scale_x_reordered() +
  facet_wrap(~ topic, scales = "free", ncol = 3) +
  coord_flip() +
  theme_minimal() +
  scale_fill_distiller(palette = "RdYlBu") +
  theme(legend.position = 'none',
        panel.grid = element_blank(),
        axis.text.y = element_text(size= 10),
        axis.text.x = element_blank(),
        plot.margin = margin(0.5, 1, 0.5, 0.5, "cm")) +
  labs(title= "TED Talks: Strongest Words by Topic", y= NULL, x= NULL)

Assign Topic Labels

We can assign the top four terms in each topic to form a topic label. This helps us know key words are influential within each topic.

topicLabels<- top_terms %>% ungroup() %>% arrange(topic, desc(beta)) %>% group_by(topic) %>%
                            mutate(Order= order(desc(beta))) %>% filter(Order < 5) %>%
                            summarise(Label= str_c(term, collapse=" ")) %>%
                            mutate(topic= str_sub(topic, 7),
                                   Label= paste0(topic,": ",str_to_sentence(Label)), 
                                   topic= as.numeric(topic)) %>% print()

Topics in Space

Although we have set the alpha on the LDA model very small, our topics will still be correlated. I found it helpful to look at how the topics group together, and how they could be laid out in two-dimensional space.

bt<-      mod %>% tidy(matrix= "beta") %>% spread(term, beta) 
hc<-      bt %>% dist() %>% hclust(method= "ward.D")
plot(hc); rect.hclust(hc, h =0.12, border = "blue")


tsne<-    mytSNE(bt) %>% mutate(text= str_wrap(topicLabels$Label,15), color= cutree(hc, h=0.12))


ggplot(tsne, aes(x= x, y=y, color= factor(color))) +
  geom_text_repel(aes(label= text), segment.alpha= 0, fontface= "bold")  +
  theme_bw() +
  theme(legend.position = "none",
        panel.grid = element_blank()
        ) +
  scale_color_manual(values= brewer.pal(8,"Set2")) +
  labs(title= "Topics in 2-Dimensional Space (tSNE)")

We can see from this that topic 4 is quite a distinct entity, while there are two groups of topics (turquoise and pink) that have empirical commonalities. The dendrogram tells us that toopic 17 actually joins on the periphery of the pink group of topics, while Topics 10 and 7 loosely relate to Topics 5 and 13. In this way we could see the domain of talks breaking down into:

Clustering Talks based on LDA gamma matrix

One big advantage of the LDA model is that it has two very handy matrices as outputs. The beta matrix we have already used above to relate terms to topics. The gamma matrix relates documents to topics, and so can be used as input to document clustering.

par(mar= c(1,1,1,1)) 
dt<-    mod %>% tidy(matrix = "gamma")  %>% 
               spread(topic,gamma) %>% putZeros() %>%
               select(-document)
hc<-    dt %>% dist() %>% hclust(method= "ward.D") 
plot(hc); rect.hclust(hc, k = 17, border = "blue")

par(mar= c(1,1,1,25))

dend<- hc %>% as.dendrogram() %>%
       set_labels(df$title) %>%
       color_branches(k= 18) %>%
       color_labels(k= 18) %>%
       set("labels_cex", 0.7) %>% plot(horiz= TRUE)

Spatial Representation by Gamma Score

resdf<- mytSNE(dt) %>% mutate(cluster= cutree(hc, k=18), text= str_wrap(df$title, 25))

plot_ly(resdf, x= ~x, y=~y, color= ~factor(cluster),
        hoverinfo = "text",
        text = ~text) %>%
  add_markers(size= 6) %>%
  layout(showlegend= FALSE, 
         title= "TED Talks: Talks tSNE by Gamma Score",
         xaxis = list(title = 'Dimension 1', color= "grey"),
         yaxis = list(title = 'Dimension 2', color= "grey"))

Grouping Talks by Highest Topic Gamma

An alternative method for grouping talks together would be to assign each talk to the topic with which they had the highest gamma score. This means some overlapping information is lost, but helps us sort the talks into categories.


titleRef<- df %>% select(document= ID, title, Year) %>% mutate(document= as.character(document))

dt0<- mod %>% tidy("gamma")
dt2<- dt0 %>% arrange(document, desc(gamma)) %>% group_by(document) %>% slice(1)
dt2<- dt2 %>% left_join(titleRef) %>% left_join(topicLabels)

dt2<- dt2 %>% ungroup() %>% group_by(topic) %>% mutate(Order= order(gamma))

ggplot(dt2, aes(x= gamma, y= factor(Order), group= Label, fill= Label)) +
  geom_col(width=1) +
  geom_text(aes(x= 0.01, y= Order, label= title), hjust= 0, size= 3, fontface= "bold", color= "white") +
  scale_x_continuous(limits = c(0, 1), expand = c(0, 0)) +
  facet_wrap(~Label, scales= "free_y", ncol= 3) +
  theme(legend.position= "none",
        panel.background = element_rect(fill= "grey30"),
        panel.grid = element_blank(),
        strip.background = element_rect(fill= "black"),
        strip.text = element_text(face= "bold", color= "white", size= 10),
        axis.text.y =  element_blank(),
        axis.ticks.y = element_blank()) +
  labs(title= "TED Talks by Topic Cluster", x= "Gamma", y=NULL)

How have talk topics evolved over time?

The analysis below shows how we can gather the gamma scores across the years to show which topics are more prominent, and how this has varied from year to year.

byYear<- dt0 %>% left_join(titleRef) %>% left_join(topicLabels) %>%
  filter(Year!= 2020) %>% group_by(Year,topic, Label) %>% summarise(Sum_gamma= sum(gamma))
yearTotals<- byYear %>% group_by(Year) %>% summarise(total_gamma= sum(Sum_gamma))

trendRef<- data.frame(
  stringsAsFactors = FALSE,
                        topic = c(4L,5L,
                                  2L,6L,8L,9L,11L,12L,7L,10L,13L,14L,1L,
                                  15L,16L,19L,3L,17L,18L),
                        trend = c("Decreasing","Decreasing","Increasing Worm",
                                  "Increasing Worm","Increasing Worm",
                                  "Increasing Worm","Increasing Worm","Lump","Lump","Lump",
                                  "Lump","Lump","Rocky","Rocky","Rocky",
                                  "Rocky","Worm","Worm","Worm"))

byYear<- left_join(byYear, yearTotals) %>% mutate(pc= Sum_gamma/total_gamma) %>% left_join(trendRef)
thetrends<- names(table(trendRef$trend))
for (k in 1:length(thetrends)) {
  sett<- byYear %>% filter(trend==thetrends[k])
g<- ggplot(sett, aes(x= Year, y=pc, fill= Label)) +
  geom_area(aes(group= Label), position= "stack", color= "grey20", size= 0.3, show.legend = TRUE, alpha= 0.7) +
  labs(title= paste("Trend over time:",thetrends[k]), fill= "Topic", y= "Percent of Gamma by Year") +
  theme_bw() +
  ylim(0, 0.7) +
  scale_x_continuous(breaks= unique(byYear$Year)) +
  theme(axis.text.x = element_text(angle= 90))
print(g)  
}

… … …

LS0tCnRpdGxlOiAnVEVEIFRhbGtzOiBBSSBhbmQgVG9waWMgTW9kZWxsaW5nJwpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKYXV0aG9yOiBDZWwgTWNDcmFja2VuCmRhdGU6ICIyMDIwLTA4LTAyIgotLS0KCiFbXSgvVXNlcnMvQ2VsZXN0ZS9Eb2N1bWVudHMvVHdpdHRlci9oZWFkZXJfcGxvdC5wbmcpCkNvbnRpbnVpbmcgb3VyIHNlcmllcyBvZiBhbmFseXNpcyBvZiB0ZXh0IGZyb20gcmVjZW50IFRFRCBUYWxrcyB3aXRoIHRoZSBzZWFyY2gga2V5d29yZCAiQUkiLCB0b2RheSdzIHBvc3QgdXNlcyB0aGUgdGV4dCBkYXRhIHRvIHBlcmZvcm0gc29tZSBzaW1wbGUgdG9waWMgbW9kZWxsaW5nIGluIFIuIAoKU29tZSByZWxldmFudCBxdWVzdGlvbnMgbWlnaHQgYmU6CgoqIEFyZSB0aGVyZSBuYXR1cmFsIHRvcGljcyB0aGF0IGV4aXN0IGluIHRoZSBkYXRhPyAKKiBDYW4gd2UgZ2V0IGFuIG92ZXJ2aWV3IG9mIHRoZSB0b3BpYyBjb250ZW50IHdpdGhvdXQgcmVhZGluZyBldmVyeSB0cmFuc2NyaXB0PwoqIENhbiB3ZSBpZGVudGlmeSBjbHVzdGVycyBvZiB0b3BpYy1yZWxhdGVkIHRhbGtzIGluIHRoZSBkYXRhIHNldD8KYGBge3IsIGVjaG89IFRSVUUsIG1lc3NhZ2U9IEZBTFNFLCB3YXJuaW5nPSBGQUxTRX0KIyBTb21lIHBhY2thZ2VzCgpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KHRpZHl0ZXh0KQpsaWJyYXJ5KGRlbmRleHRlbmQpCmxpYnJhcnkodG0pCmxpYnJhcnkodG9waWNtb2RlbHMpCmxpYnJhcnkoZ2dyZXBlbCkKbGlicmFyeShwbG90bHkpCgojIFNvbWUgZnVuY3Rpb25zCgpteXRoZW1lPC0gdGhlbWVfYncoKSArCiAgICAgICAgICB0aGVtZShzdHJpcC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGw9ImdyZXk0MCIpLAogICAgICAgICAgICAgICAgc3RyaXAudGV4dD0gZWxlbWVudF90ZXh0KGZhY2U9ICJib2xkIiwgY29sb3VyPSAid2hpdGUiKSkKCm15dFNORTwtIGZ1bmN0aW9uKHRoZW1hdHJpeCl7CiAgcGVycGxleDwtIHJvdW5kKHNxcnQobnJvdyh0aGVtYXRyaXgpKSkKICByZXM8LSBSdHNuZTo6UnRzbmUodGhlbWF0cml4LCBkaW1zPSAyLCBwZXJwbGV4aXR5PSBwZXJwbGV4KQogIHJlc2RmPC0gZGF0YS5mcmFtZSh4PSByZXMkWVssMV0sIHk9IHJlcyRZWywyXSkKICByZXNkZiR4PC0gcmVzZGYkeCArIHJub3JtKG5yb3cocmVzZGYpLDAsIDAuMikgICMgQWRkIHNvbWUgbm9pc2UgCiAgcmVzZGYkeTwtIHJlc2RmJHkgKyBybm9ybShucm93KHJlc2RmKSwwLCAwLjIpCiAgcmV0dXJuKHJlc2RmKQp9CgpwdXRaZXJvczwtIGZ1bmN0aW9uKE9CKSB7CiAgT0I8LSBPQiAlPiUgbXV0YXRlX2F0KHZhcnMoLWdyb3VwX2NvbHMoKSksfnJlcGxhY2UoLixpcy5uYSguKSwwKSkKICByZXR1cm4oT0IpCn0KCiMgTG9hZCBkYXRhIGZyb20gbGFzdCB0aW1lCmRmPC0gcmVhZFJEUygiVEVEX2RhdGEucmRzIikKaGVhZChkZiwxMCkKYGBgCiMjIyBTb21lIHRleHQgcHJlcHJvY2Vzc2luZy4uLgpCZWZvcmUgdW5kZXJ0YWtpbmcgYW55IHRleHQgbW9kZWxsaW5nIHRhc2ssIHdlIG11c3QgZG8gc29tZSBwcmUtcHJvY2Vzc2luZy4gSW4gdGhpcyBjYXNlLCB0aGUgdGVybSBBSSBpcyB1c2VkIGEgbG90IGluIHNldmVyYWwgdmFyaWF0aW9ucy4gSSB3YW50IHRvIHJlY29nbmlzZSBhbGwgb2YgdGhlc2UgYW5kIGdyb3VwIHRoZW0gdG9nZXRoZXIgdW5kZXIgdGhlIHRlcm0gInN1YmplY3RfYWkiLiAgSSB3aWxsIGFsc28gcmVtb3ZlIHN0b3Agd29yZHMsIGFuZCB1c2Ugd29yZCBzdGVtbWluZy9jb21wbGV0aW9uLgpgYGB7ciwgZWNobz0gVFJVRSwgbWVzc2FnZT0gRkFMU0UsIHdhcm5pbmc9IEZBTFNFfQojIFRva2VuaXphdGlvbiwgcHJlLXByb2Nlc3NpbmcsIHN0ZW1taW5nCnNlbnRPYmo8LSB1bm5lc3Rfc2VudGVuY2VzKGRmLCBzZW50LCBmdWxsX3RleHQpCgojIExvb2sgdGhyb3VnaCB0aGUgdGV4dCBmb3IgcmVmZXJlbmNlcyB0byB0aGUgdGVybSAiQUkiCmFpX3BhdHQ8LSAiXmFcXC4/aSB8IGFcXC4/aSB8IGFcXC4/aVxcLnwgYVxcLj9pLCIKCiMgTGFiZWwgYWxsIHRoZXNlIGFzICJzdWJqZWN0X2FpIgpzZW50T2JqJHNlbnQ8LSBzdHJfcmVwbGFjZV9hbGwoc2VudE9iaiRzZW50LCBhaV9wYXR0LCAiIHN1YmplY3RfYWkgIikKCndvcmRPYmo8LSAgICAgIHNlbnRPYmogJT4lIHVubmVzdF90b2tlbnMod29yZCwgc2VudCwgZHJvcD0gRkFMU0UpCgpvdXR3b3JkczwtIGMoInllYWgiLCJubyIsImJsYWNrIiwibGwiLCJ2ZSIsInluaCIsImRvbiIsICJpc24iLCJ3b24iLCJkaWRuIiwiY2EiLCJlbSIsICJtaWxvIiwgIm5iIiwiZW1zIiwidCIsImFwcGxhdXNlIiwgImxhdWdodGVyIikKCiMgRmlsdGVyIG91dCBzb21lIHVud2FudGVkIHRlcm1zCndvcmRPYmo8LSB3b3JkT2JqICU+JSBtdXRhdGUoTEVOPSBuY2hhcih3b3JkKSkgJT4lIGZpbHRlcihMRU4gPiAyKSAlPiUKICBmaWx0ZXIoIXdvcmQgJWluJSBzdG9wX3dvcmRzJHdvcmQpICU+JQogIGZpbHRlcighd29yZCAlaW4lIG91dHdvcmRzKSAlPiUKICBmaWx0ZXIoIXdvcmQgJWxpa2UlICJcXGQiKQoKIyBXb3JkIHN0ZW1taW5nCndvcmRPYmokc3RlbTwtIHN0ZW1Eb2N1bWVudCh3b3JkT2JqJHdvcmQpCgojIFNvbWUgYWRkaXRpb25hbCBtYW51YWwgc3RlbW1pbmcKd29yZE9iajwtIHdvcmRPYmogJT4lIG11dGF0ZShzdGVtPSBpZmVsc2Uod29yZCAlbGlrZSUgImNoaW5lc2V8Y2hpbmEiLCJjaGluYSIsc3RlbSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RlbT0gaWZlbHNlKHdvcmQgJWxpa2UlICJkZWVwZmFrIiwgImRlZXBmYWtlIiwgc3RlbSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RlbT0gaWZlbHNlKHdvcmQgJWxpa2UlICJzdXBlcmludGVsIiwic3VwZXJpbnRlbGxpZ2VudCIsIHN0ZW0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0ZW09IGlmZWxzZSh3b3JkICVsaWtlJSAid29tYW4iLCJ3b21lbiIsIHN0ZW0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0ZW09IGlmZWxzZSh3b3JkICVsaWtlJSAiZHJ1ZyIsICJkcnVnIiwgc3RlbSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RlbT0gaWZlbHNlKHdvcmQgJWxpa2UlICJtZWRpY2luIiwgIm1lZGljYWwiLCBzdGVtKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGVtPSBpZmVsc2Uod29yZCAlbGlrZSUgInBlcmNlbnQiLCAicGVyY2VudCIsIHN0ZW0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0ZW09IGlmZWxzZSh3b3JkICVsaWtlJSAiYS5pIiwgImFpIiwgc3RlbSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RlbT0gaWZlbHNlKHdvcmQgJWxpa2UlICJhY2NpZCIsImFjY2lkIiwgc3RlbSkpCgojIG1ha2UgYSBzdGVtIGNvbXBsZXRpb24gZGljdGlvbmFyeSAKdDE8LSAgd29yZE9iaiAlPiUgdW5ncm91cCgpICU+JSBjb3VudCh3b3JkLHN0ZW0pCnQyPC0gdDEgJT4lIHVuZ3JvdXAoKSAlPiUgYXJyYW5nZShzdGVtLCBkZXNjKG4pKSAlPiUgZ3JvdXBfYnkoc3RlbSkgJT4lIG11dGF0ZShPcmRlcj0gb3JkZXIoZGVzYyhuKSkpCnQyPC0gdDIgJT4lIGZpbHRlcihPcmRlcj09MSkgJT4lIHNlbGVjdCgtYyhPcmRlcixuKSkgJT4lIHVuZ3JvdXAoKSAlPiUgcmVuYW1lKGNvbXBsZXRlPSB3b3JkKQoKIyBBcHBseSBzdGVtIGNvbXBsZXRpb24Kd29yZE9iajwtIGxlZnRfam9pbih3b3JkT2JqLHQyKQp3b3JkT2JqJHdvcmQ8LSB3b3JkT2JqJGNvbXBsZXRlCmBgYAojIyMgQW55IG5vbi11c2VmdWwgd29yZHM/CkJlZm9yZSB3ZSBwcm9jZWVkIHRvIHRvcGljIG1vZGVsbGluZywgd2Ugd2FudCB0byBjaGVjayBpZiB0aGVyZSBhcmUgYW55IHdvcmRzIGluIG91ciBkYXRhIHNldCB0aGF0IGFyZSB0b28gdWJpcXVpdG91cyB0byBiZSB1c2VmdWwgZm9yIGRpc2Nlcm5pbmcgdG9waWNzLiBJbiB0aGlzIGNhc2UgdGhlcmUgaXMgb25seSBvbmUgd29yZCB0aGF0IHNlZW1zIHRvIGRvbWluYXRlIG91ciBkYXRhLCB0aGlzIGlzICJpbmZvcm1hdGlvbiIuCmBgYHtyLCBlY2hvPSBUUlVFfQojIElkZW50aWZ5IGFueSBkb21pbmF0aW5nIHdvcmRzCnQwPC0gd29yZE9iaiAlPiUgdW5ncm91cCgpICU+JSBjb3VudCh3b3JkLCBzb3J0PSBUUlVFKSAlPiUgCiAgICAgICAgICAgICAgICAgc2xpY2UoMToxMCkgJT4lIHByaW50KCkKCmdncGxvdCh0MCwgYWVzKHg9IG4sIHk9ZmN0X3Jlb3JkZXIod29yZCwgbiksIGZpbGw9bikpICsKICBnZW9tX2NvbCgpICsKICB0aGVtZV9idygpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgbGFicyh4PSAiQ291bnQiLCB5PSIiLHRpdGxlPSAiVEVEIFRhbGtzOiBNb3N0IEZyZXF1ZW50bHkgT2NjdXJyaW5nIFdvcmRzIikKYGBgCgpgYGB7ciwgZWNobz1UUlVFfQpjb21tb25fd29yZHM8LSAiaW5mb3JtYXRpb24iCiMgQ29uc3RydWN0IGEgZG9jdW1lbnQtdGVybSBtYXRyaXgKZHRtIDwtIHdvcmRPYmogJT4lIAogIGNvdW50KElELHdvcmQpICU+JSAKICBmaWx0ZXIoIXdvcmQgJWluJSBjb21tb25fd29yZHMpICU+JQogIGNhc3RfZHRtKGRvY3VtZW50PUlELCB0ZXJtPXdvcmQsIHZhbHVlPW4pCgpmcmVxX3Rlcm1zPC0gZmluZEZyZXFUZXJtcyhkdG0sMTApCm1vZF9kdG08LSBkdG1bLCBmcmVxX3Rlcm1zXQoKZ2xpbXBzZShtb2RfZHRtKQpgYGAKIyMjIE9yZGluYXJ5IENsdXN0ZXJpbmcgYmFzZWQgb24gRG9jdW1lbnQtVGVybS1NYXRyaXgKQmVmb3JlIHdlIHRyeSBhbnl0aGluZyBmYW5jeSwgd2UgY2FuIHRyeSBhIGNsdXN0ZXJpbmcgb2YgdGFsa3MgYmFzZWQgb24gdGhlIG9yZGluYXJ5IGRvY3VtZW50LXRlcm0gbWF0cml4CmBgYHtyLCBlY2hvPSBUUlVFLCBmaWcuaGVpZ2h0PSA3LCBmaWcud2lkdGg9IDUsIGZpZy5hbGlnbj0gJ2NlbnRlcid9CmRlbmQ8LSBkdG0gJT4lIGRpc3QoKSAlPiUgaGNsdXN0KG1ldGhvZD0gIndhcmQuRCIpICU+JQogICAgICAgYXMuZGVuZHJvZ3JhbSgpICU+JQogICAgICAgc2V0X2xhYmVscyhkZiR0aXRsZSkgJT4lCiAgICAgICBjb2xvcl9icmFuY2hlcyhoPSAxNDUpICU+JQogICAgICAgY29sb3JfbGFiZWxzKGg9IDE0NSkgJT4lCiAgICAgICBzZXQoImxhYmVsc19jZXgiLCAwLjgpCiAgCnBhcihtYXI9IGMoMSwxLDEsMjUpKQpwbG90KGRlbmQsIGhvcml6PSBUUlVFLCBtYWluPSAiVEVEIFRhbGtzOiBDbHVzdGVyaW5nIGJhc2VkIG9uIERUTSIpCmBgYApCYXNlZCBvbiBjbHVzdGVyaW5nIHdpdGggdGhlIHJhdyBEb2N1bWVudC1UZXJtIE1hdHJpeCB3ZSBjYW4gc2VlIGZvdXIgY2xlYXIgY2x1c3RlcnMgb2YgdGFsa3MuIEhvd2V2ZXIsIGJlY2F1c2UgdGhlcmUgYXJlIHNvIG1hbnkgZGlmZmVyZW50IHRhbGtzIGluIGVhY2ggY2x1c3RlciwgaXQgaXMgcmVsYXRpdmVseSBkaWZmaWN1bHQgdG8gZGlzdGluZ3Vpc2ggYSBjbGVhciB0b3BpYyBmb3IgZWFjaCBjbHVzdGVyLiAKCiMjIyBMYXRlbnQgRGlyaWNobGV0IEFsbG9jYXRpb24gKExEQSkKCkxEQSBpcyBhIHRvcGljIG1vZGVsbGluZyB0ZWNobmlxdWUgZm9yIGZpbmRpbmcgZ3JvdXBzIG9mIHdvcmRzIHRoYXQgdGVuZCB0byBvY2N1ciB0b2dldGhlciAtIGhlbmNlIGEgdG9waWMuIEl0J3MgaW1wb3J0YW50IHRvIG5vdGUgdGhhdCBMREEgZG9lc24ndCB0YWtlIHRoZSBtZWFuaW5nIG9mIHRoZSB3b3JkcyBpbnRvIGFjY291bnQsIHNpbXBseSB0aGVpciBmcmVxdWVuY3kgb2YgY28tb2NjdXJlbmNlLiBXZSBkb24ndCBrbm93IGluIGFkdmFuY2UgaG93IG1hbnkgdG9waWNzIHRvIHNwZWNpZnkgaW4gdGhlIExEQSBtb2RlbCwgc28gSSB3aWxsIHRyeSBhIHJhbmdlIG9mIHRvcGljIGNvdW50cyBmcm9tIDEwIHRvIDMwLiBIZXJlIHdlIGFyZSBzZWVraW5nIHRvIG1heGltaXNlIG5lZ2F0aXZlIExvZyBsaWtlbGlob29kIGFuZCBtaW5pbWlzZSBwZXJwbGV4aXR5LiAKCmBgYHtyLCBlY2hvPSBUUlVFfQprczwtIHNlcSgxMCwzMCwxKQpMTDwtIHJlcChOQSxsZW5ndGgoa3MpKQpwcDwtIHJlcChOQSxsZW5ndGgoa3MpKQoKCmZvciAoayBpbiAxOmxlbmd0aChrcykpewogIGNhdCgiXG4iLGtzW2tdKQogIG1vZCA8LSBMREEoeD1tb2RfZHRtLCBrPWtzW2tdLCBtZXRob2Q9ICJHaWJicyIsCiAgICAgICAgICAgICBjb250cm9sPWxpc3QoYWxwaGE9MC4wMDEsIHNlZWQ9MTAwMDUpKQogIExMW2tdPC0gbG9nTGlrKG1vZCkgIyBSZXRyaWV2ZSBsb2ctbGlrZWxpaG9vZAogIHBwW2tdPC0gcGVycGxleGl0eShvYmplY3Q9bW9kLCBuZXdkYXRhPW1vZF9kdG0pICMgRmluZCBwZXJwbGV4aXR5Cgp9CgpwZGF0YTwtIGRhdGEuZnJhbWUoVG9waWNzPSByZXAoa3MsMiksIHZhbHVlPSBjKExMLCBwcCksIAogICAgICAgICAgICAgICAgICAgTWV0cmljPSBjKHJlcCgiTmVnYXRpdmUgTG9nTGlrIiwgbGVuZ3RoKGtzKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwKCJQZXJwbGV4aXR5IiwgbGVuZ3RoKGtzKSkpICkKCmdncGxvdChwZGF0YSwgYWVzKHg9IFRvcGljcywgeT0gdmFsdWUsIGNvbG9yPSBNZXRyaWMpKSArCiAgZ2VvbV9saW5lKHNpemU9MS4yNSkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDE5LCBzaXplPSAwLjM1LCBsdHk9ICJkYXNoZWQiKSArCiAgZmFjZXRfd3JhcCh+TWV0cmljLCBzY2FsZXM9ICJmcmVlX3kiKSArCiAgbXl0aGVtZSArCiAgbGFicyh0aXRsZT0gIkxEQSBieSBOdW1iZXIgb2YgVG9waWNzIikKCmBgYApBY3Jvc3MgdGhlIHRlc3RlZCBudW1iZXIgb2YgdG9waWNzLCB0aGUgcGVycGxleGl0eSBjb250aW51ZXMgdG8gZGVjcmVhc2UgbW9ub3RvbmljYWxseSBzbyB0aGlzIGlzIG5vdCBhIGhlbHBmdWwgY3JpdGVyaW9uLiBUaGUgbWF4aW11bSAtTG9nTGlrIGFjcm9zcyB0aGUgdGVzdGluZyByYW5nZSBpcyAyOSBUb3BpY3MuIEZvciBvdXIgcHVycG9zZXMgdGhhdCBpcyByYXRoZXIgYSBsb3QsIHNvIGluc3RlYWQgSSBoYXZlIGRlY2lkZWQgdG8gdGFrZSB0aGUgZmlyc3QgcGVhayBwb2ludCBhdCAxOSBUb3BpY3MuCgojIyMgSW1wbGVtZW50IGEgMTktdG9waWMgc29sdXRpb24KTm93IGxldCdzIHJ1biB0aGUgTERBIG1vZGVsIGFnYWluLCBzcGVjaWZ5aW5nIDE5IHRvcGljcy4gIFRoZW4gd2Ugd2lsbCB1c2UgdGhlIGJldGEgbWF0cml4IGZyb20gdGhlIHNvbHV0aW9uIHRvIGV4cGxvcmUgdGhlIHRvcCB0ZXJtcyBjb250cmlidXRpbmcgdG8gZWFjaCB0b3BpYy4KYGBge3IgZWNobz1UUlVFLCBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTZ9Cm1vZCA8LSB0b3BpY21vZGVsczo6TERBKHg9bW9kX2R0bSwgaz0xOSwgbWV0aG9kPSAiR2liYnMiLCAKICAgICAgICAgICAgICAgICAgICAgICAgY29udHJvbD1saXN0KGFscGhhPTAuMDAxLCBzZWVkPTEwMDA1LCBidXJuaW49IDUwMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGluPSAxMDAsIGl0ZXI9IDQwMDApKQoKdG9wX3Rlcm1zIDwtIHRpZHkobW9kLCBtYXRyaXggPSAiYmV0YSIpICU+JQogICAgICAgICAgICAgIGdyb3VwX2J5KHRvcGljKSAlPiUKICAgICAgICAgICAgICBhcnJhbmdlKHRvcGljLCBkZXNjKGJldGEpKSAlPiUKICAgICAgICAgICAgICBzbGljZShzZXFfbGVuKDYpKSAlPiUKICAgICAgICAgICAgICBhcnJhbmdlKHRvcGljLCBiZXRhKSAlPiUKICAgICAgICAgICAgICBtdXRhdGUocm93ID0gcm93X251bWJlcigpKSAlPiUKICAgICAgICAgICAgICB1bmdyb3VwKCkgJT4lCiAgICAgICAgICAgICAgbXV0YXRlKHRvcGljID0gcGFzdGUoIlRvcGljIiwgc3RyX3BhZCh0b3BpYyx3aWR0aD0gMiwgcGFkPSIwIiksIHNlcCA9ICIgIikpCgojIFBsb3QgdGhlIHRvcGljcwoKdG9wX3Rlcm1zICU+JQogIG11dGF0ZSh0b3BpYyA9IGZhY3Rvcih0b3BpYyksCiAgICAgICAgIHRlcm0gPSByZW9yZGVyX3dpdGhpbih0ZXJtLCBiZXRhLCB0b3BpYykpICU+JQogIGdncGxvdChhZXModGVybSwgYmV0YSwgZmlsbCA9IGxvZyhiZXRhKSkpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSwgY29sb3I9ICJncmV5MjAiLCBzaXplPSAwLjIpICsKICBzY2FsZV94X3Jlb3JkZXJlZCgpICsKICBmYWNldF93cmFwKH4gdG9waWMsIHNjYWxlcyA9ICJmcmVlIiwgbmNvbCA9IDMpICsKICBjb29yZF9mbGlwKCkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJSZFlsQnUiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnLAogICAgICAgIHBhbmVsLmdyaWQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZT0gMTApLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHBsb3QubWFyZ2luID0gbWFyZ2luKDAuNSwgMSwgMC41LCAwLjUsICJjbSIpKSArCiAgbGFicyh0aXRsZT0gIlRFRCBUYWxrczogU3Ryb25nZXN0IFdvcmRzIGJ5IFRvcGljIiwgeT0gTlVMTCwgeD0gTlVMTCkKYGBgCgojIyMgQXNzaWduIFRvcGljIExhYmVscwpXZSBjYW4gYXNzaWduIHRoZSB0b3AgZm91ciB0ZXJtcyBpbiBlYWNoIHRvcGljIHRvIGZvcm0gYSB0b3BpYyBsYWJlbC4gVGhpcyBoZWxwcyB1cyBrbm93IGtleSB3b3JkcyBhcmUgaW5mbHVlbnRpYWwgd2l0aGluIGVhY2ggdG9waWMuCmBgYHtyLCBlY2hvPSBUUlVFLCBtZXNzYWdlPSBGQUxTRX0KdG9waWNMYWJlbHM8LSB0b3BfdGVybXMgJT4lIHVuZ3JvdXAoKSAlPiUgYXJyYW5nZSh0b3BpYywgZGVzYyhiZXRhKSkgJT4lIGdyb3VwX2J5KHRvcGljKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShPcmRlcj0gb3JkZXIoZGVzYyhiZXRhKSkpICU+JSBmaWx0ZXIoT3JkZXIgPCA1KSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN1bW1hcmlzZShMYWJlbD0gc3RyX2ModGVybSwgY29sbGFwc2U9IiAiKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUodG9waWM9IHN0cl9zdWIodG9waWMsIDcpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIExhYmVsPSBwYXN0ZTAodG9waWMsIjogIixzdHJfdG9fc2VudGVuY2UoTGFiZWwpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9waWM9IGFzLm51bWVyaWModG9waWMpKSAlPiUgcHJpbnQoKQpgYGAKIyMjIFRvcGljcyBpbiBTcGFjZQpBbHRob3VnaCB3ZSBoYXZlIHNldCB0aGUgYWxwaGEgb24gdGhlIExEQSBtb2RlbCB2ZXJ5IHNtYWxsLCBvdXIgdG9waWNzIHdpbGwgc3RpbGwgYmUgY29ycmVsYXRlZC4gSSBmb3VuZCBpdCBoZWxwZnVsIHRvIGxvb2sgYXQgaG93IHRoZSB0b3BpY3MgZ3JvdXAgdG9nZXRoZXIsIGFuZCBob3cgdGhleSBjb3VsZCBiZSBsYWlkIG91dCBpbiB0d28tZGltZW5zaW9uYWwgc3BhY2UuCmBgYHtyLCAgZWNobz0gVFJVRSwgZmlnLmhlaWdodD0gMy41LCBmaWcud2lkdGg9IDUsIGZpZy5hbGlnbj0nY2VudGVyJ30KYnQ8LSAgICAgIG1vZCAlPiUgdGlkeShtYXRyaXg9ICJiZXRhIikgJT4lIHNwcmVhZCh0ZXJtLCBiZXRhKSAKaGM8LSAgICAgIGJ0ICU+JSBkaXN0KCkgJT4lIGhjbHVzdChtZXRob2Q9ICJ3YXJkLkQiKQpwbG90KGhjKTsgcmVjdC5oY2x1c3QoaGMsIGggPTAuMTIsIGJvcmRlciA9ICJibHVlIikKCnRzbmU8LSAgICBteXRTTkUoYnQpICU+JSBtdXRhdGUodGV4dD0gc3RyX3dyYXAodG9waWNMYWJlbHMkTGFiZWwsMTUpLCBjb2xvcj0gY3V0cmVlKGhjLCBoPTAuMTIpKQoKCmdncGxvdCh0c25lLCBhZXMoeD0geCwgeT15LCBjb2xvcj0gZmFjdG9yKGNvbG9yKSkpICsKICBnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsPSB0ZXh0KSwgc2VnbWVudC5hbHBoYT0gMCwgZm9udGZhY2U9ICJib2xkIikgICsKICB0aGVtZV9idygpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKQogICAgICAgICkgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXM9IGJyZXdlci5wYWwoOCwiU2V0MiIpKSArCiAgbGFicyh0aXRsZT0gIlRvcGljcyBpbiAyLURpbWVuc2lvbmFsIFNwYWNlICh0U05FKSIpCgpgYGAKV2UgY2FuIHNlZSBmcm9tIHRoaXMgdGhhdCB0b3BpYyA0IGlzIHF1aXRlIGEgZGlzdGluY3QgZW50aXR5LCB3aGlsZSB0aGVyZSBhcmUgdHdvIGdyb3VwcyBvZiB0b3BpY3MgKHR1cnF1b2lzZSBhbmQgcGluaykgdGhhdCBoYXZlIGVtcGlyaWNhbCBjb21tb25hbGl0aWVzLiBUaGUgZGVuZHJvZ3JhbSB0ZWxscyB1cyB0aGF0IHRvb3BpYyAxNyBhY3R1YWxseSBqb2lucyBvbiB0aGUgcGVyaXBoZXJ5IG9mIHRoZSBwaW5rIGdyb3VwIG9mIHRvcGljcywgd2hpbGUgVG9waWNzIDEwIGFuZCA3IGxvb3NlbHkgcmVsYXRlIHRvIFRvcGljcyA1IGFuZCAxMy4KSW4gdGhpcyB3YXkgd2UgY291bGQgc2VlIHRoZSBkb21haW4gb2YgdGFsa3MgYnJlYWtpbmcgZG93biBpbnRvOgoKKiBCcm9hZCBpZGVhcyBhYm91dCBBSSBhbmQgd2hhdCBpcyBwb3NzaWJsZSAoVG9waWNzIDUsIDcsIDksIGFuZCAxMykKKiBUaGUgaW50ZXJzZWN0aW9uIG9mIGh1bWFuIGxpZmUgYW5kIEFJIChUdXJxdW9pc2UgdG9waWNzKQoqIEludGVyZXN0aW5nIGFwcGxpY2F0aW9ucyBvZiBBSSAoTW9zdCBwaW5rIHRvcGljcyA2LCAxMCwgMTIsIDE0LCAxNSwgMTcsIDE4LCAxOSkKKiBBcHBsaWNhdGlvbnMgb2YgUm9ib3RzIChUb3BpYyA0KQoKIyMjIENsdXN0ZXJpbmcgVGFsa3MgYmFzZWQgb24gTERBIGdhbW1hIG1hdHJpeApPbmUgYmlnIGFkdmFudGFnZSBvZiB0aGUgTERBIG1vZGVsIGlzIHRoYXQgaXQgaGFzIHR3byB2ZXJ5IGhhbmR5IG1hdHJpY2VzIGFzIG91dHB1dHMuIFRoZSBiZXRhIG1hdHJpeCB3ZSBoYXZlIGFscmVhZHkgdXNlZCBhYm92ZSB0byByZWxhdGUgdGVybXMgdG8gdG9waWNzLiAgVGhlIGdhbW1hIG1hdHJpeCByZWxhdGVzIGRvY3VtZW50cyB0byB0b3BpY3MsIGFuZCBzbyBjYW4gYmUgdXNlZCBhcyBpbnB1dCB0byBkb2N1bWVudCBjbHVzdGVyaW5nLgpgYGB7ciwgZWNobz0gVFJVRSwgZmlnLmhlaWdodD0gNiwgZmlnLndpZHRoPSA1LCBmaWcuYWxpZ249J2NlbnRlcid9CnBhcihtYXI9IGMoMSwxLDEsMSkpIApkdDwtICAgIG1vZCAlPiUgdGlkeShtYXRyaXggPSAiZ2FtbWEiKSAgJT4lIAogICAgICAgICAgICAgICBzcHJlYWQodG9waWMsZ2FtbWEpICU+JSBwdXRaZXJvcygpICU+JQogICAgICAgICAgICAgICBzZWxlY3QoLWRvY3VtZW50KQpoYzwtICAgIGR0ICU+JSBkaXN0KCkgJT4lIGhjbHVzdChtZXRob2Q9ICJ3YXJkLkQiKSAKcGxvdChoYyk7IHJlY3QuaGNsdXN0KGhjLCBrID0gMTcsIGJvcmRlciA9ICJibHVlIikKCnBhcihtYXI9IGMoMSwxLDEsMjUpKQpkZW5kPC0gaGMgJT4lIGFzLmRlbmRyb2dyYW0oKSAlPiUKICAgICAgIHNldF9sYWJlbHMoZGYkdGl0bGUpICU+JQogICAgICAgY29sb3JfYnJhbmNoZXMoaz0gMTgpICU+JQogICAgICAgY29sb3JfbGFiZWxzKGs9IDE4KSAlPiUKICAgICAgIHNldCgibGFiZWxzX2NleCIsIDAuNykgJT4lIHBsb3QoaG9yaXo9IFRSVUUpCmBgYAojIyMgU3BhdGlhbCBSZXByZXNlbnRhdGlvbiBieSBHYW1tYSBTY29yZQoKYGBge3IsIGVjaG89IFRSVUUsIG1lc3NhZ2U9IEZBTFNFLCB3YXJuaW5nPSBGQUxTRSxmaWcuaGVpZ2h0PSA0LCBmaWcud2lkdGg9IDcsIGZpZy5hbGlnbj0gJ2NlbnRlcicsb3V0LndpZHRoPSAiMTAwJSJ9CnJlc2RmPC0gbXl0U05FKGR0KSAlPiUgbXV0YXRlKGNsdXN0ZXI9IGN1dHJlZShoYywgaz0xOCksIHRleHQ9IHN0cl93cmFwKGRmJHRpdGxlLCAyNSkpCgpwbG90X2x5KHJlc2RmLCB4PSB+eCwgeT1+eSwgY29sb3I9IH5mYWN0b3IoY2x1c3RlciksCiAgICAgICAgaG92ZXJpbmZvID0gInRleHQiLAogICAgICAgIHRleHQgPSB+dGV4dCkgJT4lCiAgYWRkX21hcmtlcnMoc2l6ZT0gNikgJT4lCiAgbGF5b3V0KHNob3dsZWdlbmQ9IEZBTFNFLCAKICAgICAgICAgdGl0bGU9ICJURUQgVGFsa3M6IFRhbGtzIHRTTkUgYnkgR2FtbWEgU2NvcmUiLAogICAgICAgICB4YXhpcyA9IGxpc3QodGl0bGUgPSAnRGltZW5zaW9uIDEnLCBjb2xvcj0gImdyZXkiKSwKICAgICAgICAgeWF4aXMgPSBsaXN0KHRpdGxlID0gJ0RpbWVuc2lvbiAyJywgY29sb3I9ICJncmV5IikpCmBgYAojIyMgR3JvdXBpbmcgVGFsa3MgYnkgSGlnaGVzdCBUb3BpYyBHYW1tYQpBbiBhbHRlcm5hdGl2ZSBtZXRob2QgZm9yIGdyb3VwaW5nIHRhbGtzIHRvZ2V0aGVyIHdvdWxkIGJlIHRvIGFzc2lnbiBlYWNoIHRhbGsgdG8gdGhlIHRvcGljIHdpdGggd2hpY2ggdGhleSBoYWQgdGhlIGhpZ2hlc3QgZ2FtbWEgc2NvcmUuIFRoaXMgbWVhbnMgc29tZSBvdmVybGFwcGluZyBpbmZvcm1hdGlvbiBpcyBsb3N0LCBidXQgaGVscHMgdXMgc29ydCB0aGUgdGFsa3MgaW50byBjYXRlZ29yaWVzLgpgYGB7ciwgZWNobz0gVFJVRSwgbWVzc2FnZT0gRkFMU0UsIHdhcm5pbmc9IEZBTFNFLCBmaWcuaGVpZ2h0PSA3LCBmaWcud2lkdGg9IDYsIGZpZy5hbGlnbj0nY2VudGVyJ30KCnRpdGxlUmVmPC0gZGYgJT4lIHNlbGVjdChkb2N1bWVudD0gSUQsIHRpdGxlLCBZZWFyKSAlPiUgbXV0YXRlKGRvY3VtZW50PSBhcy5jaGFyYWN0ZXIoZG9jdW1lbnQpKQoKZHQwPC0gbW9kICU+JSB0aWR5KCJnYW1tYSIpCmR0MjwtIGR0MCAlPiUgYXJyYW5nZShkb2N1bWVudCwgZGVzYyhnYW1tYSkpICU+JSBncm91cF9ieShkb2N1bWVudCkgJT4lIHNsaWNlKDEpCmR0MjwtIGR0MiAlPiUgbGVmdF9qb2luKHRpdGxlUmVmKSAlPiUgbGVmdF9qb2luKHRvcGljTGFiZWxzKQoKZHQyPC0gZHQyICU+JSB1bmdyb3VwKCkgJT4lIGdyb3VwX2J5KHRvcGljKSAlPiUgbXV0YXRlKE9yZGVyPSBvcmRlcihnYW1tYSkpCgpnZ3Bsb3QoZHQyLCBhZXMoeD0gZ2FtbWEsIHk9IGZhY3RvcihPcmRlciksIGdyb3VwPSBMYWJlbCwgZmlsbD0gTGFiZWwpKSArCiAgZ2VvbV9jb2wod2lkdGg9MSkgKwogIGdlb21fdGV4dChhZXMoeD0gMC4wMSwgeT0gT3JkZXIsIGxhYmVsPSB0aXRsZSksIGhqdXN0PSAwLCBzaXplPSAzLCBmb250ZmFjZT0gImJvbGQiLCBjb2xvcj0gIndoaXRlIikgKwogIHNjYWxlX3hfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDEpLCBleHBhbmQgPSBjKDAsIDApKSArCiAgZmFjZXRfd3JhcCh+TGFiZWwsIHNjYWxlcz0gImZyZWVfeSIsIG5jb2w9IDMpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249ICJub25lIiwKICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGw9ICJncmV5MzAiKSwKICAgICAgICBwYW5lbC5ncmlkID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbD0gImJsYWNrIiksCiAgICAgICAgc3RyaXAudGV4dCA9IGVsZW1lbnRfdGV4dChmYWNlPSAiYm9sZCIsIGNvbG9yPSAid2hpdGUiLCBzaXplPSAxMCksCiAgICAgICAgYXhpcy50ZXh0LnkgPSAgZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIGxhYnModGl0bGU9ICJURUQgVGFsa3MgYnkgVG9waWMgQ2x1c3RlciIsIHg9ICJHYW1tYSIsIHk9TlVMTCkKYGBgCiMjIyBIb3cgaGF2ZSB0YWxrIHRvcGljcyBldm9sdmVkIG92ZXIgdGltZT8KVGhlIGFuYWx5c2lzIGJlbG93IHNob3dzIGhvdyB3ZSBjYW4gZ2F0aGVyIHRoZSBnYW1tYSBzY29yZXMgYWNyb3NzIHRoZSB5ZWFycyB0byBzaG93IHdoaWNoIHRvcGljcyBhcmUgbW9yZSBwcm9taW5lbnQsIGFuZCBob3cgdGhpcyBoYXMgdmFyaWVkIGZyb20geWVhciB0byB5ZWFyLgpgYGB7ciwgZWNobz0gVFJVRSwgbWVzc2FnZT0gRkFMU0UsIHdhcm5pbmc9IEZBTFNFLCBmaWcuaGVpZ2h0PSAyLCBmaWcud2lkdGg9IDUsIGZpZy5hbGlnbj0nY2VudGVyJ30KYnlZZWFyPC0gZHQwICU+JSBsZWZ0X2pvaW4odGl0bGVSZWYpICU+JSBsZWZ0X2pvaW4odG9waWNMYWJlbHMpICU+JQogIGZpbHRlcihZZWFyIT0gMjAyMCkgJT4lIGdyb3VwX2J5KFllYXIsdG9waWMsIExhYmVsKSAlPiUgc3VtbWFyaXNlKFN1bV9nYW1tYT0gc3VtKGdhbW1hKSkKeWVhclRvdGFsczwtIGJ5WWVhciAlPiUgZ3JvdXBfYnkoWWVhcikgJT4lIHN1bW1hcmlzZSh0b3RhbF9nYW1tYT0gc3VtKFN1bV9nYW1tYSkpCgp0cmVuZFJlZjwtIGRhdGEuZnJhbWUoCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICB0b3BpYyA9IGMoNEwsNUwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyTCw2TCw4TCw5TCwxMUwsMTJMLDdMLDEwTCwxM0wsMTRMLDFMLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMTVMLDE2TCwxOUwsM0wsMTdMLDE4TCksCiAgICAgICAgICAgICAgICAgICAgICAgIHRyZW5kID0gYygiRGVjcmVhc2luZyIsIkRlY3JlYXNpbmciLCJJbmNyZWFzaW5nIFdvcm0iLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkluY3JlYXNpbmcgV29ybSIsIkluY3JlYXNpbmcgV29ybSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSW5jcmVhc2luZyBXb3JtIiwiSW5jcmVhc2luZyBXb3JtIiwiTHVtcCIsIkx1bXAiLCJMdW1wIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJMdW1wIiwiTHVtcCIsIlJvY2t5IiwiUm9ja3kiLCJSb2NreSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUm9ja3kiLCJXb3JtIiwiV29ybSIsIldvcm0iKSkKCmJ5WWVhcjwtIGxlZnRfam9pbihieVllYXIsIHllYXJUb3RhbHMpICU+JSBtdXRhdGUocGM9IFN1bV9nYW1tYS90b3RhbF9nYW1tYSkgJT4lIGxlZnRfam9pbih0cmVuZFJlZikKdGhldHJlbmRzPC0gbmFtZXModGFibGUodHJlbmRSZWYkdHJlbmQpKQpmb3IgKGsgaW4gMTpsZW5ndGgodGhldHJlbmRzKSkgewogIHNldHQ8LSBieVllYXIgJT4lIGZpbHRlcih0cmVuZD09dGhldHJlbmRzW2tdKQpnPC0gZ2dwbG90KHNldHQsIGFlcyh4PSBZZWFyLCB5PXBjLCBmaWxsPSBMYWJlbCkpICsKICBnZW9tX2FyZWEoYWVzKGdyb3VwPSBMYWJlbCksIHBvc2l0aW9uPSAic3RhY2siLCBjb2xvcj0gImdyZXkyMCIsIHNpemU9IDAuMywgc2hvdy5sZWdlbmQgPSBUUlVFLCBhbHBoYT0gMC43KSArCiAgbGFicyh0aXRsZT0gcGFzdGUoIlRyZW5kIG92ZXIgdGltZToiLHRoZXRyZW5kc1trXSksIGZpbGw9ICJUb3BpYyIsIHk9ICJQZXJjZW50IG9mIEdhbW1hIGJ5IFllYXIiKSArCiAgdGhlbWVfYncoKSArCiAgeWxpbSgwLCAwLjcpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPSB1bmlxdWUoYnlZZWFyJFllYXIpKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGU9IDkwKSkKcHJpbnQoZykgIAp9CgpgYGAKCgouLi4KLi4uCi4uLgoKCgoKLi4uCgo=