The point of this lesson is to show that you can do a time lagged network in R, no LSA program required. This skips the “local” element of Local Similarity Analysis, but I found that in my research I was electing just to do time lagged spearman networks. Similarly, one could use this same approach to build a time lagged sparCC network or most other statistics.

This may or may not work for graphical lasso approaches.

library(tidyverse)
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
✓ ggplot2 3.2.1     ✓ purrr   0.3.3
✓ tibble  2.1.3     ✓ dplyr   0.8.3
✓ tidyr   1.0.2     ✓ stringr 1.4.0
✓ readr   1.3.1     ✓ forcats 0.4.0
── Conflicts ───────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::as_data_frame() masks tibble::as_data_frame(), igraph::as_data_frame()
x purrr::compose()       masks igraph::compose()
x tidyr::crossing()      masks igraph::crossing()
x dplyr::filter()        masks stats::filter()
x dplyr::groups()        masks igraph::groups()
x dplyr::lag()           masks stats::lag()
x purrr::simplify()      masks igraph::simplify()
library(lubridate)

Attaching package: ‘lubridate’

The following object is masked from ‘package:igraph’:

    %--%

The following object is masked from ‘package:base’:

    date
library(zoo)

Attaching package: ‘zoo’

The following objects are masked from ‘package:base’:

    as.Date, as.Date.numeric
library(SpiecEasi)
library(psych)

Attaching package: ‘psych’

The following objects are masked from ‘package:ggplot2’:

    %+%, alpha

The following objects are masked from ‘package:SpiecEasi’:

    cor2cov, shannon
library(igraph)

Read in

Lets bring in some time-series data from the SPOT dataset. These are ARISA fragements, their lengths associate with species identity. I downloaded the data here:

https://www.bco-dmo.org/dataset/535915

And then removed some variables that we are not using yet.

The data set is somewhat large. To save space, I’ve zipped it.

Fortunately, R can read zipped csv files just fine. Some of the months are missing arrisa data and are loaded in as “nd”. We can remove those later, but we tell R so it doesn’t freak out.

# read in data
spotSurface <- read_csv("SPOT/spot_arisa_surface.zip") %>%
  # treat date column as a date, rather than a character string
  mutate(date_local = ymd(date_local))
Parsed with column specification:
cols(
  date_local = col_date(format = ""),
  arisa_frag = col_character(),
  rel_abund = col_double()
)

Removing low occurance taxa

How many times do we see each taxon? How abundant are they. We filter the taxa a lot if we only keep things with a mean abundance of at least 0.5%

howMany <- spotSurface %>%
  group_by(arisa_frag) %>%
  summarize(sightings = sum(na.omit(rel_abund > 0)),
            meanAbun = mean(na.omit(rel_abund))) %>%
  arrange(-sightings)
 

keepTaxa <- howMany %>% 
  filter(sightings >= 5, meanAbun >= 0.01) %>%
  pull(arisa_frag)

keepTaxa
 [1] "ARISA_670.5" "ARISA_538.9" "ARISA_666.4" "ARISA_662"   "ARISA_686.9" "ARISA_435.5" "ARISA_682.4" "ARISA_419.5"
 [9] "ARISA_676.9" "ARISA_421.5" "ARISA_689.8" "ARISA_729.4" "ARISA_570.1" "ARISA_667.6" "ARISA_573.6" "ARISA_679.4"
[17] "ARISA_561.8" "ARISA_402.4" "ARISA_828.8" "ARISA_831.8" "ARISA_594.1"
spotSurface2 <- spotSurface %>% filter(arisa_frag %in% keepTaxa)

Processing

Lets reshape everything into a wide format data frame

spotWide <- spotSurface2 %>% pivot_wider(names_from = arisa_frag, values_from = rel_abund)

There are lots of months with missing data, and lots of months that are missing but don’t have rows. Lets update this so that we have a row for every month, even if it has NA values.

spotWide2 <- spotWide %>%
  na.omit %>%
  mutate(yr = year(date_local), mth = month(date_local)) %>%
  select(yr, mth, date_local, everything()) %>%
  arrange(yr, mth) 

goalDates <- tibble(
  yr = rep(first(spotWide2$yr):last(spotWide2$yr), each = 12),
  mth = rep(1:12, last(spotWide2$yr) - first(spotWide2$yr) + 1)
) %>%
  mutate(filler_date = ymd(paste(yr, mth, 15, sep = "_"))) %>%
  filter(filler_date > min(spotWide2$date_local) &
           filler_date < max(spotWide2$date_local)) %>%
  select(-filler_date)
spotWideAllMonths <- left_join(goalDates, spotWide2)
Joining, by = c("yr", "mth")

Ok. So we’re going to address this missing not at random data in a way that critics might call reckless. Linearly interpolating it.

spotInterp <- spotWideAllMonths %>%
  mutate_at(vars(matches("ARISA")), na.approx)
spotInterp2 <- spotInterp %>%
  mutate(date_local = if_else(
    is.na(date_local),
    ymd(paste(yr, mth, 15)),
    date_local)
  ) %>%
  select(-c(yr, mth))
spotInterpMtx <- spotInterp2 %>%
  column_to_rownames("date_local") %>%
  as.matrix()

spotClrMtx <- clr(spotInterpMtx)

Lags

Ok, so with this dataset, we can do any of the things from the earlier lessons. We can also export it for local similarity analysis outside of R.

Ok, lets make a new matrix, where the columns are dates, but then there are another series of columns of dates lagged by one.


spotUnLag <- spotInterpMtx[-1,]
spotLag <- spotInterpMtx[-nrow(spotInterpMtx),]
colnames(spotUnLag) <- paste("nolag", colnames(spotUnLag), sep = "-")
colnames(spotLag) <- paste("lag1", colnames(spotLag), sep = "-")
spotWLag <- cbind(spotUnLag, spotLag)

Now we’ll correlate everything vs everyting. Keep in mind that the unlagged vs unlagged are missing one row. We could get around this by doing everything in two batches.

Calculating clr-spearman matrix

Non lagged data

spearCorTestClr <- corr.test(spotInterpMtx, method = "spearman", adjust = "none")
spearCorClr <- spearCorTestClr$r
spearPClr <- spearCorTestClr$p

Lagged data

spearCorTestClr_Lagged <- corr.test(spotWLag, method = "spearman", adjust = "none")
spearCorClr_Lagged <- spearCorTestClr_Lagged$r
spearPClr_Lagged <- spearCorTestClr_Lagged$p
source("jacob_library.R")

Some data wrangling, as per my earlier lesson

reordered_spearCorTestClr_Lagged <- reorder_cor_and_p(spearCorClr_Lagged, spearPClr_Lagged)
spearCorClr_Lagged_Reordered <- reordered_spearCorTestClr_Lagged$r
spearPClr_Lagged_Reordered <- reordered_spearCorTestClr_Lagged$p
spearCorClr_Lagged_Proc <- spearCorClr_Lagged_Reordered %>% get_upper_tri() %>% reshape2::melt() %>% na.omit() %>% rename(rho = value)
spearPClr_Lagged_Proc <- spearPClr_Lagged_Reordered %>% get_upper_tri() %>% reshape2::melt() %>% na.omit() %>% rename(p = value)
spearRhoP_lagged <- left_join(spearCorClr_Lagged_Proc, spearPClr_Lagged_Proc, by = c("Var1", "Var2"))
spearRhoP_lagged

Parsing out the lags

wrangle_lagged <- function(preWrangled, coef = "rho"){ # I need to allow the coefficient name to change for the sparcc stuff
  # Initial wrangling
  postWrangled <- preWrangled %>% 
  separate(Var1, c("V1", "Var1"), sep = "-") %>%
  separate(Var2, c("V2", "Var2"), sep = "-") %>%
  # get rid of rows where both variables are laged
  filter(!(V1 == "lag1" & V2 == "lag1")) %>%
  filter(Var1 != Var2) %>%
  mutate(delay = if_else(V1 == "lag1" & V2 == "nolag", -1,
         if_else(V1 == "nolag" & V2 == "lag1", 1,
                 if_else(V1 == "nolag" & V2 == "nolag", 0, -9999)
         )
  )
  ) %>%
  mutate(fdr =p.adjust(p, method = "BH"))
  
# Now we select the delay with the highest score.
# 
# Note that we calculated the false discovery rate *before* we selected the value with the highest score, but *after* we removed the delay-vs-delay comparasons.
  
  bestLag <- postWrangled %>%
  group_by(Var1, Var2) %>% 
  top_n(1, !!as.name(coef)) %>%
  select(-c(V1, V2))
  
  
  ## Add arrows for easier igraph plotting
  
  arrowedData <- bestLag %>%
  #filter(fdr < 0.05) %>%
  mutate(arrow = recode(delay, `-1` = "<", `1` = ">", `0` = "-"))
  
  arrowedData
}


spearRhoP_lagged4<- spearRhoP_lagged %>% wrangle_lagged
# spearRhoP_lagged2 <- spearRhoP_lagged %>% 
#   separate(Var1, c("V1", "Var1"), sep = "-") %>%
#   separate(Var2, c("V2", "Var2"), sep = "-") %>%
#   # get rid of rows where both variables are laged
#   filter(!(V1 == "lag1" & V2 == "lag1")) %>%
#   filter(Var1 != Var2) %>%
#   mutate(delay = if_else(V1 == "lag1" & V2 == "nolag", -1,
#          if_else(V1 == "nolag" & V2 == "lag1", 1,
#                  if_else(V1 == "nolag" & V2 == "nolag", 0, -9999)
#          )
#   )
#   ) %>%
#   mutate(fdr =p.adjust(p, method = "BH"))
#   
# spearRhoP_lagged2 

Now we select the delay with the highest score.

Note that we calculated the false discovery rate before we selected the value with the highest score, but after we removed the delay-vs-delay comparasons.

# spearRhoP_lagged3 <- spearRhoP_lagged2 %>%
#   group_by(Var1, Var2) %>% 
#   top_n(1, rho) %>%
#   select(-c(V1, V2))
# spearRhoP_lagged3

And there you have a table that can go into a network. Some thoughts. There is way more data handling to do this with sparcc, but it could be done. I really ought to automate this into a general time delay function. I don’t think this works for graphical lasoo but it could be tried.

Autocorrelation may inflate some of these scores.

Plotting

I want a network where arrows point from leading to lagging nodes. Unlagged connections should be represented as lines.

# spearRhoP_lagged4 <- spearRhoP_lagged3 %>%
#   filter(fdr < 0.05) %>%
#   mutate(arrow = recode(delay, `-1` = "<", `1` = ">", `0` = "-"))
LaggedSpearGraph <- graph_from_data_frame(spearRhoP_lagged4 %>% filter(fdr < 0.05))
LaggedSpearGraph
set.seed(333)
plot(LaggedSpearGraph,vertex.size=2, vertex.label.cex = 0.75, edge.arrow.mode = E(LaggedSpearGraph)$arrow, vertex.label = NA, edge.arrow.size = .5)

Clearly, I’m not an igraph artist but you get the idea.

SPARCC

As above but this time with sparcc

Sparcc doesn’t allow relative abundance data, it has to be counts. We can fake this with alrisa, by multiplying everything by 1000 and then rounding to the nearist

spot

tp0 <- proc.time()
lagSparcc <- sparcc(spotWLagCounts)
tp1 <- proc.time()
tp1 - tp0
   user  system elapsed 
  0.361   0.021   0.379 

Bootstrapping step, so we can have p values

tp0 <- proc.time()
bootSparcc <- sparccboot(spotWLagCounts, R = 100)
tp1 <- proc.time()
tp1 - tp0
   user  system elapsed 
 66.196   0.092  66.290 

Slow, of course. In real life you’d want to do at least 1000 permutations.

Calculate P values

PSparcc <- pval.sparccboot(bootSparcc)
data.frame(PSparcc$cors, PSparcc$pvals)

Extract from the triangular matrix I ought to make a function to do this automatically.

clean_Psparcc <- function(ps_mtx, cNames){
  
  cors <-ps_mtx$cors
  pvals <- ps_mtx$pvals
  
  nVars <- length(cNames)
  
  # Dump the values into a rectangular matrix
  # Empty matrix
  sparCCpcors <- diag(0.5, nrow = nVars, ncol = nVars)
  # Fill in upper triangle
  sparCCpcors[upper.tri(sparCCpcors, diag=FALSE)] <- cors
  # Fill in lower triangle
  sparCCpcors <- sparCCpcors + t(sparCCpcors)
  
  # As above, but for p values
  sparCCpval <- diag(0.5, nrow = nVars, ncol = nVars)
  sparCCpval[upper.tri(sparCCpval, diag=FALSE)] <- pvals
  sparCCpval <- sparCCpval + t(sparCCpval)
  
  rownames(sparCCpcors) <- cNames
  colnames(sparCCpcors) <- cNames
  rownames(sparCCpval) <- cNames
  colnames(sparCCpval) <- cNames
  
  return(list(cors = sparCCpcors, p = sparCCpval))
}

cleanSparccData <- clean_Psparcc(PSparcc, colnames(spotWLag))
reshape_sparcc <- function(csparcc){
  sparCCpcors <- csparcc$cors
  sparCCpval <- csparcc$p

reordered_all_sparcc <- reorder_cor_and_p(sparCCpcors, sparCCpval)
reordered_sparccCor <- reordered_all_sparcc$r
reordered_sparccP<- reordered_all_sparcc$p


sparccCor_processed <- reordered_sparccCor  %>% get_upper_tri() %>% reshape2::melt() %>% na.omit() %>% rename(cor = value)
sparccP_processed <- reordered_sparccP  %>% get_upper_tri() %>% reshape2::melt() %>% na.omit() %>% rename(p = value)

# join the two data frames

SparccP <- left_join(sparccCor_processed, sparccP_processed, by = c("Var1", "Var2")) #%>%
  # # remove self correlations
  # filter(Var1 != Var2) %>% 
  # calculate the false discovery rate to adjust for multiple p values, not yet
  #mutate(fdr = p.adjust(p, method = "BH"))
SparccP
}

longLaggedSparccData <- reshape_sparcc(cleanSparccData)
head(longLaggedSparccData)

So all of my p-values are coming out really high, and the strongest correlations are returning as NA

wrangledLaggedSparccData <- wrangle_lagged(longLaggedSparccData, coef = "cor")
wrangledLaggedSparccData
wrangledLaggedSparccData %>% pull(p)
  [1] 0.12 0.00 0.00 0.02 0.00 0.00 0.64 0.20 0.40 0.12 0.24 0.44 0.00 0.00 0.00 0.00 0.86 0.00 0.00 0.04 0.00
 [22] 0.00 0.00 0.58 0.70 0.16 0.02 0.18 0.02 0.62 0.02 0.12 0.04 0.02 0.12 0.00 0.00 0.00 0.06 0.14 0.00 0.46
 [43] 0.06 0.00 0.00 0.00 0.32 0.54 0.04 0.60 0.00 0.00 0.18 0.32 0.12 0.88 0.30 0.58 0.20 0.02 0.06 0.66 0.04
 [64] 0.02 0.16 0.34 0.60 0.00 0.40 0.92 0.08 0.92 0.22 0.08 0.00 0.26 0.30 0.82 0.64 0.80 0.82 0.44 0.16 0.74
 [85] 0.52 0.66 0.68 0.56 0.78 0.70 0.64 0.82 0.00 0.96 0.54 0.50 0.60 0.04 0.74 0.10 0.58 0.80 0.10 0.98 0.02
[106] 0.10 0.00 0.02 0.82 0.74 0.00 0.06 0.36 0.38 0.00 0.00 0.02 0.46 0.16 0.32 0.56 0.12 0.00 0.08 0.00 0.02
[127] 0.00 0.00 0.44 0.86 0.10 0.00 0.14 0.32 0.10 0.10 0.94 0.50 0.82 0.18 0.00 0.38 0.10 0.10 0.82 0.76 0.96
[148] 0.10 0.32 0.40 0.50 0.12 0.10 0.00 0.26 0.08 0.08 0.58 0.38 0.02 0.04 0.16 0.60 0.02 0.44 0.00 0.08 0.02
[169] 0.00 0.84 0.08 0.68 0.34 0.12 0.58 0.38 0.34 0.60 0.18 0.80 0.60 0.30 0.02 0.04 0.06 0.04 0.06 0.98 0.94
[190] 0.14 0.36 0.64 0.68 0.26 0.82 0.54 0.92 0.24 0.62 0.22 0.62 0.14 0.00 0.88 0.56 0.32 0.74 0.52 0.02 0.08
[211] 0.02 0.00 0.00 0.40 0.98 0.84 0.00 0.74 0.74 0.04 0.00 0.00 0.00 0.66 0.10 0.00 0.06 0.00 0.08 0.00 0.04
LaggedSparccGraph <- graph_from_data_frame(wrangledLaggedSparccData %>% filter(fdr < 0.05))
LaggedSparccGraph
IGRAPH 5b38e28 DN-- 21 48 -- 
+ attr: name (v/c), cor (e/n), p (e/n), delay (e/n), fdr (e/n), arrow (e/c)
+ edges from 5b38e28 (vertex names):
 [1] ARISA_573.6->ARISA_662   ARISA_573.6->ARISA_570.1 ARISA_662  ->ARISA_573.6 ARISA_570.1->ARISA_573.6
 [5] ARISA_666.4->ARISA_686.9 ARISA_570.1->ARISA_435.5 ARISA_666.4->ARISA_435.5 ARISA_686.9->ARISA_435.5
 [9] ARISA_570.1->ARISA_538.9 ARISA_573.6->ARISA_538.9 ARISA_686.9->ARISA_538.9 ARISA_435.5->ARISA_538.9
[13] ARISA_686.9->ARISA_666.4 ARISA_570.1->ARISA_561.8 ARISA_573.6->ARISA_561.8 ARISA_435.5->ARISA_561.8
[17] ARISA_594.1->ARISA_561.8 ARISA_666.4->ARISA_594.1 ARISA_435.5->ARISA_594.1 ARISA_561.8->ARISA_594.1
[21] ARISA_435.5->ARISA_689.8 ARISA_594.1->ARISA_689.8 ARISA_686.9->ARISA_682.4 ARISA_670.5->ARISA_682.4
[25] ARISA_828.8->ARISA_831.8 ARISA_662  ->ARISA_402.4 ARISA_686.9->ARISA_402.4 ARISA_828.8->ARISA_402.4
[29] ARISA_831.8->ARISA_402.4 ARISA_670.5->ARISA_419.5 ARISA_828.8->ARISA_419.5 ARISA_421.5->ARISA_419.5
+ ... omitted several edges
set.seed(333)
plot(LaggedSparccGraph,vertex.size=2, vertex.label.cex = 0.75, edge.arrow.mode = E(LaggedSpearGraph)$arrow, vertex.label = NA, edge.arrow.size = .5)

As you can see, this network looks more like mola-mola as drawn by Picasso than the spearman one, but otherwise pretty qualitatively similar.

LS0tCnRpdGxlOiAiVGltZSBMYWdnZWQgQW5hbHlzaXMgaW4gUiIKb3V0cHV0OiBodG1sX25vdGVib29rCmF1dGhvcjogIkphY29iIENyYW0iCi0tLQoKVGhlIHBvaW50IG9mIHRoaXMgbGVzc29uIGlzIHRvIHNob3cgdGhhdCB5b3UgY2FuIGRvIGEgdGltZSBsYWdnZWQgbmV0d29yayBpbiBSLCBubyBMU0EgcHJvZ3JhbSByZXF1aXJlZC4gVGhpcyBza2lwcyB0aGUgImxvY2FsIiBlbGVtZW50IG9mIExvY2FsIFNpbWlsYXJpdHkgQW5hbHlzaXMsIGJ1dCBJIGZvdW5kIHRoYXQgaW4gbXkgcmVzZWFyY2ggSSB3YXMgZWxlY3RpbmcganVzdCB0byBkbyB0aW1lIGxhZ2dlZCBzcGVhcm1hbiBuZXR3b3Jrcy4gU2ltaWxhcmx5LCBvbmUgY291bGQgdXNlIHRoaXMgc2FtZSBhcHByb2FjaCB0byBidWlsZCBhIHRpbWUgbGFnZ2VkIHNwYXJDQyBuZXR3b3JrIG9yIG1vc3Qgb3RoZXIgc3RhdGlzdGljcy4KClRoaXMgbWF5IG9yIG1heSBub3Qgd29yayBmb3IgZ3JhcGhpY2FsIGxhc3NvIGFwcHJvYWNoZXMuCgoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeSh6b28pCmxpYnJhcnkoU3BpZWNFYXNpKQpsaWJyYXJ5KHBzeWNoKQpsaWJyYXJ5KGlncmFwaCkKYGBgCiMgUmVhZCBpbgoKTGV0cyBicmluZyBpbiBzb21lIHRpbWUtc2VyaWVzIGRhdGEgZnJvbSB0aGUgU1BPVCBkYXRhc2V0LiBUaGVzZSBhcmUgQVJJU0EgZnJhZ2VtZW50cywgdGhlaXIgbGVuZ3RocyBhc3NvY2lhdGUgd2l0aCBzcGVjaWVzIGlkZW50aXR5LiBJIGRvd25sb2FkZWQgdGhlIGRhdGEgaGVyZToKCmh0dHBzOi8vd3d3LmJjby1kbW8ub3JnL2RhdGFzZXQvNTM1OTE1CgpBbmQgdGhlbiByZW1vdmVkIHNvbWUgdmFyaWFibGVzIHRoYXQgd2UgYXJlIG5vdCB1c2luZyB5ZXQuIAoKVGhlIGRhdGEgc2V0IGlzIHNvbWV3aGF0IGxhcmdlLiBUbyBzYXZlIHNwYWNlLCBJJ3ZlIHppcHBlZCBpdC4KCkZvcnR1bmF0ZWx5LCBSIGNhbiByZWFkIHppcHBlZCBjc3YgZmlsZXMganVzdCBmaW5lLgpTb21lIG9mIHRoZSBtb250aHMgYXJlIG1pc3NpbmcgYXJyaXNhIGRhdGEgYW5kIGFyZSBsb2FkZWQgaW4gYXMgIm5kIi4gV2UgY2FuIHJlbW92ZSB0aG9zZSBsYXRlciwgYnV0IHdlIHRlbGwgUiBzbyBpdCBkb2Vzbid0IGZyZWFrIG91dC4KCmBgYHtyfQojIHJlYWQgaW4gZGF0YQpzcG90U3VyZmFjZSA8LSByZWFkX2NzdigiU1BPVC9zcG90X2FyaXNhX3N1cmZhY2UuemlwIikgJT4lCiAgIyB0cmVhdCBkYXRlIGNvbHVtbiBhcyBhIGRhdGUsIHJhdGhlciB0aGFuIGEgY2hhcmFjdGVyIHN0cmluZwogIG11dGF0ZShkYXRlX2xvY2FsID0geW1kKGRhdGVfbG9jYWwpKQpgYGAKCiMgUmVtb3ZpbmcgbG93IG9jY3VyYW5jZSB0YXhhCgpIb3cgbWFueSB0aW1lcyBkbyB3ZSBzZWUgZWFjaCB0YXhvbj8KSG93IGFidW5kYW50IGFyZSB0aGV5LiAKV2UgZmlsdGVyIHRoZSB0YXhhIGEgbG90IGlmIHdlIG9ubHkga2VlcCB0aGluZ3Mgd2l0aCBhIG1lYW4gYWJ1bmRhbmNlIG9mIGF0IGxlYXN0IDAuNSUKYGBge3J9Cmhvd01hbnkgPC0gc3BvdFN1cmZhY2UgJT4lCiAgZ3JvdXBfYnkoYXJpc2FfZnJhZykgJT4lCiAgc3VtbWFyaXplKHNpZ2h0aW5ncyA9IHN1bShuYS5vbWl0KHJlbF9hYnVuZCA+IDApKSwKICAgICAgICAgICAgbWVhbkFidW4gPSBtZWFuKG5hLm9taXQocmVsX2FidW5kKSkpICU+JQogIGFycmFuZ2UoLXNpZ2h0aW5ncykKIAoKa2VlcFRheGEgPC0gaG93TWFueSAlPiUgCiAgZmlsdGVyKHNpZ2h0aW5ncyA+PSA1LCBtZWFuQWJ1biA+PSAwLjAxKSAlPiUKICBwdWxsKGFyaXNhX2ZyYWcpCgprZWVwVGF4YQpgYGAKCmBgYHtyfQpzcG90U3VyZmFjZTIgPC0gc3BvdFN1cmZhY2UgJT4lIGZpbHRlcihhcmlzYV9mcmFnICVpbiUga2VlcFRheGEpCmBgYAoKCiMgUHJvY2Vzc2luZwoKTGV0cyByZXNoYXBlIGV2ZXJ5dGhpbmcgaW50byBhIHdpZGUgZm9ybWF0IGRhdGEgZnJhbWUKCmBgYHtyfQpzcG90V2lkZSA8LSBzcG90U3VyZmFjZTIgJT4lIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBhcmlzYV9mcmFnLCB2YWx1ZXNfZnJvbSA9IHJlbF9hYnVuZCkKYGBgCgpUaGVyZSBhcmUgbG90cyBvZiBtb250aHMgd2l0aCBtaXNzaW5nIGRhdGEsIGFuZCBsb3RzIG9mIG1vbnRocyB0aGF0IGFyZSBtaXNzaW5nIGJ1dCBkb24ndCBoYXZlIHJvd3MuIExldHMgdXBkYXRlIHRoaXMgc28gdGhhdCB3ZSBoYXZlIGEgcm93IGZvciBldmVyeSBtb250aCwgZXZlbiBpZiBpdCBoYXMgTkEgdmFsdWVzLgoKYGBge3J9CnNwb3RXaWRlMiA8LSBzcG90V2lkZSAlPiUKICBuYS5vbWl0ICU+JQogIG11dGF0ZSh5ciA9IHllYXIoZGF0ZV9sb2NhbCksIG10aCA9IG1vbnRoKGRhdGVfbG9jYWwpKSAlPiUKICBzZWxlY3QoeXIsIG10aCwgZGF0ZV9sb2NhbCwgZXZlcnl0aGluZygpKSAlPiUKICBhcnJhbmdlKHlyLCBtdGgpIApgYGAKCmBgYHtyfQoKZ29hbERhdGVzIDwtIHRpYmJsZSgKICB5ciA9IHJlcChmaXJzdChzcG90V2lkZTIkeXIpOmxhc3Qoc3BvdFdpZGUyJHlyKSwgZWFjaCA9IDEyKSwKICBtdGggPSByZXAoMToxMiwgbGFzdChzcG90V2lkZTIkeXIpIC0gZmlyc3Qoc3BvdFdpZGUyJHlyKSArIDEpCikgJT4lCiAgbXV0YXRlKGZpbGxlcl9kYXRlID0geW1kKHBhc3RlKHlyLCBtdGgsIDE1LCBzZXAgPSAiXyIpKSkgJT4lCiAgZmlsdGVyKGZpbGxlcl9kYXRlID4gbWluKHNwb3RXaWRlMiRkYXRlX2xvY2FsKSAmCiAgICAgICAgICAgZmlsbGVyX2RhdGUgPCBtYXgoc3BvdFdpZGUyJGRhdGVfbG9jYWwpKSAlPiUKICBzZWxlY3QoLWZpbGxlcl9kYXRlKQpgYGAKCmBgYHtyfQpzcG90V2lkZUFsbE1vbnRocyA8LSBsZWZ0X2pvaW4oZ29hbERhdGVzLCBzcG90V2lkZTIpCmBgYAoKT2suIFNvIHdlJ3JlIGdvaW5nIHRvIGFkZHJlc3MgdGhpcyBtaXNzaW5nIG5vdCBhdCByYW5kb20gZGF0YSBpbiBhIHdheSB0aGF0IGNyaXRpY3MgbWlnaHQgY2FsbCByZWNrbGVzcy4gTGluZWFybHkgIGludGVycG9sYXRpbmcgaXQuCmBgYHtyfQpzcG90SW50ZXJwIDwtIHNwb3RXaWRlQWxsTW9udGhzICU+JQogIG11dGF0ZV9hdCh2YXJzKG1hdGNoZXMoIkFSSVNBIikpLCBuYS5hcHByb3gpCmBgYAoKYGBge3J9CnNwb3RJbnRlcnAyIDwtIHNwb3RJbnRlcnAgJT4lCiAgbXV0YXRlKGRhdGVfbG9jYWwgPSBpZl9lbHNlKAogICAgaXMubmEoZGF0ZV9sb2NhbCksCiAgICB5bWQocGFzdGUoeXIsIG10aCwgMTUpKSwKICAgIGRhdGVfbG9jYWwpCiAgKSAlPiUKICBzZWxlY3QoLWMoeXIsIG10aCkpCmBgYAoKYGBge3J9CnNwb3RJbnRlcnBNdHggPC0gc3BvdEludGVycDIgJT4lCiAgY29sdW1uX3RvX3Jvd25hbWVzKCJkYXRlX2xvY2FsIikgJT4lCiAgYXMubWF0cml4KCkKCnNwb3RDbHJNdHggPC0gY2xyKHNwb3RJbnRlcnBNdHgpCmBgYAoKCiMgTGFncwpPaywgc28gd2l0aCB0aGlzIGRhdGFzZXQsIHdlIGNhbiBkbyBhbnkgb2YgdGhlIHRoaW5ncyBmcm9tIHRoZSBlYXJsaWVyIGxlc3NvbnMuCldlIGNhbiBhbHNvIGV4cG9ydCBpdCBmb3IgbG9jYWwgc2ltaWxhcml0eSBhbmFseXNpcyBvdXRzaWRlIG9mIFIuCgpPaywgbGV0cyBtYWtlIGEgbmV3IG1hdHJpeCwgd2hlcmUgdGhlIGNvbHVtbnMgYXJlIGRhdGVzLCBidXQgdGhlbiB0aGVyZSBhcmUgYW5vdGhlciBzZXJpZXMgb2YgY29sdW1ucyBvZiBkYXRlcyBsYWdnZWQgYnkgb25lLgoKYGBge3J9CgpzcG90VW5MYWcgPC0gc3BvdEludGVycE10eFstMSxdCnNwb3RMYWcgPC0gc3BvdEludGVycE10eFstbnJvdyhzcG90SW50ZXJwTXR4KSxdCmNvbG5hbWVzKHNwb3RVbkxhZykgPC0gcGFzdGUoIm5vbGFnIiwgY29sbmFtZXMoc3BvdFVuTGFnKSwgc2VwID0gIi0iKQpjb2xuYW1lcyhzcG90TGFnKSA8LSBwYXN0ZSgibGFnMSIsIGNvbG5hbWVzKHNwb3RMYWcpLCBzZXAgPSAiLSIpCnNwb3RXTGFnIDwtIGNiaW5kKHNwb3RVbkxhZywgc3BvdExhZykKYGBgCgpOb3cgd2UnbGwgY29ycmVsYXRlIGV2ZXJ5dGhpbmcgdnMgZXZlcnl0aW5nLiBLZWVwIGluIG1pbmQgdGhhdCB0aGUgdW5sYWdnZWQgdnMgdW5sYWdnZWQgYXJlIG1pc3Npbmcgb25lIHJvdy4gV2UgY291bGQgZ2V0IGFyb3VuZCB0aGlzIGJ5IGRvaW5nIGV2ZXJ5dGhpbmcgaW4gdHdvIGJhdGNoZXMuCgoKCiMgQ2FsY3VsYXRpbmcgY2xyLXNwZWFybWFuIG1hdHJpeAoKIyMgTm9uIGxhZ2dlZCBkYXRhCgpgYGB7cn0Kc3BlYXJDb3JUZXN0Q2xyIDwtIGNvcnIudGVzdChzcG90SW50ZXJwTXR4LCBtZXRob2QgPSAic3BlYXJtYW4iLCBhZGp1c3QgPSAibm9uZSIpCnNwZWFyQ29yQ2xyIDwtIHNwZWFyQ29yVGVzdENsciRyCnNwZWFyUENsciA8LSBzcGVhckNvclRlc3RDbHIkcApgYGAKCgojIyBMYWdnZWQgZGF0YQoKYGBge3J9CnNwZWFyQ29yVGVzdENscl9MYWdnZWQgPC0gY29yci50ZXN0KHNwb3RXTGFnLCBtZXRob2QgPSAic3BlYXJtYW4iLCBhZGp1c3QgPSAibm9uZSIpCnNwZWFyQ29yQ2xyX0xhZ2dlZCA8LSBzcGVhckNvclRlc3RDbHJfTGFnZ2VkJHIKc3BlYXJQQ2xyX0xhZ2dlZCA8LSBzcGVhckNvclRlc3RDbHJfTGFnZ2VkJHAKYGBgCgpgYGB7cn0Kc291cmNlKCJqYWNvYl9saWJyYXJ5LlIiKQpgYGAKClNvbWUgZGF0YSB3cmFuZ2xpbmcsIGFzIHBlciBteSBlYXJsaWVyIGxlc3NvbgoKYGBge3J9CnJlb3JkZXJlZF9zcGVhckNvclRlc3RDbHJfTGFnZ2VkIDwtIHJlb3JkZXJfY29yX2FuZF9wKHNwZWFyQ29yQ2xyX0xhZ2dlZCwgc3BlYXJQQ2xyX0xhZ2dlZCkKc3BlYXJDb3JDbHJfTGFnZ2VkX1Jlb3JkZXJlZCA8LSByZW9yZGVyZWRfc3BlYXJDb3JUZXN0Q2xyX0xhZ2dlZCRyCnNwZWFyUENscl9MYWdnZWRfUmVvcmRlcmVkIDwtIHJlb3JkZXJlZF9zcGVhckNvclRlc3RDbHJfTGFnZ2VkJHAKYGBgCgpgYGB7cn0Kc3BlYXJDb3JDbHJfTGFnZ2VkX1Byb2MgPC0gc3BlYXJDb3JDbHJfTGFnZ2VkX1Jlb3JkZXJlZCAlPiUgZ2V0X3VwcGVyX3RyaSgpICU+JSByZXNoYXBlMjo6bWVsdCgpICU+JSBuYS5vbWl0KCkgJT4lIHJlbmFtZShyaG8gPSB2YWx1ZSkKc3BlYXJQQ2xyX0xhZ2dlZF9Qcm9jIDwtIHNwZWFyUENscl9MYWdnZWRfUmVvcmRlcmVkICU+JSBnZXRfdXBwZXJfdHJpKCkgJT4lIHJlc2hhcGUyOjptZWx0KCkgJT4lIG5hLm9taXQoKSAlPiUgcmVuYW1lKHAgPSB2YWx1ZSkKYGBgCgpgYGB7cn0Kc3BlYXJSaG9QX2xhZ2dlZCA8LSBsZWZ0X2pvaW4oc3BlYXJDb3JDbHJfTGFnZ2VkX1Byb2MsIHNwZWFyUENscl9MYWdnZWRfUHJvYywgYnkgPSBjKCJWYXIxIiwgIlZhcjIiKSkKc3BlYXJSaG9QX2xhZ2dlZApgYGAKCiMjIFBhcnNpbmcgb3V0IHRoZSBsYWdzCgpgYGB7cn0Kd3JhbmdsZV9sYWdnZWQgPC0gZnVuY3Rpb24ocHJlV3JhbmdsZWQsIGNvZWYgPSAicmhvIil7ICMgSSBuZWVkIHRvIGFsbG93IHRoZSBjb2VmZmljaWVudCBuYW1lIHRvIGNoYW5nZSBmb3IgdGhlIHNwYXJjYyBzdHVmZgogICMgSW5pdGlhbCB3cmFuZ2xpbmcKICBwb3N0V3JhbmdsZWQgPC0gcHJlV3JhbmdsZWQgJT4lIAogIHNlcGFyYXRlKFZhcjEsIGMoIlYxIiwgIlZhcjEiKSwgc2VwID0gIi0iKSAlPiUKICBzZXBhcmF0ZShWYXIyLCBjKCJWMiIsICJWYXIyIiksIHNlcCA9ICItIikgJT4lCiAgIyBnZXQgcmlkIG9mIHJvd3Mgd2hlcmUgYm90aCB2YXJpYWJsZXMgYXJlIGxhZ2VkCiAgZmlsdGVyKCEoVjEgPT0gImxhZzEiICYgVjIgPT0gImxhZzEiKSkgJT4lCiAgZmlsdGVyKFZhcjEgIT0gVmFyMikgJT4lCiAgbXV0YXRlKGRlbGF5ID0gaWZfZWxzZShWMSA9PSAibGFnMSIgJiBWMiA9PSAibm9sYWciLCAtMSwKICAgICAgICAgaWZfZWxzZShWMSA9PSAibm9sYWciICYgVjIgPT0gImxhZzEiLCAxLAogICAgICAgICAgICAgICAgIGlmX2Vsc2UoVjEgPT0gIm5vbGFnIiAmIFYyID09ICJub2xhZyIsIDAsIC05OTk5KQogICAgICAgICApCiAgKQogICkgJT4lCiAgbXV0YXRlKGZkciA9cC5hZGp1c3QocCwgbWV0aG9kID0gIkJIIikpCiAgCiMgTm93IHdlIHNlbGVjdCB0aGUgZGVsYXkgd2l0aCB0aGUgaGlnaGVzdCBzY29yZS4KIyAKIyBOb3RlIHRoYXQgd2UgY2FsY3VsYXRlZCB0aGUgZmFsc2UgZGlzY292ZXJ5IHJhdGUgKmJlZm9yZSogd2Ugc2VsZWN0ZWQgdGhlIHZhbHVlIHdpdGggdGhlIGhpZ2hlc3Qgc2NvcmUsIGJ1dCAqYWZ0ZXIqIHdlIHJlbW92ZWQgdGhlIGRlbGF5LXZzLWRlbGF5IGNvbXBhcmFzb25zLgogIAogIGJlc3RMYWcgPC0gcG9zdFdyYW5nbGVkICU+JQogIGdyb3VwX2J5KFZhcjEsIFZhcjIpICU+JSAKICB0b3BfbigxLCAhIWFzLm5hbWUoY29lZikpICU+JQogIHNlbGVjdCgtYyhWMSwgVjIpKQogIAogIAogICMjIEFkZCBhcnJvd3MgZm9yIGVhc2llciBpZ3JhcGggcGxvdHRpbmcKICAKICBhcnJvd2VkRGF0YSA8LSBiZXN0TGFnICU+JQogICNmaWx0ZXIoZmRyIDwgMC4wNSkgJT4lCiAgbXV0YXRlKGFycm93ID0gcmVjb2RlKGRlbGF5LCBgLTFgID0gIjwiLCBgMWAgPSAiPiIsIGAwYCA9ICItIikpCiAgCiAgYXJyb3dlZERhdGEKfQoKCnNwZWFyUmhvUF9sYWdnZWQ0PC0gc3BlYXJSaG9QX2xhZ2dlZCAlPiUgd3JhbmdsZV9sYWdnZWQKYGBgCgpgYGB7cn0KIyBzcGVhclJob1BfbGFnZ2VkMiA8LSBzcGVhclJob1BfbGFnZ2VkICU+JSAKIyAgIHNlcGFyYXRlKFZhcjEsIGMoIlYxIiwgIlZhcjEiKSwgc2VwID0gIi0iKSAlPiUKIyAgIHNlcGFyYXRlKFZhcjIsIGMoIlYyIiwgIlZhcjIiKSwgc2VwID0gIi0iKSAlPiUKIyAgICMgZ2V0IHJpZCBvZiByb3dzIHdoZXJlIGJvdGggdmFyaWFibGVzIGFyZSBsYWdlZAojICAgZmlsdGVyKCEoVjEgPT0gImxhZzEiICYgVjIgPT0gImxhZzEiKSkgJT4lCiMgICBmaWx0ZXIoVmFyMSAhPSBWYXIyKSAlPiUKIyAgIG11dGF0ZShkZWxheSA9IGlmX2Vsc2UoVjEgPT0gImxhZzEiICYgVjIgPT0gIm5vbGFnIiwgLTEsCiMgICAgICAgICAgaWZfZWxzZShWMSA9PSAibm9sYWciICYgVjIgPT0gImxhZzEiLCAxLAojICAgICAgICAgICAgICAgICAgaWZfZWxzZShWMSA9PSAibm9sYWciICYgVjIgPT0gIm5vbGFnIiwgMCwgLTk5OTkpCiMgICAgICAgICAgKQojICAgKQojICAgKSAlPiUKIyAgIG11dGF0ZShmZHIgPXAuYWRqdXN0KHAsIG1ldGhvZCA9ICJCSCIpKQojICAgCiMgc3BlYXJSaG9QX2xhZ2dlZDIgCmBgYApOb3cgd2Ugc2VsZWN0IHRoZSBkZWxheSB3aXRoIHRoZSBoaWdoZXN0IHNjb3JlLgoKTm90ZSB0aGF0IHdlIGNhbGN1bGF0ZWQgdGhlIGZhbHNlIGRpc2NvdmVyeSByYXRlICpiZWZvcmUqIHdlIHNlbGVjdGVkIHRoZSB2YWx1ZSB3aXRoIHRoZSBoaWdoZXN0IHNjb3JlLCBidXQgKmFmdGVyKiB3ZSByZW1vdmVkIHRoZSBkZWxheS12cy1kZWxheSBjb21wYXJhc29ucy4KCmBgYHtyfQojIHNwZWFyUmhvUF9sYWdnZWQzIDwtIHNwZWFyUmhvUF9sYWdnZWQyICU+JQojICAgZ3JvdXBfYnkoVmFyMSwgVmFyMikgJT4lIAojICAgdG9wX24oMSwgcmhvKSAlPiUKIyAgIHNlbGVjdCgtYyhWMSwgVjIpKQojIHNwZWFyUmhvUF9sYWdnZWQzCmBgYAoKQW5kIHRoZXJlIHlvdSBoYXZlIGEgdGFibGUgdGhhdCBjYW4gZ28gaW50byBhIG5ldHdvcmsuClNvbWUgdGhvdWdodHMuIFRoZXJlIGlzIHdheSBtb3JlIGRhdGEgaGFuZGxpbmcgdG8gZG8gdGhpcyB3aXRoIHNwYXJjYywgYnV0IGl0IGNvdWxkIGJlIGRvbmUuCkkgcmVhbGx5IG91Z2h0IHRvIGF1dG9tYXRlIHRoaXMgaW50byBhIGdlbmVyYWwgdGltZSBkZWxheSBmdW5jdGlvbi4KSSBkb24ndCB0aGluayB0aGlzIHdvcmtzIGZvciBncmFwaGljYWwgbGFzb28gYnV0IGl0IGNvdWxkIGJlIHRyaWVkLgoKQXV0b2NvcnJlbGF0aW9uIG1heSBpbmZsYXRlIHNvbWUgb2YgdGhlc2Ugc2NvcmVzLgoKIyBQbG90dGluZwoKSSB3YW50IGEgbmV0d29yayB3aGVyZSBhcnJvd3MgcG9pbnQgZnJvbSBsZWFkaW5nIHRvIGxhZ2dpbmcgbm9kZXMuIFVubGFnZ2VkIGNvbm5lY3Rpb25zIHNob3VsZCBiZSByZXByZXNlbnRlZCBhcyBsaW5lcy4KCmBgYHtyfQojIHNwZWFyUmhvUF9sYWdnZWQ0IDwtIHNwZWFyUmhvUF9sYWdnZWQzICU+JQojICAgZmlsdGVyKGZkciA8IDAuMDUpICU+JQojICAgbXV0YXRlKGFycm93ID0gcmVjb2RlKGRlbGF5LCBgLTFgID0gIjwiLCBgMWAgPSAiPiIsIGAwYCA9ICItIikpCmBgYAoKCmBgYHtyfQpMYWdnZWRTcGVhckdyYXBoIDwtIGdyYXBoX2Zyb21fZGF0YV9mcmFtZShzcGVhclJob1BfbGFnZ2VkNCAlPiUgZmlsdGVyKGZkciA8IDAuMDUpKQpMYWdnZWRTcGVhckdyYXBoCmBgYAoKYGBge3J9CnNldC5zZWVkKDMzMykKcGxvdChMYWdnZWRTcGVhckdyYXBoLHZlcnRleC5zaXplPTIsIHZlcnRleC5sYWJlbC5jZXggPSAwLjc1LCBlZGdlLmFycm93Lm1vZGUgPSBFKExhZ2dlZFNwZWFyR3JhcGgpJGFycm93LCB2ZXJ0ZXgubGFiZWwgPSBOQSwgZWRnZS5hcnJvdy5zaXplID0gLjUpCmBgYAoKQ2xlYXJseSwgSSdtIG5vdCBhbiBpZ3JhcGggYXJ0aXN0IGJ1dCB5b3UgZ2V0IHRoZSBpZGVhLgoKIyBTUEFSQ0MKQXMgYWJvdmUgYnV0IHRoaXMgdGltZSB3aXRoIHNwYXJjYwoKU3BhcmNjIGRvZXNuJ3QgYWxsb3cgcmVsYXRpdmUgYWJ1bmRhbmNlIGRhdGEsIGl0IGhhcyB0byBiZSBjb3VudHMuIFdlIGNhbiBmYWtlIHRoaXMgd2l0aCBhbHJpc2EsIGJ5IG11bHRpcGx5aW5nIGV2ZXJ5dGhpbmcgYnkgMTAwMCBhbmQgdGhlbiByb3VuZGluZyB0byB0aGUgbmVhcmlzdAoKc3BvdApgYGB7cn0Kc3BvdFdMYWdDb3VudHMgPC0gcm91bmQoc3BvdFdMYWcgKiAxMDAwKQpgYGAKCmBgYHtyfQp0cDAgPC0gcHJvYy50aW1lKCkKbGFnU3BhcmNjIDwtIHNwYXJjYyhzcG90V0xhZ0NvdW50cykKdHAxIDwtIHByb2MudGltZSgpCnRwMSAtIHRwMApgYGAKCkJvb3RzdHJhcHBpbmcgc3RlcCwgc28gd2UgY2FuIGhhdmUgcCB2YWx1ZXMKCmBgYHtyfQp0cDAgPC0gcHJvYy50aW1lKCkKYm9vdFNwYXJjYyA8LSBzcGFyY2Nib290KHNwb3RXTGFnQ291bnRzLCBSID0gMTAwKQp0cDEgPC0gcHJvYy50aW1lKCkKdHAxIC0gdHAwCmBgYApTbG93LCBvZiBjb3Vyc2UuIEluIHJlYWwgbGlmZSB5b3UnZCB3YW50IHRvIGRvIGF0IGxlYXN0IDEwMDAgcGVybXV0YXRpb25zLgoKQ2FsY3VsYXRlIFAgdmFsdWVzCmBgYHtyfQpQU3BhcmNjIDwtIHB2YWwuc3BhcmNjYm9vdChib290U3BhcmNjKQpkYXRhLmZyYW1lKFBTcGFyY2MkY29ycywgUFNwYXJjYyRwdmFscykKYGBgCgpFeHRyYWN0IGZyb20gdGhlIHRyaWFuZ3VsYXIgbWF0cml4Ckkgb3VnaHQgdG8gbWFrZSBhIGZ1bmN0aW9uIHRvIGRvIHRoaXMgYXV0b21hdGljYWxseS4KCmBgYHtyfQpjbGVhbl9Qc3BhcmNjIDwtIGZ1bmN0aW9uKHBzX210eCwgY05hbWVzKXsKICAKICBjb3JzIDwtcHNfbXR4JGNvcnMKICBwdmFscyA8LSBwc19tdHgkcHZhbHMKICAKICBuVmFycyA8LSBsZW5ndGgoY05hbWVzKQogIAogICMgRHVtcCB0aGUgdmFsdWVzIGludG8gYSByZWN0YW5ndWxhciBtYXRyaXgKICAjIEVtcHR5IG1hdHJpeAogIHNwYXJDQ3Bjb3JzIDwtIGRpYWcoMC41LCBucm93ID0gblZhcnMsIG5jb2wgPSBuVmFycykKICAjIEZpbGwgaW4gdXBwZXIgdHJpYW5nbGUKICBzcGFyQ0NwY29yc1t1cHBlci50cmkoc3BhckNDcGNvcnMsIGRpYWc9RkFMU0UpXSA8LSBjb3JzCiAgIyBGaWxsIGluIGxvd2VyIHRyaWFuZ2xlCiAgc3BhckNDcGNvcnMgPC0gc3BhckNDcGNvcnMgKyB0KHNwYXJDQ3Bjb3JzKQogIAogICMgQXMgYWJvdmUsIGJ1dCBmb3IgcCB2YWx1ZXMKICBzcGFyQ0NwdmFsIDwtIGRpYWcoMC41LCBucm93ID0gblZhcnMsIG5jb2wgPSBuVmFycykKICBzcGFyQ0NwdmFsW3VwcGVyLnRyaShzcGFyQ0NwdmFsLCBkaWFnPUZBTFNFKV0gPC0gcHZhbHMKICBzcGFyQ0NwdmFsIDwtIHNwYXJDQ3B2YWwgKyB0KHNwYXJDQ3B2YWwpCiAgCiAgcm93bmFtZXMoc3BhckNDcGNvcnMpIDwtIGNOYW1lcwogIGNvbG5hbWVzKHNwYXJDQ3Bjb3JzKSA8LSBjTmFtZXMKICByb3duYW1lcyhzcGFyQ0NwdmFsKSA8LSBjTmFtZXMKICBjb2xuYW1lcyhzcGFyQ0NwdmFsKSA8LSBjTmFtZXMKICAKICByZXR1cm4obGlzdChjb3JzID0gc3BhckNDcGNvcnMsIHAgPSBzcGFyQ0NwdmFsKSkKfQoKY2xlYW5TcGFyY2NEYXRhIDwtIGNsZWFuX1BzcGFyY2MoUFNwYXJjYywgY29sbmFtZXMoc3BvdFdMYWcpKQpgYGAKCmBgYHtyfQpyZXNoYXBlX3NwYXJjYyA8LSBmdW5jdGlvbihjc3BhcmNjKXsKICBzcGFyQ0NwY29ycyA8LSBjc3BhcmNjJGNvcnMKICBzcGFyQ0NwdmFsIDwtIGNzcGFyY2MkcAoKcmVvcmRlcmVkX2FsbF9zcGFyY2MgPC0gcmVvcmRlcl9jb3JfYW5kX3Aoc3BhckNDcGNvcnMsIHNwYXJDQ3B2YWwpCnJlb3JkZXJlZF9zcGFyY2NDb3IgPC0gcmVvcmRlcmVkX2FsbF9zcGFyY2MkcgpyZW9yZGVyZWRfc3BhcmNjUDwtIHJlb3JkZXJlZF9hbGxfc3BhcmNjJHAKCgpzcGFyY2NDb3JfcHJvY2Vzc2VkIDwtIHJlb3JkZXJlZF9zcGFyY2NDb3IgICU+JSBnZXRfdXBwZXJfdHJpKCkgJT4lIHJlc2hhcGUyOjptZWx0KCkgJT4lIG5hLm9taXQoKSAlPiUgcmVuYW1lKGNvciA9IHZhbHVlKQpzcGFyY2NQX3Byb2Nlc3NlZCA8LSByZW9yZGVyZWRfc3BhcmNjUCAgJT4lIGdldF91cHBlcl90cmkoKSAlPiUgcmVzaGFwZTI6Om1lbHQoKSAlPiUgbmEub21pdCgpICU+JSByZW5hbWUocCA9IHZhbHVlKQoKIyBqb2luIHRoZSB0d28gZGF0YSBmcmFtZXMKClNwYXJjY1AgPC0gbGVmdF9qb2luKHNwYXJjY0Nvcl9wcm9jZXNzZWQsIHNwYXJjY1BfcHJvY2Vzc2VkLCBieSA9IGMoIlZhcjEiLCAiVmFyMiIpKSAjJT4lCiAgIyAjIHJlbW92ZSBzZWxmIGNvcnJlbGF0aW9ucwogICMgZmlsdGVyKFZhcjEgIT0gVmFyMikgJT4lIAogICMgY2FsY3VsYXRlIHRoZSBmYWxzZSBkaXNjb3ZlcnkgcmF0ZSB0byBhZGp1c3QgZm9yIG11bHRpcGxlIHAgdmFsdWVzLCBub3QgeWV0CiAgI211dGF0ZShmZHIgPSBwLmFkanVzdChwLCBtZXRob2QgPSAiQkgiKSkKU3BhcmNjUAp9Cgpsb25nTGFnZ2VkU3BhcmNjRGF0YSA8LSByZXNoYXBlX3NwYXJjYyhjbGVhblNwYXJjY0RhdGEpCmhlYWQobG9uZ0xhZ2dlZFNwYXJjY0RhdGEpCmBgYAoKU28gYWxsIG9mIG15IHAtdmFsdWVzIGFyZSBjb21pbmcgb3V0IHJlYWxseSBoaWdoLCBhbmQgdGhlIHN0cm9uZ2VzdCBjb3JyZWxhdGlvbnMgYXJlIHJldHVybmluZyBhcyBOQQoKYGBge3J9CndyYW5nbGVkTGFnZ2VkU3BhcmNjRGF0YSA8LSB3cmFuZ2xlX2xhZ2dlZChsb25nTGFnZ2VkU3BhcmNjRGF0YSwgY29lZiA9ICJjb3IiKQp3cmFuZ2xlZExhZ2dlZFNwYXJjY0RhdGEKYGBgCgpgYGB7cn0Kd3JhbmdsZWRMYWdnZWRTcGFyY2NEYXRhICU+JSBwdWxsKHApCmBgYAoKYGBge3J9CkxhZ2dlZFNwYXJjY0dyYXBoIDwtIGdyYXBoX2Zyb21fZGF0YV9mcmFtZSh3cmFuZ2xlZExhZ2dlZFNwYXJjY0RhdGEgJT4lIGZpbHRlcihmZHIgPCAwLjA1KSkKTGFnZ2VkU3BhcmNjR3JhcGgKYGBgCgpgYGB7cn0Kc2V0LnNlZWQoMzMzKQpwbG90KExhZ2dlZFNwYXJjY0dyYXBoLHZlcnRleC5zaXplPTIsIHZlcnRleC5sYWJlbC5jZXggPSAwLjc1LCBlZGdlLmFycm93Lm1vZGUgPSBFKExhZ2dlZFNwZWFyR3JhcGgpJGFycm93LCB2ZXJ0ZXgubGFiZWwgPSBOQSwgZWRnZS5hcnJvdy5zaXplID0gLjUpCmBgYAoKQXMgeW91IGNhbiBzZWUsIHRoaXMgbmV0d29yayBsb29rcyBtb3JlIGxpa2UgbW9sYS1tb2xhIGFzIGRyYXduIGJ5IFBpY2Fzc28gdGhhbiB0aGUgc3BlYXJtYW4gb25lLCBidXQgb3RoZXJ3aXNlIHByZXR0eSBxdWFsaXRhdGl2ZWx5IHNpbWlsYXIu