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.

rr #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