Demonstrates that georeferencing options vary terms of recall (how many event locations they recover) and accuracy (how far away their imputed locations tend to be from the true location)

rm(list=ls()); gc()
# !diagnostics off
library(MeasuringLandscape)
library(tidyverse)
dir_figures <- glue::glue(getwd(), "/../paper/figures/")
gc()
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)

Load Files

Load the events file

#Load Events
events_sf <- readRDS(system.file("extdata", "events_sf.Rdata", package = "MeasuringLandscape")) 
print(dim(events_sf))
[1] 10469   104
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 the suggestions file

#Reload from scratch each time in case we subset sometehing weirdly
# georef_all_dt_recommendations <- readRDS(system.file("extdata", "georef_all_dt_recomendations.Rds", package = "MeasuringLandscape")) 
georef_all_dt <- readRDS(system.file("extdata", "georef_all_dt_recomendations.Rds", package = "MeasuringLandscape")) 
glue::glue("Size of the Suggestions dataset ", nrow(georef_all_dt))
Size of the Suggestions dataset 532711
table(!is.na(events_sf$name_cleaner)) #8,638 events have at least some text

FALSE  TRUE 
 1831  8638 
table(events_sf$name_cleaner %in% georef_all_dt$name_cleaner) #8,242 events have a name in our suggestions list

 TRUE 
10469 
table(events_sf$name_cleaner %in% georef_all_dt$name_cleaner[!is.na(georef_all_dt$georef_b)]) #7,532 events with at least one gazetteer suggestion

FALSE  TRUE 
 2527  7942 
georef_all_dt %>% ggplot(aes(x=distance_km)) + geom_histogram(bins = 100)

georef_all_dt %>% summary(distance_km)
  event_hash        name_cleaner       document_district_clean document_unit_type document_date_best_year   georef_b         toponym_xb_model_prediction  place_hash        source_dataset     geometry_type     
 Length:532711      Length:532711      Length:532711           Length:532711      Min.   :1952            Length:532711      Min.   :0.5001              Length:532711      Length:532711      Length:532711     
 Class :character   Class :character   Class :character        Class :character   1st Qu.:1953            Class :character   1st Qu.:0.9788              Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character        Mode  :character   Median :1954            Mode  :character   Median :0.9974              Mode  :character   Mode  :character   Mode  :character  
                                                                                  Mean   :1954                               Mean   :0.9428                                                                      
                                                                                  3rd Qu.:1954                               3rd Qu.:0.9975                                                                      
                                                                                  Max.   :2052                               Max.   :0.9996                                                                      
                                                                                  NA's   :63468                              NA's   :2527                                                                        
 feature_code             X1               Y1               X2              Y2           distance_km     SelfReference     fuzzy           handrule1       handrule2       handrule3       handrule4     
 Length:532711      Min.   :34.88    Min.   :-2.70    Min.   :27.27   Min.   :-4.6667   Min.   :  0.00   Mode :logical   Mode :logical   Min.   :1.000   Min.   :1.000   Min.   :1.000   Min.   : 1.000  
 Class :character   1st Qu.:36.85    1st Qu.:-0.84    1st Qu.:36.82   1st Qu.:-0.9375   1st Qu.: 12.79   FALSE:276022    FALSE:167425    1st Qu.:1.000   1st Qu.:1.000   1st Qu.:1.000   1st Qu.: 1.000  
 Mode  :character   Median :36.99    Median :-0.65    Median :36.97   Median :-0.6723   Median : 27.83   TRUE :254162    TRUE :362759    Median :2.000   Median :2.000   Median :1.000   Median : 2.000  
                    Mean   :37.04    Mean   :-0.65    Mean   :37.02   Mean   :-0.6804   Mean   : 43.97   NA's :2527      NA's :2527      Mean   :1.684   Mean   :1.521   Mean   :1.079   Mean   : 3.195  
                    3rd Qu.:37.20    3rd Qu.:-0.45    3rd Qu.:37.20   3rd Qu.:-0.4615   3rd Qu.: 62.52                                   3rd Qu.:2.000   3rd Qu.:2.000   3rd Qu.:1.000   3rd Qu.: 4.000  
                    Max.   :41.72    Max.   : 3.86    Max.   :41.52   Max.   : 4.0616   Max.   :747.43                                   Max.   :2.000   Max.   :2.000   Max.   :4.000   Max.   :15.000  
                    NA's   :195733   NA's   :195733   NA's   :2527    NA's   :2527      NA's   :196476                                   NA's   :2527    NA's   :2527    NA's   :2527    NA's   :2527    
   handrule         rule_ensemble    
 Length:532711      Min.   :  0.872  
 Class :character   1st Qu.: 16.220  
 Mode  :character   Median : 21.756  
                    Mean   : 31.592  
                    3rd Qu.: 41.900  
                    Max.   :364.257  
                                     
table(georef_all_dt$distance_km==0) #These are identical matches, same event on the left hand and right hand sides

 FALSE   TRUE 
331127   5108 
#Exclude all distance = 0 obs, those are self matches
georef_all_dt <- subset(georef_all_dt, 
                        !is.na(name_cleaner) & # must have a name
                        (is.na(distance_km) | distance_km!=0)  ) #Can be either missing or not zero. Only thing we drop is zero because that's a self match
table(georef_all_dt$distance_km==0) #These are identical matches, same event on the left hand and right hand sides

 FALSE 
331127 

Calculate Recall Rate and Distance for Ensembles

This section calculates the recall rate and geographic accuracy of gazetteer suggestions. Recall is the number of events that receive at least one suggestion under a given set of rules. Accuracy is the distance between an observed location with a military coordinate and an imputed location for the same event.

Accuracy is measured differently for hand rule/ensemble and every other specific decision. Hand rule/ensemble accuracy is the distance observed for the best match selected by that process.

Accuracy for the remainder of decisions is the BEST CASE accuracy for any suggestion matching that rule. For example, if the decision is “point” geometry type, then accuracy would be the smallest observed distance of every single suggestion with a point type.

Hand Rules

print("Hand Rule Orderings")
[1] "Hand Rule Orderings"
table(georef_all_dt$handrule)

01_01_01_01 01_02_01_02 01_02_01_03 01_02_01_04 01_02_01_06 01_02_01_07 01_02_01_09 01_02_01_10 01_02_01_13 01_02_01_14 01_02_02_05 01_02_02_11 01_02_02_12 01_02_02_14 01_02_03_08 01_02_03_15 01_02_04_14 
     116885        7253        7301        7772        6175        3063        1641        1320        2326        1846        3354         816         587          70        2075          22         169 
02_01_01_01 02_02_01_02 02_02_01_03 02_02_01_04 02_02_01_06 02_02_01_07 02_02_01_09 02_02_01_10 02_02_01_13 02_02_01_14 02_02_02_05 02_02_02_11 02_02_02_12 02_02_02_14 02_02_02_15 02_02_03_08 02_02_03_14 
     132169       42709       49879       53216       29803        6373        9788        1510        8265        8933        7375         217          89        1953         242        6663          73 
02_02_03_15 02_02_04_14 NA_NA_NA_NA 
        148        2996         696 
#Hand Rule
data.table::setkey(georef_all_dt, event_hash, handrule) #Sort by event and then hand rule
georef_all_dt_handrule <- georef_all_dt[,.SD[1], by=list(event_hash) ] #Take the smallest hand rule score, lower is better
georef_all_dt_handrule_summary <- georef_all_dt_handrule[,list(
                                               rule="Hand Rule",
                                              distance_rmse=sqrt(mean(distance_km^2, na.rm=T)), 
                                              distance_mse=mean(distance_km^2, na.rm=T),
                                              distance_me=mean(distance_km, na.rm=T),
                                              distance_median=median(distance_km, na.rm=T),
                                              source_dataset_count=.N
                                              )
                                        ]
print("Distance for Hand Rule")
[1] "Distance for Hand Rule"
georef_all_dt_handrule_summary

Ensemble Rule

summary(georef_all_dt$rule_ensemble)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.872  16.538  21.756  31.803  42.354 364.257 
georef_all_dt %>% ggplot(aes(x=rule_ensemble)) + geom_histogram(bins=100)

  
#Ensemble Rule
data.table::setkey(georef_all_dt, rule_ensemble)
georef_all_dt_ensemble <- georef_all_dt[,.SD[1], by=list(event_hash) ] #Take the smallest estimated distance, lower is better
georef_all_dt_ensemble_summary <- georef_all_dt_ensemble[,list(
                                               rule="Ensemble Rule",
                                              distance_rmse=sqrt(mean(distance_km^2, na.rm=T)) ,
                                              distance_mse=mean(distance_km^2, na.rm=T),
                                              distance_me=mean(distance_km, na.rm=T),
                                              distance_median=median(distance_km, na.rm=T),
                                              source_dataset_count=.N
                                              )
                                        ]
print("Distance for Ensemble")
[1] "Distance for Ensemble"
georef_all_dt_ensemble_summary
rbind(georef_all_dt_handrule %>% mutate(label="hand"),
      georef_all_dt_ensemble %>% mutate(label="ensemble")) %>% 
      ggplot(aes(x=distance_km, fill=label)) + geom_histogram()

Calculate Recall Rate and Distance for Each Decision

Now calculate the minimum observed distance for any suggestion matching a specific decision.

#Minimum distance by event-source
georef_all_dt_bysource <- georef_all_dt[,
                                        list(distance_min=min(distance_km, na.rm=T) ),
                                        by=list(event_hash, source_dataset)] ; dim(georef_all_dt_bysource)
No non-missing values found in at least one group. Returning 'Inf' for such groups to be consistent with base
[1] 58290     3
georef_all_dt_bysource[!is.finite(distance_min), distance_min:=NA]
#Minimum distance and recall by source
georef_all_dt_bysource_summary <- georef_all_dt_bysource[,
                                                    list(
                                                      distance_rmse=sqrt(mean(distance_min^2, na.rm=T)) ,
                                                      source_dataset_count=.N),
                                                      by=list(source_dataset)
                                                ]
georef_all_dt_byfuzzy <- georef_all_dt[
                                      ,list(distance_min=min(distance_km, na.rm=T) ),
                                      by=list(event_hash, fuzzy)] ;
No non-missing values found in at least one group. Returning 'Inf' for such groups to be consistent with base
georef_all_dt_byfuzzy[!is.finite(distance_min), distance_min:=NA]
georef_all_dt_byfuzzy_summary <- georef_all_dt_byfuzzy[,
                                                       list(distance_rmse=sqrt(mean(distance_min^2, na.rm=T)) ,
                                                      source_dataset_count=.N),
                                                      by=list(fuzzy) ]
georef_all_dt_byselfreference <- georef_all_dt[,list(distance_min=min(distance_km, na.rm=T) ),
                                               by=list(event_hash, SelfReference)] ; dim(georef_all_dt_bysource)
No non-missing values found in at least one group. Returning 'Inf' for such groups to be consistent with base
[1] 58290     3
georef_all_dt_byselfreference[!is.finite(distance_min), distance_min:=NA]
georef_all_dt_byselfreference_summary <- georef_all_dt_byselfreference[,list(
                                                      distance_rmse=sqrt(mean(distance_min^2, na.rm=T)) ,
                                                      source_dataset_count=.N),
                                                      by=list(SelfReference)
                                                ]
georef_all_dt_bygeometry_type <- georef_all_dt[,list(distance_min=min(distance_km, na.rm=T) ),by=list(event_hash, geometry_type
                                                                                            )] ; dim(georef_all_dt_bysource)
No non-missing values found in at least one group. Returning 'Inf' for such groups to be consistent with base
[1] 58290     3
georef_all_dt_bygeometry_type[!is.finite(distance_min), distance_min:=NA]
georef_all_dt_bygeometry_type <- georef_all_dt_bygeometry_type[,list(
                                                      distance_rmse=sqrt(mean(distance_min^2, na.rm=T)) ,
                                                      source_dataset_count=.N),
                                                      by=list(geometry_type)
                                                ]
georef_all_dt_milcoords_summary <- data.frame(rule="Mil. Coord",
                                              distance_rmse=0,
                                              source_dataset_count=sum(!is.na(events_sf$map_coordinate_clean_latitude))
)

Summarize

recall_accuracy <- data.table::rbindlist(list(
  #georef_all_dt_milcoords_summary %>% mutate(label="Mil. Coord") %>% mutate(Type="Rule"),
  georef_all_dt_handrule_summary %>% mutate(label="Hand Rule") %>% mutate(Type="Rule"),
  georef_all_dt_ensemble_summary %>% mutate(label="Ensemble Rule") %>% mutate(Type="Rule"),
  georef_all_dt_bysource_summary %>% mutate(label=source_dataset) %>% mutate(Type="Source Gazetteer"),
  georef_all_dt_byfuzzy_summary %>% mutate(label=ifelse(fuzzy, "Fuzzy","Exact")) %>% mutate(Type="Match Type"),
  georef_all_dt_byselfreference_summary %>% mutate(label=ifelse(SelfReference, "Match To Other Events","No Match To Other Events")) %>%
    mutate(Type="Allow Match To Other Events"),
  georef_all_dt_bygeometry_type %>% mutate(label=geometry_type) %>% mutate(Type="Geometry Type")
),fill=T) %>% mutate(recall_rate=source_dataset_count/10469)

Combine and plot the results for every specific decision and the ensembles.

(Figure 2, Comparison of recall and accuracy by georeferencing method)

sentence_case <- function(x) stringr::str_to_sentence(tolower(gsub("_"," ",x)))
#The first time you run this, you'll need to install these additional fonts
#install.packages("extrafont");
#library(extrafont)
#library(extrafont)
#extrafont::font_import(prompt=F )
#capabilities()
#windowsFonts()
#sort(as.vector(unlist(windowsFonts())))
fonts <- c('Times New Roman',
           'Calibri',
           'Courier New',
           "Georgia",
           "Tunga",
           "Lucida Fax")
           #'serif','Helvetica','Bookman','Palatino')
#library(ggplot2)
fontfaces <- factor(c("plain","bold","italic","bold.italic","plain","plain"))
colours = c("Self reference" = "#F8766D",
            "Geometry type" = "#A3A500",
            "Match type" = "#00BF7D",
            "Rule" = "#00B0F6",
            "Source dataset"="#E76BF3",
            "Original geo info"="#53B400") 
#p_load(ggrepel, tools)
p_RecallBias <- recall_accuracy %>% 
               filter(!label %in% "tgn") %>% #exclude TGN for being an outlier
               mutate(
                      label=sentence_case(label),
                      Type=sentence_case(Type)
                      ) %>%
       ggplot(
       aes(x=distance_rmse,
           y=source_dataset_count / 10469,
             color=Type,
             label=label,
             family = fonts[as.numeric(as.factor(Type))],
             fontface= fontfaces[as.numeric(as.factor(Type))]
             )
)  + 
  ggrepel::geom_text_repel(size=3) +
  theme_bw() +
  xlab(stringr::str_to_sentence("Root Mean Squared Distance from Observed Coordinate (km)")) +
  ylab(stringr::str_to_sentence("Recovery Rate")) +
  theme(
    legend.position = c(0.2, 0.3), # c(0,0) bottom left, c(1,1) top-right.
  )
Error in eval(lhs, parent, parent) : object 'recall_accuracy' not found

Interpretation

In terms of accuracy, we find wide variation between decisions. In brief: Ensemble Rule > Hand Rule Exact > Fuzzy Point > Multipolygon > Polygon > Linestring Match to Other Events > No Match to Other Events events > nga > livestock_points" > historical > geonames > bing> kenya_cadastral_district > livestock_boundaries > gadm > kenya_district1962 > wikidata > google > kenya_cadastral > openstreetmap > tgn

The hand rules and supervised ensemble represent a compromise between high recovery rate and best case accuracy of each individual decision.

LS0tCnRpdGxlOiAiMDcgUmVjYWxsIGFuZCBBY2N1cmFjeSIKYXV0aG9yOiAiUmV4IFcuIERvdWdsYXNzIGFuZCBLcmlzdGVuIEhhcmtuZXNzIgpkYXRlOiAiTWFyY2ggOSwgMjAxOCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCjxzdHlsZT4KICAgIGJvZHkgLm1haW4tY29udGFpbmVyIHsKICAgICAgICBtYXgtd2lkdGg6IDEwMCU7CiAgICB9Cjwvc3R5bGU+CgoKRGVtb25zdHJhdGVzIHRoYXQgZ2VvcmVmZXJlbmNpbmcgb3B0aW9ucyB2YXJ5IHRlcm1zIG9mIHJlY2FsbCAoaG93IG1hbnkgZXZlbnQgbG9jYXRpb25zIHRoZXkgcmVjb3ZlcikgYW5kIGFjY3VyYWN5IChob3cgZmFyIGF3YXkgdGhlaXIgaW1wdXRlZCBsb2NhdGlvbnMgdGVuZCB0byBiZSBmcm9tIHRoZSB0cnVlIGxvY2F0aW9uKQoKYGBge3IgLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSB9CnJtKGxpc3Q9bHMoKSk7IGdjKCkKIyAhZGlhZ25vc3RpY3Mgb2ZmCmxpYnJhcnkoTWVhc3VyaW5nTGFuZHNjYXBlKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKCmRpcl9maWd1cmVzIDwtIGdsdWU6OmdsdWUoZ2V0d2QoKSwgIi8uLi9wYXBlci9maWd1cmVzLyIpCgpnYygpCgprbml0cjo6b3B0c19rbml0JHNldChwcm9ncmVzcyA9IFRSVUUsIHZlcmJvc2UgPSBUUlVFKQprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTgsICB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCBjYWNoZT1UUlVFKQpvcHRpb25zKHdpZHRoID0gMTYwKQoKYGBgCgojIExvYWQgRmlsZXMKCkxvYWQgdGhlIGV2ZW50cyBmaWxlCgpgYGB7cn0KCiNMb2FkIEV2ZW50cwpldmVudHNfc2YgPC0gcmVhZFJEUyhzeXN0ZW0uZmlsZSgiZXh0ZGF0YSIsICJldmVudHNfc2YuUmRhdGEiLCBwYWNrYWdlID0gIk1lYXN1cmluZ0xhbmRzY2FwZSIpKSAKcHJpbnQoZGltKGV2ZW50c19zZikpCmV2ZW50c19zZl90ZXh0X2Nvb3JkX3VuaXF1ZSA8LSBwbHlyOjpkZHBseShldmVudHNfc2ZbLGMoJ2xvY2F0aW9uX3RleHQnLCduYW1lX2NsZWFuJywnbmFtZV9jbGVhbmVyJywnZG9jdW1lbnRfZGlzdHJpY3RfY2xlYW4nLCdtYXBfY29vcmRpbmF0ZV9jbGVhbl9sYXRpdHVkZScsJ21hcF9jb29yZGluYXRlX2NsZWFuX2xvbmdpdHVkZScpXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJsb2NhdGlvbl90ZXh0IiwgdHJhbnNmb3JtLAogICAgICBtYXBfY29vcmRpbmF0ZV9oYXMgPXN1bSghaXMubmEobWFwX2Nvb3JkaW5hdGVfY2xlYW5fbGF0aXR1ZGUpKQogICAgICApCgoKYGBgCgpMb2FkIHRoZSBzdWdnZXN0aW9ucyBmaWxlCgpgYGB7cn0KCiNSZWxvYWQgZnJvbSBzY3JhdGNoIGVhY2ggdGltZSBpbiBjYXNlIHdlIHN1YnNldCBzb21ldGVoaW5nIHdlaXJkbHkKIyBnZW9yZWZfYWxsX2R0X3JlY29tbWVuZGF0aW9ucyA8LSByZWFkUkRTKHN5c3RlbS5maWxlKCJleHRkYXRhIiwgImdlb3JlZl9hbGxfZHRfcmVjb21lbmRhdGlvbnMuUmRzIiwgcGFja2FnZSA9ICJNZWFzdXJpbmdMYW5kc2NhcGUiKSkgCmdlb3JlZl9hbGxfZHQgPC0gcmVhZFJEUyhzeXN0ZW0uZmlsZSgiZXh0ZGF0YSIsICJnZW9yZWZfYWxsX2R0X3JlY29tZW5kYXRpb25zLlJkcyIsIHBhY2thZ2UgPSAiTWVhc3VyaW5nTGFuZHNjYXBlIikpIApnbHVlOjpnbHVlKCJTaXplIG9mIHRoZSBTdWdnZXN0aW9ucyBkYXRhc2V0ICIsIG5yb3coZ2VvcmVmX2FsbF9kdCkpCgp0YWJsZSghaXMubmEoZXZlbnRzX3NmJG5hbWVfY2xlYW5lcikpICM4LDYzOCBldmVudHMgaGF2ZSBhdCBsZWFzdCBzb21lIHRleHQKdGFibGUoZXZlbnRzX3NmJG5hbWVfY2xlYW5lciAlaW4lIGdlb3JlZl9hbGxfZHQkbmFtZV9jbGVhbmVyKSAjOCwyNDIgZXZlbnRzIGhhdmUgYSBuYW1lIGluIG91ciBzdWdnZXN0aW9ucyBsaXN0CnRhYmxlKGV2ZW50c19zZiRuYW1lX2NsZWFuZXIgJWluJSBnZW9yZWZfYWxsX2R0JG5hbWVfY2xlYW5lclshaXMubmEoZ2VvcmVmX2FsbF9kdCRnZW9yZWZfYildKSAjNyw1MzIgZXZlbnRzIHdpdGggYXQgbGVhc3Qgb25lIGdhemV0dGVlciBzdWdnZXN0aW9uCgpgYGAKCmBgYHtyfQoKZ2VvcmVmX2FsbF9kdCAlPiUgZ2dwbG90KGFlcyh4PWRpc3RhbmNlX2ttKSkgKyBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMTAwKQoKZ2VvcmVmX2FsbF9kdCAlPiUgc3VtbWFyeShkaXN0YW5jZV9rbSkKCnRhYmxlKGdlb3JlZl9hbGxfZHQkZGlzdGFuY2Vfa209PTApICNUaGVzZSBhcmUgaWRlbnRpY2FsIG1hdGNoZXMsIHNhbWUgZXZlbnQgb24gdGhlIGxlZnQgaGFuZCBhbmQgcmlnaHQgaGFuZCBzaWRlcwoKI0V4Y2x1ZGUgYWxsIGRpc3RhbmNlID0gMCBvYnMsIHRob3NlIGFyZSBzZWxmIG1hdGNoZXMKZ2VvcmVmX2FsbF9kdCA8LSBzdWJzZXQoZ2VvcmVmX2FsbF9kdCwgCiAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYShuYW1lX2NsZWFuZXIpICYgIyBtdXN0IGhhdmUgYSBuYW1lCiAgICAgICAgICAgICAgICAgICAgICAgIChpcy5uYShkaXN0YW5jZV9rbSkgfCBkaXN0YW5jZV9rbSE9MCkgICkgI0NhbiBiZSBlaXRoZXIgbWlzc2luZyBvciBub3QgemVyby4gT25seSB0aGluZyB3ZSBkcm9wIGlzIHplcm8gYmVjYXVzZSB0aGF0J3MgYSBzZWxmIG1hdGNoCgp0YWJsZShnZW9yZWZfYWxsX2R0JGRpc3RhbmNlX2ttPT0wKSAjVGhlc2UgYXJlIGlkZW50aWNhbCBtYXRjaGVzLCBzYW1lIGV2ZW50IG9uIHRoZSBsZWZ0IGhhbmQgYW5kIHJpZ2h0IGhhbmQgc2lkZXMKCmBgYCAKCiMgQ2FsY3VsYXRlIFJlY2FsbCBSYXRlIGFuZCBEaXN0YW5jZSBmb3IgRW5zZW1ibGVzCgpUaGlzIHNlY3Rpb24gY2FsY3VsYXRlcyB0aGUgcmVjYWxsIHJhdGUgYW5kIGdlb2dyYXBoaWMgYWNjdXJhY3kgb2YgZ2F6ZXR0ZWVyIHN1Z2dlc3Rpb25zLiBSZWNhbGwgaXMgdGhlIG51bWJlciBvZiBldmVudHMgdGhhdCByZWNlaXZlIGF0IGxlYXN0IG9uZSBzdWdnZXN0aW9uIHVuZGVyIGEgZ2l2ZW4gc2V0IG9mIHJ1bGVzLiBBY2N1cmFjeSBpcyB0aGUgZGlzdGFuY2UgYmV0d2VlbiBhbiBvYnNlcnZlZCBsb2NhdGlvbiB3aXRoIGEgbWlsaXRhcnkgY29vcmRpbmF0ZSBhbmQgYW4gaW1wdXRlZCBsb2NhdGlvbiBmb3IgdGhlIHNhbWUgZXZlbnQuIAoKQWNjdXJhY3kgaXMgbWVhc3VyZWQgZGlmZmVyZW50bHkgZm9yIGhhbmQgcnVsZS9lbnNlbWJsZSBhbmQgZXZlcnkgb3RoZXIgc3BlY2lmaWMgZGVjaXNpb24uIEhhbmQgcnVsZS9lbnNlbWJsZSBhY2N1cmFjeSBpcyB0aGUgZGlzdGFuY2Ugb2JzZXJ2ZWQgZm9yIHRoZSBiZXN0IG1hdGNoIHNlbGVjdGVkIGJ5IHRoYXQgcHJvY2Vzcy4KCkFjY3VyYWN5IGZvciB0aGUgcmVtYWluZGVyIG9mIGRlY2lzaW9ucyBpcyB0aGUgQkVTVCBDQVNFIGFjY3VyYWN5IGZvciBhbnkgc3VnZ2VzdGlvbiBtYXRjaGluZyB0aGF0IHJ1bGUuIEZvciBleGFtcGxlLCBpZiB0aGUgZGVjaXNpb24gaXMgInBvaW50IiBnZW9tZXRyeSB0eXBlLCB0aGVuIGFjY3VyYWN5IHdvdWxkIGJlIHRoZSBzbWFsbGVzdCBvYnNlcnZlZCBkaXN0YW5jZSBvZiBldmVyeSBzaW5nbGUgc3VnZ2VzdGlvbiB3aXRoIGEgcG9pbnQgdHlwZS4KCgoKSGFuZCBSdWxlcwoKYGBge3J9CgpwcmludCgiSGFuZCBSdWxlIE9yZGVyaW5ncyIpCnRhYmxlKGdlb3JlZl9hbGxfZHQkaGFuZHJ1bGUpCgojSGFuZCBSdWxlCmRhdGEudGFibGU6OnNldGtleShnZW9yZWZfYWxsX2R0LCBldmVudF9oYXNoLCBoYW5kcnVsZSkgI1NvcnQgYnkgZXZlbnQgYW5kIHRoZW4gaGFuZCBydWxlCmdlb3JlZl9hbGxfZHRfaGFuZHJ1bGUgPC0gZ2VvcmVmX2FsbF9kdFssLlNEWzFdLCBieT1saXN0KGV2ZW50X2hhc2gpIF0gI1Rha2UgdGhlIHNtYWxsZXN0IGhhbmQgcnVsZSBzY29yZSwgbG93ZXIgaXMgYmV0dGVyCmdlb3JlZl9hbGxfZHRfaGFuZHJ1bGVfc3VtbWFyeSA8LSBnZW9yZWZfYWxsX2R0X2hhbmRydWxlWyxsaXN0KAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJ1bGU9IkhhbmQgUnVsZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXN0YW5jZV9ybXNlPXNxcnQobWVhbihkaXN0YW5jZV9rbV4yLCBuYS5ybT1UKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzdGFuY2VfbXNlPW1lYW4oZGlzdGFuY2Vfa21eMiwgbmEucm09VCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXN0YW5jZV9tZT1tZWFuKGRpc3RhbmNlX2ttLCBuYS5ybT1UKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3RhbmNlX21lZGlhbj1tZWRpYW4oZGlzdGFuY2Vfa20sIG5hLnJtPVQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc291cmNlX2RhdGFzZXRfY291bnQ9Lk4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF0KCnByaW50KCJEaXN0YW5jZSBmb3IgSGFuZCBSdWxlIikKZ2VvcmVmX2FsbF9kdF9oYW5kcnVsZV9zdW1tYXJ5CgoKYGBgCgoKRW5zZW1ibGUgUnVsZQoKYGBge3J9CgoKc3VtbWFyeShnZW9yZWZfYWxsX2R0JHJ1bGVfZW5zZW1ibGUpCgpnZW9yZWZfYWxsX2R0ICU+JSBnZ3Bsb3QoYWVzKHg9cnVsZV9lbnNlbWJsZSkpICsgZ2VvbV9oaXN0b2dyYW0oYmlucz0xMDApCiAgCiNFbnNlbWJsZSBSdWxlCmRhdGEudGFibGU6OnNldGtleShnZW9yZWZfYWxsX2R0LCBydWxlX2Vuc2VtYmxlKQpnZW9yZWZfYWxsX2R0X2Vuc2VtYmxlIDwtIGdlb3JlZl9hbGxfZHRbLC5TRFsxXSwgYnk9bGlzdChldmVudF9oYXNoKSBdICNUYWtlIHRoZSBzbWFsbGVzdCBlc3RpbWF0ZWQgZGlzdGFuY2UsIGxvd2VyIGlzIGJldHRlcgpnZW9yZWZfYWxsX2R0X2Vuc2VtYmxlX3N1bW1hcnkgPC0gZ2VvcmVmX2FsbF9kdF9lbnNlbWJsZVssbGlzdCgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBydWxlPSJFbnNlbWJsZSBSdWxlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3RhbmNlX3Jtc2U9c3FydChtZWFuKGRpc3RhbmNlX2ttXjIsIG5hLnJtPVQpKSAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXN0YW5jZV9tc2U9bWVhbihkaXN0YW5jZV9rbV4yLCBuYS5ybT1UKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3RhbmNlX21lPW1lYW4oZGlzdGFuY2Vfa20sIG5hLnJtPVQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzdGFuY2VfbWVkaWFuPW1lZGlhbihkaXN0YW5jZV9rbSwgbmEucm09VCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzb3VyY2VfZGF0YXNldF9jb3VudD0uTgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXQoKcHJpbnQoIkRpc3RhbmNlIGZvciBFbnNlbWJsZSIpCmdlb3JlZl9hbGxfZHRfZW5zZW1ibGVfc3VtbWFyeQoKcmJpbmQoZ2VvcmVmX2FsbF9kdF9oYW5kcnVsZSAlPiUgbXV0YXRlKGxhYmVsPSJoYW5kIiksCiAgICAgIGdlb3JlZl9hbGxfZHRfZW5zZW1ibGUgJT4lIG11dGF0ZShsYWJlbD0iZW5zZW1ibGUiKSkgJT4lIAogICAgICBnZ3Bsb3QoYWVzKHg9ZGlzdGFuY2Vfa20sIGZpbGw9bGFiZWwpKSArIGdlb21faGlzdG9ncmFtKCkKCgpgYGAKCiMgQ2FsY3VsYXRlIFJlY2FsbCBSYXRlIGFuZCBEaXN0YW5jZSBmb3IgRWFjaCBEZWNpc2lvbgoKTm93IGNhbGN1bGF0ZSB0aGUgbWluaW11bSBvYnNlcnZlZCBkaXN0YW5jZSBmb3IgYW55IHN1Z2dlc3Rpb24gbWF0Y2hpbmcgYSBzcGVjaWZpYyBkZWNpc2lvbi4KCmBgYHtyfQoKI01pbmltdW0gZGlzdGFuY2UgYnkgZXZlbnQtc291cmNlCmdlb3JlZl9hbGxfZHRfYnlzb3VyY2UgPC0gZ2VvcmVmX2FsbF9kdFssCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0KGRpc3RhbmNlX21pbj1taW4oZGlzdGFuY2Vfa20sIG5hLnJtPVQpICksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieT1saXN0KGV2ZW50X2hhc2gsIHNvdXJjZV9kYXRhc2V0KV0gOyBkaW0oZ2VvcmVmX2FsbF9kdF9ieXNvdXJjZSkKZ2VvcmVmX2FsbF9kdF9ieXNvdXJjZVshaXMuZmluaXRlKGRpc3RhbmNlX21pbiksIGRpc3RhbmNlX21pbjo9TkFdCgojTWluaW11bSBkaXN0YW5jZSBhbmQgcmVjYWxsIGJ5IHNvdXJjZQpnZW9yZWZfYWxsX2R0X2J5c291cmNlX3N1bW1hcnkgPC0gZ2VvcmVmX2FsbF9kdF9ieXNvdXJjZVssCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0KAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXN0YW5jZV9ybXNlPXNxcnQobWVhbihkaXN0YW5jZV9taW5eMiwgbmEucm09VCkpICwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc291cmNlX2RhdGFzZXRfY291bnQ9Lk4pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieT1saXN0KHNvdXJjZV9kYXRhc2V0KQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBdCgpnZW9yZWZfYWxsX2R0X2J5ZnV6enkgPC0gZ2VvcmVmX2FsbF9kdFsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsbGlzdChkaXN0YW5jZV9taW49bWluKGRpc3RhbmNlX2ttLCBuYS5ybT1UKSApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5PWxpc3QoZXZlbnRfaGFzaCwgZnV6enkpXSA7Cmdlb3JlZl9hbGxfZHRfYnlmdXp6eVshaXMuZmluaXRlKGRpc3RhbmNlX21pbiksIGRpc3RhbmNlX21pbjo9TkFdCgoKZ2VvcmVmX2FsbF9kdF9ieWZ1enp5X3N1bW1hcnkgPC0gZ2VvcmVmX2FsbF9kdF9ieWZ1enp5WywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QoZGlzdGFuY2Vfcm1zZT1zcXJ0KG1lYW4oZGlzdGFuY2VfbWluXjIsIG5hLnJtPVQpKSAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNvdXJjZV9kYXRhc2V0X2NvdW50PS5OKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnk9bGlzdChmdXp6eSkgXQoKZ2VvcmVmX2FsbF9kdF9ieXNlbGZyZWZlcmVuY2UgPC0gZ2VvcmVmX2FsbF9kdFssbGlzdChkaXN0YW5jZV9taW49bWluKGRpc3RhbmNlX2ttLCBuYS5ybT1UKSApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5PWxpc3QoZXZlbnRfaGFzaCwgU2VsZlJlZmVyZW5jZSldIDsgZGltKGdlb3JlZl9hbGxfZHRfYnlzb3VyY2UpCgpnZW9yZWZfYWxsX2R0X2J5c2VsZnJlZmVyZW5jZVshaXMuZmluaXRlKGRpc3RhbmNlX21pbiksIGRpc3RhbmNlX21pbjo9TkFdCmdlb3JlZl9hbGxfZHRfYnlzZWxmcmVmZXJlbmNlX3N1bW1hcnkgPC0gZ2VvcmVmX2FsbF9kdF9ieXNlbGZyZWZlcmVuY2VbLGxpc3QoCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3RhbmNlX3Jtc2U9c3FydChtZWFuKGRpc3RhbmNlX21pbl4yLCBuYS5ybT1UKSkgLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzb3VyY2VfZGF0YXNldF9jb3VudD0uTiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5PWxpc3QoU2VsZlJlZmVyZW5jZSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXQoKCmdlb3JlZl9hbGxfZHRfYnlnZW9tZXRyeV90eXBlIDwtIGdlb3JlZl9hbGxfZHRbLGxpc3QoZGlzdGFuY2VfbWluPW1pbihkaXN0YW5jZV9rbSwgbmEucm09VCkgKSxieT1saXN0KGV2ZW50X2hhc2gsIGdlb21ldHJ5X3R5cGUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApXSA7IGRpbShnZW9yZWZfYWxsX2R0X2J5c291cmNlKQpnZW9yZWZfYWxsX2R0X2J5Z2VvbWV0cnlfdHlwZVshaXMuZmluaXRlKGRpc3RhbmNlX21pbiksIGRpc3RhbmNlX21pbjo9TkFdCmdlb3JlZl9hbGxfZHRfYnlnZW9tZXRyeV90eXBlIDwtIGdlb3JlZl9hbGxfZHRfYnlnZW9tZXRyeV90eXBlWyxsaXN0KAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkaXN0YW5jZV9ybXNlPXNxcnQobWVhbihkaXN0YW5jZV9taW5eMiwgbmEucm09VCkpICwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc291cmNlX2RhdGFzZXRfY291bnQ9Lk4pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieT1saXN0KGdlb21ldHJ5X3R5cGUpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF0KCmdlb3JlZl9hbGxfZHRfbWlsY29vcmRzX3N1bW1hcnkgPC0gZGF0YS5mcmFtZShydWxlPSJNaWwuIENvb3JkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3RhbmNlX3Jtc2U9MCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNvdXJjZV9kYXRhc2V0X2NvdW50PXN1bSghaXMubmEoZXZlbnRzX3NmJG1hcF9jb29yZGluYXRlX2NsZWFuX2xhdGl0dWRlKSkKKQoKYGBgCgojIFN1bW1hcml6ZQoKCmBgYHtyfQoKcmVjYWxsX2FjY3VyYWN5IDwtIGRhdGEudGFibGU6OnJiaW5kbGlzdChsaXN0KAogICNnZW9yZWZfYWxsX2R0X21pbGNvb3Jkc19zdW1tYXJ5ICU+JSBtdXRhdGUobGFiZWw9Ik1pbC4gQ29vcmQiKSAlPiUgbXV0YXRlKFR5cGU9IlJ1bGUiKSwKICBnZW9yZWZfYWxsX2R0X2hhbmRydWxlX3N1bW1hcnkgJT4lIG11dGF0ZShsYWJlbD0iSGFuZCBSdWxlIikgJT4lIG11dGF0ZShUeXBlPSJSdWxlIiksCiAgZ2VvcmVmX2FsbF9kdF9lbnNlbWJsZV9zdW1tYXJ5ICU+JSBtdXRhdGUobGFiZWw9IkVuc2VtYmxlIFJ1bGUiKSAlPiUgbXV0YXRlKFR5cGU9IlJ1bGUiKSwKICBnZW9yZWZfYWxsX2R0X2J5c291cmNlX3N1bW1hcnkgJT4lIG11dGF0ZShsYWJlbD1zb3VyY2VfZGF0YXNldCkgJT4lIG11dGF0ZShUeXBlPSJTb3VyY2UgR2F6ZXR0ZWVyIiksCiAgZ2VvcmVmX2FsbF9kdF9ieWZ1enp5X3N1bW1hcnkgJT4lIG11dGF0ZShsYWJlbD1pZmVsc2UoZnV6enksICJGdXp6eSIsIkV4YWN0IikpICU+JSBtdXRhdGUoVHlwZT0iTWF0Y2ggVHlwZSIpLAogIGdlb3JlZl9hbGxfZHRfYnlzZWxmcmVmZXJlbmNlX3N1bW1hcnkgJT4lIG11dGF0ZShsYWJlbD1pZmVsc2UoU2VsZlJlZmVyZW5jZSwgIk1hdGNoIFRvIE90aGVyIEV2ZW50cyIsIk5vIE1hdGNoIFRvIE90aGVyIEV2ZW50cyIpKSAlPiUKICAgIG11dGF0ZShUeXBlPSJBbGxvdyBNYXRjaCBUbyBPdGhlciBFdmVudHMiKSwKICBnZW9yZWZfYWxsX2R0X2J5Z2VvbWV0cnlfdHlwZSAlPiUgbXV0YXRlKGxhYmVsPWdlb21ldHJ5X3R5cGUpICU+JSBtdXRhdGUoVHlwZT0iR2VvbWV0cnkgVHlwZSIpCiksZmlsbD1UKSAlPiUgbXV0YXRlKHJlY2FsbF9yYXRlPXNvdXJjZV9kYXRhc2V0X2NvdW50LzEwNDY5KQoKCmBgYAoKQ29tYmluZSBhbmQgcGxvdCB0aGUgcmVzdWx0cyBmb3IgZXZlcnkgc3BlY2lmaWMgZGVjaXNpb24gYW5kIHRoZSBlbnNlbWJsZXMuCgooRmlndXJlIDIsIENvbXBhcmlzb24gb2YgcmVjYWxsIGFuZCBhY2N1cmFjeSBieSBnZW9yZWZlcmVuY2luZyBtZXRob2QpCgpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTh9CgpzZW50ZW5jZV9jYXNlIDwtIGZ1bmN0aW9uKHgpIHN0cmluZ3I6OnN0cl90b19zZW50ZW5jZSh0b2xvd2VyKGdzdWIoIl8iLCIgIix4KSkpCgojVGhlIGZpcnN0IHRpbWUgeW91IHJ1biB0aGlzLCB5b3UnbGwgbmVlZCB0byBpbnN0YWxsIHRoZXNlIGFkZGl0aW9uYWwgZm9udHMKI2luc3RhbGwucGFja2FnZXMoImV4dHJhZm9udCIpOwojbGlicmFyeShleHRyYWZvbnQpCiNsaWJyYXJ5KGV4dHJhZm9udCkKI2V4dHJhZm9udDo6Zm9udF9pbXBvcnQocHJvbXB0PUYgKQojY2FwYWJpbGl0aWVzKCkKI3dpbmRvd3NGb250cygpCiNzb3J0KGFzLnZlY3Rvcih1bmxpc3Qod2luZG93c0ZvbnRzKCkpKSkKCmZvbnRzIDwtIGMoJ1RpbWVzIE5ldyBSb21hbicsCiAgICAgICAgICAgJ0NhbGlicmknLAogICAgICAgICAgICdDb3VyaWVyIE5ldycsCiAgICAgICAgICAgIkdlb3JnaWEiLAogICAgICAgICAgICJUdW5nYSIsCiAgICAgICAgICAgIkx1Y2lkYSBGYXgiKQogICAgICAgICAgICMnc2VyaWYnLCdIZWx2ZXRpY2EnLCdCb29rbWFuJywnUGFsYXRpbm8nKQoKI2xpYnJhcnkoZ2dwbG90MikKCgoKZm9udGZhY2VzIDwtIGZhY3RvcihjKCJwbGFpbiIsImJvbGQiLCJpdGFsaWMiLCJib2xkLml0YWxpYyIsInBsYWluIiwicGxhaW4iKSkKCmNvbG91cnMgPSBjKCJTZWxmIHJlZmVyZW5jZSIgPSAiI0Y4NzY2RCIsCiAgICAgICAgICAgICJHZW9tZXRyeSB0eXBlIiA9ICIjQTNBNTAwIiwKICAgICAgICAgICAgIk1hdGNoIHR5cGUiID0gIiMwMEJGN0QiLAogICAgICAgICAgICAiUnVsZSIgPSAiIzAwQjBGNiIsCiAgICAgICAgICAgICJTb3VyY2UgZGF0YXNldCI9IiNFNzZCRjMiLAogICAgICAgICAgICAiT3JpZ2luYWwgZ2VvIGluZm8iPSIjNTNCNDAwIikgCgojcF9sb2FkKGdncmVwZWwsIHRvb2xzKQpwX1JlY2FsbEJpYXMgPC0gcmVjYWxsX2FjY3VyYWN5ICU+JSAKICAgICAgICAgICAgICAgZmlsdGVyKCFsYWJlbCAlaW4lICJ0Z24iKSAlPiUgI2V4Y2x1ZGUgVEdOIGZvciBiZWluZyBhbiBvdXRsaWVyCiAgICAgICAgICAgICAgIG11dGF0ZSgKICAgICAgICAgICAgICAgICAgICAgIGxhYmVsPXNlbnRlbmNlX2Nhc2UobGFiZWwpLAogICAgICAgICAgICAgICAgICAgICAgVHlwZT1zZW50ZW5jZV9jYXNlKFR5cGUpCiAgICAgICAgICAgICAgICAgICAgICApICU+JQogICAgICAgZ2dwbG90KAogICAgICAgYWVzKHg9ZGlzdGFuY2Vfcm1zZSwKICAgICAgICAgICB5PXNvdXJjZV9kYXRhc2V0X2NvdW50IC8gMTA0NjksCiAgICAgICAgICAgICBjb2xvcj1UeXBlLAogICAgICAgICAgICAgbGFiZWw9bGFiZWwsCiAgICAgICAgICAgICBmYW1pbHkgPSBmb250c1thcy5udW1lcmljKGFzLmZhY3RvcihUeXBlKSldLAogICAgICAgICAgICAgZm9udGZhY2U9IGZvbnRmYWNlc1thcy5udW1lcmljKGFzLmZhY3RvcihUeXBlKSldCiAgICAgICAgICAgICApCikgICsgCiAgZ2dyZXBlbDo6Z2VvbV90ZXh0X3JlcGVsKHNpemU9MykgKwogIHRoZW1lX2J3KCkgKwogIHhsYWIoc3RyaW5ncjo6c3RyX3RvX3NlbnRlbmNlKCJSb290IE1lYW4gU3F1YXJlZCBEaXN0YW5jZSBmcm9tIE9ic2VydmVkIENvb3JkaW5hdGUgKGttKSIpKSArCiAgeWxhYihzdHJpbmdyOjpzdHJfdG9fc2VudGVuY2UoIlJlY292ZXJ5IFJhdGUiKSkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gYygwLjIsIDAuMyksICMgYygwLDApIGJvdHRvbSBsZWZ0LCBjKDEsMSkgdG9wLXJpZ2h0LgogICkKcF9SZWNhbGxCaWFzCiAjKyBjb29yZF9jYXJ0ZXNpYW4oeT0ibG9nIikKCmdnc2F2ZSgKICBmaWxlbmFtZSA9IGdsdWU6OmdsdWUoZGlyX2ZpZ3VyZXMsICJSZWNhbGxCaWFzLnBkZiIpLAogIHBsb3QgPSBwX1JlY2FsbEJpYXMsIHdpZHRoID0gOSwgaGVpZ2h0ID0gNiwKICAgIGRldmljZSA9IGNhaXJvX3BkZiAjaGF2ZSB0byB1c2UgY2Fpcm8gdG8gY29ycmVjdGx5IGVtYmVkIHRoZSBmb250cwoKKQoKCmBgYAoKIyBJbnRlcnByZXRhdGlvbgoKSW4gdGVybXMgb2YgYWNjdXJhY3ksIHdlIGZpbmQgd2lkZSB2YXJpYXRpb24gYmV0d2VlbiBkZWNpc2lvbnMuIEluIGJyaWVmOgpFbnNlbWJsZSBSdWxlID4gSGFuZCBSdWxlCkV4YWN0ID4gRnV6enkKUG9pbnQgPiBNdWx0aXBvbHlnb24gPiBQb2x5Z29uID4gTGluZXN0cmluZwpNYXRjaCB0byBPdGhlciBFdmVudHMgPiBObyBNYXRjaCB0byBPdGhlciBFdmVudHMKZXZlbnRzID4gbmdhID4gbGl2ZXN0b2NrX3BvaW50cyIgPiBoaXN0b3JpY2FsID4gZ2VvbmFtZXMgPiBiaW5nPiBrZW55YV9jYWRhc3RyYWxfZGlzdHJpY3QgPiBsaXZlc3RvY2tfYm91bmRhcmllcyA+IGdhZG0gPiBrZW55YV9kaXN0cmljdDE5NjIgPiB3aWtpZGF0YSA+IGdvb2dsZSA+IGtlbnlhX2NhZGFzdHJhbCA+IG9wZW5zdHJlZXRtYXAgPiB0Z24gCgpUaGUgaGFuZCBydWxlcyBhbmQgc3VwZXJ2aXNlZCBlbnNlbWJsZSByZXByZXNlbnQgYSBjb21wcm9taXNlIGJldHdlZW4gaGlnaCByZWNvdmVyeSByYXRlIGFuZCBiZXN0IGNhc2UgYWNjdXJhY3kgb2YgZWFjaCBpbmRpdmlkdWFsIGRlY2lzaW9uLg==