Here, we show how to compute the (PCF) for eye movement data in three steps. The PCF reveals whether the distribution of fixation locations during a single scanpath can be explained by the overall inhomogeneity observed across all observers or whether fixation locations of a single scanpath contain additional spatial correlations. For details we refer to our manuscript (Trukenbrod, Barthelmé, Wichmann, & Engbert, 2018).

library(tidyverse)
library(spatstat)
library(ggplot2)
library(parallel)

Data Set

For our analyses we use an experiment, where participants viewed two types of images twice. The repeated presentation of images allows to investigate the influence of visual long-term memory on eye movements. From previous work we expect that the repeated presentation leads to similar fixation densities but shortens saccade amplitudes during the second inspection. The resulting point patterns are similar but differ slightly in the overall inhomogeneity, which makes a direct comparison of the eye movement behavior difficult. The same problem is true for the comparison of different image types. The PCF takes differences in the underlying inhomogeneity into account and allows a direct comparison of the spatial correlations under different viewing conditions (first vs. second presentation) and for different image types (natural vs. texture images).

Our data set contains scanpaths from 35 participants viewing 30 images twice. Images were either natural scenes or images of texture images. The initial fixation, fixations containing blinks, and fixations outside the image boundaries were removed from further analyses. Overall 57344 fixations remained.

# image coordinates
xrange <- c(1.03585769369965,32.1115885046892)
yrange <- c(0.828686154959722,25.6892708037514)
dat <- read.table('./data/SpSt1.dat',header=TRUE)
d <- dat %>%
  mutate(.,xpos=(xR+xL)/2,ypos=(yR+yL)/2) %>% 
  filter(., nth>1 & blinkFix==0 & blinkSac==0 &
           xpos>xrange[1] & xpos<xrange[2] &
           ypos>yrange[1] & ypos<yrange[2]) %>%
  select(.,id,type,nthPres,image,xpos,ypos)
d$id <- factor(d$id)
d$type <- factor(d$type,levels=c(1,2),labels=c('Natural scene','Texture'))
d$nthPres <- factor(d$nthPres,levels=c(1,2),labels=c('First','Second'))
d$image <- factor(d$image)
summary(d)
       id                   type         nthPres          image            xpos             ypos       
 3      : 2545   Natural scene:29061   First :32201   10     : 2125   Min.   : 1.095   Min.   : 0.840  
 21     : 2077   Texture      :28283   Second:25143   8      : 2083   1st Qu.:11.350   1st Qu.: 9.815  
 28     : 2017                                        24     : 2012   Median :16.055   Median :14.005  
 12     : 2010                                        26     : 2007   Mean   :16.561   Mean   :13.705  
 23     : 1992                                        3      : 2001   3rd Qu.:21.995   3rd Qu.:17.765  
 8      : 1814                                        6      : 1997   Max.   :32.110   Max.   :25.670  
 (Other):44889                                        (Other):45119                                    

Step 1. Simulation of inhomogeneous and homogeneous control processes.

For our PCF computations, we need to simulate two control point processes, namely a homogeneous and an inhomogeneous point process. Points (fixation locations) are sampled independently from each other in both control processes and due to the independence of points, we do not expect to observe any spatial correlations between points at distance \(r\). Any observed correlations would be spurious and depend on the data structure (e.g., length of fixation sequences) or a wrong parameterization of the method. Hence, both control processes ensure that correlations in the PCF arise from the empirical data and not by the method itself. In addition, the inhomogeneous point process is used in the second step to estimate an optimal bandwidth for the intensity estimation of the PCF in Step 3.

For simulation of the control processes we need to pick a bandwidth for the estimation of the fixation density. Here, we use Scott’s rule of thumb (\(\tt bw.scott()\)). For each empirical scanpath we simulated one scanpath of equal length (same number of fixations as observed in the experiment) for the inhomogeneous point process and for the homogeneous point process. As a result we now have fixations from three point processes (PP) with the same number of fixations each.

sigma <- NULL
for (img in unique(d$image)){
  for (pres in unique(d$nthPres)){
    idx <- d$nthPres==pres & d$image==img
    dImg <- d[idx,]
    ppImg <- ppp(dImg$xpos,dImg$ypos,window=owin(xrange,yrange))
    bwImg <- mean(bw.scott(ppImg))
    sigma <- rbind(sigma,data.frame(img,pres,bwImg))
    denImg <- density(ppImg,sigma=bwImg)
    sim <- rpoint(nrow(dImg),denImg)
    d$xposInhom[idx] <- round(sim$x,digits=3)
    d$yposInhom[idx] <- round(sim$y,digits=3)
  }
}
sim <- rpoint(nrow(d))
d$xposHom <- round(sim$x*diff(xrange)+xrange[1],digits=3)
d$yposHom <- round(sim$y*diff(yrange)+yrange[1],digits=3)
dx <- gather(d,'pp','x',c(5,7,9))
dy <- gather(d,'pp','y',c(6,8,10))
d <- cbind(dx[,-c(5,6,7)],y=dy$y)
d$pp <- factor(d$pp,levels=c('xpos','xposInhom','xposHom'),
               labels=c('Experiment','Inhomogeneous','Homogeneous'))
table(d$pp)  

   Experiment Inhomogeneous   Homogeneous 
        57344         57344         57344 

The next figure shows all fixations of the three point processes (experimental data, inhomogeneous PP, homogeneous PP) during the first inspection of an image. As expected, fixation locations are not uniformly distributed in the experimental data and indicate inhomogeneity. The estimated intensity of all fixation locations is depicted by gray shading where darker areas represent higher intensities. The intensity of the experimental data was used for the simulation of the inhomogeneous PP.

dImg <- filter(d,image=='1' & nthPres=='First')
bw <- filter(sigma,img=='1' & pres=='First')$bw
ggplot(data=dImg,aes(x=x,y=y,col=pp)) +
  stat_density2d(aes(alpha=..density..),show.legend=FALSE,
                 geom="raster",contour=FALSE,h=bw*c(1,1),fill="black") +
  geom_point(size=.01) +
  facet_grid(.~pp) +
  labs(x='x-Coordinate [°]',y='y-Coordinate [°]',colour='Point Process') +
  coord_fixed(xlim=xrange,ylim=yrange,expand = FALSE) +
  theme_bw(base_size = 16) +
  theme(legend.position="none") 

Examples of an empirical and of the corresponding simulated scanpaths are visualized in the next figure. The estimated intensity of each point process is displayed with gray shading. Fixations are likely to be located in areas of high average intensity. Due to the independence of fixations, scanpaths of the inhomogeneous and homogeneous point processes reveal less systematic exploration behavior than the empirical data. Hence, saccade amplitudes increase considerably for these PPs.

dTrial <- filter(d,image=='1' & nthPres=='First' & id=='1')
dImg <- filter(d,image=='1' & nthPres=='First')
bw <- filter(sigma,img=='1' & pres=='First')$bw
ggplot(data=dTrial,aes(x=x,y=y,col=pp)) +
  stat_density2d(data=dImg,aes(alpha=..density..),show.legend=FALSE,
                 geom="raster",contour=FALSE,h=bw*c(1,1),fill="black") +
  geom_point(size=2) +  geom_path(size=.5) +
  labs(x='x-Coordinate [°]',y='y-Coordinate [°]',colour='Point Proces') +
  coord_fixed(xlim=xrange,ylim=yrange,expand=FALSE) +
  theme_bw(base_size=16) +
  facet_grid(.~pp,drop=TRUE) +
  theme(legend.position="none") 

Step 2. Choose optimal bandwidth for intensity estimation of PCF

Next we need to choose an optimal bandwidth for estimation of the intensity used to calculate the inhomogeneous PCF in Step 3. This is different from the bandwidth estimated in Step 1 to simulate the inhomogeneous point process. Since fixation locations in scanpaths of both control point processes are sampled independent from the preceding fixation history, average PCFs of both point processes are expected to reveal no spatial correlations, i.e. we expect the PCF \(g(r)\approx 1\) at all distances \(r\).

To choose an optimal bandwidth, we computed the deviation from complete spatial randomness (\(\int_a^b(g(r)-1)^2dr\)) of PCFs for a range of bandwidths for each image and presentation. We varied bandwidths from 0° to 10° in steps of 0.1° and computed the deviation from complete spatial randomness of the average PCF on an image. The average deviation at each bandwidth is plotted in the next figure. Lines represent individual images. For all images, the deviation is smallest in the center an increases for smaller and larger bandwidths. For all images the optimal bandwidth is between 1.5° and 5°. The bandwidth yielding the smallest deviation from complete spatial randomness on an image was chosen for the intensity estimation of the PCF in the next step.

# average PCF
mPCF <- function(pcf){
  r <- pcf$r[1,]
  trans <- sapply(data.frame(pcf$pcf),mean)
  mPCF <- data.frame(r,trans,row.names=NULL)
  return(mPCF)
}
# PCF deviation function
devPcfComp <- function(g,rmin,rmax){
  indx <- g$r>=rmin & g$r<=rmax
  dev <- sum((g$trans[indx]-1)^2)*(g$r[2]-g$r[1])
  return(dev)
}
# compute PCF for fixation sequence
fixSeqPCF <- function(fixSeq,den,p){
  
  # start, end, number of trials
  idx <- which(diff(as.numeric(fixSeq$id))!=0)
  i1 <- c(1,idx+1)
  i2 <- c(idx,nrow(fixSeq))
  Nid <- length(i1)
  numA <-  nrow(fixSeq)
  
  alvr <- NULL
  alvt <- NULL
  alvs <- NULL
  hdev <- NULL
  aid  <- NULL
  
  for (t in 1:Nid ) {
    scanpath <- fixSeq[i1[t]:i2[t],]
    
    if ( nrow(scanpath)>p$maxlen )  {
      scanpath <- scanpath[(1:p$maxlen),]
    } # maxlen
    
    if ( nrow(scanpath)>=p$minlen ) {
      numX <- nrow(scanpath)
      id <- unique(scanpath$id)
      X <- ppp(scanpath$x,scanpath$y,window=owin(xrange,yrange))
      g <- pcfinhom(X,den,kernel="epanechnikov",r=p$reval,divisor="r") # divisor="d" | "r"
      g$trans <-  g$trans*numA/numX
      ginteg <- devPcfComp(g,p$rmin,p$rmax)
      hdev <- c(hdev,ginteg)
      alvt <- rbind(alvt,g$trans)
      alvs <- rbind(alvs,rep(t,length(g$trans)))
      alvr <- rbind(alvr,g$r)
      aid <- c(aid,id)
    } # minlen
  } # id
  pcfSummary <- list(r=alvr,pcf=alvt,trial=alvs,dev=hdev,minlen=p$minlen,
                     maxlen=p$maxlen,rmin=p$rmin,rmax=p$rmax,nId=Nid,
                     excluded=Nid-length(hdev),id=aid)
  return(pcfSummary)
} # function
optimalBandwidthPcf <- function(bw,img,pres){
  pExp <- filter(d,pp=='Experiment' & image==img & nthPres==pres)
  ppExp <- ppp(x=pExp$x,y=pExp$y,window=owin(xrange,yrange))
  denExp <- density(ppExp,sigma=bw,edge=FALSE)
  denExp$v[denExp$v<epsilon] <- epsilon
  pInhom <- filter(d,pp=='Inhomogeneous' & image==img & nthPres==pres)
  
  pcfInhom <- fixSeqPCF(pInhom,denExp,p)
  meanPcfInhom <- mPCF(pcfInhom)
  dev <- devPcfComp(meanPcfInhom,p$rmin,p$rmax)
  type <- unique(pExp$type)
  
  return(data.frame(bw,img,pres,type,dev))
}
epsilon <- .Machine$double.eps # set smallest possible computable number
p <- NULL
p$minlen <- 10     # minimum length of a scanpath
p$maxlen <- 100    # maximum length of a scanpath, otherwise truncated
p$rmin <- .1       # evaluation window of PCF (minimum)
p$rmax <- 6.5      # evaluation window of PCF (maximum)
p$sigmaMin <- .1   # minimum bandwidth for optimal bandwidth evaluation  (Step 3)
p$sigmaMax <- 10   # maximum bandwidth for optimal bandwidth evaluation (Step 3)
p$sigmaSteps <- .1 # stepsize for optimal bandwidth evaluation (Step 3)
p$reval <- seq(0,7,length.out=513) # compute PCF from to
# evaluate PCFs for all bandwidths on all images and each presentation separately
eval_bw <- seq(p$sigmaMin,p$sigmaMax,by=p$sigmaSteps)
eval_img <- unique(d$image)
eval_pres <- unique(d$nthPres)
parOptBw <- cbind(data.frame(bw=rep(eval_bw,each=length(eval_img)),img=rep(eval_img,times=length(eval_bw))),pres=rep(eval_pres,each=length(eval_bw)*length(eval_img)))
cl <- makeForkCluster(detectCores()-1)
deviation <- parLapply(cl, seq_len(nrow(parOptBw)), function(i) {
  do.call(optimalBandwidthPcf, as.list(parOptBw[i,]))
})
stopCluster(cl)
# deviation of the inhomogeneous point process for each bandwith, image, and presentation
devInhom <- do.call(rbind,lapply(deviation,as.data.frame))
# save result
save(d,devInhom,p,xrange,yrange,file='devInhom.Rdata')
# load(file='devInhom.Rdata')
ggplot(devInhom,aes(x=bw,y=dev,group=img)) +
  geom_line() +
  facet_grid(.~type+pres) +
  labs(x='Bandwidth Sigma',y='Deviation') +
  coord_cartesian(ylim=c(0,5)) +
  theme_bw(base_size=16)

Step 3. Compute PCF for each trial

In the last step we compute the PCF of our empirical data. We use the optimal bandwidth that resulted in the smallest deviation from complete spatial randomness of the PCF of the inhomogeneous PP in Step 2. PCFs of individual scanpaths on an image are plotted in the next figure (left panel, gray lines). Three example scanpaths are highlighted in black. PCFs vary strongly between individual trials for all point processes. The average empirical PCF across all scanpaths on an image (red line) deviates strongly from complete spatial randomness, i.e. \(g(r) \neq 1\) for distances smaller than 4°. At distances beyond 4° the average PCF suggests independence of points, i.e., \(g(r)\approx 1\). Thus, fixations aggregate in close proximity during individual trials. Conversely, areas further away are fixated as predicted by chance, i.e., by the overall inhomogeneity observed across all participants. As expected, inspection of the control point processes demonstrates the absence of spatial correlations. The average PCF of the inhomogeneous and homogeneous point process are constant with \(g(r) \approx 1\). An artifact of the estimation procedure at short distances is present in all estimates.

computePcfImage <- function(img,pres,bw){
  pExp <- filter(d,pp=='Experiment' & image==img & nthPres==pres)
  ppExp <- ppp(x=pExp$x,y=pExp$y,window=owin(xrange,yrange))
  denExp <- density(ppExp,sigma=bw,edge=FALSE)
  pInhom <- filter(d,pp=='Inhomogeneous' & image==img & nthPres==pres)
  ppInhom <- ppp(x=pInhom$x,y=pInhom$y,window=owin(xrange,yrange))
  denInhom <- density(ppInhom,sigma=bw,edge=FALSE)
  pHom <- filter(d,pp=='Homogeneous' & image==img & nthPres==pres)
  ppHom <- ppp(x=pHom$x,y=pHom$y,window=owin(xrange,yrange))
  denHom <- density(ppHom,sigma=bw,edge=FALSE)
  
  pcfExp <- fixSeqPCF(pExp,denExp,p)
  pcfInhom <- fixSeqPCF(pInhom,denInhom,p)
  pcfHom <- fixSeqPCF(pHom,denHom,p)
  
  pcfImage <- rbind(data.frame(pp='Experiment',id=c(pcfExp$trial),
      r=c(pcfExp$r),pcf=c(pcfExp$pcf)),
    data.frame(pp='Inhomogeneous',id=c(pcfInhom$trial),
      r=c(pcfInhom$r),pcf=c(pcfInhom$pcf)),
    data.frame(pp='Homogeneous',id=c(pcfHom$trial),
      r=c(pcfHom$r),pcf=c(pcfHom$pcf)))
  
  type <- unique(pExp$type)
  
  return(data.frame(img,pres,type,pcfImage))
}
parPcf <- devInhom %>% 
  group_by(.,img,pres) %>%
  summarize(.,bw=bw[which.min(dev)])
cl <- makeForkCluster(detectCores()-1)
pcfDataSet <- parLapply(cl, seq_len(nrow(parPcf)), function(i) {
  do.call(computePcfImage, as.list(parPcf[i,]))
})
stopCluster(cl)
pcfDataSet <- do.call(rbind,lapply(pcfDataSet,as.data.frame))
pcfTrial <- pcfDataSet %>%
  filter(.,img==1 & pres=='First') %>%
  group_by(.,pp,id,r) %>%
  summarize(.,pcf=mean(pcf))
meanPcf <- pcfDataSet %>%
  group_by(.,pp,pres,type,r) %>%
  summarize(.,pcf=mean(pcf))
meanPcfImg <- pcfDataSet %>%
  group_by(.,pp,pres,type,img,r) %>%
  summarize(.,pcf=mean(pcf))
ggplot(pcfTrial,aes(x=r,y=pcf,col=pp)) +
  geom_line(aes(group=id),col='gray70') +
  geom_line(data=filter(pcfTrial,id%in%c(1,2,3)), aes(lty=as.factor(id)),col='black') +
  geom_line(stat='summary',fun.y='mean') +
  facet_grid(.~pp) +
  coord_cartesian(ylim=c(0,3)) +
  labs(x='Distance r [°]',y='Pair correlation function g(r)',colour='Point Process',lty='Participant') +
  theme_bw(base_size=16)

The same procedure can be repeated for each image. The next Figure shows the PCFs of each image (gray lines) as well as the average across all images (colored lines). While inhomogeneous and homogeneous point processes reveal no spatial correlations, empirical PCFs show spatial aggregation at short distances, \(r < 4\)° in all conditions. A detailed discussion of the results can be found in the manuscript.

ggplot(meanPcfImg,aes(x=r,y=pcf,col=pp)) +
  geom_line(aes(group=img),col='gray70') +
  geom_line(data=meanPcf) +
#  geom_line(data=pcfTrial,col='black',stat='summary',fun.y='mean') +
  facet_grid(type+pres~pp) +
  coord_cartesian(ylim=c(0.5,2)) +
  labs(x='Distance r [°]',y='Pair correlation function g(r)',colour='Point Process') +
  theme_bw(base_size=16)

References

Trukenbrod, H. A., Barthelmé, S., Wichmann, F. A., & Engbert, R. (2018). Spatial statistics for gaze patterns in scene viewing: Effects of repeated viewing. ArXiv. Retrieved from https://arxiv.org/abs/1704.01761

LS0tCnRpdGxlOiAiQXBwbHlpbmcgdGhlIHBhaXIgY29ycmVsYXRpb24gZnVuY3Rpb24gdG8gZXllIG1vdmVtZW50cyIKbGluay1jaXRhdGlvbnM6IHllcwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogaGlkZQogIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQKYmlibGlvZ3JhcGh5OiBsaWIuYmliCmNzbDogYXBhLmNzbAotLS0KCkhlcmUsIHdlIHNob3cgaG93IHRvIGNvbXB1dGUgdGhlIFxlbXBoe3BhaXIgY29ycmVsYXRpb24gZnVuY3Rpb259IChQQ0YpIGZvciBleWUgbW92ZW1lbnQgZGF0YSBpbiB0aHJlZSBzdGVwcy4gVGhlIFBDRiByZXZlYWxzIHdoZXRoZXIgdGhlIGRpc3RyaWJ1dGlvbiBvZiBmaXhhdGlvbiBsb2NhdGlvbnMgZHVyaW5nIGEgc2luZ2xlIHNjYW5wYXRoIGNhbiBiZSBleHBsYWluZWQgYnkgdGhlIG92ZXJhbGwgaW5ob21vZ2VuZWl0eSBvYnNlcnZlZCBhY3Jvc3MgYWxsIG9ic2VydmVycyBvciB3aGV0aGVyIGZpeGF0aW9uIGxvY2F0aW9ucyBvZiBhIHNpbmdsZSBzY2FucGF0aCBjb250YWluIGFkZGl0aW9uYWwgc3BhdGlhbCBjb3JyZWxhdGlvbnMuIEZvciBkZXRhaWxzIHdlIHJlZmVyIHRvIG91ciBtYW51c2NyaXB0IFtAVHJ1a2VuYnJvZC5hclhpdi4yMDE4XS4KCmBgYHtyLCBpbmNsdWRlPUZBTFNFfQpzZXR3ZCgnfi9vd25DbG91ZC9QYXBlci8xNF9TcGF0U3RhdDFfUENGLycpCnJtKGxpc3Q9bHMoKSkKYGBgCgpgYGB7ciBwYWNrYWdlcywgZWNobz1UUlVFLCB3YXJuaW5ncz1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc3BhdHN0YXQpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShwYXJhbGxlbCkKYGBgCgojIyMjIERhdGEgU2V0CgpGb3Igb3VyIGFuYWx5c2VzIHdlIHVzZSBhbiBleHBlcmltZW50LCB3aGVyZSBwYXJ0aWNpcGFudHMgdmlld2VkIHR3byB0eXBlcyBvZiBpbWFnZXMgdHdpY2UuIFRoZSByZXBlYXRlZCBwcmVzZW50YXRpb24gb2YgaW1hZ2VzIGFsbG93cyB0byBpbnZlc3RpZ2F0ZSB0aGUgaW5mbHVlbmNlIG9mIHZpc3VhbCBsb25nLXRlcm0gbWVtb3J5IG9uIGV5ZSBtb3ZlbWVudHMuIEZyb20gcHJldmlvdXMgd29yayB3ZSBleHBlY3QgdGhhdCB0aGUgcmVwZWF0ZWQgcHJlc2VudGF0aW9uIGxlYWRzIHRvIHNpbWlsYXIgZml4YXRpb24gZGVuc2l0aWVzIGJ1dCBzaG9ydGVucyBzYWNjYWRlIGFtcGxpdHVkZXMgZHVyaW5nIHRoZSBzZWNvbmQgaW5zcGVjdGlvbi4gVGhlIHJlc3VsdGluZyBwb2ludCBwYXR0ZXJucyBhcmUgc2ltaWxhciBidXQgZGlmZmVyIHNsaWdodGx5IGluIHRoZSBvdmVyYWxsIGluaG9tb2dlbmVpdHksIHdoaWNoIG1ha2VzIGEgZGlyZWN0IGNvbXBhcmlzb24gb2YgdGhlIGV5ZSBtb3ZlbWVudCBiZWhhdmlvciBkaWZmaWN1bHQuIFRoZSBzYW1lIHByb2JsZW0gaXMgdHJ1ZSBmb3IgdGhlIGNvbXBhcmlzb24gb2YgZGlmZmVyZW50IGltYWdlIHR5cGVzLiBUaGUgUENGIHRha2VzIGRpZmZlcmVuY2VzIGluIHRoZSB1bmRlcmx5aW5nIGluaG9tb2dlbmVpdHkgaW50byBhY2NvdW50IGFuZCBhbGxvd3MgYSBkaXJlY3QgY29tcGFyaXNvbiBvZiB0aGUgc3BhdGlhbCBjb3JyZWxhdGlvbnMgdW5kZXIgZGlmZmVyZW50IHZpZXdpbmcgY29uZGl0aW9ucyAoZmlyc3QgdnMuIHNlY29uZCBwcmVzZW50YXRpb24pIGFuZCBmb3IgZGlmZmVyZW50IGltYWdlIHR5cGVzIChuYXR1cmFsIHZzLiB0ZXh0dXJlIGltYWdlcykuCgpPdXIgZGF0YSBzZXQgY29udGFpbnMgc2NhbnBhdGhzIGZyb20gYHIgbGVuZ3RoKHVuaXF1ZShkJGlkKSlgIHBhcnRpY2lwYW50cyB2aWV3aW5nIGByIGxlbmd0aCh1bmlxdWUoZCRpbWFnZSkpYCBpbWFnZXMgdHdpY2UuIEltYWdlcyB3ZXJlIGVpdGhlciBuYXR1cmFsIHNjZW5lcyBvciBpbWFnZXMgb2YgdGV4dHVyZSBpbWFnZXMuIFRoZSBpbml0aWFsIGZpeGF0aW9uLCBmaXhhdGlvbnMgY29udGFpbmluZyBibGlua3MsIGFuZCBmaXhhdGlvbnMgb3V0c2lkZSB0aGUgaW1hZ2UgYm91bmRhcmllcyB3ZXJlIHJlbW92ZWQgZnJvbSBmdXJ0aGVyIGFuYWx5c2VzLiBPdmVyYWxsIGByIHN1bShkJHBwPT0nRXhwZXJpbWVudCcpYCBmaXhhdGlvbnMgcmVtYWluZWQuCgpgYGB7ciBkYXRhLCBlY2hvPVRSVUV9CiMgaW1hZ2UgY29vcmRpbmF0ZXMKeHJhbmdlIDwtIGMoMS4wMzU4NTc2OTM2OTk2NSwzMi4xMTE1ODg1MDQ2ODkyKQp5cmFuZ2UgPC0gYygwLjgyODY4NjE1NDk1OTcyMiwyNS42ODkyNzA4MDM3NTE0KQoKZGF0IDwtIHJlYWQudGFibGUoJy4vZGF0YS9TcFN0MS5kYXQnLGhlYWRlcj1UUlVFKQoKZCA8LSBkYXQgJT4lCiAgbXV0YXRlKC4seHBvcz0oeFIreEwpLzIseXBvcz0oeVIreUwpLzIpICU+JSAKICBmaWx0ZXIoLiwgbnRoPjEgJiBibGlua0ZpeD09MCAmIGJsaW5rU2FjPT0wICYKICAgICAgICAgICB4cG9zPnhyYW5nZVsxXSAmIHhwb3M8eHJhbmdlWzJdICYKICAgICAgICAgICB5cG9zPnlyYW5nZVsxXSAmIHlwb3M8eXJhbmdlWzJdKSAlPiUKICBzZWxlY3QoLixpZCx0eXBlLG50aFByZXMsaW1hZ2UseHBvcyx5cG9zKQoKZCRpZCA8LSBmYWN0b3IoZCRpZCkKZCR0eXBlIDwtIGZhY3RvcihkJHR5cGUsbGV2ZWxzPWMoMSwyKSxsYWJlbHM9YygnTmF0dXJhbCBzY2VuZScsJ1RleHR1cmUnKSkKZCRudGhQcmVzIDwtIGZhY3RvcihkJG50aFByZXMsbGV2ZWxzPWMoMSwyKSxsYWJlbHM9YygnRmlyc3QnLCdTZWNvbmQnKSkKZCRpbWFnZSA8LSBmYWN0b3IoZCRpbWFnZSkKCnN1bW1hcnkoZCkKYGBgCgojIyMjIFN0ZXAgMS4gU2ltdWxhdGlvbiBvZiBpbmhvbW9nZW5lb3VzIGFuZCBob21vZ2VuZW91cyBjb250cm9sIHByb2Nlc3Nlcy4KCkZvciBvdXIgUENGIGNvbXB1dGF0aW9ucywgd2UgbmVlZCB0byBzaW11bGF0ZSB0d28gY29udHJvbCBwb2ludCBwcm9jZXNzZXMsIG5hbWVseSBhIGhvbW9nZW5lb3VzIGFuZCBhbiBpbmhvbW9nZW5lb3VzIHBvaW50IHByb2Nlc3MuIFBvaW50cyAoZml4YXRpb24gbG9jYXRpb25zKSBhcmUgc2FtcGxlZCBpbmRlcGVuZGVudGx5IGZyb20gZWFjaCBvdGhlciBpbiBib3RoIGNvbnRyb2wgcHJvY2Vzc2VzIGFuZCBkdWUgdG8gdGhlIGluZGVwZW5kZW5jZSBvZiBwb2ludHMsIHdlIGRvIG5vdCBleHBlY3QgdG8gb2JzZXJ2ZSBhbnkgc3BhdGlhbCBjb3JyZWxhdGlvbnMgYmV0d2VlbiBwb2ludHMgYXQgZGlzdGFuY2UgJHIkLiBBbnkgb2JzZXJ2ZWQgY29ycmVsYXRpb25zIHdvdWxkIGJlIHNwdXJpb3VzIGFuZCBkZXBlbmQgb24gdGhlIGRhdGEgc3RydWN0dXJlIChlLmcuLCBsZW5ndGggb2YgZml4YXRpb24gc2VxdWVuY2VzKSBvciBhIHdyb25nIHBhcmFtZXRlcml6YXRpb24gb2YgdGhlIG1ldGhvZC4gSGVuY2UsIGJvdGggY29udHJvbCBwcm9jZXNzZXMgZW5zdXJlIHRoYXQgY29ycmVsYXRpb25zIGluIHRoZSBQQ0YgYXJpc2UgZnJvbSB0aGUgZW1waXJpY2FsIGRhdGEgYW5kIG5vdCBieSB0aGUgbWV0aG9kIGl0c2VsZi4gSW4gYWRkaXRpb24sIHRoZSBpbmhvbW9nZW5lb3VzIHBvaW50IHByb2Nlc3MgaXMgdXNlZCBpbiB0aGUgc2Vjb25kIHN0ZXAgdG8gZXN0aW1hdGUgYW4gb3B0aW1hbCBiYW5kd2lkdGggZm9yIHRoZSBpbnRlbnNpdHkgZXN0aW1hdGlvbiBvZiB0aGUgUENGIGluIFN0ZXAgMy4KCkZvciBzaW11bGF0aW9uIG9mIHRoZSBjb250cm9sIHByb2Nlc3NlcyB3ZSBuZWVkIHRvIHBpY2sgYSBiYW5kd2lkdGggZm9yIHRoZSBlc3RpbWF0aW9uIG9mIHRoZSBmaXhhdGlvbiBkZW5zaXR5LiBIZXJlLCB3ZSB1c2UgU2NvdHQncyBydWxlIG9mIHRodW1iICgkXHR0IGJ3LnNjb3R0KCkkKS4gRm9yIGVhY2ggZW1waXJpY2FsIHNjYW5wYXRoIHdlIHNpbXVsYXRlZCBvbmUgc2NhbnBhdGggb2YgZXF1YWwgbGVuZ3RoIChzYW1lIG51bWJlciBvZiBmaXhhdGlvbnMgYXMgb2JzZXJ2ZWQgaW4gdGhlIGV4cGVyaW1lbnQpIGZvciB0aGUgaW5ob21vZ2VuZW91cyBwb2ludCBwcm9jZXNzIGFuZCBmb3IgdGhlIGhvbW9nZW5lb3VzIHBvaW50IHByb2Nlc3MuIEFzIGEgcmVzdWx0IHdlIG5vdyBoYXZlIGZpeGF0aW9ucyBmcm9tIHRocmVlIHBvaW50IHByb2Nlc3NlcyAoUFApIHdpdGggdGhlIHNhbWUgbnVtYmVyIG9mIGZpeGF0aW9ucyBlYWNoLgoKYGBge3Igc2ltUG9pbnRQcm9jZXNzLCBlY2hvPVRSVUUsIHdhcm5pbmc9RkFMU0V9CnNpZ21hIDwtIE5VTEwKZm9yIChpbWcgaW4gdW5pcXVlKGQkaW1hZ2UpKXsKICBmb3IgKHByZXMgaW4gdW5pcXVlKGQkbnRoUHJlcykpewogICAgaWR4IDwtIGQkbnRoUHJlcz09cHJlcyAmIGQkaW1hZ2U9PWltZwogICAgZEltZyA8LSBkW2lkeCxdCiAgICBwcEltZyA8LSBwcHAoZEltZyR4cG9zLGRJbWckeXBvcyx3aW5kb3c9b3dpbih4cmFuZ2UseXJhbmdlKSkKICAgIGJ3SW1nIDwtIG1lYW4oYncuc2NvdHQocHBJbWcpKQogICAgc2lnbWEgPC0gcmJpbmQoc2lnbWEsZGF0YS5mcmFtZShpbWcscHJlcyxid0ltZykpCiAgICBkZW5JbWcgPC0gZGVuc2l0eShwcEltZyxzaWdtYT1id0ltZykKICAgIHNpbSA8LSBycG9pbnQobnJvdyhkSW1nKSxkZW5JbWcpCiAgICBkJHhwb3NJbmhvbVtpZHhdIDwtIHJvdW5kKHNpbSR4LGRpZ2l0cz0zKQogICAgZCR5cG9zSW5ob21baWR4XSA8LSByb3VuZChzaW0keSxkaWdpdHM9MykKICB9Cn0Kc2ltIDwtIHJwb2ludChucm93KGQpKQpkJHhwb3NIb20gPC0gcm91bmQoc2ltJHgqZGlmZih4cmFuZ2UpK3hyYW5nZVsxXSxkaWdpdHM9MykKZCR5cG9zSG9tIDwtIHJvdW5kKHNpbSR5KmRpZmYoeXJhbmdlKSt5cmFuZ2VbMV0sZGlnaXRzPTMpCgpkeCA8LSBnYXRoZXIoZCwncHAnLCd4JyxjKDUsNyw5KSkKZHkgPC0gZ2F0aGVyKGQsJ3BwJywneScsYyg2LDgsMTApKQpkIDwtIGNiaW5kKGR4WywtYyg1LDYsNyldLHk9ZHkkeSkKZCRwcCA8LSBmYWN0b3IoZCRwcCxsZXZlbHM9YygneHBvcycsJ3hwb3NJbmhvbScsJ3hwb3NIb20nKSwKICAgICAgICAgICAgICAgbGFiZWxzPWMoJ0V4cGVyaW1lbnQnLCdJbmhvbW9nZW5lb3VzJywnSG9tb2dlbmVvdXMnKSkKdGFibGUoZCRwcCkgIApgYGAKClRoZSBuZXh0IGZpZ3VyZSBzaG93cyBhbGwgZml4YXRpb25zIG9mIHRoZSB0aHJlZSBwb2ludCBwcm9jZXNzZXMgKGV4cGVyaW1lbnRhbCBkYXRhLCBpbmhvbW9nZW5lb3VzIFBQLCBob21vZ2VuZW91cyBQUCkgZHVyaW5nIHRoZSBmaXJzdCBpbnNwZWN0aW9uIG9mIGFuIGltYWdlLiBBcyBleHBlY3RlZCwgZml4YXRpb24gbG9jYXRpb25zIGFyZSBub3QgdW5pZm9ybWx5IGRpc3RyaWJ1dGVkIGluIHRoZSBleHBlcmltZW50YWwgZGF0YSBhbmQgaW5kaWNhdGUgaW5ob21vZ2VuZWl0eS4gVGhlIGVzdGltYXRlZCBpbnRlbnNpdHkgb2YgYWxsIGZpeGF0aW9uIGxvY2F0aW9ucyBpcyBkZXBpY3RlZCBieSBncmF5IHNoYWRpbmcgd2hlcmUgZGFya2VyIGFyZWFzIHJlcHJlc2VudCBoaWdoZXIgaW50ZW5zaXRpZXMuIFRoZSBpbnRlbnNpdHkgb2YgdGhlIGV4cGVyaW1lbnRhbCBkYXRhIHdhcyB1c2VkIGZvciB0aGUgc2ltdWxhdGlvbiBvZiB0aGUgaW5ob21vZ2VuZW91cyBQUC4KCmBgYHtyIHBsb3RBbGxGaXhhdGlvbnMsIGVjaG89VFJVRSwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTR9CmRJbWcgPC0gZmlsdGVyKGQsaW1hZ2U9PScxJyAmIG50aFByZXM9PSdGaXJzdCcpCmJ3IDwtIGZpbHRlcihzaWdtYSxpbWc9PScxJyAmIHByZXM9PSdGaXJzdCcpJGJ3CgpnZ3Bsb3QoZGF0YT1kSW1nLGFlcyh4PXgseT15LGNvbD1wcCkpICsKICBzdGF0X2RlbnNpdHkyZChhZXMoYWxwaGE9Li5kZW5zaXR5Li4pLHNob3cubGVnZW5kPUZBTFNFLAogICAgICAgICAgICAgICAgIGdlb209InJhc3RlciIsY29udG91cj1GQUxTRSxoPWJ3KmMoMSwxKSxmaWxsPSJibGFjayIpICsKICBnZW9tX3BvaW50KHNpemU9LjAxKSArCiAgZmFjZXRfZ3JpZCgufnBwKSArCiAgbGFicyh4PSd4LUNvb3JkaW5hdGUgW8KwXScseT0neS1Db29yZGluYXRlIFvCsF0nLGNvbG91cj0nUG9pbnQgUHJvY2VzcycpICsKICBjb29yZF9maXhlZCh4bGltPXhyYW5nZSx5bGltPXlyYW5nZSxleHBhbmQgPSBGQUxTRSkgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDE2KSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgCmBgYAoKRXhhbXBsZXMgb2YgYW4gZW1waXJpY2FsIGFuZCBvZiB0aGUgY29ycmVzcG9uZGluZyBzaW11bGF0ZWQgc2NhbnBhdGhzIGFyZSB2aXN1YWxpemVkIGluIHRoZSBuZXh0IGZpZ3VyZS4gVGhlIGVzdGltYXRlZCBpbnRlbnNpdHkgb2YgZWFjaCBwb2ludCBwcm9jZXNzIGlzIGRpc3BsYXllZCB3aXRoIGdyYXkgc2hhZGluZy4gRml4YXRpb25zIGFyZSBsaWtlbHkgdG8gYmUgbG9jYXRlZCBpbiBhcmVhcyBvZiBoaWdoIGF2ZXJhZ2UgaW50ZW5zaXR5LiBEdWUgdG8gdGhlIGluZGVwZW5kZW5jZSBvZiBmaXhhdGlvbnMsIHNjYW5wYXRocyBvZiB0aGUgaW5ob21vZ2VuZW91cyBhbmQgaG9tb2dlbmVvdXMgcG9pbnQgcHJvY2Vzc2VzIHJldmVhbCBsZXNzIHN5c3RlbWF0aWMgZXhwbG9yYXRpb24gYmVoYXZpb3IgdGhhbiB0aGUgZW1waXJpY2FsIGRhdGEuIEhlbmNlLCBzYWNjYWRlIGFtcGxpdHVkZXMgaW5jcmVhc2UgY29uc2lkZXJhYmx5IGZvciB0aGVzZSBQUHMuCgpgYGB7ciBwbG90VHJpYWxzLCBlY2hvPVRSVUUsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0PTR9CmRUcmlhbCA8LSBmaWx0ZXIoZCxpbWFnZT09JzEnICYgbnRoUHJlcz09J0ZpcnN0JyAmIGlkPT0nMScpCmRJbWcgPC0gZmlsdGVyKGQsaW1hZ2U9PScxJyAmIG50aFByZXM9PSdGaXJzdCcpCmJ3IDwtIGZpbHRlcihzaWdtYSxpbWc9PScxJyAmIHByZXM9PSdGaXJzdCcpJGJ3CgpnZ3Bsb3QoZGF0YT1kVHJpYWwsYWVzKHg9eCx5PXksY29sPXBwKSkgKwogIHN0YXRfZGVuc2l0eTJkKGRhdGE9ZEltZyxhZXMoYWxwaGE9Li5kZW5zaXR5Li4pLHNob3cubGVnZW5kPUZBTFNFLAogICAgICAgICAgICAgICAgIGdlb209InJhc3RlciIsY29udG91cj1GQUxTRSxoPWJ3KmMoMSwxKSxmaWxsPSJibGFjayIpICsKICBnZW9tX3BvaW50KHNpemU9MikgKyAgZ2VvbV9wYXRoKHNpemU9LjUpICsKICBsYWJzKHg9J3gtQ29vcmRpbmF0ZSBbwrBdJyx5PSd5LUNvb3JkaW5hdGUgW8KwXScsY29sb3VyPSdQb2ludCBQcm9jZXMnKSArCiAgY29vcmRfZml4ZWQoeGxpbT14cmFuZ2UseWxpbT15cmFuZ2UsZXhwYW5kPUZBTFNFKSArCiAgdGhlbWVfYncoYmFzZV9zaXplPTE2KSArCiAgZmFjZXRfZ3JpZCgufnBwLGRyb3A9VFJVRSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpIApgYGAKCiMjIyMgU3RlcCAyLiBDaG9vc2Ugb3B0aW1hbCBiYW5kd2lkdGggZm9yIGludGVuc2l0eSBlc3RpbWF0aW9uIG9mIFBDRgoKTmV4dCB3ZSBuZWVkIHRvIGNob29zZSBhbiBvcHRpbWFsIGJhbmR3aWR0aCBmb3IgZXN0aW1hdGlvbiBvZiB0aGUgaW50ZW5zaXR5IHVzZWQgdG8gY2FsY3VsYXRlIHRoZSBpbmhvbW9nZW5lb3VzIFBDRiBpbiBTdGVwIDMuIFRoaXMgaXMgZGlmZmVyZW50IGZyb20gdGhlIGJhbmR3aWR0aCBlc3RpbWF0ZWQgaW4gU3RlcCAxIHRvIHNpbXVsYXRlIHRoZSBpbmhvbW9nZW5lb3VzIHBvaW50IHByb2Nlc3MuIFNpbmNlIGZpeGF0aW9uIGxvY2F0aW9ucyBpbiBzY2FucGF0aHMgb2YgYm90aCBjb250cm9sIHBvaW50IHByb2Nlc3NlcyBhcmUgc2FtcGxlZCBpbmRlcGVuZGVudCBmcm9tIHRoZSBwcmVjZWRpbmcgZml4YXRpb24gaGlzdG9yeSwgYXZlcmFnZSBQQ0ZzIG9mIGJvdGggcG9pbnQgcHJvY2Vzc2VzIGFyZSBleHBlY3RlZCB0byByZXZlYWwgbm8gc3BhdGlhbCBjb3JyZWxhdGlvbnMsIGkuZS4gd2UgZXhwZWN0IHRoZSBQQ0YgJGcocilcYXBwcm94IDEkIGF0IGFsbCBkaXN0YW5jZXMgJHIkLgoKVG8gY2hvb3NlIGFuIG9wdGltYWwgYmFuZHdpZHRoLCB3ZSBjb21wdXRlZCB0aGUgZGV2aWF0aW9uIGZyb20gY29tcGxldGUgc3BhdGlhbCByYW5kb21uZXNzICgkXGludF9hXmIoZyhyKS0xKV4yZHIkKSBvZiBQQ0ZzIGZvciBhIHJhbmdlIG9mIGJhbmR3aWR0aHMgZm9yIGVhY2ggaW1hZ2UgYW5kIHByZXNlbnRhdGlvbi4gV2UgdmFyaWVkIGJhbmR3aWR0aHMgZnJvbSAwwrAgdG8gMTDCsCBpbiBzdGVwcyBvZiAwLjHCsCBhbmQgY29tcHV0ZWQgdGhlIGRldmlhdGlvbiBmcm9tIGNvbXBsZXRlIHNwYXRpYWwgcmFuZG9tbmVzcyBvZiB0aGUgYXZlcmFnZSBQQ0Ygb24gYW4gaW1hZ2UuIFRoZSBhdmVyYWdlIGRldmlhdGlvbiBhdCBlYWNoIGJhbmR3aWR0aCBpcyBwbG90dGVkIGluIHRoZSBuZXh0IGZpZ3VyZS4gTGluZXMgcmVwcmVzZW50IGluZGl2aWR1YWwgaW1hZ2VzLiBGb3IgYWxsIGltYWdlcywgdGhlIGRldmlhdGlvbiBpcyBzbWFsbGVzdCBpbiB0aGUgY2VudGVyIGFuIGluY3JlYXNlcyBmb3Igc21hbGxlciBhbmQgbGFyZ2VyIGJhbmR3aWR0aHMuIEZvciBhbGwgaW1hZ2VzIHRoZSBvcHRpbWFsIGJhbmR3aWR0aCBpcyBiZXR3ZWVuIDEuNcKwIGFuZCA1wrAuIFRoZSBiYW5kd2lkdGggeWllbGRpbmcgdGhlIHNtYWxsZXN0IGRldmlhdGlvbiBmcm9tIGNvbXBsZXRlIHNwYXRpYWwgcmFuZG9tbmVzcyBvbiBhbiBpbWFnZSB3YXMgY2hvc2VuIGZvciB0aGUgaW50ZW5zaXR5IGVzdGltYXRpb24gb2YgdGhlIFBDRiBpbiB0aGUgbmV4dCBzdGVwLgoKYGBge3IgZGVmRnVuY3Rpb25zLCBlY2hvPVRSVUV9CiMgYXZlcmFnZSBQQ0YKbVBDRiA8LSBmdW5jdGlvbihwY2YpewogIHIgPC0gcGNmJHJbMSxdCiAgdHJhbnMgPC0gc2FwcGx5KGRhdGEuZnJhbWUocGNmJHBjZiksbWVhbikKICBtUENGIDwtIGRhdGEuZnJhbWUocix0cmFucyxyb3cubmFtZXM9TlVMTCkKICByZXR1cm4obVBDRikKfQoKIyBQQ0YgZGV2aWF0aW9uIGZ1bmN0aW9uCmRldlBjZkNvbXAgPC0gZnVuY3Rpb24oZyxybWluLHJtYXgpewogIGluZHggPC0gZyRyPj1ybWluICYgZyRyPD1ybWF4CiAgZGV2IDwtIHN1bSgoZyR0cmFuc1tpbmR4XS0xKV4yKSooZyRyWzJdLWckclsxXSkKICByZXR1cm4oZGV2KQp9CgojIGNvbXB1dGUgUENGIGZvciBmaXhhdGlvbiBzZXF1ZW5jZQpmaXhTZXFQQ0YgPC0gZnVuY3Rpb24oZml4U2VxLGRlbixwKXsKICAKICAjIHN0YXJ0LCBlbmQsIG51bWJlciBvZiB0cmlhbHMKICBpZHggPC0gd2hpY2goZGlmZihhcy5udW1lcmljKGZpeFNlcSRpZCkpIT0wKQogIGkxIDwtIGMoMSxpZHgrMSkKICBpMiA8LSBjKGlkeCxucm93KGZpeFNlcSkpCiAgTmlkIDwtIGxlbmd0aChpMSkKICBudW1BIDwtICBucm93KGZpeFNlcSkKICAKICBhbHZyIDwtIE5VTEwKICBhbHZ0IDwtIE5VTEwKICBhbHZzIDwtIE5VTEwKICBoZGV2IDwtIE5VTEwKICBhaWQgIDwtIE5VTEwKICAKICBmb3IgKHQgaW4gMTpOaWQgKSB7CiAgICBzY2FucGF0aCA8LSBmaXhTZXFbaTFbdF06aTJbdF0sXQogICAgCiAgICBpZiAoIG5yb3coc2NhbnBhdGgpPnAkbWF4bGVuICkgIHsKICAgICAgc2NhbnBhdGggPC0gc2NhbnBhdGhbKDE6cCRtYXhsZW4pLF0KICAgIH0gIyBtYXhsZW4KICAgIAogICAgaWYgKCBucm93KHNjYW5wYXRoKT49cCRtaW5sZW4gKSB7CiAgICAgIG51bVggPC0gbnJvdyhzY2FucGF0aCkKICAgICAgaWQgPC0gdW5pcXVlKHNjYW5wYXRoJGlkKQogICAgICBYIDwtIHBwcChzY2FucGF0aCR4LHNjYW5wYXRoJHksd2luZG93PW93aW4oeHJhbmdlLHlyYW5nZSkpCiAgICAgIGcgPC0gcGNmaW5ob20oWCxkZW4sa2VybmVsPSJlcGFuZWNobmlrb3YiLHI9cCRyZXZhbCxkaXZpc29yPSJyIikgIyBkaXZpc29yPSJkIiB8ICJyIgogICAgICBnJHRyYW5zIDwtICBnJHRyYW5zKm51bUEvbnVtWAogICAgICBnaW50ZWcgPC0gZGV2UGNmQ29tcChnLHAkcm1pbixwJHJtYXgpCiAgICAgIGhkZXYgPC0gYyhoZGV2LGdpbnRlZykKICAgICAgYWx2dCA8LSByYmluZChhbHZ0LGckdHJhbnMpCiAgICAgIGFsdnMgPC0gcmJpbmQoYWx2cyxyZXAodCxsZW5ndGgoZyR0cmFucykpKQogICAgICBhbHZyIDwtIHJiaW5kKGFsdnIsZyRyKQogICAgICBhaWQgPC0gYyhhaWQsaWQpCiAgICB9ICMgbWlubGVuCiAgfSAjIGlkCiAgcGNmU3VtbWFyeSA8LSBsaXN0KHI9YWx2cixwY2Y9YWx2dCx0cmlhbD1hbHZzLGRldj1oZGV2LG1pbmxlbj1wJG1pbmxlbiwKICAgICAgICAgICAgICAgICAgICAgbWF4bGVuPXAkbWF4bGVuLHJtaW49cCRybWluLHJtYXg9cCRybWF4LG5JZD1OaWQsCiAgICAgICAgICAgICAgICAgICAgIGV4Y2x1ZGVkPU5pZC1sZW5ndGgoaGRldiksaWQ9YWlkKQogIHJldHVybihwY2ZTdW1tYXJ5KQp9ICMgZnVuY3Rpb24KCm9wdGltYWxCYW5kd2lkdGhQY2YgPC0gZnVuY3Rpb24oYncsaW1nLHByZXMpewogIHBFeHAgPC0gZmlsdGVyKGQscHA9PSdFeHBlcmltZW50JyAmIGltYWdlPT1pbWcgJiBudGhQcmVzPT1wcmVzKQogIHBwRXhwIDwtIHBwcCh4PXBFeHAkeCx5PXBFeHAkeSx3aW5kb3c9b3dpbih4cmFuZ2UseXJhbmdlKSkKICBkZW5FeHAgPC0gZGVuc2l0eShwcEV4cCxzaWdtYT1idyxlZGdlPUZBTFNFKQogIGRlbkV4cCR2W2RlbkV4cCR2PGVwc2lsb25dIDwtIGVwc2lsb24KICBwSW5ob20gPC0gZmlsdGVyKGQscHA9PSdJbmhvbW9nZW5lb3VzJyAmIGltYWdlPT1pbWcgJiBudGhQcmVzPT1wcmVzKQogIAogIHBjZkluaG9tIDwtIGZpeFNlcVBDRihwSW5ob20sZGVuRXhwLHApCiAgbWVhblBjZkluaG9tIDwtIG1QQ0YocGNmSW5ob20pCiAgZGV2IDwtIGRldlBjZkNvbXAobWVhblBjZkluaG9tLHAkcm1pbixwJHJtYXgpCiAgdHlwZSA8LSB1bmlxdWUocEV4cCR0eXBlKQogIAogIHJldHVybihkYXRhLmZyYW1lKGJ3LGltZyxwcmVzLHR5cGUsZGV2KSkKfQpgYGAKCmBgYHtyLCBlY2hvPVRSVUUsIHdhcm5pbmc9RkFMU0UsIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD00LCBjYWNoZT1UUlVFfQplcHNpbG9uIDwtIC5NYWNoaW5lJGRvdWJsZS5lcHMgIyBzZXQgc21hbGxlc3QgcG9zc2libGUgY29tcHV0YWJsZSBudW1iZXIKCnAgPC0gTlVMTApwJG1pbmxlbiA8LSAxMCAgICAgIyBtaW5pbXVtIGxlbmd0aCBvZiBhIHNjYW5wYXRoCnAkbWF4bGVuIDwtIDEwMCAgICAjIG1heGltdW0gbGVuZ3RoIG9mIGEgc2NhbnBhdGgsIG90aGVyd2lzZSB0cnVuY2F0ZWQKcCRybWluIDwtIC4xICAgICAgICMgZXZhbHVhdGlvbiB3aW5kb3cgb2YgUENGIChtaW5pbXVtKQpwJHJtYXggPC0gNi41ICAgICAgIyBldmFsdWF0aW9uIHdpbmRvdyBvZiBQQ0YgKG1heGltdW0pCnAkc2lnbWFNaW4gPC0gLjEgICAjIG1pbmltdW0gYmFuZHdpZHRoIGZvciBvcHRpbWFsIGJhbmR3aWR0aCBldmFsdWF0aW9uICAoU3RlcCAzKQpwJHNpZ21hTWF4IDwtIDEwICAgIyBtYXhpbXVtIGJhbmR3aWR0aCBmb3Igb3B0aW1hbCBiYW5kd2lkdGggZXZhbHVhdGlvbiAoU3RlcCAzKQpwJHNpZ21hU3RlcHMgPC0gLjEgIyBzdGVwc2l6ZSBmb3Igb3B0aW1hbCBiYW5kd2lkdGggZXZhbHVhdGlvbiAoU3RlcCAzKQpwJHJldmFsIDwtIHNlcSgwLDcsbGVuZ3RoLm91dD01MTMpICMgY29tcHV0ZSBQQ0YgZnJvbSB0bwoKIyBldmFsdWF0ZSBQQ0ZzIGZvciBhbGwgYmFuZHdpZHRocyBvbiBhbGwgaW1hZ2VzIGFuZCBlYWNoIHByZXNlbnRhdGlvbiBzZXBhcmF0ZWx5CmV2YWxfYncgPC0gc2VxKHAkc2lnbWFNaW4scCRzaWdtYU1heCxieT1wJHNpZ21hU3RlcHMpCmV2YWxfaW1nIDwtIHVuaXF1ZShkJGltYWdlKQpldmFsX3ByZXMgPC0gdW5pcXVlKGQkbnRoUHJlcykKcGFyT3B0QncgPC0gY2JpbmQoZGF0YS5mcmFtZShidz1yZXAoZXZhbF9idyxlYWNoPWxlbmd0aChldmFsX2ltZykpLGltZz1yZXAoZXZhbF9pbWcsdGltZXM9bGVuZ3RoKGV2YWxfYncpKSkscHJlcz1yZXAoZXZhbF9wcmVzLGVhY2g9bGVuZ3RoKGV2YWxfYncpKmxlbmd0aChldmFsX2ltZykpKQoKY2wgPC0gbWFrZUZvcmtDbHVzdGVyKGRldGVjdENvcmVzKCktMSkKZGV2aWF0aW9uIDwtIHBhckxhcHBseShjbCwgc2VxX2xlbihucm93KHBhck9wdEJ3KSksIGZ1bmN0aW9uKGkpIHsKICBkby5jYWxsKG9wdGltYWxCYW5kd2lkdGhQY2YsIGFzLmxpc3QocGFyT3B0QndbaSxdKSkKfSkKc3RvcENsdXN0ZXIoY2wpCgojIGRldmlhdGlvbiBvZiB0aGUgaW5ob21vZ2VuZW91cyBwb2ludCBwcm9jZXNzIGZvciBlYWNoIGJhbmR3aXRoLCBpbWFnZSwgYW5kIHByZXNlbnRhdGlvbgpkZXZJbmhvbSA8LSBkby5jYWxsKHJiaW5kLGxhcHBseShkZXZpYXRpb24sYXMuZGF0YS5mcmFtZSkpCgojIHNhdmUgcmVzdWx0CnNhdmUoZCxkZXZJbmhvbSxwLHhyYW5nZSx5cmFuZ2UsZmlsZT0nZGV2SW5ob20uUmRhdGEnKQojIGxvYWQoZmlsZT0nZGV2SW5ob20uUmRhdGEnKQoKZ2dwbG90KGRldkluaG9tLGFlcyh4PWJ3LHk9ZGV2LGdyb3VwPWltZykpICsKICBnZW9tX2xpbmUoKSArCiAgZmFjZXRfZ3JpZCgufnR5cGUrcHJlcykgKwogIGxhYnMoeD0nQmFuZHdpZHRoIFNpZ21hJyx5PSdEZXZpYXRpb24nKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW09YygwLDUpKSArCiAgdGhlbWVfYncoYmFzZV9zaXplPTE2KQpgYGAKCiMjIyMgU3RlcCAzLiBDb21wdXRlIFBDRiBmb3IgZWFjaCB0cmlhbAogCkluIHRoZSBsYXN0IHN0ZXAgd2UgY29tcHV0ZSB0aGUgUENGIG9mIG91ciBlbXBpcmljYWwgZGF0YS4gV2UgdXNlIHRoZSBvcHRpbWFsIGJhbmR3aWR0aCB0aGF0IHJlc3VsdGVkIGluIHRoZSBzbWFsbGVzdCBkZXZpYXRpb24gZnJvbSBjb21wbGV0ZSBzcGF0aWFsIHJhbmRvbW5lc3Mgb2YgdGhlIFBDRiBvZiB0aGUgaW5ob21vZ2VuZW91cyBQUCBpbiBTdGVwIDIuIFBDRnMgb2YgaW5kaXZpZHVhbCBzY2FucGF0aHMgb24gYW4gaW1hZ2UgYXJlIHBsb3R0ZWQgaW4gdGhlIG5leHQgZmlndXJlIChsZWZ0IHBhbmVsLCBncmF5IGxpbmVzKS4gVGhyZWUgZXhhbXBsZSBzY2FucGF0aHMgYXJlIGhpZ2hsaWdodGVkIGluIGJsYWNrLiBQQ0ZzIHZhcnkgc3Ryb25nbHkgYmV0d2VlbiBpbmRpdmlkdWFsIHRyaWFscyBmb3IgYWxsIHBvaW50IHByb2Nlc3Nlcy4gVGhlIGF2ZXJhZ2UgZW1waXJpY2FsIFBDRiBhY3Jvc3MgYWxsIHNjYW5wYXRocyBvbiBhbiBpbWFnZSAocmVkIGxpbmUpIGRldmlhdGVzIHN0cm9uZ2x5IGZyb20gY29tcGxldGUgc3BhdGlhbCByYW5kb21uZXNzLCBpLmUuICRnKHIpIFxuZXEgMSQgZm9yIGRpc3RhbmNlcyBzbWFsbGVyIHRoYW4gNMKwLiBBdCBkaXN0YW5jZXMgYmV5b25kIDTCsCB0aGUgYXZlcmFnZSBQQ0Ygc3VnZ2VzdHMgaW5kZXBlbmRlbmNlIG9mIHBvaW50cywgaS5lLiwgJGcocilcYXBwcm94IDEkLiBUaHVzLCBmaXhhdGlvbnMgYWdncmVnYXRlIGluIGNsb3NlIHByb3hpbWl0eSBkdXJpbmcgaW5kaXZpZHVhbCB0cmlhbHMuIENvbnZlcnNlbHksIGFyZWFzIGZ1cnRoZXIgYXdheSBhcmUgZml4YXRlZCBhcyBwcmVkaWN0ZWQgYnkgY2hhbmNlLCBpLmUuLCBieSB0aGUgb3ZlcmFsbCBpbmhvbW9nZW5laXR5IG9ic2VydmVkIGFjcm9zcyBhbGwgcGFydGljaXBhbnRzLiBBcyBleHBlY3RlZCwgaW5zcGVjdGlvbiBvZiB0aGUgY29udHJvbCBwb2ludCBwcm9jZXNzZXMgZGVtb25zdHJhdGVzIHRoZSBhYnNlbmNlIG9mIHNwYXRpYWwgY29ycmVsYXRpb25zLiBUaGUgYXZlcmFnZSBQQ0Ygb2YgdGhlIGluaG9tb2dlbmVvdXMgYW5kIGhvbW9nZW5lb3VzIHBvaW50IHByb2Nlc3MgYXJlIGNvbnN0YW50IHdpdGggICRnKHIpIFxhcHByb3ggMSQuIEFuIGFydGlmYWN0IG9mIHRoZSBlc3RpbWF0aW9uIHByb2NlZHVyZSAgYXQgc2hvcnQgZGlzdGFuY2VzIGlzIHByZXNlbnQgaW4gYWxsIGVzdGltYXRlcy4KIApgYGB7ciBwY2ZEYXRhU2V0LCBlY2hvPVRSVUUsIHdhcm5pbmc9RkFMU0V9CmNvbXB1dGVQY2ZJbWFnZSA8LSBmdW5jdGlvbihpbWcscHJlcyxidyl7CiAgcEV4cCA8LSBmaWx0ZXIoZCxwcD09J0V4cGVyaW1lbnQnICYgaW1hZ2U9PWltZyAmIG50aFByZXM9PXByZXMpCiAgcHBFeHAgPC0gcHBwKHg9cEV4cCR4LHk9cEV4cCR5LHdpbmRvdz1vd2luKHhyYW5nZSx5cmFuZ2UpKQogIGRlbkV4cCA8LSBkZW5zaXR5KHBwRXhwLHNpZ21hPWJ3LGVkZ2U9RkFMU0UpCgogIHBJbmhvbSA8LSBmaWx0ZXIoZCxwcD09J0luaG9tb2dlbmVvdXMnICYgaW1hZ2U9PWltZyAmIG50aFByZXM9PXByZXMpCiAgcHBJbmhvbSA8LSBwcHAoeD1wSW5ob20keCx5PXBJbmhvbSR5LHdpbmRvdz1vd2luKHhyYW5nZSx5cmFuZ2UpKQogIGRlbkluaG9tIDwtIGRlbnNpdHkocHBJbmhvbSxzaWdtYT1idyxlZGdlPUZBTFNFKQoKICBwSG9tIDwtIGZpbHRlcihkLHBwPT0nSG9tb2dlbmVvdXMnICYgaW1hZ2U9PWltZyAmIG50aFByZXM9PXByZXMpCiAgcHBIb20gPC0gcHBwKHg9cEhvbSR4LHk9cEhvbSR5LHdpbmRvdz1vd2luKHhyYW5nZSx5cmFuZ2UpKQogIGRlbkhvbSA8LSBkZW5zaXR5KHBwSG9tLHNpZ21hPWJ3LGVkZ2U9RkFMU0UpCiAgCiAgcGNmRXhwIDwtIGZpeFNlcVBDRihwRXhwLGRlbkV4cCxwKQogIHBjZkluaG9tIDwtIGZpeFNlcVBDRihwSW5ob20sZGVuSW5ob20scCkKICBwY2ZIb20gPC0gZml4U2VxUENGKHBIb20sZGVuSG9tLHApCiAgCiAgcGNmSW1hZ2UgPC0gcmJpbmQoZGF0YS5mcmFtZShwcD0nRXhwZXJpbWVudCcsaWQ9YyhwY2ZFeHAkdHJpYWwpLAogICAgICByPWMocGNmRXhwJHIpLHBjZj1jKHBjZkV4cCRwY2YpKSwKICAgIGRhdGEuZnJhbWUocHA9J0luaG9tb2dlbmVvdXMnLGlkPWMocGNmSW5ob20kdHJpYWwpLAogICAgICByPWMocGNmSW5ob20kcikscGNmPWMocGNmSW5ob20kcGNmKSksCiAgICBkYXRhLmZyYW1lKHBwPSdIb21vZ2VuZW91cycsaWQ9YyhwY2ZIb20kdHJpYWwpLAogICAgICByPWMocGNmSG9tJHIpLHBjZj1jKHBjZkhvbSRwY2YpKSkKICAKICB0eXBlIDwtIHVuaXF1ZShwRXhwJHR5cGUpCiAgCiAgcmV0dXJuKGRhdGEuZnJhbWUoaW1nLHByZXMsdHlwZSxwY2ZJbWFnZSkpCn0KCnBhclBjZiA8LSBkZXZJbmhvbSAlPiUgCiAgZ3JvdXBfYnkoLixpbWcscHJlcykgJT4lCiAgc3VtbWFyaXplKC4sYnc9Yndbd2hpY2gubWluKGRldildKQoKCmNsIDwtIG1ha2VGb3JrQ2x1c3RlcihkZXRlY3RDb3JlcygpLTEpCnBjZkRhdGFTZXQgPC0gcGFyTGFwcGx5KGNsLCBzZXFfbGVuKG5yb3cocGFyUGNmKSksIGZ1bmN0aW9uKGkpIHsKICBkby5jYWxsKGNvbXB1dGVQY2ZJbWFnZSwgYXMubGlzdChwYXJQY2ZbaSxdKSkKfSkKc3RvcENsdXN0ZXIoY2wpCgpwY2ZEYXRhU2V0IDwtIGRvLmNhbGwocmJpbmQsbGFwcGx5KHBjZkRhdGFTZXQsYXMuZGF0YS5mcmFtZSkpCgpwY2ZUcmlhbCA8LSBwY2ZEYXRhU2V0ICU+JQogIGZpbHRlciguLGltZz09MSAmIHByZXM9PSdGaXJzdCcpICU+JQogIGdyb3VwX2J5KC4scHAsaWQscikgJT4lCiAgc3VtbWFyaXplKC4scGNmPW1lYW4ocGNmKSkKCm1lYW5QY2YgPC0gcGNmRGF0YVNldCAlPiUKICBncm91cF9ieSguLHBwLHByZXMsdHlwZSxyKSAlPiUKICBzdW1tYXJpemUoLixwY2Y9bWVhbihwY2YpKQoKbWVhblBjZkltZyA8LSBwY2ZEYXRhU2V0ICU+JQogIGdyb3VwX2J5KC4scHAscHJlcyx0eXBlLGltZyxyKSAlPiUKICBzdW1tYXJpemUoLixwY2Y9bWVhbihwY2YpKQpgYGAKCmBgYHtyIHBsb3RQY2ZUcmlhbCwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQ9NH0KZ2dwbG90KHBjZlRyaWFsLGFlcyh4PXIseT1wY2YsY29sPXBwKSkgKwogIGdlb21fbGluZShhZXMoZ3JvdXA9aWQpLGNvbD0nZ3JheTcwJykgKwogIGdlb21fbGluZShkYXRhPWZpbHRlcihwY2ZUcmlhbCxpZCVpbiVjKDEsMiwzKSksIGFlcyhsdHk9YXMuZmFjdG9yKGlkKSksY29sPSdibGFjaycpICsKICBnZW9tX2xpbmUoc3RhdD0nc3VtbWFyeScsZnVuLnk9J21lYW4nKSArCiAgZmFjZXRfZ3JpZCgufnBwKSArCiAgY29vcmRfY2FydGVzaWFuKHlsaW09YygwLDMpKSArCiAgbGFicyh4PSdEaXN0YW5jZSByIFvCsF0nLHk9J1BhaXIgY29ycmVsYXRpb24gZnVuY3Rpb24gZyhyKScsY29sb3VyPSdQb2ludCBQcm9jZXNzJyxsdHk9J1BhcnRpY2lwYW50JykgKwogIHRoZW1lX2J3KGJhc2Vfc2l6ZT0xNikKYGBgCgpUaGUgc2FtZSBwcm9jZWR1cmUgY2FuIGJlIHJlcGVhdGVkIGZvciBlYWNoIGltYWdlLiBUaGUgbmV4dCBGaWd1cmUgc2hvd3MgdGhlIFBDRnMgb2YgZWFjaCBpbWFnZSAoZ3JheSBsaW5lcykgYXMgd2VsbCBhcyB0aGUgYXZlcmFnZSBhY3Jvc3MgYWxsIGltYWdlcyAoY29sb3JlZCBsaW5lcykuIFdoaWxlIGluaG9tb2dlbmVvdXMgYW5kIGhvbW9nZW5lb3VzIHBvaW50IHByb2Nlc3NlcyByZXZlYWwgbm8gc3BhdGlhbCBjb3JyZWxhdGlvbnMsIGVtcGlyaWNhbCBQQ0ZzIHNob3cgc3BhdGlhbCBhZ2dyZWdhdGlvbiBhdCBzaG9ydCBkaXN0YW5jZXMsICRyIDwgNCTCsCBpbiBhbGwgY29uZGl0aW9ucy4gQSBkZXRhaWxlZCBkaXNjdXNzaW9uIG9mIHRoZSByZXN1bHRzIGNhbiBiZSBmb3VuZCBpbiB0aGUgbWFudXNjcmlwdC4KCmBgYHtyIGFsbFBjZnMsIGVjaG89VFJVRSwgZmlnLndpZHRoID0gMTIsIGZpZy5oZWlnaHQ9OH0KZ2dwbG90KG1lYW5QY2ZJbWcsYWVzKHg9cix5PXBjZixjb2w9cHApKSArCiAgZ2VvbV9saW5lKGFlcyhncm91cD1pbWcpLGNvbD0nZ3JheTcwJykgKwogIGdlb21fbGluZShkYXRhPW1lYW5QY2YpICsKIyAgZ2VvbV9saW5lKGRhdGE9cGNmVHJpYWwsY29sPSdibGFjaycsc3RhdD0nc3VtbWFyeScsZnVuLnk9J21lYW4nKSArCiAgZmFjZXRfZ3JpZCh0eXBlK3ByZXN+cHApICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbT1jKDAuNSwyKSkgKwogIGxhYnMoeD0nRGlzdGFuY2UgciBbwrBdJyx5PSdQYWlyIGNvcnJlbGF0aW9uIGZ1bmN0aW9uIGcociknLGNvbG91cj0nUG9pbnQgUHJvY2VzcycpICsKICB0aGVtZV9idyhiYXNlX3NpemU9MTYpCgpgYGAKCiMjIyMgUmVmZXJlbmNlcwo=