Fuzzy Matcher Stage 2
The fuzzy matcher predicts the likelihood that two toponyms are the same place even though their spellings might be different. It has two stages.
This file develops Stage 2 of the fuzzy toponym matcher. Its job is to refine the prediction for the subset of plausible matches and provide an actual probability of a match.
rm(list=ls()); gc()
library(MeasuringLandscape)
library(tidyverse)
dir_figures <- glue::glue(getwd(), "/../paper/figures/")
knitr::opts_knit$set(progress = TRUE, verbose = TRUE)
knitr::opts_chunk$set(fig.width=12, fig.height=8, warning=FALSE, message=FALSE, cache=TRUE)
options(width = 160)
It is based on an events dataset built and cleaned in the file “01 Prep Events Counts”.
#Load Events
events_sf <- readRDS(system.file("extdata", "events_sf.Rdata", package = "MeasuringLandscape"))
events_dt <- data.table::as.data.table(events_sf) #%>% unique()
dim(events_sf)
[1] 10469 104
unlist(strsplit(events_sf$name_cleaner,"")) %>% janitor::tabyl() %>% janitor::adorn_crosstab(., denom = "col", show_n = T, digits = 1, show_totals = T)
'janitor::adorn_crosstab' is deprecated.
Use 'use the various adorn_ functions instead. See the "tabyl" vignette for examples.' instead.
See help("Deprecated")
events_sf_text_coord_unique <- plyr::ddply(events_sf[,c('location_text','name_clean','name_cleaner','document_district_clean',
'map_coordinate_clean_latitude','map_coordinate_clean_longitude')],
"location_text", transform,
map_coordinate_has =sum(!is.na(map_coordinate_clean_latitude))
)
#Load Gazeteers
flatfiles_sf_roi <- readRDS(system.file("extdata", "flatfiles_sf_roi.Rdata", package = "MeasuringLandscape"))
dim(flatfiles_sf_roi)
[1] 55966 22
flatfiles_dt <- data.table::as.data.table(flatfiles_sf_roi)
data.table::setkey(flatfiles_dt,place_hash)
Summary statistics
table( !is.na(sf::st_coordinates(events_sf)[,1] ) ,
!is.na(events_sf$name_cleaner) )
FALSE TRUE
FALSE 1377 3500
TRUE 454 5138
Produce a dataset for hand labeling
This takes every event with a coordinate and looks up the 10 nearest points in the gazetteers. It saves this as a csv file that a human can label as a toponym match or mismatch.
r
r #Should only need to do this once #create_toponym_dataset_forlabeling()
Choose Features
vars_id <- c("name_cleaner_a","name_cleaner_b",'test','extranegative')
vars_weights <- c("weights")
vars_y <- c("rex_match")
vars_x_string <- c(#"exact_match",
"Jaro",
"Optimal_String_Alignment" ,
"Levenshtein",
"Damerau_Levenshtein" ,
"Longest_Common_Substring" ,
"q_gram_1",
"q_gram_2",
"q_gram_3",
"q_gram_4",
"q_gram_5",
'Cosine_1',
'Cosine_2',
'Cosine_3',
'Cosine_4',
'Cosine_5',
"Jaccard" ,
"First_Mistmatch" ,
"a_nchar" ,
"b_nchar" ,
"ab_nchar_diff" ,
"dJaro",
"dOptimal_String_Alignment" ,
"dLevenshtein" ,
"dDamerau_Levenshtein" ,
"dLongest_Common_Substring",
"dq_gram",
"dCosine",
"dJaccard"
# "OM",
# "OMloc",
# "OMslen"
# ,"OMspell",
# "TWED",
# "LCS",
# "LCP",
# "RLCP",
# "NMS",
# "NMSMST",
# "SVRspell",
# "CHI2"
)
vars_x_stem <- paste0(vars_x_string, "_stemmed")
vars_x_string_corpus <- c(
"corpus_mention_count_a",
"corpus_mention_year_min_a",
"corpus_mention_year_median_a",
"corpus_mention_year_mean_a",
"corpus_mention_year_max_a",
"corpus_mention_count_b",
"corpus_mention_year_min_b",
"corpus_mention_year_median_b",
"corpus_mention_year_mean_b",
"corpus_mention_year_max_b",
"gazeteer_mentions_count_a",
"gazeteer_mentions_count_b",
"gazeteer_stem_mentions_count_a",
"gazeteer_stem_mentions_count_b"
#"postfix_has_a"
#"postfix_has_b"
#"ngram_a", #Don't run, it's just a word count and it's missing if it's never found
#"ngram_b"
)
vars_x_stem_corpus <- paste0(vars_x_string_corpus, "_stemmed")
vars_x_everything <- c(
vars_x_string,
vars_x_stem#,
#vars_x_string_corpus, #excluding corpus features
#vars_x_stem_corpus #excluding corpus features
)
vars_x_onlystring <- c(vars_x_string)
vars_x_stringcorpus <- c(vars_x_string, vars_x_string_corpus)
vars_x_string_and_stem <- c(vars_x_string, vars_x_stem)
Create Training Test Split
handlabeled <- MeasuringLandscape:::toponym_training_dataset_load()
[1] "Hand labels original"
[1] 20242 8
0 1
17050 3192
[1] "Drop identical labels"
[1] 18405 10
0 1
17047 1358
[1] "Invert Diads"
[1] 34496 10
0 1
32374 2122
fromscratch=F
if(fromscratch){
toponym_training_dataset <- create_toponym_split_training_test(
handlabeled=handlabeled,
vars_id=vars_id,
vars_weights=vars_weights,
vars_y=vars_y,
vars_x=vars_x_everything,
neg_count=0, #you lose so many to cosine distance
fromscratch=T
)
saveRDS(toponym_training_dataset,
file=glue(getwd(), "/../inst/extdata/toponym_training_dataset.Rds")
)
}
toponym_training_dataset <- readRDS(system.file("extdata", "toponym_training_dataset.Rds", package = "MeasuringLandscape"))
Summarize training data
glue::glue("Handlabeled Training Dataset")
Handlabeled Training Dataset
dim(handlabeled)
[1] 34496 10
table(handlabeled$rex_match)
0 1
32374 2122
glue::glue("All Data Resampled with Features \n")
All Data Resampled with Features
dim(toponym_training_dataset$xy_all)
[1] 14936 62
table(toponym_training_dataset$xy_all$rex_match)
0 1
12820 2116
glue::glue("Training Split \n")
Training Split
dim(toponym_training_dataset$xy_train)
[1] 12611 62
table(toponym_training_dataset$xy_train$rex_match)
0 1
10803 1808
glue::glue("Test Split \n")
Test Split
dim(toponym_training_dataset$xy_test)
[1] 2325 62
table(toponym_training_dataset$xy_test$rex_match)
0 1
2017 308
Fit XGBoost models predicting match
xgb.save(toponym_xb_string_and_stem,
glue::glue(getwd(), "/../inst/extdata/toponym_xb_string_and_stem.bin")
) #Have to save separately for some reason
[1] TRUE
(Appendix Figure 5, AUC and Precision Recall curves for toponym fuzzy matcher stage 2, predicting likelihood of a match.)
dtest<-xgb.DMatrix(data= as.matrix(as.data.frame(global_test)[,vars_x_everything]),
label=as.numeric(global_test$rex_match),
weight = global_test$weights,
missing = NA)
global_test$toponym_xb_everything2 <- predict(toponym_xb_everything2, dtest )
global_test$toponym_xb_everything2 <- 1/(1 + exp(-global_test$toponym_xb_everything2))
#global_test$toponym_xb_onlystring <- predict(toponym_xb_onlystring, dtest )
#global_test$toponym_xb_onlystring <- 1/(1 + exp(-global_test$toponym_xb_onlystring))
global_test$toponym_xb_string_and_stem <- predict(toponym_xb_string_and_stem, dtest )
global_test$toponym_xb_string_and_stem <- 1/(1 + exp(-global_test$toponym_xb_string_and_stem))
#p_load(Metrics)
#p_load(MLmetrics)
tocompare <- c(#"toponym_xb_everything2",
#"toponym_xb_onlystring"#,
#"toponym_xb_stringcorpus"#,
"toponym_xb_string_and_stem"
) # "toponym_xb_everything",, "toponym_xb_everything2_extra","toponym_xb_onlystring_extra","toponym_xb_everything2_extra_large", "toponym_xb_onlystring_extra_large", "toponym_xb_everything2_uber_large" , "toponym_xb_onlystring_uber_large"
print("ce")
[1] "ce"
sapply(tocompare, function(q) { Metrics::ce(actual=global_test$rex_match, predicted=global_test[,q]>.5) })
toponym_xb_string_and_stem
0.03526882
print("F1_Score")
[1] "F1_Score"
sapply(tocompare, function(q) { MLmetrics::F1_Score(global_test$rex_match, as.numeric( global_test[,q]>.5) ) })
toponym_xb_string_and_stem
0.9795613
print("logloss")
[1] "logloss"
sapply(tocompare, function(q) { Metrics::logLoss(actual=global_test$rex_match, predicted=global_test[,q]) })
toponym_xb_string_and_stem
0.09686789
print("confusion")
[1] "confusion"
sapply(tocompare, function(q) { table(global_test$rex_match, global_test[,q]>.5) })
toponym_xb_string_and_stem
[1,] 1965
[2,] 30
[3,] 52
[4,] 278
#p_load(precrec)
msmdat1 <- precrec::evalmod( precrec::mmdata(scores=list(
#global_test$toponym_xb_everything,
#global_test$toponym_rf_everything,
#global_test$toponym_xb_everything_extracted,
#global_test$toponym_xb_everything2, #highest PRC
#global_test$toponym_xb_everything3,
#global_test$toponym_xb_everything4,
global_test$toponym_xb_string_and_stem#,
#global_test$toponym_xb_onlystring#,
#global_test$toponym_xb_stringcorpus#,
#global_test$toponym_xb_onlystring_tiny
#global_test$toponym_xb_everything2_extra,
#global_test$toponym_xb_onlystring_extra,
#global_test$toponym_xb_everything2_extra_large,
#global_test$toponym_xb_onlystring_extra_large,
#global_test$toponym_xb_everything2_uber_large,
#global_test$toponym_xb_onlystring_uber_large
),
labels=as.numeric(as.data.frame(global_test)$rex_match),
modnames = c(#"toponym_xb_everything",
#"toponym_rf_everything",
#"toponym_xb_everything_features",
#"toponym_xb_everything2",
#"toponym_xb_everything3",
#"toponym_xb_everything4",
"toponym_xb_string_and_stem"#,
#"toponym_xb_onlystring"#,
#"toponym_xb_stringcorpus"#,
#"toponym_xb_onlystring_tiny"
#"toponym_xb_everything2_extra",
#"toponym_xb_onlystring_extra",
#"toponym_xb_everything2_extra_large",
#"toponym_xb_onlystring_extra_large",
#"toponym_xb_everything2_uber_large" ,
#"toponym_xb_onlystring_uber_large"
)
) )
msmdat1
=== AUCs ===
Model name Dataset ID Curve type AUC
1 toponym_xb_string_and_stem 1 ROC 0.9882251
2 toponym_xb_string_and_stem 1 PRC 0.9268585
=== Input data ===
Model name Dataset ID # of negatives # of positives
1 toponym_xb_string_and_stem 1 2017 308
uauc1 <- precrec::evalmod(scores = global_test$toponym_xb_string_and_stem,
labels=as.numeric(as.data.frame(global_test)$rex_match)#,
#mode="aucroc"
)
plot(uauc1)
#dev.off()
pdf(file=glue::glue(dir_figures, "p_auc_stage2.pdf"), width=5.5)
plot(uauc1)
#autoplot(uauc1)
dev.off()
png
2

autoplot(uauc1)

Evaluate the model (Appendix Figure 6, AUC and Precision Recall curves for toponym fuzzy matcher stage 2, predicting likelihood of a match.)
#p_load(DiagrammeR)
#xgb.plot.tree(model = xb)
#xgb.plot.tree(model = toponym_xb_everything2, trees = 0, show_node_id = TRUE, render = TRUE)
importance_importance <- xgb.importance(feature_names=vars_x_string_and_stem, model = toponym_xb_string_and_stem)
xgb.plot.importance(importance_importance)
pdf(file=glue::glue(dir_figures, "p_variable_importance_stage2.pdf"), width=5.5, height=6)
xgb.plot.importance(importance_importance)
#autoplot(uauc1)
dev.off()
png
2

Analysis of Residuals
global_test$toponym_xb_everything2_correct <- (global_test$toponym_xb_everything2>.5) == global_test$rex_match
#global_test$toponym_xb_onlystring_uber_large_correct <- (global_test$toponym_xb_onlystring_uber_large>.5)==global_test$rex_match
#table(global_test$toponym_xb_onlystring_uber_large_correct)
LS0tCnRpdGxlOiAiMDQgRnV6enkgTWF0Y2hlciBTdGFnZSAyIChYR0Jvb3N0KSIKYXV0aG9yOiAiUmV4IFcuIERvdWdsYXNzIGFuZCBLcmlzdGVuIEhhcmtuZXNzIgpkYXRlOiAiTWFyY2ggOSwgMjAxOCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCjxzdHlsZT4KICAgIGJvZHkgLm1haW4tY29udGFpbmVyIHsKICAgICAgICBtYXgtd2lkdGg6IDEwMCU7CiAgICB9Cjwvc3R5bGU+CgojIEZ1enp5IE1hdGNoZXIgU3RhZ2UgMgoKVGhlIGZ1enp5IG1hdGNoZXIgcHJlZGljdHMgdGhlIGxpa2VsaWhvb2QgdGhhdCB0d28gdG9wb255bXMgYXJlIHRoZSBzYW1lIHBsYWNlIGV2ZW4gdGhvdWdoIHRoZWlyIHNwZWxsaW5ncyBtaWdodCBiZSBkaWZmZXJlbnQuIEl0IGhhcyB0d28gc3RhZ2VzLgoKVGhpcyBmaWxlIGRldmVsb3BzIFN0YWdlIDIgb2YgdGhlIGZ1enp5IHRvcG9ueW0gbWF0Y2hlci4gSXRzIGpvYiBpcyB0byByZWZpbmUgdGhlIHByZWRpY3Rpb24gZm9yIHRoZSBzdWJzZXQgb2YgcGxhdXNpYmxlIG1hdGNoZXMgYW5kIHByb3ZpZGUgYW4gYWN0dWFsIHByb2JhYmlsaXR5IG9mIGEgbWF0Y2guCiAKCmBgYHtyLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSB9CnJtKGxpc3Q9bHMoKSk7IGdjKCkKbGlicmFyeShNZWFzdXJpbmdMYW5kc2NhcGUpCmxpYnJhcnkodGlkeXZlcnNlKQpkaXJfZmlndXJlcyA8LSBnbHVlOjpnbHVlKGdldHdkKCksICIvLi4vcGFwZXIvZmlndXJlcy8iKQoKa25pdHI6Om9wdHNfa25pdCRzZXQocHJvZ3Jlc3MgPSBUUlVFLCB2ZXJib3NlID0gVFJVRSkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD04LCAgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9VFJVRSkKb3B0aW9ucyh3aWR0aCA9IDE2MCkKCmBgYAoKSXQgaXMgYmFzZWQgb24gYW4gZXZlbnRzIGRhdGFzZXQgYnVpbHQgYW5kIGNsZWFuZWQgaW4gdGhlIGZpbGUgIjAxIFByZXAgRXZlbnRzIENvdW50cyIuCgpgYGB7cn0KCiNMb2FkIEV2ZW50cwpldmVudHNfc2YgPC0gcmVhZFJEUyhzeXN0ZW0uZmlsZSgiZXh0ZGF0YSIsICJldmVudHNfc2YuUmRhdGEiLCBwYWNrYWdlID0gIk1lYXN1cmluZ0xhbmRzY2FwZSIpKSAKZXZlbnRzX2R0IDwtIGRhdGEudGFibGU6OmFzLmRhdGEudGFibGUoZXZlbnRzX3NmKSAjJT4lIHVuaXF1ZSgpIApkaW0oZXZlbnRzX3NmKQoKdW5saXN0KHN0cnNwbGl0KGV2ZW50c19zZiRuYW1lX2NsZWFuZXIsIiIpKSAlPiUgamFuaXRvcjo6dGFieWwoKSAlPiUgamFuaXRvcjo6YWRvcm5fY3Jvc3N0YWIoLiwgZGVub20gPSAiY29sIiwgc2hvd19uID0gVCwgZGlnaXRzID0gMSwgc2hvd190b3RhbHMgPSBUKQoKZXZlbnRzX3NmX3RleHRfY29vcmRfdW5pcXVlIDwtIHBseXI6OmRkcGx5KGV2ZW50c19zZlssYygnbG9jYXRpb25fdGV4dCcsJ25hbWVfY2xlYW4nLCduYW1lX2NsZWFuZXInLCdkb2N1bWVudF9kaXN0cmljdF9jbGVhbicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ21hcF9jb29yZGluYXRlX2NsZWFuX2xhdGl0dWRlJywnbWFwX2Nvb3JkaW5hdGVfY2xlYW5fbG9uZ2l0dWRlJyldLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImxvY2F0aW9uX3RleHQiLCB0cmFuc2Zvcm0sCiAgICAgIG1hcF9jb29yZGluYXRlX2hhcyA9c3VtKCFpcy5uYShtYXBfY29vcmRpbmF0ZV9jbGVhbl9sYXRpdHVkZSkpCiAgICAgICkKCiNMb2FkIEdhemV0ZWVycwpmbGF0ZmlsZXNfc2Zfcm9pIDwtIHJlYWRSRFMoc3lzdGVtLmZpbGUoImV4dGRhdGEiLCAiZmxhdGZpbGVzX3NmX3JvaS5SZGF0YSIsIHBhY2thZ2UgPSAiTWVhc3VyaW5nTGFuZHNjYXBlIikpIApkaW0oZmxhdGZpbGVzX3NmX3JvaSkKZmxhdGZpbGVzX2R0IDwtIGRhdGEudGFibGU6OmFzLmRhdGEudGFibGUoZmxhdGZpbGVzX3NmX3JvaSkKZGF0YS50YWJsZTo6c2V0a2V5KGZsYXRmaWxlc19kdCxwbGFjZV9oYXNoKQoKCmBgYAoKU3VtbWFyeSBzdGF0aXN0aWNzCgpgYGB7cn0KCnRhYmxlKCAhaXMubmEoc2Y6OnN0X2Nvb3JkaW5hdGVzKGV2ZW50c19zZilbLDFdICkgLAogICAgICAgIWlzLm5hKGV2ZW50c19zZiRuYW1lX2NsZWFuZXIpICkKCmBgYAoKCiMgUHJvZHVjZSBhIGRhdGFzZXQgZm9yIGhhbmQgbGFiZWxpbmcKClRoaXMgdGFrZXMgZXZlcnkgZXZlbnQgd2l0aCBhIGNvb3JkaW5hdGUgYW5kIGxvb2tzIHVwIHRoZSAxMCBuZWFyZXN0IHBvaW50cyBpbiB0aGUgZ2F6ZXR0ZWVycy4gSXQgc2F2ZXMgdGhpcyBhcyBhIGNzdiBmaWxlIHRoYXQgYSBodW1hbiBjYW4gbGFiZWwgYXMgYSB0b3BvbnltIG1hdGNoIG9yIG1pc21hdGNoLgoKYGBge3J9CgojU2hvdWxkIG9ubHkgbmVlZCB0byBkbyB0aGlzIG9uY2UKI01lYXN1cmluZ0xhbmRzY2FwZTo6OmNyZWF0ZV90b3BvbnltX2RhdGFzZXRfZm9ybGFiZWxpbmcoKQoKYGBgCgoKIyBDaG9vc2UgRmVhdHVyZXMgCgpgYGB7cn0KCnZhcnNfaWQgPC0gYygibmFtZV9jbGVhbmVyX2EiLCJuYW1lX2NsZWFuZXJfYiIsJ3Rlc3QnLCdleHRyYW5lZ2F0aXZlJykKdmFyc193ZWlnaHRzIDwtIGMoIndlaWdodHMiKQp2YXJzX3kgPC0gYygicmV4X21hdGNoIikKCnZhcnNfeF9zdHJpbmcgPC0gYygjImV4YWN0X21hdGNoIiwgICAgICAgICAgICAgICAKIkphcm8iLAoiT3B0aW1hbF9TdHJpbmdfQWxpZ25tZW50IiAgICAsCiJMZXZlbnNodGVpbiIsCiJEYW1lcmF1X0xldmVuc2h0ZWluIiAgICAsCiJMb25nZXN0X0NvbW1vbl9TdWJzdHJpbmciICAgICAsCiJxX2dyYW1fMSIsCiJxX2dyYW1fMiIsCiJxX2dyYW1fMyIsCiJxX2dyYW1fNCIsCiJxX2dyYW1fNSIsCidDb3NpbmVfMScsCidDb3NpbmVfMicsCidDb3NpbmVfMycsCidDb3NpbmVfNCcsCidDb3NpbmVfNScsCiJKYWNjYXJkIiAgICAgICAgICAgICAgLAogIkZpcnN0X01pc3RtYXRjaCIgICAgICAgICAsCiJhX25jaGFyIiAgICAgLAoiYl9uY2hhciIgICAsCiJhYl9uY2hhcl9kaWZmIiAgICAgICAsICAgICAgICAgICAgIAoiZEphcm8iLAoiZE9wdGltYWxfU3RyaW5nX0FsaWdubWVudCIgICAgICAsCiJkTGV2ZW5zaHRlaW4iICAgICAsCiJkRGFtZXJhdV9MZXZlbnNodGVpbiIgICwgICAgICAgICAgIAoiZExvbmdlc3RfQ29tbW9uX1N1YnN0cmluZyIsCiJkcV9ncmFtIiwKImRDb3NpbmUiLAoiZEphY2NhcmQiCiMgIk9NIiwKIyAiT01sb2MiLAojICJPTXNsZW4iCiMgLCJPTXNwZWxsIiwKIyAiVFdFRCIsCiMgIkxDUyIsICAgICAgICAgICAgICAgICAKIyAiTENQIiwKIyAiUkxDUCIsCiMgIk5NUyIsCiMgIk5NU01TVCIsICAgICAgICAgICAgICAgICAKIyAiU1ZSc3BlbGwiLAojICJDSEkyIgopIAoKdmFyc194X3N0ZW0gPC0gcGFzdGUwKHZhcnNfeF9zdHJpbmcsICJfc3RlbW1lZCIpCgp2YXJzX3hfc3RyaW5nX2NvcnB1cyA8LSBjKAoKImNvcnB1c19tZW50aW9uX2NvdW50X2EiLAoiY29ycHVzX21lbnRpb25feWVhcl9taW5fYSIsCiJjb3JwdXNfbWVudGlvbl95ZWFyX21lZGlhbl9hIiwKImNvcnB1c19tZW50aW9uX3llYXJfbWVhbl9hIiwKImNvcnB1c19tZW50aW9uX3llYXJfbWF4X2EiLAoKImNvcnB1c19tZW50aW9uX2NvdW50X2IiLAoiY29ycHVzX21lbnRpb25feWVhcl9taW5fYiIsCiJjb3JwdXNfbWVudGlvbl95ZWFyX21lZGlhbl9iIiwKImNvcnB1c19tZW50aW9uX3llYXJfbWVhbl9iIiwKImNvcnB1c19tZW50aW9uX3llYXJfbWF4X2IiLAoKImdhemV0ZWVyX21lbnRpb25zX2NvdW50X2EiLAoiZ2F6ZXRlZXJfbWVudGlvbnNfY291bnRfYiIsCiJnYXpldGVlcl9zdGVtX21lbnRpb25zX2NvdW50X2EiLAoiZ2F6ZXRlZXJfc3RlbV9tZW50aW9uc19jb3VudF9iIgoKIyJwb3N0Zml4X2hhc19hIgojInBvc3RmaXhfaGFzX2IiIAoKIyJuZ3JhbV9hIiwgI0Rvbid0IHJ1biwgaXQncyBqdXN0IGEgd29yZCBjb3VudCBhbmQgaXQncyBtaXNzaW5nIGlmIGl0J3MgbmV2ZXIgZm91bmQKIyJuZ3JhbV9iIgopCgoKdmFyc194X3N0ZW1fY29ycHVzIDwtIHBhc3RlMCh2YXJzX3hfc3RyaW5nX2NvcnB1cywgIl9zdGVtbWVkIikKdmFyc194X2V2ZXJ5dGhpbmcgPC0gIGMoCiAgICAgICAgICAgICAgICAgICAgICAgIHZhcnNfeF9zdHJpbmcsCiAgICAgICAgICAgICAgICAgICAgICAgIHZhcnNfeF9zdGVtIywKICAgICAgICAgICAgICAgICAgICAgICAgI3ZhcnNfeF9zdHJpbmdfY29ycHVzLCAjZXhjbHVkaW5nIGNvcnB1cyBmZWF0dXJlcwogICAgICAgICAgICAgICAgICAgICAgICAjdmFyc194X3N0ZW1fY29ycHVzICAjZXhjbHVkaW5nIGNvcnB1cyBmZWF0dXJlcwogICAgICAgICAgICAgICAgICAgICAgICApICAgICAKdmFyc194X29ubHlzdHJpbmcgPC0gYyh2YXJzX3hfc3RyaW5nKQp2YXJzX3hfc3RyaW5nY29ycHVzIDwtICBjKHZhcnNfeF9zdHJpbmcsICB2YXJzX3hfc3RyaW5nX2NvcnB1cykgICAgIAp2YXJzX3hfc3RyaW5nX2FuZF9zdGVtIDwtIGModmFyc194X3N0cmluZywgdmFyc194X3N0ZW0pCgpgYGAKCiMgQ3JlYXRlIFRyYWluaW5nIFRlc3QgU3BsaXQKCmBgYHtyfQoKaGFuZGxhYmVsZWQgPC0gTWVhc3VyaW5nTGFuZHNjYXBlOjo6dG9wb255bV90cmFpbmluZ19kYXRhc2V0X2xvYWQoKQoKZnJvbXNjcmF0Y2g9RgppZihmcm9tc2NyYXRjaCl7ICAgIAogIAogICAgdG9wb255bV90cmFpbmluZ19kYXRhc2V0IDwtIGNyZWF0ZV90b3BvbnltX3NwbGl0X3RyYWluaW5nX3Rlc3QoIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhhbmRsYWJlbGVkPWhhbmRsYWJlbGVkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcnNfaWQ9dmFyc19pZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJzX3dlaWdodHM9dmFyc193ZWlnaHRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhcnNfeT12YXJzX3ksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyc194PXZhcnNfeF9ldmVyeXRoaW5nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5lZ19jb3VudD0wLCAjeW91IGxvc2Ugc28gbWFueSB0byBjb3NpbmUgZGlzdGFuY2UKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcm9tc2NyYXRjaD1UCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgIAogICAgc2F2ZVJEUyh0b3BvbnltX3RyYWluaW5nX2RhdGFzZXQsCiAgICAgICAgICAgIGZpbGU9Z2x1ZShnZXR3ZCgpLCAiLy4uL2luc3QvZXh0ZGF0YS90b3BvbnltX3RyYWluaW5nX2RhdGFzZXQuUmRzIikKICAgICAgICAgICAgKQp9CnRvcG9ueW1fdHJhaW5pbmdfZGF0YXNldCA8LSByZWFkUkRTKHN5c3RlbS5maWxlKCJleHRkYXRhIiwgInRvcG9ueW1fdHJhaW5pbmdfZGF0YXNldC5SZHMiLCBwYWNrYWdlID0gIk1lYXN1cmluZ0xhbmRzY2FwZSIpKQogIApgYGAKCiMgU3VtbWFyaXplIHRyYWluaW5nIGRhdGEKCmBgYHtyfQoKCmdsdWU6OmdsdWUoIkhhbmRsYWJlbGVkIFRyYWluaW5nIERhdGFzZXQiKQpkaW0oaGFuZGxhYmVsZWQpCnRhYmxlKGhhbmRsYWJlbGVkJHJleF9tYXRjaCkKCgpnbHVlOjpnbHVlKCJBbGwgRGF0YSBSZXNhbXBsZWQgd2l0aCBGZWF0dXJlcyBcbiIpCmRpbSh0b3BvbnltX3RyYWluaW5nX2RhdGFzZXQkeHlfYWxsKQp0YWJsZSh0b3BvbnltX3RyYWluaW5nX2RhdGFzZXQkeHlfYWxsJHJleF9tYXRjaCkKCgpnbHVlOjpnbHVlKCJUcmFpbmluZyBTcGxpdCBcbiIpCmRpbSh0b3BvbnltX3RyYWluaW5nX2RhdGFzZXQkeHlfdHJhaW4pCnRhYmxlKHRvcG9ueW1fdHJhaW5pbmdfZGF0YXNldCR4eV90cmFpbiRyZXhfbWF0Y2gpCgoKZ2x1ZTo6Z2x1ZSgiVGVzdCBTcGxpdCBcbiIpCmRpbSh0b3BvbnltX3RyYWluaW5nX2RhdGFzZXQkeHlfdGVzdCkKdGFibGUodG9wb255bV90cmFpbmluZ19kYXRhc2V0JHh5X3Rlc3QkcmV4X21hdGNoKQoKYGBgCgoKIyBGaXQgWEdCb29zdCBtb2RlbHMgcHJlZGljdGluZyBtYXRjaAoKYGBge3J9CgoKcmVxdWlyZShtZXRob2RzKQpnbG9iYWxfdGVzdCA8LSB0b3BvbnltX3RyYWluaW5nX2RhdGFzZXRbWyd4eV9hbGwnXV0gJT4lIGZpbHRlcih0ZXN0ICVpbiUgVCAgJiAhaXMubmEocmV4X21hdGNoKSApCgpzdW13bmVnPXN1bSh0b3BvbnltX3RyYWluaW5nX2RhdGFzZXRbWyd4eV90cmFpbiddXSRyZXhfbWF0Y2g9PTApCnN1bXdwb3M9c3VtKHRvcG9ueW1fdHJhaW5pbmdfZGF0YXNldFtbJ3h5X3RyYWluJ11dJHJleF9tYXRjaD09MSkKcGFyYW0yIDwtIGxpc3QoIm9iamVjdGl2ZSIgPSBNZWFzdXJpbmdMYW5kc2NhcGU6Ojpsb2dyZWdvYmosICMib2JqZWN0aXZlIiA9ImJpbmFyeTpsb2dpc3RpYyIsICMKICAgICAgICAgICAgICAic2NhbGVfcG9zX3dlaWdodCIgPSBzdW13bmVnIC8gc3Vtd3BvcywKICAgICAgICAgICAgICAiZXRhIiA9IDAuMywKICAgICAgICAgICAgICAibWF4X2RlcHRoIiA9IDYsCiAgICAgICAgICAgICAgImV2YWxfbWV0cmljIiA9ICJhdWMiLAogICAgICAgICAgICAgICMiZXZhbF9tZXRyaWMiID0gYXJlYV91bmRlcl9wcl9jdXJ2ZV9tZXRyaWMsCiAgICAgICAgICAgICAgIyJldmFsX21ldHJpYyIgPSAiYW1zQDAuMTUiLAogICAgICAgICAgICAgICJzaWxlbnQiID0gMSwKICAgICAgICAgICAgICAibnRocmVhZCIgPSA0OCwKICAgICAgICAgICAgICAnbWF4aW1pemUnPVQpCgp0b3BvbnltX3hiX2V2ZXJ5dGhpbmcyIDwtIE1lYXN1cmluZ0xhbmRzY2FwZTo6OnRyYWluX2FuX3hiKHRvcG9ueW1fdHJhaW5pbmdfZGF0YXNldFtbJ3h5X3RyYWluJ11dLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9wb255bV90cmFpbmluZ19kYXRhc2V0W1sneHlfdGVzdCddXSAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YXJzX3g9dmFyc194X2V2ZXJ5dGhpbmcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbT1wYXJhbTIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1c2Vfd2VpZ2h0cz1GLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXh0cmFjdF9mZWF0dXJlcz1GICkKeGdiLnNhdmUodG9wb255bV94Yl9ldmVyeXRoaW5nMiwKICAgICAgIGdsdWU6OmdsdWUoZ2V0d2QoKSwgIi8uLi9pbnN0L2V4dGRhdGEvdG9wb255bV94Yl9ldmVyeXRoaW5nMi5iaW4iKQogICAgICAgICApICNIYXZlIHRvIHNhdmUgc2VwYXJhdGVseSBmb3Igc29tZSByZWFzb24KCnRvcG9ueW1feGJfb25seXN0cmluZyA8LSBNZWFzdXJpbmdMYW5kc2NhcGU6Ojp0cmFpbl9hbl94Yih0b3BvbnltX3RyYWluaW5nX2RhdGFzZXRbWyd4eV90cmFpbiddXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvcG9ueW1fdHJhaW5pbmdfZGF0YXNldFtbJ3h5X3Rlc3QnXV0gLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyc194PXZhcnNfeF9vbmx5c3RyaW5nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyYW09cGFyYW0yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXNlX3dlaWdodHM9RiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4dHJhY3RfZmVhdHVyZXM9RiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pc3Npbmc9TkEpCnhnYi5zYXZlKHRvcG9ueW1feGJfb25seXN0cmluZywKICAgICAgIGdsdWU6OmdsdWUoZ2V0d2QoKSwgIi8uLi9pbnN0L2V4dGRhdGEvdG9wb255bV94Yl9vbmx5c3RyaW5nLmJpbiIpCiAgICAgICAgICkgI0hhdmUgdG8gc2F2ZSBzZXBhcmF0ZWx5IGZvciBzb21lIHJlYXNvbgoKCgp0b3BvbnltX3hiX3N0cmluZ19hbmRfc3RlbSA8LSBNZWFzdXJpbmdMYW5kc2NhcGU6Ojp0cmFpbl9hbl94Yih0b3BvbnltX3RyYWluaW5nX2RhdGFzZXRbWyd4eV90cmFpbiddXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvcG9ueW1fdHJhaW5pbmdfZGF0YXNldFtbJ3h5X3Rlc3QnXV0gLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyc194PXZhcnNfeF9zdHJpbmdfYW5kX3N0ZW0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXJhbT1wYXJhbTIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1c2Vfd2VpZ2h0cz1GLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXh0cmFjdF9mZWF0dXJlcz1GLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWlzc2luZz1OQSkKCnhnYi5zYXZlKHRvcG9ueW1feGJfc3RyaW5nX2FuZF9zdGVtLAogICAgICAgZ2x1ZTo6Z2x1ZShnZXR3ZCgpLCAiLy4uL2luc3QvZXh0ZGF0YS90b3BvbnltX3hiX3N0cmluZ19hbmRfc3RlbS5iaW4iKQogICAgICAgICApICNIYXZlIHRvIHNhdmUgc2VwYXJhdGVseSBmb3Igc29tZSByZWFzb24KCgoKYGBgCgooQXBwZW5kaXggRmlndXJlIDUsIEFVQyBhbmQgUHJlY2lzaW9uIFJlY2FsbCBjdXJ2ZXMgZm9yIHRvcG9ueW0gZnV6enkgbWF0Y2hlciBzdGFnZSAyLCBwcmVkaWN0aW5nIGxpa2VsaWhvb2Qgb2YgYSBtYXRjaC4pCgpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTh9CgpkdGVzdDwteGdiLkRNYXRyaXgoZGF0YT0gYXMubWF0cml4KGFzLmRhdGEuZnJhbWUoZ2xvYmFsX3Rlc3QpWyx2YXJzX3hfZXZlcnl0aGluZ10pLAogICAgICAgICAgICAgICAgICAgbGFiZWw9YXMubnVtZXJpYyhnbG9iYWxfdGVzdCRyZXhfbWF0Y2gpLAogICAgICAgICAgICAgICAgICAgd2VpZ2h0ID0gZ2xvYmFsX3Rlc3Qkd2VpZ2h0cywKICAgICAgICAgICAgICAgICAgIG1pc3NpbmcgPSBOQSkKCmdsb2JhbF90ZXN0JHRvcG9ueW1feGJfZXZlcnl0aGluZzIgPC0gcHJlZGljdCh0b3BvbnltX3hiX2V2ZXJ5dGhpbmcyLCBkdGVzdCApCmdsb2JhbF90ZXN0JHRvcG9ueW1feGJfZXZlcnl0aGluZzIgPC0gMS8oMSArIGV4cCgtZ2xvYmFsX3Rlc3QkdG9wb255bV94Yl9ldmVyeXRoaW5nMikpCgojZ2xvYmFsX3Rlc3QkdG9wb255bV94Yl9vbmx5c3RyaW5nIDwtIHByZWRpY3QodG9wb255bV94Yl9vbmx5c3RyaW5nLCBkdGVzdCApCiNnbG9iYWxfdGVzdCR0b3BvbnltX3hiX29ubHlzdHJpbmcgPC0gMS8oMSArIGV4cCgtZ2xvYmFsX3Rlc3QkdG9wb255bV94Yl9vbmx5c3RyaW5nKSkKCgpnbG9iYWxfdGVzdCR0b3BvbnltX3hiX3N0cmluZ19hbmRfc3RlbSA8LSBwcmVkaWN0KHRvcG9ueW1feGJfc3RyaW5nX2FuZF9zdGVtLCBkdGVzdCApCmdsb2JhbF90ZXN0JHRvcG9ueW1feGJfc3RyaW5nX2FuZF9zdGVtIDwtIDEvKDEgKyBleHAoLWdsb2JhbF90ZXN0JHRvcG9ueW1feGJfc3RyaW5nX2FuZF9zdGVtKSkKCiNwX2xvYWQoTWV0cmljcykKI3BfbG9hZChNTG1ldHJpY3MpCnRvY29tcGFyZSA8LSBjKCMidG9wb255bV94Yl9ldmVyeXRoaW5nMiIsCiAgICAgICAgICAgICAgICMidG9wb255bV94Yl9vbmx5c3RyaW5nIiMsCiAgICAgICAgICAgICAgICMidG9wb255bV94Yl9zdHJpbmdjb3JwdXMiIywKICAgICAgICAgICAgICAgInRvcG9ueW1feGJfc3RyaW5nX2FuZF9zdGVtIgogICAgICAgICAgICAgICApICMgInRvcG9ueW1feGJfZXZlcnl0aGluZyIsLCAidG9wb255bV94Yl9ldmVyeXRoaW5nMl9leHRyYSIsInRvcG9ueW1feGJfb25seXN0cmluZ19leHRyYSIsInRvcG9ueW1feGJfZXZlcnl0aGluZzJfZXh0cmFfbGFyZ2UiLCAidG9wb255bV94Yl9vbmx5c3RyaW5nX2V4dHJhX2xhcmdlIiwgInRvcG9ueW1feGJfZXZlcnl0aGluZzJfdWJlcl9sYXJnZSIgLCAidG9wb255bV94Yl9vbmx5c3RyaW5nX3ViZXJfbGFyZ2UiCnByaW50KCJjZSIpCnNhcHBseSh0b2NvbXBhcmUsIGZ1bmN0aW9uKHEpIHsgIE1ldHJpY3M6OmNlKGFjdHVhbD1nbG9iYWxfdGVzdCRyZXhfbWF0Y2gsIHByZWRpY3RlZD1nbG9iYWxfdGVzdFsscV0+LjUpICB9KQpwcmludCgiRjFfU2NvcmUiKQpzYXBwbHkodG9jb21wYXJlLCBmdW5jdGlvbihxKSB7ICBNTG1ldHJpY3M6OkYxX1Njb3JlKGdsb2JhbF90ZXN0JHJleF9tYXRjaCwgYXMubnVtZXJpYyggZ2xvYmFsX3Rlc3RbLHFdPi41KSApICB9KQpwcmludCgibG9nbG9zcyIpCnNhcHBseSh0b2NvbXBhcmUsIGZ1bmN0aW9uKHEpIHsgIE1ldHJpY3M6OmxvZ0xvc3MoYWN0dWFsPWdsb2JhbF90ZXN0JHJleF9tYXRjaCwgcHJlZGljdGVkPWdsb2JhbF90ZXN0WyxxXSkgIH0pCnByaW50KCJjb25mdXNpb24iKQpzYXBwbHkodG9jb21wYXJlLCBmdW5jdGlvbihxKSB7ICB0YWJsZShnbG9iYWxfdGVzdCRyZXhfbWF0Y2gsICBnbG9iYWxfdGVzdFsscV0+LjUpICB9KQoKCiNwX2xvYWQocHJlY3JlYykKbXNtZGF0MSA8LSBwcmVjcmVjOjpldmFsbW9kKCBwcmVjcmVjOjptbWRhdGEoc2NvcmVzPWxpc3QoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI2dsb2JhbF90ZXN0JHRvcG9ueW1feGJfZXZlcnl0aGluZywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjZ2xvYmFsX3Rlc3QkdG9wb255bV9yZl9ldmVyeXRoaW5nLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICNnbG9iYWxfdGVzdCR0b3BvbnltX3hiX2V2ZXJ5dGhpbmdfZXh0cmFjdGVkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICNnbG9iYWxfdGVzdCR0b3BvbnltX3hiX2V2ZXJ5dGhpbmcyLCAjaGlnaGVzdCBQUkMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjZ2xvYmFsX3Rlc3QkdG9wb255bV94Yl9ldmVyeXRoaW5nMywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjZ2xvYmFsX3Rlc3QkdG9wb255bV94Yl9ldmVyeXRoaW5nNCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnbG9iYWxfdGVzdCR0b3BvbnltX3hiX3N0cmluZ19hbmRfc3RlbSMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI2dsb2JhbF90ZXN0JHRvcG9ueW1feGJfb25seXN0cmluZyMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI2dsb2JhbF90ZXN0JHRvcG9ueW1feGJfc3RyaW5nY29ycHVzIywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjZ2xvYmFsX3Rlc3QkdG9wb255bV94Yl9vbmx5c3RyaW5nX3RpbnkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjZ2xvYmFsX3Rlc3QkdG9wb255bV94Yl9ldmVyeXRoaW5nMl9leHRyYSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjZ2xvYmFsX3Rlc3QkdG9wb255bV94Yl9vbmx5c3RyaW5nX2V4dHJhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICNnbG9iYWxfdGVzdCR0b3BvbnltX3hiX2V2ZXJ5dGhpbmcyX2V4dHJhX2xhcmdlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICNnbG9iYWxfdGVzdCR0b3BvbnltX3hiX29ubHlzdHJpbmdfZXh0cmFfbGFyZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgI2dsb2JhbF90ZXN0JHRvcG9ueW1feGJfZXZlcnl0aGluZzJfdWJlcl9sYXJnZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjZ2xvYmFsX3Rlc3QkdG9wb255bV94Yl9vbmx5c3RyaW5nX3ViZXJfbGFyZ2UKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLAogICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHM9YXMubnVtZXJpYyhhcy5kYXRhLmZyYW1lKGdsb2JhbF90ZXN0KSRyZXhfbWF0Y2gpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RuYW1lcyA9IGMoIyJ0b3BvbnltX3hiX2V2ZXJ5dGhpbmciLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyJ0b3BvbnltX3JmX2V2ZXJ5dGhpbmciLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyJ0b3BvbnltX3hiX2V2ZXJ5dGhpbmdfZmVhdHVyZXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyJ0b3BvbnltX3hiX2V2ZXJ5dGhpbmcyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMidG9wb255bV94Yl9ldmVyeXRoaW5nMyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjInRvcG9ueW1feGJfZXZlcnl0aGluZzQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInRvcG9ueW1feGJfc3RyaW5nX2FuZF9zdGVtIiMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjInRvcG9ueW1feGJfb25seXN0cmluZyIjLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyJ0b3BvbnltX3hiX3N0cmluZ2NvcnB1cyIjLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyJ0b3BvbnltX3hiX29ubHlzdHJpbmdfdGlueSIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMidG9wb255bV94Yl9ldmVyeXRoaW5nMl9leHRyYSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjInRvcG9ueW1feGJfb25seXN0cmluZ19leHRyYSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjInRvcG9ueW1feGJfZXZlcnl0aGluZzJfZXh0cmFfbGFyZ2UiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMidG9wb255bV94Yl9vbmx5c3RyaW5nX2V4dHJhX2xhcmdlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMidG9wb255bV94Yl9ldmVyeXRoaW5nMl91YmVyX2xhcmdlIiAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjInRvcG9ueW1feGJfb25seXN0cmluZ191YmVyX2xhcmdlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgICAgICAgICApICkKCm1zbWRhdDEKCnVhdWMxIDwtIHByZWNyZWM6OmV2YWxtb2Qoc2NvcmVzID0gZ2xvYmFsX3Rlc3QkdG9wb255bV94Yl9zdHJpbmdfYW5kX3N0ZW0sCiAgICAgICAgICAgICAgICAgbGFiZWxzPWFzLm51bWVyaWMoYXMuZGF0YS5mcmFtZShnbG9iYWxfdGVzdCkkcmV4X21hdGNoKSMsCiAgICAgICAgICAgICAgICAgI21vZGU9ImF1Y3JvYyIKICAgICAgICAgICAgICAgICApCgpwbG90KHVhdWMxKQoKI2Rldi5vZmYoKQpwZGYoZmlsZT1nbHVlOjpnbHVlKGRpcl9maWd1cmVzLCAicF9hdWNfc3RhZ2UyLnBkZiIpLCB3aWR0aD01LjUpCnBsb3QodWF1YzEpCiNhdXRvcGxvdCh1YXVjMSkKZGV2Lm9mZigpCmF1dG9wbG90KHVhdWMxKQoKYGBgCgoKRXZhbHVhdGUgdGhlIG1vZGVsCihBcHBlbmRpeCBGaWd1cmUgNiwgQVVDIGFuZCBQcmVjaXNpb24gUmVjYWxsIGN1cnZlcyBmb3IgdG9wb255bSBmdXp6eSBtYXRjaGVyIHN0YWdlIDIsIHByZWRpY3RpbmcgbGlrZWxpaG9vZCBvZiBhIG1hdGNoLikKCgpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTh9CiNwX2xvYWQoRGlhZ3JhbW1lUikKI3hnYi5wbG90LnRyZWUobW9kZWwgPSB4YikKI3hnYi5wbG90LnRyZWUobW9kZWwgPSB0b3BvbnltX3hiX2V2ZXJ5dGhpbmcyLCB0cmVlcyA9IDAsIHNob3dfbm9kZV9pZCA9IFRSVUUsIHJlbmRlciA9IFRSVUUpCgppbXBvcnRhbmNlX2ltcG9ydGFuY2UgPC0geGdiLmltcG9ydGFuY2UoZmVhdHVyZV9uYW1lcz12YXJzX3hfc3RyaW5nX2FuZF9zdGVtLCBtb2RlbCA9IHRvcG9ueW1feGJfc3RyaW5nX2FuZF9zdGVtKQp4Z2IucGxvdC5pbXBvcnRhbmNlKGltcG9ydGFuY2VfaW1wb3J0YW5jZSkKCnBkZihmaWxlPWdsdWU6OmdsdWUoZGlyX2ZpZ3VyZXMsICJwX3ZhcmlhYmxlX2ltcG9ydGFuY2Vfc3RhZ2UyLnBkZiIpLCB3aWR0aD01LjUsIGhlaWdodD02KQp4Z2IucGxvdC5pbXBvcnRhbmNlKGltcG9ydGFuY2VfaW1wb3J0YW5jZSkKI2F1dG9wbG90KHVhdWMxKQpkZXYub2ZmKCkKCmBgYAoKIyBBbmFseXNpcyBvZiBSZXNpZHVhbHMKCmBgYHtyfQoKZ2xvYmFsX3Rlc3QkdG9wb255bV94Yl9ldmVyeXRoaW5nMl9jb3JyZWN0IDwtIChnbG9iYWxfdGVzdCR0b3BvbnltX3hiX2V2ZXJ5dGhpbmcyPi41KSA9PSBnbG9iYWxfdGVzdCRyZXhfbWF0Y2gKCgojZ2xvYmFsX3Rlc3QkdG9wb255bV94Yl9vbmx5c3RyaW5nX3ViZXJfbGFyZ2VfY29ycmVjdCA8LSAoZ2xvYmFsX3Rlc3QkdG9wb255bV94Yl9vbmx5c3RyaW5nX3ViZXJfbGFyZ2U+LjUpPT1nbG9iYWxfdGVzdCRyZXhfbWF0Y2gKI3RhYmxlKGdsb2JhbF90ZXN0JHRvcG9ueW1feGJfb25seXN0cmluZ191YmVyX2xhcmdlX2NvcnJlY3QpCgoKYGBgCgoK