Demonstrate that different georeferencing decisions will produce different results in a simple linear regression model in terms of both statistical significance and substantive effects.

rm(list=ls()); gc()
# !diagnostics off
library(MeasuringLandscape)
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)

Create Hexagon Unit of Analysis

covariate_list <-  MeasuringLandscape:::prep_covariates()
region_of_interest_sf <- MeasuringLandscape:::create_roi(bottom_left_x=35.67,
                                        bottom_left_y=-1.43285,
                                        top_right_x=38.19,
                                        top_right_y=0.54543,
                                        crs_out=4326)
hex <- MeasuringLandscape:::create_hexagon_df(cellsize_km=10,
                            region_of_interest=region_of_interest_sf,
                            type="hexagonal",
                            crs=4326 )
[1] 10

Measure covariates at each hexagon

#hex$district <- new_over(st_centroid(hex) , covariate_list[[1]]  , 'name' )
#hex$cadastral <- new_over(st_centroid(hex) , covariate_list[[2]]  , 'name' )
#hex$language <- new_over(st_centroid(hex) , covariate_list[[3]]  , 'LANGUAGE' )
#hex$tribe <- new_over(st_centroid(hex) , covariate_list[[4]]  , 'Tribe' )
hex$rain <- log( MeasuringLandscape:::new_over(st_centroid(hex) , covariate_list[['raster_rain']]  , 'Tribe' ) + 1)
no non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infst_centroid assumes attributes are constant over geometries of xst_centroid does not give correct centroids for longitude/latitude data
hex$population <- log(  MeasuringLandscape:::new_over(st_centroid(hex) , covariate_list[['pop_raster_roi']]  , '' ) + 1)
no non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infst_centroid assumes attributes are constant over geometries of xst_centroid does not give correct centroids for longitude/latitude data
hex$treecover <-log(  MeasuringLandscape:::new_over(st_centroid(hex) , covariate_list[['forest_raster_roi']]  , '' ) + 1)
no non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infst_centroid assumes attributes are constant over geometries of xst_centroid does not give correct centroids for longitude/latitude data
hex$ruggedness <- log(  MeasuringLandscape:::new_over(st_centroid(hex) , covariate_list[['ruggedness_raster_roi']]  , '' ) + 1)
no non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infst_centroid assumes attributes are constant over geometries of xst_centroid does not give correct centroids for longitude/latitude data
hex$roads_distance <-log(  MeasuringLandscape:::new_over(st_centroid(hex) , covariate_list[['roads_distance_to']]  , '' ) + 1)
no non-missing arguments to min; returning Infno non-missing arguments to max; returning -Infst_centroid assumes attributes are constant over geometries of xst_centroid does not give correct centroids for longitude/latitude data
#hex$landuse <- new_over(st_centroid(hex) , covariate_list[[9]]  , 'LANDUSE' )

Tally number of events in each hexagon according to each georeferencing rule and fit a linear model

#Reload from scratch each time in case we subset sometehing weirdly
georef_all_dt <- readRDS(system.file("extdata", "georef_all_dt_recomendations.Rds", package = "MeasuringLandscape")) 
georef_all_dt[!is.finite(distance_km),distance_km:=NA] 
georef_all_dt <- subset(georef_all_dt,  distance_km=!0) #This excludes all self references , we need that here but not necessarily elsewhere
georef_all_dt_events_sf <- georef_all_dt  %>% 
                         filter(!is.na(X1) & !is.na(Y2)) %>% #can drop anything without a coordinate because you'll never be able to aggregate those
                         data.frame() %>% st_as_sf(coords = c("X2","Y2"),  crs = 4326, agr = "constant", remove=F, na.fail =F)
temp <- georef_all_dt_events_sf %>% filter(fuzzy) %>% filter(!duplicated(event_hash))
hex$dv <- log( sapply( st_covers(hex, temp), sum, na.rm=T) + 1)
although coordinates are longitude/latitude, st_covers assumes that they are planar
vars_all <- c("dv","rain","population","treecover","ruggedness","roads_distance") #,"landuse"
#lapply(hex[,vars_all], hist)
lm1 <- lm(dv ~ ., data=as.data.frame(hex)[,vars_all])
temp <- georef_all_dt_events_sf %>% filter(!duplicated(event_hash))
hex$dv <-  sapply( st_covers(hex, temp), length) 
although coordinates are longitude/latitude, st_covers assumes that they are planar
broom::tidy(lm1)

Fit Linear Models using Each Possible Georeferencing Decision

For each possible georeferencing decision, we fit a seperate linear model predicting the number of events in an area as a function of five covariates, population, rainfall, distance from roads, ruggedness, and treecover. The unit of obsevration is a hexagonal bin of size 10km. The model is a negative binomial regression.

#Hand Rule
lm_results_list0 <- list()
data.table::setkey(georef_all_dt, rule_ensemble)
georef_all_dt_ensemble <- georef_all_dt[,.SD[1], by=list(event_hash) ]
condition <- paste(georef_all_dt_events_sf$event_hash, georef_all_dt_events_sf$place_hash) %in% 
             paste(georef_all_dt_ensemble$event_hash, georef_all_dt_ensemble$place_hash) #table(condition)
temp <- georef_all_dt_events_sf[condition,] %>% filter(!duplicated(event_hash))
hex$dv <- sapply( st_covers(hex, temp), length) 
although coordinates are longitude/latitude, st_covers assumes that they are planar
lm1 <- MASS::glm.nb(dv ~ ., data= na.omit(as.data.frame(hex)[,vars_all]) )
temp_results <- broom::tidy(lm1)
temp_results$Type="Rule"
temp_results$label <- "Hand Rule"
lm_results_list0[[as.character("Hand Rule")]] <- temp_results
#Ensemble Rule
data.table::setkey(georef_all_dt, rule_ensemble)
georef_all_dt_ensemble <- georef_all_dt[,.SD[1], by=list(event_hash) ]
condition <- paste(georef_all_dt_events_sf$event_hash, georef_all_dt_events_sf$place_hash) %in% 
             paste(georef_all_dt_ensemble$event_hash, georef_all_dt_ensemble$place_hash) #table(condition)
temp <- georef_all_dt_events_sf[condition,] %>% filter(!duplicated(event_hash))
hex$dv <- sapply( st_covers(hex, temp), length) 
although coordinates are longitude/latitude, st_covers assumes that they are planar
lm1 <- MASS::glm.nb(dv ~ ., data= na.omit(as.data.frame(hex)[,vars_all]) )
temp_results <- broom::tidy(lm1)
temp_results$Type="Rule"
temp_results$label <- "Ensemble Rule"
lm_results_list0[[as.character("Ensemble Rule")]] <- temp_results
  
#Source Dataset
lm_results_list <- list()
for(q in unique(georef_all_dt_events_sf$source_dataset)){
    print(q)
  try({ #TGN fails
  condition <- georef_all_dt_events_sf$source_dataset==q
  temp <- georef_all_dt_events_sf[condition,] %>% filter(!duplicated(event_hash))
  hex$dv <- sapply( st_covers(hex, temp), length) 
  lm1 <- MASS::glm.nb(dv ~ .,
                data= na.omit(as.data.frame(hex)[,vars_all]) 
                )
  temp_results <- broom::tidy(lm1)
  temp_results$Type="Source Dataset"
  temp_results$label <- q
  lm_results_list[[as.character(q)]] <- temp_results
  })
}
[1] "events"
although coordinates are longitude/latitude, st_covers assumes that they are planar
[1] "historical"
although coordinates are longitude/latitude, st_covers assumes that they are planar
[1] "nga"
although coordinates are longitude/latitude, st_covers assumes that they are planar
[1] "geonames"
although coordinates are longitude/latitude, st_covers assumes that they are planar
[1] "livestock_points"
although coordinates are longitude/latitude, st_covers assumes that they are planar
[1] "bing"
although coordinates are longitude/latitude, st_covers assumes that they are planar
[1] "wikidata"
although coordinates are longitude/latitude, st_covers assumes that they are planar
glm.fit: algorithm did not convergealternation limit reached
[1] "tgn"
although coordinates are longitude/latitude, st_covers assumes that they are planar
step size truncated due to divergenceError in glm.fitter(x = X, y = Y, w = w, etastart = eta, offset = offset,  : 
  NA/NaN/Inf in 'x'
[1] "google"
although coordinates are longitude/latitude, st_covers assumes that they are planar
glm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not convergealternation limit reached
[1] "openstreetmap"
although coordinates are longitude/latitude, st_covers assumes that they are planar
step size truncated due to divergenceError in glm.fitter(x = X, y = Y, w = w, etastart = eta, offset = offset,  : 
  NA/NaN/Inf in 'x'
[1] "gadm"
although coordinates are longitude/latitude, st_covers assumes that they are planar
[1] "kenya_cadastral_district"
although coordinates are longitude/latitude, st_covers assumes that they are planar
glm.fit: algorithm did not convergeglm.fit: algorithm did not convergeglm.fit: algorithm did not converge
[1] "kenya_district1962"
although coordinates are longitude/latitude, st_covers assumes that they are planar
glm.fit: algorithm did not convergeglm.fit: algorithm did not convergeiteration limit reachedNaNs producediteration limit reachedNaNs producedglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeiteration limit reachedNaNs producediteration limit reachedNaNs producedglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeiteration limit reachedNaNs producediteration limit reachedNaNs producedglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeiteration limit reachedNaNs producediteration limit reachedNaNs producedglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeiteration limit reachedNaNs producediteration limit reachedNaNs producedglm.fit: algorithm did not convergeglm.fit: algorithm did not convergeiteration limit reachedNaNs producediteration limit reachedNaNs producedglm.fit: algorithm did not convergealternation limit reached
[1] "livestock_boundaries"
although coordinates are longitude/latitude, st_covers assumes that they are planar
[1] "kenya_cadastral"
although coordinates are longitude/latitude, st_covers assumes that they are planar
Error in glm.fitter(x = X, y = Y, w = w, etastart = eta, offset = offset,  : 
  NA/NaN/Inf in 'x'
#Geometry Type
#Half the time, linestring doesn't converge. Exclude from the plots downstream anyway
lm_results_list2 <- list()
for(q in unique(georef_all_dt_events_sf$geometry_type)){
  print(q)
  try({
    condition <- georef_all_dt_events_sf$geometry_type==q
    temp <- georef_all_dt_events_sf[condition,] %>% filter(!duplicated(event_hash))
    hex$dv <- sapply( st_covers(hex, temp), length) 
    lm1 <- MASS::glm.nb(dv ~ ., data= na.omit(as.data.frame(hex)[,vars_all]) ,
                  control=glm.control(maxit=100) #Up the number of iterations so it converges
                  ) 
    temp_results <- broom::tidy(lm1)
    temp_results$Type="Geometry Type"
    temp_results$label <- q
    lm_results_list2[[as.character(q)]] <- temp_results
  })
}
[1] "POINT"
although coordinates are longitude/latitude, st_covers assumes that they are planar
[1] "MULTIPOLYGON"
although coordinates are longitude/latitude, st_covers assumes that they are planar
[1] "POLYGON"
although coordinates are longitude/latitude, st_covers assumes that they are planar
[1] "LINESTRING"
although coordinates are longitude/latitude, st_covers assumes that they are planar
step size truncated due to divergenceError in glm.fitter(x = X, y = Y, w = w, etastart = eta, offset = offset,  : 
  NA/NaN/Inf in 'x'
#Fuzzy
lm_results_list3 <- list()
for(q in unique(georef_all_dt_events_sf$fuzzy)){
  print(q)
  condition <- georef_all_dt_events_sf$fuzzy==q
  temp <- georef_all_dt_events_sf[condition,] %>% filter(!duplicated(event_hash))
  hex$dv <- sapply( st_covers(hex, temp), length) 
  lm1 <- MASS::glm.nb(dv ~ ., data= na.omit(as.data.frame(hex)[,vars_all]) )
  temp_results <- broom::tidy(lm1)
  temp_results$Type="Match Type"
  temp_results$label <- q
  lm_results_list3[[as.character(q)]] <- temp_results
}
[1] FALSE
although coordinates are longitude/latitude, st_covers assumes that they are planar
[1] TRUE
although coordinates are longitude/latitude, st_covers assumes that they are planar
#Self Reference
lm_results_list4 <- list()
for(q in unique(georef_all_dt_events_sf$SelfReference)){
  print(q)
  condition <- georef_all_dt_events_sf$SelfReference==q
  temp <- georef_all_dt_events_sf[condition,] %>% filter(!duplicated(event_hash))
  hex$dv <- sapply( st_covers(hex, temp), length) 
  lm1 <- MASS::glm.nb(dv ~ ., data= na.omit(as.data.frame(hex)[,vars_all]) )
  temp_results <- broom::tidy(lm1)
  temp_results$Type="SelfReference"
  temp_results$label <- q
  lm_results_list4[[as.character(q)]] <- temp_results
}
[1] TRUE
although coordinates are longitude/latitude, st_covers assumes that they are planar
[1] FALSE
although coordinates are longitude/latitude, st_covers assumes that they are planar
glm.fit: algorithm did not convergealternation limit reached

Plot the coefficients for each model

(Figure 5)

lm_results_dt <- data.table::rbindlist( list(  data.table::rbindlist(lm_results_list0),
                                   data.table::rbindlist(lm_results_list),
                                   data.table::rbindlist(lm_results_list2),
                                   data.table::rbindlist(lm_results_list3),
                                   data.table::rbindlist(lm_results_list4)
)
                           )
lm_results_dt <- lm_results_dt %>% mutate(rate= round(exp(estimate),2) )
sentence_case <- function(x) stringr::str_to_sentence(tolower(gsub("_"," ",x)))
#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")
           #'serif','Helvetica','Bookman','Palatino')
#qplot(1:10)+theme(text=element_text(family="Gill Sans Ultra Bold"))
#qplot(1:10)+theme(text=element_text(family=fonts[1]))
#qplot(1:10)+theme(text=element_text(family=fonts[2]))
#qplot(1:10)+theme(text=element_text(family=fonts[3]))
#qplot(1:10)+theme(text=element_text(family=fonts[4]))
#qplot(1:10)+theme(text=element_text(family=fonts[5]))
lm_results_dt <- data.table::as.data.table(lm_results_dt)
lm_results_dt[Type=="Match Type" & label=="FALSE", label:= "Exact",]
lm_results_dt[Type=="Match Type" & label=="TRUE", label:= "Fuzzy",]
lm_results_dt[Type=="SelfReference" & label=="FALSE", label:= "Self Ref.",]
lm_results_dt[Type=="SelfReference" & label=="TRUE", label:= "No Self Ref.",]
lm_results_dt[term=="treecover" , term:="tree cover",]
fontfaces <- factor(c("plain","bold","italic","bold.italic","plain"))
#p_load(ggrepel, tools)
p_lm <- lm_results_dt %>% 
        filter(!(label %in% c('kenya_district1962','kenya_cadastral','kenya_cadastral_district',"LINESTRING"))) %>%
        filter(term != "(Intercept)") %>%
        #mutate(label[Type=="Match Type"]=gsub("FALSE", "Exact", label[Type=="Match Type"])) %>%
        #mutate(label[Type=="Match Type"]=gsub("True", "Fuzzy", label[Type=="Match Type"])) %>%
        
        mutate(Type=sentence_case(Type),
               term=sentence_case(term),
               label=sentence_case(label)) %>%
  ggplot(aes(x=rate,
             y=p.value,
             color=Type,
             label=label,
             family = fonts[as.numeric(as.factor(Type))],
             fontface= fontfaces[as.numeric(as.factor(Type))]
             ))  + 
  facet_wrap(~term, scales="free") + 
  theme_bw() +
  geom_vline(xintercept = 1, , color="grey") + 
  geom_hline(yintercept = 0.01, linetype=3, color="grey") + 
  ggrepel::geom_text_repel(size=2) + 
  xlab(sentence_case("Coefficient Estimates (Change in relative rate of Violent Events)")) +
  ylab(sentence_case("Coefficient P-Value Estimates (0.01 Threshold Indicated)")) +
  theme(
    legend.position = c(0.9, 0.1), # c(0,0) bottom left, c(1,1) top-right.
  )
p_lm

 #+ coord_cartesian(y="log")
ggsave(
  filename = glue::glue(dir_figures, "p_lm.pdf"),
  plot = p_lm, width = 9, height = 6,
  device = cairo_pdf #have to use cairo to correctly embed the fonts
)

Interpretation

We evaluated five coefficients in a typical linear model predicting the number of violent events to occur in geographic area. The only thing we varied from model to model was the method by which we georeferenced the underlying events. We varied them across four main decisions, fuzzy/exact matching, geometry type, whether to allow self reference, and the source gazeteer. We further included two proposed cominations of decisions, one based on fixed hand tailored rules of when to prefer on decision to another, and another based on a supervsied learned model whose rules can vary based on specific context.

We found variation in our coefficient estimates across the different geographic decisions. The coefficeints varried in sign, magnitude, and statistical significance. We found variation in the stability of coefficients by each covariate. Both population and rainfall had stable estimates, with consistent sign, significance, and substantive effects with a few outliars. Distance, ruggedness, and treecover had unstable coefficients, with variation in significance and occasionaly the direction of the effect. In a few outliars, we observed a statistically significant effect in the opposite direction.

LS0tCnRpdGxlOiAiMTEgU28gV2hhdCIKYXV0aG9yOiAiUmV4IFcuIERvdWdsYXNzIGFuZCBLcmlzdGVuIEhhcmtuZXNzIgpkYXRlOiAiTWFyY2ggOSwgMjAxOCIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCjxzdHlsZT4KICAgIGJvZHkgLm1haW4tY29udGFpbmVyIHsKICAgICAgICBtYXgtd2lkdGg6IDEwMCU7CiAgICB9Cjwvc3R5bGU+CgogIApEZW1vbnN0cmF0ZSB0aGF0IGRpZmZlcmVudCBnZW9yZWZlcmVuY2luZyBkZWNpc2lvbnMgd2lsbCBwcm9kdWNlIGRpZmZlcmVudCByZXN1bHRzIGluIGEgc2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIGluIHRlcm1zIG9mIGJvdGggc3RhdGlzdGljYWwgc2lnbmlmaWNhbmNlIGFuZCBzdWJzdGFudGl2ZSBlZmZlY3RzLgoKYGBge3IgLCByZXN1bHRzPSdoaWRlJywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KCnJtKGxpc3Q9bHMoKSk7IGdjKCkKIyAhZGlhZ25vc3RpY3Mgb2ZmCmxpYnJhcnkoTWVhc3VyaW5nTGFuZHNjYXBlKQoKZGlyX2ZpZ3VyZXMgPC0gZ2x1ZTo6Z2x1ZShnZXR3ZCgpLCAiLy4uL3BhcGVyL2ZpZ3VyZXMvIikKCmdjKCkKCmtuaXRyOjpvcHRzX2tuaXQkc2V0KHByb2dyZXNzID0gVFJVRSwgdmVyYm9zZSA9IFRSVUUpCmtuaXRyOjpvcHRzX2NodW5rJHNldChmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9OCwgIHdhcm5pbmc9RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPVRSVUUpCm9wdGlvbnMod2lkdGggPSAxNjApCgpgYGAKCiMgQ3JlYXRlIEhleGFnb24gVW5pdCBvZiBBbmFseXNpcwoKYGBge3J9Cgpjb3ZhcmlhdGVfbGlzdCA8LSAgTWVhc3VyaW5nTGFuZHNjYXBlOjo6cHJlcF9jb3ZhcmlhdGVzKCkKCnJlZ2lvbl9vZl9pbnRlcmVzdF9zZiA8LSBNZWFzdXJpbmdMYW5kc2NhcGU6OjpjcmVhdGVfcm9pKGJvdHRvbV9sZWZ0X3g9MzUuNjcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3R0b21fbGVmdF95PS0xLjQzMjg1LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdG9wX3JpZ2h0X3g9MzguMTksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0b3BfcmlnaHRfeT0wLjU0NTQzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY3JzX291dD00MzI2KQoKaGV4IDwtIE1lYXN1cmluZ0xhbmRzY2FwZTo6OmNyZWF0ZV9oZXhhZ29uX2RmKGNlbGxzaXplX2ttPTEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnaW9uX29mX2ludGVyZXN0PXJlZ2lvbl9vZl9pbnRlcmVzdF9zZiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGU9ImhleGFnb25hbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjcnM9NDMyNiApCmBgYAoKIyBNZWFzdXJlIGNvdmFyaWF0ZXMgYXQgZWFjaCBoZXhhZ29uCgpgYGB7cn0KCiNoZXgkZGlzdHJpY3QgPC0gbmV3X292ZXIoc3RfY2VudHJvaWQoaGV4KSAsIGNvdmFyaWF0ZV9saXN0W1sxXV0gICwgJ25hbWUnICkKI2hleCRjYWRhc3RyYWwgPC0gbmV3X292ZXIoc3RfY2VudHJvaWQoaGV4KSAsIGNvdmFyaWF0ZV9saXN0W1syXV0gICwgJ25hbWUnICkKI2hleCRsYW5ndWFnZSA8LSBuZXdfb3ZlcihzdF9jZW50cm9pZChoZXgpICwgY292YXJpYXRlX2xpc3RbWzNdXSAgLCAnTEFOR1VBR0UnICkKI2hleCR0cmliZSA8LSBuZXdfb3ZlcihzdF9jZW50cm9pZChoZXgpICwgY292YXJpYXRlX2xpc3RbWzRdXSAgLCAnVHJpYmUnICkKaGV4JHJhaW4gPC0gbG9nKCBNZWFzdXJpbmdMYW5kc2NhcGU6OjpuZXdfb3ZlcihzdF9jZW50cm9pZChoZXgpICwgY292YXJpYXRlX2xpc3RbWydyYXN0ZXJfcmFpbiddXSAgLCAnVHJpYmUnICkgKyAxKQpoZXgkcG9wdWxhdGlvbiA8LSBsb2coICBNZWFzdXJpbmdMYW5kc2NhcGU6OjpuZXdfb3ZlcihzdF9jZW50cm9pZChoZXgpICwgY292YXJpYXRlX2xpc3RbWydwb3BfcmFzdGVyX3JvaSddXSAgLCAnJyApICsgMSkKaGV4JHRyZWVjb3ZlciA8LWxvZyggIE1lYXN1cmluZ0xhbmRzY2FwZTo6Om5ld19vdmVyKHN0X2NlbnRyb2lkKGhleCkgLCBjb3ZhcmlhdGVfbGlzdFtbJ2ZvcmVzdF9yYXN0ZXJfcm9pJ11dICAsICcnICkgKyAxKQpoZXgkcnVnZ2VkbmVzcyA8LSBsb2coICBNZWFzdXJpbmdMYW5kc2NhcGU6OjpuZXdfb3ZlcihzdF9jZW50cm9pZChoZXgpICwgY292YXJpYXRlX2xpc3RbWydydWdnZWRuZXNzX3Jhc3Rlcl9yb2knXV0gICwgJycgKSArIDEpCmhleCRyb2Fkc19kaXN0YW5jZSA8LWxvZyggIE1lYXN1cmluZ0xhbmRzY2FwZTo6Om5ld19vdmVyKHN0X2NlbnRyb2lkKGhleCkgLCBjb3ZhcmlhdGVfbGlzdFtbJ3JvYWRzX2Rpc3RhbmNlX3RvJ11dICAsICcnICkgKyAxKQojaGV4JGxhbmR1c2UgPC0gbmV3X292ZXIoc3RfY2VudHJvaWQoaGV4KSAsIGNvdmFyaWF0ZV9saXN0W1s5XV0gICwgJ0xBTkRVU0UnICkKCmBgYAoKIyBUYWxseSBudW1iZXIgb2YgZXZlbnRzIGluIGVhY2ggaGV4YWdvbiBhY2NvcmRpbmcgdG8gZWFjaCBnZW9yZWZlcmVuY2luZyBydWxlIGFuZCBmaXQgYSBsaW5lYXIgbW9kZWwKCmBgYHtyfQojUmVsb2FkIGZyb20gc2NyYXRjaCBlYWNoIHRpbWUgaW4gY2FzZSB3ZSBzdWJzZXQgc29tZXRlaGluZyB3ZWlyZGx5Cmdlb3JlZl9hbGxfZHQgPC0gcmVhZFJEUyhzeXN0ZW0uZmlsZSgiZXh0ZGF0YSIsICJnZW9yZWZfYWxsX2R0X3JlY29tZW5kYXRpb25zLlJkcyIsIHBhY2thZ2UgPSAiTWVhc3VyaW5nTGFuZHNjYXBlIikpIApnZW9yZWZfYWxsX2R0WyFpcy5maW5pdGUoZGlzdGFuY2Vfa20pLGRpc3RhbmNlX2ttOj1OQV0gCmdlb3JlZl9hbGxfZHQgPC0gc3Vic2V0KGdlb3JlZl9hbGxfZHQsICBkaXN0YW5jZV9rbT0hMCkgI1RoaXMgZXhjbHVkZXMgYWxsIHNlbGYgcmVmZXJlbmNlcyAsIHdlIG5lZWQgdGhhdCBoZXJlIGJ1dCBub3QgbmVjZXNzYXJpbHkgZWxzZXdoZXJlCmdlb3JlZl9hbGxfZHRfZXZlbnRzX3NmIDwtIGdlb3JlZl9hbGxfZHQgICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcighaXMubmEoWDEpICYgIWlzLm5hKFkyKSkgJT4lICNjYW4gZHJvcCBhbnl0aGluZyB3aXRob3V0IGEgY29vcmRpbmF0ZSBiZWNhdXNlIHlvdSdsbCBuZXZlciBiZSBhYmxlIHRvIGFnZ3JlZ2F0ZSB0aG9zZQogICAgICAgICAgICAgICAgICAgICAgICAgZGF0YS5mcmFtZSgpICU+JSBzdF9hc19zZihjb29yZHMgPSBjKCJYMiIsIlkyIiksICBjcnMgPSA0MzI2LCBhZ3IgPSAiY29uc3RhbnQiLCByZW1vdmU9RiwgbmEuZmFpbCA9RikKCnRlbXAgPC0gZ2VvcmVmX2FsbF9kdF9ldmVudHNfc2YgJT4lIGZpbHRlcihmdXp6eSkgJT4lIGZpbHRlcighZHVwbGljYXRlZChldmVudF9oYXNoKSkKaGV4JGR2IDwtIGxvZyggc2FwcGx5KCBzdF9jb3ZlcnMoaGV4LCB0ZW1wKSwgc3VtLCBuYS5ybT1UKSArIDEpCgp2YXJzX2FsbCA8LSBjKCJkdiIsInJhaW4iLCJwb3B1bGF0aW9uIiwidHJlZWNvdmVyIiwicnVnZ2VkbmVzcyIsInJvYWRzX2Rpc3RhbmNlIikgIywibGFuZHVzZSIKI2xhcHBseShoZXhbLHZhcnNfYWxsXSwgaGlzdCkKbG0xIDwtIGxtKGR2IH4gLiwgZGF0YT1hcy5kYXRhLmZyYW1lKGhleClbLHZhcnNfYWxsXSkKCgp0ZW1wIDwtIGdlb3JlZl9hbGxfZHRfZXZlbnRzX3NmICU+JSBmaWx0ZXIoIWR1cGxpY2F0ZWQoZXZlbnRfaGFzaCkpCmhleCRkdiA8LSAgc2FwcGx5KCBzdF9jb3ZlcnMoaGV4LCB0ZW1wKSwgbGVuZ3RoKSAKYnJvb206OnRpZHkobG0xKQoKYGBgCgojIEZpdCBMaW5lYXIgTW9kZWxzIHVzaW5nIEVhY2ggUG9zc2libGUgR2VvcmVmZXJlbmNpbmcgRGVjaXNpb24KCkZvciBlYWNoIHBvc3NpYmxlIGdlb3JlZmVyZW5jaW5nIGRlY2lzaW9uLCB3ZSBmaXQgYSBzZXBlcmF0ZSBsaW5lYXIgbW9kZWwgcHJlZGljdGluZyB0aGUgbnVtYmVyIG9mIGV2ZW50cyBpbiBhbiBhcmVhIGFzIGEgZnVuY3Rpb24gb2YgZml2ZSBjb3ZhcmlhdGVzLCBwb3B1bGF0aW9uLCByYWluZmFsbCwgZGlzdGFuY2UgZnJvbSByb2FkcywgcnVnZ2VkbmVzcywgYW5kIHRyZWVjb3Zlci4gVGhlIHVuaXQgb2Ygb2JzZXZyYXRpb24gaXMgYSBoZXhhZ29uYWwgYmluIG9mIHNpemUgMTBrbS4gVGhlIG1vZGVsIGlzIGEgbmVnYXRpdmUgYmlub21pYWwgcmVncmVzc2lvbi4KCmBgYHtyfQoKCiNIYW5kIFJ1bGUKbG1fcmVzdWx0c19saXN0MCA8LSBsaXN0KCkKZGF0YS50YWJsZTo6c2V0a2V5KGdlb3JlZl9hbGxfZHQsIHJ1bGVfZW5zZW1ibGUpCmdlb3JlZl9hbGxfZHRfZW5zZW1ibGUgPC0gZ2VvcmVmX2FsbF9kdFssLlNEWzFdLCBieT1saXN0KGV2ZW50X2hhc2gpIF0KCmNvbmRpdGlvbiA8LSBwYXN0ZShnZW9yZWZfYWxsX2R0X2V2ZW50c19zZiRldmVudF9oYXNoLCBnZW9yZWZfYWxsX2R0X2V2ZW50c19zZiRwbGFjZV9oYXNoKSAlaW4lIAogICAgICAgICAgICAgcGFzdGUoZ2VvcmVmX2FsbF9kdF9lbnNlbWJsZSRldmVudF9oYXNoLCBnZW9yZWZfYWxsX2R0X2Vuc2VtYmxlJHBsYWNlX2hhc2gpICN0YWJsZShjb25kaXRpb24pCnRlbXAgPC0gZ2VvcmVmX2FsbF9kdF9ldmVudHNfc2ZbY29uZGl0aW9uLF0gJT4lIGZpbHRlcighZHVwbGljYXRlZChldmVudF9oYXNoKSkKaGV4JGR2IDwtIHNhcHBseSggc3RfY292ZXJzKGhleCwgdGVtcCksIGxlbmd0aCkgCmxtMSA8LSBNQVNTOjpnbG0ubmIoZHYgfiAuLCBkYXRhPSBuYS5vbWl0KGFzLmRhdGEuZnJhbWUoaGV4KVssdmFyc19hbGxdKSApCnRlbXBfcmVzdWx0cyA8LSBicm9vbTo6dGlkeShsbTEpCnRlbXBfcmVzdWx0cyRUeXBlPSJSdWxlIgp0ZW1wX3Jlc3VsdHMkbGFiZWwgPC0gIkhhbmQgUnVsZSIKbG1fcmVzdWx0c19saXN0MFtbYXMuY2hhcmFjdGVyKCJIYW5kIFJ1bGUiKV1dIDwtIHRlbXBfcmVzdWx0cwoKI0Vuc2VtYmxlIFJ1bGUKZGF0YS50YWJsZTo6c2V0a2V5KGdlb3JlZl9hbGxfZHQsIHJ1bGVfZW5zZW1ibGUpCmdlb3JlZl9hbGxfZHRfZW5zZW1ibGUgPC0gZ2VvcmVmX2FsbF9kdFssLlNEWzFdLCBieT1saXN0KGV2ZW50X2hhc2gpIF0KCmNvbmRpdGlvbiA8LSBwYXN0ZShnZW9yZWZfYWxsX2R0X2V2ZW50c19zZiRldmVudF9oYXNoLCBnZW9yZWZfYWxsX2R0X2V2ZW50c19zZiRwbGFjZV9oYXNoKSAlaW4lIAogICAgICAgICAgICAgcGFzdGUoZ2VvcmVmX2FsbF9kdF9lbnNlbWJsZSRldmVudF9oYXNoLCBnZW9yZWZfYWxsX2R0X2Vuc2VtYmxlJHBsYWNlX2hhc2gpICN0YWJsZShjb25kaXRpb24pCnRlbXAgPC0gZ2VvcmVmX2FsbF9kdF9ldmVudHNfc2ZbY29uZGl0aW9uLF0gJT4lIGZpbHRlcighZHVwbGljYXRlZChldmVudF9oYXNoKSkKaGV4JGR2IDwtIHNhcHBseSggc3RfY292ZXJzKGhleCwgdGVtcCksIGxlbmd0aCkgCmxtMSA8LSBNQVNTOjpnbG0ubmIoZHYgfiAuLCBkYXRhPSBuYS5vbWl0KGFzLmRhdGEuZnJhbWUoaGV4KVssdmFyc19hbGxdKSApCnRlbXBfcmVzdWx0cyA8LSBicm9vbTo6dGlkeShsbTEpCnRlbXBfcmVzdWx0cyRUeXBlPSJSdWxlIgp0ZW1wX3Jlc3VsdHMkbGFiZWwgPC0gIkVuc2VtYmxlIFJ1bGUiCmxtX3Jlc3VsdHNfbGlzdDBbW2FzLmNoYXJhY3RlcigiRW5zZW1ibGUgUnVsZSIpXV0gPC0gdGVtcF9yZXN1bHRzCgoKCiAgCiNTb3VyY2UgRGF0YXNldApsbV9yZXN1bHRzX2xpc3QgPC0gbGlzdCgpCmZvcihxIGluIHVuaXF1ZShnZW9yZWZfYWxsX2R0X2V2ZW50c19zZiRzb3VyY2VfZGF0YXNldCkpewogICAgcHJpbnQocSkKICB0cnkoeyAjVEdOIGZhaWxzCgogIGNvbmRpdGlvbiA8LSBnZW9yZWZfYWxsX2R0X2V2ZW50c19zZiRzb3VyY2VfZGF0YXNldD09cQogIHRlbXAgPC0gZ2VvcmVmX2FsbF9kdF9ldmVudHNfc2ZbY29uZGl0aW9uLF0gJT4lIGZpbHRlcighZHVwbGljYXRlZChldmVudF9oYXNoKSkKICBoZXgkZHYgPC0gc2FwcGx5KCBzdF9jb3ZlcnMoaGV4LCB0ZW1wKSwgbGVuZ3RoKSAKICBsbTEgPC0gTUFTUzo6Z2xtLm5iKGR2IH4gLiwKICAgICAgICAgICAgICAgIGRhdGE9IG5hLm9taXQoYXMuZGF0YS5mcmFtZShoZXgpWyx2YXJzX2FsbF0pIAogICAgICAgICAgICAgICAgKQogIHRlbXBfcmVzdWx0cyA8LSBicm9vbTo6dGlkeShsbTEpCiAgdGVtcF9yZXN1bHRzJFR5cGU9IlNvdXJjZSBEYXRhc2V0IgogIHRlbXBfcmVzdWx0cyRsYWJlbCA8LSBxCiAgbG1fcmVzdWx0c19saXN0W1thcy5jaGFyYWN0ZXIocSldXSA8LSB0ZW1wX3Jlc3VsdHMKICB9KQp9CgojR2VvbWV0cnkgVHlwZQojSGFsZiB0aGUgdGltZSwgbGluZXN0cmluZyBkb2Vzbid0IGNvbnZlcmdlLiBFeGNsdWRlIGZyb20gdGhlIHBsb3RzIGRvd25zdHJlYW0gYW55d2F5CmxtX3Jlc3VsdHNfbGlzdDIgPC0gbGlzdCgpCmZvcihxIGluIHVuaXF1ZShnZW9yZWZfYWxsX2R0X2V2ZW50c19zZiRnZW9tZXRyeV90eXBlKSl7CiAgcHJpbnQocSkKICB0cnkoewogICAgY29uZGl0aW9uIDwtIGdlb3JlZl9hbGxfZHRfZXZlbnRzX3NmJGdlb21ldHJ5X3R5cGU9PXEKICAgIHRlbXAgPC0gZ2VvcmVmX2FsbF9kdF9ldmVudHNfc2ZbY29uZGl0aW9uLF0gJT4lIGZpbHRlcighZHVwbGljYXRlZChldmVudF9oYXNoKSkKICAgIGhleCRkdiA8LSBzYXBwbHkoIHN0X2NvdmVycyhoZXgsIHRlbXApLCBsZW5ndGgpIAogICAgbG0xIDwtIE1BU1M6OmdsbS5uYihkdiB+IC4sIGRhdGE9IG5hLm9taXQoYXMuZGF0YS5mcmFtZShoZXgpWyx2YXJzX2FsbF0pICwKICAgICAgICAgICAgICAgICAgY29udHJvbD1nbG0uY29udHJvbChtYXhpdD0xMDApICNVcCB0aGUgbnVtYmVyIG9mIGl0ZXJhdGlvbnMgc28gaXQgY29udmVyZ2VzCiAgICAgICAgICAgICAgICAgICkgCiAgICB0ZW1wX3Jlc3VsdHMgPC0gYnJvb206OnRpZHkobG0xKQogICAgdGVtcF9yZXN1bHRzJFR5cGU9Ikdlb21ldHJ5IFR5cGUiCiAgICB0ZW1wX3Jlc3VsdHMkbGFiZWwgPC0gcQogICAgbG1fcmVzdWx0c19saXN0MltbYXMuY2hhcmFjdGVyKHEpXV0gPC0gdGVtcF9yZXN1bHRzCiAgfSkKfQoKI0Z1enp5CmxtX3Jlc3VsdHNfbGlzdDMgPC0gbGlzdCgpCmZvcihxIGluIHVuaXF1ZShnZW9yZWZfYWxsX2R0X2V2ZW50c19zZiRmdXp6eSkpewogIHByaW50KHEpCiAgY29uZGl0aW9uIDwtIGdlb3JlZl9hbGxfZHRfZXZlbnRzX3NmJGZ1enp5PT1xCiAgdGVtcCA8LSBnZW9yZWZfYWxsX2R0X2V2ZW50c19zZltjb25kaXRpb24sXSAlPiUgZmlsdGVyKCFkdXBsaWNhdGVkKGV2ZW50X2hhc2gpKQogIGhleCRkdiA8LSBzYXBwbHkoIHN0X2NvdmVycyhoZXgsIHRlbXApLCBsZW5ndGgpIAogIGxtMSA8LSBNQVNTOjpnbG0ubmIoZHYgfiAuLCBkYXRhPSBuYS5vbWl0KGFzLmRhdGEuZnJhbWUoaGV4KVssdmFyc19hbGxdKSApCiAgdGVtcF9yZXN1bHRzIDwtIGJyb29tOjp0aWR5KGxtMSkKICB0ZW1wX3Jlc3VsdHMkVHlwZT0iTWF0Y2ggVHlwZSIKICB0ZW1wX3Jlc3VsdHMkbGFiZWwgPC0gcQogIGxtX3Jlc3VsdHNfbGlzdDNbW2FzLmNoYXJhY3RlcihxKV1dIDwtIHRlbXBfcmVzdWx0cwp9CgojU2VsZiBSZWZlcmVuY2UKbG1fcmVzdWx0c19saXN0NCA8LSBsaXN0KCkKZm9yKHEgaW4gdW5pcXVlKGdlb3JlZl9hbGxfZHRfZXZlbnRzX3NmJFNlbGZSZWZlcmVuY2UpKXsKICBwcmludChxKQogIGNvbmRpdGlvbiA8LSBnZW9yZWZfYWxsX2R0X2V2ZW50c19zZiRTZWxmUmVmZXJlbmNlPT1xCiAgdGVtcCA8LSBnZW9yZWZfYWxsX2R0X2V2ZW50c19zZltjb25kaXRpb24sXSAlPiUgZmlsdGVyKCFkdXBsaWNhdGVkKGV2ZW50X2hhc2gpKQogIGhleCRkdiA8LSBzYXBwbHkoIHN0X2NvdmVycyhoZXgsIHRlbXApLCBsZW5ndGgpIAogIGxtMSA8LSBNQVNTOjpnbG0ubmIoZHYgfiAuLCBkYXRhPSBuYS5vbWl0KGFzLmRhdGEuZnJhbWUoaGV4KVssdmFyc19hbGxdKSApCiAgdGVtcF9yZXN1bHRzIDwtIGJyb29tOjp0aWR5KGxtMSkKICB0ZW1wX3Jlc3VsdHMkVHlwZT0iU2VsZlJlZmVyZW5jZSIKICB0ZW1wX3Jlc3VsdHMkbGFiZWwgPC0gcQogIGxtX3Jlc3VsdHNfbGlzdDRbW2FzLmNoYXJhY3RlcihxKV1dIDwtIHRlbXBfcmVzdWx0cwp9CgoKCgpgYGAKCiMgUGxvdCB0aGUgY29lZmZpY2llbnRzIGZvciBlYWNoIG1vZGVsCgooRmlndXJlIDUpCgpgYGB7ciwgZmlnLndpZHRoPTE1LCBmaWcuaGVpZ2h0PTEwfQoKCmxtX3Jlc3VsdHNfZHQgPC0gZGF0YS50YWJsZTo6cmJpbmRsaXN0KCBsaXN0KCAgZGF0YS50YWJsZTo6cmJpbmRsaXN0KGxtX3Jlc3VsdHNfbGlzdDApLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEudGFibGU6OnJiaW5kbGlzdChsbV9yZXN1bHRzX2xpc3QpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEudGFibGU6OnJiaW5kbGlzdChsbV9yZXN1bHRzX2xpc3QyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhLnRhYmxlOjpyYmluZGxpc3QobG1fcmVzdWx0c19saXN0MyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0YS50YWJsZTo6cmJpbmRsaXN0KGxtX3Jlc3VsdHNfbGlzdDQpCikKICAgICAgICAgICAgICAgICAgICAgICAgICAgKQpsbV9yZXN1bHRzX2R0IDwtIGxtX3Jlc3VsdHNfZHQgJT4lIG11dGF0ZShyYXRlPSByb3VuZChleHAoZXN0aW1hdGUpLDIpICkKCgpzZW50ZW5jZV9jYXNlIDwtIGZ1bmN0aW9uKHgpIHN0cmluZ3I6OnN0cl90b19zZW50ZW5jZSh0b2xvd2VyKGdzdWIoIl8iLCIgIix4KSkpCgojaW5zdGFsbC5wYWNrYWdlcygiZXh0cmFmb250Iik7CiNsaWJyYXJ5KGV4dHJhZm9udCkKI2xpYnJhcnkoZXh0cmFmb250KQojZXh0cmFmb250Ojpmb250X2ltcG9ydChwcm9tcHQ9RiApCiNjYXBhYmlsaXRpZXMoKQojd2luZG93c0ZvbnRzKCkKI3NvcnQoYXMudmVjdG9yKHVubGlzdCh3aW5kb3dzRm9udHMoKSkpKQoKZm9udHMgPC0gYygnVGltZXMgTmV3IFJvbWFuJywKICAgICAgICAgICAnQ2FsaWJyaScsCiAgICAgICAgICAgJ0NvdXJpZXIgTmV3JywKICAgICAgICAgICAiR2VvcmdpYSIsCiAgICAgICAgICAgIlR1bmdhIikKICAgICAgICAgICAjJ3NlcmlmJywnSGVsdmV0aWNhJywnQm9va21hbicsJ1BhbGF0aW5vJykKCiNxcGxvdCgxOjEwKSt0aGVtZSh0ZXh0PWVsZW1lbnRfdGV4dChmYW1pbHk9IkdpbGwgU2FucyBVbHRyYSBCb2xkIikpCiNxcGxvdCgxOjEwKSt0aGVtZSh0ZXh0PWVsZW1lbnRfdGV4dChmYW1pbHk9Zm9udHNbMV0pKQojcXBsb3QoMToxMCkrdGhlbWUodGV4dD1lbGVtZW50X3RleHQoZmFtaWx5PWZvbnRzWzJdKSkKI3FwbG90KDE6MTApK3RoZW1lKHRleHQ9ZWxlbWVudF90ZXh0KGZhbWlseT1mb250c1szXSkpCiNxcGxvdCgxOjEwKSt0aGVtZSh0ZXh0PWVsZW1lbnRfdGV4dChmYW1pbHk9Zm9udHNbNF0pKQojcXBsb3QoMToxMCkrdGhlbWUodGV4dD1lbGVtZW50X3RleHQoZmFtaWx5PWZvbnRzWzVdKSkKCmxtX3Jlc3VsdHNfZHQgPC0gZGF0YS50YWJsZTo6YXMuZGF0YS50YWJsZShsbV9yZXN1bHRzX2R0KQpsbV9yZXN1bHRzX2R0W1R5cGU9PSJNYXRjaCBUeXBlIiAmIGxhYmVsPT0iRkFMU0UiLCBsYWJlbDo9ICJFeGFjdCIsXQpsbV9yZXN1bHRzX2R0W1R5cGU9PSJNYXRjaCBUeXBlIiAmIGxhYmVsPT0iVFJVRSIsIGxhYmVsOj0gIkZ1enp5IixdCgpsbV9yZXN1bHRzX2R0W1R5cGU9PSJTZWxmUmVmZXJlbmNlIiAmIGxhYmVsPT0iRkFMU0UiLCBsYWJlbDo9ICJTZWxmIFJlZi4iLF0KbG1fcmVzdWx0c19kdFtUeXBlPT0iU2VsZlJlZmVyZW5jZSIgJiBsYWJlbD09IlRSVUUiLCBsYWJlbDo9ICJObyBTZWxmIFJlZi4iLF0KCgpsbV9yZXN1bHRzX2R0W3Rlcm09PSJ0cmVlY292ZXIiICwgdGVybTo9InRyZWUgY292ZXIiLF0KCgpmb250ZmFjZXMgPC0gZmFjdG9yKGMoInBsYWluIiwiYm9sZCIsIml0YWxpYyIsImJvbGQuaXRhbGljIiwicGxhaW4iKSkKI3BfbG9hZChnZ3JlcGVsLCB0b29scykKcF9sbSA8LSBsbV9yZXN1bHRzX2R0ICU+JSAKICAgICAgICBmaWx0ZXIoIShsYWJlbCAlaW4lIGMoJ2tlbnlhX2Rpc3RyaWN0MTk2MicsJ2tlbnlhX2NhZGFzdHJhbCcsJ2tlbnlhX2NhZGFzdHJhbF9kaXN0cmljdCcsIkxJTkVTVFJJTkciKSkpICU+JQogICAgICAgIGZpbHRlcih0ZXJtICE9ICIoSW50ZXJjZXB0KSIpICU+JQogICAgICAgICNtdXRhdGUobGFiZWxbVHlwZT09Ik1hdGNoIFR5cGUiXT1nc3ViKCJGQUxTRSIsICJFeGFjdCIsIGxhYmVsW1R5cGU9PSJNYXRjaCBUeXBlIl0pKSAlPiUKICAgICAgICAjbXV0YXRlKGxhYmVsW1R5cGU9PSJNYXRjaCBUeXBlIl09Z3N1YigiVHJ1ZSIsICJGdXp6eSIsIGxhYmVsW1R5cGU9PSJNYXRjaCBUeXBlIl0pKSAlPiUKICAgICAgICAKICAgICAgICBtdXRhdGUoVHlwZT1zZW50ZW5jZV9jYXNlKFR5cGUpLAogICAgICAgICAgICAgICB0ZXJtPXNlbnRlbmNlX2Nhc2UodGVybSksCiAgICAgICAgICAgICAgIGxhYmVsPXNlbnRlbmNlX2Nhc2UobGFiZWwpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9cmF0ZSwKICAgICAgICAgICAgIHk9cC52YWx1ZSwKICAgICAgICAgICAgIGNvbG9yPVR5cGUsCiAgICAgICAgICAgICBsYWJlbD1sYWJlbCwKICAgICAgICAgICAgIGZhbWlseSA9IGZvbnRzW2FzLm51bWVyaWMoYXMuZmFjdG9yKFR5cGUpKV0sCiAgICAgICAgICAgICBmb250ZmFjZT0gZm9udGZhY2VzW2FzLm51bWVyaWMoYXMuZmFjdG9yKFR5cGUpKV0KICAgICAgICAgICAgICkpICArIAogIGZhY2V0X3dyYXAofnRlcm0sIHNjYWxlcz0iZnJlZSIpICsgCiAgdGhlbWVfYncoKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMSwgLCBjb2xvcj0iZ3JleSIpICsgCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC4wMSwgbGluZXR5cGU9MywgY29sb3I9ImdyZXkiKSArIAogIGdncmVwZWw6Omdlb21fdGV4dF9yZXBlbChzaXplPTIpICsgCiAgeGxhYihzZW50ZW5jZV9jYXNlKCJDb2VmZmljaWVudCBFc3RpbWF0ZXMgKENoYW5nZSBpbiByZWxhdGl2ZSByYXRlIG9mIFZpb2xlbnQgRXZlbnRzKSIpKSArCiAgeWxhYihzZW50ZW5jZV9jYXNlKCJDb2VmZmljaWVudCBQLVZhbHVlIEVzdGltYXRlcyAoMC4wMSBUaHJlc2hvbGQgSW5kaWNhdGVkKSIpKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSBjKDAuOSwgMC4xKSwgIyBjKDAsMCkgYm90dG9tIGxlZnQsIGMoMSwxKSB0b3AtcmlnaHQuCiAgKQpwX2xtCiAjKyBjb29yZF9jYXJ0ZXNpYW4oeT0ibG9nIikKCmdnc2F2ZSgKICBmaWxlbmFtZSA9IGdsdWU6OmdsdWUoZGlyX2ZpZ3VyZXMsICJwX2xtLnBkZiIpLAogIHBsb3QgPSBwX2xtLCB3aWR0aCA9IDksIGhlaWdodCA9IDYsCiAgZGV2aWNlID0gY2Fpcm9fcGRmICNoYXZlIHRvIHVzZSBjYWlybyB0byBjb3JyZWN0bHkgZW1iZWQgdGhlIGZvbnRzCikKCmBgYAoKIyBJbnRlcnByZXRhdGlvbgoKV2UgZXZhbHVhdGVkIGZpdmUgY29lZmZpY2llbnRzIGluIGEgdHlwaWNhbCBsaW5lYXIgbW9kZWwgcHJlZGljdGluZyB0aGUgbnVtYmVyIG9mIHZpb2xlbnQgZXZlbnRzIHRvIG9jY3VyIGluIGdlb2dyYXBoaWMgYXJlYS4gVGhlIG9ubHkgdGhpbmcgd2UgdmFyaWVkIGZyb20gbW9kZWwgdG8gbW9kZWwgd2FzIHRoZSBtZXRob2QgYnkgd2hpY2ggd2UgZ2VvcmVmZXJlbmNlZCB0aGUgdW5kZXJseWluZyBldmVudHMuIFdlIHZhcmllZCB0aGVtIGFjcm9zcyBmb3VyIG1haW4gZGVjaXNpb25zLCBmdXp6eS9leGFjdCBtYXRjaGluZywgZ2VvbWV0cnkgdHlwZSwgd2hldGhlciB0byBhbGxvdyBzZWxmIHJlZmVyZW5jZSwgYW5kIHRoZSBzb3VyY2UgZ2F6ZXRlZXIuIFdlIGZ1cnRoZXIgaW5jbHVkZWQgdHdvIHByb3Bvc2VkIGNvbWluYXRpb25zIG9mIGRlY2lzaW9ucywgb25lIGJhc2VkIG9uIGZpeGVkIGhhbmQgdGFpbG9yZWQgcnVsZXMgb2Ygd2hlbiB0byBwcmVmZXIgb24gZGVjaXNpb24gdG8gYW5vdGhlciwgYW5kIGFub3RoZXIgYmFzZWQgb24gYSBzdXBlcnZzaWVkIGxlYXJuZWQgbW9kZWwgd2hvc2UgcnVsZXMgY2FuIHZhcnkgYmFzZWQgb24gc3BlY2lmaWMgY29udGV4dC4KCgpXZSBmb3VuZCB2YXJpYXRpb24gaW4gb3VyIGNvZWZmaWNpZW50IGVzdGltYXRlcyBhY3Jvc3MgdGhlIGRpZmZlcmVudCBnZW9ncmFwaGljIGRlY2lzaW9ucy4gVGhlIGNvZWZmaWNlaW50cyB2YXJyaWVkIGluIHNpZ24sIG1hZ25pdHVkZSwgYW5kIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZS4gV2UgZm91bmQgdmFyaWF0aW9uIGluIHRoZSBzdGFiaWxpdHkgb2YgY29lZmZpY2llbnRzIGJ5IGVhY2ggY292YXJpYXRlLiBCb3RoIHBvcHVsYXRpb24gYW5kIHJhaW5mYWxsIGhhZCBzdGFibGUgZXN0aW1hdGVzLCB3aXRoIGNvbnNpc3RlbnQgc2lnbiwgc2lnbmlmaWNhbmNlLCBhbmQgc3Vic3RhbnRpdmUgZWZmZWN0cyB3aXRoIGEgZmV3IG91dGxpYXJzLiBEaXN0YW5jZSwgcnVnZ2VkbmVzcywgYW5kIHRyZWVjb3ZlciBoYWQgdW5zdGFibGUgY29lZmZpY2llbnRzLCB3aXRoIHZhcmlhdGlvbiBpbiBzaWduaWZpY2FuY2UgYW5kIG9jY2FzaW9uYWx5IHRoZSBkaXJlY3Rpb24gb2YgdGhlIGVmZmVjdC4gSW4gYSBmZXcgb3V0bGlhcnMsIHdlIG9ic2VydmVkIGEgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCBlZmZlY3QgaW4gdGhlIG9wcG9zaXRlIGRpcmVjdGlvbi4KCg==