### Load standardpackages
library(tidyverse) # Collection of all the good stuff like dplyr, ggplot2 ect.
library(magrittr) # For extra-piping operators (eg. %<>%)

library(tidygraph)
library(ggraph)
library(igraph)

In this session, you will learn:

  1. What are alternative ways to create network structures.
  2. What are different options to visualize networks and highlight properties.
  3. How to analyse multi-modal networks.

Types of networks

We up to now already talked about different ways how networks can be constructed. Up to now, we mainly focussed on:

  • Interaction between entities
  • Co-occurence

However, network analysis and modelling is also fully consistent with other structures, which are often a natural outcome of supervised or unsupervised ML exercises:

  • Similarities
  • Hirarchies (tree-structures)

Similarity networks

  • Since similarity is a relational property between entities, similarity matrices obviously can be modeled as a network. Lets illustrate that at the classican mtcars example.
mtcars %>% head() 
  • We could first run a PCA to reduce the dimensionality of the numerical data.
cars_pca <- mtcars[,c(1:7,10,11)] %>% 
  drop_na() %>%
  prcomp(center = TRUE , scale = TRUE)
  • Next, we could create a distance matrix (using the dist()) function.
cars_dist <- cars_pca$x %>% dist(method = "euclidean") 

La voila. Such a distance matrix representas a relational structure and can be modelled as a network.

g <- cars_dist %>% 
  as.matrix() %>%
  as_tbl_graph(directed = FALSE) 
g <- g %>% simplify() %>% as_tbl_graph()
g
# A tbl_graph: 32 nodes and 496 edges
#
# An undirected simple graph with 1 component
#
# Node Data: 32 x 1 (active)
  name             
  <chr>            
1 Mazda RX4        
2 Mazda RX4 Wag    
3 Datsun 710       
4 Hornet 4 Drive   
5 Hornet Sportabout
6 Valiant          
# … with 26 more rows
#
# Edge Data: 496 x 3
   from    to weight
  <int> <int>  <dbl>
1     1     2  0.408
2     1     3  2.57 
3     1     4  3.38 
# … with 493 more rows
  • Since the network is based on a distance matrix, we would like to reverse that to get edges representing similarity.
  • Since similarity structures are usually fully connected networks, we probably also want to create some sparsity by deleting lower quantile edge weights.
g <- g %E>%
  mutate(weight = max(weight) - weight) %>%
  filter(weight >= weight %>% quantile(0.75)) %N>%
  filter(!node_is_isolated()) %>%
  mutate(community = group_louvain(weights = weight) %>% factor())

Lets take a look!

set.seed(1337)
g %>% ggraph(layout = "nicely") + 
  geom_node_point(aes(col = community, size = centrality_degree(weights = weight))) + 
  geom_edge_link(aes(width = weight), alpha = 0.25) +
  scale_edge_width(range = c(0.1, 2)) + 
  geom_node_text(aes(label = name, filter = percent_rank(centrality_degree(weights = weight)) > 0.5), repel = TRUE) +
  theme_graph() + 
  theme(legend.position = 'bottom')

Hierarchy (tree) networks

Hirarchical structures are obviously also relational. The difference is, that the connectivity structure tends to be constraint to other levels.

create_tree(20, 3) %>% 
    mutate(leaf = node_is_leaf(), root = node_is_root()) %>% 
    ggraph(layout = 'tree') +
    geom_edge_diagonal() +
    geom_node_point(aes(filter = leaf), colour = 'forestgreen', size = 10) +
    geom_node_point(aes(filter = root), colour = 'firebrick', size = 10) +
    theme_graph()

  • In addition to real life examples such as organigrams, evolutionary trees etc., many ML models result in tree-structures (eg. decision trees).
  • We will at our car example execute a hierarchical clustering, which leads to a tree structure (visualized in the dendogram).
cars_hc <- cars_dist %>%
  hclust(method = "ward.D2")

Again, this structure can be directly transfered to a graph object.

g <- cars_hc %>% as_tbl_graph()
g
# A tbl_graph: 63 nodes and 62 edges
#
# A rooted tree
#
# Node Data: 63 x 4 (active)
  height leaf  label           members
   <dbl> <lgl> <chr>             <int>
1   0    TRUE  "Porsche 914-2"       1
2   0    TRUE  "Lotus Europa"        1
3   1.62 FALSE ""                    2
4   0    TRUE  "Honda Civic"         1
5   0    TRUE  "Fiat X1-9"           1
6   0    TRUE  "Fiat 128"            1
# … with 57 more rows
#
# Edge Data: 62 x 2
   from    to
  <int> <int>
1     3     1
2     3     2
3     8     6
# … with 59 more rows
g %>% ggraph(layout = 'dendrogram') + 
  geom_edge_diagonal(aes(col = .N()$height[from])) +
  geom_node_point(aes(col =height)) +
  geom_node_text(aes(filter = leaf, label = label), angle=90, hjust=1, nudge_y=-0.1) + 
  theme_graph() + 
  theme(legend.position = 'none')

  ylim(-0.6, NA) 
<ScaleContinuousPosition>
 Range:  
 Limits:    0 --    1

Multi-Modal Networks

Intuition

  • Now its time to talk about an interesting type of networks, multi-modal. This means, a network has several “modes”, meaning connects entities on different conceptual levels. T
  • he most common one is a 2-mode (or bipartite) network.
  • Examples could be an Author \(\rightarrow\) Paper, Inventor \(\rightarrow\) Patent, Member \(\rightarrow\) Club network.
  • Here, the elements in the different modes represent different things.
  • We can alalyse them in seperation (and sometimes we should), but often its helpful to “project”" them onto one mode.
  • Here, we create a node in one mode by joint association with another mode.

## Demonstartion

  • While that sounds simple, it can be a very powerful technique, as I will demonstrate now.
g
# A tbl_graph: 14 nodes and 15 edges
#
# A directed acyclic simple graph with 3 components
#
# Edge Data: 15 x 2 (active)
   from    to
  <int> <int>
1     1     7
2     1    11
3     1    13
4     1    14
5     2     8
6     2    10
# … with 9 more rows
#
# Node Data: 14 x 1
  type 
  <lgl>
1 FALSE
2 FALSE
3 FALSE
# … with 11 more rows
people <- c('Jesper', 'Pernille', 'Morten', 'Lise', 'Christian', 'Mette', 'Casper', 'Dorte', 'Jacob', 'Helle')
places <- c('Yoga House', 'Crossfit', 'Jazz Club', 'Jomfru Anne Gade')
g <- g %N>%
  mutate(name = c(places, people))
g
# A tbl_graph: 14 nodes and 15 edges
#
# A directed acyclic simple graph with 3 components
#
# Node Data: 14 x 2 (active)
  type  name            
  <lgl> <chr>           
1 FALSE Yoga House      
2 FALSE Crossfit        
3 FALSE Jazz Club       
4 FALSE Jomfru Anne Gade
5 TRUE  Jesper          
6 TRUE  Pernille        
# … with 8 more rows
#
# Edge Data: 15 x 2
   from    to
  <int> <int>
1     1     7
2     1    11
3     1    13
# … with 12 more rows
set.seed(1337)
p0 <- g %>% ggraph("bipartite") + 
  geom_edge_link(alpha = 0.25) + 
  geom_node_point(aes(col = type, size = centrality_degree(mode = 'all'))) + 
  geom_node_text(aes(label = name), repel = TRUE) + 
  theme_graph() +
  theme(legend.position = 'none') + 
  labs(title = '2-mode network places-people')

p0

Creating bipartite projections

Within the fraph object

  • Having a 2-mode network, we can use the igraph function bipartite_projection to create a 2x 1-mode network out of it.
g_projected <- g %>% bipartite_projection()
  • We now will have a 1-mode network between people as well as one between places.
g_projected
$proj1
IGRAPH 8864ec5 UNW- 4 5 -- Full bipartite graph
+ attr: name (g/c), name (v/c), weight (e/n)
+ edges from 8864ec5 (vertex names):
[1] Yoga House--Jomfru Anne Gade Yoga House--Crossfit         Yoga House--Jazz Club       
[4] Crossfit  --Jazz Club        Crossfit  --Jomfru Anne Gade

$proj2
IGRAPH 357e4d0 UNW- 10 19 -- Full bipartite graph
+ attr: name (g/c), name (v/c), weight (e/n)
+ edges from 357e4d0 (vertex names):
 [1] Pernille--Mette  Pernille--Casper Pernille--Dorte  Pernille--Jacob  Morten  --Casper Morten  --Jacob 
 [7] Morten  --Helle  Lise    --Mette  Lise    --Dorte  Lise    --Jacob  Lise    --Helle  Mette   --Dorte 
[13] Mette   --Jacob  Mette   --Casper Casper  --Jacob  Casper  --Helle  Casper  --Dorte  Dorte   --Jacob 
[19] Jacob   --Helle 
g_places <- g_projected[['proj1']] %>% as_tbl_graph(directed = FALSE)
g_people <- g_projected[['proj2']] %>% as_tbl_graph(directed = FALSE)
  • Lets take a look:
set.seed(1337)
library(patchwork)

p1 <- g_places %>% ggraph(layout = "nicely") + 
  geom_node_point(aes(size = centrality_degree(weights = weight)), col = 'red') + 
  geom_edge_link(aes(width = weight), alpha = 0.25) +
  scale_edge_width(range = c(0.1, 2)) + 
  geom_node_text(aes(label = name), repel = TRUE) +
  theme_graph() + 
  theme(legend.position = 'none') + 
  labs(title = '1-mode network places')

p2 <- g_people %>% ggraph(layout = "nicely") + 
  geom_node_point(aes(size = centrality_degree(weights = weight)), col = 'skyblue2') + 
  geom_edge_link(aes(width = weight), alpha = 0.25) +
  scale_edge_width(range = c(0.1, 2)) + 
  geom_node_text(aes(label = name), repel = TRUE) +
  theme_graph() + 
  theme(legend.position = 'none') + 
  labs(title = '1-mode network people')

p0 / (p1 + p2)

  • Alright, but lets assume we have a 2-mode edgelist to start with… what possibilities do we have then?
el_2m <- g %E>% 
  mutate(from_name = .N()$name[from],
         to_name = .N()$name[to]) %>%
  as.tibble() %>%
  select(to_name, from_name) %>%
  rename(from = to_name, to = from_name)
el_2m
  • Such an edgelist could obviously be loaded into a graph object the usual way.
  • We just have to assign types then
g_2m <- el_2m %>% as_tbl_graph(directed = TRUE)  %N>%
  mutate(type = name %in% (el_2m %>% pull(from)))

Via matrix nultiplication

  • We can also do the projection outside of the graph and first create a 2-mode matrix.
  • This can easily be done by crosstabulating the edgelist.
mat_2m <- el_2m %>%
  table() %>% 
  as.matrix()
mat_2m
          to
from       Crossfit Jazz Club Jomfru Anne Gade Yoga House
  Casper          0         0                1          1
  Dorte           1         0                1          0
  Helle           0         1                0          1
  Jacob           1         0                1          1
  Lise            1         1                0          0
  Mette           1         0                1          0
  Morten          0         0                0          1
  Pernille        0         0                1          0
  • Again, sparse matrices are usually more efficient.
library(Matrix)
mat_2m %<>% Matrix(sparse = TRUE)

mat_2m
8 x 4 sparse Matrix of class "dgCMatrix"
          to
from       Crossfit Jazz Club Jomfru Anne Gade Yoga House
  Casper          .         .                1          1
  Dorte           1         .                1          .
  Helle           .         1                .          1
  Jacob           1         .                1          1
  Lise            1         1                .          .
  Mette           1         .                1          .
  Morten          .         .                .          1
  Pernille        .         .                1          .
  • Matrix algebra can help to do the 1-mode projection directly in the matrix
  • Taking the dotproduct of the matrix and its transposed form will result in the 1-mode projection of mode 1 (m %*% t(m))
mat_people <- mat_2m %*% t(mat_2m)
mat_people
8 x 8 sparse Matrix of class "dgCMatrix"
         Casper Dorte Helle Jacob Lise Mette Morten Pernille
Casper        2     1     1     2    .     1      1        1
Dorte         1     2     .     2    1     2      .        1
Helle         1     .     2     1    1     .      1        .
Jacob         2     2     1     3    1     2      1        1
Lise          .     1     1     1    2     1      .        .
Mette         1     2     .     2    1     2      .        1
Morten        1     .     1     1    .     .      1        .
Pernille      1     1     .     1    .     1      .        1
  • Taking the dotproduct of the transposed matrix and its original form will result in the 1-mode projection of mode 1 (t(m) %*% m)
mat_places <- t(mat_2m) %*% mat_2m
mat_places
4 x 4 sparse Matrix of class "dgCMatrix"
                 Crossfit Jazz Club Jomfru Anne Gade Yoga House
Crossfit                4         1                3          1
Jazz Club               1         2                .          1
Jomfru Anne Gade        3         .                5          2
Yoga House              1         1                2          4
  • Note this is still very inefficient, since the matrix is first created in full, and then transformed to a sparse one.
  • Directly starting with a sparse matrix makes the process much more efficient
  • That makes a huge difference for large graphs
  • I here provide you an efficient function to use
## Helper function
el_to_sparse_matrix <- function(data, # the edgelist
                                mode_1, # which variable indicates mode 1
                                mode_2, # which variable indicates mode 2
                                projection = 'none' # If a pojection should be done, possible is 'none', 'mode1', 'mode2' 
                                ){
  
  # Define inputs
  i_input <- data %>% pull({{mode_1}}) 
  j_input <- data %>% pull({{mode_2}}) 
  
  require(Matrix)
  mat <- spMatrix(nrow = i_input %>% n_distinct(),
                  ncol = j_input %>% n_distinct(),
                  i = i_input %>% factor() %>% as.numeric(),
                  j = j_input %>% factor() %>% as.numeric(),
                  x = rep(1, i_input %>% length() ) )
  
  row.names(mat) <- i_input %>% factor() %>% levels()
  colnames(mat) <- j_input %>% factor() %>% levels()
  
  # Projection if necessary
  if(projection == 'mode1'){mat %<>% tcrossprod()}
  if(projection == 'mode2'){mat %<>% crossprod()}  
    
  return(mat)
}
mat_people <- el_2m %>% el_to_sparse_matrix(from, to, projection = 'mode1')
mat_places <- el_2m %>% el_to_sparse_matrix(from, to, projection = 'mode2')
mat_people
8 x 8 sparse Matrix of class "dsCMatrix"
         Casper Dorte Helle Jacob Lise Mette Morten Pernille
Casper        2     1     1     2    .     1      1        1
Dorte         1     2     .     2    1     2      .        1
Helle         1     .     2     1    1     .      1        .
Jacob         2     2     1     3    1     2      1        1
Lise          .     1     1     1    2     1      .        .
Mette         1     2     .     2    1     2      .        1
Morten        1     .     1     1    .     .      1        .
Pernille      1     1     .     1    .     1      .        1
mat_places
4 x 4 sparse Matrix of class "dsCMatrix"
                 Crossfit Jazz Club Jomfru Anne Gade Yoga House
Crossfit                4         1                3          1
Jazz Club               1         2                .          1
Jomfru Anne Gade        3         .                5          2
Yoga House              1         1                2          4

Via Joins

  • FInally, the easiest way
el_people <- el_2m %>% left_join(el_2m, by = 'to') %>%
  select(-to) %>%
  rename(from = from.x, to = from.y) %>%
  filter(from != to) %>%
  count(from, to, name = 'weight')
el_people
el_places <- el_2m %>% left_join(el_2m, by = 'from') %>%
  select(-from) %>%
  rename(from = to.x, to = to.y) %>%
  filter(from != to) %>%
  count(from, to, name = 'weight')
el_places

Case study: Bibliographic networks

Basics

Lets talk about bibliographic networks. In short, that are networks between documents which cite each others. That can be (commonly) academic publications, but also patents or policy reports. Conceptually, we can see them as 2 mode networks, between articles and their reference. That helps us to apply some interesting metrics, such as:

  • direct citations
  • Bibliographic coupling
  • Co–citations

Interestingly, different projections of this 2-mode network give the whole resulting 1-mode network a different meaning.

  • We will here do a brief bibliometric network analysis.

  • While there exist specialized packages to do it more conveniently (eg. bibliometrix), we will for mximum clarity construct everything somewhat by hand.

  • I will illustrate more in detail in the following. The example is based on some own work, where i here in very simple way recreate some parts of the analysis.

  • Rakas, M., & Hain, D. S. (2019). The state of innovation system research: What happens beneath the surface?. Research Policy, 45 (9). DOI: https://doi.org/10.1016/j.respol.2019.04.011

The Data

  • We will use bibliometrix data on articles from Scopus on recent publications containing the term network analysis in their title, abstract, or keywords.
  • To do so, we first use the following search string: TITLE-ABS-KEY ( "network analysis" ) AND ( LIMIT-TO ( DOCTYPE , "ar" ) OR LIMIT-TO ( DOCTYPE , "cp" ) ) AND ( LIMIT-TO ( LANGUAGE , "English" ) ) AND ( LIMIT-TO ( SRCTYPE , "j" ) OR LIMIT-TO ( SRCTYPE , "p" ) ) AND ( LIMIT-TO ( PUBYEAR , 2021 ) OR LIMIT-TO ( PUBYEAR , 2020 ) OR LIMIT-TO ( PUBYEAR , 2019 ) OR LIMIT-TO ( PUBYEAR , 2018 ) OR LIMIT-TO ( PUBYEAR , 2017 ) OR LIMIT-TO ( PUBYEAR , 2016 ) OR LIMIT-TO ( PUBYEAR , 2015 ) )
  • We sort the results by citations, and download the 2k most cited ones as csv. We select al possible fields to download.
rm(list=ls())
data <- read_csv('https://github.com/SDS-AAU/SDS-master/raw/master/00_data/networks_bibliometrics/biblio_nw.csv')
data %>%
  glimpse()
Rows: 2,000
Columns: 43
$ Authors                     <chr> "Wang D., Cui P., Zhu W.", "Thorsson V., Gibbs D.L., Brown S.D., Wolf D.…
$ `Author(s) ID`              <chr> "56780729900;34568700100;7404232311;", "6602742809;57189045500;571991919…
$ Title                       <chr> "Structural deep network embedding", "The Immune Landscape of Cancer", "…
$ Year                        <dbl> 2016, 2018, 2015, 2015, 2015, 2015, 2016, 2015, 2015, 2015, 2015, 2017, …
$ `Source title`              <chr> "Proceedings of the ACM SIGKDD International Conference on Knowledge Dis…
$ Volume                      <chr> "13-17-August-2016", "48", "163", "15", "2015-January", "162", "428", "4…
$ Issue                       <chr> NA, "4", "3", "15", NA, "2", "4", "2", NA, "11", "1", "6343", "7590", "J…
$ `Art. No.`                  <chr> NA, NA, NA, NA, NA, "8281", NA, NA, "bav028", NA, NA, NA, NA, "386", NA,…
$ `Page start`                <chr> "1225", "812", "712", "2597", "2111", "375", "688", "106", NA, "2490", "…
$ `Page end`                  <chr> "1234", "830.e14", "723", "2601", "2117", "390", "692", "114", NA, "2502…
$ `Page count`                <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 16, NA, NA, NA, NA, …
$ `Cited by`                  <dbl> 781, 607, 507, 468, 418, 404, 378, 377, 363, 356, 354, 347, 347, 328, 32…
$ DOI                         <chr> "10.1145/2939672.2939753", "10.1016/j.immuni.2018.03.023", "10.1016/j.ce…
$ Link                        <chr> "https://www.scopus.com/inward/record.uri?eid=2-s2.0-84985034266&doi=10.…
$ Affiliations                <chr> "Tsinghua National Laboratory for Information Science and Technology, De…
$ `Authors with affiliations` <chr> "Wang, D., Tsinghua National Laboratory for Information Science and Tech…
$ Abstract                    <chr> "Network embedding is an important method to learn low-dimensional repre…
$ `Author Keywords`           <chr> "Deep learning; Network analysis; Network embedding", "cancer genomics; …
$ `Index Keywords`            <chr> "Classification (of information); Data mining; Electric network analysis…
$ `Funding Details`           <chr> "National Key Basic Research Program For Youth: 2015CB352300\n\nNational…
$ `Funding Text 1`            <chr> "This work was supported by National Program on Key Basic Research Proje…
$ `Funding Text 2`            <chr> NA, "Michael Seiler, Peter G. Smith, Ping Zhu, Silvia Buonamici, and Lih…
$ `Funding Text 3`            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ `Funding Text 4`            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ `Funding Text 5`            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ `Funding Text 6`            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ `Funding Text 7`            <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ References                  <chr> "Belkin, M., Niyogi, P., Laplacian eigenmaps for dimensionality reductio…
$ `Correspondence Address`    <chr> NA, NA, "Hyman, A.A.; Max Planck Institute of Molecular Cell Biology and…
$ Editors                     <chr> NA, NA, NA, NA, "Wooldridge M.Yang Q.", NA, NA, NA, NA, NA, NA, NA, NA, …
$ Sponsors                    <chr> "ACM SIGKDD;ACM SIGMOD", NA, NA, NA, "Alibaba.com;Department of Computer…
$ Publisher                   <chr> "Association for Computing Machinery", "Cell Press", "Cell Press", "Wile…
$ `Conference name`           <chr> "22nd ACM SIGKDD International Conference on Knowledge Discovery and Dat…
$ `Conference date`           <chr> "13 August 2016 through 17 August 2016", NA, NA, NA, "25 July 2015 throu…
$ `Conference location`       <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ `Conference code`           <dbl> 123286, NA, NA, NA, 116754, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ ISSN                        <chr> NA, "10747613", "00928674", "16159853", "10450823", "00928674", "0022283…
$ ISBN                        <chr> "9781450342322", NA, NA, NA, "9781577357384", NA, NA, NA, NA, NA, NA, NA…
$ CODEN                       <chr> NA, "IUNIE", "CELLB", "PROTC", NA, "CELLB", "JMOBA", "NGENE", NA, NA, "N…
$ `Abbreviated Source Title`  <chr> "Proc. ACM SIGKDD Int. Conf. Knowl. Discov. Data Min.", "Immunity", "Cel…
$ `Document Type`             <chr> "Conference Paper", "Article", "Article", "Article", "Conference Paper",…
$ Source                      <chr> "Scopus", "Scopus", "Scopus", "Scopus", "Scopus", "Scopus", "Scopus", "S…
$ EID                         <chr> "2-s2.0-84985034266", "2-s2.0-85044934017", "2-s2.0-84948067587", "2-s2.…

Preprocessing

data %<>%
  select(EID, Authors, `Author(s) ID`, Title, `Abbreviated Source Title`, Year, Affiliations, `Author Keywords`, `Cited by`, Abstract, References) %>%
  rename(author = Authors,
         author_id = `Author(s) ID`,
         title = Title,
         journal = `Abbreviated Source Title`,
         year = Year,
         affiliation = Affiliations,
         keywords = `Author Keywords`,
         citations = `Cited by`,
         Abstract = Abstract,
         references = References)
data[2, 'author']
data[2, 'author_id']
data[2, 'affiliation']
data[2, "keywords"]
data[2, "references"]
data %>% select(title, author, citations) %>% 
  unnest(author) %>%
  head()
data %>% select(title, author, citations) %>% 
  unnest(author) %>%
  group_by(author) %>%
  summarise(n = n(),
            citations = citations %>% sum(na.rm = TRUE)) %>%
  arrange(desc(citations)) %>%
  head(10)

Bipartite Network Generation

I will now transfer them to an article \(\rightarrow\) reference edgelist.

el_2m <- data %>% select(EID, references) %>% unnest(references) 
el_2m %>% head()

Bibliographic Coupling

Create the graph

  • We now need to create a projection of the 2-mode matrix to one of the modes.
  • We could do that more efficiently via using the sparse matrix calculation I did before, but for maximum clarity lets do it via a join.
  • We will first create the article to article network.
el_bib <- el_2m %>% left_join(el_2m, by = 'references') %>%
  rename(from = EID.x, 
         to = EID.y) %>%
  select(from, to) %>%
  filter(from != to) %>%
  count(from, to, name = 'weight')
el_bib %>% head()
el_bib %>% 
  ggplot(aes(x = weight)) +
  geom_histogram()

el_bib %<>%
  filter(weight >=2)
el_bib %>% 
  ggplot(aes(x = weight)) +
  geom_histogram()

  • Ok, great, we now can create a graph
g_bib <- el_bib %>% as_tbl_graph(directed = FALSE) %>% 
  igraph::simplify() %>%
  as_tbl_graph(directed = FALSE) 
g_bib
# A tbl_graph: 684 nodes and 1046 edges
#
# An undirected simple graph with 109 components
#
# Node Data: 684 x 1 (active)
  name              
  <chr>             
1 2-s2.0-84909633782
2 2-s2.0-84910060076
3 2-s2.0-84912573275
4 2-s2.0-84918503641
5 2-s2.0-84918774371
6 2-s2.0-84919882800
# … with 678 more rows
#
# Edge Data: 1,046 x 3
   from    to weight
  <int> <int>  <dbl>
1     1    94      4
2     1   162      6
3     1   255     14
# … with 1,043 more rows
g_bib <- g_bib %N>% 
  filter(!node_is_isolated()) %N>% 
  filter(percent_rank(centrality_degree(weights = weight)) >= 0.5)
  • A first obvious thing to do would be a community detection
g_bib <- g_bib %N>%
  mutate(com = group_louvain(weights = weight)) 
  • Now, we can also create community internal statistics, such as the within-community degree.
  • To do so, we use the powerful morph() functions of tidygraph, which basically let you apply group_by style operations on graph structures, where graph calculations are execited on subgraphs.
  • That let you e.g. contract nodes, work on the linegraph representation, split communities to separate graphs etc.
g_bib <- g_bib %N>%
  morph(to_split, com) %>% 
    mutate(cent_dgr_int = centrality_degree(weights = weight)) %>%
    mutate(com_center = cent_dgr_int == max(cent_dgr_int)) %>%
  unmorph() 
g_bib %N>%
  as_tibble() %>%
  count(com, sort = TRUE)
  • Ok, seems like there are too many communities to do something meaningful.
  • Lets restrict it to only only communities with more than 20 members.
g_bib <- g_bib %N>%
  add_count(com, name = 'com_n') %>%
  mutate(com = ifelse(com_n >= 20, com, NA) ) %>%
  select(-com_n)  
g_bib <- g_bib %N>%
  left_join(data %>% select(EID, title, journal, year, citations), by = c('name' = 'EID')) %>%
  mutate(title = title %>% str_trunc(30))
  • Lets take a look at the network.
set.seed(1337)
g_bib %>%
  ggraph(layout = 'graphopt') + 
  geom_edge_link(aes(width = weight,
                     color = .N()$com[from] %>% as.factor()), # Notice that
                alpha = 0.5, 
                show.legend = FALSE) +
      scale_edge_width(range = c(0.5, 2)) + 
  geom_node_point(aes(color = com %>% as.factor(),
                      size = centrality_degree(weight = weight), 
                      alpha = citations)) +
  geom_node_text(aes(label = title, filter = com_center == TRUE), repel = TRUE) +
  theme_graph()   + 
  theme(legend.position = 'bottom') + 
  labs(title = 'Bibliographic Coupling Network',
       subtitle = 'Network Analysis 2015-2020',
       color = 'Community',
       size = 'Degree',
       alpha = 'Citations') 

data %>%
  select(EID, author, year, title, journal, citations) %>%
  inner_join(g_bib %N>% as_tibble() %>% select(name, com, cent_dgr_int), by = c('EID' = 'name')) %>%
  group_by(com) %>%
    arrange(desc(cent_dgr_int)) %>%
    slice(1:10) %>%
  ungroup() %>%
  select(com, title, cent_dgr_int, citations) %>%
  mutate(title = title %>% str_trunc(75))

Co-Citation Analysis

  • We can now do exactly the same for the reference (=co-citation) network

Create the graph

el_cit %>% 
  ggplot(aes(x = weight)) +
  geom_histogram()

g_cit <- el_cit %>% as_tbl_graph(directed = FALSE) %>% 
  igraph::simplify() %>%
  as_tbl_graph(directed = FALSE)  %N>% 
  filter(!node_is_isolated()) %N>% 
  filter(percent_rank(centrality_degree(weights = weight)) >= 0.5) %N>%
  mutate(com = group_louvain(weights = weight)) %N>%
  morph(to_split, com) %>% 
    mutate(cent_dgr_int = centrality_degree(weights = weight)) %>%
    mutate(com_center = cent_dgr_int == max(cent_dgr_int)) %>%
  unmorph() 
g_cit %N>%
  as_tibble() %>%
  count(com, sort = TRUE)
  • Ok, seems like there are too many communities to do something meaningful.
  • Lets restrict it to only only communities with more than 20 members.
g_cit <- g_cit %N>%
  add_count(com, name = 'com_n') %>%
  mutate(com = ifelse(com_n >= 10, com, NA) ) %>%
  select(-com_n)  

Lets take a look at the network.

set.seed(1337)
g_cit %N>%
  mutate(name = name %>% str_trunc(75)) %>%
  ggraph(layout = 'graphopt') + 
  geom_edge_link(aes(width = weight,
                     color = .N()$com[from] %>% as.factor()), # Notice that
                alpha = 0.5, 
                show.legend = FALSE) +
      scale_edge_width(range = c(0.5, 2)) + 
  geom_node_point(aes(color = com %>% as.factor(),
                      size = centrality_degree(weight = weight), 
                      alpha = cent_dgr_int)) +
  geom_node_text(aes(label = name, filter = com_center == TRUE & percent_rank(cent_dgr_int) > 0.80 ), repel = TRUE) +
  theme_graph()   + 
  theme(legend.position = 'bottom') + 
  labs(title = 'Bibliographic Coupling Network',
       subtitle = 'Network Analysis 2015-2020',
       color = 'Community',
       size = 'Degree',
       alpha = 'Citations') 

g_cit %N>%
  as_tibble() %>%
  group_by(com) %>%
    arrange(desc(cent_dgr_int)) %>%
    slice(1:10) %>%
  ungroup() %>%
  mutate(name = name %>% str_trunc(75)) %>%
  select(com, name, cent_dgr_int)

Joint analysis

el_joint <- el_2m %>%
  inner_join(g_bib %N>% as_tibble() %>% select(name, com) %>% drop_na(), by = c('EID' = 'name')) %>%
  inner_join(g_cit %N>% as_tibble() %>% select(name, com) %>% drop_na(), by = c('references' = 'name')) %>%
  rename(from = com.x, to = com.y) %>%
  count(from, to, name = 'weight')
el_joint
g_joint <- el_joint %>% as_tbl_graph(directed = TRUE) 
g_joint <- g_joint %N>%
  mutate(type = name %>% str_detect('bib'))
g_joint %>% ggraph("bipartite") + 
  geom_edge_link(alpha = 0.25) + 
  geom_node_point(aes(col = type, size = centrality_degree(mode = 'all'))) + 
  geom_node_text(aes(label = name), repel = TRUE) + 
  theme_graph() +
  theme(legend.position = 'none')

Endnotes

Complementary exercises

Please do Exercise 1 in the corresponding section on Github. This time you are about to do your own bibliographic analysis!

References

Paper mentioned in the text

Other own work dealing with 2-mode networks

  • Hain, Daniel S., and Roman Jurowetzki. “Incremental by Design? On the Role of Incumbents in Technology Niches.” In Foundations of Economic Change, pp. 299-332. Springer, Cham, 2017.
  • Hain, D., Buchmann, T., Kudic, M., & Müller, M. (2018). Endogenous dynamics of innovation networks in the German automotive industry: analysing structural network evolution using a stochastic actor-oriented approach. International Journal of Computational Economics and Econometrics, 8(3-4), 325-344.
  • Jurowetzki, Roman, and Daniel S. Hain. “Mapping the (r-) evolution of technological fields–a semantic network approach.” Social Informatics, pp. 359-383. Springer, Cham, 2014.

Packages & Ecosystem

You can find more info about:

Other Sources

  • An example notebook where I use the ideas presented hee for a simple analysis of technological relatedness can be found here

Session info

sessionInfo()
LS0tCnRpdGxlOiAnQWR2YW5jZWQgTmV0d29yayBBbmFseXNpczogQmlwYXJ0aXRlICgyLW1vZGUpIG5ldHdwb2tzOiBBcHBsaWNhdGlvbiAoUiknCmF1dGhvcjogIkRhbmllbCBTLiBIYWluIChkc2hAYnVzaW5lc3MuYWF1LmRrKSIKZGF0ZTogIlVwZGF0ZWQgYHIgZm9ybWF0KFN5cy50aW1lKCksICclQiAlZCwgJVknKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogZmFsc2UKICAgIHRoZW1lOiBmbGF0bHkKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KIyMjIEdlbmVyaWMgcHJlYW1ibGUKcm0obGlzdD1scygpKQpTeXMuc2V0ZW52KExBTkcgPSAiZW4iKSAjIEZvciBlbmdsaXNoIGxhbmd1YWdlCm9wdGlvbnMoc2NpcGVuID0gNSkgIyBUbyBkZWFjdGl2YXRlIGFubm95aW5nIHNjaWVudGlmaWMgbnVtYmVyIG5vdGF0aW9uCgojIyMgS25pdHIgb3B0aW9ucwpsaWJyYXJ5KGtuaXRyKSAjIEZvciBkaXNwbGF5IG9mIHRoZSBtYXJrZG93bgprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZz1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZT1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgY29tbWVudD1GQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgIGZpZy5hbGlnbj0iY2VudGVyIgogICAgICAgICAgICAgICAgICAgICApCmBgYAoKYGBge3J9CiMjIyBMb2FkIHN0YW5kYXJkcGFja2FnZXMKbGlicmFyeSh0aWR5dmVyc2UpICMgQ29sbGVjdGlvbiBvZiBhbGwgdGhlIGdvb2Qgc3R1ZmYgbGlrZSBkcGx5ciwgZ2dwbG90MiBlY3QuCmxpYnJhcnkobWFncml0dHIpICMgRm9yIGV4dHJhLXBpcGluZyBvcGVyYXRvcnMgKGVnLiAlPD4lKQoKbGlicmFyeSh0aWR5Z3JhcGgpCmxpYnJhcnkoZ2dyYXBoKQpsaWJyYXJ5KGlncmFwaCkKYGBgCgoKSW4gdGhpcyBzZXNzaW9uLCB5b3Ugd2lsbCBsZWFybjoKCjEuIFdoYXQgYXJlIGFsdGVybmF0aXZlIHdheXMgdG8gY3JlYXRlIG5ldHdvcmsgc3RydWN0dXJlcy4KMi4gV2hhdCBhcmUgZGlmZmVyZW50IG9wdGlvbnMgdG8gdmlzdWFsaXplIG5ldHdvcmtzIGFuZCBoaWdobGlnaHQgcHJvcGVydGllcy4KMy4gSG93IHRvIGFuYWx5c2UgbXVsdGktbW9kYWwgbmV0d29ya3MuCgojIFR5cGVzIG9mIG5ldHdvcmtzCgpXZSB1cCB0byBub3cgYWxyZWFkeSB0YWxrZWQgYWJvdXQgZGlmZmVyZW50IHdheXMgaG93IG5ldHdvcmtzIGNhbiBiZSBjb25zdHJ1Y3RlZC4gVXAgdG8gbm93LCB3ZSBtYWlubHkgZm9jdXNzZWQgb246CgoqIEludGVyYWN0aW9uIGJldHdlZW4gZW50aXRpZXMKKiBDby1vY2N1cmVuY2UKCkhvd2V2ZXIsIG5ldHdvcmsgYW5hbHlzaXMgYW5kIG1vZGVsbGluZyBpcyBhbHNvIGZ1bGx5IGNvbnNpc3RlbnQgd2l0aCBvdGhlciBzdHJ1Y3R1cmVzLCB3aGljaCBhcmUgb2Z0ZW4gYSBuYXR1cmFsIG91dGNvbWUgb2Ygc3VwZXJ2aXNlZCBvciB1bnN1cGVydmlzZWQgTUwgZXhlcmNpc2VzOgoKKiBTaW1pbGFyaXRpZXMKKiBIaXJhcmNoaWVzICh0cmVlLXN0cnVjdHVyZXMpCgojIyBTaW1pbGFyaXR5IG5ldHdvcmtzCgoqIFNpbmNlIHNpbWlsYXJpdHkgaXMgYSByZWxhdGlvbmFsIHByb3BlcnR5IGJldHdlZW4gZW50aXRpZXMsIHNpbWlsYXJpdHkgbWF0cmljZXMgb2J2aW91c2x5IGNhbiBiZSBtb2RlbGVkIGFzIGEgbmV0d29yay4gTGV0cyBpbGx1c3RyYXRlIHRoYXQgYXQgdGhlIGNsYXNzaWNhbiBgbXRjYXJzYCBleGFtcGxlLgoKYGBge3J9Cm10Y2FycyAlPiUgaGVhZCgpIApgYGAKCiogV2UgY291bGQgZmlyc3QgcnVuIGEgUENBIHRvIHJlZHVjZSB0aGUgZGltZW5zaW9uYWxpdHkgb2YgdGhlIG51bWVyaWNhbCBkYXRhLgoKYGBge3J9CmNhcnNfcGNhIDwtIG10Y2Fyc1ssYygxOjcsMTAsMTEpXSAlPiUgCiAgZHJvcF9uYSgpICU+JQogIHByY29tcChjZW50ZXIgPSBUUlVFICwgc2NhbGUgPSBUUlVFKQpgYGAKCiogTmV4dCwgd2UgY291bGQgY3JlYXRlIGEgZGlzdGFuY2UgbWF0cml4ICh1c2luZyB0aGUgYGRpc3QoKWApIGZ1bmN0aW9uLgoKYGBge3J9CmNhcnNfZGlzdCA8LSBjYXJzX3BjYSR4ICU+JSBkaXN0KG1ldGhvZCA9ICJldWNsaWRlYW4iKSAKYGBgCgpMYSB2b2lsYS4gU3VjaCBhIGRpc3RhbmNlIG1hdHJpeCByZXByZXNlbnRhcyBhIHJlbGF0aW9uYWwgc3RydWN0dXJlIGFuZCBjYW4gYmUgbW9kZWxsZWQgYXMgYSBuZXR3b3JrLgoKYGBge3J9CmcgPC0gY2Fyc19kaXN0ICU+JSAKICBhcy5tYXRyaXgoKSAlPiUKICBhc190YmxfZ3JhcGgoZGlyZWN0ZWQgPSBGQUxTRSkgCmBgYAoKYGBge3J9CmcgPC0gZyAlPiUgc2ltcGxpZnkoKSAlPiUgYXNfdGJsX2dyYXBoKCkKYGBgCgpgYGB7cn0KZwpgYGAKCiogU2luY2UgdGhlIG5ldHdvcmsgaXMgYmFzZWQgb24gYSBkaXN0YW5jZSBtYXRyaXgsIHdlIHdvdWxkIGxpa2UgdG8gcmV2ZXJzZSB0aGF0IHRvIGdldCBlZGdlcyByZXByZXNlbnRpbmcgc2ltaWxhcml0eS4gCiogU2luY2Ugc2ltaWxhcml0eSBzdHJ1Y3R1cmVzIGFyZSB1c3VhbGx5IGZ1bGx5IGNvbm5lY3RlZCBuZXR3b3Jrcywgd2UgcHJvYmFibHkgYWxzbyB3YW50IHRvIGNyZWF0ZSBzb21lIHNwYXJzaXR5IGJ5IGRlbGV0aW5nIGxvd2VyIHF1YW50aWxlIGVkZ2Ugd2VpZ2h0cy4KCmBgYHtyfQpnIDwtIGcgJUU+JQogIG11dGF0ZSh3ZWlnaHQgPSBtYXgod2VpZ2h0KSAtIHdlaWdodCkgJT4lCiAgZmlsdGVyKHdlaWdodCA+PSB3ZWlnaHQgJT4lIHF1YW50aWxlKDAuNzUpKSAlTj4lCiAgZmlsdGVyKCFub2RlX2lzX2lzb2xhdGVkKCkpICU+JQogIG11dGF0ZShjb21tdW5pdHkgPSBncm91cF9sb3V2YWluKHdlaWdodHMgPSB3ZWlnaHQpICU+JSBmYWN0b3IoKSkKYGBgCgpMZXRzIHRha2UgYSBsb29rIQoKYGBge3J9CnNldC5zZWVkKDEzMzcpCmcgJT4lIGdncmFwaChsYXlvdXQgPSAibmljZWx5IikgKyAKICBnZW9tX25vZGVfcG9pbnQoYWVzKGNvbCA9IGNvbW11bml0eSwgc2l6ZSA9IGNlbnRyYWxpdHlfZGVncmVlKHdlaWdodHMgPSB3ZWlnaHQpKSkgKyAKICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSB3ZWlnaHQpLCBhbHBoYSA9IDAuMjUpICsKICBzY2FsZV9lZGdlX3dpZHRoKHJhbmdlID0gYygwLjEsIDIpKSArIAogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUsIGZpbHRlciA9IHBlcmNlbnRfcmFuayhjZW50cmFsaXR5X2RlZ3JlZSh3ZWlnaHRzID0gd2VpZ2h0KSkgPiAwLjUpLCByZXBlbCA9IFRSVUUpICsKICB0aGVtZV9ncmFwaCgpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ2JvdHRvbScpCmBgYAoKCiMjIEhpZXJhcmNoeSAodHJlZSkgbmV0d29ya3MKCkhpcmFyY2hpY2FsIHN0cnVjdHVyZXMgYXJlIG9idmlvdXNseSBhbHNvIHJlbGF0aW9uYWwuIFRoZSBkaWZmZXJlbmNlIGlzLCB0aGF0IHRoZSBjb25uZWN0aXZpdHkgc3RydWN0dXJlIHRlbmRzIHRvIGJlIGNvbnN0cmFpbnQgdG8gb3RoZXIgbGV2ZWxzLgoKYGBge3J9CmNyZWF0ZV90cmVlKDIwLCAzKSAlPiUgCiAgICBtdXRhdGUobGVhZiA9IG5vZGVfaXNfbGVhZigpLCByb290ID0gbm9kZV9pc19yb290KCkpICU+JSAKICAgIGdncmFwaChsYXlvdXQgPSAndHJlZScpICsKICAgIGdlb21fZWRnZV9kaWFnb25hbCgpICsKICAgIGdlb21fbm9kZV9wb2ludChhZXMoZmlsdGVyID0gbGVhZiksIGNvbG91ciA9ICdmb3Jlc3RncmVlbicsIHNpemUgPSAxMCkgKwogICAgZ2VvbV9ub2RlX3BvaW50KGFlcyhmaWx0ZXIgPSByb290KSwgY29sb3VyID0gJ2ZpcmVicmljaycsIHNpemUgPSAxMCkgKwogICAgdGhlbWVfZ3JhcGgoKQpgYGAKCiogSW4gYWRkaXRpb24gdG8gcmVhbCBsaWZlIGV4YW1wbGVzIHN1Y2ggYXMgb3JnYW5pZ3JhbXMsIGV2b2x1dGlvbmFyeSB0cmVlcyBldGMuLCBtYW55IE1MIG1vZGVscyByZXN1bHQgaW4gdHJlZS1zdHJ1Y3R1cmVzIChlZy4gZGVjaXNpb24gdHJlZXMpLgoqIFdlIHdpbGwgYXQgb3VyIGNhciBleGFtcGxlIGV4ZWN1dGUgYSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZywgd2hpY2ggbGVhZHMgdG8gYSB0cmVlIHN0cnVjdHVyZSAodmlzdWFsaXplZCBpbiB0aGUgZGVuZG9ncmFtKS4KCmBgYHtyfQpjYXJzX2hjIDwtIGNhcnNfZGlzdCAlPiUKICBoY2x1c3QobWV0aG9kID0gIndhcmQuRDIiKQpgYGAKCkFnYWluLCB0aGlzIHN0cnVjdHVyZSBjYW4gYmUgZGlyZWN0bHkgdHJhbnNmZXJlZCB0byBhIGdyYXBoIG9iamVjdC4KCmBgYHtyfQpnIDwtIGNhcnNfaGMgJT4lIGFzX3RibF9ncmFwaCgpCmBgYAoKYGBge3J9CmcKYGBgCgoKYGBge3J9CmcgJT4lIGdncmFwaChsYXlvdXQgPSAnZGVuZHJvZ3JhbScpICsgCiAgZ2VvbV9lZGdlX2RpYWdvbmFsKGFlcyhjb2wgPSAuTigpJGhlaWdodFtmcm9tXSkpICsKICBnZW9tX25vZGVfcG9pbnQoYWVzKGNvbCA9aGVpZ2h0KSkgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhmaWx0ZXIgPSBsZWFmLCBsYWJlbCA9IGxhYmVsKSwgYW5nbGU9OTAsIGhqdXN0PTEsIG51ZGdlX3k9LTAuMSkgKyAKICB0aGVtZV9ncmFwaCgpICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKQogIHlsaW0oLTAuNiwgTkEpIApgYGAKCgojIE11bHRpLU1vZGFsIE5ldHdvcmtzCgojIyBJbnR1aXRpb24KCiogTm93IGl0cyB0aW1lIHRvIHRhbGsgYWJvdXQgYW4gaW50ZXJlc3RpbmcgdHlwZSBvZiBuZXR3b3JrcywgbXVsdGktbW9kYWwuIFRoaXMgbWVhbnMsIGEgbmV0d29yayBoYXMgc2V2ZXJhbCAibW9kZXMiLCBtZWFuaW5nIGNvbm5lY3RzIGVudGl0aWVzIG9uIGRpZmZlcmVudCBjb25jZXB0dWFsIGxldmVscy4gVAoqIGhlIG1vc3QgY29tbW9uIG9uZSBpcyBhICoqMi1tb2RlKiogKG9yICoqYmlwYXJ0aXRlKiopIG5ldHdvcmsuIAoqIEV4YW1wbGVzIGNvdWxkIGJlIGFuIEF1dGhvciAkXHJpZ2h0YXJyb3ckIFBhcGVyLCBJbnZlbnRvciAkXHJpZ2h0YXJyb3ckIFBhdGVudCwgTWVtYmVyICRccmlnaHRhcnJvdyQgQ2x1YiBuZXR3b3JrLiAKKiBIZXJlLCB0aGUgZWxlbWVudHMgaW4gdGhlIGRpZmZlcmVudCBtb2RlcyByZXByZXNlbnQgZGlmZmVyZW50IHRoaW5ncy4gCiogV2UgY2FuIGFsYWx5c2UgdGhlbSBpbiBzZXBlcmF0aW9uIChhbmQgc29tZXRpbWVzIHdlIHNob3VsZCksIGJ1dCBvZnRlbiBpdHMgaGVscGZ1bCB0byAicHJvamVjdCIiIHRoZW0gb250byBvbmUgbW9kZS4gCiogSGVyZSwgd2UgY3JlYXRlIGEgbm9kZSBpbiBvbmUgbW9kZSBieSBqb2ludCBhc3NvY2lhdGlvbiB3aXRoIGFub3RoZXIgbW9kZS4KCiFbXShodHRwczovL3Nkcy1hYXUuZ2l0aHViLmlvL1NEUy1tYXN0ZXIvMDBfbWVkaWEvbmV0d29ya3NfMm1vZGUucG5nKXt3aWR0aD01MDBweH0KIyMgRGVtb25zdGFydGlvbgoKKiBXaGlsZSB0aGF0IHNvdW5kcyBzaW1wbGUsIGl0IGNhbiBiZSBhIHZlcnkgcG93ZXJmdWwgdGVjaG5pcXVlLCBhcyBJIHdpbGwgZGVtb25zdHJhdGUgbm93LgoKYGBge3J9CnNldC5zZWVkKDEzMzcpCmcgPC0gY3JlYXRlX2JpcGFydGl0ZSg0LCAxMCwgZGlyZWN0ZWQgPSBUUlVFLCBtb2RlID0gIm91dCIpICVFPiUKICBzYW1wbGVfbigxNSkKYGBgCgpgYGB7cn0KcGVvcGxlIDwtIGMoJ0plc3BlcicsICdQZXJuaWxsZScsICdNb3J0ZW4nLCAnTGlzZScsICdDaHJpc3RpYW4nLCAnTWV0dGUnLCAnQ2FzcGVyJywgJ0RvcnRlJywgJ0phY29iJywgJ0hlbGxlJykKcGxhY2VzIDwtIGMoJ1lvZ2EgSG91c2UnLCAnQ3Jvc3NmaXQnLCAnSmF6eiBDbHViJywgJ0pvbWZydSBBbm5lIEdhZGUnKQpgYGAKCmBgYHtyfQpnIDwtIGcgJU4+JQogIG11dGF0ZShuYW1lID0gYyhwbGFjZXMsIHBlb3BsZSkpCmBgYAoKYGBge3J9CmcKYGBgCgoKYGBge3J9CnNldC5zZWVkKDEzMzcpCnAwIDwtIGcgJT4lIGdncmFwaCgiYmlwYXJ0aXRlIikgKyAKICBnZW9tX2VkZ2VfbGluayhhbHBoYSA9IDAuMjUpICsgCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhjb2wgPSB0eXBlLCBzaXplID0gY2VudHJhbGl0eV9kZWdyZWUobW9kZSA9ICdhbGwnKSkpICsgCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIHJlcGVsID0gVFJVRSkgKyAKICB0aGVtZV9ncmFwaCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpICsgCiAgbGFicyh0aXRsZSA9ICcyLW1vZGUgbmV0d29yayBwbGFjZXMtcGVvcGxlJykKCnAwCmBgYAoKIyMgQ3JlYXRpbmcgYmlwYXJ0aXRlIHByb2plY3Rpb25zCgojIyMgV2l0aGluIHRoZSBmcmFwaCBvYmplY3QgCgoqIEhhdmluZyBhIDItbW9kZSBuZXR3b3JrLCB3ZSBjYW4gdXNlIHRoZSBgaWdyYXBoYCBmdW5jdGlvbiBgYmlwYXJ0aXRlX3Byb2plY3Rpb25gIHRvIGNyZWF0ZSBhIDJ4IDEtbW9kZSBuZXR3b3JrIG91dCBvZiBpdC4KCmBgYHtyfQpnX3Byb2plY3RlZCA8LSBnICU+JSBiaXBhcnRpdGVfcHJvamVjdGlvbigpCmBgYAoKKiBXZSBub3cgd2lsbCBoYXZlIGEgMS1tb2RlIG5ldHdvcmsgYmV0d2VlbiBwZW9wbGUgYXMgd2VsbCBhcyBvbmUgYmV0d2VlbiBwbGFjZXMuCgpgYGB7cn0KZ19wcm9qZWN0ZWQKYGBgCgpgYGB7cn0KZ19wbGFjZXMgPC0gZ19wcm9qZWN0ZWRbWydwcm9qMSddXSAlPiUgYXNfdGJsX2dyYXBoKGRpcmVjdGVkID0gRkFMU0UpCmdfcGVvcGxlIDwtIGdfcHJvamVjdGVkW1sncHJvajInXV0gJT4lIGFzX3RibF9ncmFwaChkaXJlY3RlZCA9IEZBTFNFKQpgYGAKCiogTGV0cyB0YWtlIGEgbG9vazoKCmBgYHtyLCBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9MTB9CnNldC5zZWVkKDEzMzcpCmxpYnJhcnkocGF0Y2h3b3JrKQoKcDEgPC0gZ19wbGFjZXMgJT4lIGdncmFwaChsYXlvdXQgPSAibmljZWx5IikgKyAKICBnZW9tX25vZGVfcG9pbnQoYWVzKHNpemUgPSBjZW50cmFsaXR5X2RlZ3JlZSh3ZWlnaHRzID0gd2VpZ2h0KSksIGNvbCA9ICdyZWQnKSArIAogIGdlb21fZWRnZV9saW5rKGFlcyh3aWR0aCA9IHdlaWdodCksIGFscGhhID0gMC4yNSkgKwogIHNjYWxlX2VkZ2Vfd2lkdGgocmFuZ2UgPSBjKDAuMSwgMikpICsgCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIHJlcGVsID0gVFJVRSkgKwogIHRoZW1lX2dyYXBoKCkgKyAKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpICsgCiAgbGFicyh0aXRsZSA9ICcxLW1vZGUgbmV0d29yayBwbGFjZXMnKQoKcDIgPC0gZ19wZW9wbGUgJT4lIGdncmFwaChsYXlvdXQgPSAibmljZWx5IikgKyAKICBnZW9tX25vZGVfcG9pbnQoYWVzKHNpemUgPSBjZW50cmFsaXR5X2RlZ3JlZSh3ZWlnaHRzID0gd2VpZ2h0KSksIGNvbCA9ICdza3libHVlMicpICsgCiAgZ2VvbV9lZGdlX2xpbmsoYWVzKHdpZHRoID0gd2VpZ2h0KSwgYWxwaGEgPSAwLjI1KSArCiAgc2NhbGVfZWRnZV93aWR0aChyYW5nZSA9IGMoMC4xLCAyKSkgKyAKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBuYW1lKSwgcmVwZWwgPSBUUlVFKSArCiAgdGhlbWVfZ3JhcGgoKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdub25lJykgKyAKICBsYWJzKHRpdGxlID0gJzEtbW9kZSBuZXR3b3JrIHBlb3BsZScpCgpwMCAvIChwMSArIHAyKQpgYGAKCiogQWxyaWdodCwgYnV0IGxldHMgYXNzdW1lIHdlIGhhdmUgYSAyLW1vZGUgZWRnZWxpc3QgdG8gc3RhcnQgd2l0aC4uLiB3aGF0IHBvc3NpYmlsaXRpZXMgZG8gd2UgaGF2ZSB0aGVuPwoKYGBge3J9CmVsXzJtIDwtIGcgJUU+JSAKICBtdXRhdGUoZnJvbV9uYW1lID0gLk4oKSRuYW1lW2Zyb21dLAogICAgICAgICB0b19uYW1lID0gLk4oKSRuYW1lW3RvXSkgJT4lCiAgYXMudGliYmxlKCkgJT4lCiAgc2VsZWN0KHRvX25hbWUsIGZyb21fbmFtZSkgJT4lCiAgcmVuYW1lKGZyb20gPSB0b19uYW1lLCB0byA9IGZyb21fbmFtZSkKYGBgCgpgYGB7cn0KZWxfMm0KYGBgCgoqIFN1Y2ggYW4gZWRnZWxpc3QgY291bGQgb2J2aW91c2x5IGJlIGxvYWRlZCBpbnRvIGEgZ3JhcGggb2JqZWN0IHRoZSB1c3VhbCB3YXkuCiogV2UganVzdCBoYXZlIHRvIGFzc2lnbiB0eXBlcyB0aGVuCgpgYGB7cn0KZ18ybSA8LSBlbF8ybSAlPiUgYXNfdGJsX2dyYXBoKGRpcmVjdGVkID0gVFJVRSkgICVOPiUKICBtdXRhdGUodHlwZSA9IG5hbWUgJWluJSAoZWxfMm0gJT4lIHB1bGwoZnJvbSkpKQpgYGAKCgojIyMgVmlhIG1hdHJpeCBudWx0aXBsaWNhdGlvbgoKKiBXZSBjYW4gYWxzbyBkbyB0aGUgcHJvamVjdGlvbiBvdXRzaWRlIG9mIHRoZSBncmFwaCBhbmQgZmlyc3QgY3JlYXRlIGEgMi1tb2RlIG1hdHJpeC4gCiogVGhpcyBjYW4gZWFzaWx5IGJlIGRvbmUgYnkgY3Jvc3N0YWJ1bGF0aW5nIHRoZSBlZGdlbGlzdC4KCmBgYHtyIH0KbWF0XzJtIDwtIGVsXzJtICU+JQogIHRhYmxlKCkgJT4lIAogIGFzLm1hdHJpeCgpCmBgYAoKYGBge3J9Cm1hdF8ybQpgYGAKCiogQWdhaW4sIHNwYXJzZSBtYXRyaWNlcyBhcmUgdXN1YWxseSBtb3JlIGVmZmljaWVudC4KCmBgYHtyfQpsaWJyYXJ5KE1hdHJpeCkKbWF0XzJtICU8PiUgTWF0cml4KHNwYXJzZSA9IFRSVUUpCgptYXRfMm0KYGBgCgoKKiBNYXRyaXggYWxnZWJyYSBjYW4gaGVscCB0byBkbyB0aGUgMS1tb2RlIHByb2plY3Rpb24gZGlyZWN0bHkgaW4gdGhlIG1hdHJpeAoqIFRha2luZyB0aGUgZG90cHJvZHVjdCBvZiB0aGUgbWF0cml4IGFuZCBpdHMgdHJhbnNwb3NlZCBmb3JtIHdpbGwgcmVzdWx0IGluIHRoZSAxLW1vZGUgcHJvamVjdGlvbiBvZiBtb2RlIDEgKGBtICUqJSB0KG0pYCkKCmBgYHtyfQptYXRfcGVvcGxlIDwtIG1hdF8ybSAlKiUgdChtYXRfMm0pCmBgYAoKYGBge3J9Cm1hdF9wZW9wbGUKYGBgCgoqIFRha2luZyB0aGUgZG90cHJvZHVjdCBvZiB0aGUgdHJhbnNwb3NlZCBtYXRyaXggYW5kIGl0cyBvcmlnaW5hbCBmb3JtIHdpbGwgcmVzdWx0IGluIHRoZSAxLW1vZGUgcHJvamVjdGlvbiBvZiBtb2RlIDEgKGB0KG0pICUqJSBtYCkKCmBgYHtyfQptYXRfcGxhY2VzIDwtIHQobWF0XzJtKSAlKiUgbWF0XzJtCmBgYAoKYGBge3J9Cm1hdF9wbGFjZXMKYGBgCgoqIE5vdGUgdGhpcyBpcyBzdGlsbCB2ZXJ5IGluZWZmaWNpZW50LCBzaW5jZSB0aGUgbWF0cml4IGlzIGZpcnN0IGNyZWF0ZWQgaW4gZnVsbCwgYW5kIHRoZW4gdHJhbnNmb3JtZWQgdG8gYSBzcGFyc2Ugb25lLgoqIERpcmVjdGx5IHN0YXJ0aW5nIHdpdGggYSBzcGFyc2UgbWF0cml4IG1ha2VzIHRoZSBwcm9jZXNzIG11Y2ggbW9yZSBlZmZpY2llbnQKKiBUaGF0IG1ha2VzIGEgaHVnZSBkaWZmZXJlbmNlIGZvciBsYXJnZSBncmFwaHMKKiBJIGhlcmUgcHJvdmlkZSB5b3UgYW4gZWZmaWNpZW50IGZ1bmN0aW9uIHRvIHVzZQoKYGBge3J9CiMjIEhlbHBlciBmdW5jdGlvbgplbF90b19zcGFyc2VfbWF0cml4IDwtIGZ1bmN0aW9uKGRhdGEsICMgdGhlIGVkZ2VsaXN0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZV8xLCAjIHdoaWNoIHZhcmlhYmxlIGluZGljYXRlcyBtb2RlIDEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlXzIsICMgd2hpY2ggdmFyaWFibGUgaW5kaWNhdGVzIG1vZGUgMgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb2plY3Rpb24gPSAnbm9uZScgIyBJZiBhIHBvamVjdGlvbiBzaG91bGQgYmUgZG9uZSwgcG9zc2libGUgaXMgJ25vbmUnLCAnbW9kZTEnLCAnbW9kZTInIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICl7CiAgCiAgIyBEZWZpbmUgaW5wdXRzCiAgaV9pbnB1dCA8LSBkYXRhICU+JSBwdWxsKHt7bW9kZV8xfX0pIAogIGpfaW5wdXQgPC0gZGF0YSAlPiUgcHVsbCh7e21vZGVfMn19KSAKICAKICByZXF1aXJlKE1hdHJpeCkKICBtYXQgPC0gc3BNYXRyaXgobnJvdyA9IGlfaW5wdXQgJT4lIG5fZGlzdGluY3QoKSwKICAgICAgICAgICAgICAgICAgbmNvbCA9IGpfaW5wdXQgJT4lIG5fZGlzdGluY3QoKSwKICAgICAgICAgICAgICAgICAgaSA9IGlfaW5wdXQgJT4lIGZhY3RvcigpICU+JSBhcy5udW1lcmljKCksCiAgICAgICAgICAgICAgICAgIGogPSBqX2lucHV0ICU+JSBmYWN0b3IoKSAlPiUgYXMubnVtZXJpYygpLAogICAgICAgICAgICAgICAgICB4ID0gcmVwKDEsIGlfaW5wdXQgJT4lIGxlbmd0aCgpICkgKQogIAogIHJvdy5uYW1lcyhtYXQpIDwtIGlfaW5wdXQgJT4lIGZhY3RvcigpICU+JSBsZXZlbHMoKQogIGNvbG5hbWVzKG1hdCkgPC0gal9pbnB1dCAlPiUgZmFjdG9yKCkgJT4lIGxldmVscygpCiAgCiAgIyBQcm9qZWN0aW9uIGlmIG5lY2Vzc2FyeQogIGlmKHByb2plY3Rpb24gPT0gJ21vZGUxJyl7bWF0ICU8PiUgdGNyb3NzcHJvZCgpfQogIGlmKHByb2plY3Rpb24gPT0gJ21vZGUyJyl7bWF0ICU8PiUgY3Jvc3Nwcm9kKCl9ICAKICAgIAogIHJldHVybihtYXQpCn0KYGBgCgpgYGB7cn0KbWF0X3Blb3BsZSA8LSBlbF8ybSAlPiUgZWxfdG9fc3BhcnNlX21hdHJpeChmcm9tLCB0bywgcHJvamVjdGlvbiA9ICdtb2RlMScpCm1hdF9wbGFjZXMgPC0gZWxfMm0gJT4lIGVsX3RvX3NwYXJzZV9tYXRyaXgoZnJvbSwgdG8sIHByb2plY3Rpb24gPSAnbW9kZTInKQpgYGAKCmBgYHtyfQptYXRfcGVvcGxlCm1hdF9wbGFjZXMKYGBgCgoKIyMjIFZpYSBKb2lucwoKKiBGSW5hbGx5LCB0aGUgZWFzaWVzdCB3YXkgCgpgYGB7cn0KZWxfcGVvcGxlIDwtIGVsXzJtICU+JSBsZWZ0X2pvaW4oZWxfMm0sIGJ5ID0gJ3RvJykgJT4lCiAgc2VsZWN0KC10bykgJT4lCiAgcmVuYW1lKGZyb20gPSBmcm9tLngsIHRvID0gZnJvbS55KSAlPiUKICBmaWx0ZXIoZnJvbSAhPSB0bykgJT4lCiAgY291bnQoZnJvbSwgdG8sIG5hbWUgPSAnd2VpZ2h0JykKYGBgCgpgYGB7cn0KZWxfcGVvcGxlCmBgYAoKCmBgYHtyfQplbF9wbGFjZXMgPC0gZWxfMm0gJT4lIGxlZnRfam9pbihlbF8ybSwgYnkgPSAnZnJvbScpICU+JQogIHNlbGVjdCgtZnJvbSkgJT4lCiAgcmVuYW1lKGZyb20gPSB0by54LCB0byA9IHRvLnkpICU+JQogIGZpbHRlcihmcm9tICE9IHRvKSAlPiUKICBjb3VudChmcm9tLCB0bywgbmFtZSA9ICd3ZWlnaHQnKQpgYGAKCmBgYHtyfQplbF9wbGFjZXMKYGBgCgoKIyBDYXNlIHN0dWR5OiBCaWJsaW9ncmFwaGljIG5ldHdvcmtzCgojIyBCYXNpY3MKCkxldHMgdGFsayBhYm91dCBiaWJsaW9ncmFwaGljIG5ldHdvcmtzLiBJbiBzaG9ydCwgdGhhdCBhcmUgbmV0d29ya3MgYmV0d2VlbiBkb2N1bWVudHMgd2hpY2ggY2l0ZSBlYWNoIG90aGVycy4gVGhhdCBjYW4gYmUgKGNvbW1vbmx5KSBhY2FkZW1pYyBwdWJsaWNhdGlvbnMsIGJ1dCBhbHNvIHBhdGVudHMgb3IgcG9saWN5IHJlcG9ydHMuIENvbmNlcHR1YWxseSwgd2UgY2FuIHNlZSB0aGVtIGFzIDIgbW9kZSBuZXR3b3JrcywgYmV0d2VlbiBhcnRpY2xlcyBhbmQgdGhlaXIgcmVmZXJlbmNlLiBUaGF0IGhlbHBzIHVzIHRvIGFwcGx5IHNvbWUgaW50ZXJlc3RpbmcgbWV0cmljcywgc3VjaCBhczoKCiogZGlyZWN0IGNpdGF0aW9ucwoqIEJpYmxpb2dyYXBoaWMgY291cGxpbmcKKiBDby0tY2l0YXRpb25zCgpJbnRlcmVzdGluZ2x5LCBkaWZmZXJlbnQgcHJvamVjdGlvbnMgb2YgdGhpcyAyLW1vZGUgbmV0d29yayBnaXZlIHRoZSB3aG9sZSByZXN1bHRpbmcgMS1tb2RlIG5ldHdvcmsgYSBkaWZmZXJlbnQgbWVhbmluZy4KCiFbXShodHRwczovL3Nkcy1hYXUuZ2l0aHViLmlvL1NEUy1tYXN0ZXIvMDBfbWVkaWEvbmV0d29ya3NfYmlibGlvLnBuZyl7d2lkdGg9NTAwcHh9CgoqIFdlIHdpbGwgaGVyZSBkbyBhIGJyaWVmIGJpYmxpb21ldHJpYyBuZXR3b3JrIGFuYWx5c2lzLgoqIFdoaWxlIHRoZXJlIGV4aXN0IHNwZWNpYWxpemVkIHBhY2thZ2VzIHRvIGRvIGl0IG1vcmUgY29udmVuaWVudGx5IChlZy4gW2JpYmxpb21ldHJpeF0oaHR0cHM6Ly93d3cuYmlibGlvbWV0cml4Lm9yZy8pKSwgd2Ugd2lsbCBmb3IgbXhpbXVtIGNsYXJpdHkgY29uc3RydWN0IGV2ZXJ5dGhpbmcgc29tZXdoYXQgYnkgaGFuZC4KCiogSSB3aWxsIGlsbHVzdHJhdGUgbW9yZSBpbiBkZXRhaWwgaW4gdGhlIGZvbGxvd2luZy4gVGhlIGV4YW1wbGUgaXMgYmFzZWQgb24gc29tZSBvd24gd29yaywgd2hlcmUgaSBoZXJlIGluICB2ZXJ5IHNpbXBsZSB3YXkgcmVjcmVhdGUgc29tZSBwYXJ0cyBvZiB0aGUgYW5hbHlzaXMuIAoqIFJha2FzLCBNLiwgJiBIYWluLCBELiBTLiAoMjAxOSkuIFRoZSBzdGF0ZSBvZiBpbm5vdmF0aW9uIHN5c3RlbSByZXNlYXJjaDogV2hhdCBoYXBwZW5zIGJlbmVhdGggdGhlIHN1cmZhY2U/LiBSZXNlYXJjaCBQb2xpY3ksIDQ1ICg5KS4gRE9JOiBodHRwczovL2RvaS5vcmcvMTAuMTAxNi9qLnJlc3BvbC4yMDE5LjA0LjAxMQoKCiMjIFRoZSBEYXRhCgoqIFdlIHdpbGwgdXNlIGJpYmxpb21ldHJpeCBkYXRhIG9uIGFydGljbGVzIGZyb20gU2NvcHVzIG9uIHJlY2VudCBwdWJsaWNhdGlvbnMgY29udGFpbmluZyB0aGUgdGVybSBgbmV0d29yayBhbmFseXNpc2AgaW4gdGhlaXIgdGl0bGUsIGFic3RyYWN0LCBvciBrZXl3b3Jkcy4gCiogVG8gZG8gc28sIHdlIGZpcnN0IHVzZSB0aGUgZm9sbG93aW5nIHNlYXJjaCBzdHJpbmc6IGBUSVRMRS1BQlMtS0VZICggIm5ldHdvcmsgYW5hbHlzaXMiICkgIEFORCAgKCBMSU1JVC1UTyAoIERPQ1RZUEUgLCAgImFyIiApICBPUiAgTElNSVQtVE8gKCBET0NUWVBFICwgICJjcCIgKSApICBBTkQgICggTElNSVQtVE8gKCBMQU5HVUFHRSAsICAiRW5nbGlzaCIgKSApICBBTkQgICggTElNSVQtVE8gKCBTUkNUWVBFICwgICJqIiApICBPUiAgTElNSVQtVE8gKCBTUkNUWVBFICwgICJwIiApICkgIEFORCAgKCBMSU1JVC1UTyAoIFBVQllFQVIgLCAgMjAyMSApICBPUiAgTElNSVQtVE8gKCBQVUJZRUFSICwgIDIwMjAgKSAgT1IgIExJTUlULVRPICggUFVCWUVBUiAsICAyMDE5ICkgIE9SICBMSU1JVC1UTyAoIFBVQllFQVIgLCAgMjAxOCApICBPUiAgTElNSVQtVE8gKCBQVUJZRUFSICwgIDIwMTcgKSAgT1IgIExJTUlULVRPICggUFVCWUVBUiAsICAyMDE2ICkgIE9SICBMSU1JVC1UTyAoIFBVQllFQVIgLCAgMjAxNSApICkgYAoqIFdlIHNvcnQgdGhlIHJlc3VsdHMgYnkgY2l0YXRpb25zLCBhbmQgZG93bmxvYWQgdGhlIDJrIG1vc3QgY2l0ZWQgb25lcyBhcyBgY3N2YC4gV2Ugc2VsZWN0IGFsIHBvc3NpYmxlIGZpZWxkcyB0byBkb3dubG9hZC4KCmBgYHtyfQpybShsaXN0PWxzKCkpCmRhdGEgPC0gcmVhZF9jc3YoJ2h0dHBzOi8vZ2l0aHViLmNvbS9TRFMtQUFVL1NEUy1tYXN0ZXIvcmF3L21hc3Rlci8wMF9kYXRhL25ldHdvcmtzX2JpYmxpb21ldHJpY3MvYmlibGlvX253LmNzdicpCmBgYAoKYGBge3J9CmRhdGEgJT4lCiAgZ2xpbXBzZSgpCmBgYAoKIyMgUHJlcHJvY2Vzc2luZwoKYGBge3J9CmRhdGEgJTw+JQogIHNlbGVjdChFSUQsIEF1dGhvcnMsIGBBdXRob3IocykgSURgLCBUaXRsZSwgYEFiYnJldmlhdGVkIFNvdXJjZSBUaXRsZWAsIFllYXIsIEFmZmlsaWF0aW9ucywgYEF1dGhvciBLZXl3b3Jkc2AsIGBDaXRlZCBieWAsIEFic3RyYWN0LCBSZWZlcmVuY2VzKSAlPiUKICByZW5hbWUoYXV0aG9yID0gQXV0aG9ycywKICAgICAgICAgYXV0aG9yX2lkID0gYEF1dGhvcihzKSBJRGAsCiAgICAgICAgIHRpdGxlID0gVGl0bGUsCiAgICAgICAgIGpvdXJuYWwgPSBgQWJicmV2aWF0ZWQgU291cmNlIFRpdGxlYCwKICAgICAgICAgeWVhciA9IFllYXIsCiAgICAgICAgIGFmZmlsaWF0aW9uID0gQWZmaWxpYXRpb25zLAogICAgICAgICBrZXl3b3JkcyA9IGBBdXRob3IgS2V5d29yZHNgLAogICAgICAgICBjaXRhdGlvbnMgPSBgQ2l0ZWQgYnlgLAogICAgICAgICBBYnN0cmFjdCA9IEFic3RyYWN0LAogICAgICAgICByZWZlcmVuY2VzID0gUmVmZXJlbmNlcykKYGBgCgoKYGBge3J9CmRhdGFbMiwgJ2F1dGhvciddCmBgYAoKYGBge3J9CmRhdGFbMiwgJ2F1dGhvcl9pZCddCmBgYAoKYGBge3J9CmRhdGFbMiwgJ2FmZmlsaWF0aW9uJ10KYGBgCgpgYGB7cn0KZGF0YVsyLCAia2V5d29yZHMiXQpgYGAKCmBgYHtyfQpkYXRhWzIsICJyZWZlcmVuY2VzIl0KYGBgCgoKYGBge3J9CmRhdGEgJTw+JSAKICBtdXRhdGUoYXV0aG9yID0gYXV0aG9yICU+JSBzdHJfc3BsaXQoJywgJyksCiAgICAgICAgIGF1dGhvcl9pZCA9IGF1dGhvcl9pZCAlPiUgc3RyX3NwbGl0KCc7JyksCiAgICAgICAgIGFmZmlsaWF0aW9uID0gYWZmaWxpYXRpb24gJT4lIHN0cl9zcGxpdCgnOycpLAogICAgICAgICBrZXl3b3JkcyA9IGtleXdvcmRzICU+JSBzdHJfc3BsaXQoJzsgJyksCiAgICAgICAgIHJlZmVyZW5jZXMgPSByZWZlcmVuY2VzICU+JSBzdHJfc3BsaXQoJzsgJykpCmBgYAoKCmBgYHtyfQpkYXRhICU+JSBzZWxlY3QodGl0bGUsIGF1dGhvciwgY2l0YXRpb25zKSAlPiUgCiAgdW5uZXN0KGF1dGhvcikgJT4lCiAgaGVhZCgpCmBgYAoKYGBge3J9CmRhdGEgJT4lIHNlbGVjdCh0aXRsZSwgYXV0aG9yLCBjaXRhdGlvbnMpICU+JSAKICB1bm5lc3QoYXV0aG9yKSAlPiUKICBncm91cF9ieShhdXRob3IpICU+JQogIHN1bW1hcmlzZShuID0gbigpLAogICAgICAgICAgICBjaXRhdGlvbnMgPSBjaXRhdGlvbnMgJT4lIHN1bShuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKGRlc2MoY2l0YXRpb25zKSkgJT4lCiAgaGVhZCgxMCkKYGBgCiMjIEJpcGFydGl0ZSBOZXR3b3JrIEdlbmVyYXRpb24KCkkgd2lsbCBub3cgdHJhbnNmZXIgdGhlbSB0byBhbiBhcnRpY2xlICRccmlnaHRhcnJvdyQgcmVmZXJlbmNlIGVkZ2VsaXN0LgoKYGBge3J9CmVsXzJtIDwtIGRhdGEgJT4lIHNlbGVjdChFSUQsIHJlZmVyZW5jZXMpICU+JSB1bm5lc3QocmVmZXJlbmNlcykgCmBgYAoKYGBge3J9CmVsXzJtICU+JSBoZWFkKCkKYGBgCgojIyMgQmlibGlvZ3JhcGhpYyBDb3VwbGluZwoKIyMjIyBDcmVhdGUgdGhlIGdyYXBoCgoqIFdlIG5vdyBuZWVkIHRvIGNyZWF0ZSBhIHByb2plY3Rpb24gb2YgdGhlIDItbW9kZSBtYXRyaXggdG8gb25lIG9mIHRoZSBtb2Rlcy4KKiBXZSBjb3VsZCBkbyB0aGF0IG1vcmUgZWZmaWNpZW50bHkgdmlhIHVzaW5nIHRoZSBzcGFyc2UgbWF0cml4IGNhbGN1bGF0aW9uIEkgZGlkIGJlZm9yZSwgYnV0IGZvciBtYXhpbXVtIGNsYXJpdHkgbGV0cyBkbyBpdCB2aWEgYSBqb2luLgoqIFdlIHdpbGwgZmlyc3QgY3JlYXRlIHRoZSBhcnRpY2xlIHRvIGFydGljbGUgbmV0d29yay4KCmBgYHtyfQplbF9iaWIgPC0gZWxfMm0gJT4lIGxlZnRfam9pbihlbF8ybSwgYnkgPSAncmVmZXJlbmNlcycpICU+JQogIHJlbmFtZShmcm9tID0gRUlELngsIAogICAgICAgICB0byA9IEVJRC55KSAlPiUKICBzZWxlY3QoZnJvbSwgdG8pICU+JQogIGZpbHRlcihmcm9tICE9IHRvKSAlPiUKICBjb3VudChmcm9tLCB0bywgbmFtZSA9ICd3ZWlnaHQnKQpgYGAKCmBgYHtyfQplbF9iaWIgJT4lIGhlYWQoKQpgYGAKCmBgYHtyfQplbF9iaWIgJT4lIAogIGdncGxvdChhZXMoeCA9IHdlaWdodCkpICsKICBnZW9tX2hpc3RvZ3JhbSgpCmBgYAoKYGBge3J9CmVsX2JpYiAlPD4lCiAgZmlsdGVyKHdlaWdodCA+PTIpCmBgYAoKYGBge3J9CmVsX2JpYiAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gd2VpZ2h0KSkgKwogIGdlb21faGlzdG9ncmFtKCkKYGBgCiogT2ssIGdyZWF0LCB3ZSBub3cgY2FuIGNyZWF0ZSBhIGdyYXBoCgpgYGB7cn0KZ19iaWIgPC0gZWxfYmliICU+JSBhc190YmxfZ3JhcGgoZGlyZWN0ZWQgPSBGQUxTRSkgJT4lIAogIGlncmFwaDo6c2ltcGxpZnkoKSAlPiUKICBhc190YmxfZ3JhcGgoZGlyZWN0ZWQgPSBGQUxTRSkgCmBgYAoKYGBge3J9CmdfYmliCmBgYAoKCmBgYHtyfQpnX2JpYiA8LSBnX2JpYiAlTj4lIAogIGZpbHRlcighbm9kZV9pc19pc29sYXRlZCgpKSAlTj4lIAogIGZpbHRlcihwZXJjZW50X3JhbmsoY2VudHJhbGl0eV9kZWdyZWUod2VpZ2h0cyA9IHdlaWdodCkpID49IDAuNSkKYGBgCgoKKiBBIGZpcnN0IG9idmlvdXMgdGhpbmcgdG8gZG8gd291bGQgYmUgYSBjb21tdW5pdHkgZGV0ZWN0aW9uCgpgYGB7cn0KZ19iaWIgPC0gZ19iaWIgJU4+JQogIG11dGF0ZShjb20gPSBncm91cF9sb3V2YWluKHdlaWdodHMgPSB3ZWlnaHQpKSAKYGBgCgoqIE5vdywgd2UgY2FuIGFsc28gY3JlYXRlIGNvbW11bml0eSBpbnRlcm5hbCBzdGF0aXN0aWNzLCBzdWNoIGFzIHRoZSB3aXRoaW4tY29tbXVuaXR5IGRlZ3JlZS4KKiBUbyBkbyBzbywgd2UgdXNlIHRoZSBwb3dlcmZ1bCBgbW9ycGgoKWAgZnVuY3Rpb25zIG9mIGB0aWR5Z3JhcGhgLCB3aGljaCBiYXNpY2FsbHkgbGV0IHlvdSBhcHBseSBgZ3JvdXBfYnlgIHN0eWxlIG9wZXJhdGlvbnMgb24gZ3JhcGggc3RydWN0dXJlcywgd2hlcmUgZ3JhcGggY2FsY3VsYXRpb25zIGFyZSBleGVjaXRlZCBvbiBzdWJncmFwaHMuCiogVGhhdCBsZXQgeW91IGUuZy4gY29udHJhY3Qgbm9kZXMsIHdvcmsgb24gdGhlIGxpbmVncmFwaCByZXByZXNlbnRhdGlvbiwgc3BsaXQgY29tbXVuaXRpZXMgdG8gc2VwYXJhdGUgZ3JhcGhzIGV0Yy4gCgpgYGB7cn0KZ19iaWIgPC0gZ19iaWIgJU4+JQogIG1vcnBoKHRvX3NwbGl0LCBjb20pICU+JSAKICAgIG11dGF0ZShjZW50X2Rncl9pbnQgPSBjZW50cmFsaXR5X2RlZ3JlZSh3ZWlnaHRzID0gd2VpZ2h0KSkgJT4lCiAgICBtdXRhdGUoY29tX2NlbnRlciA9IGNlbnRfZGdyX2ludCA9PSBtYXgoY2VudF9kZ3JfaW50KSkgJT4lCiAgdW5tb3JwaCgpIApgYGAKCgpgYGB7cn0KZ19iaWIgJU4+JQogIGFzX3RpYmJsZSgpICU+JQogIGNvdW50KGNvbSwgc29ydCA9IFRSVUUpCmBgYAoKKiBPaywgc2VlbXMgbGlrZSB0aGVyZSBhcmUgdG9vIG1hbnkgY29tbXVuaXRpZXMgdG8gZG8gc29tZXRoaW5nIG1lYW5pbmdmdWwuIAoqIExldHMgcmVzdHJpY3QgaXQgdG8gb25seSBvbmx5IGNvbW11bml0aWVzIHdpdGggbW9yZSB0aGFuIDIwIG1lbWJlcnMuCgpgYGB7cn0KZ19iaWIgPC0gZ19iaWIgJU4+JQogIGFkZF9jb3VudChjb20sIG5hbWUgPSAnY29tX24nKSAlPiUKICBtdXRhdGUoY29tID0gaWZlbHNlKGNvbV9uID49IDIwLCBjb20sIE5BKSApICU+JQogIHNlbGVjdCgtY29tX24pICAKYGBgCgpgYGB7cn0KZ19iaWIgPC0gZ19iaWIgJU4+JQogIGxlZnRfam9pbihkYXRhICU+JSBzZWxlY3QoRUlELCB0aXRsZSwgam91cm5hbCwgeWVhciwgY2l0YXRpb25zKSwgYnkgPSBjKCduYW1lJyA9ICdFSUQnKSkgJT4lCiAgbXV0YXRlKHRpdGxlID0gdGl0bGUgJT4lIHN0cl90cnVuYygzMCkpCmBgYAoKCiogTGV0cyB0YWtlIGEgbG9vayBhdCB0aGUgbmV0d29yay4KCmBgYHtyLCBmaWcuaGVpZ2h0PTEyLjUsIGZpZy53aWR0aD0xMi41fQpzZXQuc2VlZCgxMzM3KQpnX2JpYiAlPiUKICBnZ3JhcGgobGF5b3V0ID0gJ2dyYXBob3B0JykgKyAKICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSB3ZWlnaHQsCiAgICAgICAgICAgICAgICAgICAgIGNvbG9yID0gLk4oKSRjb21bZnJvbV0gJT4lIGFzLmZhY3RvcigpKSwgIyBOb3RpY2UgdGhhdAogICAgICAgICAgICAgICAgYWxwaGEgPSAwLjUsIAogICAgICAgICAgICAgICAgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICBzY2FsZV9lZGdlX3dpZHRoKHJhbmdlID0gYygwLjUsIDIpKSArIAogIGdlb21fbm9kZV9wb2ludChhZXMoY29sb3IgPSBjb20gJT4lIGFzLmZhY3RvcigpLAogICAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IGNlbnRyYWxpdHlfZGVncmVlKHdlaWdodCA9IHdlaWdodCksIAogICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSBjaXRhdGlvbnMpKSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gdGl0bGUsIGZpbHRlciA9IGNvbV9jZW50ZXIgPT0gVFJVRSksIHJlcGVsID0gVFJVRSkgKwogIHRoZW1lX2dyYXBoKCkgICArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdib3R0b20nKSArIAogIGxhYnModGl0bGUgPSAnQmlibGlvZ3JhcGhpYyBDb3VwbGluZyBOZXR3b3JrJywKICAgICAgIHN1YnRpdGxlID0gJ05ldHdvcmsgQW5hbHlzaXMgMjAxNS0yMDIwJywKICAgICAgIGNvbG9yID0gJ0NvbW11bml0eScsCiAgICAgICBzaXplID0gJ0RlZ3JlZScsCiAgICAgICBhbHBoYSA9ICdDaXRhdGlvbnMnKSAKYGBgCgoKCmBgYHtyfQpkYXRhICU+JQogIHNlbGVjdChFSUQsIGF1dGhvciwgeWVhciwgdGl0bGUsIGpvdXJuYWwsIGNpdGF0aW9ucykgJT4lCiAgaW5uZXJfam9pbihnX2JpYiAlTj4lIGFzX3RpYmJsZSgpICU+JSBzZWxlY3QobmFtZSwgY29tLCBjZW50X2Rncl9pbnQpLCBieSA9IGMoJ0VJRCcgPSAnbmFtZScpKSAlPiUKICBncm91cF9ieShjb20pICU+JQogICAgYXJyYW5nZShkZXNjKGNlbnRfZGdyX2ludCkpICU+JQogICAgc2xpY2UoMToxMCkgJT4lCiAgdW5ncm91cCgpICU+JQogIHNlbGVjdChjb20sIHRpdGxlLCBjZW50X2Rncl9pbnQsIGNpdGF0aW9ucykgJT4lCiAgbXV0YXRlKHRpdGxlID0gdGl0bGUgJT4lIHN0cl90cnVuYyg3NSkpCmBgYAoKIyMgQ28tQ2l0YXRpb24gQW5hbHlzaXMKCiogV2UgY2FuIG5vdyBkbyBleGFjdGx5IHRoZSBzYW1lIGZvciB0aGUgcmVmZXJlbmNlICg9Y28tY2l0YXRpb24pIG5ldHdvcmsKCiMjIyMgQ3JlYXRlIHRoZSBncmFwaAoKYGBge3J9CmVsX2NpdCA8LSBlbF8ybSAlPiUgbGVmdF9qb2luKGVsXzJtLCBieSA9ICdFSUQnKSAlPiUKICByZW5hbWUoZnJvbSA9IHJlZmVyZW5jZXMueCwgCiAgICAgICAgIHRvID0gcmVmZXJlbmNlcy55KSAlPiUKICBzZWxlY3QoZnJvbSwgdG8pICU+JQogIGZpbHRlcihmcm9tICE9IHRvKSAlPiUKICBjb3VudChmcm9tLCB0bywgbmFtZSA9ICd3ZWlnaHQnKQpgYGAKCmBgYHtyfQplbF9jaXQgJTw+JQogIGZpbHRlcih3ZWlnaHQgPj0zLAogICAgICAgICAhc3RyX2RldGVjdChmcm9tLCAnXFw6XFwsICcpLAogICAgICAgICAhc3RyX2RldGVjdCh0bywgJ1xcOlxcLCAnKSwKICAgICAgICAgc3RyX2xlbmd0aChmcm9tKSA+IDUwLAogICAgICAgICBzdHJfbGVuZ3RoKHRvKSA+IDUwCiAgICAgICAgICkKYGBgCgpgYGB7cn0KZWxfY2l0ICU+JSAKICBnZ3Bsb3QoYWVzKHggPSB3ZWlnaHQpKSArCiAgZ2VvbV9oaXN0b2dyYW0oKQpgYGAKCmBgYHtyfQpnX2NpdCA8LSBlbF9jaXQgJT4lIGFzX3RibF9ncmFwaChkaXJlY3RlZCA9IEZBTFNFKSAlPiUgCiAgaWdyYXBoOjpzaW1wbGlmeSgpICU+JQogIGFzX3RibF9ncmFwaChkaXJlY3RlZCA9IEZBTFNFKSAgJU4+JSAKICBmaWx0ZXIoIW5vZGVfaXNfaXNvbGF0ZWQoKSkgJU4+JSAKICBmaWx0ZXIocGVyY2VudF9yYW5rKGNlbnRyYWxpdHlfZGVncmVlKHdlaWdodHMgPSB3ZWlnaHQpKSA+PSAwLjUpICVOPiUKICBtdXRhdGUoY29tID0gZ3JvdXBfbG91dmFpbih3ZWlnaHRzID0gd2VpZ2h0KSkgJU4+JQogIG1vcnBoKHRvX3NwbGl0LCBjb20pICU+JSAKICAgIG11dGF0ZShjZW50X2Rncl9pbnQgPSBjZW50cmFsaXR5X2RlZ3JlZSh3ZWlnaHRzID0gd2VpZ2h0KSkgJT4lCiAgICBtdXRhdGUoY29tX2NlbnRlciA9IGNlbnRfZGdyX2ludCA9PSBtYXgoY2VudF9kZ3JfaW50KSkgJT4lCiAgdW5tb3JwaCgpIApgYGAKCgpgYGB7cn0KZ19jaXQgJU4+JQogIGFzX3RpYmJsZSgpICU+JQogIGNvdW50KGNvbSwgc29ydCA9IFRSVUUpCmBgYAoKKiBPaywgc2VlbXMgbGlrZSB0aGVyZSBhcmUgdG9vIG1hbnkgY29tbXVuaXRpZXMgdG8gZG8gc29tZXRoaW5nIG1lYW5pbmdmdWwuIAoqIExldHMgcmVzdHJpY3QgaXQgdG8gb25seSBvbmx5IGNvbW11bml0aWVzIHdpdGggbW9yZSB0aGFuIDIwIG1lbWJlcnMuCgpgYGB7cn0KZ19jaXQgPC0gZ19jaXQgJU4+JQogIGFkZF9jb3VudChjb20sIG5hbWUgPSAnY29tX24nKSAlPiUKICBtdXRhdGUoY29tID0gaWZlbHNlKGNvbV9uID49IDEwLCBjb20sIE5BKSApICU+JQogIHNlbGVjdCgtY29tX24pICAKYGBgCgpMZXRzIHRha2UgYSBsb29rIGF0IHRoZSBuZXR3b3JrLgoKYGBge3IsIGZpZy5oZWlnaHQ9MTIuNSwgZmlnLndpZHRoPTEyLjV9CnNldC5zZWVkKDEzMzcpCmdfY2l0ICVOPiUKICBtdXRhdGUobmFtZSA9IG5hbWUgJT4lIHN0cl90cnVuYyg3NSkpICU+JQogIGdncmFwaChsYXlvdXQgPSAnZ3JhcGhvcHQnKSArIAogIGdlb21fZWRnZV9saW5rKGFlcyh3aWR0aCA9IHdlaWdodCwKICAgICAgICAgICAgICAgICAgICAgY29sb3IgPSAuTigpJGNvbVtmcm9tXSAlPiUgYXMuZmFjdG9yKCkpLCAjIE5vdGljZSB0aGF0CiAgICAgICAgICAgICAgICBhbHBoYSA9IDAuNSwgCiAgICAgICAgICAgICAgICBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgIHNjYWxlX2VkZ2Vfd2lkdGgocmFuZ2UgPSBjKDAuNSwgMikpICsgCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhjb2xvciA9IGNvbSAlPiUgYXMuZmFjdG9yKCksCiAgICAgICAgICAgICAgICAgICAgICBzaXplID0gY2VudHJhbGl0eV9kZWdyZWUod2VpZ2h0ID0gd2VpZ2h0KSwgCiAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IGNlbnRfZGdyX2ludCkpICsKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBuYW1lLCBmaWx0ZXIgPSBjb21fY2VudGVyID09IFRSVUUgJiBwZXJjZW50X3JhbmsoY2VudF9kZ3JfaW50KSA+IDAuODAgKSwgcmVwZWwgPSBUUlVFKSArCiAgdGhlbWVfZ3JhcGgoKSAgICsgCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ2JvdHRvbScpICsgCiAgbGFicyh0aXRsZSA9ICdCaWJsaW9ncmFwaGljIENvdXBsaW5nIE5ldHdvcmsnLAogICAgICAgc3VidGl0bGUgPSAnTmV0d29yayBBbmFseXNpcyAyMDE1LTIwMjAnLAogICAgICAgY29sb3IgPSAnQ29tbXVuaXR5JywKICAgICAgIHNpemUgPSAnRGVncmVlJywKICAgICAgIGFscGhhID0gJ0NpdGF0aW9ucycpIApgYGAKCmBgYHtyfQpnX2NpdCAlTj4lCiAgYXNfdGliYmxlKCkgJT4lCiAgZ3JvdXBfYnkoY29tKSAlPiUKICAgIGFycmFuZ2UoZGVzYyhjZW50X2Rncl9pbnQpKSAlPiUKICAgIHNsaWNlKDE6MTApICU+JQogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUobmFtZSA9IG5hbWUgJT4lIHN0cl90cnVuYyg3NSkpICU+JQogIHNlbGVjdChjb20sIG5hbWUsIGNlbnRfZGdyX2ludCkKYGBgCgoKIyMgSm9pbnQgYW5hbHlzaXMKCmBgYHtyfQplbF9qb2ludCA8LSBlbF8ybSAlPiUKICBpbm5lcl9qb2luKGdfYmliICVOPiUgYXNfdGliYmxlKCkgJT4lIHNlbGVjdChuYW1lLCBjb20pICU+JSBkcm9wX25hKCksIGJ5ID0gYygnRUlEJyA9ICduYW1lJykpICU+JQogIGlubmVyX2pvaW4oZ19jaXQgJU4+JSBhc190aWJibGUoKSAlPiUgc2VsZWN0KG5hbWUsIGNvbSkgJT4lIGRyb3BfbmEoKSwgYnkgPSBjKCdyZWZlcmVuY2VzJyA9ICduYW1lJykpICU+JQogIHJlbmFtZShmcm9tID0gY29tLngsIHRvID0gY29tLnkpICU+JQogIGNvdW50KGZyb20sIHRvLCBuYW1lID0gJ3dlaWdodCcpCmBgYAoKYGBge3J9CmVsX2pvaW50CmBgYAoKYGBge3J9CmVsX2pvaW50ICU8PiUKICBtdXRhdGUoZnJvbSA9IHBhc3RlKCdiaWInLCBmcm9tLCBzZXAgPSAnXycpLAogICAgICAgICB0byA9IHBhc3RlKCdjaXQnLCB0bywgc2VwID0gJ18nKSkKYGBgCgpgYGB7cn0KZ19qb2ludCA8LSBlbF9qb2ludCAlPiUgYXNfdGJsX2dyYXBoKGRpcmVjdGVkID0gVFJVRSkgCmBgYAoKYGBge3J9Cmdfam9pbnQgPC0gZ19qb2ludCAlTj4lCiAgbXV0YXRlKHR5cGUgPSBuYW1lICU+JSBzdHJfZGV0ZWN0KCdiaWInKSkKYGBgCgoKYGBge3J9Cmdfam9pbnQgJT4lIGdncmFwaCgiYmlwYXJ0aXRlIikgKyAKICBnZW9tX2VkZ2VfbGluayhhbHBoYSA9IDAuMjUpICsgCiAgZ2VvbV9ub2RlX3BvaW50KGFlcyhjb2wgPSB0eXBlLCBzaXplID0gY2VudHJhbGl0eV9kZWdyZWUobW9kZSA9ICdhbGwnKSkpICsgCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIHJlcGVsID0gVFJVRSkgKyAKICB0aGVtZV9ncmFwaCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpCmBgYAoKCiMgRW5kbm90ZXMKCiMjIyBDb21wbGVtZW50YXJ5IGV4ZXJjaXNlcwoKUGxlYXNlIGRvICoqRXhlcmNpc2UgMSoqIGluIHRoZSBjb3JyZXNwb25kaW5nIHNlY3Rpb24gb24gYEdpdGh1YmAuIFRoaXMgdGltZSB5b3UgYXJlIGFib3V0IHRvIGRvIHlvdXIgb3duIGJpYmxpb2dyYXBoaWMgYW5hbHlzaXMhCgojIyMgUmVmZXJlbmNlcwoKUGFwZXIgbWVudGlvbmVkIGluIHRoZSB0ZXh0CgoqIFJha2FzLCBNLiwgJiBIYWluLCBELiBTLiAoMjAxOSkuIFRoZSBzdGF0ZSBvZiBpbm5vdmF0aW9uIHN5c3RlbSByZXNlYXJjaDogV2hhdCBoYXBwZW5zIGJlbmVhdGggdGhlIHN1cmZhY2U/LiBSZXNlYXJjaCBQb2xpY3ksIDQ1ICg5KS4gRE9JOiBodHRwczovL2RvaS5vcmcvMTAuMTAxNi9qLnJlc3BvbC4yMDE5LjA0LjAxMQoKT3RoZXIgb3duIHdvcmsgZGVhbGluZyB3aXRoIDItbW9kZSBuZXR3b3JrcwoKKiBIYWluLCBEYW5pZWwgUy4sIGFuZCBSb21hbiBKdXJvd2V0emtpLiAiSW5jcmVtZW50YWwgYnkgRGVzaWduPyBPbiB0aGUgUm9sZSBvZiBJbmN1bWJlbnRzIGluIFRlY2hub2xvZ3kgTmljaGVzLiIgSW4gRm91bmRhdGlvbnMgb2YgRWNvbm9taWMgQ2hhbmdlLCBwcC4gMjk5LTMzMi4gU3ByaW5nZXIsIENoYW0sIDIwMTcuCiogSGFpbiwgRC4sIEJ1Y2htYW5uLCBULiwgS3VkaWMsIE0uLCAmIE3DvGxsZXIsIE0uICgyMDE4KS4gRW5kb2dlbm91cyBkeW5hbWljcyBvZiBpbm5vdmF0aW9uIG5ldHdvcmtzIGluIHRoZSBHZXJtYW4gYXV0b21vdGl2ZSBpbmR1c3RyeTogYW5hbHlzaW5nIHN0cnVjdHVyYWwgbmV0d29yayBldm9sdXRpb24gdXNpbmcgYSBzdG9jaGFzdGljIGFjdG9yLW9yaWVudGVkIGFwcHJvYWNoLiBJbnRlcm5hdGlvbmFsIEpvdXJuYWwgb2YgQ29tcHV0YXRpb25hbCBFY29ub21pY3MgYW5kIEVjb25vbWV0cmljcywgOCgzLTQpLCAzMjUtMzQ0LgoqIEp1cm93ZXR6a2ksIFJvbWFuLCBhbmQgRGFuaWVsIFMuIEhhaW4uICJNYXBwaW5nIHRoZSAoci0pIGV2b2x1dGlvbiBvZiB0ZWNobm9sb2dpY2FsIGZpZWxkc+KAk2Egc2VtYW50aWMgbmV0d29yayBhcHByb2FjaC4iIFNvY2lhbCBJbmZvcm1hdGljcywgcHAuIDM1OS0zODMuIFNwcmluZ2VyLCBDaGFtLCAyMDE0LgoKIyMjIFBhY2thZ2VzICYgRWNvc3lzdGVtCgpZb3UgY2FuIGZpbmQgbW9yZSBpbmZvIGFib3V0OgoKKiBgdGlkeWdyYXBoYCBbaGVyZV0oaHR0cHM6Ly90aWR5Z3JhcGguZGF0YS1pbWFnaW5pc3QuY29tLykKKiBgZ2dyYXBoYCBbaGVyZV0oaHR0cHM6Ly9nZ3JhcGguZGF0YS1pbWFnaW5pc3QuY29tLykKKiBgYmlibGlvbWV0cml4YCBbaGVyZV0oaHR0cDovL3d3dy5iaWJsaW9tZXRyaXgub3JnLykKCiMjIyBPdGhlciBTb3VyY2VzCgoqIEFuIGV4YW1wbGUgbm90ZWJvb2sgd2hlcmUgSSB1c2UgdGhlIGlkZWFzIHByZXNlbnRlZCBoZWUgZm9yIGEgc2ltcGxlIGFuYWx5c2lzIG9mIHRlY2hub2xvZ2ljYWwgcmVsYXRlZG5lc3MgY2FuIGJlIGZvdW5kIFtoZXJlXShodHRwczovL3Jhd2Nkbi5naXRoYWNrLmNvbS9kYW5pZWwtaGFpbi9TRENfSU0vOWJmODY4M2ZmZWVhNzAzZTUwZTU1MDZhYzBlYjNkZDc1NDQ2MjFjMy9TM18yX0Vjb25vbWljX2NvbXBsZXhpdHkuaHRtbCkKCgojIyMgU2Vzc2lvbiBpbmZvCmBgYHtyfQpzZXNzaW9uSW5mbygpCmBgYAoKCgoK