This workflow should serve as step-by-step guidance starting from
downloading a dataset from Neotoma and processing it, to estimating
ecosystem property (diversity in this case).
:warning: This workflow is only meant as an example:
There are several additional steps for data preparation which should be
done for any fossil pollen dataset from Neotoma!
See FOSSILPOL,
an R-based modular workflow to process multiple fossil pollen records to
create a comprehensive, standardised dataset compilation, ready for
multi-record and multi-proxy analyses at various spatial and temporal
scales.
Attach packages
library(tidyverse) # general data wrangling and visualisation ✨
library(pander) # nice tables 😍
library(neotoma2) # access to the Neotoma database 🌿
library(Bchron) # age-depth modelling 🕰️
library(mgcv) # GAM fitting 📈
library(marginaleffects) # predicting trends 📈
library(janitor) # string cleaning 🧹
library(here) # for working directory 🗺️
ggplot2::theme_set(
ggplot2::theme_bw() +
ggplot2::theme(
axis.title = ggplot2::element_text(size = 25),
axis.text = ggplot2::element_text(size = 15),
strip.text = ggplot2::element_text(size = 15),
panel.grid = ggplot2::element_blank()
)
)
Download a dataset from Neotoma
Here we have selected the Lingua d’Oca (ID = 52162)
record by Feredico Di Rita.
Reference paper: Di Rita, F., A. Celant, and D. Magri. 2010. Holocene
environmental instability in the wetland north of the Tiber delta (Rome,
Italy): sea-lake-man interactions. Journal of Paleolimnology
44:51-67.
sel_dataset_download <-
neotoma2::get_downloads(52162)
Prepare the pollen counts
# get samples
sel_counts <-
neotoma2::samples(sel_dataset_download)
# select only "pollen" taxa
sel_taxon_list_selected <-
neotoma2::taxa(sel_dataset_download) %>%
dplyr::filter(element == "pollen") %>%
purrr::pluck("variablename")
# prepare taxa table
sel_counts_selected <-
sel_counts %>%
as.data.frame() %>%
dplyr::mutate(sample_id = as.character(sampleid)) %>%
tibble::as_tibble() %>%
dplyr::select("sample_id", "value", "variablename") %>%
# only include selected taxons
dplyr::filter(
variablename %in% sel_taxon_list_selected
) %>%
# turn into the wider format
tidyr::pivot_wider(
names_from = "variablename",
values_from = "value",
values_fill = 0
) %>%
# clean names
janitor::clean_names()
head(sel_counts_selected)[, 1:5]
510392 |
1 |
1 |
1 |
1 |
510393 |
3 |
0 |
0 |
0 |
510394 |
6 |
0 |
1 |
0 |
510396 |
5 |
0 |
0 |
0 |
510391 |
8 |
0 |
0 |
0 |
510395 |
6 |
0 |
0 |
0 |
Here, we strongly advocate that attention should be paid to the
selection of the ecological groups, the selection of depositional
environments, as well as the harmonisation of the pollen taxa. However,
that is not the subject of this workflow, but any analysis to be
published needs careful preparation of the fossil pollen datasets!
We can now try to visualise the taxa per sample_id
sel_counts_selected %>%
tibble::rowid_to_column("ID") %>%
tidyr::pivot_longer(
cols = -c(sample_id, ID),
names_to = "taxa",
values_to = "n_grains"
) %>%
ggplot2::ggplot(
mapping = ggplot2::aes(
x = ID,
y = n_grains,
fill = taxa
),
) +
ggplot2::geom_bar(
stat = "identity",
position = "fill"
) +
ggplot2::labs(
x = "sample_id",
y = "proportion of pollen grains"
) +
ggplot2::theme(
axis.text.x = ggplot2::element_blank(),
legend.position = "none"
)
Preparation of the levels
Sample depth
Extract depth for each level
sel_level <-
neotoma2::samples(sel_dataset_download) %>%
tibble::as_tibble() %>%
dplyr::mutate(sample_id = as.character(sampleid)) %>%
dplyr::distinct(sample_id, depth) %>%
dplyr::relocate(sample_id)
head(sel_level)
510392 |
5 |
510393 |
10 |
510394 |
15 |
510396 |
20 |
510391 |
25 |
510395 |
30 |
Age-depth modelling
We highly recommend recalculating the age-depth model ‘de Novo’ as
different methodologies might have been applied to each record. However,
age-depth modelling is a very complicated topic, which we will not dig
into today. Just note that many parts of the age-depth modelling need
attention (selection of chronology control points, using correct
calibration curves, etc.).
# Here we only present a few of the important steps of preparation of the
# chronology control table. There are many more potential issues, but
# solving those is not the focus of this workflow.
# First, get the chronologies and check which we want to use used
sel_chron_control_table_download <-
neotoma2::chroncontrols(sel_dataset_download)
# prepare the table
sel_chron_control_table <-
sel_chron_control_table_download %>%
# Here select the ID of one of the chronology
dplyr::filter(chronologyid == 37228) %>%
tibble::as_tibble() %>%
# Here we calculate the error as the average of the age `limitolder` and
# `agelimityounger`
dplyr::mutate(
error = round((agelimitolder - agelimityounger) / 2)
) %>%
# As Bchron cannot accept an error of 0, we need to replace the value with 1
dplyr::mutate(
error = replace(error, error == 0, 1),
error = ifelse(is.na(error), 1, error)
) %>%
# As Bchron cannot accept an thickness of 0, we need to replace the value with 1
dplyr::mutate(
thickness = ifelse(is.na(thickness), 1, thickness)
) %>%
# We need to specify which calibration curve should be used for what point
dplyr::mutate(
curve = ifelse(as.data.frame(sel_dataset_download)["lat"] > 0, "intcal20", "shcal20"),
curve = ifelse(chroncontroltype != "Radiocarbon", "normal", curve)
) %>%
tibble::column_to_rownames("chroncontrolid") %>%
dplyr::arrange(depth) %>%
dplyr::select(
chroncontrolage, error, depth, thickness, chroncontroltype, curve
)
i_multiplier <- 0.1 # increase to 5
# Those are default values suggested by the Bchron package
n_iteration_default <- 10e3
n_burn_default <- 2e3
n_thin_default <- 8
# Let's multiply them by our i_multiplier
n_iteration <- n_iteration_default * i_multiplier
n_burn <- n_burn_default * i_multiplier
n_thin <- max(c(1, n_thin_default * i_multiplier))
# run Bchron
ad_model <-
Bchron::Bchronology(
ages = sel_chron_control_table$chroncontrolage,
ageSds = sel_chron_control_table$error,
positions = sel_chron_control_table$depth,
calCurves = sel_chron_control_table$curve,
positionThicknesses = sel_chron_control_table$thickness,
iterations = n_iteration,
burn = n_burn,
thin = n_thin
)
Visually check the age-depth models
plot(ad_model)
Predict ages
age_position <-
Bchron:::predict.BchronologyRun(object = ad_model, newPositions = sel_level$depth)
age_uncertainties <-
age_position %>%
as.data.frame() %>%
dplyr::mutate_all(., as.integer) %>%
as.matrix()
colnames(age_uncertainties) <- sel_level$sample_id
# Let's take the median age of all possible ages (i.e. the estimated age
# from each age-depth model run) as our default.
sel_level_predicted <-
sel_level %>%
dplyr::mutate(
age = apply(
age_uncertainties, 2,
stats::quantile,
probs = 0.5
)
)
Visualisation of our data
Let’s now make a simple pollen diagram with proportions of the main
pollen taxa (x-axis) against our estimated ages along depth
(y-axis).
data_rownames <-
sel_counts_selected %>%
tibble::column_to_rownames("sample_id")
data_percentages <-
(data_rownames / rowSums(data_rownames)) * 100
col_sum_non_zero <-
colSums(data_percentages) > 0
data_filtered <-
data_percentages %>%
dplyr::select(
dplyr::any_of(
names(col_sum_non_zero)[col_sum_non_zero]
)
) %>%
tibble::rownames_to_column("sample_id") %>%
dplyr::relocate(sample_id) %>%
tibble::tibble()
data_filtered %>%
dplyr::inner_join(
sel_level_predicted,
by = dplyr::join_by(sample_id)
) %>%
tidyr::pivot_longer(
cols = -c(sample_id, depth, age),
names_to = "taxa",
values_to = "proportion_of_grains"
) %>%
dplyr::group_by(taxa) %>%
# Calculate the average proportion of grains
dplyr::mutate(
avg_prop = mean(proportion_of_grains)
) %>%
# Only keep the main taxa (on average >= 1% pollen grains)
dplyr::filter(avg_prop >= 1) %>%
dplyr::ungroup() %>%
ggplot2::ggplot(
mapping = ggplot2::aes(
y = age,
x = proportion_of_grains,
xmax = proportion_of_grains,
xmin = 0,
fill = taxa,
col = taxa
),
) +
ggplot2::geom_ribbon() +
ggplot2::scale_y_continuous(trans = "reverse") +
ggplot2::scale_x_continuous(breaks = c(0, 1)) +
ggplot2::facet_wrap(~taxa, nrow = 1) +
ggplot2::theme(
legend.position = "none",
panel.border = ggplot2::element_blank(),
strip.background = ggplot2::element_blank(),
strip.text = ggplot2::element_blank(),
axis.ticks.x = ggplot2::element_blank(),
axis.text.x = ggplot2::element_blank()
) +
ggplot2::labs(
x = "Proportion of grains",
y = "Age (cal yr BP)"
)
Estimation of ecosystem property
Now we will use our prepared fossil pollen data to estimate the
diversity. We will use {REcopol} package, which has easy-to-use
functions to analyse fossil pollen data. See package website for
more information. Specifically, we will estimate rarefied values of Hill
numbers.
# function to estimate diversity
get_diversity <- function(data_source, round = TRUE, sel_method = "") {
# helper function
get_diversity_taxonomic <- function(data_matrix, sample_size) {
hill0 <- function(data, sample_size) {
data_sub <- data[data > 0]
data_sum <- sum(data_sub)
if (sample_size <= data_sum) {
res <- sum(1 - exp(lchoose(data_sum - data_sub, sample_size) -
lchoose(data_sum, sample_size)))
return(res)
} else {
return(0)
}
}
fk_hat <- function(data, sample_size) {
data_sub <- data[data > 0]
data_sum <- sum(data_sub)
if (sample_size <= data_sum) {
sub <- function(k) {
sum(exp(lchoose(data_sub, k) + lchoose(data_sum -
data_sub, sample_size - k) - lchoose(
data_sum,
sample_size
)))
}
res <- sapply(1:sample_size, sub)
return(res)
} else {
return(0)
}
}
hill1 <- function(data, sample_size) {
data_sub <- data[data > 0]
data_sum <- sum(data_sub)
if (sample_size <= data_sum) {
k <- 1:sample_size
res <- exp(-sum(k / sample_size * log(k / sample_size) *
fk_hat(data_sub, sample_size)))
return(res)
} else {
return(0)
}
}
hill2 <- function(data, sample_size) {
data_sub <- data[data > 0]
data_sum <- sum(data_sub)
if (sample_size <= data_sum) {
res <- 1 / (1 / sample_size + (1 - 1 / sample_size) * sum(data_sub *
(data_sub - 1) / data_sum / (data_sum - 1)))
return(res)
} else {
return(0)
}
}
est_n_0 <-
sapply(sample_size, function(n) {
apply(data_matrix, 1, hill0, sample_size = n)
})
est_n_1 <-
sapply(sample_size, function(n) {
apply(data_matrix, 1, hill1, sample_size = n)
})
est_n_2 <-
sapply(sample_size, function(n) {
apply(data_matrix, 1, hill2, sample_size = n)
})
hill_diversity <-
cbind(est_n_0, est_n_1, est_n_2, est_n_1 -
est_n_2, est_n_2 / est_n_1, est_n_1 / est_n_0) %>%
as.data.frame() %>%
tibble::as_tibble() %>%
dplyr::mutate(sample_id = row.names(data_matrix)) %>%
tibble::column_to_rownames("sample_id") %>%
purrr::set_names(
nm = c(
"n0",
"n1", "n2", "n1_minus_n2", "n2_divided_by_n1", "n1_divided_by_n0"
)
)
return(hill_diversity)
}
if (
isTRUE(round)
) {
data_matrix <-
data_source %>%
tibble::column_to_rownames("sample_id") %>%
dplyr::mutate_all(., .f = floor) %>%
as.matrix() %>%
round()
} else {
data_matrix <-
data_source %>%
tibble::column_to_rownames("sample_id") %>%
as.matrix()
}
sample_size <-
apply(data_matrix, 1, sum) %>%
floor() %>%
min()
div <-
get_diversity_taxonomic(
data_matrix = data_matrix,
sample_size = sample_size
)
res <-
div %>%
tibble::rownames_to_column("sample_id") %>%
dplyr::relocate(sample_id) %>%
as.data.frame() %>%
tibble::as_tibble()
return(res)
}
data_diversity <-
get_diversity(
data_source = sel_counts_selected,
sel_method = "taxonomic"
)
head(data_diversity)
Table continues below
510392 |
25.17 |
12.76 |
8.095 |
4.664 |
0.6344 |
510393 |
24.39 |
11.66 |
6.795 |
4.867 |
0.5826 |
510394 |
24.33 |
11.13 |
6.707 |
4.426 |
0.6024 |
510396 |
20.29 |
7.68 |
3.952 |
3.728 |
0.5146 |
510391 |
26.27 |
13.12 |
7.43 |
5.694 |
0.5661 |
510395 |
22.78 |
11.89 |
7.615 |
4.279 |
0.6402 |
0.5068 |
0.4782 |
0.4576 |
0.3784 |
0.4995 |
0.522 |
Now we can fit a temporal trend as a GAM model.
data_to_fit <-
dplyr::inner_join(
data_diversity,
sel_level_predicted,
by = "sample_id"
)
mod_n0 <-
mgcv::gam(
n0 ~ s(age, k = 25, bs = "tp"),
data = data_to_fit,
method = "REML",
family = mgcv::tw(link = "log")
)
summary(mod_n0)
#>
#> Family: Tweedie(p=1.01)
#> Link function: log
#>
#> Formula:
#> n0 ~ s(age, k = 25, bs = "tp")
#>
#> Parametric coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 2.9714 0.0221 134.5 <2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> Approximate significance of smooth terms:
#> edf Ref.df F p-value
#> s(age) 4.675 5.839 14.26 <2e-16 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> R-sq.(adj) = 0.597 Deviance explained = 60.1%
#> -REML = 178.14 Scale est. = 0.58249 n = 64
# mgcv::gam.check(mod_n0, k.sample = 10e3, k.rep = 1e3)
Now we can visualise the results.
age_dummy <-
tibble::tibble(
age = seq(
from = min(data_to_fit$age),
to = max(data_to_fit$age),
length.out = 100
)
)
data_predicted <-
marginaleffects::predictions(
model = mod_n0,
newdata = age_dummy
) %>%
tibble::as_tibble() %>%
dplyr::rename(
fit = estimate,
lwr = conf.low,
upr = conf.high,
sd_error = std.error
) %>%
dplyr::select(
dplyr::all_of(
c(
"age",
"fit",
"sd_error",
"lwr",
"upr"
)
)
)
data_predicted %>%
ggplot2::ggplot(
mapping = ggplot2::aes(
x = age,
y = fit
)
) +
ggplot2::geom_ribbon(
mapping = ggplot2::aes(
ymin = lwr,
ymax = upr
),
alpha = 0.2
) +
ggplot2::geom_line() +
geom_point(
data = data_to_fit,
mapping = ggplot2::aes(
y = n0
)
) +
ggplot2::scale_x_continuous(
trans = "reverse"
) +
ggplot2::labs(
y = "Hill's N0",
x = "Age (cal yr BP)"
)
LS0tCnRpdGxlOiBTdGVwLWJ5LXN0ZXAgZ3VpZGUKZm9ybWF0OiAKICBnZm06CiAgICBmaWctd2lkdGg6IDcKICAgIGZpZy1oZWlnaHQ6IDYKICAgIHdyYXA6IG5vbmUKLS0tCgpgYGB7ciBjaHVuay1zZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGNvbGxhcHNlID0gVFJVRSwKICBjb21tZW50ID0gIiM+IgopCmBgYAoKVGhpcyB3b3JrZmxvdyBzaG91bGQgc2VydmUgYXMgc3RlcC1ieS1zdGVwIGd1aWRhbmNlIHN0YXJ0aW5nIGZyb20gZG93bmxvYWRpbmcgYSBkYXRhc2V0IGZyb20gTmVvdG9tYSBhbmQgcHJvY2Vzc2luZyBpdCwgdG8gZXN0aW1hdGluZyBlY29zeXN0ZW0gcHJvcGVydHkgKGRpdmVyc2l0eSBpbiB0aGlzIGNhc2UpLgoKOndhcm5pbmc6ICoqVGhpcyB3b3JrZmxvdyBpcyBvbmx5IG1lYW50IGFzIGFuIGV4YW1wbGUqKjogVGhlcmUgYXJlIHNldmVyYWwgYWRkaXRpb25hbCBzdGVwcyBmb3IgZGF0YSBwcmVwYXJhdGlvbiB3aGljaCBzaG91bGQgYmUgZG9uZSBmb3IgYW55IGZvc3NpbCBwb2xsZW4gZGF0YXNldCBmcm9tIE5lb3RvbWEhCgpTZWUgWyoqRk9TU0lMUE9MKipdKGh0dHBzOi8vaG9wZS11aWItYmlvLmdpdGh1Yi5pby9GT1NTSUxQT0wtd2Vic2l0ZS8pLCBhbiBSLWJhc2VkIG1vZHVsYXIgd29ya2Zsb3cgdG8gcHJvY2VzcyBtdWx0aXBsZSBmb3NzaWwgcG9sbGVuIHJlY29yZHMgdG8gY3JlYXRlIGEgY29tcHJlaGVuc2l2ZSwgc3RhbmRhcmRpc2VkIGRhdGFzZXQgY29tcGlsYXRpb24sIHJlYWR5IGZvciBtdWx0aS1yZWNvcmQgYW5kIG11bHRpLXByb3h5IGFuYWx5c2VzIGF0IHZhcmlvdXMgc3BhdGlhbCBhbmQgdGVtcG9yYWwgc2NhbGVzLgoKIyMgSW5zdGFsbCBwYWNrYWdlcwoKUGxlYXNlIGZvbGxvdyB0aGUgW3ByZS13b3Jrc2hvcCBpbnN0cnVjdGlvbnNdKC9kb2NzL3ByZV93b3Jrc2hvcC5odG1sKSB0byBtYWtlIHN1cmUgYWxsIHBhY2thZ2VzIGFyZSBpbnN0YWxsZWQuCgojIyBBdHRhY2ggcGFja2FnZXMKCmBgYHtyIHBrZy1hdHRhY2gsIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkgIyBnZW5lcmFsIGRhdGEgd3JhbmdsaW5nIGFuZCB2aXN1YWxpc2F0aW9uIOKcqApsaWJyYXJ5KHBhbmRlcikgIyBuaWNlIHRhYmxlcyDwn5iNCmxpYnJhcnkobmVvdG9tYTIpICMgYWNjZXNzIHRvIHRoZSBOZW90b21hIGRhdGFiYXNlIPCfjL8KbGlicmFyeShCY2hyb24pICMgYWdlLWRlcHRoIG1vZGVsbGluZyDwn5Ww77iPCmxpYnJhcnkobWdjdikgIyBHQU0gZml0dGluZyDwn5OICmxpYnJhcnkobWFyZ2luYWxlZmZlY3RzKSAjIHByZWRpY3RpbmcgdHJlbmRzIPCfk4gKbGlicmFyeShqYW5pdG9yKSAjIHN0cmluZyBjbGVhbmluZyDwn6e5CmxpYnJhcnkoaGVyZSkgIyBmb3Igd29ya2luZyBkaXJlY3Rvcnkg8J+Xuu+4jwpgYGAKCmBgYHtyIHRoZW1lLXNldHVwLCBpbmNsdWRlPVRSVUV9CmdncGxvdDI6OnRoZW1lX3NldCgKICBnZ3Bsb3QyOjp0aGVtZV9idygpICsKICAgIGdncGxvdDI6OnRoZW1lKAogICAgICBheGlzLnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAyNSksCiAgICAgIGF4aXMudGV4dCA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gMTUpLAogICAgICBzdHJpcC50ZXh0ID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxNSksCiAgICAgIHBhbmVsLmdyaWQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCkKICAgICkKKQpgYGAKCiMjIERvd25sb2FkIGEgZGF0YXNldCBmcm9tIE5lb3RvbWEKCkhlcmUgd2UgaGF2ZSBzZWxlY3RlZCB0aGUgKipMaW5ndWEgZCdPY2EqKiAoSUQgPSA1MjE2MikgcmVjb3JkIGJ5IEZlcmVkaWNvIERpIFJpdGEuCgpSZWZlcmVuY2UgcGFwZXI6IERpIFJpdGEsIEYuLCBBLiBDZWxhbnQsIGFuZCBELiBNYWdyaS4gMjAxMC4gSG9sb2NlbmUgZW52aXJvbm1lbnRhbCBpbnN0YWJpbGl0eSBpbiB0aGUgd2V0bGFuZCBub3J0aCBvZiB0aGUgVGliZXIgZGVsdGEgKFJvbWUsIEl0YWx5KTogc2VhLWxha2UtbWFuIGludGVyYWN0aW9ucy4gKkpvdXJuYWwgb2YgUGFsZW9saW1ub2xvZ3kqIDQ0OjUxLTY3LgoKYGBge3IgZG93bmxvYWRfb2ZfZGF0YSwgcmVzdWx0cz0naGlkZScsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnNlbF9kYXRhc2V0X2Rvd25sb2FkIDwtCiAgbmVvdG9tYTI6OmdldF9kb3dubG9hZHMoNTIxNjIpCmBgYAoKIyMgUHJlcGFyZSB0aGUgcG9sbGVuIGNvdW50cwoKYGBge3IgY291bnRfcHJlcGFyYXRpb24sIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nPUZBTFNFfQojIGdldCBzYW1wbGVzCnNlbF9jb3VudHMgPC0KICBuZW90b21hMjo6c2FtcGxlcyhzZWxfZGF0YXNldF9kb3dubG9hZCkKCiMgc2VsZWN0IG9ubHkgInBvbGxlbiIgdGF4YQpzZWxfdGF4b25fbGlzdF9zZWxlY3RlZCA8LQogIG5lb3RvbWEyOjp0YXhhKHNlbF9kYXRhc2V0X2Rvd25sb2FkKSAlPiUKICBkcGx5cjo6ZmlsdGVyKGVsZW1lbnQgPT0gInBvbGxlbiIpICU+JQogIHB1cnJyOjpwbHVjaygidmFyaWFibGVuYW1lIikKCiMgcHJlcGFyZSB0YXhhIHRhYmxlCnNlbF9jb3VudHNfc2VsZWN0ZWQgPC0KICBzZWxfY291bnRzICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBkcGx5cjo6bXV0YXRlKHNhbXBsZV9pZCA9IGFzLmNoYXJhY3RlcihzYW1wbGVpZCkpICU+JQogIHRpYmJsZTo6YXNfdGliYmxlKCkgJT4lCiAgZHBseXI6OnNlbGVjdCgic2FtcGxlX2lkIiwgInZhbHVlIiwgInZhcmlhYmxlbmFtZSIpICU+JQogICMgb25seSBpbmNsdWRlIHNlbGVjdGVkIHRheG9ucwogIGRwbHlyOjpmaWx0ZXIoCiAgICB2YXJpYWJsZW5hbWUgJWluJSBzZWxfdGF4b25fbGlzdF9zZWxlY3RlZAogICkgJT4lCiAgIyB0dXJuIGludG8gdGhlIHdpZGVyIGZvcm1hdAogIHRpZHlyOjpwaXZvdF93aWRlcigKICAgIG5hbWVzX2Zyb20gPSAidmFyaWFibGVuYW1lIiwKICAgIHZhbHVlc19mcm9tID0gInZhbHVlIiwKICAgIHZhbHVlc19maWxsID0gMAogICkgJT4lCiAgIyBjbGVhbiBuYW1lcwogIGphbml0b3I6OmNsZWFuX25hbWVzKCkKCmhlYWQoc2VsX2NvdW50c19zZWxlY3RlZClbLCAxOjVdCmBgYAoKYGBge3IgY291bnRfZGlwbGF5LCBlY2hvPUZBTFNFLCByZXN1bHRzPSdhc2lzJywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KcGFuZGVyOjpwYW5kb2MudGFibGUoaGVhZChzZWxfY291bnRzX3NlbGVjdGVkKVssIDE6NV0pCmBgYAoKSGVyZSwgd2Ugc3Ryb25nbHkgYWR2b2NhdGUgdGhhdCBhdHRlbnRpb24gc2hvdWxkIGJlIHBhaWQgdG8gdGhlIHNlbGVjdGlvbiBvZiB0aGUgZWNvbG9naWNhbCBncm91cHMsIHRoZSBzZWxlY3Rpb24gb2YgZGVwb3NpdGlvbmFsIGVudmlyb25tZW50cywgYXMgd2VsbCBhcyB0aGUgaGFybW9uaXNhdGlvbiBvZiB0aGUgcG9sbGVuIHRheGEuIEhvd2V2ZXIsIHRoYXQgaXMgbm90IHRoZSBzdWJqZWN0IG9mIHRoaXMgd29ya2Zsb3csIGJ1dCBhbnkgYW5hbHlzaXMgdG8gYmUgcHVibGlzaGVkIG5lZWRzIGNhcmVmdWwgcHJlcGFyYXRpb24gb2YgdGhlIGZvc3NpbCBwb2xsZW4gZGF0YXNldHMhCgpXZSBjYW4gbm93IHRyeSB0byB2aXN1YWxpc2UgdGhlIHRheGEgcGVyIHNhbXBsZV9pZAoKYGBge3IgY291bnRfdmlzfQpzZWxfY291bnRzX3NlbGVjdGVkICU+JQogIHRpYmJsZTo6cm93aWRfdG9fY29sdW1uKCJJRCIpICU+JQogIHRpZHlyOjpwaXZvdF9sb25nZXIoCiAgICBjb2xzID0gLWMoc2FtcGxlX2lkLCBJRCksCiAgICBuYW1lc190byA9ICJ0YXhhIiwKICAgIHZhbHVlc190byA9ICJuX2dyYWlucyIKICApICU+JQogIGdncGxvdDI6OmdncGxvdCgKICAgIG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoCiAgICAgIHggPSBJRCwKICAgICAgeSA9IG5fZ3JhaW5zLAogICAgICBmaWxsID0gdGF4YQogICAgKSwKICApICsKICBnZ3Bsb3QyOjpnZW9tX2JhcigKICAgIHN0YXQgPSAiaWRlbnRpdHkiLAogICAgcG9zaXRpb24gPSAiZmlsbCIKICApICsKICBnZ3Bsb3QyOjpsYWJzKAogICAgeCA9ICJzYW1wbGVfaWQiLAogICAgeSA9ICJwcm9wb3J0aW9uIG9mIHBvbGxlbiBncmFpbnMiCiAgKSArCiAgZ2dwbG90Mjo6dGhlbWUoCiAgICBheGlzLnRleHQueCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIgogICkKYGBgCgojIyBQcmVwYXJhdGlvbiBvZiB0aGUgbGV2ZWxzCgojIyMgU2FtcGxlIGRlcHRoCgpFeHRyYWN0IGRlcHRoIGZvciBlYWNoIGxldmVsCgpgYGB7ciBsZXZlbF9wcmVwYXJpb24sIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nPUZBTFNFfQpzZWxfbGV2ZWwgPC0KICBuZW90b21hMjo6c2FtcGxlcyhzZWxfZGF0YXNldF9kb3dubG9hZCkgJT4lCiAgdGliYmxlOjphc190aWJibGUoKSAlPiUKICBkcGx5cjo6bXV0YXRlKHNhbXBsZV9pZCA9IGFzLmNoYXJhY3RlcihzYW1wbGVpZCkpICU+JQogIGRwbHlyOjpkaXN0aW5jdChzYW1wbGVfaWQsIGRlcHRoKSAlPiUKICBkcGx5cjo6cmVsb2NhdGUoc2FtcGxlX2lkKQoKaGVhZChzZWxfbGV2ZWwpCmBgYAoKYGBge3IgbGV2ZWxfZGlwbGF5LCBlY2hvPUZBTFNFLCByZXN1bHRzPSdhc2lzJywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KcGFuZGVyOjpwYW5kb2MudGFibGUoaGVhZChzZWxfbGV2ZWwpKQpgYGAKCiMjIyBBZ2UtZGVwdGggbW9kZWxsaW5nCgpXZSBoaWdobHkgcmVjb21tZW5kIHJlY2FsY3VsYXRpbmcgdGhlIGFnZS1kZXB0aCBtb2RlbCAnZGUgTm92bycgYXMgZGlmZmVyZW50IG1ldGhvZG9sb2dpZXMgbWlnaHQgaGF2ZSBiZWVuIGFwcGxpZWQgdG8gZWFjaCByZWNvcmQuIEhvd2V2ZXIsIGFnZS1kZXB0aCBtb2RlbGxpbmcgaXMgYSB2ZXJ5IGNvbXBsaWNhdGVkIHRvcGljLCB3aGljaCB3ZSB3aWxsIG5vdCBkaWcgaW50byB0b2RheS4gSnVzdCBub3RlIHRoYXQgbWFueSBwYXJ0cyBvZiB0aGUgYWdlLWRlcHRoIG1vZGVsbGluZyBuZWVkIGF0dGVudGlvbiAoc2VsZWN0aW9uIG9mIGNocm9ub2xvZ3kgY29udHJvbCBwb2ludHMsIHVzaW5nIGNvcnJlY3QgY2FsaWJyYXRpb24gY3VydmVzLCBldGMuKS4KCmBgYHtyIHJ1bl9iY2hyb24sIHJlc3VsdHM9J2hpZGUnLCB3YXJuaW5nPUZBTFNFfQojIEhlcmUgd2Ugb25seSBwcmVzZW50IGEgZmV3IG9mIHRoZSBpbXBvcnRhbnQgc3RlcHMgb2YgcHJlcGFyYXRpb24gb2YgdGhlCiMgICBjaHJvbm9sb2d5IGNvbnRyb2wgdGFibGUuIFRoZXJlIGFyZSBtYW55IG1vcmUgcG90ZW50aWFsIGlzc3VlcywgYnV0CiMgICBzb2x2aW5nIHRob3NlIGlzIG5vdCB0aGUgZm9jdXMgb2YgdGhpcyB3b3JrZmxvdy4KCiMgRmlyc3QsIGdldCB0aGUgY2hyb25vbG9naWVzIGFuZCBjaGVjayB3aGljaCB3ZSB3YW50IHRvIHVzZSB1c2VkCnNlbF9jaHJvbl9jb250cm9sX3RhYmxlX2Rvd25sb2FkIDwtCiAgbmVvdG9tYTI6OmNocm9uY29udHJvbHMoc2VsX2RhdGFzZXRfZG93bmxvYWQpCgojIHByZXBhcmUgdGhlIHRhYmxlCnNlbF9jaHJvbl9jb250cm9sX3RhYmxlIDwtCiAgc2VsX2Nocm9uX2NvbnRyb2xfdGFibGVfZG93bmxvYWQgJT4lCiAgIyBIZXJlIHNlbGVjdCB0aGUgSUQgb2Ygb25lIG9mIHRoZSBjaHJvbm9sb2d5CiAgZHBseXI6OmZpbHRlcihjaHJvbm9sb2d5aWQgPT0gMzcyMjgpICU+JQogIHRpYmJsZTo6YXNfdGliYmxlKCkgJT4lCiAgIyBIZXJlIHdlIGNhbGN1bGF0ZSB0aGUgZXJyb3IgYXMgdGhlIGF2ZXJhZ2Ugb2YgdGhlIGFnZSBgbGltaXRvbGRlcmAgYW5kCiAgIyAgIGBhZ2VsaW1pdHlvdW5nZXJgCiAgZHBseXI6Om11dGF0ZSgKICAgIGVycm9yID0gcm91bmQoKGFnZWxpbWl0b2xkZXIgLSBhZ2VsaW1pdHlvdW5nZXIpIC8gMikKICApICU+JQogICMgQXMgQmNocm9uIGNhbm5vdCBhY2NlcHQgYW4gZXJyb3Igb2YgMCwgd2UgbmVlZCB0byByZXBsYWNlIHRoZSB2YWx1ZSB3aXRoIDEKICBkcGx5cjo6bXV0YXRlKAogICAgZXJyb3IgPSByZXBsYWNlKGVycm9yLCBlcnJvciA9PSAwLCAxKSwKICAgIGVycm9yID0gaWZlbHNlKGlzLm5hKGVycm9yKSwgMSwgZXJyb3IpCiAgKSAlPiUKICAjIEFzIEJjaHJvbiBjYW5ub3QgYWNjZXB0IGFuIHRoaWNrbmVzcyBvZiAwLCB3ZSBuZWVkIHRvIHJlcGxhY2UgdGhlIHZhbHVlIHdpdGggMQogIGRwbHlyOjptdXRhdGUoCiAgICB0aGlja25lc3MgPSBpZmVsc2UoaXMubmEodGhpY2tuZXNzKSwgMSwgdGhpY2tuZXNzKQogICkgJT4lCiAgIyBXZSBuZWVkIHRvIHNwZWNpZnkgd2hpY2ggY2FsaWJyYXRpb24gY3VydmUgc2hvdWxkIGJlIHVzZWQgZm9yIHdoYXQgcG9pbnQKICBkcGx5cjo6bXV0YXRlKAogICAgY3VydmUgPSBpZmVsc2UoYXMuZGF0YS5mcmFtZShzZWxfZGF0YXNldF9kb3dubG9hZClbImxhdCJdID4gMCwgImludGNhbDIwIiwgInNoY2FsMjAiKSwKICAgIGN1cnZlID0gaWZlbHNlKGNocm9uY29udHJvbHR5cGUgIT0gIlJhZGlvY2FyYm9uIiwgIm5vcm1hbCIsIGN1cnZlKQogICkgJT4lCiAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoImNocm9uY29udHJvbGlkIikgJT4lCiAgZHBseXI6OmFycmFuZ2UoZGVwdGgpICU+JQogIGRwbHlyOjpzZWxlY3QoCiAgICBjaHJvbmNvbnRyb2xhZ2UsIGVycm9yLCBkZXB0aCwgdGhpY2tuZXNzLCBjaHJvbmNvbnRyb2x0eXBlLCBjdXJ2ZQogICkKCmlfbXVsdGlwbGllciA8LSAwLjEgIyBpbmNyZWFzZSB0byA1CgojIFRob3NlIGFyZSBkZWZhdWx0IHZhbHVlcyBzdWdnZXN0ZWQgYnkgdGhlIEJjaHJvbiBwYWNrYWdlCm5faXRlcmF0aW9uX2RlZmF1bHQgPC0gMTBlMwpuX2J1cm5fZGVmYXVsdCA8LSAyZTMKbl90aGluX2RlZmF1bHQgPC0gOAoKIyBMZXQncyBtdWx0aXBseSB0aGVtIGJ5IG91ciBpX211bHRpcGxpZXIKbl9pdGVyYXRpb24gPC0gbl9pdGVyYXRpb25fZGVmYXVsdCAqIGlfbXVsdGlwbGllcgpuX2J1cm4gPC0gbl9idXJuX2RlZmF1bHQgKiBpX211bHRpcGxpZXIKbl90aGluIDwtIG1heChjKDEsIG5fdGhpbl9kZWZhdWx0ICogaV9tdWx0aXBsaWVyKSkKCiMgcnVuIEJjaHJvbgphZF9tb2RlbCA8LQogIEJjaHJvbjo6QmNocm9ub2xvZ3koCiAgICBhZ2VzID0gc2VsX2Nocm9uX2NvbnRyb2xfdGFibGUkY2hyb25jb250cm9sYWdlLAogICAgYWdlU2RzID0gc2VsX2Nocm9uX2NvbnRyb2xfdGFibGUkZXJyb3IsCiAgICBwb3NpdGlvbnMgPSBzZWxfY2hyb25fY29udHJvbF90YWJsZSRkZXB0aCwKICAgIGNhbEN1cnZlcyA9IHNlbF9jaHJvbl9jb250cm9sX3RhYmxlJGN1cnZlLAogICAgcG9zaXRpb25UaGlja25lc3NlcyA9IHNlbF9jaHJvbl9jb250cm9sX3RhYmxlJHRoaWNrbmVzcywKICAgIGl0ZXJhdGlvbnMgPSBuX2l0ZXJhdGlvbiwKICAgIGJ1cm4gPSBuX2J1cm4sCiAgICB0aGluID0gbl90aGluCiAgKQpgYGAKClZpc3VhbGx5IGNoZWNrIHRoZSBhZ2UtZGVwdGggbW9kZWxzCgpgYGB7ciBiY2hyb25fZmlndXJlLCByZXN1bHRzPSdtYXJrdXAnLCB3YXJuaW5nPUZBTFNFfQpwbG90KGFkX21vZGVsKQpgYGAKCiMjIyMgUHJlZGljdCBhZ2VzCgpgYGB7ciBhZ2VfdW5jZXJ0YWludGllcywgcmVzdWx0cz0naGlkZScsIHdhcm5pbmc9RkFMU0V9CmFnZV9wb3NpdGlvbiA8LQogIEJjaHJvbjo6OnByZWRpY3QuQmNocm9ub2xvZ3lSdW4ob2JqZWN0ID0gYWRfbW9kZWwsIG5ld1Bvc2l0aW9ucyA9IHNlbF9sZXZlbCRkZXB0aCkKCmFnZV91bmNlcnRhaW50aWVzIDwtCiAgYWdlX3Bvc2l0aW9uICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBkcGx5cjo6bXV0YXRlX2FsbCguLCBhcy5pbnRlZ2VyKSAlPiUKICBhcy5tYXRyaXgoKQoKY29sbmFtZXMoYWdlX3VuY2VydGFpbnRpZXMpIDwtIHNlbF9sZXZlbCRzYW1wbGVfaWQKCiMgTGV0J3MgdGFrZSB0aGUgbWVkaWFuIGFnZSBvZiBhbGwgcG9zc2libGUgYWdlcyAoaS5lLiB0aGUgZXN0aW1hdGVkIGFnZQojICAgZnJvbSBlYWNoIGFnZS1kZXB0aCBtb2RlbCBydW4pIGFzIG91ciBkZWZhdWx0LgpzZWxfbGV2ZWxfcHJlZGljdGVkIDwtCiAgc2VsX2xldmVsICU+JQogIGRwbHlyOjptdXRhdGUoCiAgICBhZ2UgPSBhcHBseSgKICAgICAgYWdlX3VuY2VydGFpbnRpZXMsIDIsCiAgICAgIHN0YXRzOjpxdWFudGlsZSwKICAgICAgcHJvYnMgPSAwLjUKICAgICkKICApCmBgYAoKIyMjIFZpc3VhbGlzYXRpb24gb2Ygb3VyIGRhdGEKCkxldCdzIG5vdyBtYWtlIGEgc2ltcGxlIHBvbGxlbiBkaWFncmFtIHdpdGggcHJvcG9ydGlvbnMgb2YgdGhlIG1haW4gcG9sbGVuIHRheGEgKHgtYXhpcykgYWdhaW5zdCBvdXIgZXN0aW1hdGVkIGFnZXMgYWxvbmcgZGVwdGggKHktYXhpcykuCgpgYGB7ciB2aXNfZGF0YV93aXRoX2FnZXN9CmRhdGFfcm93bmFtZXMgPC0KICBzZWxfY291bnRzX3NlbGVjdGVkICU+JQogIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKCJzYW1wbGVfaWQiKQoKZGF0YV9wZXJjZW50YWdlcyA8LQogIChkYXRhX3Jvd25hbWVzIC8gcm93U3VtcyhkYXRhX3Jvd25hbWVzKSkgKiAxMDAKCmNvbF9zdW1fbm9uX3plcm8gPC0KICBjb2xTdW1zKGRhdGFfcGVyY2VudGFnZXMpID4gMAoKZGF0YV9maWx0ZXJlZCA8LQogIGRhdGFfcGVyY2VudGFnZXMgJT4lCiAgZHBseXI6OnNlbGVjdCgKICAgIGRwbHlyOjphbnlfb2YoCiAgICAgIG5hbWVzKGNvbF9zdW1fbm9uX3plcm8pW2NvbF9zdW1fbm9uX3plcm9dCiAgICApCiAgKSAlPiUKICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlX2lkIikgJT4lCiAgZHBseXI6OnJlbG9jYXRlKHNhbXBsZV9pZCkgJT4lCiAgdGliYmxlOjp0aWJibGUoKSAKCmRhdGFfZmlsdGVyZWQgJT4lCiAgZHBseXI6OmlubmVyX2pvaW4oCiAgICBzZWxfbGV2ZWxfcHJlZGljdGVkLAogICAgYnkgPSBkcGx5cjo6am9pbl9ieShzYW1wbGVfaWQpCiAgKSAlPiUKICB0aWR5cjo6cGl2b3RfbG9uZ2VyKAogICAgY29scyA9IC1jKHNhbXBsZV9pZCwgZGVwdGgsIGFnZSksCiAgICBuYW1lc190byA9ICJ0YXhhIiwKICAgIHZhbHVlc190byA9ICJwcm9wb3J0aW9uX29mX2dyYWlucyIKICApICU+JQogIGRwbHlyOjpncm91cF9ieSh0YXhhKSAlPiUKICAjIENhbGN1bGF0ZSB0aGUgYXZlcmFnZSBwcm9wb3J0aW9uIG9mIGdyYWlucwogIGRwbHlyOjptdXRhdGUoCiAgICBhdmdfcHJvcCA9IG1lYW4ocHJvcG9ydGlvbl9vZl9ncmFpbnMpCiAgKSAlPiUKICAjIE9ubHkga2VlcCB0aGUgbWFpbiB0YXhhIChvbiBhdmVyYWdlID49IDElIHBvbGxlbiBncmFpbnMpCiAgZHBseXI6OmZpbHRlcihhdmdfcHJvcCA+PSAxKSAlPiUKICBkcGx5cjo6dW5ncm91cCgpICU+JQogIGdncGxvdDI6OmdncGxvdCgKICAgIG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoCiAgICAgIHkgPSBhZ2UsCiAgICAgIHggPSBwcm9wb3J0aW9uX29mX2dyYWlucywKICAgICAgeG1heCA9IHByb3BvcnRpb25fb2ZfZ3JhaW5zLAogICAgICB4bWluID0gMCwKICAgICAgZmlsbCA9IHRheGEsCiAgICAgIGNvbCA9IHRheGEKICAgICksCiAgKSArCiAgZ2dwbG90Mjo6Z2VvbV9yaWJib24oKSArCiAgZ2dwbG90Mjo6c2NhbGVfeV9jb250aW51b3VzKHRyYW5zID0gInJldmVyc2UiKSArCiAgZ2dwbG90Mjo6c2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IGMoMCwgMSkpICsKICBnZ3Bsb3QyOjpmYWNldF93cmFwKH50YXhhLCBucm93ID0gMSkgKwogIGdncGxvdDI6OnRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgcGFuZWwuYm9yZGVyID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgc3RyaXAuYmFja2dyb3VuZCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwKICAgIHN0cmlwLnRleHQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRpY2tzLnggPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICBheGlzLnRleHQueCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKQogICkgKwogIGdncGxvdDI6OmxhYnMoCiAgICB4ID0gIlByb3BvcnRpb24gb2YgZ3JhaW5zIiwKICAgIHkgPSAiQWdlIChjYWwgeXIgQlApIgogICkKYGBgCgojIyBFc3RpbWF0aW9uIG9mIGVjb3N5c3RlbSBwcm9wZXJ0eQoKTm93IHdlIHdpbGwgdXNlIG91ciBwcmVwYXJlZCBmb3NzaWwgcG9sbGVuIGRhdGEgdG8gZXN0aW1hdGUgdGhlIGRpdmVyc2l0eS4gV2Ugd2lsbCB1c2Uge1JFY29wb2x9IHBhY2thZ2UsIHdoaWNoIGhhcyBlYXN5LXRvLXVzZSBmdW5jdGlvbnMgdG8gYW5hbHlzZSBmb3NzaWwgcG9sbGVuIGRhdGEuIFNlZSBwYWNrYWdlIFt3ZWJzaXRlXShodHRwczovL2hvcGUtdWliLWJpby5naXRodWIuaW8vUi1FY29wb2wtcGFja2FnZS8pIGZvciBtb3JlIGluZm9ybWF0aW9uLiBTcGVjaWZpY2FsbHksIHdlIHdpbGwgZXN0aW1hdGUgcmFyZWZpZWQgdmFsdWVzIG9mIFtIaWxsIG51bWJlcnNdKGh0dHBzOi8vZXNham91cm5hbHMub25saW5lbGlicmFyeS53aWxleS5jb20vZG9pL2Ficy8xMC4yMzA3LzE5MzQzNTIpLgoKYGBge3Igd29ya2Fyb3VuZCwgcmVzdWx0cz0naGlkZScsIHdhcm5pbmc9RkFMU0V9CiMgZnVuY3Rpb24gdG8gZXN0aW1hdGUgZGl2ZXJzaXR5CmdldF9kaXZlcnNpdHkgPC0gZnVuY3Rpb24oZGF0YV9zb3VyY2UsIHJvdW5kID0gVFJVRSwgc2VsX21ldGhvZCA9ICIiKSB7CiAgIyBoZWxwZXIgZnVuY3Rpb24KICBnZXRfZGl2ZXJzaXR5X3RheG9ub21pYyA8LSBmdW5jdGlvbihkYXRhX21hdHJpeCwgc2FtcGxlX3NpemUpIHsKICAgIGhpbGwwIDwtIGZ1bmN0aW9uKGRhdGEsIHNhbXBsZV9zaXplKSB7CiAgICAgIGRhdGFfc3ViIDwtIGRhdGFbZGF0YSA+IDBdCiAgICAgIGRhdGFfc3VtIDwtIHN1bShkYXRhX3N1YikKICAgICAgaWYgKHNhbXBsZV9zaXplIDw9IGRhdGFfc3VtKSB7CiAgICAgICAgcmVzIDwtIHN1bSgxIC0gZXhwKGxjaG9vc2UoZGF0YV9zdW0gLSBkYXRhX3N1Yiwgc2FtcGxlX3NpemUpIC0KICAgICAgICAgIGxjaG9vc2UoZGF0YV9zdW0sIHNhbXBsZV9zaXplKSkpCiAgICAgICAgcmV0dXJuKHJlcykKICAgICAgfSBlbHNlIHsKICAgICAgICByZXR1cm4oMCkKICAgICAgfQogICAgfQogICAgZmtfaGF0IDwtIGZ1bmN0aW9uKGRhdGEsIHNhbXBsZV9zaXplKSB7CiAgICAgIGRhdGFfc3ViIDwtIGRhdGFbZGF0YSA+IDBdCiAgICAgIGRhdGFfc3VtIDwtIHN1bShkYXRhX3N1YikKICAgICAgaWYgKHNhbXBsZV9zaXplIDw9IGRhdGFfc3VtKSB7CiAgICAgICAgc3ViIDwtIGZ1bmN0aW9uKGspIHsKICAgICAgICAgIHN1bShleHAobGNob29zZShkYXRhX3N1YiwgaykgKyBsY2hvb3NlKGRhdGFfc3VtIC0KICAgICAgICAgICAgZGF0YV9zdWIsIHNhbXBsZV9zaXplIC0gaykgLSBsY2hvb3NlKAogICAgICAgICAgICBkYXRhX3N1bSwKICAgICAgICAgICAgc2FtcGxlX3NpemUKICAgICAgICAgICkpKQogICAgICAgIH0KICAgICAgICByZXMgPC0gc2FwcGx5KDE6c2FtcGxlX3NpemUsIHN1YikKICAgICAgICByZXR1cm4ocmVzKQogICAgICB9IGVsc2UgewogICAgICAgIHJldHVybigwKQogICAgICB9CiAgICB9CiAgICBoaWxsMSA8LSBmdW5jdGlvbihkYXRhLCBzYW1wbGVfc2l6ZSkgewogICAgICBkYXRhX3N1YiA8LSBkYXRhW2RhdGEgPiAwXQogICAgICBkYXRhX3N1bSA8LSBzdW0oZGF0YV9zdWIpCiAgICAgIGlmIChzYW1wbGVfc2l6ZSA8PSBkYXRhX3N1bSkgewogICAgICAgIGsgPC0gMTpzYW1wbGVfc2l6ZQogICAgICAgIHJlcyA8LSBleHAoLXN1bShrIC8gc2FtcGxlX3NpemUgKiBsb2coayAvIHNhbXBsZV9zaXplKSAqCiAgICAgICAgICBma19oYXQoZGF0YV9zdWIsIHNhbXBsZV9zaXplKSkpCiAgICAgICAgcmV0dXJuKHJlcykKICAgICAgfSBlbHNlIHsKICAgICAgICByZXR1cm4oMCkKICAgICAgfQogICAgfQogICAgaGlsbDIgPC0gZnVuY3Rpb24oZGF0YSwgc2FtcGxlX3NpemUpIHsKICAgICAgZGF0YV9zdWIgPC0gZGF0YVtkYXRhID4gMF0KICAgICAgZGF0YV9zdW0gPC0gc3VtKGRhdGFfc3ViKQogICAgICBpZiAoc2FtcGxlX3NpemUgPD0gZGF0YV9zdW0pIHsKICAgICAgICByZXMgPC0gMSAvICgxIC8gc2FtcGxlX3NpemUgKyAoMSAtIDEgLyBzYW1wbGVfc2l6ZSkgKiBzdW0oZGF0YV9zdWIgKgogICAgICAgICAgKGRhdGFfc3ViIC0gMSkgLyBkYXRhX3N1bSAvIChkYXRhX3N1bSAtIDEpKSkKICAgICAgICByZXR1cm4ocmVzKQogICAgICB9IGVsc2UgewogICAgICAgIHJldHVybigwKQogICAgICB9CiAgICB9CiAgICBlc3Rfbl8wIDwtCiAgICAgIHNhcHBseShzYW1wbGVfc2l6ZSwgZnVuY3Rpb24obikgewogICAgICAgIGFwcGx5KGRhdGFfbWF0cml4LCAxLCBoaWxsMCwgc2FtcGxlX3NpemUgPSBuKQogICAgICB9KQogICAgZXN0X25fMSA8LQogICAgICBzYXBwbHkoc2FtcGxlX3NpemUsIGZ1bmN0aW9uKG4pIHsKICAgICAgICBhcHBseShkYXRhX21hdHJpeCwgMSwgaGlsbDEsIHNhbXBsZV9zaXplID0gbikKICAgICAgfSkKICAgIGVzdF9uXzIgPC0KICAgICAgc2FwcGx5KHNhbXBsZV9zaXplLCBmdW5jdGlvbihuKSB7CiAgICAgICAgYXBwbHkoZGF0YV9tYXRyaXgsIDEsIGhpbGwyLCBzYW1wbGVfc2l6ZSA9IG4pCiAgICAgIH0pCiAgICBoaWxsX2RpdmVyc2l0eSA8LQogICAgICBjYmluZChlc3Rfbl8wLCBlc3Rfbl8xLCBlc3Rfbl8yLCBlc3Rfbl8xIC0KICAgICAgICBlc3Rfbl8yLCBlc3Rfbl8yIC8gZXN0X25fMSwgZXN0X25fMSAvIGVzdF9uXzApICU+JQogICAgICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgICAgIHRpYmJsZTo6YXNfdGliYmxlKCkgJT4lCiAgICAgIGRwbHlyOjptdXRhdGUoc2FtcGxlX2lkID0gcm93Lm5hbWVzKGRhdGFfbWF0cml4KSkgJT4lCiAgICAgIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKCJzYW1wbGVfaWQiKSAlPiUKICAgICAgcHVycnI6OnNldF9uYW1lcygKICAgICAgICBubSA9IGMoCiAgICAgICAgICAibjAiLAogICAgICAgICAgIm4xIiwgIm4yIiwgIm4xX21pbnVzX24yIiwgIm4yX2RpdmlkZWRfYnlfbjEiLCAibjFfZGl2aWRlZF9ieV9uMCIKICAgICAgICApCiAgICAgICkKICAgIHJldHVybihoaWxsX2RpdmVyc2l0eSkKICB9CgogIGlmICgKICAgIGlzVFJVRShyb3VuZCkKICApIHsKICAgIGRhdGFfbWF0cml4IDwtCiAgICAgIGRhdGFfc291cmNlICU+JQogICAgICB0aWJibGU6OmNvbHVtbl90b19yb3duYW1lcygic2FtcGxlX2lkIikgJT4lCiAgICAgIGRwbHlyOjptdXRhdGVfYWxsKC4sIC5mID0gZmxvb3IpICU+JQogICAgICBhcy5tYXRyaXgoKSAlPiUKICAgICAgcm91bmQoKQogIH0gZWxzZSB7CiAgICBkYXRhX21hdHJpeCA8LQogICAgICBkYXRhX3NvdXJjZSAlPiUKICAgICAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoInNhbXBsZV9pZCIpICU+JQogICAgICBhcy5tYXRyaXgoKQogIH0KCiAgc2FtcGxlX3NpemUgPC0KICAgIGFwcGx5KGRhdGFfbWF0cml4LCAxLCBzdW0pICU+JQogICAgZmxvb3IoKSAlPiUKICAgIG1pbigpCgogIGRpdiA8LQogICAgZ2V0X2RpdmVyc2l0eV90YXhvbm9taWMoCiAgICAgIGRhdGFfbWF0cml4ID0gZGF0YV9tYXRyaXgsCiAgICAgIHNhbXBsZV9zaXplID0gc2FtcGxlX3NpemUKICAgICkKCiAgcmVzIDwtCiAgICBkaXYgJT4lCiAgICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlX2lkIikgJT4lCiAgICBkcGx5cjo6cmVsb2NhdGUoc2FtcGxlX2lkKSAlPiUKICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgIHRpYmJsZTo6YXNfdGliYmxlKCkKICByZXR1cm4ocmVzKQp9CgpgYGAKCmBgYHtyIGRpdmVyc2l0eSwgcmVzdWx0cz0naGlkZScsIHdhcm5pbmc9RkFMU0V9CmRhdGFfZGl2ZXJzaXR5IDwtCiAgZ2V0X2RpdmVyc2l0eSgKICAgIGRhdGFfc291cmNlID0gc2VsX2NvdW50c19zZWxlY3RlZCwKICAgIHNlbF9tZXRob2QgPSAidGF4b25vbWljIgogICkKCmhlYWQoZGF0YV9kaXZlcnNpdHkpCmBgYAoKYGBge3IgZGl2ZXJzaXR5X2RpcGxheSwgZWNobz1GQUxTRSwgcmVzdWx0cz0nYXNpcycsIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0V9CnBhbmRlcjo6cGFuZG9jLnRhYmxlKGhlYWQoZGF0YV9kaXZlcnNpdHkpKQpgYGAKCk5vdyB3ZSBjYW4gZml0IGEgdGVtcG9yYWwgdHJlbmQgYXMgYSBHQU0gbW9kZWwuCgpgYGB7ciB0ZW1wb3JhbF90cmVuZH0KZGF0YV90b19maXQgPC0KICBkcGx5cjo6aW5uZXJfam9pbigKICAgIGRhdGFfZGl2ZXJzaXR5LAogICAgc2VsX2xldmVsX3ByZWRpY3RlZCwKICAgIGJ5ID0gInNhbXBsZV9pZCIKICApCgptb2RfbjAgPC0KICBtZ2N2OjpnYW0oCiAgICBuMCB+IHMoYWdlLCBrID0gMjUsIGJzID0gInRwIiksCiAgICBkYXRhID0gZGF0YV90b19maXQsCiAgICBtZXRob2QgPSAiUkVNTCIsCiAgICBmYW1pbHkgPSBtZ2N2Ojp0dyhsaW5rID0gImxvZyIpCiAgKQoKc3VtbWFyeShtb2RfbjApCgojIG1nY3Y6OmdhbS5jaGVjayhtb2RfbjAsIGsuc2FtcGxlID0gMTBlMywgay5yZXAgPSAxZTMpCmBgYAoKTm93IHdlIGNhbiB2aXN1YWxpc2UgdGhlIHJlc3VsdHMuCgpgYGB7ciBwbG90X2dhbX0KYWdlX2R1bW15IDwtCiAgdGliYmxlOjp0aWJibGUoCiAgICBhZ2UgPSBzZXEoCiAgICAgIGZyb20gPSBtaW4oZGF0YV90b19maXQkYWdlKSwKICAgICAgdG8gPSBtYXgoZGF0YV90b19maXQkYWdlKSwKICAgICAgbGVuZ3RoLm91dCA9IDEwMAogICAgKQogICkKCmRhdGFfcHJlZGljdGVkIDwtCiAgbWFyZ2luYWxlZmZlY3RzOjpwcmVkaWN0aW9ucygKICAgIG1vZGVsID0gbW9kX24wLAogICAgbmV3ZGF0YSA9IGFnZV9kdW1teQogICkgJT4lCiAgdGliYmxlOjphc190aWJibGUoKSAlPiUKICBkcGx5cjo6cmVuYW1lKAogICAgZml0ID0gZXN0aW1hdGUsCiAgICBsd3IgPSBjb25mLmxvdywKICAgIHVwciA9IGNvbmYuaGlnaCwKICAgIHNkX2Vycm9yID0gc3RkLmVycm9yCiAgKSAlPiUKICBkcGx5cjo6c2VsZWN0KAogICAgZHBseXI6OmFsbF9vZigKICAgICAgYygKICAgICAgICAiYWdlIiwKICAgICAgICAiZml0IiwKICAgICAgICAic2RfZXJyb3IiLAogICAgICAgICJsd3IiLAogICAgICAgICJ1cHIiCiAgICAgICkKICAgICkKICApCgpkYXRhX3ByZWRpY3RlZCAlPiUKICBnZ3Bsb3QyOjpnZ3Bsb3QoCiAgICBtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKAogICAgICB4ID0gYWdlLAogICAgICB5ID0gZml0CiAgICApCiAgKSArCiAgZ2dwbG90Mjo6Z2VvbV9yaWJib24oCiAgICBtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKAogICAgICB5bWluID0gbHdyLAogICAgICB5bWF4ID0gdXByCiAgICApLAogICAgYWxwaGEgPSAwLjIKICApICsKICBnZ3Bsb3QyOjpnZW9tX2xpbmUoKSArCiAgZ2VvbV9wb2ludCgKICAgIGRhdGEgPSBkYXRhX3RvX2ZpdCwKICAgIG1hcHBpbmcgPSBnZ3Bsb3QyOjphZXMoCiAgICAgIHkgPSBuMAogICAgKQogICkgKwogIGdncGxvdDI6OnNjYWxlX3hfY29udGludW91cygKICAgIHRyYW5zID0gInJldmVyc2UiCiAgKSArCiAgZ2dwbG90Mjo6bGFicygKICAgIHkgPSAiSGlsbCdzIE4wIiwKICAgIHggPSAiQWdlIChjYWwgeXIgQlApIgogICkKYGBgCg==